feat: migrate to postgres, add migrations

This commit is contained in:
Matej Stieranka 2025-06-27 20:10:08 +02:00
parent 1d06466f16
commit 8841442191
16 changed files with 360 additions and 451 deletions

1
.npmrc
View file

@ -1 +0,0 @@
public-hoist-pattern[]=*@libsql*

View file

@ -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

View file

@ -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

View file

@ -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,
},
});

View file

@ -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
);

View file

@ -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;

View file

@ -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": {}
}
}

View file

@ -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
}
]

View file

@ -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/**/*"],
},
};

View file

@ -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
}
}

330
pnpm-lock.yaml generated
View file

@ -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==}

19
src/db/getInstance.ts Normal file
View file

@ -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),
};
}

View file

@ -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);
}

View file

@ -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);

6
src/instrumentation.ts Normal file
View file

@ -0,0 +1,6 @@
export async function register() {
if (process.env.NEXT_RUNTIME === "nodejs") {
const { runMigrations } = await import("./migrate");
await runMigrations();
}
}

18
src/migrate.ts Normal file
View file

@ -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.");
}