From 88414421919ee9b6a7efdd514347bf87cce1bd89 Mon Sep 17 00:00:00 2001 From: Matej Stieranka Date: Fri, 27 Jun 2025 20:10:08 +0200 Subject: [PATCH] feat: migrate to postgres, add migrations --- .npmrc | 1 - Dockerfile | 8 +- compose.yml | 20 +- drizzle.config.ts | 21 +- drizzle/0000_low_firebird.sql | 17 -- drizzle/0000_noisy_omega_red.sql | 18 ++ drizzle/meta/0000_snapshot.json | 70 ++++--- drizzle/meta/_journal.json | 8 +- next.config.ts | 6 +- package.json | 101 +++++----- pnpm-lock.yaml | 330 +++++++++++-------------------- src/db/getInstance.ts | 19 ++ src/db/index.ts | 129 +++++------- src/db/schema.ts | 39 ++-- src/instrumentation.ts | 6 + src/migrate.ts | 18 ++ 16 files changed, 360 insertions(+), 451 deletions(-) delete mode 100644 .npmrc delete mode 100644 drizzle/0000_low_firebird.sql create mode 100644 drizzle/0000_noisy_omega_red.sql create mode 100644 src/db/getInstance.ts create mode 100644 src/instrumentation.ts create mode 100644 src/migrate.ts diff --git a/.npmrc b/.npmrc deleted file mode 100644 index 5330325..0000000 --- a/.npmrc +++ /dev/null @@ -1 +0,0 @@ -public-hoist-pattern[]=*@libsql* diff --git a/Dockerfile b/Dockerfile index c2de9b6..0327195 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,8 +24,8 @@ COPY . . # Uncomment the following line in case you want to disable telemetry during the build. ENV NEXT_TELEMETRY_DISABLED=1 -ARG DB_FILE_NAME -ENV DB_FILE_NAME=${DB_FILE_NAME} +ENV DB_DRIVER="pglite" +ENV DB_LOCATION="./data" RUN corepack enable pnpm && pnpm run build @@ -47,6 +47,10 @@ COPY --from=builder /app/public ./public COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static +RUN mkdir -p /app/data +COPY --from=builder --chown=nextjs:nodejs /app/data ./data +COPY --from=builder --chown=nextjs:nodejs /app/drizzle ./ + USER nextjs EXPOSE 3000 diff --git a/compose.yml b/compose.yml index 077dd38..8d57b38 100644 --- a/compose.yml +++ b/compose.yml @@ -4,16 +4,24 @@ services: build: context: . args: - DB_FILE_NAME: ${DB_FILE_NAME} + DB_LOCATION: ${DB_LOCATION} environment: AUTH_SECRET: ${AUTH_SECRET} AUTH_GITHUB_ID: ${AUTH_GITHUB_ID} AUTH_GITHUB_SECRET: ${AUTH_GITHUB_SECRET} - DB_FILE_NAME: ${DB_FILE_NAME} + DB_DRIVER: ${DB_DRIVER} + DB_LOCATION: ${DB_LOCATION} ports: - 3000:3000 restart: unless-stopped - volumes: - - type: bind - source: ./data/data.db - target: /app/data.db + postgres: + container_name: inffo2-postgres + hostname: inffo2-postgres + image: postgres:15 + environment: + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_DB: ${POSTGRES_DB} + ports: + - 5432:5432 + restart: unless-stopped diff --git a/drizzle.config.ts b/drizzle.config.ts index 0bbb38f..47d7f15 100644 --- a/drizzle.config.ts +++ b/drizzle.config.ts @@ -1,11 +1,16 @@ -import 'dotenv/config'; -import { defineConfig } from 'drizzle-kit'; +import "dotenv/config"; +import { defineConfig } from "drizzle-kit"; + +if (!process.env.DB_LOCATION) { + throw new Error("DB_LOCATION environment variable is not set"); +} export default defineConfig({ - out: './drizzle', - schema: './src/db/schema.ts', - dialect: 'sqlite', - dbCredentials: { - url: process.env.DB_FILE_NAME!, - }, + out: "./drizzle", + schema: "./src/db/schema.ts", + dialect: "postgresql", + ...(process.env.DB_DRIVER === "pglite" ? { driver: "pglite" } : {}), + dbCredentials: { + url: process.env.DB_LOCATION, + }, }); diff --git a/drizzle/0000_low_firebird.sql b/drizzle/0000_low_firebird.sql deleted file mode 100644 index affe216..0000000 --- a/drizzle/0000_low_firebird.sql +++ /dev/null @@ -1,17 +0,0 @@ -CREATE TABLE `eventsTable` ( - `id` integer PRIMARY KEY NOT NULL, - `startTime` text NOT NULL, - `endTime` text NOT NULL, - `name` text NOT NULL, - `title` text NOT NULL, - `description` text NOT NULL, - `type` text NOT NULL, - `lineId` integer NOT NULL, - FOREIGN KEY (`lineId`) REFERENCES `linesTable`(`id`) ON UPDATE cascade ON DELETE cascade -); ---> statement-breakpoint -CREATE TABLE `linesTable` ( - `id` integer PRIMARY KEY NOT NULL, - `name` text NOT NULL, - `description` text NOT NULL -); diff --git a/drizzle/0000_noisy_omega_red.sql b/drizzle/0000_noisy_omega_red.sql new file mode 100644 index 0000000..8ff6820 --- /dev/null +++ b/drizzle/0000_noisy_omega_red.sql @@ -0,0 +1,18 @@ +CREATE TABLE "eventsTable" ( + "id" integer PRIMARY KEY NOT NULL, + "startTime" timestamp NOT NULL, + "endTime" timestamp NOT NULL, + "name" text NOT NULL, + "title" text NOT NULL, + "description" text NOT NULL, + "type" text NOT NULL, + "lineId" integer NOT NULL +); +--> statement-breakpoint +CREATE TABLE "linesTable" ( + "id" integer PRIMARY KEY NOT NULL, + "name" text NOT NULL, + "description" text NOT NULL +); +--> statement-breakpoint +ALTER TABLE "eventsTable" ADD CONSTRAINT "eventsTable_lineId_linesTable_id_fk" FOREIGN KEY ("lineId") REFERENCES "public"."linesTable"("id") ON DELETE cascade ON UPDATE cascade; \ No newline at end of file diff --git a/drizzle/meta/0000_snapshot.json b/drizzle/meta/0000_snapshot.json index 2d0fc78..752daae 100644 --- a/drizzle/meta/0000_snapshot.json +++ b/drizzle/meta/0000_snapshot.json @@ -1,67 +1,60 @@ { - "version": "6", - "dialect": "sqlite", - "id": "67a86f93-2bc8-4fe2-8786-995874bf9863", + "id": "c0102a7e-5d09-41ca-88d1-c66f2f299e65", "prevId": "00000000-0000-0000-0000-000000000000", + "version": "7", + "dialect": "postgresql", "tables": { - "eventsTable": { + "public.eventsTable": { "name": "eventsTable", + "schema": "", "columns": { "id": { "name": "id", "type": "integer", "primaryKey": true, - "notNull": true, - "autoincrement": false + "notNull": true }, "startTime": { "name": "startTime", - "type": "text", + "type": "timestamp", "primaryKey": false, - "notNull": true, - "autoincrement": false + "notNull": true }, "endTime": { "name": "endTime", - "type": "text", + "type": "timestamp", "primaryKey": false, - "notNull": true, - "autoincrement": false + "notNull": true }, "name": { "name": "name", "type": "text", "primaryKey": false, - "notNull": true, - "autoincrement": false + "notNull": true }, "title": { "name": "title", "type": "text", "primaryKey": false, - "notNull": true, - "autoincrement": false + "notNull": true }, "description": { "name": "description", "type": "text", "primaryKey": false, - "notNull": true, - "autoincrement": false + "notNull": true }, "type": { "name": "type", "type": "text", "primaryKey": false, - "notNull": true, - "autoincrement": false + "notNull": true }, "lineId": { "name": "lineId", "type": "integer", "primaryKey": false, - "notNull": true, - "autoincrement": false + "notNull": true } }, "indexes": {}, @@ -82,48 +75,51 @@ }, "compositePrimaryKeys": {}, "uniqueConstraints": {}, - "checkConstraints": {} + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false }, - "linesTable": { + "public.linesTable": { "name": "linesTable", + "schema": "", "columns": { "id": { "name": "id", "type": "integer", "primaryKey": true, - "notNull": true, - "autoincrement": false + "notNull": true }, "name": { "name": "name", "type": "text", "primaryKey": false, - "notNull": true, - "autoincrement": false + "notNull": true }, "description": { "name": "description", "type": "text", "primaryKey": false, - "notNull": true, - "autoincrement": false + "notNull": true } }, "indexes": {}, "foreignKeys": {}, "compositePrimaryKeys": {}, "uniqueConstraints": {}, - "checkConstraints": {} + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false } }, - "views": {}, "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, "_meta": { + "columns": {}, "schemas": {}, - "tables": {}, - "columns": {} - }, - "internal": { - "indexes": {} + "tables": {} } } \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index dad47d0..7b7dbdc 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -1,12 +1,12 @@ { "version": "7", - "dialect": "sqlite", + "dialect": "postgresql", "entries": [ { "idx": 0, - "version": "6", - "when": 1750781935205, - "tag": "0000_low_firebird", + "version": "7", + "when": 1750786732013, + "tag": "0000_noisy_omega_red", "breakpoints": true } ] diff --git a/next.config.ts b/next.config.ts index bfd0027..92346c6 100644 --- a/next.config.ts +++ b/next.config.ts @@ -2,11 +2,9 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { output: "standalone", + serverExternalPackages: ["@electric-sql/pglite"], outputFileTracingIncludes: { - "./**/*": [ - "./node_modules/@libsql/darwin*/**/*", - "./node_modules/@libsql/linux*/**/*", - ], + "**/*": ["./drizzle/**/*"], }, }; diff --git a/package.json b/package.json index 4fe4851..a67bd3b 100644 --- a/package.json +++ b/package.json @@ -1,50 +1,55 @@ { - "name": "inffo2", - "version": "0.1.0", - "private": true, - "packageManager": "pnpm@8.12.0", - "scripts": { - "dev": "next dev --turbopack", - "build": "next build", - "start": "next start", - "lint": "next lint", - "push-db": "drizzle-kit push" - }, - "dependencies": { - "@libsql/client": "^0.15.9", - "@radix-ui/react-collapsible": "^1.1.11", - "@radix-ui/react-dialog": "^1.1.14", - "@radix-ui/react-dropdown-menu": "^2.1.15", - "@radix-ui/react-label": "^2.1.7", - "@radix-ui/react-select": "^2.2.5", - "@radix-ui/react-separator": "^1.1.7", - "@radix-ui/react-slot": "^1.2.3", - "@radix-ui/react-tooltip": "^1.2.7", - "cheerio": "^1.1.0", - "class-variance-authority": "^0.7.1", - "clsx": "^2.1.1", - "dotenv": "^16.5.0", - "drizzle-orm": "^0.44.2", - "drizzle-zod": "^0.8.2", - "lucide-react": "^0.522.0", - "next": "15.3.4", - "next-auth": "5.0.0-beta.29", - "next-themes": "^0.4.6", - "react": "^19.0.0", - "react-dom": "^19.0.0", - "tailwind-merge": "^3.3.1", - "zod": "^3.25.67" - }, - "devDependencies": { - "@biomejs/biome": "2.0.5", - "@tailwindcss/postcss": "^4", - "@types/node": "^20", - "@types/react": "^19", - "@types/react-dom": "^19", - "drizzle-kit": "^0.31.2", - "tailwindcss": "^4", - "tsx": "^4.20.3", - "tw-animate-css": "^1.3.4", - "typescript": "^5" - } + "name": "inffo2", + "version": "0.1.0", + "private": true, + "packageManager": "pnpm@8.12.0", + "scripts": { + "dev": "next dev --turbopack", + "build": "next build", + "start": "next start", + "lint": "next lint", + "push-db": "drizzle-kit push" + }, + "dependencies": { + "@electric-sql/pglite": "^0.3.3", + "@radix-ui/react-collapsible": "^1.1.11", + "@radix-ui/react-dialog": "^1.1.14", + "@radix-ui/react-dropdown-menu": "^2.1.15", + "@radix-ui/react-label": "^2.1.7", + "@radix-ui/react-select": "^2.2.5", + "@radix-ui/react-separator": "^1.1.7", + "@radix-ui/react-slot": "^1.2.3", + "@radix-ui/react-tooltip": "^1.2.7", + "cheerio": "^1.1.0", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "dotenv": "^16.5.0", + "drizzle-orm": "^0.44.2", + "drizzle-zod": "^0.8.2", + "lucide-react": "^0.522.0", + "next": "15.3.4", + "next-auth": "5.0.0-beta.29", + "next-themes": "^0.4.6", + "pg": "^8.16.2", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "tailwind-merge": "^3.3.1", + "zod": "^3.25.67" + }, + "devDependencies": { + "@biomejs/biome": "2.0.5", + "@tailwindcss/postcss": "^4", + "@types/node": "^20", + "@types/pg": "^8.15.4", + "@types/react": "^19", + "@types/react-dom": "^19", + "drizzle-kit": "^0.31.2", + "tailwindcss": "^4", + "tsx": "^4.20.3", + "tw-animate-css": "^1.3.4", + "typescript": "^5" + }, + "browser": { + "crypto": false + } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e49a36f..bdf5ecf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,9 +5,9 @@ settings: excludeLinksFromLockfile: false dependencies: - '@libsql/client': - specifier: ^0.15.9 - version: 0.15.9 + '@electric-sql/pglite': + specifier: ^0.3.3 + version: 0.3.3 '@radix-ui/react-collapsible': specifier: ^1.1.11 version: 1.1.11(@types/react-dom@19.1.6)(@types/react@19.1.8)(react-dom@19.1.0)(react@19.1.0) @@ -46,7 +46,7 @@ dependencies: version: 16.5.0 drizzle-orm: specifier: ^0.44.2 - version: 0.44.2(@libsql/client@0.15.9) + version: 0.44.2(@electric-sql/pglite@0.3.3)(@types/pg@8.15.4)(pg@8.16.2) drizzle-zod: specifier: ^0.8.2 version: 0.8.2(drizzle-orm@0.44.2)(zod@3.25.67) @@ -62,6 +62,9 @@ dependencies: next-themes: specifier: ^0.4.6 version: 0.4.6(react-dom@19.1.0)(react@19.1.0) + pg: + specifier: ^8.16.2 + version: 8.16.2 react: specifier: ^19.0.0 version: 19.1.0 @@ -85,6 +88,9 @@ devDependencies: '@types/node': specifier: ^20 version: 20.19.1 + '@types/pg': + specifier: ^8.15.4 + version: 8.15.4 '@types/react': specifier: ^19 version: 19.1.8 @@ -234,6 +240,10 @@ packages: resolution: {integrity: sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==} dev: true + /@electric-sql/pglite@0.3.3: + resolution: {integrity: sha512-JrvHOx9q0yvKEby0bK8qzGTVw6K+yEg8enxDWb2IwNKr5XZxRrBb+GNIqoAIP7yXyhRg5jcENWmdHmtnAT87vA==} + dev: false + /@emnapi/runtime@1.4.3: resolution: {integrity: sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==} requiresBuild: true @@ -943,128 +953,6 @@ packages: '@jridgewell/sourcemap-codec': 1.5.0 dev: true - /@libsql/client@0.15.9: - resolution: {integrity: sha512-VT3do0a0vwYVaNcp/y05ikkKS3OrFR5UeEf5SUuYZVgKVl1Nc1k9ajoYSsOid8AD/vlhLDB5yFQaV4HmT/OB9w==} - dependencies: - '@libsql/core': 0.15.9 - '@libsql/hrana-client': 0.7.0 - js-base64: 3.7.7 - libsql: 0.5.13 - promise-limit: 2.7.0 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - dev: false - - /@libsql/core@0.15.9: - resolution: {integrity: sha512-4OVdeAmuaCUq5hYT8NNn0nxlO9AcA/eTjXfUZ+QK8MT3Dz7Z76m73x7KxjU6I64WyXX98dauVH2b9XM+d84npw==} - dependencies: - js-base64: 3.7.7 - dev: false - - /@libsql/darwin-arm64@0.5.13: - resolution: {integrity: sha512-ASz/EAMLDLx3oq9PVvZ4zBXXHbz2TxtxUwX2xpTRFR4V4uSHAN07+jpLu3aK5HUBLuv58z7+GjaL5w/cyjR28Q==} - cpu: [arm64] - os: [darwin] - requiresBuild: true - dev: false - optional: true - - /@libsql/darwin-x64@0.5.13: - resolution: {integrity: sha512-kzglniv1difkq8opusSXM7u9H0WoEPeKxw0ixIfcGfvlCVMJ+t9UNtXmyNHW68ljdllje6a4C6c94iPmIYafYA==} - cpu: [x64] - os: [darwin] - requiresBuild: true - dev: false - optional: true - - /@libsql/hrana-client@0.7.0: - resolution: {integrity: sha512-OF8fFQSkbL7vJY9rfuegK1R7sPgQ6kFMkDamiEccNUvieQ+3urzfDFI616oPl8V7T9zRmnTkSjMOImYCAVRVuw==} - dependencies: - '@libsql/isomorphic-fetch': 0.3.1 - '@libsql/isomorphic-ws': 0.1.5 - js-base64: 3.7.7 - node-fetch: 3.3.2 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - dev: false - - /@libsql/isomorphic-fetch@0.3.1: - resolution: {integrity: sha512-6kK3SUK5Uu56zPq/Las620n5aS9xJq+jMBcNSOmjhNf/MUvdyji4vrMTqD7ptY7/4/CAVEAYDeotUz60LNQHtw==} - engines: {node: '>=18.0.0'} - dev: false - - /@libsql/isomorphic-ws@0.1.5: - resolution: {integrity: sha512-DtLWIH29onUYR00i0GlQ3UdcTRC6EP4u9w/h9LxpUZJWRMARk6dQwZ6Jkd+QdwVpuAOrdxt18v0K2uIYR3fwFg==} - dependencies: - '@types/ws': 8.18.1 - ws: 8.18.2 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - dev: false - - /@libsql/linux-arm-gnueabihf@0.5.13: - resolution: {integrity: sha512-UEW+VZN2r0mFkfztKOS7cqfS8IemuekbjUXbXCwULHtusww2QNCXvM5KU9eJCNE419SZCb0qaEWYytcfka8qeA==} - cpu: [arm] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@libsql/linux-arm-musleabihf@0.5.13: - resolution: {integrity: sha512-NMDgLqryYBv4Sr3WoO/m++XDjR5KLlw9r/JK4Ym6A1XBv2bxQQNhH0Lxx3bjLW8qqhBD4+0xfms4d2cOlexPyA==} - cpu: [arm] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@libsql/linux-arm64-gnu@0.5.13: - resolution: {integrity: sha512-/wCxVdrwl1ee6D6LEjwl+w4SxuLm5UL9Kb1LD5n0bBGs0q+49ChdPPh7tp175iRgkcrTgl23emymvt1yj3KxVQ==} - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@libsql/linux-arm64-musl@0.5.13: - resolution: {integrity: sha512-xnVAbZIanUgX57XqeI5sNaDnVilp0Di5syCLSEo+bRyBobe/1IAeehNZpyVbCy91U2N6rH1C/mZU7jicVI9x+A==} - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@libsql/linux-x64-gnu@0.5.13: - resolution: {integrity: sha512-/mfMRxcQAI9f8t7tU3QZyh25lXgXKzgin9B9TOSnchD73PWtsVhlyfA6qOCfjQl5kr4sHscdXD5Yb3KIoUgrpQ==} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@libsql/linux-x64-musl@0.5.13: - resolution: {integrity: sha512-rdefPTpQCVwUjIQYbDLMv3qpd5MdrT0IeD0UZPGqhT9AWU8nJSQoj2lfyIDAWEz7PPOVCY4jHuEn7FS2sw9kRA==} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@libsql/win32-x64-msvc@0.5.13: - resolution: {integrity: sha512-aNcmDrD1Ws+dNZIv9ECbxBQumqB9MlSVEykwfXJpqv/593nABb8Ttg5nAGUPtnADyaGDTrGvPPP81d/KsKho4Q==} - cpu: [x64] - os: [win32] - requiresBuild: true - dev: false - optional: true - - /@neon-rs/load@0.0.4: - resolution: {integrity: sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw==} - dev: false - /@next/env@15.3.4: resolution: {integrity: sha512-ZkdYzBseS6UjYzz6ylVKPOK+//zLWvD6Ta+vpoye8cW11AjiQjGYVibF0xuvT4L0iJfAPfZLFidaEzAOywyOAQ==} dev: false @@ -1982,6 +1870,13 @@ packages: dependencies: undici-types: 6.21.0 + /@types/pg@8.15.4: + resolution: {integrity: sha512-I6UNVBAoYbvuWkkU3oosC8yxqH21f4/Jc4DK71JLG3dT2mdlGe1z+ep/LQGXaKaOgcvUrsQoPRqfgtMcvZiJhg==} + dependencies: + '@types/node': 20.19.1 + pg-protocol: 1.10.2 + pg-types: 2.2.0 + /@types/react-dom@19.1.6(@types/react@19.1.8): resolution: {integrity: sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==} peerDependencies: @@ -1994,12 +1889,6 @@ packages: dependencies: csstype: 3.1.3 - /@types/ws@8.18.1: - resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} - dependencies: - '@types/node': 20.19.1 - dev: false - /aria-hidden@1.2.6: resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} engines: {node: '>=10'} @@ -2126,11 +2015,6 @@ packages: /csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} - /data-uri-to-buffer@4.0.1: - resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} - engines: {node: '>= 12'} - dev: false - /debug@4.4.1: resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} engines: {node: '>=6.0'} @@ -2143,11 +2027,6 @@ packages: ms: 2.1.3 dev: true - /detect-libc@2.0.2: - resolution: {integrity: sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==} - engines: {node: '>=8'} - dev: false - /detect-libc@2.0.4: resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==} engines: {node: '>=8'} @@ -2200,7 +2079,7 @@ packages: - supports-color dev: true - /drizzle-orm@0.44.2(@libsql/client@0.15.9): + /drizzle-orm@0.44.2(@electric-sql/pglite@0.3.3)(@types/pg@8.15.4)(pg@8.16.2): resolution: {integrity: sha512-zGAqBzWWkVSFjZpwPOrmCrgO++1kZ5H/rZ4qTGeGOe18iXGVJWf3WPfHOVwFIbmi8kHjfJstC6rJomzGx8g/dQ==} peerDependencies: '@aws-sdk/client-rds-data': '>=3' @@ -2292,7 +2171,9 @@ packages: sqlite3: optional: true dependencies: - '@libsql/client': 0.15.9 + '@electric-sql/pglite': 0.3.3 + '@types/pg': 8.15.4 + pg: 8.16.2 dev: false /drizzle-zod@0.8.2(drizzle-orm@0.44.2)(zod@3.25.67): @@ -2301,7 +2182,7 @@ packages: drizzle-orm: '>=0.36.0' zod: ^3.25.1 dependencies: - drizzle-orm: 0.44.2(@libsql/client@0.15.9) + drizzle-orm: 0.44.2(@electric-sql/pglite@0.3.3)(@types/pg@8.15.4)(pg@8.16.2) zod: 3.25.67 dev: false @@ -2404,21 +2285,6 @@ packages: '@esbuild/win32-x64': 0.25.5 dev: true - /fetch-blob@3.2.0: - resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} - engines: {node: ^12.20 || >= 14.13} - dependencies: - node-domexception: 1.0.0 - web-streams-polyfill: 3.3.3 - dev: false - - /formdata-polyfill@4.0.10: - resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} - engines: {node: '>=12.20.0'} - dependencies: - fetch-blob: 3.2.0 - dev: false - /fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -2473,29 +2339,6 @@ packages: resolution: {integrity: sha512-QxG7EaliDARm1O1S8BGakqncGT9s25bKL1WSf6/oa17Tkqwi8D2ZNglqCF+DsYF88/rV66Q/Q2mFAy697E1DUg==} dev: false - /js-base64@3.7.7: - resolution: {integrity: sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==} - dev: false - - /libsql@0.5.13: - resolution: {integrity: sha512-5Bwoa/CqzgkTwySgqHA5TsaUDRrdLIbdM4egdPcaAnqO3aC+qAgS6BwdzuZwARA5digXwiskogZ8H7Yy4XfdOg==} - cpu: [x64, arm64, wasm32, arm] - os: [darwin, linux, win32] - dependencies: - '@neon-rs/load': 0.0.4 - detect-libc: 2.0.2 - optionalDependencies: - '@libsql/darwin-arm64': 0.5.13 - '@libsql/darwin-x64': 0.5.13 - '@libsql/linux-arm-gnueabihf': 0.5.13 - '@libsql/linux-arm-musleabihf': 0.5.13 - '@libsql/linux-arm64-gnu': 0.5.13 - '@libsql/linux-arm64-musl': 0.5.13 - '@libsql/linux-x64-gnu': 0.5.13 - '@libsql/linux-x64-musl': 0.5.13 - '@libsql/win32-x64-msvc': 0.5.13 - dev: false - /lightningcss-darwin-arm64@1.30.1: resolution: {integrity: sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==} engines: {node: '>= 12.0.0'} @@ -2721,21 +2564,6 @@ packages: - babel-plugin-macros dev: false - /node-domexception@1.0.0: - resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} - engines: {node: '>=10.5.0'} - deprecated: Use your platform's native DOMException instead - dev: false - - /node-fetch@3.3.2: - resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dependencies: - data-uri-to-buffer: 4.0.1 - fetch-blob: 3.2.0 - formdata-polyfill: 4.0.10 - dev: false - /nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} dependencies: @@ -2765,6 +2593,65 @@ packages: entities: 6.0.1 dev: false + /pg-cloudflare@1.2.6: + resolution: {integrity: sha512-uxmJAnmIgmYgnSFzgOf2cqGQBzwnRYcrEgXuFjJNEkpedEIPBSEzxY7ph4uA9k1mI+l/GR0HjPNS6FKNZe8SBQ==} + requiresBuild: true + dev: false + optional: true + + /pg-connection-string@2.9.1: + resolution: {integrity: sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==} + dev: false + + /pg-int8@1.0.1: + resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} + engines: {node: '>=4.0.0'} + + /pg-pool@3.10.1(pg@8.16.2): + resolution: {integrity: sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==} + peerDependencies: + pg: '>=8.0' + dependencies: + pg: 8.16.2 + dev: false + + /pg-protocol@1.10.2: + resolution: {integrity: sha512-Ci7jy8PbaWxfsck2dwZdERcDG2A0MG8JoQILs+uZNjABFuBuItAZCWUNz8sXRDMoui24rJw7WlXqgpMdBSN/vQ==} + + /pg-types@2.2.0: + resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} + engines: {node: '>=4'} + dependencies: + pg-int8: 1.0.1 + postgres-array: 2.0.0 + postgres-bytea: 1.0.0 + postgres-date: 1.0.7 + postgres-interval: 1.2.0 + + /pg@8.16.2: + resolution: {integrity: sha512-OtLWF0mKLmpxelOt9BqVq83QV6bTfsS0XLegIeAKqKjurRnRKie1Dc1iL89MugmSLhftxw6NNCyZhm1yQFLMEQ==} + engines: {node: '>= 16.0.0'} + peerDependencies: + pg-native: '>=3.0.1' + peerDependenciesMeta: + pg-native: + optional: true + dependencies: + pg-connection-string: 2.9.1 + pg-pool: 3.10.1(pg@8.16.2) + pg-protocol: 1.10.2 + pg-types: 2.2.0 + pgpass: 1.0.5 + optionalDependencies: + pg-cloudflare: 1.2.6 + dev: false + + /pgpass@1.0.5: + resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==} + dependencies: + split2: 4.2.0 + dev: false + /picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -2786,6 +2673,24 @@ packages: source-map-js: 1.2.1 dev: true + /postgres-array@2.0.0: + resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} + engines: {node: '>=4'} + + /postgres-bytea@1.0.0: + resolution: {integrity: sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==} + engines: {node: '>=0.10.0'} + + /postgres-date@1.0.7: + resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} + engines: {node: '>=0.10.0'} + + /postgres-interval@1.2.0: + resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} + engines: {node: '>=0.10.0'} + dependencies: + xtend: 4.0.2 + /preact-render-to-string@6.5.11(preact@10.24.3): resolution: {integrity: sha512-ubnauqoGczeGISiOh6RjX0/cdaF8v/oDXIjO85XALCQjwQP+SB4RDXXtvZ6yTYSjG+PC1QRP2AhPgCEsM2EvUw==} peerDependencies: @@ -2798,10 +2703,6 @@ packages: resolution: {integrity: sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA==} dev: false - /promise-limit@2.7.0: - resolution: {integrity: sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw==} - dev: false - /react-dom@19.1.0(react@19.1.0): resolution: {integrity: sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==} peerDependencies: @@ -2944,6 +2845,11 @@ packages: engines: {node: '>=0.10.0'} dev: true + /split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + dev: false + /streamsearch@1.1.0: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} engines: {node: '>=10.0.0'} @@ -3055,11 +2961,6 @@ packages: tslib: 2.8.1 dev: false - /web-streams-polyfill@3.3.3: - resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} - engines: {node: '>= 8'} - dev: false - /whatwg-encoding@3.1.1: resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} engines: {node: '>=18'} @@ -3072,18 +2973,9 @@ packages: engines: {node: '>=18'} dev: false - /ws@8.18.2: - resolution: {integrity: sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - dev: false + /xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} /yallist@5.0.0: resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} diff --git a/src/db/getInstance.ts b/src/db/getInstance.ts new file mode 100644 index 0000000..d8aebdb --- /dev/null +++ b/src/db/getInstance.ts @@ -0,0 +1,19 @@ +import { drizzle as drizzlePostgres } from "drizzle-orm/node-postgres"; +import { drizzle as drizzlePglite } from "drizzle-orm/pglite"; + +export function getDbInstance() { + if (!process.env.DB_LOCATION) { + throw new Error("DB_LOCATION environment variable is not set"); + } + + if (process.env.DB_DRIVER === "pglite") { + return { + type: "pglite" as const, + db: drizzlePglite(process.env.DB_LOCATION), + }; + } + return { + type: "postgres" as const, + db: drizzlePostgres(process.env.DB_LOCATION), + }; +} diff --git a/src/db/index.ts b/src/db/index.ts index 747667b..38c5689 100644 --- a/src/db/index.ts +++ b/src/db/index.ts @@ -1,22 +1,10 @@ import type { Event, Line } from "@/common/parser"; import "dotenv/config"; import { and, eq, gte, inArray, like, lte, or } from "drizzle-orm"; -import { drizzle } from "drizzle-orm/libsql"; -import { migrate } from "drizzle-orm/libsql/migrator"; -import { - eventsTable, - type eventsTableSelectSchema, - linesTable, -} from "./schema"; +import { getDbInstance } from "./getInstance"; +import { eventsTable, linesTable } from "./schema"; -if (!process.env.DB_FILE_NAME) { - throw new Error("DB_FILE_NAME environment variable is not set"); -} - -const db = drizzle(process.env.DB_FILE_NAME); -await migrate(db, { - migrationsFolder: "./drizzle", -}); +const { db } = getDbInstance(); export async function saveLines(lines: Line[]) { await db @@ -33,21 +21,8 @@ export async function saveLines(lines: Line[]) { export async function updateLineEvents(lineId: number, events: Event[]) { await db.transaction(async (tx) => { - // First, delete existing events for the line await tx.delete(eventsTable).where(eq(eventsTable.lineId, lineId)); - // Then, insert the new events - await tx.insert(eventsTable).values( - events.map((event) => ({ - id: event.id, - startTime: event.startTime.toISOString(), - endTime: event.endTime.toISOString(), - name: event.name, - title: event.title, - description: event.description, - type: event.type, - lineId: lineId, - })), - ); + await tx.insert(eventsTable).values(events); }); } @@ -68,36 +43,22 @@ export async function getLineById(lineId: number) { } export async function getEventsForLine(lineId: number) { - return formatEvents( - await db - .select() - .from(eventsTable) - .where(eq(eventsTable.lineId, lineId)) - .orderBy(eventsTable.startTime, eventsTable.endTime), - ); + return db + .select() + .from(eventsTable) + .where(eq(eventsTable.lineId, lineId)) + .orderBy(eventsTable.startTime, eventsTable.endTime); } export async function getEventsByIds(eventIds: number[]) { if (eventIds.length === 0) { return []; } - return formatEvents( - await db - .select() - .from(eventsTable) - .where(inArray(eventsTable.id, eventIds)) - .orderBy(eventsTable.startTime, eventsTable.endTime), - ); -} - -function formatEvents( - events: ReturnType<(typeof eventsTableSelectSchema)["parse"]>[], -) { - return events.map((event) => ({ - ...event, - startTime: new Date(event.startTime), - endTime: new Date(event.endTime), - })) satisfies Event[]; + return db + .select() + .from(eventsTable) + .where(inArray(eventsTable.id, eventIds)) + .orderBy(eventsTable.startTime, eventsTable.endTime); } export async function getAllEvents() { @@ -122,42 +83,40 @@ export async function getEventsForDate(date: string) { const endOfDay = new Date(date); endOfDay.setDate(endOfDay.getDate() + 1); - return formatEvents( - await db - .select() - .from(eventsTable) - .where( - and( - gte(eventsTable.startTime, startOfDay.toISOString()), - lte(eventsTable.endTime, endOfDay.toISOString()), - ), - ) - .orderBy(eventsTable.startTime, eventsTable.endTime), - ); + return db + .select() + .from(eventsTable) + .where( + and( + gte(eventsTable.startTime, startOfDay), + lte(eventsTable.endTime, endOfDay), + ), + ) + .orderBy(eventsTable.startTime, eventsTable.endTime); } export async function getEventById(eventId: number) { - return formatEvents( - await db - .select() - .from(eventsTable) - .where(eq(eventsTable.id, eventId)) - .limit(1), - )[0]; + const event = await db + .select() + .from(eventsTable) + .where(eq(eventsTable.id, eventId)) + .limit(1); + if (event.length === 0) { + return null; + } + return event[0]; } export async function searchEvents(query: string) { - return formatEvents( - await db - .select() - .from(eventsTable) - .where( - or( - like(eventsTable.name, `%${query}%`), - like(eventsTable.title, `%${query}%`), - like(eventsTable.description, `%${query}%`), - ), - ) - .orderBy(eventsTable.startTime, eventsTable.endTime), - ); + return await db + .select() + .from(eventsTable) + .where( + or( + like(eventsTable.name, `%${query}%`), + like(eventsTable.title, `%${query}%`), + like(eventsTable.description, `%${query}%`), + ), + ) + .orderBy(eventsTable.startTime, eventsTable.endTime); } diff --git a/src/db/schema.ts b/src/db/schema.ts index 36dbb85..dfbbbc5 100644 --- a/src/db/schema.ts +++ b/src/db/schema.ts @@ -1,24 +1,23 @@ -import { int, sqliteTable, text } from "drizzle-orm/sqlite-core"; -import { createSelectSchema } from "drizzle-zod"; +import { integer, pgTable, text, timestamp } from "drizzle-orm/pg-core"; -export const linesTable = sqliteTable("linesTable", { - id: int().primaryKey(), - name: text().notNull(), - description: text().notNull(), +export const linesTable = pgTable("linesTable", { + id: integer().primaryKey(), + name: text().notNull(), + description: text().notNull(), }); -export const eventsTable = sqliteTable("eventsTable", { - id: int().primaryKey(), - startTime: text().notNull(), - endTime: text().notNull(), - name: text().notNull(), - title: text().notNull(), - description: text().notNull(), - type: text().notNull(), - lineId: int().notNull().references(() => linesTable.id, { - onDelete: "cascade", - onUpdate: "cascade", - }), +export const eventsTable = pgTable("eventsTable", { + id: integer().primaryKey(), + startTime: timestamp().notNull(), + endTime: timestamp().notNull(), + name: text().notNull(), + title: text().notNull(), + description: text().notNull(), + type: text().notNull(), + lineId: integer() + .notNull() + .references(() => linesTable.id, { + onDelete: "cascade", + onUpdate: "cascade", + }), }); - -export const eventsTableSelectSchema = createSelectSchema(eventsTable); diff --git a/src/instrumentation.ts b/src/instrumentation.ts new file mode 100644 index 0000000..52a920b --- /dev/null +++ b/src/instrumentation.ts @@ -0,0 +1,6 @@ +export async function register() { + if (process.env.NEXT_RUNTIME === "nodejs") { + const { runMigrations } = await import("./migrate"); + await runMigrations(); + } +} diff --git a/src/migrate.ts b/src/migrate.ts new file mode 100644 index 0000000..e295f3c --- /dev/null +++ b/src/migrate.ts @@ -0,0 +1,18 @@ +import { migrate as migratePostgres } from "drizzle-orm/node-postgres/migrator"; +import { migrate as migratePglite } from "drizzle-orm/pglite/migrator"; +import { getDbInstance } from "./db/getInstance"; + +export async function runMigrations() { + if (typeof window !== "undefined") { + return; + } + const { db, type } = getDbInstance(); + if (type === "pglite") { + await migratePglite(db, { migrationsFolder: "./drizzle" }); + } else if (type === "postgres") { + await migratePostgres(db, { migrationsFolder: "./drizzle" }); + } else { + throw new Error(`Unsupported database type: ${type}`); + } + console.log("Migrations completed successfully."); +}