From fad0d24de0887babddfbec9865be5259a3d08868 Mon Sep 17 00:00:00 2001 From: Martin Alexander Kaminski Date: Sat, 3 Oct 2026 22:02:00 +0200 Subject: [PATCH 01/18] feat: add basic Drizzle setup --- backend/.env.example | 1 + backend/drizzle.config.ts | 11 + backend/package.json | 17 +- backend/pnpm-lock.yaml | 903 ++++++++++++++++++++++++++++++++++++ backend/pnpm-workspace.yaml | 3 +- backend/src/db/index.ts | 6 + backend/src/db/schema.ts | 8 + backend/tsconfig.json | 2 +- 8 files changed, 946 insertions(+), 5 deletions(-) create mode 100644 backend/.env.example create mode 100644 backend/drizzle.config.ts create mode 100644 backend/src/db/index.ts create mode 100644 backend/src/db/schema.ts diff --git a/backend/.env.example b/backend/.env.example new file mode 100644 index 0000000..ce81892 --- /dev/null +++ b/backend/.env.example @@ -0,0 +1 @@ +DATABASE_URL= diff --git a/backend/drizzle.config.ts b/backend/drizzle.config.ts new file mode 100644 index 0000000..bb9afba --- /dev/null +++ b/backend/drizzle.config.ts @@ -0,0 +1,11 @@ +import "dotenv/config"; +import { defineConfig } from "drizzle-kit"; + +export default defineConfig({ + out: "./drizzle", + schema: "./src/db/schema.ts", + dialect: "postgresql", + dbCredentials: { + url: process.env.DATABASE_URL!, + }, +}); diff --git a/backend/package.json b/backend/package.json index 6132bbf..b25c388 100644 --- a/backend/package.json +++ b/backend/package.json @@ -8,10 +8,14 @@ "pnpm": ">=12.4.2" }, "description": "", - "main": "dist/index.js", + "main": "dist/src/index.js", "scripts": { "compile": "tsc", - "start": "pnpm compile && node ./dist/index.js" + "start": "pnpm compile && node ./dist/src/index.js", + "db:push": "drizzle-kit push", + "db:generate": "drizzle-kit generate", + "db:migrate": "drizzle-kit migrate", + "db:studio": "drizzle-kit studio" }, "keywords": [], "author": "", @@ -29,14 +33,21 @@ "@as-integrations/express5": "^1.1.2", "@graphql-tools/merge": "^9.2.6", "cors": "^2.8.6", + "csv-parse": "^7.0.3", + "dotenv": "^18.0.5", + "drizzle-orm": "1.0.0-rc.4", "express": "^5.2.1", - "graphql": "^17.0.2" + "graphql": "^17.0.2", + "pg": "^8.23.1" }, "devDependencies": { "@types/cors": "^2.8.19", "@types/express": "^5.0.6", "@types/node": "^26.6.4", + "@types/pg": "^8.23.1", + "drizzle-kit": "1.0.0-rc.4", "prettier": "3.9.9", + "tsx": "^4.23.15", "typescript": "^7.0.2" } } diff --git a/backend/pnpm-lock.yaml b/backend/pnpm-lock.yaml index eb5d579..6a19368 100644 --- a/backend/pnpm-lock.yaml +++ b/backend/pnpm-lock.yaml @@ -178,12 +178,24 @@ importers: cors: specifier: ^2.8.6 version: 2.8.6 + csv-parse: + specifier: ^7.0.3 + version: 7.0.3 + dotenv: + specifier: ^18.0.5 + version: 18.0.5 + drizzle-orm: + specifier: 1.0.0-rc.4 + version: 1.0.0-rc.4(@types/pg@8.23.1)(pg@8.23.1) express: specifier: ^5.2.1 version: 5.2.1 graphql: specifier: ^17.0.2 version: 17.0.2 + pg: + specifier: ^8.23.1 + version: 8.23.1 devDependencies: '@types/cors': specifier: ^2.8.19 @@ -194,9 +206,18 @@ importers: '@types/node': specifier: ^26.6.4 version: 26.6.4 + '@types/pg': + specifier: ^8.23.1 + version: 8.23.1 + drizzle-kit: + specifier: 1.0.0-rc.4 + version: 1.0.0-rc.4 prettier: specifier: 3.9.9 version: 3.9.9 + tsx: + specifier: ^4.23.15 + version: 4.23.15 typescript: specifier: ^7.0.2 version: 7.0.2 @@ -293,6 +314,321 @@ packages: '@apollo/server': ^4.0.0 || ^5.0.0 express: ^5.0.0 + '@drizzle-team/brocli@0.12.1': + resolution: {integrity: sha512-yHwomvolVafvUXhy5TdiJVkuNCxXn+bKOs2aWiWEQh/5YaV97Oa/abJz3dmx7lxgIUsUlS1Jl4mEfvJPo3YsVw==} + + '@esbuild/aix-ppc64@0.25.12': + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/aix-ppc64@0.28.2': + resolution: {integrity: sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.12': + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm64@0.28.2': + resolution: {integrity: sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.12': + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-arm@0.28.2': + resolution: {integrity: sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.12': + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/android-x64@0.28.2': + resolution: {integrity: sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.12': + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-arm64@0.28.2': + resolution: {integrity: sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.12': + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/darwin-x64@0.28.2': + resolution: {integrity: sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.12': + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-arm64@0.28.2': + resolution: {integrity: sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.12': + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.28.2': + resolution: {integrity: sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.12': + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm64@0.28.2': + resolution: {integrity: sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.12': + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-arm@0.28.2': + resolution: {integrity: sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.12': + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-ia32@0.28.2': + resolution: {integrity: sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.12': + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.28.2': + resolution: {integrity: sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.12': + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-mips64el@0.28.2': + resolution: {integrity: sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.12': + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-ppc64@0.28.2': + resolution: {integrity: sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.12': + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-riscv64@0.28.2': + resolution: {integrity: sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.12': + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-s390x@0.28.2': + resolution: {integrity: sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.12': + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/linux-x64@0.28.2': + resolution: {integrity: sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.12': + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-arm64@0.28.2': + resolution: {integrity: sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.12': + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.28.2': + resolution: {integrity: sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.12': + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-arm64@0.28.2': + resolution: {integrity: sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.12': + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.28.2': + resolution: {integrity: sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.12': + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/openharmony-arm64@0.28.2': + resolution: {integrity: sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.25.12': + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.28.2': + resolution: {integrity: sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.12': + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-arm64@0.28.2': + resolution: {integrity: sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.12': + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-ia32@0.28.2': + resolution: {integrity: sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.12': + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@esbuild/win32-x64@0.28.2': + resolution: {integrity: sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@graphql-tools/merge@9.2.6': resolution: {integrity: sha512-M7iI/qoCnks6iITkimtdZgI2SrWOInr+QRWuhEsInaumzQGPd0pnWbRAVTRVcFN2Gu9hM2aS5kYzASC93d9epw==} engines: {node: '>=16.0.0'} @@ -316,6 +652,10 @@ packages: peerDependencies: graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + '@js-temporal/polyfill@0.5.1': + resolution: {integrity: sha512-hloP58zRVCRSpgDxmqCWJNlizAlUgJFqG2ypq79DCvyv9tHjRYMDOcPFjzfl/A1/YxDvRCZz8wvZvmapQnKwFQ==} + engines: {node: '>=12'} + '@protobufjs/aspromise@1.1.2': resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} @@ -370,6 +710,9 @@ packages: '@types/node@26.6.4': resolution: {integrity: sha512-ldVPDCzj7fsaGZrLB0NuHuTvJcsNasysBAqMolr/cgxrLd1xbqxIr3XJiPnHHJUCxj5sNF1vnRj9aWnrVh5Jcg==} + '@types/pg@8.23.1': + resolution: {integrity: sha512-fKVHpikPdg4GKks3JuLEhvwSyvwzF23hnabPy6DD8ljVbC7+6J5dQzdv4arV6jqq57djnMgs1HKBxX4P8aBI3A==} + '@types/qs@6.15.1': resolution: {integrity: sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==} @@ -565,6 +908,9 @@ packages: resolution: {integrity: sha512-Pcw1JTvZLSJH83iiGWt6fRcT+BjZlCDRVwYLbUcHzv/CRpB7r0MlSrGbIyQvVSNyGnbt7G4AXuyCiDR3POvZ1A==} engines: {node: '>=16.0.0'} + csv-parse@7.0.3: + resolution: {integrity: sha512-YFd3QM/yo17vH91L1IOZuvl09zM0zEEtdfTcOCKWcMdM++MNaQSyp09slXyFCLaPXvHstQFx/xC8myiFHSMUvw==} + debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -582,6 +928,161 @@ packages: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} + dotenv@18.0.5: + resolution: {integrity: sha512-aBrGvt6KhjxbEnatqMWOMidftwsrCiCvqh1yNIGB9QyF0J+VcWkKDMsvYOEm7ZS82oiV+BT1sTOtIlAmUlTpwQ==} + engines: {node: '>=12'} + hasBin: true + + drizzle-kit@1.0.0-rc.4: + resolution: {integrity: sha512-KZCpjRyu+oYHLj/UJogfFlOkWhHVkaEI2EOT1U3NDVXUzLoTyPjqwFxwOrlQxsY6jzRyxcz4EacqboGfhEeYrA==} + hasBin: true + + drizzle-orm@1.0.0-rc.4: + resolution: {integrity: sha512-BT+pf+qoiYHqltoA88Jmf6ilGMXPlpfE0hEJKc2adRtMCAl25Swk/t5gXcWxZNAwdtf3F5gCd2FpeOyP/pT0Hw==} + peerDependencies: + '@aws-sdk/client-rds-data': '>=3' + '@cloudflare/workers-types': '>=4' + '@effect/sql-d1': '>=4.0.0-beta.83 || >=4.0.0' + '@effect/sql-libsql': '>=4.0.0-beta.83 || >=4.0.0' + '@effect/sql-mysql2': '>=4.0.0-beta.83 || >=4.0.0' + '@effect/sql-pg': '>=4.0.0-beta.83 || >=4.0.0' + '@effect/sql-pglite': '>=4.0.0-beta.83 || >=4.0.0' + '@effect/sql-sqlite-bun': '>=4.0.0-beta.83 || >=4.0.0' + '@effect/sql-sqlite-do': '>=4.0.0-beta.83 || >=4.0.0' + '@effect/sql-sqlite-node': '>=4.0.0-beta.83 || >=4.0.0' + '@effect/sql-sqlite-wasm': '>=4.0.0-beta.83 || >=4.0.0' + '@electric-sql/pglite': '>=0.2.0' + '@libsql/client': '>=0.10.0' + '@libsql/client-wasm': '>=0.10.0' + '@neondatabase/serverless': '>=0.10.0' + '@op-engineering/op-sqlite': '>=2' + '@opentelemetry/api': ^1.4.1 + '@planetscale/database': '>=1.13' + '@sinclair/typebox': '>=0.34.8' + '@sqlitecloud/drivers': '>=1.0.653' + '@tidbcloud/serverless': '*' + '@tursodatabase/database': '>=0.6.0-pre.28 || >=0.6.0' + '@tursodatabase/database-common': '>=0.6.0-pre.28 || >=0.6.0' + '@tursodatabase/database-wasm': '>=0.6.0-pre.28 || >=0.6.0' + '@tursodatabase/serverless': '>=1.1.3' + '@tursodatabase/sync': '>=0.6.0-pre.28 || >=0.6.0' + '@types/better-sqlite3': '*' + '@types/mssql': ^9.1.4 + '@types/pg': '*' + '@types/sql.js': '*' + '@upstash/redis': '>=1.34.7' + '@vercel/postgres': '>=0.8.0' + '@xata.io/client': '*' + arktype: '>=2.0.0' + better-sqlite3: '>=9.3.0' + bun-types: '*' + effect: '>=4.0.0-beta.83 || >=4.0.0' + expo-sqlite: '>=14.0.0' + mssql: ^11.0.1 + mysql2: '>=2' + pg: '>=8' + postgres: '>=3' + sql.js: '>=1' + sqlite3: '>=5' + typebox: '>=1.0.0' + valibot: '>=1.0.0-beta.7' + zod: ^3.25.0 || ^4.0.0 + peerDependenciesMeta: + '@aws-sdk/client-rds-data': + optional: true + '@cloudflare/workers-types': + optional: true + '@effect/sql-d1': + optional: true + '@effect/sql-libsql': + optional: true + '@effect/sql-mysql2': + optional: true + '@effect/sql-pg': + optional: true + '@effect/sql-pglite': + optional: true + '@effect/sql-sqlite-bun': + optional: true + '@effect/sql-sqlite-do': + optional: true + '@effect/sql-sqlite-node': + optional: true + '@effect/sql-sqlite-wasm': + optional: true + '@electric-sql/pglite': + optional: true + '@libsql/client': + optional: true + '@libsql/client-wasm': + optional: true + '@neondatabase/serverless': + optional: true + '@op-engineering/op-sqlite': + optional: true + '@opentelemetry/api': + optional: true + '@planetscale/database': + optional: true + '@sinclair/typebox': + optional: true + '@sqlitecloud/drivers': + optional: true + '@tidbcloud/serverless': + optional: true + '@tursodatabase/database': + optional: true + '@tursodatabase/database-common': + optional: true + '@tursodatabase/database-wasm': + optional: true + '@tursodatabase/serverless': + optional: true + '@tursodatabase/sync': + optional: true + '@types/better-sqlite3': + optional: true + '@types/mssql': + optional: true + '@types/pg': + optional: true + '@types/sql.js': + optional: true + '@upstash/redis': + optional: true + '@vercel/postgres': + optional: true + '@xata.io/client': + optional: true + arktype: + optional: true + better-sqlite3: + optional: true + bun-types: + optional: true + effect: + optional: true + expo-sqlite: + optional: true + mssql: + optional: true + mysql2: + optional: true + pg: + optional: true + postgres: + optional: true + sql.js: + optional: true + sqlite3: + optional: true + typebox: + optional: true + valibot: + optional: true + zod: + optional: true + dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} @@ -605,6 +1106,16 @@ packages: resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} engines: {node: '>= 0.4'} + esbuild@0.25.12: + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} + engines: {node: '>=18'} + hasBin: true + + esbuild@0.28.2: + resolution: {integrity: sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==} + engines: {node: '>=18'} + hasBin: true + escape-html@1.0.3: resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} @@ -632,6 +1143,11 @@ packages: resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} engines: {node: '>= 0.8'} + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} @@ -643,6 +1159,9 @@ packages: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} + get-tsconfig@4.14.3: + resolution: {integrity: sha512-++QEw4DIY7WGoukz+/+A/8dGYPT9l9yIadnmSgZ8Rjr3YVSVDipQSO9CdnJo9ePqFqUUqh+wk9uIaoiAwsiPkA==} + gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} @@ -695,6 +1214,13 @@ packages: isarray@2.0.5: resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + jiti@2.7.0: + resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} + hasBin: true + + jsbi@4.3.2: + resolution: {integrity: sha512-9fqMSQbhJykSeii05nxKl4m6Eqn2P6rOlYiS+C5Dr/HPIU/7yZxu5qzbs40tgaFORiw2Amd0mirjxatXYMkIew==} + lodash.sortby@4.7.0: resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} @@ -758,10 +1284,60 @@ packages: path-to-regexp@8.4.2: resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} + pg-cloudflare@1.4.1: + resolution: {integrity: sha512-6PQbsFWZcp9EmJEwy5cGQ2La+AMWpP46lgbb8X+U/XsHIUweYDNCpeuKck5RxL2MdVFi7krbbEi5nX4Zh7JhrQ==} + + pg-connection-string@2.14.1: + resolution: {integrity: sha512-qR3kGNPBLpCNtz0evbKA0Y/MRFXwSSdT+pTJvYp/bXTcReZbvX1kzF0IyTc1QnxqF7AZbOeBhNL8R5mYQZV/MA==} + + pg-int8@1.0.1: + resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} + engines: {node: '>=4.0.0'} + + pg-pool@3.14.0: + resolution: {integrity: sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw==} + peerDependencies: + pg: '>=8.0' + + pg-protocol@1.16.1: + resolution: {integrity: sha512-p9VOFMiHB/ZbJATetbg+99PxssTVSQRnyuPSQ67mN1+1KBOjZaZ83ZQzltnxPhJwSsC3nwVjJ10DVJlerbFzLg==} + + pg-types@2.2.0: + resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} + engines: {node: '>=4'} + + pg@8.23.1: + resolution: {integrity: sha512-aL96AHANtWjPLDOLqnhx+ngp9+UK7ETEU8VJrDCGvsSSi/mGLcWYsS6Herg7lmaBJe4uwrfqsa7gTEFaSizDoQ==} + engines: {node: '>= 16.0.0'} + peerDependencies: + pg-native: '>=3.0.1' + peerDependenciesMeta: + pg-native: + optional: true + + pgpass@1.0.5: + resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==} + possible-typed-array-names@1.1.0: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} + postgres-array@2.0.0: + resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} + engines: {node: '>=4'} + + postgres-bytea@1.0.1: + resolution: {integrity: sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==} + 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'} + prettier@3.9.9: resolution: {integrity: sha512-Z/CJHIkdujO/OtN7nXUii0Rf3VT5SRuhjBA82Xvu2XhBUgX3nhP67T0LHceBdQLex7OOFGTox+Q5Yg8Jk2Qivg==} engines: {node: '>=14'} @@ -783,6 +1359,9 @@ packages: resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} engines: {node: '>= 0.10'} + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + retry@0.13.1: resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} engines: {node: '>= 4'} @@ -833,6 +1412,10 @@ packages: resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==} engines: {node: '>= 0.4'} + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + statuses@2.0.2: resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} engines: {node: '>= 0.8'} @@ -848,6 +1431,11 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tsx@4.23.15: + resolution: {integrity: sha512-Yiex1Ovn8z2xPpOWckIiysV1SSyRMY9BkLF++q0yKiDxCqRhosKfMg3janKkiLBwZ5c/YryloKwGZcrEmtwxKw==} + engines: {node: '>=18.0.0'} + hasBin: true + type-is@2.1.0: resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==} engines: {node: '>= 18'} @@ -883,6 +1471,10 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + snapshots: '@apollo/cache-control-types@1.0.3(graphql@17.0.2)': @@ -996,6 +1588,164 @@ snapshots: '@apollo/server': 5.5.1(graphql@17.0.2) express: 5.2.1 + '@drizzle-team/brocli@0.12.1': {} + + '@esbuild/aix-ppc64@0.25.12': + optional: true + + '@esbuild/aix-ppc64@0.28.2': + optional: true + + '@esbuild/android-arm64@0.25.12': + optional: true + + '@esbuild/android-arm64@0.28.2': + optional: true + + '@esbuild/android-arm@0.25.12': + optional: true + + '@esbuild/android-arm@0.28.2': + optional: true + + '@esbuild/android-x64@0.25.12': + optional: true + + '@esbuild/android-x64@0.28.2': + optional: true + + '@esbuild/darwin-arm64@0.25.12': + optional: true + + '@esbuild/darwin-arm64@0.28.2': + optional: true + + '@esbuild/darwin-x64@0.25.12': + optional: true + + '@esbuild/darwin-x64@0.28.2': + optional: true + + '@esbuild/freebsd-arm64@0.25.12': + optional: true + + '@esbuild/freebsd-arm64@0.28.2': + optional: true + + '@esbuild/freebsd-x64@0.25.12': + optional: true + + '@esbuild/freebsd-x64@0.28.2': + optional: true + + '@esbuild/linux-arm64@0.25.12': + optional: true + + '@esbuild/linux-arm64@0.28.2': + optional: true + + '@esbuild/linux-arm@0.25.12': + optional: true + + '@esbuild/linux-arm@0.28.2': + optional: true + + '@esbuild/linux-ia32@0.25.12': + optional: true + + '@esbuild/linux-ia32@0.28.2': + optional: true + + '@esbuild/linux-loong64@0.25.12': + optional: true + + '@esbuild/linux-loong64@0.28.2': + optional: true + + '@esbuild/linux-mips64el@0.25.12': + optional: true + + '@esbuild/linux-mips64el@0.28.2': + optional: true + + '@esbuild/linux-ppc64@0.25.12': + optional: true + + '@esbuild/linux-ppc64@0.28.2': + optional: true + + '@esbuild/linux-riscv64@0.25.12': + optional: true + + '@esbuild/linux-riscv64@0.28.2': + optional: true + + '@esbuild/linux-s390x@0.25.12': + optional: true + + '@esbuild/linux-s390x@0.28.2': + optional: true + + '@esbuild/linux-x64@0.25.12': + optional: true + + '@esbuild/linux-x64@0.28.2': + optional: true + + '@esbuild/netbsd-arm64@0.25.12': + optional: true + + '@esbuild/netbsd-arm64@0.28.2': + optional: true + + '@esbuild/netbsd-x64@0.25.12': + optional: true + + '@esbuild/netbsd-x64@0.28.2': + optional: true + + '@esbuild/openbsd-arm64@0.25.12': + optional: true + + '@esbuild/openbsd-arm64@0.28.2': + optional: true + + '@esbuild/openbsd-x64@0.25.12': + optional: true + + '@esbuild/openbsd-x64@0.28.2': + optional: true + + '@esbuild/openharmony-arm64@0.25.12': + optional: true + + '@esbuild/openharmony-arm64@0.28.2': + optional: true + + '@esbuild/sunos-x64@0.25.12': + optional: true + + '@esbuild/sunos-x64@0.28.2': + optional: true + + '@esbuild/win32-arm64@0.25.12': + optional: true + + '@esbuild/win32-arm64@0.28.2': + optional: true + + '@esbuild/win32-ia32@0.25.12': + optional: true + + '@esbuild/win32-ia32@0.28.2': + optional: true + + '@esbuild/win32-x64@0.25.12': + optional: true + + '@esbuild/win32-x64@0.28.2': + optional: true + '@graphql-tools/merge@9.2.6(graphql@17.0.2)': dependencies: '@graphql-tools/utils': 12.0.3(graphql@17.0.2) @@ -1021,6 +1771,10 @@ snapshots: dependencies: graphql: 17.0.2 + '@js-temporal/polyfill@0.5.1': + dependencies: + jsbi: 4.3.2 + '@protobufjs/aspromise@1.1.2': {} '@protobufjs/base64@1.1.2': {} @@ -1077,6 +1831,12 @@ snapshots: dependencies: undici-types: 8.9.0 + '@types/pg@8.23.1': + dependencies: + '@types/node': 26.6.4 + pg-protocol: 1.16.1 + pg-types: 2.2.0 + '@types/qs@6.15.1': {} '@types/range-parser@1.2.7': {} @@ -1219,6 +1979,8 @@ snapshots: dependencies: tslib: 2.8.1 + csv-parse@7.0.3: {} + debug@4.4.3: dependencies: ms: 2.1.3 @@ -1231,6 +1993,21 @@ snapshots: depd@2.0.0: {} + dotenv@18.0.5: {} + + drizzle-kit@1.0.0-rc.4: + dependencies: + '@drizzle-team/brocli': 0.12.1 + '@js-temporal/polyfill': 0.5.1 + esbuild: 0.25.12 + get-tsconfig: 4.14.3 + jiti: 2.7.0 + + drizzle-orm@1.0.0-rc.4(@types/pg@8.23.1)(pg@8.23.1): + optionalDependencies: + '@types/pg': 8.23.1 + pg: 8.23.1 + dunder-proto@1.0.1: dependencies: call-bind-apply-helpers: 1.0.2 @@ -1249,6 +2026,64 @@ snapshots: dependencies: es-errors: 1.3.0 + esbuild@0.25.12: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.12 + '@esbuild/android-arm': 0.25.12 + '@esbuild/android-arm64': 0.25.12 + '@esbuild/android-x64': 0.25.12 + '@esbuild/darwin-arm64': 0.25.12 + '@esbuild/darwin-x64': 0.25.12 + '@esbuild/freebsd-arm64': 0.25.12 + '@esbuild/freebsd-x64': 0.25.12 + '@esbuild/linux-arm': 0.25.12 + '@esbuild/linux-arm64': 0.25.12 + '@esbuild/linux-ia32': 0.25.12 + '@esbuild/linux-loong64': 0.25.12 + '@esbuild/linux-mips64el': 0.25.12 + '@esbuild/linux-ppc64': 0.25.12 + '@esbuild/linux-riscv64': 0.25.12 + '@esbuild/linux-s390x': 0.25.12 + '@esbuild/linux-x64': 0.25.12 + '@esbuild/netbsd-arm64': 0.25.12 + '@esbuild/netbsd-x64': 0.25.12 + '@esbuild/openbsd-arm64': 0.25.12 + '@esbuild/openbsd-x64': 0.25.12 + '@esbuild/openharmony-arm64': 0.25.12 + '@esbuild/sunos-x64': 0.25.12 + '@esbuild/win32-arm64': 0.25.12 + '@esbuild/win32-ia32': 0.25.12 + '@esbuild/win32-x64': 0.25.12 + + esbuild@0.28.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.28.2 + '@esbuild/android-arm': 0.28.2 + '@esbuild/android-arm64': 0.28.2 + '@esbuild/android-x64': 0.28.2 + '@esbuild/darwin-arm64': 0.28.2 + '@esbuild/darwin-x64': 0.28.2 + '@esbuild/freebsd-arm64': 0.28.2 + '@esbuild/freebsd-x64': 0.28.2 + '@esbuild/linux-arm': 0.28.2 + '@esbuild/linux-arm64': 0.28.2 + '@esbuild/linux-ia32': 0.28.2 + '@esbuild/linux-loong64': 0.28.2 + '@esbuild/linux-mips64el': 0.28.2 + '@esbuild/linux-ppc64': 0.28.2 + '@esbuild/linux-riscv64': 0.28.2 + '@esbuild/linux-s390x': 0.28.2 + '@esbuild/linux-x64': 0.28.2 + '@esbuild/netbsd-arm64': 0.28.2 + '@esbuild/netbsd-x64': 0.28.2 + '@esbuild/openbsd-arm64': 0.28.2 + '@esbuild/openbsd-x64': 0.28.2 + '@esbuild/openharmony-arm64': 0.28.2 + '@esbuild/sunos-x64': 0.28.2 + '@esbuild/win32-arm64': 0.28.2 + '@esbuild/win32-ia32': 0.28.2 + '@esbuild/win32-x64': 0.28.2 + escape-html@1.0.3: {} etag@1.8.1: {} @@ -1305,6 +2140,9 @@ snapshots: fresh@2.0.0: {} + fsevents@2.3.3: + optional: true + function-bind@1.1.2: {} get-intrinsic@1.3.0: @@ -1325,6 +2163,10 @@ snapshots: dunder-proto: 1.0.1 es-object-atoms: 1.1.2 + get-tsconfig@4.14.3: + dependencies: + resolve-pkg-maps: 1.0.0 + gopd@1.2.0: {} graphql@17.0.2: {} @@ -1369,6 +2211,10 @@ snapshots: isarray@2.0.5: {} + jiti@2.7.0: {} + + jsbi@4.3.2: {} + lodash.sortby@4.7.0: {} loglevel@1.9.2: {} @@ -1411,8 +2257,53 @@ snapshots: path-to-regexp@8.4.2: {} + pg-cloudflare@1.4.1: + optional: true + + pg-connection-string@2.14.1: {} + + pg-int8@1.0.1: {} + + pg-pool@3.14.0(pg@8.23.1): + dependencies: + pg: 8.23.1 + + pg-protocol@1.16.1: {} + + pg-types@2.2.0: + dependencies: + pg-int8: 1.0.1 + postgres-array: 2.0.0 + postgres-bytea: 1.0.1 + postgres-date: 1.0.7 + postgres-interval: 1.2.0 + + pg@8.23.1: + dependencies: + pg-connection-string: 2.14.1 + pg-pool: 3.14.0(pg@8.23.1) + pg-protocol: 1.16.1 + pg-types: 2.2.0 + pgpass: 1.0.5 + optionalDependencies: + pg-cloudflare: 1.4.1 + + pgpass@1.0.5: + dependencies: + split2: 4.2.0 + possible-typed-array-names@1.1.0: {} + postgres-array@2.0.0: {} + + postgres-bytea@1.0.1: {} + + postgres-date@1.0.7: {} + + postgres-interval@1.2.0: + dependencies: + xtend: 4.0.2 + prettier@3.9.9: {} proxy-addr@2.0.8: @@ -1434,6 +2325,8 @@ snapshots: iconv-lite: 0.7.3 unpipe: 1.0.0 + resolve-pkg-maps@1.0.0: {} + retry@0.13.1: {} router@2.2.0: @@ -1520,6 +2413,8 @@ snapshots: side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 + split2@4.2.0: {} + statuses@2.0.2: {} to-buffer@1.2.2: @@ -1532,6 +2427,12 @@ snapshots: tslib@2.8.1: {} + tsx@4.23.15: + dependencies: + esbuild: 0.28.2 + optionalDependencies: + fsevents: 2.3.3 + type-is@2.1.0: dependencies: content-type: 2.1.0 @@ -1586,3 +2487,5 @@ snapshots: has-tostringtag: 1.0.2 wrappy@1.0.2: {} + + xtend@4.0.2: {} diff --git a/backend/pnpm-workspace.yaml b/backend/pnpm-workspace.yaml index e23c7f6..e21f80f 100644 --- a/backend/pnpm-workspace.yaml +++ b/backend/pnpm-workspace.yaml @@ -1,2 +1,3 @@ allowBuilds: - "@apollo/protobufjs": false + "@apollo/protobufjs": true + esbuild: true diff --git a/backend/src/db/index.ts b/backend/src/db/index.ts new file mode 100644 index 0000000..74c3117 --- /dev/null +++ b/backend/src/db/index.ts @@ -0,0 +1,6 @@ +import "dotenv/config"; +import { drizzle } from "drizzle-orm/node-postgres"; + +const db = drizzle(process.env.DATABASE_URL!); + +export { db }; diff --git a/backend/src/db/schema.ts b/backend/src/db/schema.ts new file mode 100644 index 0000000..54e5161 --- /dev/null +++ b/backend/src/db/schema.ts @@ -0,0 +1,8 @@ +import { integer, pgTable, varchar } from "drizzle-orm/pg-core"; + +export const usersTable = pgTable("users", { + id: integer().primaryKey().generatedAlwaysAsIdentity(), + name: varchar({ length: 255 }).notNull(), + age: integer().notNull(), + email: varchar({ length: 255 }).notNull().unique(), +}); diff --git a/backend/tsconfig.json b/backend/tsconfig.json index 2f9c028..f2083af 100644 --- a/backend/tsconfig.json +++ b/backend/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "rootDir": "./src", + "rootDir": ".", "outDir": "dist", "lib": [ "es2023" From eee8907dfeaee5d8dbe31e7ea7d830704c02c710 Mon Sep 17 00:00:00 2001 From: Martin Alexander Kaminski Date: Sat, 3 Oct 2026 23:39:56 +0200 Subject: [PATCH 02/18] feat: add basic Drizzle database schema --- backend/src/db/schema.ts | 78 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 73 insertions(+), 5 deletions(-) diff --git a/backend/src/db/schema.ts b/backend/src/db/schema.ts index 54e5161..614aa82 100644 --- a/backend/src/db/schema.ts +++ b/backend/src/db/schema.ts @@ -1,8 +1,76 @@ -import { integer, pgTable, varchar } from "drizzle-orm/pg-core"; +import { defineRelations } from "drizzle-orm"; +import { + integer, + pgTable, + varchar, + text, + primaryKey, +} from "drizzle-orm/pg-core"; -export const usersTable = pgTable("users", { +const books = pgTable("books", { id: integer().primaryKey().generatedAlwaysAsIdentity(), - name: varchar({ length: 255 }).notNull(), - age: integer().notNull(), - email: varchar({ length: 255 }).notNull().unique(), + isbn: varchar("isbn", { length: 13 }).notNull(), + title: text("title").notNull(), + series: text("series"), + author: text("author").notNull(), + description: text("description"), + language: text("language"), + bookFormat: text("book_format").notNull(), + edition: text("edition"), + pages: integer("pages").notNull(), + publisher: text("publisher"), + publishDate: text("publish_date"), + coverImgUrl: text("cover_img_url"), }); + +const bookGenres = pgTable("book_genres", { + id: integer().primaryKey().generatedAlwaysAsIdentity(), + genre: text("genre").notNull(), +}); + +const bookToGenres = pgTable( + "book_to_genres", + { + bookId: integer("book_id") + .notNull() + .references(() => books.id, { onDelete: "cascade" }), + genreId: integer("genre_id") + .notNull() + .references(() => bookGenres.id, { onDelete: "cascade" }), + }, + (t) => [primaryKey({ columns: [t.bookId, t.genreId] })], +); + +const relations = defineRelations({ books, bookGenres, bookToGenres }, (r) => ({ + books: { + genres: r.many.bookGenres({ + from: r.books.id.through(r.bookToGenres.bookId), + to: r.bookGenres.id.through(r.bookToGenres.genreId), + }), + }, + bookGenres: { + books: r.many.books(), + }, +})); + +type InsertBook = typeof books.$inferInsert; +type SelectBook = typeof books.$inferSelect; + +type InsertBookGenre = typeof bookGenres.$inferInsert; +type SelectBookGenre = typeof bookGenres.$inferSelect; + +type InsertBookToGenre = typeof bookToGenres.$inferInsert; +type SelectBookToGenre = typeof bookToGenres.$inferSelect; + +export { + books, + bookGenres, + bookToGenres, + relations, + type InsertBook, + type SelectBook, + type InsertBookGenre, + type SelectBookGenre, + type InsertBookToGenre, + type SelectBookToGenre, +}; From 901a309b5cf9c1cd70dbf77b05f7fb133f5d5e61 Mon Sep 17 00:00:00 2001 From: Martin Alexander Kaminski Date: Sat, 3 Oct 2026 23:40:42 +0200 Subject: [PATCH 03/18] feat: setup commands for local Postgres setup --- backend/.env.example | 2 +- backend/package.json | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/backend/.env.example b/backend/.env.example index ce81892..7938570 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -1 +1 @@ -DATABASE_URL= +DATABASE_URL=postgresql://postgres@localhost:5433/readr diff --git a/backend/package.json b/backend/package.json index b25c388..ab1b8d3 100644 --- a/backend/package.json +++ b/backend/package.json @@ -12,10 +12,18 @@ "scripts": { "compile": "tsc", "start": "pnpm compile && node ./dist/src/index.js", + "db:init": "initdb -D db-data -U postgres -E UTF8 --auth=trust", + "db:start": "pg_ctl -D db-data -l ./db-data/db-data.log -w -o \"-p 5433 -c listen_addresses=127.0.0.1\" start", + "db:create": "createdb -h 127.0.0.1 -p 5433 -U postgres readr", + "db:stop": "pg_ctl -D db-data stop", + "db:status": "pg_ctl -D db-data status", + "db:psql": "psql -h 127.0.0.1 -p 5433 -U postgres readr", + "db:delete": "node -e \"require('child_process').spawnSync('pg_ctl',['-D','db-data','stop'],{stdio:'inherit'});require('fs').rmSync('db-data',{recursive:true,force:true})\"", "db:push": "drizzle-kit push", "db:generate": "drizzle-kit generate", "db:migrate": "drizzle-kit migrate", - "db:studio": "drizzle-kit studio" + "db:studio": "drizzle-kit studio", + "db:setup": "pnpm db:delete && pnpm run db:init && pnpm run db:start && pnpm run db:create && pnpm run db:push && pnpm run db:seed" }, "keywords": [], "author": "", From 98eb94245f7b40f5bcaee3a20f768c4868ee242d Mon Sep 17 00:00:00 2001 From: Martin Alexander Kaminski Date: Sat, 3 Oct 2026 23:40:53 +0200 Subject: [PATCH 04/18] feat: add seeding script for database --- .gitignore | 3 ++ backend/package.json | 1 + backend/src/db/seed/index.ts | 94 ++++++++++++++++++++++++++++++++++++ 3 files changed, 98 insertions(+) create mode 100644 backend/src/db/seed/index.ts diff --git a/.gitignore b/.gitignore index 29d2c79..3bf0c56 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,6 @@ dist-ssr # React Router .react-router/ build/ + +db-data/ +book-data.csv diff --git a/backend/package.json b/backend/package.json index ab1b8d3..07896ac 100644 --- a/backend/package.json +++ b/backend/package.json @@ -23,6 +23,7 @@ "db:generate": "drizzle-kit generate", "db:migrate": "drizzle-kit migrate", "db:studio": "drizzle-kit studio", + "db:seed": "tsx src/db/seed/index.ts", "db:setup": "pnpm db:delete && pnpm run db:init && pnpm run db:start && pnpm run db:create && pnpm run db:push && pnpm run db:seed" }, "keywords": [], diff --git a/backend/src/db/seed/index.ts b/backend/src/db/seed/index.ts new file mode 100644 index 0000000..c0569ff --- /dev/null +++ b/backend/src/db/seed/index.ts @@ -0,0 +1,94 @@ +import { createReadStream } from "node:fs"; +import { resolve, dirname } from "node:path"; +import { fileURLToPath } from "node:url"; +import { parse } from "csv-parse"; +import { db } from "../index.ts"; +import { books, type InsertBook } from "../schema.ts"; + +type BookRow = { + bookId: string; + title: string; + series: string | null; + author: string; + rating: number; + description: string | null; + language: string | null; + isbn: string; + genres: string[]; + characters: string[]; + bookFormat: string; + edition: string | null; + pages: number | string; + publisher: string | null; + publishDate: string | null; + firstPublishDate: string | null; + awards: string[]; + numRatings: number; + ratingsByStars: string[]; + likedPercent: number; + setting: string | null; + coverImg: string | null; + bbeScore: number; + bbeVotes: number; + price: number; +}; + +const BATCH_SIZE = 500; +const BOOK_COUNT = 52_478; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const parser = createReadStream(resolve(__dirname, "book-data.csv")).pipe( + parse({ columns: true }), +); + +let inserted = 0; +let booksToInsert: InsertBook[] = []; + +for await (const book of parser as AsyncIterable) { + if (typeof book.pages === "string") { + book.pages = parseInt(book.pages); + + if (isNaN(book.pages)) { + book.pages = 0; + } + } + + booksToInsert.push({ + isbn: book.isbn, + title: book.title, + series: book.series, + author: book.author, + description: book.description, + language: book.language, + bookFormat: book.bookFormat, + edition: book.edition, + pages: book.pages, + publisher: book.publisher, + publishDate: book.publishDate, + coverImgUrl: book.coverImg, + }); + + try { + if (booksToInsert.length >= BATCH_SIZE) { + await db.insert(books).values(booksToInsert); + booksToInsert = []; + inserted += BATCH_SIZE; + } + } catch (error) { + console.error(`Error inserting book with ISBN ${book.isbn}:`, error); + console.log(`Book data: ${JSON.stringify(book, null, 2)}`); + process.exit(1); + } + + if (inserted % 5000 === 0) { + console.clear(); + console.log( + `Inserted ${inserted} (${Math.round((inserted / BOOK_COUNT) * 100)}%) of ${BOOK_COUNT} books...`, + ); + } +} + +console.clear(); +console.log("Seeding complete!"); From 4e23eb9aec5496f024410c1d6d07729c50be6f6b Mon Sep 17 00:00:00 2001 From: Martin Alexander Kaminski Date: Sat, 3 Oct 2026 23:41:19 +0200 Subject: [PATCH 05/18] docs: add README to backend --- backend/README.md | 90 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 backend/README.md diff --git a/backend/README.md b/backend/README.md new file mode 100644 index 0000000..0820f0c --- /dev/null +++ b/backend/README.md @@ -0,0 +1,90 @@ +# Readr - backend + +## Getting started + +### Installing PostgreSQL + +As we do not use Docker, a local installation of PostgreSQL will be required. + +#### Linux + +Install Postgres using your package manager: + +```sh +apt install postgresql # For Debian-based distributions +dnf install postgresql-server # For Red Hat-based distributions +``` + +Make sure the PostgreSQL version is 18. + +#### Windows + +Download and install PostgreSQL from the official [download page](https://www.postgresql.org/download/). + +After installation, attempt to run these commands: + +```sh +postgres --version +initdb --version +``` + +If both succeed, you can proceed with the instructions. + +Otherwise, Postgres and its utilities need to be added to your `PATH`. + +If you need help, you can use [this guide](https://www.architectryan.com/2018/03/17/add-to-the-path-on-windows-10/). +The most likely path that you need to add is `C:\Program Files\PostgreSQL\18\bin`, however, if you chose a different installation folder, you need to adjust this path. + +#### MacOS + +The simplest way to install Postgres on MacOS is to use [homebrew](https://brew.sh/): + +```sh +brew install postgresql@18 +``` + +### Setting the environment variables + +Copy the [.env.example](./.env.example) and replace the `DATABASE_URL` if needed. + +### Downloading the book dataset + +The book dataset is quite large (~70 MB), so we prefer to not keep it inside the Git repository. + +The book dataset is available on Kaggle: +[Comprehensive Literary Greats Dataset](https://www.kaggle.com/datasets/thedevastator/comprehensive-literary-greats-dataset) + +The easiest way to download it is using cURL: + +```sh +curl -L -o comprehensive-literary-greats-dataset.zip https://www.kaggle.com/api/v1/datasets/download/thedevastator/comprehensive-literary-greats-dataset +``` + +After downloading: + +- Extract the `.csv` file +- Rename it to `book-data.csv` +- Move it to [./src/db/seed](./src/db/seed/) + +Now you're ready to insert the data to the database! + +### Using the database + +We have multiple commands defined in [package.json](./package.json) which will make our lives easier. + +The command `pnpm db:setup` does multiple steps automatically: + +- Deletes any previously running database using `pnpm db:delete` +- Initializes the database using `pnpm db:init` +- Starts the database using `pnpm db:start` +- Creates the SQL database itself using `pnpm db:create` +- Pushes the newest version of our SQL schema using `pnpm db:push` +- Seeds the database with our dataset using `pnpm db:seed` + +> [!CAUTION] +> `pnpm db:setup` and `pnpm db:delete` are destructive commands! +> They delete the local database running from the `db-data` folder. + +The database files will be placed in a folder named `db-data`. + +You can also use the `pnpm db:studio` command in order to inspect the database live. From 4793d2b31897775a4406c24e7adea35e1a2cf990 Mon Sep 17 00:00:00 2001 From: Martin Alexander Kaminski Date: Sat, 3 Oct 2026 23:43:52 +0200 Subject: [PATCH 06/18] docs: move documentation for frontend --- README.md | 4 ++-- CONTRIBUTING.md => frontend/README.md | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) rename CONTRIBUTING.md => frontend/README.md (87%) diff --git a/README.md b/README.md index 161a9e1..c2258e1 100644 --- a/README.md +++ b/README.md @@ -13,9 +13,9 @@ Visit our project here: [Readr](http://it2810-37.idi.ntnu.no/project2/) > [!NOTE] > The project is not deployed yet. -## Contributing and getting started +## Getting started -In order to get started with the project, check the [Contributing guidelines](./CONTRIBUTING.md). +In order to get started with the project, check the [README.md for the frontend](./frontend/README.md) and the [README.md for the backend](./backend/README.md). ## Testing diff --git a/CONTRIBUTING.md b/frontend/README.md similarity index 87% rename from CONTRIBUTING.md rename to frontend/README.md index 817b821..8aa4d5d 100644 --- a/CONTRIBUTING.md +++ b/frontend/README.md @@ -1,6 +1,4 @@ -# Contributing to Readr - -This is a guide on setting up and contributing to the project. +# Readr - frontend ## Getting started @@ -19,17 +17,20 @@ To learn about other ways to install pnpm, see the [pnpm installation instructio Next, install dependencies: ```sh +cd frontend pnpm i # or: pnpm install ``` Run the application: ```sh +cd frontend pnpm dev # or: pnpm run dev ``` In order to prepare a production-ready version of the app, run: ```sh +cd frontend pnpm build # or: pnpm run build ``` From 6153d57fba5c1b8d6bdc57cdbe9902bee1df2ca9 Mon Sep 17 00:00:00 2001 From: Martin Alexander Kaminski Date: Sat, 3 Oct 2026 23:44:06 +0200 Subject: [PATCH 07/18] docs: add comment about separate frontend guide in backend guide --- backend/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/backend/README.md b/backend/README.md index 0820f0c..7caa8b3 100644 --- a/backend/README.md +++ b/backend/README.md @@ -2,6 +2,9 @@ ## Getting started +This guide assumes you have set up `pnpm` and have a running frontend app. +If not, please follow the frontend guide in the [README.md for the frontend](../frontend/README.md) first. + ### Installing PostgreSQL As we do not use Docker, a local installation of PostgreSQL will be required. From 45e1227ae748650dcdbd79095d783b849eb32188 Mon Sep 17 00:00:00 2001 From: Martin Alexander Kaminski Date: Sun, 4 Oct 2026 19:07:45 +0200 Subject: [PATCH 08/18] chore: make Prettier ignore db-data --- backend/.prettierignore | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/.prettierignore b/backend/.prettierignore index 423d062..807885b 100644 --- a/backend/.prettierignore +++ b/backend/.prettierignore @@ -1,2 +1,3 @@ pnpm-lock.yaml *.generated.* +db-data From 32ff112bcae9e1304e10e7d8af30bb81758f1503 Mon Sep 17 00:00:00 2001 From: Martin Alexander Kaminski Date: Sun, 4 Oct 2026 19:10:08 +0200 Subject: [PATCH 09/18] feat: add proper build and dev scripts with esbuild --- backend/package.json | 9 ++++++--- backend/pnpm-lock.yaml | 3 +++ backend/tsconfig.json | 14 ++++++++------ 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/backend/package.json b/backend/package.json index 913043e..96a58e0 100644 --- a/backend/package.json +++ b/backend/package.json @@ -8,10 +8,12 @@ "pnpm": ">=12.4.2" }, "description": "", - "main": "dist/src/index.js", + "main": "dist/index.js", "scripts": { - "compile": "tsc", - "start": "pnpm compile && node ./dist/src/src/index.js", + "typecheck": "tsc", + "dev": "tsx src/index.ts", + "build": "esbuild src/index.ts --bundle --platform=node --format=esm --target=node24 --packages=external --outfile=dist/index.js", + "start": "pnpm build && node dist/index.js", "db:init": "initdb -D db-data -U postgres -E UTF8 --auth=trust", "db:start": "pg_ctl -D db-data -l ./db-data/db-data.log -w -o \"-p 5433 -c listen_addresses=127.0.0.1\" start", "db:create": "createdb -h 127.0.0.1 -p 5433 -U postgres readr", @@ -57,6 +59,7 @@ "@types/node": "^26.6.4", "@types/pg": "^8.23.1", "drizzle-kit": "1.0.0-rc.4", + "esbuild": "0.28.2", "prettier": "3.9.9", "tsx": "^4.23.15", "typescript": "^7.0.2" diff --git a/backend/pnpm-lock.yaml b/backend/pnpm-lock.yaml index 77f98f4..58c4a04 100644 --- a/backend/pnpm-lock.yaml +++ b/backend/pnpm-lock.yaml @@ -218,6 +218,9 @@ importers: drizzle-kit: specifier: 1.0.0-rc.4 version: 1.0.0-rc.4 + esbuild: + specifier: 0.28.2 + version: 0.28.2 prettier: specifier: 3.9.9 version: 3.9.9 diff --git a/backend/tsconfig.json b/backend/tsconfig.json index 19fbd3f..cebc19c 100644 --- a/backend/tsconfig.json +++ b/backend/tsconfig.json @@ -1,18 +1,20 @@ { "compilerOptions": { "rootDir": ".", - "outDir": "dist", "lib": ["es2023"], - "target": "es2023", - "module": "node18", - "moduleResolution": "node16", + "target": "es2022", + "module": "preserve", + "moduleResolution": "bundler", "esModuleInterop": true, "types": ["node"], - "rewriteRelativeImportExtensions": true, + "noEmit": true, "allowImportingTsExtensions": true, "erasableSyntaxOnly": true, "verbatimModuleSyntax": true, "strict": true, - "skipLibCheck": true + "skipLibCheck": true, + "paths": { + "~/*": ["./src/*"] + } } } From 7444f2be8270b574304826e3db958da96780e73f Mon Sep 17 00:00:00 2001 From: Martin Alexander Kaminski Date: Sun, 4 Oct 2026 19:13:38 +0200 Subject: [PATCH 10/18] refactor: use absolute imports, drop .ts extension --- backend/codegen.ts | 1 - backend/src/db/seed/index.ts | 4 ++-- backend/src/index.ts | 4 ++-- backend/src/schema/books/resolvers/Book.ts | 2 +- backend/src/schema/books/resolvers/Query/books.ts | 2 +- backend/src/schema/resolvers.generated.ts | 6 +++--- 6 files changed, 9 insertions(+), 10 deletions(-) diff --git a/backend/codegen.ts b/backend/codegen.ts index 6908909..cd59d61 100644 --- a/backend/codegen.ts +++ b/backend/codegen.ts @@ -6,7 +6,6 @@ const config: CodegenConfig = { schema: "**/schema.graphql", generates: { "src/schema": defineConfig({ - importExtension: ".ts", typesPluginsConfig: { useTypeImports: true }, }), }, diff --git a/backend/src/db/seed/index.ts b/backend/src/db/seed/index.ts index c0569ff..a51170a 100644 --- a/backend/src/db/seed/index.ts +++ b/backend/src/db/seed/index.ts @@ -2,8 +2,8 @@ import { createReadStream } from "node:fs"; import { resolve, dirname } from "node:path"; import { fileURLToPath } from "node:url"; import { parse } from "csv-parse"; -import { db } from "../index.ts"; -import { books, type InsertBook } from "../schema.ts"; +import { db } from "~/db"; +import { books, type InsertBook } from "~/db/schema"; type BookRow = { bookId: string; diff --git a/backend/src/index.ts b/backend/src/index.ts index 3eab8a8..3d6e6ea 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -4,8 +4,8 @@ import express, { type Express } from "express"; import http from "http"; import cors from "cors"; import { expressMiddleware } from "@as-integrations/express5"; -import { typeDefs } from "./schema/typeDefs.generated.ts"; -import { resolvers } from "./schema/resolvers.generated.ts"; +import { typeDefs } from "~/schema/typeDefs.generated"; +import { resolvers } from "~/schema/resolvers.generated"; type ApolloContext = { token?: string; diff --git a/backend/src/schema/books/resolvers/Book.ts b/backend/src/schema/books/resolvers/Book.ts index a01a63c..93bd77e 100644 --- a/backend/src/schema/books/resolvers/Book.ts +++ b/backend/src/schema/books/resolvers/Book.ts @@ -1,3 +1,3 @@ -import type { BookResolvers } from "./../../types.generated.ts"; +import type { BookResolvers } from "~/schema/types.generated"; export const Book: BookResolvers = {}; diff --git a/backend/src/schema/books/resolvers/Query/books.ts b/backend/src/schema/books/resolvers/Query/books.ts index 21d9899..a0b4efd 100644 --- a/backend/src/schema/books/resolvers/Query/books.ts +++ b/backend/src/schema/books/resolvers/Query/books.ts @@ -1,4 +1,4 @@ -import type { QueryResolvers } from "./../../../types.generated.ts"; +import type { QueryResolvers } from "~/schema/types.generated"; const bookData = [ { diff --git a/backend/src/schema/resolvers.generated.ts b/backend/src/schema/resolvers.generated.ts index 36068db..db1eec5 100644 --- a/backend/src/schema/resolvers.generated.ts +++ b/backend/src/schema/resolvers.generated.ts @@ -1,7 +1,7 @@ /* This file was automatically generated. DO NOT UPDATE MANUALLY. */ - import type { Resolvers } from './types.generated.ts'; - import { books as Query_books } from './books/resolvers/Query/books.ts'; -import { Book } from './books/resolvers/Book.ts'; + import type { Resolvers } from './types.generated'; + import { books as Query_books } from './books/resolvers/Query/books'; +import { Book } from './books/resolvers/Book'; export const resolvers: Resolvers = { Query: { books: Query_books }, From f5c4434ba1e0cc11cb3e49dfd8bfb300618c679e Mon Sep 17 00:00:00 2001 From: Martin Alexander Kaminski Date: Sun, 4 Oct 2026 19:20:09 +0200 Subject: [PATCH 11/18] feat: force no .ts extension in imports --- backend/tsconfig.json | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/tsconfig.json b/backend/tsconfig.json index cebc19c..6e5d8c0 100644 --- a/backend/tsconfig.json +++ b/backend/tsconfig.json @@ -8,7 +8,6 @@ "esModuleInterop": true, "types": ["node"], "noEmit": true, - "allowImportingTsExtensions": true, "erasableSyntaxOnly": true, "verbatimModuleSyntax": true, "strict": true, From f3b88a153246166d5a0062e079cb5d880a0d06aa Mon Sep 17 00:00:00 2001 From: Martin Alexander Kaminski Date: Sun, 4 Oct 2026 19:36:07 +0200 Subject: [PATCH 12/18] fix: insert last batch of books from dataset --- backend/src/db/seed/index.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/backend/src/db/seed/index.ts b/backend/src/db/seed/index.ts index a51170a..0f3f673 100644 --- a/backend/src/db/seed/index.ts +++ b/backend/src/db/seed/index.ts @@ -90,5 +90,9 @@ for await (const book of parser as AsyncIterable) { } } +// Insert last batch if there are any remaining books to insert +await db.insert(books).values(booksToInsert); + console.clear(); console.log("Seeding complete!"); +process.exit(0); From b6940ea1c2a5eeb29265ff7208d4b11e486c57fe Mon Sep 17 00:00:00 2001 From: Martin Alexander Kaminski Date: Sun, 4 Oct 2026 19:36:33 +0200 Subject: [PATCH 13/18] feat: add relations to db object to enable relational queries --- backend/src/db/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/src/db/index.ts b/backend/src/db/index.ts index 74c3117..eaf3a39 100644 --- a/backend/src/db/index.ts +++ b/backend/src/db/index.ts @@ -1,6 +1,7 @@ import "dotenv/config"; import { drizzle } from "drizzle-orm/node-postgres"; +import { relations } from "~/db/schema"; -const db = drizzle(process.env.DATABASE_URL!); +const db = drizzle(process.env.DATABASE_URL!, { relations }); export { db }; From 2f8aa761397331e915f1620a817c077dbe6191c2 Mon Sep 17 00:00:00 2001 From: Martin Alexander Kaminski Date: Sun, 4 Oct 2026 19:37:54 +0200 Subject: [PATCH 14/18] feat: add resolver for searching books --- .../src/schema/books/resolvers/Query/books.ts | 20 -------- .../books/resolvers/Query/searchBooks.ts | 33 +++++++++++++ backend/src/schema/books/schema.graphql | 26 ++++++++-- backend/src/schema/resolvers.generated.ts | 4 +- backend/src/schema/schema.generated.graphqls | 17 +++++-- backend/src/schema/typeDefs.generated.ts | 2 +- backend/src/schema/types.generated.ts | 49 ++++++++++++++++--- 7 files changed, 116 insertions(+), 35 deletions(-) delete mode 100644 backend/src/schema/books/resolvers/Query/books.ts create mode 100644 backend/src/schema/books/resolvers/Query/searchBooks.ts diff --git a/backend/src/schema/books/resolvers/Query/books.ts b/backend/src/schema/books/resolvers/Query/books.ts deleted file mode 100644 index a0b4efd..0000000 --- a/backend/src/schema/books/resolvers/Query/books.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { QueryResolvers } from "~/schema/types.generated"; - -const bookData = [ - { - title: "The Awakening", - author: "Kate Chopin", - }, - { - title: "City of Glass", - author: "Paul Auster", - }, -]; - -export const books: NonNullable = async ( - _parent, - _arg, - _ctx, -) => { - return bookData; -}; diff --git a/backend/src/schema/books/resolvers/Query/searchBooks.ts b/backend/src/schema/books/resolvers/Query/searchBooks.ts new file mode 100644 index 0000000..b041fb8 --- /dev/null +++ b/backend/src/schema/books/resolvers/Query/searchBooks.ts @@ -0,0 +1,33 @@ +import { db } from "~/db"; +import type { QueryResolvers } from "~/schema/types.generated"; + +export const searchBooks: NonNullable = async ( + _parent, + args, + _ctx, +) => { + return db.query.books.findMany({ + where: { + title: { + ilike: `%${args.title ?? ""}%`, + }, + series: { + ilike: `%${args.series ?? ""}%`, + }, + author: { + ilike: `%${args.author ?? ""}%`, + }, + language: { + ilike: `%${args.language ?? ""}%`, + }, + bookFormat: { + ilike: `%${args.bookFormat ?? ""}%`, + }, + publisher: { + ilike: `%${args.publisher ?? ""}%`, + }, + }, + limit: args.limit, + offset: args.offset, + }); +}; diff --git a/backend/src/schema/books/schema.graphql b/backend/src/schema/books/schema.graphql index fceecf9..50cdc2f 100644 --- a/backend/src/schema/books/schema.graphql +++ b/backend/src/schema/books/schema.graphql @@ -1,8 +1,28 @@ type Book { - title: String - author: String + id: Int! + isbn: String! + title: String! + series: String + author: String! + description: String + language: String + bookFormat: String! + edition: String + pages: Int! + publisher: String + publishDate: String + coverImgUrl: String } extend type Query { - books: [Book] + searchBooks( + title: String + series: String + author: String + language: String + bookFormat: String + publisher: String + limit: Int = 12 + offset: Int = 0 + ): [Book] } diff --git a/backend/src/schema/resolvers.generated.ts b/backend/src/schema/resolvers.generated.ts index db1eec5..5f9d8f6 100644 --- a/backend/src/schema/resolvers.generated.ts +++ b/backend/src/schema/resolvers.generated.ts @@ -1,9 +1,9 @@ /* This file was automatically generated. DO NOT UPDATE MANUALLY. */ import type { Resolvers } from './types.generated'; - import { books as Query_books } from './books/resolvers/Query/books'; + import { searchBooks as Query_searchBooks } from './books/resolvers/Query/searchBooks'; import { Book } from './books/resolvers/Book'; export const resolvers: Resolvers = { - Query: { books: Query_books }, + Query: { searchBooks: Query_searchBooks }, Book: Book diff --git a/backend/src/schema/schema.generated.graphqls b/backend/src/schema/schema.generated.graphqls index 68c6f7d..3eb7209 100644 --- a/backend/src/schema/schema.generated.graphqls +++ b/backend/src/schema/schema.generated.graphqls @@ -1,8 +1,19 @@ type Book { - author: String - title: String + author: String! + bookFormat: String! + coverImgUrl: String + description: String + edition: String + id: Int! + isbn: String! + language: String + pages: Int! + publishDate: String + publisher: String + series: String + title: String! } type Query { - books: [Book] + searchBooks(author: String, bookFormat: String, language: String, limit: Int = 12, offset: Int = 0, publisher: String, series: String, title: String): [Book] } \ No newline at end of file diff --git a/backend/src/schema/typeDefs.generated.ts b/backend/src/schema/typeDefs.generated.ts index 2c56e78..33982ad 100644 --- a/backend/src/schema/typeDefs.generated.ts +++ b/backend/src/schema/typeDefs.generated.ts @@ -1,2 +1,2 @@ import type { DocumentNode } from 'graphql'; - export const typeDefs = {"kind":"Document","definitions":[{"name":{"kind":"Name","value":"Query"},"kind":"ObjectTypeDefinition","fields":[{"kind":"FieldDefinition","name":{"kind":"Name","value":"books"},"type":{"kind":"ListType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Book"}}}}],"directives":[],"interfaces":[]},{"kind":"ObjectTypeDefinition","name":{"kind":"Name","value":"Book"},"fields":[{"kind":"FieldDefinition","name":{"kind":"Name","value":"title"},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"FieldDefinition","name":{"kind":"Name","value":"author"},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}]},{"kind":"SchemaDefinition","operationTypes":[{"kind":"OperationTypeDefinition","type":{"kind":"NamedType","name":{"kind":"Name","value":"Query"}},"operation":"query"}]}]} as unknown as DocumentNode \ No newline at end of file + export const typeDefs = {"kind":"Document","definitions":[{"name":{"kind":"Name","value":"Query"},"kind":"ObjectTypeDefinition","fields":[{"kind":"FieldDefinition","name":{"kind":"Name","value":"searchBooks"},"arguments":[{"kind":"InputValueDefinition","name":{"kind":"Name","value":"title"},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"InputValueDefinition","name":{"kind":"Name","value":"series"},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"InputValueDefinition","name":{"kind":"Name","value":"author"},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"InputValueDefinition","name":{"kind":"Name","value":"language"},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"InputValueDefinition","name":{"kind":"Name","value":"bookFormat"},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"InputValueDefinition","name":{"kind":"Name","value":"publisher"},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"InputValueDefinition","name":{"kind":"Name","value":"limit"},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}},"defaultValue":{"kind":"IntValue","value":"12"}},{"kind":"InputValueDefinition","name":{"kind":"Name","value":"offset"},"type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}},"defaultValue":{"kind":"IntValue","value":"0"}}],"type":{"kind":"ListType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Book"}}}}],"directives":[],"interfaces":[]},{"kind":"ObjectTypeDefinition","name":{"kind":"Name","value":"Book"},"fields":[{"kind":"FieldDefinition","name":{"kind":"Name","value":"id"},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}},{"kind":"FieldDefinition","name":{"kind":"Name","value":"isbn"},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"FieldDefinition","name":{"kind":"Name","value":"title"},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"FieldDefinition","name":{"kind":"Name","value":"series"},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"FieldDefinition","name":{"kind":"Name","value":"author"},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"FieldDefinition","name":{"kind":"Name","value":"description"},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"FieldDefinition","name":{"kind":"Name","value":"language"},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"FieldDefinition","name":{"kind":"Name","value":"bookFormat"},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"FieldDefinition","name":{"kind":"Name","value":"edition"},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"FieldDefinition","name":{"kind":"Name","value":"pages"},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Int"}}}},{"kind":"FieldDefinition","name":{"kind":"Name","value":"publisher"},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"FieldDefinition","name":{"kind":"Name","value":"publishDate"},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"FieldDefinition","name":{"kind":"Name","value":"coverImgUrl"},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}]},{"kind":"SchemaDefinition","operationTypes":[{"kind":"OperationTypeDefinition","type":{"kind":"NamedType","name":{"kind":"Name","value":"Query"}},"operation":"query"}]}]} as unknown as DocumentNode \ No newline at end of file diff --git a/backend/src/schema/types.generated.ts b/backend/src/schema/types.generated.ts index f1b29bc..e7dc620 100644 --- a/backend/src/schema/types.generated.ts +++ b/backend/src/schema/types.generated.ts @@ -1,6 +1,7 @@ import type { GraphQLResolveInfo } from 'graphql'; export type Maybe = T | null | undefined; export type InputMaybe = T | null | undefined; +export type RequireFields = Omit & { [P in K]-?: NonNullable }; /** All built-in and custom scalars, mapped to their actual values */ export type Scalars = { ID: { input: string; output: string; } @@ -12,13 +13,36 @@ export type Scalars = { export type Book = { __typename?: 'Book'; - author?: Maybe; - title?: Maybe; + author: Scalars['String']['output']; + bookFormat: Scalars['String']['output']; + coverImgUrl?: Maybe; + description?: Maybe; + edition?: Maybe; + id: Scalars['Int']['output']; + isbn: Scalars['String']['output']; + language?: Maybe; + pages: Scalars['Int']['output']; + publishDate?: Maybe; + publisher?: Maybe; + series?: Maybe; + title: Scalars['String']['output']; }; export type Query = { __typename?: 'Query'; - books?: Maybe>>; + searchBooks?: Maybe>>; +}; + + +export type QuerysearchBooksArgs = { + author?: InputMaybe; + bookFormat?: InputMaybe; + language?: InputMaybe; + limit?: InputMaybe; + offset?: InputMaybe; + publisher?: InputMaybe; + series?: InputMaybe; + title?: InputMaybe; }; @@ -96,6 +120,7 @@ export type DirectiveResolverFn, TParent = export type ResolversTypes = { Book: ResolverTypeWrapper; String: ResolverTypeWrapper; + Int: ResolverTypeWrapper; Query: ResolverTypeWrapper>; Boolean: ResolverTypeWrapper; }; @@ -104,17 +129,29 @@ export type ResolversTypes = { export type ResolversParentTypes = { Book: Book; String: Scalars['String']['output']; + Int: Scalars['Int']['output']; Query: Record; Boolean: Scalars['Boolean']['output']; }; export type BookResolvers = { - author?: Resolver, ParentType, ContextType>; - title?: Resolver, ParentType, ContextType>; + author?: Resolver; + bookFormat?: Resolver; + coverImgUrl?: Resolver, ParentType, ContextType>; + description?: Resolver, ParentType, ContextType>; + edition?: Resolver, ParentType, ContextType>; + id?: Resolver; + isbn?: Resolver; + language?: Resolver, ParentType, ContextType>; + pages?: Resolver; + publishDate?: Resolver, ParentType, ContextType>; + publisher?: Resolver, ParentType, ContextType>; + series?: Resolver, ParentType, ContextType>; + title?: Resolver; }; export type QueryResolvers = { - books?: Resolver>>, ParentType, ContextType>; + searchBooks?: Resolver>>, ParentType, ContextType, RequireFields>; }; export type Resolvers = { From ebff1585d483b9b61bbbb5a6e09bb6d5d90e2367 Mon Sep 17 00:00:00 2001 From: Martin Alexander Kaminski Date: Wed, 7 Oct 2026 20:57:40 +0200 Subject: [PATCH 15/18] docs: add note that running db and backend must be done in correct folder --- backend/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/README.md b/backend/README.md index 0a6cf81..6610ea8 100644 --- a/backend/README.md +++ b/backend/README.md @@ -73,6 +73,8 @@ Now you're ready to insert the data to the database! ### Using the database +When running the database and backend, please go into the backend folder using `cd backend`. + We have multiple commands defined in [package.json](./package.json) which will make our lives easier. The command `pnpm db:setup` does multiple steps automatically: From b36f8d375e1a79f8ec218ddc4a1b05a6947ba27c Mon Sep 17 00:00:00 2001 From: Martin Alexander Kaminski Date: Wed, 7 Oct 2026 21:00:24 +0200 Subject: [PATCH 16/18] docs: add comment on setting up the dotenv values --- backend/README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/backend/README.md b/backend/README.md index 6610ea8..6dbaaed 100644 --- a/backend/README.md +++ b/backend/README.md @@ -71,6 +71,17 @@ After downloading: Now you're ready to insert the data to the database! +### Setting up the database + +Copy the `.env.example` file and create your own, private and gitignored version: + +```sh +cd backend +cp .env.example .env +``` + +The default values work fine for local development. + ### Using the database When running the database and backend, please go into the backend folder using `cd backend`. From 0e3fd63e55e0764dc5422b0517b0ed6ea02fb64a Mon Sep 17 00:00:00 2001 From: Martin Alexander Kaminski Date: Wed, 7 Oct 2026 21:05:18 +0200 Subject: [PATCH 17/18] fix: store NULL values in DB instead of empty strings --- backend/src/db/seed/index.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/backend/src/db/seed/index.ts b/backend/src/db/seed/index.ts index 0f3f673..3459d6e 100644 --- a/backend/src/db/seed/index.ts +++ b/backend/src/db/seed/index.ts @@ -58,16 +58,16 @@ for await (const book of parser as AsyncIterable) { booksToInsert.push({ isbn: book.isbn, title: book.title, - series: book.series, + series: book.series || null, author: book.author, - description: book.description, - language: book.language, + description: book.description || null, + language: book.language || null, bookFormat: book.bookFormat, - edition: book.edition, + edition: book.edition || null, pages: book.pages, - publisher: book.publisher, - publishDate: book.publishDate, - coverImgUrl: book.coverImg, + publisher: book.publisher || null, + publishDate: book.publishDate || null, + coverImgUrl: book.coverImg || null, }); try { From 721d29c0eb1d7e11d98923dab5bdd8a15a6dcb7c Mon Sep 17 00:00:00 2001 From: Martin Alexander Kaminski Date: Wed, 7 Oct 2026 21:05:47 +0200 Subject: [PATCH 18/18] fix: do not search for books with empty strings Only filter those columns we actually care about --- .../src/schema/books/resolvers/Query/searchBooks.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/backend/src/schema/books/resolvers/Query/searchBooks.ts b/backend/src/schema/books/resolvers/Query/searchBooks.ts index b041fb8..80fe730 100644 --- a/backend/src/schema/books/resolvers/Query/searchBooks.ts +++ b/backend/src/schema/books/resolvers/Query/searchBooks.ts @@ -9,22 +9,22 @@ export const searchBooks: NonNullable = async ( return db.query.books.findMany({ where: { title: { - ilike: `%${args.title ?? ""}%`, + ilike: args.title ? `%${args.title ?? ""}%` : undefined, }, series: { - ilike: `%${args.series ?? ""}%`, + ilike: args.series ? `%${args.series ?? ""}%` : undefined, }, author: { - ilike: `%${args.author ?? ""}%`, + ilike: args.author ? `%${args.author ?? ""}%` : undefined, }, language: { - ilike: `%${args.language ?? ""}%`, + ilike: args.language ? `%${args.language ?? ""}%` : undefined, }, bookFormat: { - ilike: `%${args.bookFormat ?? ""}%`, + ilike: args.bookFormat ? `%${args.bookFormat ?? ""}%` : undefined, }, publisher: { - ilike: `%${args.publisher ?? ""}%`, + ilike: args.publisher ? `%${args.publisher ?? ""}%` : undefined, }, }, limit: args.limit,