diff --git a/app/components/equipment/EquipmentTable.vue b/app/components/equipment/EquipmentTable.vue index ac91e5b..57d8f86 100644 --- a/app/components/equipment/EquipmentTable.vue +++ b/app/components/equipment/EquipmentTable.vue @@ -65,7 +65,7 @@ import EmptyContent from './EmptyContent.vue'; export interface EquipmentItem { - readonly id: string; + readonly id: number; readonly name: string; readonly weight: number; } diff --git a/app/composables/use-checklist-store.ts b/app/composables/use-checklist-store.ts index 3a32f24..2f607aa 100644 --- a/app/composables/use-checklist-store.ts +++ b/app/composables/use-checklist-store.ts @@ -3,7 +3,7 @@ import type { ChecklistItemModel } from "~/models/checklist"; export function useChecklistStore() { const items = useState('checklistItems', () => []) - async function removeItem(checklistId: string, itemId: string) { + async function removeItem(checklistId: string, itemId: number) { try { const deletedItem = await $fetch(`/api/checklists/${checklistId}/items/${itemId}`, { method: 'DELETE' @@ -15,7 +15,7 @@ export function useChecklistStore() { } } - async function addItem(checklistId: string, equipmentId: string) { + async function addItem(checklistId: string, equipmentId: number) { try { const insertedItem = await $fetch(`/api/checklists/${checklistId}/items`, { method: 'POST', diff --git a/app/composables/use-equipment-search.ts b/app/composables/use-equipment-search.ts index 4a00934..34daa9e 100644 --- a/app/composables/use-equipment-search.ts +++ b/app/composables/use-equipment-search.ts @@ -2,7 +2,7 @@ import { useDebounceFn } from '@vueuse/core'; import type { EquipmentItem } from '~/components/equipment/EquipmentTable.vue'; interface EquipmentData { - readonly id: string; + readonly id: number; readonly name: string | null; readonly weight: number; readonly createdAt: string; diff --git a/app/composables/use-user-equipment.ts b/app/composables/use-user-equipment.ts index 91f0a90..4d1a4f1 100644 --- a/app/composables/use-user-equipment.ts +++ b/app/composables/use-user-equipment.ts @@ -1,5 +1,5 @@ interface EquipmentItem { - readonly id: string; + readonly id: number; readonly name: string; readonly weight: number; readonly createdAt: string; diff --git a/app/models/checklist.ts b/app/models/checklist.ts index 44f5163..30e4bb7 100644 --- a/app/models/checklist.ts +++ b/app/models/checklist.ts @@ -1,10 +1,10 @@ interface ChecklistEquipment { - readonly id: string; + readonly id: number; readonly name: string; readonly weight: number; } export interface ChecklistItemModel { - readonly id: string; + readonly id: number; readonly equipment: ChecklistEquipment; } diff --git a/app/pages/checklists/[checklistId].vue b/app/pages/checklists/[checklistId].vue index bece523..c7346d9 100644 --- a/app/pages/checklists/[checklistId].vue +++ b/app/pages/checklists/[checklistId].vue @@ -87,7 +87,7 @@ }) interface InventoryItem { - readonly id: string; + readonly id: number; readonly name: string; } @@ -166,7 +166,7 @@ } } - async function handleOptionClick(equipmentId: string) { + async function handleOptionClick(equipmentId: number) { await addItem(checklistId, equipmentId) options.value = options.value.filter(({ id }) => id !== equipmentId) diff --git a/app/pages/inventory.vue b/app/pages/inventory.vue index d3c845b..592c8b2 100644 --- a/app/pages/inventory.vue +++ b/app/pages/inventory.vue @@ -29,7 +29,7 @@ import PerdSearch from '~/components/PerdSearch/PerdSearch.vue'; interface EquipmentItem { - readonly id: string; + readonly id: number; readonly name: string; readonly weight: number; readonly createdAt: string; diff --git a/constants.ts b/constants.ts index 8cd4eb8..3499ef6 100644 --- a/constants.ts +++ b/constants.ts @@ -8,7 +8,12 @@ export const publicApiPaths = [ ] export const limits = { - maxUserNameLength: 32, - maxEquipmentItemNameLength: 64, maxChecklistNameLength: 32, + maxEquipmentItemNameLength: 64, + maxEquipmentTypeNameLength: 32, + maxEquipmentGroupNameLength: 32, + maxEquipmentAttributeNameLength: 32, + maxOAuthProviderTypeLength: 32, + maxOAuthProviderNameLength: 32, + maxUserNameLength: 32 } diff --git a/drizzle.config.ts b/drizzle.config.ts index 61e7207..1b11b91 100644 --- a/drizzle.config.ts +++ b/drizzle.config.ts @@ -7,9 +7,11 @@ if (process.env.DATABASE_URL === undefined) { export default defineConfig({ dialect: 'postgresql', schema: './server/database/schema.ts', - out: './server/database/migrations', + out: './server/database/migrations' - dbCredentials: { - url: process.env.DATABASE_URL - } + // TODO: drizzle-kit doesn't support different drivers at the same time + // https://orm.drizzle.team/kit-docs/upgrade-21#:~:text=For%20postgresql%20dialect%2C%20Drizzle%20will%3A + // dbCredentials: { + // url: process.env.DATABASE_URL + // } }) diff --git a/nuxt.config.ts b/nuxt.config.ts index 370227c..1d81d7e 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -10,7 +10,8 @@ export default defineNuxtConfig({ }, typescript: { - strict: true + strict: true, + typeCheck: true }, app: { diff --git a/package-lock.json b/package-lock.json index 9ea4884..74d5fec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,9 +24,11 @@ "consola": "3.2.3", "drizzle-kit": "0.24.2", "sass": "1.79.2", + "tsx": "4.19.1", "typescript": "5.6.2", "ufo": "1.5.4", "valibot": "0.42.0", + "vue-tsc": "2.1.6", "wrangler": "3.78.6", "ws": "8.18.0" } @@ -3306,6 +3308,35 @@ "vue": "^3.0.0" } }, + "node_modules/@volar/language-core": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.5.tgz", + "integrity": "sha512-F4tA0DCO5Q1F5mScHmca0umsi2ufKULAnMOVBfMsZdT4myhVl4WdKRwCaKcfOkIEuyrAVvtq1ESBdZ+rSyLVww==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@volar/source-map": "2.4.5" + } + }, + "node_modules/@volar/source-map": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.5.tgz", + "integrity": "sha512-varwD7RaKE2J/Z+Zu6j3mNNJbNT394qIxXwdvz/4ao/vxOfyClZpSDtLKkwWmecinkOVos5+PWkWraelfMLfpw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@volar/typescript": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.5.tgz", + "integrity": "sha512-mcT1mHvLljAEtHviVcBuOyAwwMKz1ibXTi5uYtP/pf4XxoAzpdkQ+Br2IC0NPCvLCbjPZmbf3I0udndkfB1CDg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.5", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" + } + }, "node_modules/@vue-macros/common": { "version": "1.14.0", "resolved": "https://registry.npmjs.org/@vue-macros/common/-/common-1.14.0.tgz", @@ -3441,6 +3472,17 @@ "@vue/shared": "3.5.6" } }, + "node_modules/@vue/compiler-vue2": { + "version": "2.7.16", + "resolved": "https://registry.npmjs.org/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz", + "integrity": "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "de-indent": "^1.0.2", + "he": "^1.2.0" + } + }, "node_modules/@vue/devtools-api": { "version": "6.6.4", "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", @@ -3506,6 +3548,47 @@ "rfdc": "^1.4.1" } }, + "node_modules/@vue/language-core": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.1.6.tgz", + "integrity": "sha512-MW569cSky9R/ooKMh6xa2g1D0AtRKbL56k83dzus/bx//RDJk24RHWkMzbAlXjMdDNyxAaagKPRquBIxkxlCkg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "~2.4.1", + "@vue/compiler-dom": "^3.4.0", + "@vue/compiler-vue2": "^2.7.16", + "@vue/shared": "^3.4.0", + "computeds": "^0.0.1", + "minimatch": "^9.0.3", + "muggle-string": "^0.4.1", + "path-browserify": "^1.0.1" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vue/language-core/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@vue/reactivity": { "version": "3.5.6", "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.6.tgz", @@ -4577,6 +4660,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/computeds": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/computeds/-/computeds-0.0.1.tgz", + "integrity": "sha512-7CEBgcMjVmitjYo5q8JTJVra6X5mQ20uTThdK+0kR7UEaDrAWEQcRiBtWJzga4eRpP6afNwwLsX2SET2JhVB1Q==", + "devOptional": true, + "license": "MIT" + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -4928,6 +5018,13 @@ "url": "https://github.com/sponsors/kossnocorp" } }, + "node_modules/de-indent": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", + "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", + "devOptional": true, + "license": "MIT" + }, "node_modules/debug": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", @@ -6476,6 +6573,16 @@ "node": ">= 0.4" } }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "devOptional": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, "node_modules/hookable": { "version": "5.5.3", "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", @@ -7428,6 +7535,13 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "devOptional": true, + "license": "MIT" + }, "node_modules/mustache": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", @@ -8601,6 +8715,13 @@ "node": ">= 0.8" } }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "devOptional": true, + "license": "MIT" + }, "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -10523,6 +10644,26 @@ "dev": true, "license": "0BSD" }, + "node_modules/tsx": { + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.1.tgz", + "integrity": "sha512-0flMz1lh74BR4wOvBjuh9olbnwqCPc35OOlfyzHba0Dc+QNUeWX/Gq2YTbnwcWPO3BMd8fkzRVrHcsR+a7z7rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.23.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, "node_modules/type-fest": { "version": "3.13.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz", @@ -11795,6 +11936,24 @@ "vue": "^3.2.0" } }, + "node_modules/vue-tsc": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.1.6.tgz", + "integrity": "sha512-f98dyZp5FOukcYmbFpuSCJ4Z0vHSOSmxGttZJCsFeX0M4w/Rsq0s4uKXjcSRsZqsRgQa6z7SfuO+y0HVICE57Q==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@volar/typescript": "~2.4.1", + "@vue/language-core": "2.1.6", + "semver": "^7.5.4" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + } + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", diff --git a/package.json b/package.json index 3165c65..b46622c 100644 --- a/package.json +++ b/package.json @@ -9,9 +9,11 @@ "generate": "nuxt generate", "postinstall": "nuxt prepare", "preview": "wrangler pages dev", - "db:generate": "npx drizzle-kit generate", - "db:migrate": "drizzle-kit migrate", - "db:studio": "npx drizzle-kit studio" + "db:generate": "drizzle-kit generate", + "db:migrate": "tsx ./tools/migrate.ts", + "db:migrate:local": "tsx --env-file=.env ./tools/migrate.ts", + "db:studio": "drizzle-kit studio", + "typecheck": "nuxi typecheck" }, "dependencies": { "date-fns": "4.1.0", @@ -31,9 +33,11 @@ "consola": "3.2.3", "drizzle-kit": "0.24.2", "sass": "1.79.2", + "tsx": "4.19.1", "typescript": "5.6.2", "ufo": "1.5.4", "valibot": "0.42.0", + "vue-tsc": "2.1.6", "wrangler": "3.78.6", "ws": "8.18.0" } diff --git a/server/api/admin/create-equipment.post.ts b/server/api/admin/create-equipment.post.ts index 2db9b5d..ac057e1 100644 --- a/server/api/admin/create-equipment.post.ts +++ b/server/api/admin/create-equipment.post.ts @@ -23,7 +23,7 @@ export default defineEventHandler(async (event) => { }) } - const [{ itemId }] = await db + const [foundItem] = await db .insert(tables.equipment) .values({ name, @@ -33,9 +33,16 @@ export default defineEventHandler(async (event) => { itemId: tables.equipment.id }) + if (foundItem === undefined) { + throw createError({ + statusCode: 500, + message: 'Failed to create equipment' + }) + } + setResponseStatus(event, 201) return { - itemId + itemId: foundItem.itemId } }) diff --git a/server/api/auth/create-session.post.ts b/server/api/auth/create-session.post.ts index 4fad0c1..93bff4a 100644 --- a/server/api/auth/create-session.post.ts +++ b/server/api/auth/create-session.post.ts @@ -3,7 +3,7 @@ export default defineEventHandler(async (event) => { // TODO (#101): check if user is already logged in - const [{ userId }] = await db + const [foundUser] = await db .insert(tables.users) .values({ isAdmin: false @@ -13,15 +13,22 @@ export default defineEventHandler(async (event) => { isAdmin: tables.users.isAdmin }) + if (foundUser === undefined) { + throw createError({ + statusCode: 500, + message: 'Failed to create user' + }) + } + const session = await useAppSession(event) setResponseStatus(event, 201) await session.update({ - userId + userId: foundUser.userId }) return { - userId + userId: foundUser.userId } }) diff --git a/server/api/checklists/[checklistId]/index.delete.ts b/server/api/checklists/[checklistId]/index.delete.ts index 3475715..486b80e 100644 --- a/server/api/checklists/[checklistId]/index.delete.ts +++ b/server/api/checklists/[checklistId]/index.delete.ts @@ -1,10 +1,9 @@ import { and, eq } from 'drizzle-orm' -import { validateId } from '~~/server/utils/validate' export default defineEventHandler(async (event) => { const userId = await validateSessionUser(event) const checklistIdParam = getRouterParam(event, 'checklistId') - const checklistId = validateId(checklistIdParam) + const checklistId = validateIdString(checklistIdParam) const deleted = await event.context.db .delete(tables.checklists) diff --git a/server/api/checklists/[checklistId]/index.get.ts b/server/api/checklists/[checklistId]/index.get.ts index 8fd4151..9459f66 100644 --- a/server/api/checklists/[checklistId]/index.get.ts +++ b/server/api/checklists/[checklistId]/index.get.ts @@ -1,5 +1,4 @@ import { and, eq } from 'drizzle-orm' -import { validateId } from '~~/server/utils/validate'; interface ReturnData { readonly id: string; @@ -9,7 +8,7 @@ interface ReturnData { export default defineEventHandler(async (event) : Promise => { const userId = await validateSessionUser(event) const checklistIdParam = getRouterParam(event, 'checklistId') - const checklistId = validateId(checklistIdParam) + const checklistId = validateIdString(checklistIdParam) const result = await event.context.db.query.checklists .findFirst({ diff --git a/server/api/checklists/[checklistId]/items/[itemId].delete.ts b/server/api/checklists/[checklistId]/items/[itemId].delete.ts index 80733f0..99ee905 100644 --- a/server/api/checklists/[checklistId]/items/[itemId].delete.ts +++ b/server/api/checklists/[checklistId]/items/[itemId].delete.ts @@ -2,8 +2,8 @@ import { and, eq, exists } from 'drizzle-orm' import * as v from 'valibot' const paramsSchema = v.object({ - checklistId: idValidator, - itemId: idValidator + checklistId: idValidatorString, + itemId: stringToIntegerValidator }) type ParamsData = v.InferOutput diff --git a/server/api/checklists/[checklistId]/items/index.get.ts b/server/api/checklists/[checklistId]/items/index.get.ts index 23ca862..6d1909a 100644 --- a/server/api/checklists/[checklistId]/items/index.get.ts +++ b/server/api/checklists/[checklistId]/items/index.get.ts @@ -3,7 +3,7 @@ import { and, asc, eq } from 'drizzle-orm' export default defineEventHandler(async (event) => { const userId = await validateSessionUser(event) const checklistIdParam = getRouterParam(event, 'checklistId') - const checklistId = validateId(checklistIdParam) + const checklistId = validateIdString(checklistIdParam) const result = await event.context.db .select({ diff --git a/server/api/checklists/[checklistId]/items/index.post.ts b/server/api/checklists/[checklistId]/items/index.post.ts index 00e30ab..f42e11e 100644 --- a/server/api/checklists/[checklistId]/items/index.post.ts +++ b/server/api/checklists/[checklistId]/items/index.post.ts @@ -2,7 +2,7 @@ import { eq } from 'drizzle-orm' import * as v from 'valibot' const bodySchema = v.object({ - equipmentId: idValidator + equipmentId: idValidatorNumber }) type BodyData = v.InferOutput @@ -17,11 +17,11 @@ export default defineEventHandler(async (event) => { await validateSessionUser(event) const checklistIdParam = getRouterParam(event, 'checklistId') - const checklistId = validateId(checklistIdParam) + const checklistId = validateIdString(checklistIdParam) const { equipmentId } = await readValidatedBody(event, validateBody) try { - const [{ itemId }] = await db + const [foundItem] = await db .insert(tables.checklistItems) .values({ checklistId, @@ -31,8 +31,15 @@ export default defineEventHandler(async (event) => { itemId: tables.checklistItems.id }) + if (foundItem === undefined) { + throw createError({ + statusCode: 500, + message: 'Failed to create checklist item' + }) + } + const insertedItem = await db.query.checklistItems.findFirst({ - where: eq(tables.checklistItems.id, itemId), + where: eq(tables.checklistItems.id, foundItem.itemId), columns: { id: true diff --git a/server/api/equipment/index.get.ts b/server/api/equipment/index.get.ts index 855d7eb..0bc2f09 100644 --- a/server/api/equipment/index.get.ts +++ b/server/api/equipment/index.get.ts @@ -1,7 +1,7 @@ import { and, asc, eq, isNull, like } from 'drizzle-orm' interface ReturnData { - readonly id: string; + readonly id: number; readonly name: string; readonly weight: number; readonly createdAt: Date; diff --git a/server/api/equipment/index.post.ts b/server/api/equipment/index.post.ts index cc3aa59..e46327c 100644 --- a/server/api/equipment/index.post.ts +++ b/server/api/equipment/index.post.ts @@ -1,7 +1,7 @@ import * as v from 'valibot' const bodySchema = v.object({ - equipmentId: v.pipe(v.string(), v.nonEmpty()), + equipmentId: idValidatorNumber }) type BodyData = v.InferOutput diff --git a/server/api/inventory/index.get.ts b/server/api/inventory/index.get.ts index f96c2da..7b4f561 100644 --- a/server/api/inventory/index.get.ts +++ b/server/api/inventory/index.get.ts @@ -1,7 +1,7 @@ import { and, asc, eq, isNull, like } from 'drizzle-orm' interface ReturnData { - readonly id: string; + readonly id: number; readonly name: string; } diff --git a/server/api/user/equipment/[id].delete.ts b/server/api/user/equipment/[id].delete.ts index 1ef276b..059b29b 100644 --- a/server/api/user/equipment/[id].delete.ts +++ b/server/api/user/equipment/[id].delete.ts @@ -1,10 +1,9 @@ import { and, eq } from 'drizzle-orm' -import { validateId } from '~~/server/utils/validate' export default defineEventHandler(async (event) => { const userId = await validateSessionUser(event) const id = getRouterParam(event, 'id') - const equipmentId = validateId(id) + const equipmentId = validateStringToInteger(id) await event.context.db .delete(tables.userEquipment) diff --git a/server/database/migrations/0000_dry_wasp.sql b/server/database/migrations/0000_dry_wasp.sql deleted file mode 100644 index ecbdb7c..0000000 --- a/server/database/migrations/0000_dry_wasp.sql +++ /dev/null @@ -1,68 +0,0 @@ -CREATE EXTENSION IF NOT EXISTS ulid; - -CREATE TABLE IF NOT EXISTS "checklistItems" ( - "id" "ulid" PRIMARY KEY DEFAULT gen_monotonic_ulid() NOT NULL, - "checklistId" "ulid" NOT NULL, - "equipmentId" "ulid" NOT NULL, - CONSTRAINT "checklistItems_checklistId_equipmentId_unique" UNIQUE("checklistId","equipmentId") -); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS "checklists" ( - "id" "ulid" PRIMARY KEY DEFAULT gen_monotonic_ulid() NOT NULL, - "userId" "ulid" NOT NULL, - "name" varchar(32) NOT NULL, - "createdAt" timestamp with time zone DEFAULT now() NOT NULL -); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS "equipment" ( - "id" "ulid" PRIMARY KEY DEFAULT gen_monotonic_ulid() NOT NULL, - "name" varchar(64) NOT NULL, - "weight" integer NOT NULL, - "createdAt" timestamp with time zone DEFAULT now() NOT NULL -); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS "userEquipment" ( - "userId" "ulid" NOT NULL, - "equipmentId" "ulid" NOT NULL, - "createdAt" timestamp with time zone DEFAULT now() NOT NULL, - CONSTRAINT "userEquipment_userId_equipmentId_pk" PRIMARY KEY("userId","equipmentId") -); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS "users" ( - "id" "ulid" PRIMARY KEY DEFAULT gen_monotonic_ulid() NOT NULL, - "name" varchar(32), - "createdAt" timestamp with time zone DEFAULT now() NOT NULL, - "isAdmin" boolean DEFAULT false NOT NULL -); ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "checklistItems" ADD CONSTRAINT "checklistItems_checklistId_checklists_id_fk" FOREIGN KEY ("checklistId") REFERENCES "public"."checklists"("id") ON DELETE cascade ON UPDATE cascade; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "checklistItems" ADD CONSTRAINT "checklistItems_equipmentId_equipment_id_fk" FOREIGN KEY ("equipmentId") REFERENCES "public"."equipment"("id") ON DELETE cascade ON UPDATE cascade; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "checklists" ADD CONSTRAINT "checklists_userId_users_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE cascade; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "userEquipment" ADD CONSTRAINT "userEquipment_userId_users_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE cascade; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "userEquipment" ADD CONSTRAINT "userEquipment_equipmentId_equipment_id_fk" FOREIGN KEY ("equipmentId") REFERENCES "public"."equipment"("id") ON DELETE cascade ON UPDATE cascade; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -CREATE INDEX IF NOT EXISTS "createdAtIndex" ON "checklists" USING btree ("createdAt"); diff --git a/server/database/migrations/0000_furry_mystique.sql b/server/database/migrations/0000_furry_mystique.sql new file mode 100644 index 0000000..89c94f6 --- /dev/null +++ b/server/database/migrations/0000_furry_mystique.sql @@ -0,0 +1,177 @@ +DO $$ BEGIN + CREATE TYPE "public"."equipmentAttributeDataType" AS ENUM('boolean', 'string', 'integer', 'decimal'); +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "checklistItems" ( + "id" serial PRIMARY KEY NOT NULL, + "checklistId" "ulid" NOT NULL, + "equipmentId" integer NOT NULL, + CONSTRAINT "checklistItems_checklistId_equipmentId_unique" UNIQUE("checklistId","equipmentId") +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "checklists" ( + "id" "ulid" PRIMARY KEY DEFAULT gen_ulid() NOT NULL, + "userId" "ulid" NOT NULL, + "name" varchar(32) NOT NULL, + "createdAt" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "equipment" ( + "id" serial PRIMARY KEY NOT NULL, + "name" varchar(64) NOT NULL, + "description" text, + "weight" integer DEFAULT 0 NOT NULL, + "equipmentTypeId" integer, + "equipmentGroupId" integer, + "createdAt" timestamp with time zone DEFAULT now() NOT NULL, + "updatedAt" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "equipmentAttributeValues" ( + "id" serial PRIMARY KEY NOT NULL, + "equipmentId" integer NOT NULL, + "equipmentAttributeId" integer NOT NULL, + "value" varchar NOT NULL, + CONSTRAINT "equipmentAttributeValues_equipmentId_equipmentAttributeId_unique" UNIQUE("equipmentId","equipmentAttributeId") +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "equipmentAttributes" ( + "id" serial PRIMARY KEY NOT NULL, + "name" varchar(32) NOT NULL, + "dataType" "equipmentAttributeDataType" NOT NULL +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "equipmentGroups" ( + "id" serial PRIMARY KEY NOT NULL, + "name" varchar(32) NOT NULL +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "equipmentTypeAttributes" ( + "equipmentTypeId" integer, + "equipmentAttributeId" integer, + CONSTRAINT "equipmentTypeAttributes_equipmentTypeId_equipmentAttributeId_pk" PRIMARY KEY("equipmentTypeId","equipmentAttributeId") +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "equipmentTypes" ( + "id" serial PRIMARY KEY NOT NULL, + "name" varchar(32) NOT NULL +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "oauthAccounts" ( + "id" "ulid" PRIMARY KEY DEFAULT gen_ulid() NOT NULL, + "userId" "ulid" NOT NULL, + "providerId" integer NOT NULL, + "accountId" varchar NOT NULL, + "createdAt" timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT "oauthAccounts_providerId_accountId_unique" UNIQUE("providerId","accountId") +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "oauthProviders" ( + "id" serial PRIMARY KEY NOT NULL, + "type" varchar(32) NOT NULL, + "name" varchar(32) NOT NULL, + "createdAt" timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT "oauthProviders_type_unique" UNIQUE("type") +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "userEquipment" ( + "userId" "ulid", + "equipmentId" integer, + "createdAt" timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT "userEquipment_userId_equipmentId_pk" PRIMARY KEY("userId","equipmentId") +); +--> statement-breakpoint +CREATE TABLE IF NOT EXISTS "users" ( + "id" "ulid" PRIMARY KEY DEFAULT gen_ulid() NOT NULL, + "name" varchar(32), + "createdAt" timestamp with time zone DEFAULT now() NOT NULL, + "isAdmin" boolean DEFAULT false NOT NULL +); +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "checklistItems" ADD CONSTRAINT "checklistItems_checklistId_checklists_id_fk" FOREIGN KEY ("checklistId") REFERENCES "public"."checklists"("id") ON DELETE cascade ON UPDATE cascade; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "checklistItems" ADD CONSTRAINT "checklistItems_equipmentId_equipment_id_fk" FOREIGN KEY ("equipmentId") REFERENCES "public"."equipment"("id") ON DELETE cascade ON UPDATE cascade; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "checklists" ADD CONSTRAINT "checklists_userId_users_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE cascade; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "equipment" ADD CONSTRAINT "equipment_equipmentTypeId_equipmentTypes_id_fk" FOREIGN KEY ("equipmentTypeId") REFERENCES "public"."equipmentTypes"("id") ON DELETE set null ON UPDATE cascade; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "equipment" ADD CONSTRAINT "equipment_equipmentGroupId_equipmentGroups_id_fk" FOREIGN KEY ("equipmentGroupId") REFERENCES "public"."equipmentGroups"("id") ON DELETE set null ON UPDATE cascade; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "equipmentAttributeValues" ADD CONSTRAINT "equipmentAttributeValues_equipmentId_equipment_id_fk" FOREIGN KEY ("equipmentId") REFERENCES "public"."equipment"("id") ON DELETE cascade ON UPDATE cascade; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "equipmentAttributeValues" ADD CONSTRAINT "equipmentAttributeValues_equipmentAttributeId_equipmentAttributes_id_fk" FOREIGN KEY ("equipmentAttributeId") REFERENCES "public"."equipmentAttributes"("id") ON DELETE cascade ON UPDATE cascade; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "equipmentTypeAttributes" ADD CONSTRAINT "equipmentTypeAttributes_equipmentTypeId_equipmentTypes_id_fk" FOREIGN KEY ("equipmentTypeId") REFERENCES "public"."equipmentTypes"("id") ON DELETE cascade ON UPDATE cascade; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "equipmentTypeAttributes" ADD CONSTRAINT "equipmentTypeAttributes_equipmentAttributeId_equipmentAttributes_id_fk" FOREIGN KEY ("equipmentAttributeId") REFERENCES "public"."equipmentAttributes"("id") ON DELETE cascade ON UPDATE cascade; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "oauthAccounts" ADD CONSTRAINT "oauthAccounts_userId_users_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE cascade; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "oauthAccounts" ADD CONSTRAINT "oauthAccounts_providerId_oauthProviders_id_fk" FOREIGN KEY ("providerId") REFERENCES "public"."oauthProviders"("id") ON DELETE cascade ON UPDATE cascade; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "userEquipment" ADD CONSTRAINT "userEquipment_userId_users_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE cascade; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "userEquipment" ADD CONSTRAINT "userEquipment_equipmentId_equipment_id_fk" FOREIGN KEY ("equipmentId") REFERENCES "public"."equipment"("id") ON DELETE cascade ON UPDATE cascade; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "createdAtIndex" ON "checklists" USING btree ("createdAt");--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "equipment_equipmentTypeId_index" ON "equipment" USING btree ("equipmentTypeId");--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "equipment_equipmentGroupId_index" ON "equipment" USING btree ("equipmentGroupId");--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "equipmentAttributeValues_equipmentId_index" ON "equipmentAttributeValues" USING btree ("equipmentId");--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "equipmentAttributeValues_equipmentAttributeId_index" ON "equipmentAttributeValues" USING btree ("equipmentAttributeId");--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "equipmentTypeAttributes_equipmentTypeId_index" ON "equipmentTypeAttributes" USING btree ("equipmentTypeId");--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "equipmentTypeAttributes_equipmentAttributeId_index" ON "equipmentTypeAttributes" USING btree ("equipmentAttributeId"); \ No newline at end of file diff --git a/server/database/migrations/0001_magenta_mordo.sql b/server/database/migrations/0001_magenta_mordo.sql deleted file mode 100644 index deba28d..0000000 --- a/server/database/migrations/0001_magenta_mordo.sql +++ /dev/null @@ -1,4 +0,0 @@ -ALTER TABLE "checklistItems" ALTER COLUMN "id" SET DEFAULT gen_ulid();--> statement-breakpoint -ALTER TABLE "checklists" ALTER COLUMN "id" SET DEFAULT gen_ulid();--> statement-breakpoint -ALTER TABLE "equipment" ALTER COLUMN "id" SET DEFAULT gen_ulid();--> statement-breakpoint -ALTER TABLE "users" ALTER COLUMN "id" SET DEFAULT gen_ulid(); \ No newline at end of file diff --git a/server/database/migrations/0002_thin_jimmy_woo.sql b/server/database/migrations/0002_thin_jimmy_woo.sql deleted file mode 100644 index 38e3fd4..0000000 --- a/server/database/migrations/0002_thin_jimmy_woo.sql +++ /dev/null @@ -1,28 +0,0 @@ -CREATE TABLE IF NOT EXISTS "oauthAccounts" ( - "id" "ulid" PRIMARY KEY DEFAULT gen_ulid() NOT NULL, - "userId" "ulid" NOT NULL, - "providerId" "ulid" NOT NULL, - "accountId" varchar NOT NULL, - "createdAt" timestamp with time zone DEFAULT now() NOT NULL, - CONSTRAINT "oauthAccounts_providerId_accountId_unique" UNIQUE("providerId","accountId") -); ---> statement-breakpoint -CREATE TABLE IF NOT EXISTS "oauthProviders" ( - "id" "ulid" PRIMARY KEY DEFAULT gen_ulid() NOT NULL, - "type" varchar(32) NOT NULL, - "name" varchar(255) NOT NULL, - "createdAt" timestamp with time zone DEFAULT now() NOT NULL, - CONSTRAINT "oauthProviders_type_unique" UNIQUE("type") -); ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "oauthAccounts" ADD CONSTRAINT "oauthAccounts_userId_users_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE cascade; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; ---> statement-breakpoint -DO $$ BEGIN - ALTER TABLE "oauthAccounts" ADD CONSTRAINT "oauthAccounts_providerId_oauthProviders_id_fk" FOREIGN KEY ("providerId") REFERENCES "public"."oauthProviders"("id") ON DELETE cascade ON UPDATE cascade; -EXCEPTION - WHEN duplicate_object THEN null; -END $$; diff --git a/server/database/migrations/meta/0000_snapshot.json b/server/database/migrations/meta/0000_snapshot.json index 1e6910e..c361cda 100644 --- a/server/database/migrations/meta/0000_snapshot.json +++ b/server/database/migrations/meta/0000_snapshot.json @@ -1,5 +1,5 @@ { - "id": "ab106675-c2f9-45b3-97dd-2560bade641d", + "id": "a41b8ce5-8f4e-48d1-a599-90b1d0b8a63a", "prevId": "00000000-0000-0000-0000-000000000000", "version": "7", "dialect": "postgresql", @@ -10,10 +10,9 @@ "columns": { "id": { "name": "id", - "type": "ulid", + "type": "serial", "primaryKey": true, - "notNull": true, - "default": "gen_monotonic_ulid()" + "notNull": true }, "checklistId": { "name": "checklistId", @@ -23,7 +22,7 @@ }, "equipmentId": { "name": "equipmentId", - "type": "ulid", + "type": "integer", "primaryKey": false, "notNull": true } @@ -78,7 +77,7 @@ "type": "ulid", "primaryKey": true, "notNull": true, - "default": "gen_monotonic_ulid()" + "default": "gen_ulid()" }, "userId": { "name": "userId", @@ -141,10 +140,9 @@ "columns": { "id": { "name": "id", - "type": "ulid", + "type": "serial", "primaryKey": true, - "notNull": true, - "default": "gen_monotonic_ulid()" + "notNull": true }, "name": { "name": "name", @@ -152,11 +150,30 @@ "primaryKey": false, "notNull": true }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, "weight": { "name": "weight", "type": "integer", "primaryKey": false, - "notNull": true + "notNull": true, + "default": 0 + }, + "equipmentTypeId": { + "name": "equipmentTypeId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "equipmentGroupId": { + "name": "equipmentGroupId", + "type": "integer", + "primaryKey": false, + "notNull": false }, "createdAt": { "name": "createdAt", @@ -164,6 +181,223 @@ "primaryKey": false, "notNull": true, "default": "now()" + }, + "updatedAt": { + "name": "updatedAt", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "equipment_equipmentTypeId_index": { + "name": "equipment_equipmentTypeId_index", + "columns": [ + { + "expression": "equipmentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "equipment_equipmentGroupId_index": { + "name": "equipment_equipmentGroupId_index", + "columns": [ + { + "expression": "equipmentGroupId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "equipment_equipmentTypeId_equipmentTypes_id_fk": { + "name": "equipment_equipmentTypeId_equipmentTypes_id_fk", + "tableFrom": "equipment", + "tableTo": "equipmentTypes", + "columnsFrom": [ + "equipmentTypeId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "equipment_equipmentGroupId_equipmentGroups_id_fk": { + "name": "equipment_equipmentGroupId_equipmentGroups_id_fk", + "tableFrom": "equipment", + "tableTo": "equipmentGroups", + "columnsFrom": [ + "equipmentGroupId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.equipmentAttributeValues": { + "name": "equipmentAttributeValues", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "equipmentId": { + "name": "equipmentId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "equipmentAttributeId": { + "name": "equipmentAttributeId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "varchar", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "equipmentAttributeValues_equipmentId_index": { + "name": "equipmentAttributeValues_equipmentId_index", + "columns": [ + { + "expression": "equipmentId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "equipmentAttributeValues_equipmentAttributeId_index": { + "name": "equipmentAttributeValues_equipmentAttributeId_index", + "columns": [ + { + "expression": "equipmentAttributeId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "equipmentAttributeValues_equipmentId_equipment_id_fk": { + "name": "equipmentAttributeValues_equipmentId_equipment_id_fk", + "tableFrom": "equipmentAttributeValues", + "tableTo": "equipment", + "columnsFrom": [ + "equipmentId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "equipmentAttributeValues_equipmentAttributeId_equipmentAttributes_id_fk": { + "name": "equipmentAttributeValues_equipmentAttributeId_equipmentAttributes_id_fk", + "tableFrom": "equipmentAttributeValues", + "tableTo": "equipmentAttributes", + "columnsFrom": [ + "equipmentAttributeId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "equipmentAttributeValues_equipmentId_equipmentAttributeId_unique": { + "name": "equipmentAttributeValues_equipmentId_equipmentAttributeId_unique", + "nullsNotDistinct": false, + "columns": [ + "equipmentId", + "equipmentAttributeId" + ] + } + } + }, + "public.equipmentAttributes": { + "name": "equipmentAttributes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "dataType": { + "name": "dataType", + "type": "equipmentAttributeDataType", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.equipmentGroups": { + "name": "equipmentGroups", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true } }, "indexes": {}, @@ -171,6 +405,237 @@ "compositePrimaryKeys": {}, "uniqueConstraints": {} }, + "public.equipmentTypeAttributes": { + "name": "equipmentTypeAttributes", + "schema": "", + "columns": { + "equipmentTypeId": { + "name": "equipmentTypeId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "equipmentAttributeId": { + "name": "equipmentAttributeId", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "equipmentTypeAttributes_equipmentTypeId_index": { + "name": "equipmentTypeAttributes_equipmentTypeId_index", + "columns": [ + { + "expression": "equipmentTypeId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "equipmentTypeAttributes_equipmentAttributeId_index": { + "name": "equipmentTypeAttributes_equipmentAttributeId_index", + "columns": [ + { + "expression": "equipmentAttributeId", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "equipmentTypeAttributes_equipmentTypeId_equipmentTypes_id_fk": { + "name": "equipmentTypeAttributes_equipmentTypeId_equipmentTypes_id_fk", + "tableFrom": "equipmentTypeAttributes", + "tableTo": "equipmentTypes", + "columnsFrom": [ + "equipmentTypeId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "equipmentTypeAttributes_equipmentAttributeId_equipmentAttributes_id_fk": { + "name": "equipmentTypeAttributes_equipmentAttributeId_equipmentAttributes_id_fk", + "tableFrom": "equipmentTypeAttributes", + "tableTo": "equipmentAttributes", + "columnsFrom": [ + "equipmentAttributeId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "equipmentTypeAttributes_equipmentTypeId_equipmentAttributeId_pk": { + "name": "equipmentTypeAttributes_equipmentTypeId_equipmentAttributeId_pk", + "columns": [ + "equipmentTypeId", + "equipmentAttributeId" + ] + } + }, + "uniqueConstraints": {} + }, + "public.equipmentTypes": { + "name": "equipmentTypes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.oauthAccounts": { + "name": "oauthAccounts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "ulid", + "primaryKey": true, + "notNull": true, + "default": "gen_ulid()" + }, + "userId": { + "name": "userId", + "type": "ulid", + "primaryKey": false, + "notNull": true + }, + "providerId": { + "name": "providerId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "accountId": { + "name": "accountId", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "oauthAccounts_userId_users_id_fk": { + "name": "oauthAccounts_userId_users_id_fk", + "tableFrom": "oauthAccounts", + "tableTo": "users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "oauthAccounts_providerId_oauthProviders_id_fk": { + "name": "oauthAccounts_providerId_oauthProviders_id_fk", + "tableFrom": "oauthAccounts", + "tableTo": "oauthProviders", + "columnsFrom": [ + "providerId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauthAccounts_providerId_accountId_unique": { + "name": "oauthAccounts_providerId_accountId_unique", + "nullsNotDistinct": false, + "columns": [ + "providerId", + "accountId" + ] + } + } + }, + "public.oauthProviders": { + "name": "oauthProviders", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "type": { + "name": "type", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "createdAt": { + "name": "createdAt", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauthProviders_type_unique": { + "name": "oauthProviders_type_unique", + "nullsNotDistinct": false, + "columns": [ + "type" + ] + } + } + }, "public.userEquipment": { "name": "userEquipment", "schema": "", @@ -179,13 +644,13 @@ "name": "userId", "type": "ulid", "primaryKey": false, - "notNull": true + "notNull": false }, "equipmentId": { "name": "equipmentId", - "type": "ulid", + "type": "integer", "primaryKey": false, - "notNull": true + "notNull": false }, "createdAt": { "name": "createdAt", @@ -244,7 +709,7 @@ "type": "ulid", "primaryKey": true, "notNull": true, - "default": "gen_monotonic_ulid()" + "default": "gen_ulid()" }, "name": { "name": "name", @@ -273,7 +738,18 @@ "uniqueConstraints": {} } }, - "enums": {}, + "enums": { + "public.equipmentAttributeDataType": { + "name": "equipmentAttributeDataType", + "schema": "public", + "values": [ + "boolean", + "string", + "integer", + "decimal" + ] + } + }, "schemas": {}, "sequences": {}, "_meta": { diff --git a/server/database/migrations/meta/0001_snapshot.json b/server/database/migrations/meta/0001_snapshot.json deleted file mode 100644 index 1ffc67a..0000000 --- a/server/database/migrations/meta/0001_snapshot.json +++ /dev/null @@ -1,284 +0,0 @@ -{ - "id": "753077c6-adb2-4a1c-b4d4-34de92430719", - "prevId": "ab106675-c2f9-45b3-97dd-2560bade641d", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.checklistItems": { - "name": "checklistItems", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "ulid", - "primaryKey": true, - "notNull": true, - "default": "gen_ulid()" - }, - "checklistId": { - "name": "checklistId", - "type": "ulid", - "primaryKey": false, - "notNull": true - }, - "equipmentId": { - "name": "equipmentId", - "type": "ulid", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "checklistItems_checklistId_checklists_id_fk": { - "name": "checklistItems_checklistId_checklists_id_fk", - "tableFrom": "checklistItems", - "tableTo": "checklists", - "columnsFrom": [ - "checklistId" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "cascade" - }, - "checklistItems_equipmentId_equipment_id_fk": { - "name": "checklistItems_equipmentId_equipment_id_fk", - "tableFrom": "checklistItems", - "tableTo": "equipment", - "columnsFrom": [ - "equipmentId" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "cascade" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "checklistItems_checklistId_equipmentId_unique": { - "name": "checklistItems_checklistId_equipmentId_unique", - "nullsNotDistinct": false, - "columns": [ - "checklistId", - "equipmentId" - ] - } - } - }, - "public.checklists": { - "name": "checklists", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "ulid", - "primaryKey": true, - "notNull": true, - "default": "gen_ulid()" - }, - "userId": { - "name": "userId", - "type": "ulid", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "varchar(32)", - "primaryKey": false, - "notNull": true - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "createdAtIndex": { - "name": "createdAtIndex", - "columns": [ - { - "expression": "createdAt", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "checklists_userId_users_id_fk": { - "name": "checklists_userId_users_id_fk", - "tableFrom": "checklists", - "tableTo": "users", - "columnsFrom": [ - "userId" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "cascade" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "public.equipment": { - "name": "equipment", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "ulid", - "primaryKey": true, - "notNull": true, - "default": "gen_ulid()" - }, - "name": { - "name": "name", - "type": "varchar(64)", - "primaryKey": false, - "notNull": true - }, - "weight": { - "name": "weight", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "public.userEquipment": { - "name": "userEquipment", - "schema": "", - "columns": { - "userId": { - "name": "userId", - "type": "ulid", - "primaryKey": false, - "notNull": true - }, - "equipmentId": { - "name": "equipmentId", - "type": "ulid", - "primaryKey": false, - "notNull": true - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "userEquipment_userId_users_id_fk": { - "name": "userEquipment_userId_users_id_fk", - "tableFrom": "userEquipment", - "tableTo": "users", - "columnsFrom": [ - "userId" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "cascade" - }, - "userEquipment_equipmentId_equipment_id_fk": { - "name": "userEquipment_equipmentId_equipment_id_fk", - "tableFrom": "userEquipment", - "tableTo": "equipment", - "columnsFrom": [ - "equipmentId" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "cascade" - } - }, - "compositePrimaryKeys": { - "userEquipment_userId_equipmentId_pk": { - "name": "userEquipment_userId_equipmentId_pk", - "columns": [ - "userId", - "equipmentId" - ] - } - }, - "uniqueConstraints": {} - }, - "public.users": { - "name": "users", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "ulid", - "primaryKey": true, - "notNull": true, - "default": "gen_ulid()" - }, - "name": { - "name": "name", - "type": "varchar(32)", - "primaryKey": false, - "notNull": false - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "isAdmin": { - "name": "isAdmin", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - } - }, - "enums": {}, - "schemas": {}, - "sequences": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} \ No newline at end of file diff --git a/server/database/migrations/meta/0002_snapshot.json b/server/database/migrations/meta/0002_snapshot.json deleted file mode 100644 index 15ff34e..0000000 --- a/server/database/migrations/meta/0002_snapshot.json +++ /dev/null @@ -1,406 +0,0 @@ -{ - "id": "9e2102ae-52eb-4dff-b112-83e4759eded4", - "prevId": "753077c6-adb2-4a1c-b4d4-34de92430719", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.checklistItems": { - "name": "checklistItems", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "ulid", - "primaryKey": true, - "notNull": true, - "default": "gen_ulid()" - }, - "checklistId": { - "name": "checklistId", - "type": "ulid", - "primaryKey": false, - "notNull": true - }, - "equipmentId": { - "name": "equipmentId", - "type": "ulid", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "checklistItems_checklistId_checklists_id_fk": { - "name": "checklistItems_checklistId_checklists_id_fk", - "tableFrom": "checklistItems", - "tableTo": "checklists", - "columnsFrom": [ - "checklistId" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "cascade" - }, - "checklistItems_equipmentId_equipment_id_fk": { - "name": "checklistItems_equipmentId_equipment_id_fk", - "tableFrom": "checklistItems", - "tableTo": "equipment", - "columnsFrom": [ - "equipmentId" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "cascade" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "checklistItems_checklistId_equipmentId_unique": { - "name": "checklistItems_checklistId_equipmentId_unique", - "nullsNotDistinct": false, - "columns": [ - "checklistId", - "equipmentId" - ] - } - } - }, - "public.checklists": { - "name": "checklists", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "ulid", - "primaryKey": true, - "notNull": true, - "default": "gen_ulid()" - }, - "userId": { - "name": "userId", - "type": "ulid", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "varchar(32)", - "primaryKey": false, - "notNull": true - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "createdAtIndex": { - "name": "createdAtIndex", - "columns": [ - { - "expression": "createdAt", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "checklists_userId_users_id_fk": { - "name": "checklists_userId_users_id_fk", - "tableFrom": "checklists", - "tableTo": "users", - "columnsFrom": [ - "userId" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "cascade" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "public.equipment": { - "name": "equipment", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "ulid", - "primaryKey": true, - "notNull": true, - "default": "gen_ulid()" - }, - "name": { - "name": "name", - "type": "varchar(64)", - "primaryKey": false, - "notNull": true - }, - "weight": { - "name": "weight", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "public.oauthAccounts": { - "name": "oauthAccounts", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "ulid", - "primaryKey": true, - "notNull": true, - "default": "gen_ulid()" - }, - "userId": { - "name": "userId", - "type": "ulid", - "primaryKey": false, - "notNull": true - }, - "providerId": { - "name": "providerId", - "type": "ulid", - "primaryKey": false, - "notNull": true - }, - "accountId": { - "name": "accountId", - "type": "varchar", - "primaryKey": false, - "notNull": true - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "oauthAccounts_userId_users_id_fk": { - "name": "oauthAccounts_userId_users_id_fk", - "tableFrom": "oauthAccounts", - "tableTo": "users", - "columnsFrom": [ - "userId" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "cascade" - }, - "oauthAccounts_providerId_oauthProviders_id_fk": { - "name": "oauthAccounts_providerId_oauthProviders_id_fk", - "tableFrom": "oauthAccounts", - "tableTo": "oauthProviders", - "columnsFrom": [ - "providerId" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "cascade" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "oauthAccounts_providerId_accountId_unique": { - "name": "oauthAccounts_providerId_accountId_unique", - "nullsNotDistinct": false, - "columns": [ - "providerId", - "accountId" - ] - } - } - }, - "public.oauthProviders": { - "name": "oauthProviders", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "ulid", - "primaryKey": true, - "notNull": true, - "default": "gen_ulid()" - }, - "type": { - "name": "type", - "type": "varchar(32)", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "varchar(255)", - "primaryKey": false, - "notNull": true - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "oauthProviders_type_unique": { - "name": "oauthProviders_type_unique", - "nullsNotDistinct": false, - "columns": [ - "type" - ] - } - } - }, - "public.userEquipment": { - "name": "userEquipment", - "schema": "", - "columns": { - "userId": { - "name": "userId", - "type": "ulid", - "primaryKey": false, - "notNull": true - }, - "equipmentId": { - "name": "equipmentId", - "type": "ulid", - "primaryKey": false, - "notNull": true - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "userEquipment_userId_users_id_fk": { - "name": "userEquipment_userId_users_id_fk", - "tableFrom": "userEquipment", - "tableTo": "users", - "columnsFrom": [ - "userId" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "cascade" - }, - "userEquipment_equipmentId_equipment_id_fk": { - "name": "userEquipment_equipmentId_equipment_id_fk", - "tableFrom": "userEquipment", - "tableTo": "equipment", - "columnsFrom": [ - "equipmentId" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "cascade" - } - }, - "compositePrimaryKeys": { - "userEquipment_userId_equipmentId_pk": { - "name": "userEquipment_userId_equipmentId_pk", - "columns": [ - "userId", - "equipmentId" - ] - } - }, - "uniqueConstraints": {} - }, - "public.users": { - "name": "users", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "ulid", - "primaryKey": true, - "notNull": true, - "default": "gen_ulid()" - }, - "name": { - "name": "name", - "type": "varchar(32)", - "primaryKey": false, - "notNull": false - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "isAdmin": { - "name": "isAdmin", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - } - }, - "enums": {}, - "schemas": {}, - "sequences": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} \ No newline at end of file diff --git a/server/database/migrations/meta/_journal.json b/server/database/migrations/meta/_journal.json index 35c2086..7b5550a 100644 --- a/server/database/migrations/meta/_journal.json +++ b/server/database/migrations/meta/_journal.json @@ -5,22 +5,8 @@ { "idx": 0, "version": "7", - "when": 1724100830555, - "tag": "0000_dry_wasp", - "breakpoints": true - }, - { - "idx": 1, - "version": "7", - "when": 1724101985802, - "tag": "0001_magenta_mordo", - "breakpoints": true - }, - { - "idx": 2, - "version": "7", - "when": 1724876180185, - "tag": "0002_thin_jimmy_woo", + "when": 1726954431056, + "tag": "0000_furry_mystique", "breakpoints": true } ] diff --git a/server/database/schema.ts b/server/database/schema.ts index d1f9627..c48ceee 100644 --- a/server/database/schema.ts +++ b/server/database/schema.ts @@ -10,7 +10,10 @@ import { varchar, primaryKey, index, - unique + unique, + serial, + text, + pgEnum } from 'drizzle-orm/pg-core' /** @@ -66,21 +69,19 @@ export const users = pgTable('users', { export const oauthProviders = pgTable('oauthProviders', { id: - ulid('id') - .notNull() - .default(sql`gen_ulid()`) + serial('id') .primaryKey(), type: varchar('type', { - length: 32 + length: limits.maxOAuthProviderTypeLength }) .notNull() .unique(), name: varchar('name', { - length: 255 + length: limits.maxOAuthProviderNameLength }) .notNull(), @@ -115,7 +116,7 @@ export const oauthAccounts = pgTable('oauthAccounts', { }), providerId: - ulid('providerId') + integer('providerId') .notNull() .references(() => oauthProviders.id, { onDelete: 'cascade', @@ -136,6 +137,42 @@ export const oauthAccounts = pgTable('oauthAccounts', { uniqueProviderIdAccountId: unique().on(table.providerId, table.accountId) })) +/** + * Equipment types table + * + * This table is used to store equipment types + */ + +export const equipmentTypes = pgTable('equipmentTypes', { + id: + serial('id') + .primaryKey(), + + name: + varchar('name', { + length: limits.maxEquipmentTypeNameLength + }) + .notNull() +}) + +/** + * Equipment groups table + * + * This table is used to store equipment groups + */ + +export const equipmentGroups = pgTable('equipmentGroups', { + id: + serial('id') + .primaryKey(), + + name: + varchar('name', { + length: limits.maxEquipmentGroupNameLength + }) + .notNull() +}) + /** * Equipment table * @@ -144,9 +181,7 @@ export const oauthAccounts = pgTable('oauthAccounts', { export const equipment = pgTable('equipment', { id: - ulid('id') - .notNull() - .default(sql`gen_ulid()`) + serial('id') .primaryKey(), name: @@ -155,18 +190,148 @@ export const equipment = pgTable('equipment', { }) .notNull(), + description: text('description'), + weight: integer('weight') - .notNull(), + .notNull() + .default(0), + + equipmentTypeId: + integer('equipmentTypeId') + .references(() => equipmentTypes.id, { + onDelete: 'set null', + onUpdate: 'cascade' + }), + + equipmentGroupId: + integer('equipmentGroupId') + .references(() => equipmentGroups.id, { + onDelete: 'set null', + onUpdate: 'cascade' + }), createdAt: timestamp('createdAt', { withTimezone: true }) .notNull() + .defaultNow(), + + updatedAt: + timestamp('updatedAt', { + withTimezone: true + }) + .notNull() .defaultNow() + .$onUpdate(() => sql`now()`) +}, (table) => { + return { + typeIdIndex: index().on(table.equipmentTypeId), + groupIdIndex: index().on(table.equipmentGroupId) + } +}) + +/** + * Equipment attributes data types + * + * Types of data that can be stored in the attribute + */ + +export const equipmentAttributeDataType = pgEnum('equipmentAttributeDataType', [ + 'boolean', + 'string', + 'integer', + 'decimal' +]) + +/** + * Equipment attributes table + * + * This table is used to store equipment attributes + */ + +export const equipmentAttributes = pgTable('equipmentAttributes', { + id: + serial('id') + .primaryKey(), + + name: + varchar('name', { + length: limits.maxEquipmentAttributeNameLength + }) + .notNull(), + + dataType: + equipmentAttributeDataType('dataType') + .notNull() }) +/** + * Equipment type attributes table + * + * This table is used to store the attributes of the equipment type + */ + +export const equipmentTypeAttributes = pgTable('equipmentTypeAttributes', { + equipmentTypeId: + integer('equipmentTypeId') + .references(() => equipmentTypes.id, { + onDelete: 'cascade', + onUpdate: 'cascade' + }), + + equipmentAttributeId: + integer('equipmentAttributeId') + .references(() => equipmentAttributes.id, { + onDelete: 'cascade', + onUpdate: 'cascade' + }) +}, (table) => ({ + primaryKey: primaryKey({ + columns: [table.equipmentTypeId, table.equipmentAttributeId] + }), + + equipmentTypeIdIndex: index().on(table.equipmentTypeId), + attributeIdIndex: index().on(table.equipmentAttributeId) +})) + +/** + * Equipment attribute values table + * + * This table is used to store the values of the equipment attributes + */ + +export const equipmentAttributeValues = pgTable('equipmentAttributeValues', { + id: + serial('id') + .primaryKey(), + + equipmentId: + integer('equipmentId') + .notNull() + .references(() => equipment.id, { + onDelete: 'cascade', + onUpdate: 'cascade' + }), + + equipmentAttributeId: + integer('equipmentAttributeId') + .notNull() + .references(() => equipmentAttributes.id, { + onDelete: 'cascade', + onUpdate: 'cascade' + }), + + value: + varchar('value') + .notNull() +}, (table) => ({ + uniqueEquipmentIdAttributeId: unique().on(table.equipmentId, table.equipmentAttributeId), + equipmentIdIndex: index().on(table.equipmentId), + attributeIdIndex: index().on(table.equipmentAttributeId) +})) + /** * User's equipment table * @@ -176,15 +341,13 @@ export const equipment = pgTable('equipment', { export const userEquipment = pgTable('userEquipment', { userId: ulid('userId') - .notNull() .references(() => users.id, { onDelete: 'cascade', onUpdate: 'cascade' }), equipmentId: - ulid('equipmentId') - .notNull() + integer('equipmentId') .references(() => equipment.id, { onDelete: 'cascade', onUpdate: 'cascade' @@ -249,9 +412,7 @@ export const checklists = pgTable('checklists', { export const checklistItems = pgTable('checklistItems', { id: - ulid('id') - .notNull() - .default(sql`gen_ulid()`) + serial('id') .primaryKey(), checklistId: @@ -263,7 +424,7 @@ export const checklistItems = pgTable('checklistItems', { }), equipmentId: - ulid('equipmentId') + integer('equipmentId') .notNull() .references(() => equipment.id, { onDelete: 'cascade', @@ -287,9 +448,59 @@ export const oauthProvidersRelations = relations(oauthProviders, ({ many }) => ( oauthAccounts: many(oauthAccounts) })) -export const equipmentRelations = relations(equipment, ({ many }) => ({ +export const equipmentTypesRelations = relations(equipmentTypes, ({ many }) => ({ + equipment: many(equipment), + equipmentTypeAttributes: many(equipmentTypeAttributes) +})) + +export const equipmentGroupsRelations = relations(equipmentGroups, ({ many }) => ({ + equipment: many(equipment) +})) + +export const equipmentRelations = relations(equipment, ({ many, one }) => ({ userEquipment: many(userEquipment), - checklistItems: many(checklistItems) + checklistItems: many(checklistItems), + + equipmentType: one(equipmentTypes, { + fields: [equipment.equipmentTypeId], + references: [equipmentTypes.id] + }), + + equipmentGroup: one(equipmentGroups, { + fields: [equipment.equipmentGroupId], + references: [equipmentGroups.id] + }), + + equipmentAttributeValues: many(equipmentAttributeValues) +})) + +export const equipmentAttributesRelations = relations(equipmentAttributes, ({ many }) => ({ + equipmentTypeAttributes: many(equipmentTypeAttributes), + equipmentAttributeValues: many(equipmentAttributeValues) +})) + +export const equipmentTypeAttributesRelations = relations(equipmentTypeAttributes, ({ one }) => ({ + equipmentType: one(equipmentTypes, { + fields: [equipmentTypeAttributes.equipmentTypeId], + references: [equipmentTypes.id] + }), + + equipmentAttribute: one(equipmentAttributes, { + fields: [equipmentTypeAttributes.equipmentAttributeId], + references: [equipmentAttributes.id] + }) +})) + +export const equipmentAttributeValuesRelations = relations(equipmentAttributeValues, ({ one }) => ({ + equipment: one(equipment, { + fields: [equipmentAttributeValues.equipmentId], + references: [equipment.id] + }), + + equipmentAttribute: one(equipmentAttributes, { + fields: [equipmentAttributeValues.equipmentAttributeId], + references: [equipmentAttributes.id] + }) })) export const oauthAccountsRelations = relations(oauthAccounts, ({ one }) => ({ diff --git a/server/utils/database.ts b/server/utils/database.ts index f459a94..34c7afc 100644 --- a/server/utils/database.ts +++ b/server/utils/database.ts @@ -12,7 +12,7 @@ export function createDrizzle() { throw new Error('DATABASE_URL is not defined') } - if (import.meta.dev === true || process.env.WRANGLER_DEV) { + if (import.meta.dev === true || process.env.WRANGLER_DEV || process.env.LOCAL_DATABASE) { // Check docker-compose.yml for the details neonConfig.fetchEndpoint = 'http://db.localtest.me:4444/sql' } @@ -37,7 +37,7 @@ export function createDrizzle() { export function createDrizzleWebsocket() { neonConfig.webSocketConstructor = ws - if (import.meta.dev === true) { + if (import.meta.dev === true || process.env.LOCAL_DATABASE) { neonConfig.wsProxy = (host) => `${host}:5433/v1` neonConfig.useSecureWebSocket = false; neonConfig.pipelineTLS = false; diff --git a/server/utils/providers/twitch.ts b/server/utils/providers/twitch.ts index ab9e52e..71c0acf 100644 --- a/server/utils/providers/twitch.ts +++ b/server/utils/providers/twitch.ts @@ -2,7 +2,7 @@ import { H3Event } from 'h3' import consola from 'consola' import { joinURL } from 'ufo' -import { +import type { TwitchOAuthTokenResponse, TwitchUser, TwitchUsersResponse diff --git a/server/utils/user.ts b/server/utils/user.ts index 2bca78f..d4b8b67 100644 --- a/server/utils/user.ts +++ b/server/utils/user.ts @@ -1,6 +1,6 @@ import type { H3Event } from 'h3' import { and, eq } from 'drizzle-orm' -import { OAuthProvider } from '~/models/oauth'; +import type { OAuthProvider } from '~/models/oauth'; interface ReturnUser { readonly userId: string | null; @@ -94,7 +94,7 @@ export async function createOAuthUser( } // Create a new user - const [{ userId, isAdmin }] = await transaction + const [foundUser] = await transaction .insert(tables.users) .values({ isAdmin: false @@ -104,7 +104,7 @@ export async function createOAuthUser( isAdmin: tables.users.isAdmin }) - if (userId === undefined) { + if (foundUser?.userId === undefined) { throw createError({ message: 'Failed to create user', statusCode: 500 @@ -115,14 +115,14 @@ export async function createOAuthUser( await transaction .insert(tables.oauthAccounts) .values({ - userId, + userId: foundUser.userId, accountId, providerId: providerData.id }) return { - userId, - isAdmin + userId: foundUser.userId, + isAdmin: foundUser.isAdmin } }) diff --git a/server/utils/validate.ts b/server/utils/validate.ts index 161e0cd..abc6100 100644 --- a/server/utils/validate.ts +++ b/server/utils/validate.ts @@ -1,7 +1,14 @@ import * as v from 'valibot' import type { H3Event, EventHandlerRequest } from 'h3' -export const idValidator = v.pipe(v.string(), v.nonEmpty()) +export const idValidatorString = v.pipe(v.string(), v.nonEmpty()) +export const idValidatorNumber = v.number() + +export const stringToIntegerValidator = v.pipe( + v.string(), + v.transform(input => Number(input)), + v.integer() +) export async function validateSessionUser(event: H3Event) { const session = await useAppSession(event) @@ -16,8 +23,38 @@ export async function validateSessionUser(event: H3Event) { return userId } -export function validateId(id: unknown) { - const { issues, output, success } = v.safeParse(idValidator, id) +export function validateIdString(id: unknown) { + const { issues, output, success } = v.safeParse(idValidatorString, id) + + if (success) { + return output + } + + throw createError({ + statusCode: 400, + statusMessage: 'Validation Error', + message: issues[0].message, + data: issues + }) +} + +export function validateIdNumber(id: unknown) { + const { issues, output, success } = v.safeParse(idValidatorNumber, id) + + if (success) { + return output + } + + throw createError({ + statusCode: 400, + statusMessage: 'Validation Error', + message: issues[0].message, + data: issues + }) +} + +export function validateStringToInteger(id: unknown) { + const { issues, output, success } = v.safeParse(stringToIntegerValidator, id) if (success) { return output diff --git a/tools/migrate.ts b/tools/migrate.ts new file mode 100644 index 0000000..34ac44a --- /dev/null +++ b/tools/migrate.ts @@ -0,0 +1,41 @@ +import { sql } from 'drizzle-orm' +import { createDrizzleWebsocket, tables } from '~~/server/utils/database' +import { migrate } from 'drizzle-orm/neon-serverless/migrator' +import drizzleConfig from '~~/drizzle.config' + +if (process.env.DATABASE_URL === undefined) { + throw new Error('DATABASE_URL is not defined') +} + +if (drizzleConfig.out === undefined) { + throw new Error('drizzleConfig.out is not defined') +} + +const db = createDrizzleWebsocket() + +async function beforeMigration() { + // Register ULID extension + await db.execute(sql`CREATE EXTENSION IF NOT EXISTS ulid`) +} + +async function afterMigration() { + // Add twitch provider if it doesn't exist + await db.insert(tables.oauthProviders) + .values({ + type: 'twitch', + name: 'Twitch' + }) + .onConflictDoNothing() +} + +/** + * Migrate the database + */ + +await beforeMigration() + +await migrate(db, { + migrationsFolder: drizzleConfig.out +}) + +await afterMigration()