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/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/backend/.env.example b/backend/.env.example new file mode 100644 index 0000000..7938570 --- /dev/null +++ b/backend/.env.example @@ -0,0 +1 @@ +DATABASE_URL=postgresql://postgres@localhost:5433/readr 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 diff --git a/backend/README.md b/backend/README.md index 605c5bb..6dbaaed 100644 --- a/backend/README.md +++ b/backend/README.md @@ -1,5 +1,110 @@ # Readr - backend +## 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. + +#### 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! + +### 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`. + +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. + ## GraphQL automatically generated types Whenever you change the GraphQL schema in the folder [./src/schema](./src/schema/), please run the following command: 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/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 7943b7d..213b75f 100644 --- a/backend/package.json +++ b/backend/package.json @@ -8,11 +8,26 @@ "pnpm": ">=12.4.2" }, "description": "", - "main": "dist/src/index.js", + "main": "dist/index.js", "scripts": { "prestart": "pnpm exec graphql-codegen", - "compile": "tsc", - "start": "pnpm compile && node ./dist/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", + "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: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": [], "author": "", @@ -30,8 +45,12 @@ "@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": { "@eddeee888/gcg-typescript-resolver-files": "^0.19.0", @@ -39,7 +58,11 @@ "@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", + "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 57fe326..58c4a04 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: '@eddeee888/gcg-typescript-resolver-files': specifier: ^0.19.0 @@ -200,9 +212,21 @@ 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 + esbuild: + specifier: 0.28.2 + version: 0.28.2 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 @@ -378,6 +402,9 @@ packages: resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==} engines: {node: '>=6.9.0'} + '@drizzle-team/brocli@0.12.1': + resolution: {integrity: sha512-yHwomvolVafvUXhy5TdiJVkuNCxXn+bKOs2aWiWEQh/5YaV97Oa/abJz3dmx7lxgIUsUlS1Jl4mEfvJPo3YsVw==} + '@eddeee888/gcg-server-config@0.6.0': resolution: {integrity: sha512-1VbpixLCLkuS56zj3ZL2GtOJvNIzOZgxVxLiIChhSmCJN+28tekIxPTKsX0lTaH4AlpvOF+eNLJa5dJJQmqyoA==} @@ -398,6 +425,318 @@ packages: resolution: {integrity: sha512-nl9cmUh8F+2/NNzpcLjzNqtaVn0m8XSzzxrPU2ByHhlz4Hqlc5QBfRsemZkoJMKrMviJXyJYdKZiUP1o70F3Kw==} engines: {node: '>=18.0.0'} + '@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] + '@fastify/busboy@3.2.2': resolution: {integrity: sha512-yXSS27qPExaXeuLvMRMXOLtpipzfQYNjG3FkunDWKGfMYjKuhFXko9CVzqxm8jcF+lmtS9Fd89QNdh9XDjnbNg==} @@ -799,6 +1138,10 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@js-temporal/polyfill@0.5.1': + resolution: {integrity: sha512-hloP58zRVCRSpgDxmqCWJNlizAlUgJFqG2ypq79DCvyv9tHjRYMDOcPFjzfl/A1/YxDvRCZz8wvZvmapQnKwFQ==} + engines: {node: '>=12'} + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -871,6 +1214,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==} @@ -1212,6 +1558,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==} + data-uri-to-buffer@4.0.1: resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} engines: {node: '>= 12'} @@ -1252,6 +1601,161 @@ packages: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=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'} @@ -1292,6 +1796,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 + escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -1354,6 +1868,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==} @@ -1377,6 +1896,9 @@ packages: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} + get-tsconfig@4.14.3: + resolution: {integrity: sha512-++QEw4DIY7WGoukz+/+A/8dGYPT9l9yIadnmSgZ8Rjr3YVSVDipQSO9CdnJo9ePqFqUUqh+wk9uIaoiAwsiPkA==} + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -1547,6 +2069,9 @@ packages: resolution: {integrity: sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==} hasBin: true + jsbi@4.3.2: + resolution: {integrity: sha512-9fqMSQbhJykSeii05nxKl4m6Eqn2P6rOlYiS+C5Dr/HPIU/7yZxu5qzbs40tgaFORiw2Amd0mirjxatXYMkIew==} + jsesc@3.1.0: resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} engines: {node: '>=6'} @@ -1743,6 +2268,40 @@ packages: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} + 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==} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -1754,6 +2313,22 @@ packages: 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'} @@ -1795,6 +2370,9 @@ packages: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + restore-cursor@5.1.0: resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} engines: {node: '>=18'} @@ -1883,6 +2461,10 @@ packages: resolution: {integrity: sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==} engines: {node: '>=20'} + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + sponge-case@2.0.3: resolution: {integrity: sha512-i4h9ZGRfxV6Xw3mpZSFOfbXjf0cQcYmssGWutgNIfFZ2VM+YIWfD71N/kjjwK6X/AAHzBr+rciEcn/L34S8TGw==} @@ -1941,6 +2523,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'} @@ -2021,6 +2608,10 @@ packages: utf-8-validate: optional: true + xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} @@ -2273,6 +2864,8 @@ snapshots: '@babel/helper-string-parser': 7.29.7 '@babel/helper-validator-identifier': 7.29.7 + '@drizzle-team/brocli@0.12.1': {} + '@eddeee888/gcg-server-config@0.6.0(graphql@17.0.2)': dependencies: '@graphql-codegen/typescript': 6.1.0(graphql@17.0.2) @@ -2316,6 +2909,162 @@ snapshots: '@whatwg-node/promise-helpers': 1.3.2 tslib: 2.8.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 + '@fastify/busboy@3.2.2': {} '@graphql-codegen/add@7.1.1(graphql@17.0.2)': @@ -2863,6 +3612,10 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.6.0 + '@js-temporal/polyfill@0.5.1': + dependencies: + jsbi: 4.3.2 + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -2940,6 +3693,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': {} @@ -3222,6 +3981,8 @@ snapshots: dependencies: tslib: 2.8.1 + csv-parse@7.0.3: {} + data-uri-to-buffer@4.0.1: {} dataloader@2.2.3: {} @@ -3248,6 +4009,21 @@ snapshots: dependencies: path-type: 4.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 @@ -3278,6 +4054,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 + escalade@3.2.0: {} escape-html@1.0.3: {} @@ -3373,6 +4207,9 @@ snapshots: fresh@2.0.0: {} + fsevents@2.3.3: + optional: true + function-bind@1.1.2: {} gensync@1.0.0-beta.2: {} @@ -3399,6 +4236,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 + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -3551,6 +4392,8 @@ snapshots: dependencies: argparse: 2.0.1 + jsbi@4.3.2: {} + jsesc@3.1.0: {} json-parse-even-better-errors@2.3.1: {} @@ -3711,12 +4554,57 @@ snapshots: path-type@4.0.0: {} + 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 + picocolors@1.1.1: {} picomatch@2.3.2: {} 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: @@ -3750,6 +4638,8 @@ snapshots: resolve-from@5.0.0: {} + resolve-pkg-maps@1.0.0: {} + restore-cursor@5.1.0: dependencies: onetime: 7.0.0 @@ -3867,6 +4757,8 @@ snapshots: ansi-styles: 6.2.3 is-fullwidth-code-point: 5.1.0 + split2@4.2.0: {} + sponge-case@2.0.3: {} statuses@2.0.2: {} @@ -3923,6 +4815,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 @@ -4009,6 +4907,8 @@ snapshots: ws@8.22.0: {} + xtend@4.0.2: {} + y18n@5.0.8: {} yallist@3.1.1: {} 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..eaf3a39 --- /dev/null +++ b/backend/src/db/index.ts @@ -0,0 +1,7 @@ +import "dotenv/config"; +import { drizzle } from "drizzle-orm/node-postgres"; +import { relations } from "~/db/schema"; + +const db = drizzle(process.env.DATABASE_URL!, { relations }); + +export { db }; diff --git a/backend/src/db/schema.ts b/backend/src/db/schema.ts new file mode 100644 index 0000000..614aa82 --- /dev/null +++ b/backend/src/db/schema.ts @@ -0,0 +1,76 @@ +import { defineRelations } from "drizzle-orm"; +import { + integer, + pgTable, + varchar, + text, + primaryKey, +} from "drizzle-orm/pg-core"; + +const books = pgTable("books", { + id: integer().primaryKey().generatedAlwaysAsIdentity(), + 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, +}; diff --git a/backend/src/db/seed/index.ts b/backend/src/db/seed/index.ts new file mode 100644 index 0000000..3459d6e --- /dev/null +++ b/backend/src/db/seed/index.ts @@ -0,0 +1,98 @@ +import { createReadStream } from "node:fs"; +import { resolve, dirname } from "node:path"; +import { fileURLToPath } from "node:url"; +import { parse } from "csv-parse"; +import { db } from "~/db"; +import { books, type InsertBook } from "~/db/schema"; + +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 || null, + author: book.author, + description: book.description || null, + language: book.language || null, + bookFormat: book.bookFormat, + edition: book.edition || null, + pages: book.pages, + publisher: book.publisher || null, + publishDate: book.publishDate || null, + coverImgUrl: book.coverImg || null, + }); + + 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...`, + ); + } +} + +// 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); 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 deleted file mode 100644 index 21d9899..0000000 --- a/backend/src/schema/books/resolvers/Query/books.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { QueryResolvers } from "./../../../types.generated.ts"; - -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..80fe730 --- /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 ? `%${args.title ?? ""}%` : undefined, + }, + series: { + ilike: args.series ? `%${args.series ?? ""}%` : undefined, + }, + author: { + ilike: args.author ? `%${args.author ?? ""}%` : undefined, + }, + language: { + ilike: args.language ? `%${args.language ?? ""}%` : undefined, + }, + bookFormat: { + ilike: args.bookFormat ? `%${args.bookFormat ?? ""}%` : undefined, + }, + publisher: { + ilike: args.publisher ? `%${args.publisher ?? ""}%` : undefined, + }, + }, + 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 36068db..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.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 { 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 = { diff --git a/backend/tsconfig.json b/backend/tsconfig.json index 19fbd3f..6e5d8c0 100644 --- a/backend/tsconfig.json +++ b/backend/tsconfig.json @@ -1,18 +1,19 @@ { "compilerOptions": { "rootDir": ".", - "outDir": "dist", "lib": ["es2023"], - "target": "es2023", - "module": "node18", - "moduleResolution": "node16", + "target": "es2022", + "module": "preserve", + "moduleResolution": "bundler", "esModuleInterop": true, "types": ["node"], - "rewriteRelativeImportExtensions": true, - "allowImportingTsExtensions": true, + "noEmit": true, "erasableSyntaxOnly": true, "verbatimModuleSyntax": true, "strict": true, - "skipLibCheck": true + "skipLibCheck": true, + "paths": { + "~/*": ["./src/*"] + } } } diff --git a/CONTRIBUTING.md b/frontend/README.md similarity index 92% rename from CONTRIBUTING.md rename to frontend/README.md index d60e063..068e082 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,18 +17,21 @@ 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 ```