From 7c87867b419b9296d77b8475a8104280422b1693 Mon Sep 17 00:00:00 2001 From: lukas Date: Fri, 21 Aug 2026 20:29:37 +0200 Subject: [PATCH] :sparkles: first version --- .dockerignore | 20 + .env.example | 2 + .gitea/workflows/ci.yml | 153 ++ .idea/dataSources.xml | 12 + .idea/jsonCatalog.xml | 6 + Dockerfile | 31 + docker-compose.prod.yml | 35 + drizzle/0000_milky_squadron_sinister.sql | 73 + drizzle/0001_melted_supernaut.sql | 2 + drizzle/0002_deep_dorian_gray.sql | 8 + drizzle/0003_big_jack_flag.sql | 1 + drizzle/0004_wide_thunderbolt.sql | 1 + drizzle/0005_nice_stark_industries.sql | 2 + drizzle/0006_uneven_captain_cross.sql | 2 + drizzle/0007_dusty_payback.sql | 1 + drizzle/0008_even_toxin.sql | 1 + drizzle/0009_nosy_alice.sql | 1 + drizzle/0010_common_masque.sql | 1 + drizzle/0011_cloudy_gwen_stacy.sql | 1 + drizzle/meta/0000_snapshot.json | 459 ++++++ drizzle/meta/0001_snapshot.json | 473 ++++++ drizzle/meta/0002_snapshot.json | 520 +++++++ drizzle/meta/0003_snapshot.json | 527 +++++++ drizzle/meta/0004_snapshot.json | 533 +++++++ drizzle/meta/0005_snapshot.json | 546 +++++++ drizzle/meta/0006_snapshot.json | 558 +++++++ drizzle/meta/0007_snapshot.json | 564 +++++++ drizzle/meta/0008_snapshot.json | 570 +++++++ drizzle/meta/0009_snapshot.json | 576 +++++++ drizzle/meta/0010_snapshot.json | 582 +++++++ drizzle/meta/0011_snapshot.json | 589 +++++++ drizzle/meta/_journal.json | 90 ++ package-lock.json | 143 +- package.json | 3 +- src/components/action-menu.tsx | 108 ++ src/components/app-sidebar.tsx | 128 ++ src/components/confirm-dialog.tsx | 99 ++ src/components/reservation-charts.tsx | 321 ++++ .../reservation-confirmed-modal.tsx | 108 ++ src/components/reservation-datetime-field.tsx | 362 +++++ src/components/reservation-dialog.tsx | 456 ++++++ src/components/site-header.tsx | 30 +- src/components/staff-member-detail-dialog.tsx | 83 + src/components/staff-tabs.tsx | 51 + src/components/status-badge.tsx | 5 + src/components/success-modal.tsx | 67 + src/components/table-detail-dialog.tsx | 68 + src/db/schema.ts | 56 +- src/lib/auth-instance.ts | 13 + src/lib/auth.ts | 11 +- src/lib/datetime.ts | 7 +- src/lib/email.ts | 275 ++++ src/lib/opening-hours.ts | 143 ++ src/lib/phone.ts | 15 + src/lib/reservations.ts | 404 ++++- src/lib/server-validator.ts | 17 + src/lib/settings.ts | 64 + src/lib/staff.ts | 6 +- src/lib/table-image.ts | 24 + src/routeTree.gen.ts | 84 +- src/routes/impressum.tsx | 2 +- src/routes/index.tsx | 260 +++- src/routes/staff.index.tsx | 468 +++--- src/routes/staff.login.tsx | 74 +- src/routes/staff.settings.tsx | 373 +++++ src/routes/staff.tables.tsx | 338 ++++ src/routes/staff.team.tsx | 195 ++- src/routes/stornieren.$token.tsx | 96 ++ src/styles.css | 1354 ++++++++++++++++- vite.config.ts | 3 + 70 files changed, 12771 insertions(+), 483 deletions(-) create mode 100644 .dockerignore create mode 100644 .gitea/workflows/ci.yml create mode 100644 .idea/dataSources.xml create mode 100644 .idea/jsonCatalog.xml create mode 100644 Dockerfile create mode 100644 docker-compose.prod.yml create mode 100644 drizzle/0000_milky_squadron_sinister.sql create mode 100644 drizzle/0001_melted_supernaut.sql create mode 100644 drizzle/0002_deep_dorian_gray.sql create mode 100644 drizzle/0003_big_jack_flag.sql create mode 100644 drizzle/0004_wide_thunderbolt.sql create mode 100644 drizzle/0005_nice_stark_industries.sql create mode 100644 drizzle/0006_uneven_captain_cross.sql create mode 100644 drizzle/0007_dusty_payback.sql create mode 100644 drizzle/0008_even_toxin.sql create mode 100644 drizzle/0009_nosy_alice.sql create mode 100644 drizzle/0010_common_masque.sql create mode 100644 drizzle/0011_cloudy_gwen_stacy.sql create mode 100644 drizzle/meta/0000_snapshot.json create mode 100644 drizzle/meta/0001_snapshot.json create mode 100644 drizzle/meta/0002_snapshot.json create mode 100644 drizzle/meta/0003_snapshot.json create mode 100644 drizzle/meta/0004_snapshot.json create mode 100644 drizzle/meta/0005_snapshot.json create mode 100644 drizzle/meta/0006_snapshot.json create mode 100644 drizzle/meta/0007_snapshot.json create mode 100644 drizzle/meta/0008_snapshot.json create mode 100644 drizzle/meta/0009_snapshot.json create mode 100644 drizzle/meta/0010_snapshot.json create mode 100644 drizzle/meta/0011_snapshot.json create mode 100644 drizzle/meta/_journal.json create mode 100644 src/components/action-menu.tsx create mode 100644 src/components/app-sidebar.tsx create mode 100644 src/components/confirm-dialog.tsx create mode 100644 src/components/reservation-charts.tsx create mode 100644 src/components/reservation-confirmed-modal.tsx create mode 100644 src/components/reservation-datetime-field.tsx create mode 100644 src/components/reservation-dialog.tsx create mode 100644 src/components/staff-member-detail-dialog.tsx create mode 100644 src/components/staff-tabs.tsx create mode 100644 src/components/status-badge.tsx create mode 100644 src/components/success-modal.tsx create mode 100644 src/components/table-detail-dialog.tsx create mode 100644 src/lib/email.ts create mode 100644 src/lib/opening-hours.ts create mode 100644 src/lib/phone.ts create mode 100644 src/lib/server-validator.ts create mode 100644 src/lib/settings.ts create mode 100644 src/lib/table-image.ts create mode 100644 src/routes/staff.settings.tsx create mode 100644 src/routes/staff.tables.tsx create mode 100644 src/routes/stornieren.$token.tsx diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..6763bb9 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,20 @@ +node_modules +dist +dist-ssr +.git +.gitea +.github +.idea +.vscode +.env +.env.* +!.env.example +*.local +.nitro +.tanstack +.wrangler +.output +.vinxi +__unconfig* +todos.json +README.md diff --git a/.env.example b/.env.example index 82574c6..1efbd5c 100644 --- a/.env.example +++ b/.env.example @@ -3,3 +3,5 @@ BETTER_AUTH_SECRET=replace-with-a-long-random-string BETTER_AUTH_URL=http://localhost:3000 STAFF_SEED_EMAIL=admin@steakhaus-meppen.de STAFF_SEED_PASSWORD=replace-with-a-strong-password +RESEND_API_KEY=replace-with-your-resend-api-key +RESERVATIONS_FROM_EMAIL=Steakhaus bei Mirko diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..2d17c2e --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,153 @@ +name: CI + +on: + push: + branches: + - main + - proof + workflow_dispatch: + +env: + REGISTRY: git.lukas-harat.com + IMAGE_NAME: ${{ github.repository }} + +permissions: + contents: read + packages: write + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Typecheck + run: npx tsc --noEmit + + build: + needs: test + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ secrets.USERNAME }} + password: ${{ secrets.TOKEN }} + + - name: Build and push image + uses: docker/build-push-action@v6 + with: + context: . + push: true + tags: | + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} + cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache + cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache,mode=max + + deploy: + needs: build + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Deploy to TrueNAS + env: + DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }} + DEPLOY_PORT: ${{ secrets.DEPLOY_PORT }} + DEPLOY_USER: ${{ secrets.DEPLOY_USER }} + DEPLOY_PATH: ${{ secrets.DEPLOY_PATH }} + DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }} + + POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }} + BETTER_AUTH_SECRET: ${{ secrets.BETTER_AUTH_SECRET }} + BETTER_AUTH_URL: ${{ secrets.BETTER_AUTH_URL }} + RESEND_API_KEY: ${{ secrets.RESEND_API_KEY }} + RESERVATIONS_FROM_EMAIL: ${{ secrets.RESERVATIONS_FROM_EMAIL }} + + IMAGE_TAG: ${{ github.sha }} + + run: | + set -euo pipefail + + mkdir -p ~/.ssh + + printf '%s\n' "$DEPLOY_SSH_KEY" > ~/.ssh/deploy_key + chmod 600 ~/.ssh/deploy_key + + ssh-keyscan \ + -p "${DEPLOY_PORT:-22}" \ + -H "$DEPLOY_HOST" \ + >> ~/.ssh/known_hosts + + echo "Creating deployment directory if needed..." + + ssh \ + -i ~/.ssh/deploy_key \ + -p "${DEPLOY_PORT:-22}" \ + "$DEPLOY_USER@$DEPLOY_HOST" \ + "mkdir -p '$DEPLOY_PATH'" + + echo "Uploading compose file..." + + scp \ + -i ~/.ssh/deploy_key \ + -P "${DEPLOY_PORT:-22}" \ + docker-compose.prod.yml \ + "$DEPLOY_USER@$DEPLOY_HOST:$DEPLOY_PATH/docker-compose.yml" + + echo "Deploying..." + + ssh \ + -i ~/.ssh/deploy_key \ + -p "${DEPLOY_PORT:-22}" \ + "$DEPLOY_USER@$DEPLOY_HOST" \ + "POSTGRES_PASSWORD=$(printf '%q' "$POSTGRES_PASSWORD") \ + BETTER_AUTH_SECRET=$(printf '%q' "$BETTER_AUTH_SECRET") \ + BETTER_AUTH_URL=$(printf '%q' "$BETTER_AUTH_URL") \ + RESEND_API_KEY=$(printf '%q' "$RESEND_API_KEY") \ + RESERVATIONS_FROM_EMAIL=$(printf '%q' "$RESERVATIONS_FROM_EMAIL") \ + IMAGE_TAG=$(printf '%q' "$IMAGE_TAG") \ + bash -c ' + set -euo pipefail + + cd \"$DEPLOY_PATH\" + + echo \"Pulling images...\" + docker compose pull + + echo \"Starting PostgreSQL...\" + docker compose up -d postgres + + echo \"Running database migrations...\" + docker compose run --rm app npx drizzle-kit migrate + + echo \"Starting application...\" + docker compose up -d + + echo \"Cleaning unused images...\" + docker image prune -f + + echo \"Deployment complete.\" + '" diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml new file mode 100644 index 0000000..7cd46bd --- /dev/null +++ b/.idea/dataSources.xml @@ -0,0 +1,12 @@ + + + + + postgresql + true + org.postgresql.Driver + jdbc:postgresql://localhost:5432/postgres + $ProjectFileDir$ + + + \ No newline at end of file diff --git a/.idea/jsonCatalog.xml b/.idea/jsonCatalog.xml new file mode 100644 index 0000000..0b181a9 --- /dev/null +++ b/.idea/jsonCatalog.xml @@ -0,0 +1,6 @@ + + + + false + + \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..b93b09b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,31 @@ +# syntax=docker/dockerfile:1 + +FROM node:22-alpine AS builder +WORKDIR /app + +COPY package.json package-lock.json ./ +RUN npm ci + +COPY . . + +# Dummy values so the build-time env checks in src/db/index.ts and +# src/lib/auth-instance.ts don't throw while bundling. The real values are +# supplied at container runtime. +ENV DATABASE_URL=postgres://build:build@localhost:5432/build \ + BETTER_AUTH_SECRET=build-time-placeholder \ + BETTER_AUTH_URL=http://localhost:3000 + +RUN npm run build + +FROM node:22-alpine AS runner +WORKDIR /app +ENV NODE_ENV=production + +# This build has no standalone Node server entrypoint — dist/server/server.js +# only exports a fetch handler. `vite preview` (via the tanstackStart plugin) +# is the supported way to serve it, and it re-resolves the app's router entry +# from src/ at startup, so the full source tree (not just dist/) has to ship. +COPY --from=builder /app ./ + +EXPOSE 3000 +CMD ["npm", "run", "preview", "--", "--host", "0.0.0.0", "--port", "3000"] diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml new file mode 100644 index 0000000..69770c5 --- /dev/null +++ b/docker-compose.prod.yml @@ -0,0 +1,35 @@ +services: + app: + image: git.lukas-harat.com/lukas/promo_steakhouse:${IMAGE_TAG:-latest} + restart: unless-stopped + ports: + - '3003:3000' + environment: + DATABASE_URL: postgres://steakhouse:${POSTGRES_PASSWORD}@postgres:5432/steakhouse + BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET} + BETTER_AUTH_URL: ${BETTER_AUTH_URL} + STAFF_SEED_EMAIL: ${STAFF_SEED_EMAIL} + STAFF_SEED_PASSWORD: ${STAFF_SEED_PASSWORD} + RESEND_API_KEY: ${RESEND_API_KEY} + RESERVATIONS_FROM_EMAIL: ${RESERVATIONS_FROM_EMAIL} + depends_on: + postgres: + condition: service_healthy + + postgres: + image: postgres:16-alpine + restart: unless-stopped + environment: + POSTGRES_USER: steakhouse + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_DB: steakhouse + volumes: + - steakhouse-postgres-data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U steakhouse -d steakhouse"] + interval: 5s + timeout: 5s + retries: 10 + +volumes: + steakhouse-postgres-data: diff --git a/drizzle/0000_milky_squadron_sinister.sql b/drizzle/0000_milky_squadron_sinister.sql new file mode 100644 index 0000000..cbcdd82 --- /dev/null +++ b/drizzle/0000_milky_squadron_sinister.sql @@ -0,0 +1,73 @@ +CREATE TABLE "account" ( + "id" text PRIMARY KEY NOT NULL, + "account_id" text NOT NULL, + "provider_id" text NOT NULL, + "user_id" text NOT NULL, + "access_token" text, + "refresh_token" text, + "id_token" text, + "access_token_expires_at" timestamp, + "refresh_token_expires_at" timestamp, + "scope" text, + "password" text, + "created_at" timestamp NOT NULL, + "updated_at" timestamp NOT NULL +); +--> statement-breakpoint +CREATE TABLE "reservations" ( + "id" serial PRIMARY KEY NOT NULL, + "table_id" integer NOT NULL, + "customer_name" text NOT NULL, + "customer_email" text NOT NULL, + "party_size" integer NOT NULL, + "reservation_time" timestamp with time zone NOT NULL, + "created_at" timestamp with time zone DEFAULT now() NOT NULL, + CONSTRAINT "reservations_table_id_reservation_time_unique" UNIQUE("table_id","reservation_time") +); +--> statement-breakpoint +CREATE TABLE "restaurant_tables" ( + "id" serial PRIMARY KEY NOT NULL, + "name" text NOT NULL, + "seats" integer NOT NULL +); +--> statement-breakpoint +CREATE TABLE "session" ( + "id" text PRIMARY KEY NOT NULL, + "expires_at" timestamp NOT NULL, + "token" text NOT NULL, + "created_at" timestamp NOT NULL, + "updated_at" timestamp NOT NULL, + "ip_address" text, + "user_agent" text, + "user_id" text NOT NULL, + "impersonated_by" text, + CONSTRAINT "session_token_unique" UNIQUE("token") +); +--> statement-breakpoint +CREATE TABLE "user" ( + "id" text PRIMARY KEY NOT NULL, + "name" text NOT NULL, + "email" text NOT NULL, + "email_verified" boolean NOT NULL, + "image" text, + "created_at" timestamp NOT NULL, + "updated_at" timestamp NOT NULL, + "role" text, + "banned" boolean, + "ban_reason" text, + "ban_expires" timestamp, + CONSTRAINT "user_email_unique" UNIQUE("email") +); +--> statement-breakpoint +CREATE TABLE "verification" ( + "id" text PRIMARY KEY NOT NULL, + "identifier" text NOT NULL, + "value" text NOT NULL, + "expires_at" timestamp NOT NULL, + "created_at" timestamp, + "updated_at" timestamp +); +--> statement-breakpoint +ALTER TABLE "account" ADD CONSTRAINT "account_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "reservations" ADD CONSTRAINT "reservations_table_id_restaurant_tables_id_fk" FOREIGN KEY ("table_id") REFERENCES "public"."restaurant_tables"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "session" ADD CONSTRAINT "session_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action; \ No newline at end of file diff --git a/drizzle/0001_melted_supernaut.sql b/drizzle/0001_melted_supernaut.sql new file mode 100644 index 0000000..7771034 --- /dev/null +++ b/drizzle/0001_melted_supernaut.sql @@ -0,0 +1,2 @@ +ALTER TABLE "reservations" ADD COLUMN "cancel_token" text DEFAULT gen_random_uuid() NOT NULL;--> statement-breakpoint +ALTER TABLE "reservations" ADD CONSTRAINT "reservations_cancel_token_unique" UNIQUE("cancel_token"); \ No newline at end of file diff --git a/drizzle/0002_deep_dorian_gray.sql b/drizzle/0002_deep_dorian_gray.sql new file mode 100644 index 0000000..d680ecf --- /dev/null +++ b/drizzle/0002_deep_dorian_gray.sql @@ -0,0 +1,8 @@ +CREATE TABLE "restaurant_settings" ( + "id" serial PRIMARY KEY NOT NULL, + "reservation_interval_minutes" integer DEFAULT 15 NOT NULL, + "reservation_duration_minutes" integer DEFAULT 90 NOT NULL, + "min_lead_time_minutes" integer DEFAULT 60 NOT NULL +); +--> statement-breakpoint +ALTER TABLE "reservations" ADD COLUMN "duration_minutes" integer DEFAULT 90 NOT NULL; \ No newline at end of file diff --git a/drizzle/0003_big_jack_flag.sql b/drizzle/0003_big_jack_flag.sql new file mode 100644 index 0000000..a51ec26 --- /dev/null +++ b/drizzle/0003_big_jack_flag.sql @@ -0,0 +1 @@ +ALTER TABLE "restaurant_settings" ADD COLUMN "max_lead_time_days" integer DEFAULT 60 NOT NULL; \ No newline at end of file diff --git a/drizzle/0004_wide_thunderbolt.sql b/drizzle/0004_wide_thunderbolt.sql new file mode 100644 index 0000000..2b1f490 --- /dev/null +++ b/drizzle/0004_wide_thunderbolt.sql @@ -0,0 +1 @@ +ALTER TABLE "restaurant_tables" ADD COLUMN "image_url" text; \ No newline at end of file diff --git a/drizzle/0005_nice_stark_industries.sql b/drizzle/0005_nice_stark_industries.sql new file mode 100644 index 0000000..7f53a2e --- /dev/null +++ b/drizzle/0005_nice_stark_industries.sql @@ -0,0 +1,2 @@ +ALTER TABLE "restaurant_settings" ADD COLUMN "table_display_mode" text DEFAULT 'individual' NOT NULL;--> statement-breakpoint +ALTER TABLE "restaurant_settings" ADD COLUMN "table_overview_image_url" text; \ No newline at end of file diff --git a/drizzle/0006_uneven_captain_cross.sql b/drizzle/0006_uneven_captain_cross.sql new file mode 100644 index 0000000..042cbfc --- /dev/null +++ b/drizzle/0006_uneven_captain_cross.sql @@ -0,0 +1,2 @@ +ALTER TABLE "restaurant_tables" ADD COLUMN "marker_x" double precision;--> statement-breakpoint +ALTER TABLE "restaurant_tables" ADD COLUMN "marker_y" double precision; \ No newline at end of file diff --git a/drizzle/0007_dusty_payback.sql b/drizzle/0007_dusty_payback.sql new file mode 100644 index 0000000..0114f2b --- /dev/null +++ b/drizzle/0007_dusty_payback.sql @@ -0,0 +1 @@ +ALTER TABLE "restaurant_tables" ADD COLUMN "table_number" integer; \ No newline at end of file diff --git a/drizzle/0008_even_toxin.sql b/drizzle/0008_even_toxin.sql new file mode 100644 index 0000000..51ca032 --- /dev/null +++ b/drizzle/0008_even_toxin.sql @@ -0,0 +1 @@ +ALTER TABLE "reservations" ADD COLUMN "message" text; \ No newline at end of file diff --git a/drizzle/0009_nosy_alice.sql b/drizzle/0009_nosy_alice.sql new file mode 100644 index 0000000..e0ee462 --- /dev/null +++ b/drizzle/0009_nosy_alice.sql @@ -0,0 +1 @@ +ALTER TABLE "reservations" ADD COLUMN "customer_phone" text; \ No newline at end of file diff --git a/drizzle/0010_common_masque.sql b/drizzle/0010_common_masque.sql new file mode 100644 index 0000000..17ed09e --- /dev/null +++ b/drizzle/0010_common_masque.sql @@ -0,0 +1 @@ +ALTER TABLE "user" ADD COLUMN "phone" text; \ No newline at end of file diff --git a/drizzle/0011_cloudy_gwen_stacy.sql b/drizzle/0011_cloudy_gwen_stacy.sql new file mode 100644 index 0000000..50370f2 --- /dev/null +++ b/drizzle/0011_cloudy_gwen_stacy.sql @@ -0,0 +1 @@ +ALTER TABLE "reservations" ADD COLUMN "status" text DEFAULT 'confirmed' NOT NULL; \ No newline at end of file diff --git a/drizzle/meta/0000_snapshot.json b/drizzle/meta/0000_snapshot.json new file mode 100644 index 0000000..5d4d16f --- /dev/null +++ b/drizzle/meta/0000_snapshot.json @@ -0,0 +1,459 @@ +{ + "id": "fc9539d6-3015-4ae3-bbf2-7a1a9f4de482", + "prevId": "00000000-0000-0000-0000-000000000000", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.reservations": { + "name": "reservations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "table_id": { + "name": "table_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "customer_name": { + "name": "customer_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "customer_email": { + "name": "customer_email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "party_size": { + "name": "party_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "reservation_time": { + "name": "reservation_time", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "reservations_table_id_restaurant_tables_id_fk": { + "name": "reservations_table_id_restaurant_tables_id_fk", + "tableFrom": "reservations", + "tableTo": "restaurant_tables", + "columnsFrom": [ + "table_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "reservations_table_id_reservation_time_unique": { + "name": "reservations_table_id_reservation_time_unique", + "nullsNotDistinct": false, + "columns": [ + "table_id", + "reservation_time" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.restaurant_tables": { + "name": "restaurant_tables", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "seats": { + "name": "seats", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "impersonated_by": { + "name": "impersonated_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "banned": { + "name": "banned", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ban_expires": { + "name": "ban_expires", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/0001_snapshot.json b/drizzle/meta/0001_snapshot.json new file mode 100644 index 0000000..155d02e --- /dev/null +++ b/drizzle/meta/0001_snapshot.json @@ -0,0 +1,473 @@ +{ + "id": "65d581ab-b3c7-4ffb-8a79-afcf26be5af5", + "prevId": "fc9539d6-3015-4ae3-bbf2-7a1a9f4de482", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.reservations": { + "name": "reservations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "table_id": { + "name": "table_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "customer_name": { + "name": "customer_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "customer_email": { + "name": "customer_email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "party_size": { + "name": "party_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "reservation_time": { + "name": "reservation_time", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "cancel_token": { + "name": "cancel_token", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "reservations_table_id_restaurant_tables_id_fk": { + "name": "reservations_table_id_restaurant_tables_id_fk", + "tableFrom": "reservations", + "tableTo": "restaurant_tables", + "columnsFrom": [ + "table_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "reservations_cancel_token_unique": { + "name": "reservations_cancel_token_unique", + "nullsNotDistinct": false, + "columns": [ + "cancel_token" + ] + }, + "reservations_table_id_reservation_time_unique": { + "name": "reservations_table_id_reservation_time_unique", + "nullsNotDistinct": false, + "columns": [ + "table_id", + "reservation_time" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.restaurant_tables": { + "name": "restaurant_tables", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "seats": { + "name": "seats", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "impersonated_by": { + "name": "impersonated_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "banned": { + "name": "banned", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ban_expires": { + "name": "ban_expires", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/0002_snapshot.json b/drizzle/meta/0002_snapshot.json new file mode 100644 index 0000000..98624a7 --- /dev/null +++ b/drizzle/meta/0002_snapshot.json @@ -0,0 +1,520 @@ +{ + "id": "5e900bf2-030d-4eb3-a8ab-3b4c7d85e203", + "prevId": "65d581ab-b3c7-4ffb-8a79-afcf26be5af5", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.reservations": { + "name": "reservations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "table_id": { + "name": "table_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "customer_name": { + "name": "customer_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "customer_email": { + "name": "customer_email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "party_size": { + "name": "party_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "reservation_time": { + "name": "reservation_time", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "duration_minutes": { + "name": "duration_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 90 + }, + "cancel_token": { + "name": "cancel_token", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "reservations_table_id_restaurant_tables_id_fk": { + "name": "reservations_table_id_restaurant_tables_id_fk", + "tableFrom": "reservations", + "tableTo": "restaurant_tables", + "columnsFrom": [ + "table_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "reservations_cancel_token_unique": { + "name": "reservations_cancel_token_unique", + "nullsNotDistinct": false, + "columns": [ + "cancel_token" + ] + }, + "reservations_table_id_reservation_time_unique": { + "name": "reservations_table_id_reservation_time_unique", + "nullsNotDistinct": false, + "columns": [ + "table_id", + "reservation_time" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.restaurant_settings": { + "name": "restaurant_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "reservation_interval_minutes": { + "name": "reservation_interval_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 15 + }, + "reservation_duration_minutes": { + "name": "reservation_duration_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 90 + }, + "min_lead_time_minutes": { + "name": "min_lead_time_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 60 + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.restaurant_tables": { + "name": "restaurant_tables", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "seats": { + "name": "seats", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "impersonated_by": { + "name": "impersonated_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "banned": { + "name": "banned", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ban_expires": { + "name": "ban_expires", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/0003_snapshot.json b/drizzle/meta/0003_snapshot.json new file mode 100644 index 0000000..7cd9cca --- /dev/null +++ b/drizzle/meta/0003_snapshot.json @@ -0,0 +1,527 @@ +{ + "id": "d03b9103-cb6a-4968-a016-c04270ec176a", + "prevId": "5e900bf2-030d-4eb3-a8ab-3b4c7d85e203", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.reservations": { + "name": "reservations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "table_id": { + "name": "table_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "customer_name": { + "name": "customer_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "customer_email": { + "name": "customer_email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "party_size": { + "name": "party_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "reservation_time": { + "name": "reservation_time", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "duration_minutes": { + "name": "duration_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 90 + }, + "cancel_token": { + "name": "cancel_token", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "reservations_table_id_restaurant_tables_id_fk": { + "name": "reservations_table_id_restaurant_tables_id_fk", + "tableFrom": "reservations", + "tableTo": "restaurant_tables", + "columnsFrom": [ + "table_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "reservations_cancel_token_unique": { + "name": "reservations_cancel_token_unique", + "nullsNotDistinct": false, + "columns": [ + "cancel_token" + ] + }, + "reservations_table_id_reservation_time_unique": { + "name": "reservations_table_id_reservation_time_unique", + "nullsNotDistinct": false, + "columns": [ + "table_id", + "reservation_time" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.restaurant_settings": { + "name": "restaurant_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "reservation_interval_minutes": { + "name": "reservation_interval_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 15 + }, + "reservation_duration_minutes": { + "name": "reservation_duration_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 90 + }, + "min_lead_time_minutes": { + "name": "min_lead_time_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 60 + }, + "max_lead_time_days": { + "name": "max_lead_time_days", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 60 + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.restaurant_tables": { + "name": "restaurant_tables", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "seats": { + "name": "seats", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "impersonated_by": { + "name": "impersonated_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "banned": { + "name": "banned", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ban_expires": { + "name": "ban_expires", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/0004_snapshot.json b/drizzle/meta/0004_snapshot.json new file mode 100644 index 0000000..e353d1f --- /dev/null +++ b/drizzle/meta/0004_snapshot.json @@ -0,0 +1,533 @@ +{ + "id": "05eac8a1-b172-4e4d-90d2-a409ecde610a", + "prevId": "d03b9103-cb6a-4968-a016-c04270ec176a", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.reservations": { + "name": "reservations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "table_id": { + "name": "table_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "customer_name": { + "name": "customer_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "customer_email": { + "name": "customer_email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "party_size": { + "name": "party_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "reservation_time": { + "name": "reservation_time", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "duration_minutes": { + "name": "duration_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 90 + }, + "cancel_token": { + "name": "cancel_token", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "reservations_table_id_restaurant_tables_id_fk": { + "name": "reservations_table_id_restaurant_tables_id_fk", + "tableFrom": "reservations", + "tableTo": "restaurant_tables", + "columnsFrom": [ + "table_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "reservations_cancel_token_unique": { + "name": "reservations_cancel_token_unique", + "nullsNotDistinct": false, + "columns": [ + "cancel_token" + ] + }, + "reservations_table_id_reservation_time_unique": { + "name": "reservations_table_id_reservation_time_unique", + "nullsNotDistinct": false, + "columns": [ + "table_id", + "reservation_time" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.restaurant_settings": { + "name": "restaurant_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "reservation_interval_minutes": { + "name": "reservation_interval_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 15 + }, + "reservation_duration_minutes": { + "name": "reservation_duration_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 90 + }, + "min_lead_time_minutes": { + "name": "min_lead_time_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 60 + }, + "max_lead_time_days": { + "name": "max_lead_time_days", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 60 + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.restaurant_tables": { + "name": "restaurant_tables", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "seats": { + "name": "seats", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "image_url": { + "name": "image_url", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "impersonated_by": { + "name": "impersonated_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "banned": { + "name": "banned", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ban_expires": { + "name": "ban_expires", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/0005_snapshot.json b/drizzle/meta/0005_snapshot.json new file mode 100644 index 0000000..01e8121 --- /dev/null +++ b/drizzle/meta/0005_snapshot.json @@ -0,0 +1,546 @@ +{ + "id": "71bc6bc2-516c-4af1-8c61-3df7814e9976", + "prevId": "05eac8a1-b172-4e4d-90d2-a409ecde610a", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.reservations": { + "name": "reservations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "table_id": { + "name": "table_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "customer_name": { + "name": "customer_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "customer_email": { + "name": "customer_email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "party_size": { + "name": "party_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "reservation_time": { + "name": "reservation_time", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "duration_minutes": { + "name": "duration_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 90 + }, + "cancel_token": { + "name": "cancel_token", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "reservations_table_id_restaurant_tables_id_fk": { + "name": "reservations_table_id_restaurant_tables_id_fk", + "tableFrom": "reservations", + "tableTo": "restaurant_tables", + "columnsFrom": [ + "table_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "reservations_cancel_token_unique": { + "name": "reservations_cancel_token_unique", + "nullsNotDistinct": false, + "columns": [ + "cancel_token" + ] + }, + "reservations_table_id_reservation_time_unique": { + "name": "reservations_table_id_reservation_time_unique", + "nullsNotDistinct": false, + "columns": [ + "table_id", + "reservation_time" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.restaurant_settings": { + "name": "restaurant_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "reservation_interval_minutes": { + "name": "reservation_interval_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 15 + }, + "reservation_duration_minutes": { + "name": "reservation_duration_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 90 + }, + "min_lead_time_minutes": { + "name": "min_lead_time_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 60 + }, + "max_lead_time_days": { + "name": "max_lead_time_days", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 60 + }, + "table_display_mode": { + "name": "table_display_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'individual'" + }, + "table_overview_image_url": { + "name": "table_overview_image_url", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.restaurant_tables": { + "name": "restaurant_tables", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "seats": { + "name": "seats", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "image_url": { + "name": "image_url", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "impersonated_by": { + "name": "impersonated_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "banned": { + "name": "banned", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ban_expires": { + "name": "ban_expires", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/0006_snapshot.json b/drizzle/meta/0006_snapshot.json new file mode 100644 index 0000000..c261f07 --- /dev/null +++ b/drizzle/meta/0006_snapshot.json @@ -0,0 +1,558 @@ +{ + "id": "cc3da65b-b162-4866-ac44-b7d177588316", + "prevId": "71bc6bc2-516c-4af1-8c61-3df7814e9976", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.reservations": { + "name": "reservations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "table_id": { + "name": "table_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "customer_name": { + "name": "customer_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "customer_email": { + "name": "customer_email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "party_size": { + "name": "party_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "reservation_time": { + "name": "reservation_time", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "duration_minutes": { + "name": "duration_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 90 + }, + "cancel_token": { + "name": "cancel_token", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "reservations_table_id_restaurant_tables_id_fk": { + "name": "reservations_table_id_restaurant_tables_id_fk", + "tableFrom": "reservations", + "tableTo": "restaurant_tables", + "columnsFrom": [ + "table_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "reservations_cancel_token_unique": { + "name": "reservations_cancel_token_unique", + "nullsNotDistinct": false, + "columns": [ + "cancel_token" + ] + }, + "reservations_table_id_reservation_time_unique": { + "name": "reservations_table_id_reservation_time_unique", + "nullsNotDistinct": false, + "columns": [ + "table_id", + "reservation_time" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.restaurant_settings": { + "name": "restaurant_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "reservation_interval_minutes": { + "name": "reservation_interval_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 15 + }, + "reservation_duration_minutes": { + "name": "reservation_duration_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 90 + }, + "min_lead_time_minutes": { + "name": "min_lead_time_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 60 + }, + "max_lead_time_days": { + "name": "max_lead_time_days", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 60 + }, + "table_display_mode": { + "name": "table_display_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'individual'" + }, + "table_overview_image_url": { + "name": "table_overview_image_url", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.restaurant_tables": { + "name": "restaurant_tables", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "seats": { + "name": "seats", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "image_url": { + "name": "image_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "marker_x": { + "name": "marker_x", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "marker_y": { + "name": "marker_y", + "type": "double precision", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "impersonated_by": { + "name": "impersonated_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "banned": { + "name": "banned", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ban_expires": { + "name": "ban_expires", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/0007_snapshot.json b/drizzle/meta/0007_snapshot.json new file mode 100644 index 0000000..5e727df --- /dev/null +++ b/drizzle/meta/0007_snapshot.json @@ -0,0 +1,564 @@ +{ + "id": "0914058c-53fc-4e93-b703-a907e0ae72a8", + "prevId": "cc3da65b-b162-4866-ac44-b7d177588316", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.reservations": { + "name": "reservations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "table_id": { + "name": "table_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "customer_name": { + "name": "customer_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "customer_email": { + "name": "customer_email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "party_size": { + "name": "party_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "reservation_time": { + "name": "reservation_time", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "duration_minutes": { + "name": "duration_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 90 + }, + "cancel_token": { + "name": "cancel_token", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "reservations_table_id_restaurant_tables_id_fk": { + "name": "reservations_table_id_restaurant_tables_id_fk", + "tableFrom": "reservations", + "tableTo": "restaurant_tables", + "columnsFrom": [ + "table_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "reservations_cancel_token_unique": { + "name": "reservations_cancel_token_unique", + "nullsNotDistinct": false, + "columns": [ + "cancel_token" + ] + }, + "reservations_table_id_reservation_time_unique": { + "name": "reservations_table_id_reservation_time_unique", + "nullsNotDistinct": false, + "columns": [ + "table_id", + "reservation_time" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.restaurant_settings": { + "name": "restaurant_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "reservation_interval_minutes": { + "name": "reservation_interval_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 15 + }, + "reservation_duration_minutes": { + "name": "reservation_duration_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 90 + }, + "min_lead_time_minutes": { + "name": "min_lead_time_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 60 + }, + "max_lead_time_days": { + "name": "max_lead_time_days", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 60 + }, + "table_display_mode": { + "name": "table_display_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'individual'" + }, + "table_overview_image_url": { + "name": "table_overview_image_url", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.restaurant_tables": { + "name": "restaurant_tables", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "seats": { + "name": "seats", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "table_number": { + "name": "table_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "image_url": { + "name": "image_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "marker_x": { + "name": "marker_x", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "marker_y": { + "name": "marker_y", + "type": "double precision", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "impersonated_by": { + "name": "impersonated_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "banned": { + "name": "banned", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ban_expires": { + "name": "ban_expires", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/0008_snapshot.json b/drizzle/meta/0008_snapshot.json new file mode 100644 index 0000000..0237bba --- /dev/null +++ b/drizzle/meta/0008_snapshot.json @@ -0,0 +1,570 @@ +{ + "id": "c95ecb29-7866-4794-9aad-8a6fba8e7bc4", + "prevId": "0914058c-53fc-4e93-b703-a907e0ae72a8", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.reservations": { + "name": "reservations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "table_id": { + "name": "table_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "customer_name": { + "name": "customer_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "customer_email": { + "name": "customer_email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "party_size": { + "name": "party_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "reservation_time": { + "name": "reservation_time", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "duration_minutes": { + "name": "duration_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 90 + }, + "cancel_token": { + "name": "cancel_token", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "reservations_table_id_restaurant_tables_id_fk": { + "name": "reservations_table_id_restaurant_tables_id_fk", + "tableFrom": "reservations", + "tableTo": "restaurant_tables", + "columnsFrom": [ + "table_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "reservations_cancel_token_unique": { + "name": "reservations_cancel_token_unique", + "nullsNotDistinct": false, + "columns": [ + "cancel_token" + ] + }, + "reservations_table_id_reservation_time_unique": { + "name": "reservations_table_id_reservation_time_unique", + "nullsNotDistinct": false, + "columns": [ + "table_id", + "reservation_time" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.restaurant_settings": { + "name": "restaurant_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "reservation_interval_minutes": { + "name": "reservation_interval_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 15 + }, + "reservation_duration_minutes": { + "name": "reservation_duration_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 90 + }, + "min_lead_time_minutes": { + "name": "min_lead_time_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 60 + }, + "max_lead_time_days": { + "name": "max_lead_time_days", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 60 + }, + "table_display_mode": { + "name": "table_display_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'individual'" + }, + "table_overview_image_url": { + "name": "table_overview_image_url", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.restaurant_tables": { + "name": "restaurant_tables", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "seats": { + "name": "seats", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "table_number": { + "name": "table_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "image_url": { + "name": "image_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "marker_x": { + "name": "marker_x", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "marker_y": { + "name": "marker_y", + "type": "double precision", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "impersonated_by": { + "name": "impersonated_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "banned": { + "name": "banned", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ban_expires": { + "name": "ban_expires", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/0009_snapshot.json b/drizzle/meta/0009_snapshot.json new file mode 100644 index 0000000..b73355d --- /dev/null +++ b/drizzle/meta/0009_snapshot.json @@ -0,0 +1,576 @@ +{ + "id": "69162a80-7a1b-480b-85c2-7f11e2f1725c", + "prevId": "c95ecb29-7866-4794-9aad-8a6fba8e7bc4", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.reservations": { + "name": "reservations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "table_id": { + "name": "table_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "customer_name": { + "name": "customer_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "customer_email": { + "name": "customer_email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "customer_phone": { + "name": "customer_phone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "party_size": { + "name": "party_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "reservation_time": { + "name": "reservation_time", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "duration_minutes": { + "name": "duration_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 90 + }, + "cancel_token": { + "name": "cancel_token", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "reservations_table_id_restaurant_tables_id_fk": { + "name": "reservations_table_id_restaurant_tables_id_fk", + "tableFrom": "reservations", + "tableTo": "restaurant_tables", + "columnsFrom": [ + "table_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "reservations_cancel_token_unique": { + "name": "reservations_cancel_token_unique", + "nullsNotDistinct": false, + "columns": [ + "cancel_token" + ] + }, + "reservations_table_id_reservation_time_unique": { + "name": "reservations_table_id_reservation_time_unique", + "nullsNotDistinct": false, + "columns": [ + "table_id", + "reservation_time" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.restaurant_settings": { + "name": "restaurant_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "reservation_interval_minutes": { + "name": "reservation_interval_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 15 + }, + "reservation_duration_minutes": { + "name": "reservation_duration_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 90 + }, + "min_lead_time_minutes": { + "name": "min_lead_time_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 60 + }, + "max_lead_time_days": { + "name": "max_lead_time_days", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 60 + }, + "table_display_mode": { + "name": "table_display_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'individual'" + }, + "table_overview_image_url": { + "name": "table_overview_image_url", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.restaurant_tables": { + "name": "restaurant_tables", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "seats": { + "name": "seats", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "table_number": { + "name": "table_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "image_url": { + "name": "image_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "marker_x": { + "name": "marker_x", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "marker_y": { + "name": "marker_y", + "type": "double precision", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "impersonated_by": { + "name": "impersonated_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "banned": { + "name": "banned", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ban_expires": { + "name": "ban_expires", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/0010_snapshot.json b/drizzle/meta/0010_snapshot.json new file mode 100644 index 0000000..6f2e8ce --- /dev/null +++ b/drizzle/meta/0010_snapshot.json @@ -0,0 +1,582 @@ +{ + "id": "b5488545-3b1f-4145-a565-f4fcdc5b3748", + "prevId": "69162a80-7a1b-480b-85c2-7f11e2f1725c", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.reservations": { + "name": "reservations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "table_id": { + "name": "table_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "customer_name": { + "name": "customer_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "customer_email": { + "name": "customer_email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "customer_phone": { + "name": "customer_phone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "party_size": { + "name": "party_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "reservation_time": { + "name": "reservation_time", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "duration_minutes": { + "name": "duration_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 90 + }, + "cancel_token": { + "name": "cancel_token", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "reservations_table_id_restaurant_tables_id_fk": { + "name": "reservations_table_id_restaurant_tables_id_fk", + "tableFrom": "reservations", + "tableTo": "restaurant_tables", + "columnsFrom": [ + "table_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "reservations_cancel_token_unique": { + "name": "reservations_cancel_token_unique", + "nullsNotDistinct": false, + "columns": [ + "cancel_token" + ] + }, + "reservations_table_id_reservation_time_unique": { + "name": "reservations_table_id_reservation_time_unique", + "nullsNotDistinct": false, + "columns": [ + "table_id", + "reservation_time" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.restaurant_settings": { + "name": "restaurant_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "reservation_interval_minutes": { + "name": "reservation_interval_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 15 + }, + "reservation_duration_minutes": { + "name": "reservation_duration_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 90 + }, + "min_lead_time_minutes": { + "name": "min_lead_time_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 60 + }, + "max_lead_time_days": { + "name": "max_lead_time_days", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 60 + }, + "table_display_mode": { + "name": "table_display_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'individual'" + }, + "table_overview_image_url": { + "name": "table_overview_image_url", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.restaurant_tables": { + "name": "restaurant_tables", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "seats": { + "name": "seats", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "table_number": { + "name": "table_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "image_url": { + "name": "image_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "marker_x": { + "name": "marker_x", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "marker_y": { + "name": "marker_y", + "type": "double precision", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "impersonated_by": { + "name": "impersonated_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "banned": { + "name": "banned", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ban_expires": { + "name": "ban_expires", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "phone": { + "name": "phone", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/0011_snapshot.json b/drizzle/meta/0011_snapshot.json new file mode 100644 index 0000000..8a413f6 --- /dev/null +++ b/drizzle/meta/0011_snapshot.json @@ -0,0 +1,589 @@ +{ + "id": "b91348a4-3a6f-4c6b-a7a0-c60c06eeb9f9", + "prevId": "b5488545-3b1f-4145-a565-f4fcdc5b3748", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.reservations": { + "name": "reservations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "table_id": { + "name": "table_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "customer_name": { + "name": "customer_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "customer_email": { + "name": "customer_email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "customer_phone": { + "name": "customer_phone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "party_size": { + "name": "party_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'confirmed'" + }, + "reservation_time": { + "name": "reservation_time", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "duration_minutes": { + "name": "duration_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 90 + }, + "cancel_token": { + "name": "cancel_token", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "reservations_table_id_restaurant_tables_id_fk": { + "name": "reservations_table_id_restaurant_tables_id_fk", + "tableFrom": "reservations", + "tableTo": "restaurant_tables", + "columnsFrom": [ + "table_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "reservations_cancel_token_unique": { + "name": "reservations_cancel_token_unique", + "nullsNotDistinct": false, + "columns": [ + "cancel_token" + ] + }, + "reservations_table_id_reservation_time_unique": { + "name": "reservations_table_id_reservation_time_unique", + "nullsNotDistinct": false, + "columns": [ + "table_id", + "reservation_time" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.restaurant_settings": { + "name": "restaurant_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "reservation_interval_minutes": { + "name": "reservation_interval_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 15 + }, + "reservation_duration_minutes": { + "name": "reservation_duration_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 90 + }, + "min_lead_time_minutes": { + "name": "min_lead_time_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 60 + }, + "max_lead_time_days": { + "name": "max_lead_time_days", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 60 + }, + "table_display_mode": { + "name": "table_display_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'individual'" + }, + "table_overview_image_url": { + "name": "table_overview_image_url", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.restaurant_tables": { + "name": "restaurant_tables", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "seats": { + "name": "seats", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "table_number": { + "name": "table_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "image_url": { + "name": "image_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "marker_x": { + "name": "marker_x", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "marker_y": { + "name": "marker_y", + "type": "double precision", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "impersonated_by": { + "name": "impersonated_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "banned": { + "name": "banned", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ban_expires": { + "name": "ban_expires", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "phone": { + "name": "phone", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json new file mode 100644 index 0000000..2060162 --- /dev/null +++ b/drizzle/meta/_journal.json @@ -0,0 +1,90 @@ +{ + "version": "7", + "dialect": "postgresql", + "entries": [ + { + "idx": 0, + "version": "7", + "when": 1787335013744, + "tag": "0000_milky_squadron_sinister", + "breakpoints": true + }, + { + "idx": 1, + "version": "7", + "when": 1787338587505, + "tag": "0001_melted_supernaut", + "breakpoints": true + }, + { + "idx": 2, + "version": "7", + "when": 1787604850455, + "tag": "0002_deep_dorian_gray", + "breakpoints": true + }, + { + "idx": 3, + "version": "7", + "when": 1787646264161, + "tag": "0003_big_jack_flag", + "breakpoints": true + }, + { + "idx": 4, + "version": "7", + "when": 1787683584543, + "tag": "0004_wide_thunderbolt", + "breakpoints": true + }, + { + "idx": 5, + "version": "7", + "when": 1787684215918, + "tag": "0005_nice_stark_industries", + "breakpoints": true + }, + { + "idx": 6, + "version": "7", + "when": 1787684576667, + "tag": "0006_uneven_captain_cross", + "breakpoints": true + }, + { + "idx": 7, + "version": "7", + "when": 1787685540508, + "tag": "0007_dusty_payback", + "breakpoints": true + }, + { + "idx": 8, + "version": "7", + "when": 1787686033197, + "tag": "0008_even_toxin", + "breakpoints": true + }, + { + "idx": 9, + "version": "7", + "when": 1787686357931, + "tag": "0009_nosy_alice", + "breakpoints": true + }, + { + "idx": 10, + "version": "7", + "when": 1787688761615, + "tag": "0010_common_masque", + "breakpoints": true + }, + { + "idx": 11, + "version": "7", + "when": 1787690760361, + "tag": "0011_cloudy_gwen_stacy", + "breakpoints": true + } + ] +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 081cbc5..57f8066 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "postgres": "^3.4.9", "react": "^19.2.0", "react-dom": "^19.2.0", + "resend": "^6.21.0", "zod": "^4.4.3" }, "devDependencies": { @@ -453,7 +454,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -470,7 +470,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -487,7 +486,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -504,7 +502,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -521,7 +518,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -538,7 +534,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -555,7 +550,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -572,7 +566,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -589,7 +582,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -606,7 +598,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -623,7 +614,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -640,7 +630,6 @@ "cpu": [ "mips64el" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -657,7 +646,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -674,7 +662,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -691,7 +678,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -708,7 +694,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -725,7 +710,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -742,7 +726,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -759,7 +742,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -776,7 +758,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -793,7 +774,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -810,7 +790,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -877,7 +856,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -894,7 +872,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -911,7 +888,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -928,7 +904,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -945,7 +920,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -962,7 +936,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -979,7 +952,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -996,7 +968,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1013,7 +984,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1030,7 +1000,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1047,7 +1016,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1064,7 +1032,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1081,7 +1048,6 @@ "cpu": [ "mips64el" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1098,7 +1064,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1115,7 +1080,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1132,7 +1096,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1149,7 +1112,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1166,7 +1128,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1183,7 +1144,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1200,7 +1160,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1217,7 +1176,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1234,7 +1192,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1251,7 +1208,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1268,7 +1224,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1285,7 +1240,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1302,7 +1256,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1455,7 +1408,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1472,7 +1424,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1489,7 +1440,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1506,7 +1456,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1523,7 +1472,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1540,7 +1488,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1557,7 +1504,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1574,7 +1520,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1591,7 +1536,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1608,7 +1552,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1625,7 +1568,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1642,7 +1584,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1659,7 +1600,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1676,7 +1616,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1693,6 +1632,12 @@ "devOptional": true, "license": "MIT" }, + "node_modules/@stablelib/base64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@stablelib/base64/-/base64-1.0.1.tgz", + "integrity": "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==", + "license": "MIT" + }, "node_modules/@standard-schema/spec": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", @@ -2158,7 +2103,7 @@ "version": "22.20.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.1.tgz", "integrity": "sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "undici-types": "~6.21.0" @@ -2621,7 +2566,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2638,7 +2582,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2655,7 +2598,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2672,7 +2614,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2689,7 +2630,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2706,7 +2646,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2723,7 +2662,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2740,7 +2678,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2757,7 +2694,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2774,7 +2710,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2791,7 +2726,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2808,7 +2742,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2825,7 +2758,6 @@ "cpu": [ "mips64el" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2842,7 +2774,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2859,7 +2790,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2876,7 +2806,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2893,7 +2822,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2910,7 +2838,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2927,7 +2854,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2944,7 +2870,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2961,7 +2886,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2978,7 +2902,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2995,7 +2918,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3012,7 +2934,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3029,7 +2950,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3046,7 +2966,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3293,6 +3212,12 @@ "integrity": "sha512-9U/jZUgjnSGyntRr6y5Muu1MJcwFl6kPu7k8qLF0IMNfLqvw0NZ4nnVDq0RVoZ0RvCyumib4Ez3KYrVfilrw+g==", "license": "MIT" }, + "node_modules/fast-sha256": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-sha256/-/fast-sha256-1.3.0.tgz", + "integrity": "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==", + "license": "Unlicense" + }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -3320,7 +3245,6 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -3826,6 +3750,12 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/postal-mime": { + "version": "2.7.5", + "resolved": "https://registry.npmjs.org/postal-mime/-/postal-mime-2.7.5.tgz", + "integrity": "sha512-GNEXKvWFQnbgO5NlrGzVa0FmWzBZ24PersAWErttSg1Hjpf0ATxTwS5DOMGaOpTG6bUh5cTr7xi0jAD942wCJA==", + "license": "MIT-0" + }, "node_modules/postcss": { "version": "8.5.26", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", @@ -3927,6 +3857,27 @@ "node": ">=0.10.0" } }, + "node_modules/resend": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/resend/-/resend-6.21.0.tgz", + "integrity": "sha512-XT1eP2iA8rZ51NSO2CJlL1ZE2baBoPpOGMgz2p0o2uluz/YU3XfItpGymXDlL0Sp2Mc+y9+Xxsfdccfi4f2FzQ==", + "license": "MIT", + "dependencies": { + "postal-mime": "2.7.5", + "standardwebhooks": "1.0.0" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@react-email/render": "*" + }, + "peerDependenciesMeta": { + "@react-email/render": { + "optional": true + } + } + }, "node_modules/resolve-pkg-maps": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", @@ -4070,6 +4021,16 @@ "node": ">=20.16.0" } }, + "node_modules/standardwebhooks": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/standardwebhooks/-/standardwebhooks-1.0.0.tgz", + "integrity": "sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg==", + "license": "MIT", + "dependencies": { + "@stablelib/base64": "^1.0.0", + "fast-sha256": "^1.3.0" + } + }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -4157,7 +4118,7 @@ "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/unplugin": { diff --git a/package.json b/package.json index 77e8eea..6ab506b 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "#/*": "./src/*" }, "scripts": { - "dev": "vite dev --port 3000", + "dev": "vite dev --port 3000 --host", "generate-routes": "tsr generate", "build": "vite build", "preview": "vite preview", @@ -24,6 +24,7 @@ "postgres": "^3.4.9", "react": "^19.2.0", "react-dom": "^19.2.0", + "resend": "^6.21.0", "zod": "^4.4.3" }, "devDependencies": { diff --git a/src/components/action-menu.tsx b/src/components/action-menu.tsx new file mode 100644 index 0000000..604182a --- /dev/null +++ b/src/components/action-menu.tsx @@ -0,0 +1,108 @@ +import { useEffect, useRef, useState } from 'react' +import { createPortal } from 'react-dom' + +type Props = { + label?: string + children: React.ReactNode +} + +function DotsIcon() { + return ( + + ) +} + +type Position = { top: number; right: number } + +// A small "⋮" menu that reveals its children (typically a stack of `.btn`s) in a +// dropdown — used to tuck row actions away instead of listing every button inline. +// The panel is portaled to and positioned with `position: fixed` from a +// measured rect rather than anchored with `position: absolute` in place, since +// rows live inside `.table-scroll` (`overflow-x: auto`, which per the CSS spec +// forces `overflow-y` to clip too) — an in-place dropdown on the last row or two +// would otherwise get cut off at the table's bottom edge. +export function ActionMenu({ label = 'Aktionen', children }: Props) { + const [position, setPosition] = useState(null) + const triggerRef = useRef(null) + const panelRef = useRef(null) + const open = position !== null + + function close() { + setPosition(null) + } + + function toggle() { + if (open) { + close() + return + } + + const rect = triggerRef.current?.getBoundingClientRect() + if (!rect) return + setPosition({ top: rect.bottom + 4, right: window.innerWidth - rect.right }) + } + + useEffect(() => { + if (!open) return + + function handleOutsideClick(event: MouseEvent) { + const target = event.target as Node + if (!triggerRef.current?.contains(target) && !panelRef.current?.contains(target)) { + close() + } + } + function handleKeyDown(event: KeyboardEvent) { + if (event.key === 'Escape') close() + } + // The menu's position is measured once, on open — rather than track scroll to + // keep it glued to the trigger, just close it, matching how most native and + // web dropdowns behave when the page moves under them. + function handleScroll() { + close() + } + + document.addEventListener('mousedown', handleOutsideClick) + document.addEventListener('keydown', handleKeyDown) + window.addEventListener('scroll', handleScroll, true) + return () => { + document.removeEventListener('mousedown', handleOutsideClick) + document.removeEventListener('keydown', handleKeyDown) + window.removeEventListener('scroll', handleScroll, true) + } + }, [open]) + + return ( + <> + + + {open && + position && + createPortal( +
+ {children} +
, + document.body, + )} + + ) +} diff --git a/src/components/app-sidebar.tsx b/src/components/app-sidebar.tsx new file mode 100644 index 0000000..0c2c285 --- /dev/null +++ b/src/components/app-sidebar.tsx @@ -0,0 +1,128 @@ +import { useEffect, useRef, useState } from 'react' +import { Link, useRouter } from '@tanstack/react-router' + +import { authClient } from '#/lib/auth-client' + +type StaffUser = { name: string; role?: string | null } +type SectionLink = { label: string; hash: string } + +type Props = { + staffUser?: StaffUser | null + sectionLinks?: SectionLink[] +} + +function MenuIcon() { + return ( + + ) +} + +export function AppSidebar({ staffUser, sectionLinks }: Props) { + const router = useRouter() + const [isOpen, setIsOpen] = useState(false) + const navRef = useRef(null) + + useEffect(() => { + if (!isOpen) return + + function handleOutsideClick(event: MouseEvent) { + if (navRef.current && !navRef.current.contains(event.target as Node)) { + setIsOpen(false) + } + } + function handleKeyDown(event: KeyboardEvent) { + if (event.key === 'Escape') setIsOpen(false) + } + + document.addEventListener('mousedown', handleOutsideClick) + document.addEventListener('keydown', handleKeyDown) + return () => { + document.removeEventListener('mousedown', handleOutsideClick) + document.removeEventListener('keydown', handleKeyDown) + } + }, [isOpen]) + + async function handleLogout() { + await authClient.signOut() + await router.navigate({ to: '/staff/login' }) + await router.invalidate() + } + + return ( + + ) +} diff --git a/src/components/confirm-dialog.tsx b/src/components/confirm-dialog.tsx new file mode 100644 index 0000000..f560b7d --- /dev/null +++ b/src/components/confirm-dialog.tsx @@ -0,0 +1,99 @@ +import { useEffect, useRef } from 'react' + +type Props = { + open: boolean + title: string + message: React.ReactNode + // A final, visually distinct callout for what irreversible actions this + // confirms — kept separate from `message` so callers don't have to hand-roll + // the emphasis every time (e.g. "This cannot be undone."). + warning?: React.ReactNode + confirmLabel?: string + cancelLabel?: string + danger?: boolean + onConfirm: () => void + onCancel: () => void +} + +function WarningIcon() { + return ( + + ) +} + +export function ConfirmDialog({ + open, + title, + message, + warning, + confirmLabel = 'Bestätigen', + cancelLabel = 'Abbrechen', + danger, + onConfirm, + onCancel, +}: Props) { + const confirmRef = useRef(null) + + useEffect(() => { + if (!open) return + + confirmRef.current?.focus() + + function handleKeyDown(event: KeyboardEvent) { + if (event.key === 'Escape') onCancel() + } + + document.addEventListener('keydown', handleKeyDown) + return () => document.removeEventListener('keydown', handleKeyDown) + }, [open, onCancel]) + + if (!open) return null + + return ( +
+
event.stopPropagation()} + > +

+ {title} +

+

+ {message} +

+ {warning && ( +
+ + {warning} +
+ )} +
+ + +
+
+
+ ) +} diff --git a/src/components/reservation-charts.tsx b/src/components/reservation-charts.tsx new file mode 100644 index 0000000..98d6e1c --- /dev/null +++ b/src/components/reservation-charts.tsx @@ -0,0 +1,321 @@ +import { useState } from 'react' + +import { getOpeningRangesForWeekday, type OpeningRange } from '#/lib/opening-hours' + +type ReservationLike = { + tableId: number + reservationTime: Date +} + +type TableLike = { + id: number + name: string +} + +type Props = { + reservations: ReservationLike[] + tables: TableLike[] +} + +type BarDatum = { + label: string + value: number + tooltip: string +} + +type Granularity = 'today' | 'week' | 'month' +type DateBucketGranularity = 'week' | 'month' + +const GRANULARITY_LABELS: Record = { + today: 'Heute', + week: 'Woche', + month: 'Monat', +} + +// How many buckets — and how far out — each granularity covers: enough weeks/months +// to spot a trend without the axis becoming unreadable. +const BUCKET_COUNTS: Record = { + week: 8, + month: 6, +} + +const WEEK_LABEL_FORMATTER = new Intl.DateTimeFormat('de-DE', { day: '2-digit', month: '2-digit' }) +const MONTH_AXIS_FORMATTER = new Intl.DateTimeFormat('de-DE', { month: 'short' }) +const MONTH_TOOLTIP_FORMATTER = new Intl.DateTimeFormat('de-DE', { month: 'long', year: 'numeric' }) + +function reservationsLabel(count: number) { + return count === 1 ? 'Reservierung' : 'Reservierungen' +} + +function addBucketUnit(date: Date, granularity: DateBucketGranularity, amount: number) { + const d = new Date(date) + if (granularity === 'week') d.setDate(d.getDate() + amount * 7) + else d.setMonth(d.getMonth() + amount) + return d +} + +// The start of the first bucket: the Monday of this week, or the 1st of this month — +// so the current, still-in-progress bucket covers the whole period, not just what's +// left of it (the caller is expected to pass in ALL reservations, not just upcoming +// ones, so this week's/month's earlier days are still counted). +function bucketRangeStart(granularity: DateBucketGranularity) { + const now = new Date() + now.setHours(0, 0, 0, 0) + + if (granularity === 'month') { + now.setDate(1) + return now + } + const mondayOffset = (now.getDay() + 6) % 7 + now.setDate(now.getDate() - mondayOffset) + return now +} + +function formatBucketLabel(start: Date, granularity: DateBucketGranularity) { + if (granularity === 'month') return MONTH_AXIS_FORMATTER.format(start) + return WEEK_LABEL_FORMATTER.format(start) +} + +function formatBucketTooltip(start: Date, end: Date, granularity: DateBucketGranularity, count: number) { + const countLabel = `${count} ${reservationsLabel(count)}` + + if (granularity === 'month') return `${MONTH_TOOLTIP_FORMATTER.format(start)}: ${countLabel}` + + const lastDayOfWeek = new Date(end.getTime() - 24 * 60 * 60 * 1000) + return `${WEEK_LABEL_FORMATTER.format(start)}–${WEEK_LABEL_FORMATTER.format(lastDayOfWeek)}: ${countLabel}` +} + +function buildBucketedCounts(reservations: ReservationLike[], granularity: DateBucketGranularity): BarDatum[] { + const rangeStart = bucketRangeStart(granularity) + const bucketCount = BUCKET_COUNTS[granularity] + + return Array.from({ length: bucketCount }, (_, i) => { + const start = addBucketUnit(rangeStart, granularity, i) + const end = addBucketUnit(rangeStart, granularity, i + 1) + const count = reservations.filter((r) => { + const time = new Date(r.reservationTime).getTime() + return time >= start.getTime() && time < end.getTime() + }).length + + return { + label: formatBucketLabel(start, granularity), + value: count, + tooltip: formatBucketTooltip(start, end, granularity, count), + } + }) +} + +// The restaurant can have more than one service window in a day (e.g. lunch + +// dinner) — span the whole gap between them rather than faceting the chart, so a +// quiet hour between services reads as "no bookings then", not a missing bar. +function hourSpanForRanges(ranges: OpeningRange[]) { + const startHour = Math.min(...ranges.map((r) => Number(r.start.split(':')[0]))) + const endHour = Math.max( + ...ranges.map((r) => { + const [hour, minute] = r.end.split(':').map(Number) + return minute > 0 ? hour + 1 : hour + }), + ) + return { startHour, endHour } +} + +function buildHourlyCountsForToday(reservations: ReservationLike[], ranges: OpeningRange[]): BarDatum[] { + const { startHour, endHour } = hourSpanForRanges(ranges) + const dayStart = new Date() + dayStart.setHours(0, 0, 0, 0) + + return Array.from({ length: endHour - startHour }, (_, i) => { + const hour = startHour + i + const start = new Date(dayStart) + start.setHours(hour) + const end = new Date(dayStart) + end.setHours(hour + 1) + + const count = reservations.filter((r) => { + const time = new Date(r.reservationTime).getTime() + return time >= start.getTime() && time < end.getTime() + }).length + + return { + label: `${String(hour).padStart(2, '0')}:00`, + value: count, + tooltip: `${String(hour).padStart(2, '0')}:00–${String(hour + 1).padStart(2, '0')}:00: ${count} ${reservationsLabel(count)}`, + } + }) +} + +function buildTimelineData( + reservations: ReservationLike[], + granularity: Granularity, +): { data: BarDatum[]; emptyMessage: string } { + if (granularity === 'today') { + const ranges = getOpeningRangesForWeekday(new Date().getDay()) + if (ranges.length === 0) { + return { data: [], emptyMessage: 'Heute ist Ruhetag.' } + } + return { + data: buildHourlyCountsForToday(reservations, ranges), + emptyMessage: 'Heute sind noch keine Reservierungen eingetragen.', + } + } + + return { + data: buildBucketedCounts(reservations, granularity), + emptyMessage: 'In diesem Zeitraum sind noch keine Reservierungen eingetragen.', + } +} + +// The full date range the timeline chart currently covers, so the per-table chart +// can be filtered to the same window and the two charts always tell one consistent +// story instead of the table breakdown silently covering a different span. +function getTimelineRange(granularity: Granularity): { start: Date; end: Date } { + if (granularity === 'today') { + const start = new Date() + start.setHours(0, 0, 0, 0) + const end = new Date(start) + end.setDate(end.getDate() + 1) + return { start, end } + } + + const start = bucketRangeStart(granularity) + const end = addBucketUnit(start, granularity, BUCKET_COUNTS[granularity]) + return { start, end } +} + +function buildTableCounts(reservations: ReservationLike[], tables: TableLike[]): BarDatum[] { + return tables.map((table) => { + const count = reservations.filter((r) => r.tableId === table.id).length + return { + label: table.name, + value: count, + tooltip: `${table.name}: ${count} ${reservationsLabel(count)}`, + } + }) +} + +// Produces clean, evenly-stepped tick values (0, 2, 4, 6, …) so the gridlines +// never land on an awkward number like 7 or 13. +function niceTicks(maxValue: number, tickCount = 4): number[] { + if (maxValue <= 0) return [0, 1] + + const rawStep = maxValue / (tickCount - 1) + const magnitude = 10 ** Math.floor(Math.log10(rawStep)) + const residual = rawStep / magnitude + const niceResidual = residual <= 1 ? 1 : residual <= 2 ? 2 : residual <= 5 ? 5 : 10 + // Counts are always whole numbers, so a sub-1 step (e.g. 0.5) would just + // round two distinct ticks down to the same displayed integer. + const step = Math.max(1, niceResidual * magnitude) + const niceMax = Math.ceil(maxValue / step) * step + + const ticks: number[] = [] + for (let v = 0; v <= niceMax + 1e-9; v += step) ticks.push(Math.round(v)) + return ticks +} + +function BarChartBody({ data, showValueLabels }: { data: BarDatum[]; showValueLabels?: boolean }) { + const ticks = niceTicks(Math.max(...data.map((d) => d.value), 0)) + const chartMax = ticks[ticks.length - 1] || 1 + + return ( +
+
+ {ticks.map((tick) => ( +
+ {tick} +
+ ))} +
+ {data.map((d, i) => ( +
+
+ {showValueLabels && d.value > 0 && {d.value}} +
+
{d.tooltip}
+
+ ))} +
+
+
+ {data.map((d, i) => ( + + {d.label} + + ))} +
+
+ ) +} + +function ChartCard({ + title, + headerExtra, + data, + showValueLabels, + emptyMessage, +}: { + title: string + headerExtra?: React.ReactNode + data: BarDatum[] + showValueLabels?: boolean + emptyMessage: string +}) { + const hasData = data.some((d) => d.value > 0) + + return ( +
+
+

{title}

+ {headerExtra} +
+ {hasData ? :

{emptyMessage}

} +
+ ) +} + +function GranularityToggle({ value, onChange }: { value: Granularity; onChange: (g: Granularity) => void }) { + return ( +
+ {(Object.keys(GRANULARITY_LABELS) as Granularity[]).map((g) => ( + + ))} +
+ ) +} + +export function ReservationCharts({ reservations, tables }: Props) { + const [granularity, setGranularity] = useState('today') + + const timeline = buildTimelineData(reservations, granularity) + + const { start, end } = getTimelineRange(granularity) + const reservationsInRange = reservations.filter((r) => { + const time = new Date(r.reservationTime).getTime() + return time >= start.getTime() && time < end.getTime() + }) + const tableCounts = buildTableCounts(reservationsInRange, tables) + + return ( +
+ } + data={timeline.data} + emptyMessage={timeline.emptyMessage} + /> + +
+ ) +} diff --git a/src/components/reservation-confirmed-modal.tsx b/src/components/reservation-confirmed-modal.tsx new file mode 100644 index 0000000..4b2c71c --- /dev/null +++ b/src/components/reservation-confirmed-modal.tsx @@ -0,0 +1,108 @@ +import { useEffect, useRef } from 'react' + +type Props = { + open: boolean + customerName: string + customerEmail: string + tableName: string + date: string + startTime: string + endTime: string + partySize: number + onClose: () => void +} + +function FlameBadgeIcon() { + return ( + + ) +} + +export function ReservationConfirmedModal({ + open, + customerName, + customerEmail, + tableName, + date, + startTime, + endTime, + partySize, + onClose, +}: Props) { + const closeRef = useRef(null) + + useEffect(() => { + if (!open) return + + closeRef.current?.focus() + + function handleKeyDown(event: KeyboardEvent) { + if (event.key === 'Escape') onClose() + } + + document.addEventListener('keydown', handleKeyDown) + return () => document.removeEventListener('keydown', handleKeyDown) + }, [open, onClose]) + + if (!open) return null + + return ( +
+
event.stopPropagation()} + > + + +

+ Tisch reserviert! +

+

+ Bis bald, {customerName}! Wir halten Ihnen den Platz warm und legen schon mal Kohle nach. +

+ +
+
+
Tisch
+
{tableName}
+
+
+
Datum
+
{date}
+
+
+
Uhrzeit
+
+ {startTime} – {endTime} +
+
+
+
Personen
+
{partySize}
+
+
+ +

+ Eine Bestätigung ist unterwegs an {customerEmail}. Dort finden Sie auch einen + Link, falls sich doch noch etwas ändert. +

+ +
+ +
+
+
+ ) +} diff --git a/src/components/reservation-datetime-field.tsx b/src/components/reservation-datetime-field.tsx new file mode 100644 index 0000000..835a818 --- /dev/null +++ b/src/components/reservation-datetime-field.tsx @@ -0,0 +1,362 @@ +import { useEffect, useRef, useState } from 'react' + +import { formatDateOnly } from '#/lib/datetime' +import { generateTimeOptions, getOpeningRangesForWeekday, RESERVATION_INTERVAL_MINUTES } from '#/lib/opening-hours' + +type Props = { + value: string + onChange: (value: string) => void + // "HH:MM" times already taken (e.g. by another reservation on the same table) + // for the currently selected date — shown disabled rather than removed, so the + // slot doesn't look like it doesn't exist, just that it isn't free. + bookedTimes?: string[] + // How far apart selectable times are, and how far in advance a booking must be + // made — both configurable restaurant settings, falling back to sane defaults. + intervalMinutes?: number + minLeadTimeMinutes?: number + // How long a reservation booked at a slot occupies the table — a slot too close + // to closing time to fit the whole reservation is left out of the list entirely. + durationMinutes?: number + // How far into the future a booking may be made. Undefined leaves the horizon open, + // since not every caller of this field needs the restriction. + maxLeadTimeDays?: number +} + +type DayCell = { day: number; dateStr: string } | null + +function splitValue(value: string) { + const [date = '', time = ''] = value.split('T') + return { date, time } +} + +function getWeekday(dateStr: string) { + return new Date(`${dateStr}T00:00`).getDay() +} + +function pad2(n: number) { + return String(n).padStart(2, '0') +} + +function isoToDisplay(iso: string) { + if (!iso) return '' + const [year, month, day] = iso.split('-') + return `${day}.${month}.${year}` +} + +// Progressively inserts dots as digits are typed, e.g. "22081" -> "22.08.1". +function maskDateInput(raw: string) { + const digits = raw.replace(/\D/g, '').slice(0, 8) + let out = digits.slice(0, 2) + if (digits.length > 2) out += '.' + digits.slice(2, 4) + if (digits.length > 4) out += '.' + digits.slice(4, 8) + return out +} + +// Returns an ISO "YYYY-MM-DD" string, or null if the text isn't a complete, real calendar date. +function parseDisplayDate(raw: string) { + const digits = raw.replace(/\D/g, '') + if (digits.length !== 8) return null + + const day = Number(digits.slice(0, 2)) + const month = Number(digits.slice(2, 4)) + const year = Number(digits.slice(4, 8)) + const parsed = new Date(year, month - 1, day) + + if (parsed.getFullYear() !== year || parsed.getMonth() !== month - 1 || parsed.getDate() !== day) { + return null + } + + return `${digits.slice(4, 8)}-${digits.slice(2, 4)}-${digits.slice(0, 2)}` +} + +// Monday-first weeks covering the given month, with null filler cells for days +// that belong to the adjacent month. +function getMonthGrid(year: number, month: number): DayCell[][] { + const firstOfMonth = new Date(year, month, 1) + const leadingBlanks = (firstOfMonth.getDay() + 6) % 7 + const daysInMonth = new Date(year, month + 1, 0).getDate() + + const cells: DayCell[] = Array(leadingBlanks).fill(null) + for (let day = 1; day <= daysInMonth; day++) { + cells.push({ day, dateStr: `${year}-${pad2(month + 1)}-${pad2(day)}` }) + } + while (cells.length % 7 !== 0) cells.push(null) + + const weeks: DayCell[][] = [] + for (let i = 0; i < cells.length; i += 7) weeks.push(cells.slice(i, i + 7)) + return weeks +} + +const WEEKDAY_HEADER_LABELS = ['Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So'] + +function CalendarIcon() { + return ( + + ) +} + +export function ReservationDateTimeField({ + value, + onChange, + bookedTimes = [], + intervalMinutes = RESERVATION_INTERVAL_MINUTES, + minLeadTimeMinutes = 0, + durationMinutes = RESERVATION_INTERVAL_MINUTES, + maxLeadTimeDays, +}: Props) { + const { date, time } = splitValue(value) + const [dateText, setDateText] = useState(() => isoToDisplay(date)) + const [isCalendarOpen, setIsCalendarOpen] = useState(false) + const [view, setView] = useState(() => { + const [year, month] = (date || formatDateOnly(new Date())).split('-') + return { year: Number(year), month: Number(month) - 1 } + }) + const containerRef = useRef(null) + + useEffect(() => { + setDateText(isoToDisplay(date)) + }, [date]) + + useEffect(() => { + if (!isCalendarOpen) return + + const [year, month] = (date || formatDateOnly(new Date())).split('-') + setView({ year: Number(year), month: Number(month) - 1 }) + + function handleOutsideClick(event: MouseEvent) { + if (containerRef.current && !containerRef.current.contains(event.target as Node)) { + setIsCalendarOpen(false) + } + } + function handleKeyDown(event: KeyboardEvent) { + if (event.key === 'Escape') setIsCalendarOpen(false) + } + + document.addEventListener('mousedown', handleOutsideClick) + document.addEventListener('keydown', handleKeyDown) + + // Only locks the background on mobile, where the popover becomes a fixed, + // full-screen overlay that would otherwise let the page behind it scroll — + // on desktop the popover is just a small anchored dropdown, and pinning the + // body there only removes the scrollbar and shifts the whole page sideways. + // Matches the `@media (max-width: 30rem)` breakpoint the popover-to-modal + // CSS switches on. + const isMobile = window.matchMedia('(max-width: 30rem)').matches + + if (!isMobile) { + return () => { + document.removeEventListener('mousedown', handleOutsideClick) + document.removeEventListener('keydown', handleKeyDown) + } + } + + // `overflow: hidden` alone doesn't stop touch-driven rubber-band scrolling on + // iOS Safari, so the body is pinned in place instead and the scroll position + // restored after. + const scrollY = window.scrollY + const body = document.body.style + const previousStyle = { position: body.position, top: body.top, left: body.left, right: body.right } + body.position = 'fixed' + body.top = `-${scrollY}px` + body.left = '0' + body.right = '0' + + return () => { + document.removeEventListener('mousedown', handleOutsideClick) + document.removeEventListener('keydown', handleKeyDown) + body.position = previousStyle.position + body.top = previousStyle.top + body.left = previousStyle.left + body.right = previousStyle.right + // `behavior: 'instant'` is required, not just the default — the page sets + // `scroll-behavior: smooth` globally, which would otherwise animate this + // restore from the top of the page instead of snapping back in place. + window.scrollTo({ top: scrollY, left: 0, behavior: 'instant' }) + } + // Only re-run when the popover opens/closes — reopening should reset to the + // currently selected month, not follow every keystroke in the text field. + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isCalendarOpen]) + + const today = formatDateOnly(new Date()) + const isPast = date !== '' && date < today + const earliestAllowedTime = new Date(Date.now() + minLeadTimeMinutes * 60_000) + const latestDate = + maxLeadTimeDays === undefined ? null : formatDateOnly(new Date(Date.now() + maxLeadTimeDays * 86_400_000)) + const isBeyondHorizon = date !== '' && latestDate !== null && date > latestDate + + // Past dates and dates beyond the booking horizon never have slots — shared by both + // the render below and commitDate, so a date committed from anywhere (typed, or + // otherwise) can't slip through with a leftover, still-"valid"-looking time. + function optionsForDate(dateStr: string) { + if (!dateStr || dateStr < today || (latestDate !== null && dateStr > latestDate)) return [] + return generateTimeOptions(getOpeningRangesForWeekday(getWeekday(dateStr)), intervalMinutes, durationMinutes) + } + + function isSelectableDate(dateStr: string) { + return optionsForDate(dateStr).length > 0 + } + + const allOptions = optionsForDate(date) + // Compares full timestamps (not just "is it today") so a lead time longer than + // what's left of today correctly spills over into tomorrow's early slots too. + const timeOptions = allOptions.filter( + (option) => new Date(`${date}T${option}`).getTime() >= earliestAllowedTime.getTime(), + ) + + const bookedTimesSet = new Set(bookedTimes) + const availableTimeOptions = timeOptions.filter((option) => !bookedTimesSet.has(option)) + + const isClosed = date !== '' && !isPast && !isBeyondHorizon && allOptions.length === 0 + const noSlotsLeft = date !== '' && !isPast && allOptions.length > 0 && availableTimeOptions.length === 0 + const dateIncomplete = parseDisplayDate(dateText) === null + + function commitDate(newDate: string) { + const newOptions = optionsForDate(newDate) + const stillValid = newOptions.includes(time) + onChange(`${newDate}T${stillValid ? time : newOptions[0] ?? ''}`) + } + + function handleTextChange(raw: string) { + const masked = maskDateInput(raw) + setDateText(masked) + + const parsed = parseDisplayDate(masked) + if (parsed) { + commitDate(parsed) + } + } + + function handleDayPick(dateStr: string) { + commitDate(dateStr) + setIsCalendarOpen(false) + } + + function handleTimeChange(newTime: string) { + onChange(`${date}T${newTime}`) + } + + let timePlaceholder = 'Uhrzeit wählen' + if (dateIncomplete) timePlaceholder = 'Datum eingeben' + else if (isPast) timePlaceholder = 'Datum liegt in der Vergangenheit' + else if (isBeyondHorizon) timePlaceholder = 'Datum liegt zu weit in der Zukunft' + else if (isClosed) timePlaceholder = 'An diesem Tag geschlossen' + else if (noSlotsLeft) timePlaceholder = 'Keine Zeiten mehr verfügbar' + + const monthLabel = new Date(view.year, view.month, 1).toLocaleDateString('de-DE', { + month: 'long', + year: 'numeric', + }) + const [todayYear, todayMonthStr] = today.split('-') + const isAtEarliestMonth = view.year === Number(todayYear) && view.month === Number(todayMonthStr) - 1 + const isAtLatestMonth = (() => { + if (latestDate === null) return false + const [latestYear, latestMonthStr] = latestDate.split('-') + return view.year === Number(latestYear) && view.month === Number(latestMonthStr) - 1 + })() + + function goToMonth(offset: number) { + setView((v) => { + const total = v.year * 12 + v.month + offset + return { year: Math.floor(total / 12), month: ((total % 12) + 12) % 12 } + }) + } + + return ( +
+
+ handleTextChange(e.target.value)} + required + /> + + + {isCalendarOpen && ( +
setIsCalendarOpen(false)}> +
event.stopPropagation()} + > +
+ + {monthLabel} + +
+
+ {WEEKDAY_HEADER_LABELS.map((label) => ( + {label} + ))} +
+
+ {getMonthGrid(view.year, view.month) + .flat() + .map((cell, index) => ( + + ))} +
+
+
+ )} +
+ + +
+ ) +} diff --git a/src/components/reservation-dialog.tsx b/src/components/reservation-dialog.tsx new file mode 100644 index 0000000..02e150d --- /dev/null +++ b/src/components/reservation-dialog.tsx @@ -0,0 +1,456 @@ +import { useEffect, useState } from 'react' + +import { ActionMenu } from '#/components/action-menu' +import { ReservationDateTimeField } from '#/components/reservation-datetime-field' +import { formatDateOnly, formatDateTimeLocal } from '#/lib/datetime' +import { generateTimeOptions, getNextAvailableSlot, getOpeningRangesForWeekday } from '#/lib/opening-hours' +import { + RESERVATION_STATUS_LABELS, + type ReservationStatus, + type getReservations, + type getTables, +} from '#/lib/reservations' +import { type getReservationSettings } from '#/lib/settings' + +type ReservationRow = Awaited>[number] +type TableRow = Awaited>[number] +type SettingsRow = Awaited> + +export type ReservationPayload = { + tableId: number + customerName: string + customerEmail: string + customerPhone: string + message: string + partySize: number + reservationTime: string +} + +type FormState = { + tableId: number | string + customerName: string + customerEmail: string + customerPhone: string + message: string + partySize: number + reservationTime: string +} + +type Props = { + // `null` reservation with `open: true` is "create" mode. + open: boolean + reservation: ReservationRow | null + tables: TableRow[] + settings: SettingsRow + reservations: ReservationRow[] + onClose: () => void + onCreate: (payload: ReservationPayload) => Promise + onUpdate: (id: number, payload: ReservationPayload) => Promise + onStatusChange: (status: ReservationStatus) => void + isUpdatingStatus?: boolean + onDelete: () => void + statusError?: string | null +} + +const STATUS_OPTIONS = Object.keys(RESERVATION_STATUS_LABELS) as ReservationStatus[] + +const dateFormatter = new Intl.DateTimeFormat('de-DE', { + dateStyle: 'full', +}) + +const timeFormatter = new Intl.DateTimeFormat('de-DE', { + timeStyle: 'short', +}) + +const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ +// Also used as the phone input's `pattern` attribute below, so the two stay in sync. +const PHONE_PATTERN_SOURCE = String.raw`[+]?[0-9()\-.\s]{6,30}` +const PHONE_PATTERN = new RegExp(`^${PHONE_PATTERN_SOURCE}$`) + +function emptyForm(defaultTableId: number | string, settings: SettingsRow): FormState { + const slot = getNextAvailableSlot( + new Date(Date.now() + settings.minLeadTimeMinutes * 60_000), + settings.reservationIntervalMinutes, + settings.reservationDurationMinutes, + ) + + return { + tableId: defaultTableId, + customerName: '', + customerEmail: '', + customerPhone: '', + message: '', + partySize: 2, + reservationTime: slot ? `${slot.date}T${slot.time}` : '', + } +} + +function formFromReservation(reservation: ReservationRow): FormState { + return { + tableId: reservation.tableId, + customerName: reservation.customerName, + customerEmail: reservation.customerEmail, + customerPhone: reservation.customerPhone ?? '', + message: reservation.message ?? '', + partySize: reservation.partySize, + reservationTime: formatDateTimeLocal(new Date(reservation.reservationTime)), + } +} + +// Rendered with a `key` (see staff.index.tsx) so a fresh instance mounts every time +// the dialog opens — that's what lets this component safely derive its initial +// state from props once, instead of syncing it via effects. +export function ReservationDialog({ + open, + reservation, + tables, + settings, + reservations, + onClose, + onCreate, + onUpdate, + onStatusChange, + isUpdatingStatus, + onDelete, + statusError, +}: Props) { + const [isEditing, setIsEditing] = useState(reservation === null) + const [form, setForm] = useState(() => + reservation ? formFromReservation(reservation) : emptyForm(tables[0]?.id ?? '', settings), + ) + const [error, setError] = useState(null) + const [isSubmitting, setIsSubmitting] = useState(false) + + useEffect(() => { + function handleKeyDown(event: KeyboardEvent) { + if (event.key === 'Escape') onClose() + } + + document.addEventListener('keydown', handleKeyDown) + return () => document.removeEventListener('keydown', handleKeyDown) + }, [onClose]) + + if (!open) return null + + // Other reservations already sitting on the currently selected table + date, so the + // time picker can block them instead of letting staff pick a slot the server will + // just reject as double-booked. A reservation occupies its whole configured + // duration, not just its exact start time, so every interval-grid slot that falls + // inside that window counts as taken too. `minLeadTimeMinutes` is reused here as a + // table turnover buffer, extending the taken window on both sides so back-to-back + // bookings on the same table always leave that much of a gap. + const [selectedDate] = form.reservationTime.split('T') + const otherReservationsOnSelection = reservations.filter( + (r) => + r.id !== reservation?.id && + String(r.tableId) === String(form.tableId) && + formatDateOnly(new Date(r.reservationTime)) === selectedDate, + ) + const turnoverBufferMs = settings.minLeadTimeMinutes * 60_000 + const bookedTimesForForm = selectedDate + ? generateTimeOptions( + getOpeningRangesForWeekday(new Date(`${selectedDate}T00:00`).getDay()), + settings.reservationIntervalMinutes, + settings.reservationDurationMinutes, + ).filter((option) => { + const slotStart = new Date(`${selectedDate}T${option}`).getTime() + return otherReservationsOnSelection.some((r) => { + const otherStart = new Date(r.reservationTime).getTime() - turnoverBufferMs + const otherEnd = otherStart + r.durationMinutes * 60_000 + 2 * turnoverBufferMs + return slotStart >= otherStart && slotStart < otherEnd + }) + }) + : [] + + // Mirrors the form fields' own `required`/`type`/`pattern`/`min`/`max` constraints, + // so the submit button is disabled until they'd all actually pass — instead of only + // catching an invalid table/time on submit and leaving everything else to the + // browser's native validation popup. + const isFormValid = + Boolean(form.tableId) && + form.customerName.trim().length > 0 && + EMAIL_PATTERN.test(form.customerEmail) && + PHONE_PATTERN.test(form.customerPhone) && + form.partySize >= 1 && + form.partySize <= 20 && + Boolean(form.reservationTime.split('T')[1]) + + function startEditing() { + if (reservation) setForm(formFromReservation(reservation)) + setError(null) + setIsEditing(true) + } + + function cancelEditing() { + if (!reservation) { + onClose() + return + } + setForm(formFromReservation(reservation)) + setError(null) + setIsEditing(false) + } + + async function handleSubmit(event: React.FormEvent) { + event.preventDefault() + setError(null) + + if (!form.tableId) { + setError('Bitte wählen Sie einen Tisch aus') + return + } + + if (!form.reservationTime.split('T')[1]) { + setError('Bitte wählen Sie eine gültige Uhrzeit aus') + return + } + + setIsSubmitting(true) + try { + const payload: ReservationPayload = { + tableId: Number(form.tableId), + customerName: form.customerName, + customerEmail: form.customerEmail, + customerPhone: form.customerPhone, + message: form.message, + partySize: Number(form.partySize), + reservationTime: new Date(form.reservationTime).toISOString(), + } + + if (reservation) { + await onUpdate(reservation.id, payload) + } else { + await onCreate(payload) + } + onClose() + } catch (err) { + setError(err instanceof Error ? err.message : 'Etwas ist schiefgelaufen') + } finally { + setIsSubmitting(false) + } + } + + const title = reservation + ? isEditing + ? `Reservierung von ${reservation.customerName} bearbeiten` + : `Reservierung von ${reservation.customerName}` + : 'Neue Reservierung' + + return ( +
+
event.stopPropagation()} + > +
+

+ {title} +

+ {reservation && !isEditing && ( +
+ + + +
+ )} +
+ + {isEditing ? ( +
+ + +
+ + + +
+ +
+ + + +
+ + + +