From fd8ae575c4f5fd61fe2bdb9635ad0795337f6830 Mon Sep 17 00:00:00 2001 From: epowell Date: Sat, 9 May 2026 14:00:43 -0500 Subject: [PATCH 1/4] Unify schema from app/ and cron-service/ into classes-prisma --- .dockerignore | 21 ++ app/.dockerignore | 15 -- app/Dockerfile | 26 +- app/Dockerfile.dev | 18 +- app/package.json | 3 +- .../migrations/0_init/migration.sql | 0 .../migration.sql | 0 .../migration.sql | 0 .../migration.sql | 0 .../migration.sql | 0 .../migration.sql | 0 .../migrations/migration_lock.toml | 0 {app/prisma => classes-prisma}/schema.prisma | 0 compose.dev.yaml | 10 +- compose.yaml | 4 +- cron-service/Dockerfile | 16 +- cron-service/package.json | 2 +- .../prisma/migrations/0_init/migration.sql | 228 ------------------ .../migration.sql | 24 -- .../migration.sql | 2 - .../migration.sql | 11 - .../migration.sql | 21 -- .../migration.sql | 29 --- .../prisma/migrations/migration_lock.toml | 3 - cron-service/prisma/schema.prisma | 123 ---------- plan-potteryShelves.prompt.md | 100 ++++++++ 26 files changed, 156 insertions(+), 500 deletions(-) create mode 100644 .dockerignore delete mode 100644 app/.dockerignore rename {app/prisma => classes-prisma}/migrations/0_init/migration.sql (100%) rename {app/prisma => classes-prisma}/migrations/20240127184840_add_event_cancellations/migration.sql (100%) rename {app/prisma => classes-prisma}/migrations/20240424210639_add_visible_bool/migration.sql (100%) rename {app/prisma => classes-prisma}/migrations/20241009210514_cascading_deletes/migration.sql (100%) rename {app/prisma => classes-prisma}/migrations/20251019205030_update_auth_tables/migration.sql (100%) rename {app/prisma => classes-prisma}/migrations/20251019213622_update_user_id_datatype/migration.sql (100%) rename {app/prisma => classes-prisma}/migrations/migration_lock.toml (100%) rename {app/prisma => classes-prisma}/schema.prisma (100%) delete mode 100644 cron-service/prisma/migrations/0_init/migration.sql delete mode 100644 cron-service/prisma/migrations/20240127184840_add_event_cancellations/migration.sql delete mode 100644 cron-service/prisma/migrations/20240424210639_add_visible_bool/migration.sql delete mode 100644 cron-service/prisma/migrations/20241009210514_cascading_deletes/migration.sql delete mode 100644 cron-service/prisma/migrations/20251019205030_update_auth_tables/migration.sql delete mode 100644 cron-service/prisma/migrations/20251019213622_update_user_id_datatype/migration.sql delete mode 100644 cron-service/prisma/migrations/migration_lock.toml delete mode 100644 cron-service/prisma/schema.prisma create mode 100644 plan-potteryShelves.prompt.md diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..848e229 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,21 @@ +**/.git +**/.gitignore +**/.idea +**/.vscode +**/.DS_Store +**/node_modules +.env +.env.* +!.env.example +secrets/ + +# App-specific +app/.svelte-kit +app/build +app/.npmrc +app/.prettierignore +app/.prettierrc +app/.eslintignore +app/.eslintrc.cjs +app/npm-debug.log + diff --git a/app/.dockerignore b/app/.dockerignore deleted file mode 100644 index 9987668..0000000 --- a/app/.dockerignore +++ /dev/null @@ -1,15 +0,0 @@ -node_modules -.svelte-kit -.env -.dockerignore -npm-debug.log -Dockerfile -Dockerfile.dev -Dockerfile.preview -.gitignore -.git -.npmrc -.prettierignore -.prettierrc -.eslintignore -.eslintrc.cjs diff --git a/app/Dockerfile b/app/Dockerfile index 9d19c83..3fcf7f3 100644 --- a/app/Dockerfile +++ b/app/Dockerfile @@ -1,20 +1,18 @@ # Builder image FROM node:lts AS builder -# Set environment variables ARG NODE_ENV=development -# Set working directory WORKDIR /usr/src/app -# Copy package.json and package-lock.json -COPY package*.json ./ +# Copy package.json from app subdirectory +COPY app/package*.json ./ -# Install dependencies RUN npm ci -# Copy source code -COPY . . +# Copy app source and shared schema +COPY app/ . +COPY classes-prisma/ /usr/src/classes-prisma/ # Add build secrets RUN --mount=type=secret,id=postgres-user \ @@ -23,13 +21,10 @@ RUN --mount=type=secret,id=postgres-user \ RUN --mount=type=secret,id=postgres-pass \ POSTGRES_PASS=$(cat /run/secrets/postgres-pass) -# Generate Prisma Client for build RUN npx prisma generate -# Build the app RUN npm run build -# Remove dev dependencies RUN npm prune --production # Running image @@ -39,20 +34,17 @@ RUN apt-get update && apt-get upgrade -y ENV NODE_ENV=production -# Set working directory WORKDIR /usr/src/app -# Copy package.json -COPY --chown=node:node package*.json ./ +COPY --chown=node:node app/package*.json ./ -#Copy prisma files -COPY --chown=node:node ./prisma ./prisma +# Copy shared Prisma schema and migrations +COPY --chown=node:node classes-prisma/ /usr/src/classes-prisma/ -# Copy built assets COPY --chown=node:node --from=builder /usr/src/app/build ./build COPY --chown=node:node --from=builder /usr/src/app/node_modules ./node_modules -COPY --chown=node:node ./wait-for-it.sh /wait-for-it.sh +COPY --chown=node:node app/wait-for-it.sh /wait-for-it.sh RUN chmod +x /wait-for-it.sh USER node diff --git a/app/Dockerfile.dev b/app/Dockerfile.dev index 252b3d8..12a34c5 100644 --- a/app/Dockerfile.dev +++ b/app/Dockerfile.dev @@ -7,25 +7,23 @@ ENV NODE_ENV=development # Set working directory WORKDIR /usr/src/app -# Copy package.json and package-lock.json -COPY package*.json ./ +# Copy package.json from app subdirectory +COPY app/package*.json ./ # Install dependencies RUN npm install -# Copy source code -COPY . . - -# Build the app -# RUN npm run build +# Copy app source and shared schema +COPY app/ . +COPY classes-prisma/ /usr/src/classes-prisma/ # Expose ports for SvelteKit and Vite's HMR EXPOSE 5173 EXPOSE 24678 EXPOSE 5555 -COPY ./entrypoint.sh /entrypoint.sh -COPY ./wait-for-it.sh /wait-for-it.sh +COPY app/entrypoint.sh /entrypoint.sh +COPY app/wait-for-it.sh /wait-for-it.sh RUN chmod +x /entrypoint.sh \ && chmod +x /wait-for-it.sh @@ -33,5 +31,3 @@ ENTRYPOINT [ "/wait-for-it.sh", "db:5432", "-t", "10", "-s" , "--", "/entrypoint # Start SvelteKit development server CMD ["npm", "run", "deploy:dev"] - -#CMD ["npx", "prisma", "studio", "--port", "5555"] \ No newline at end of file diff --git a/app/package.json b/app/package.json index 1167b73..0a025ef 100644 --- a/app/package.json +++ b/app/package.json @@ -48,7 +48,6 @@ }, "type": "module", "prisma": { - "seed": "vite-node ./prisma/seed.js", - "schema": "./prisma/schema.prisma" + "schema": "../classes-prisma/schema.prisma" } } diff --git a/app/prisma/migrations/0_init/migration.sql b/classes-prisma/migrations/0_init/migration.sql similarity index 100% rename from app/prisma/migrations/0_init/migration.sql rename to classes-prisma/migrations/0_init/migration.sql diff --git a/app/prisma/migrations/20240127184840_add_event_cancellations/migration.sql b/classes-prisma/migrations/20240127184840_add_event_cancellations/migration.sql similarity index 100% rename from app/prisma/migrations/20240127184840_add_event_cancellations/migration.sql rename to classes-prisma/migrations/20240127184840_add_event_cancellations/migration.sql diff --git a/app/prisma/migrations/20240424210639_add_visible_bool/migration.sql b/classes-prisma/migrations/20240424210639_add_visible_bool/migration.sql similarity index 100% rename from app/prisma/migrations/20240424210639_add_visible_bool/migration.sql rename to classes-prisma/migrations/20240424210639_add_visible_bool/migration.sql diff --git a/app/prisma/migrations/20241009210514_cascading_deletes/migration.sql b/classes-prisma/migrations/20241009210514_cascading_deletes/migration.sql similarity index 100% rename from app/prisma/migrations/20241009210514_cascading_deletes/migration.sql rename to classes-prisma/migrations/20241009210514_cascading_deletes/migration.sql diff --git a/app/prisma/migrations/20251019205030_update_auth_tables/migration.sql b/classes-prisma/migrations/20251019205030_update_auth_tables/migration.sql similarity index 100% rename from app/prisma/migrations/20251019205030_update_auth_tables/migration.sql rename to classes-prisma/migrations/20251019205030_update_auth_tables/migration.sql diff --git a/app/prisma/migrations/20251019213622_update_user_id_datatype/migration.sql b/classes-prisma/migrations/20251019213622_update_user_id_datatype/migration.sql similarity index 100% rename from app/prisma/migrations/20251019213622_update_user_id_datatype/migration.sql rename to classes-prisma/migrations/20251019213622_update_user_id_datatype/migration.sql diff --git a/app/prisma/migrations/migration_lock.toml b/classes-prisma/migrations/migration_lock.toml similarity index 100% rename from app/prisma/migrations/migration_lock.toml rename to classes-prisma/migrations/migration_lock.toml diff --git a/app/prisma/schema.prisma b/classes-prisma/schema.prisma similarity index 100% rename from app/prisma/schema.prisma rename to classes-prisma/schema.prisma diff --git a/compose.dev.yaml b/compose.dev.yaml index 84efa2e..8454d74 100644 --- a/compose.dev.yaml +++ b/compose.dev.yaml @@ -25,8 +25,8 @@ services: env_file: - .env build: - context: ./app - dockerfile: Dockerfile.dev + context: . + dockerfile: app/Dockerfile.dev secrets: - postgres-user - postgres-password @@ -44,6 +44,7 @@ services: volumes: - ./app:/usr/src/app - /usr/src/app/node_modules + - ./classes-prisma:/usr/src/classes-prisma environment: DEVELOPMENT: "true" TZ: "America/Chicago" @@ -55,13 +56,16 @@ services: cron: env_file: - .env - build: ./cron-service + build: + context: . + dockerfile: cron-service/Dockerfile depends_on: - db restart: unless-stopped volumes: - ./cron-service:/var/task - /var/task/node_modules + - ./classes-prisma:/classes-prisma networks: - backend diff --git a/compose.yaml b/compose.yaml index 975b81a..f5b07fc 100644 --- a/compose.yaml +++ b/compose.yaml @@ -44,7 +44,9 @@ services: cron: env_file: - .env - build: ./cron-service + build: + context: . + dockerfile: cron-service/Dockerfile depends_on: - db restart: unless-stopped diff --git a/cron-service/Dockerfile b/cron-service/Dockerfile index c1f63c3..694d3f9 100644 --- a/cron-service/Dockerfile +++ b/cron-service/Dockerfile @@ -1,22 +1,20 @@ FROM public.ecr.aws/lambda/nodejs:22 AS builder -# Set environment variables -# ENV NODE_ENV=production +COPY cron-service/package*.json ${LAMBDA_TASK_ROOT}/ -# Copy package.json and package-lock.json -COPY package*.json ${LAMBDA_TASK_ROOT} - -# Install dependencies RUN npm ci --omit=dev FROM public.ecr.aws/lambda/nodejs:22 AS final COPY --from=builder /var/task/node_modules /var/task/node_modules -COPY ./prisma ${LAMBDA_TASK_ROOT}/prisma -COPY ./handler.js ${LAMBDA_TASK_ROOT}/handler.js +COPY cron-service/prisma ${LAMBDA_TASK_ROOT}/prisma +COPY cron-service/handler.js ${LAMBDA_TASK_ROOT}/handler.js +COPY cron-service/package.json ${LAMBDA_TASK_ROOT}/package.json -COPY ./package.json ${LAMBDA_TASK_ROOT}/package.json +# Copy shared Prisma schema and migrations +# schema path "../classes-prisma/schema.prisma" relative to /var/task resolves to /classes-prisma +COPY classes-prisma/ /classes-prisma/ # Generate Prisma Client for build RUN npx prisma generate diff --git a/cron-service/package.json b/cron-service/package.json index dbf8a2d..3c9bdf4 100644 --- a/cron-service/package.json +++ b/cron-service/package.json @@ -12,6 +12,6 @@ "main": "handler.js", "prisma": { "seed": "node ./prisma/seed.js", - "schema": "./prisma/schema.prisma" + "schema": "../classes-prisma/schema.prisma" } } diff --git a/cron-service/prisma/migrations/0_init/migration.sql b/cron-service/prisma/migrations/0_init/migration.sql deleted file mode 100644 index 1d85e41..0000000 --- a/cron-service/prisma/migrations/0_init/migration.sql +++ /dev/null @@ -1,228 +0,0 @@ --- CreateEnum -CREATE TYPE "RequestType" AS ENUM ('ONDEMAND', 'NOTIFICATION'); - --- CreateTable -CREATE TABLE "NeonEventCategory" ( - "id" SERIAL NOT NULL, - "name" VARCHAR(50) NOT NULL, - "archCategoriesId" INTEGER, - - CONSTRAINT "NeonEventCategory_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "AsmblyArchCategory" ( - "id" SERIAL NOT NULL, - "name" VARCHAR(50) NOT NULL, - - CONSTRAINT "AsmblyArchCategory_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "NeonEventTeacher" ( - "id" SERIAL NOT NULL, - "name" VARCHAR(50) NOT NULL, - - CONSTRAINT "NeonEventTeacher_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "NeonBaseRegLink" ( - "id" SERIAL NOT NULL, - "url" VARCHAR(255) NOT NULL, - - CONSTRAINT "NeonBaseRegLink_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "NeonEventType" ( - "id" SERIAL NOT NULL, - "name" VARCHAR(50) NOT NULL, - - CONSTRAINT "NeonEventType_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "NeonEventInstance" ( - "eventId" INTEGER NOT NULL, - "eventTypeId" INTEGER NOT NULL, - "teacherId" INTEGER NOT NULL, - "categoryId" INTEGER NOT NULL, - "attendeeCount" SMALLINT NOT NULL, - "startDateTime" TIMESTAMP(3) NOT NULL, - "endDateTime" TIMESTAMP(3) NOT NULL, - "summary" TEXT, - "price" REAL NOT NULL, - "capacity" SMALLINT NOT NULL, - - CONSTRAINT "NeonEventInstance_pkey" PRIMARY KEY ("eventId") -); - --- CreateTable -CREATE TABLE "NeonEventInstanceRequest" ( - "id" SERIAL NOT NULL, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "fulfilled" BOOLEAN NOT NULL DEFAULT false, - "eventId" INTEGER NOT NULL, - "requesterId" INTEGER NOT NULL, - - CONSTRAINT "NeonEventInstanceRequest_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "NeonEventRequester" ( - "id" SERIAL NOT NULL, - "email" TEXT NOT NULL, - "firstName" VARCHAR(30) NOT NULL, - "lastName" VARCHAR(30) NOT NULL, - - CONSTRAINT "NeonEventRequester_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "NeonEventTypeRequest" ( - "id" SERIAL NOT NULL, - "requesterId" INTEGER NOT NULL, - "classTypeId" INTEGER NOT NULL, - "requestType" "RequestType" NOT NULL, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "fulfilled" BOOLEAN NOT NULL DEFAULT false, - - CONSTRAINT "NeonEventTypeRequest_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "User" ( - "id" TEXT NOT NULL, - "neon_id" INTEGER NOT NULL, - - CONSTRAINT "User_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "Session" ( - "id" TEXT NOT NULL, - "user_id" TEXT NOT NULL, - "active_expires" BIGINT NOT NULL, - "idle_expires" BIGINT NOT NULL, - - CONSTRAINT "Session_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "Key" ( - "id" TEXT NOT NULL, - "hashed_password" TEXT, - "user_id" TEXT NOT NULL, - - CONSTRAINT "Key_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "_NeonEventCategoryToNeonEventType" ( - "A" INTEGER NOT NULL, - "B" INTEGER NOT NULL -); - --- CreateTable -CREATE TABLE "_NeonEventTeacherToNeonEventType" ( - "A" INTEGER NOT NULL, - "B" INTEGER NOT NULL -); - --- CreateIndex -CREATE UNIQUE INDEX "NeonEventCategory_name_key" ON "NeonEventCategory"("name"); - --- CreateIndex -CREATE UNIQUE INDEX "AsmblyArchCategory_name_key" ON "AsmblyArchCategory"("name"); - --- CreateIndex -CREATE UNIQUE INDEX "NeonEventTeacher_name_key" ON "NeonEventTeacher"("name"); - --- CreateIndex -CREATE UNIQUE INDEX "NeonBaseRegLink_url_key" ON "NeonBaseRegLink"("url"); - --- CreateIndex -CREATE UNIQUE INDEX "NeonEventType_name_key" ON "NeonEventType"("name"); - --- CreateIndex -CREATE UNIQUE INDEX "NeonEventInstanceRequest_eventId_requesterId_key" ON "NeonEventInstanceRequest"("eventId", "requesterId"); - --- CreateIndex -CREATE UNIQUE INDEX "NeonEventRequester_email_key" ON "NeonEventRequester"("email"); - --- CreateIndex -CREATE UNIQUE INDEX "NeonEventTypeRequest_requestType_classTypeId_requesterId_key" ON "NeonEventTypeRequest"("requestType", "classTypeId", "requesterId"); - --- CreateIndex -CREATE UNIQUE INDEX "User_id_key" ON "User"("id"); - --- CreateIndex -CREATE UNIQUE INDEX "User_neon_id_key" ON "User"("neon_id"); - --- CreateIndex -CREATE UNIQUE INDEX "Session_id_key" ON "Session"("id"); - --- CreateIndex -CREATE INDEX "Session_user_id_idx" ON "Session"("user_id"); - --- CreateIndex -CREATE UNIQUE INDEX "Key_id_key" ON "Key"("id"); - --- CreateIndex -CREATE INDEX "Key_user_id_idx" ON "Key"("user_id"); - --- CreateIndex -CREATE UNIQUE INDEX "_NeonEventCategoryToNeonEventType_AB_unique" ON "_NeonEventCategoryToNeonEventType"("A", "B"); - --- CreateIndex -CREATE INDEX "_NeonEventCategoryToNeonEventType_B_index" ON "_NeonEventCategoryToNeonEventType"("B"); - --- CreateIndex -CREATE UNIQUE INDEX "_NeonEventTeacherToNeonEventType_AB_unique" ON "_NeonEventTeacherToNeonEventType"("A", "B"); - --- CreateIndex -CREATE INDEX "_NeonEventTeacherToNeonEventType_B_index" ON "_NeonEventTeacherToNeonEventType"("B"); - --- AddForeignKey -ALTER TABLE "NeonEventCategory" ADD CONSTRAINT "NeonEventCategory_archCategoriesId_fkey" FOREIGN KEY ("archCategoriesId") REFERENCES "AsmblyArchCategory"("id") ON DELETE SET NULL ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "NeonEventInstance" ADD CONSTRAINT "NeonEventInstance_categoryId_fkey" FOREIGN KEY ("categoryId") REFERENCES "NeonEventCategory"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "NeonEventInstance" ADD CONSTRAINT "NeonEventInstance_eventTypeId_fkey" FOREIGN KEY ("eventTypeId") REFERENCES "NeonEventType"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "NeonEventInstance" ADD CONSTRAINT "NeonEventInstance_teacherId_fkey" FOREIGN KEY ("teacherId") REFERENCES "NeonEventTeacher"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "NeonEventInstanceRequest" ADD CONSTRAINT "NeonEventInstanceRequest_eventId_fkey" FOREIGN KEY ("eventId") REFERENCES "NeonEventInstance"("eventId") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "NeonEventInstanceRequest" ADD CONSTRAINT "NeonEventInstanceRequest_requesterId_fkey" FOREIGN KEY ("requesterId") REFERENCES "NeonEventRequester"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "NeonEventTypeRequest" ADD CONSTRAINT "NeonEventTypeRequest_classTypeId_fkey" FOREIGN KEY ("classTypeId") REFERENCES "NeonEventType"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "NeonEventTypeRequest" ADD CONSTRAINT "NeonEventTypeRequest_requesterId_fkey" FOREIGN KEY ("requesterId") REFERENCES "NeonEventRequester"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Session" ADD CONSTRAINT "Session_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "Key" ADD CONSTRAINT "Key_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "_NeonEventCategoryToNeonEventType" ADD CONSTRAINT "_NeonEventCategoryToNeonEventType_A_fkey" FOREIGN KEY ("A") REFERENCES "NeonEventCategory"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "_NeonEventCategoryToNeonEventType" ADD CONSTRAINT "_NeonEventCategoryToNeonEventType_B_fkey" FOREIGN KEY ("B") REFERENCES "NeonEventType"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "_NeonEventTeacherToNeonEventType" ADD CONSTRAINT "_NeonEventTeacherToNeonEventType_A_fkey" FOREIGN KEY ("A") REFERENCES "NeonEventTeacher"("id") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "_NeonEventTeacherToNeonEventType" ADD CONSTRAINT "_NeonEventTeacherToNeonEventType_B_fkey" FOREIGN KEY ("B") REFERENCES "NeonEventType"("id") ON DELETE CASCADE ON UPDATE CASCADE; - diff --git a/cron-service/prisma/migrations/20240127184840_add_event_cancellations/migration.sql b/cron-service/prisma/migrations/20240127184840_add_event_cancellations/migration.sql deleted file mode 100644 index bb5ef5c..0000000 --- a/cron-service/prisma/migrations/20240127184840_add_event_cancellations/migration.sql +++ /dev/null @@ -1,24 +0,0 @@ --- CreateTable -CREATE TABLE "NeonEventInstanceCancellee" ( - "neonId" INTEGER NOT NULL, - - CONSTRAINT "NeonEventInstanceCancellee_pkey" PRIMARY KEY ("neonId") -); - --- CreateTable -CREATE TABLE "_NeonEventInstanceToNeonEventInstanceCancellee" ( - "A" INTEGER NOT NULL, - "B" INTEGER NOT NULL -); - --- CreateIndex -CREATE UNIQUE INDEX "_NeonEventInstanceToNeonEventInstanceCancellee_AB_unique" ON "_NeonEventInstanceToNeonEventInstanceCancellee"("A", "B"); - --- CreateIndex -CREATE INDEX "_NeonEventInstanceToNeonEventInstanceCancellee_B_index" ON "_NeonEventInstanceToNeonEventInstanceCancellee"("B"); - --- AddForeignKey -ALTER TABLE "_NeonEventInstanceToNeonEventInstanceCancellee" ADD CONSTRAINT "_NeonEventInstanceToNeonEventInstanceCancellee_A_fkey" FOREIGN KEY ("A") REFERENCES "NeonEventInstance"("eventId") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "_NeonEventInstanceToNeonEventInstanceCancellee" ADD CONSTRAINT "_NeonEventInstanceToNeonEventInstanceCancellee_B_fkey" FOREIGN KEY ("B") REFERENCES "NeonEventInstanceCancellee"("neonId") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/cron-service/prisma/migrations/20240424210639_add_visible_bool/migration.sql b/cron-service/prisma/migrations/20240424210639_add_visible_bool/migration.sql deleted file mode 100644 index aff3771..0000000 --- a/cron-service/prisma/migrations/20240424210639_add_visible_bool/migration.sql +++ /dev/null @@ -1,2 +0,0 @@ --- AlterTable -ALTER TABLE "NeonEventType" ADD COLUMN "visible" BOOLEAN DEFAULT true; diff --git a/cron-service/prisma/migrations/20241009210514_cascading_deletes/migration.sql b/cron-service/prisma/migrations/20241009210514_cascading_deletes/migration.sql deleted file mode 100644 index b028421..0000000 --- a/cron-service/prisma/migrations/20241009210514_cascading_deletes/migration.sql +++ /dev/null @@ -1,11 +0,0 @@ --- DropForeignKey -ALTER TABLE "NeonEventInstanceRequest" DROP CONSTRAINT "NeonEventInstanceRequest_eventId_fkey"; - --- DropForeignKey -ALTER TABLE "NeonEventTypeRequest" DROP CONSTRAINT "NeonEventTypeRequest_classTypeId_fkey"; - --- AddForeignKey -ALTER TABLE "NeonEventInstanceRequest" ADD CONSTRAINT "NeonEventInstanceRequest_eventId_fkey" FOREIGN KEY ("eventId") REFERENCES "NeonEventInstance"("eventId") ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "NeonEventTypeRequest" ADD CONSTRAINT "NeonEventTypeRequest_classTypeId_fkey" FOREIGN KEY ("classTypeId") REFERENCES "NeonEventType"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/cron-service/prisma/migrations/20251019205030_update_auth_tables/migration.sql b/cron-service/prisma/migrations/20251019205030_update_auth_tables/migration.sql deleted file mode 100644 index 7af411a..0000000 --- a/cron-service/prisma/migrations/20251019205030_update_auth_tables/migration.sql +++ /dev/null @@ -1,21 +0,0 @@ -/* - Warnings: - - - You are about to drop the column `active_expires` on the `Session` table. All the data in the column will be lost. - - You are about to drop the column `idle_expires` on the `Session` table. All the data in the column will be lost. - - You are about to drop the `Key` table. If the table is not empty, all the data it contains will be lost. - - Added the required column `created_at` to the `Session` table without a default value. This is not possible if the table is not empty. - - Added the required column `secret_hash` to the `Session` table without a default value. This is not possible if the table is not empty. - -*/ --- DropForeignKey -ALTER TABLE "Key" DROP CONSTRAINT "Key_user_id_fkey"; - --- AlterTable -ALTER TABLE "Session" DROP COLUMN "active_expires", -DROP COLUMN "idle_expires", -ADD COLUMN "created_at" INTEGER NOT NULL, -ADD COLUMN "secret_hash" BYTEA NOT NULL; - --- DropTable -DROP TABLE "Key"; diff --git a/cron-service/prisma/migrations/20251019213622_update_user_id_datatype/migration.sql b/cron-service/prisma/migrations/20251019213622_update_user_id_datatype/migration.sql deleted file mode 100644 index c91cb41..0000000 --- a/cron-service/prisma/migrations/20251019213622_update_user_id_datatype/migration.sql +++ /dev/null @@ -1,29 +0,0 @@ -/* - Warnings: - - - The primary key for the `User` table will be changed. If it partially fails, the table could be left without primary key constraint. - - The `id` column on the `User` table would be dropped and recreated. This will lead to data loss if there is data in the column. - - Changed the type of `user_id` on the `Session` table. No cast exists, the column would be dropped and recreated, which cannot be done if there is data, since the column is required. - -*/ --- DropForeignKey -ALTER TABLE "Session" DROP CONSTRAINT "Session_user_id_fkey"; - --- DropIndex -DROP INDEX "User_id_key"; - --- AlterTable -ALTER TABLE "Session" DROP COLUMN "user_id", -ADD COLUMN "user_id" INTEGER NOT NULL; - --- AlterTable -ALTER TABLE "User" DROP CONSTRAINT "User_pkey", -DROP COLUMN "id", -ADD COLUMN "id" SERIAL NOT NULL, -ADD CONSTRAINT "User_pkey" PRIMARY KEY ("id"); - --- CreateIndex -CREATE INDEX "Session_user_id_idx" ON "Session"("user_id"); - --- AddForeignKey -ALTER TABLE "Session" ADD CONSTRAINT "Session_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/cron-service/prisma/migrations/migration_lock.toml b/cron-service/prisma/migrations/migration_lock.toml deleted file mode 100644 index fbffa92..0000000 --- a/cron-service/prisma/migrations/migration_lock.toml +++ /dev/null @@ -1,3 +0,0 @@ -# Please do not edit this file manually -# It should be added in your version-control system (i.e. Git) -provider = "postgresql" \ No newline at end of file diff --git a/cron-service/prisma/schema.prisma b/cron-service/prisma/schema.prisma deleted file mode 100644 index 5dea6a7..0000000 --- a/cron-service/prisma/schema.prisma +++ /dev/null @@ -1,123 +0,0 @@ -generator client { - provider = "prisma-client-js" -} - -datasource db { - provider = "postgresql" - url = env("DATABASE_URL") -} - -model NeonEventCategory { - id Int @id @default(autoincrement()) - name String @unique @db.VarChar(50) - archCategoriesId Int? - archCategories AsmblyArchCategory? @relation(fields: [archCategoriesId], references: [id]) - instances NeonEventInstance[] - events NeonEventType[] @relation("NeonEventCategoryToNeonEventType") -} - -model AsmblyArchCategory { - id Int @id @default(autoincrement()) - name String @unique @db.VarChar(50) - baseCategories NeonEventCategory[] -} - -model NeonEventTeacher { - id Int @id @default(autoincrement()) - name String @unique @db.VarChar(50) - instances NeonEventInstance[] - events NeonEventType[] @relation("NeonEventTeacherToNeonEventType") -} - -model NeonBaseRegLink { - id Int @id @default(autoincrement()) - url String @unique @db.VarChar(255) -} - -model NeonEventType { - id Int @id @default(autoincrement()) - name String @unique @db.VarChar(100) - visible Boolean? @default(true) - instances NeonEventInstance[] - request NeonEventTypeRequest[] - category NeonEventCategory[] @relation("NeonEventCategoryToNeonEventType") - teacher NeonEventTeacher[] @relation("NeonEventTeacherToNeonEventType") -} - -model NeonEventInstance { - eventId Int @id - eventTypeId Int - teacherId Int - categoryId Int - attendeeCount Int @db.SmallInt - startDateTime DateTime - endDateTime DateTime - summary String? - price Float @db.Real - capacity Int @db.SmallInt - category NeonEventCategory @relation(fields: [categoryId], references: [id]) - eventType NeonEventType @relation(fields: [eventTypeId], references: [id]) - teacher NeonEventTeacher @relation(fields: [teacherId], references: [id]) - requests NeonEventInstanceRequest[] - cancellees NeonEventInstanceCancellee[] -} - -model NeonEventInstanceCancellee { - neonId Int @id - eventInstanceCancellations NeonEventInstance[] -} - -model NeonEventInstanceRequest { - id Int @id @default(autoincrement()) - createdAt DateTime @default(now()) - fulfilled Boolean @default(false) - eventId Int - requesterId Int - eventInstance NeonEventInstance @relation(fields: [eventId], references: [eventId], onDelete: Cascade) - requester NeonEventRequester @relation(fields: [requesterId], references: [id]) - - @@unique([eventId, requesterId], name: "eventInstanceRequest") -} - -model NeonEventRequester { - id Int @id @default(autoincrement()) - email String @unique - firstName String @db.VarChar(30) - lastName String @db.VarChar(30) - classInstanceRequests NeonEventInstanceRequest[] - classTypeRequests NeonEventTypeRequest[] -} - -model NeonEventTypeRequest { - id Int @id @default(autoincrement()) - requesterId Int - classTypeId Int - requestType RequestType - createdAt DateTime @default(now()) - fulfilled Boolean @default(false) - classType NeonEventType @relation(fields: [classTypeId], references: [id], onDelete: Cascade) - requester NeonEventRequester @relation(fields: [requesterId], references: [id]) - - @@unique([requestType, classTypeId, requesterId], name: "eventTypeRequest") -} - -model User { - id Int @id @default(autoincrement()) - neon_id Int @unique - auth_session Session[] -} - -model Session { - id String @id @unique - secret_hash Bytes - user_id Int - created_at Int - user User @relation(fields: [user_id], references: [id], onDelete: Cascade) - - @@index([user_id]) -} - -enum RequestType { - ONDEMAND - NOTIFICATION -} diff --git a/plan-potteryShelves.prompt.md b/plan-potteryShelves.prompt.md new file mode 100644 index 0000000..e094156 --- /dev/null +++ b/plan-potteryShelves.prompt.md @@ -0,0 +1,100 @@ +## Plan: Integrate Pottery Shelves into Asmbly Classes Site + +Rewrite the pottery-shelves Next.js/React app as a SvelteKit sub-route (`/pots`) within the asmbly-classes project. The pottery data lives in a **separate PostgreSQL database** on the same server (full isolation). All ~15 API routes and 3 pages are rewritten as Svelte components and SvelteKit endpoints. Auth reuses the existing Neon CRM flow; styles adopt Tailwind v3 + DaisyUI conventions. + +### Steps + +1. **Create a second Prisma client for pottery data isolation.** Add a second Prisma schema at `prisma/pots/schema.prisma` defining `shelves`, `assignments`, `current_assignments` (as a view), `settings`, and `change_log` tables—mirroring the Supabase schema and its migrations. Create a new `src/lib/pots/postgres.js` exporting a dedicated `PrismaClient` configured via a `POTS_DATABASE_URL` env var, keeping it fully separate from the classes `prisma` instance. + +2. **Add the `/pots` route group with its own layout.** Create `src/routes/(pots-app)/pots/+layout.svelte` with a simplified nav (ceramics wordmark, back-to-classes link) using DaisyUI classes. This layout **does not import or reference** any classes data models. Add sub-routes: `+page.svelte` (login), `member/+page.svelte`, and `admin/+page.svelte`, rewriting the React components (`MemberFlow.tsx`, `AdminDashboard.tsx`) as Svelte with reactive `$:` blocks replacing `useState`/`useMemo`. + +3. **Rewrite API routes as SvelteKit server endpoints.** Create `src/routes/(pots-app)/pots/api/` mirroring the pottery API structure (`shelves/+server.js`, `claim/+server.js`, `vacate/+server.js`, `check-member/+server.js`, `member/*/+server.js`, `admin/*/+server.js`). Each imports only from `$lib/pots/postgres.js` and `$lib/pots/` helpers—never from `$lib/postgres.js`. Port the Neon CRM lookup from `lib/neon.ts` into `$lib/pots/neon.js`, reusing env vars already available to the classes app. + +4. **Port email and changelog helpers.** Add `resend` to `package.json` dependencies. Rewrite `lib/email.ts` and `lib/changelog.ts` into `$lib/pots/email.js` and `$lib/pots/changelog.js`, using the pots Prisma client for changelog inserts and removing all Supabase/Next.js imports. + +5. **Integrate auth with existing Neon session system.** Pottery admin auth should reuse the classes app's `validateSessionToken` from `hooks.server.js`. Member auth (email-only Neon CRM check) needs no session—just the API call. Add an admin allow-list in the pots `settings` table. Update `hooks.server.js` to pass `event.locals` through to `/pots` routes without granting access to class Prisma models. + +6. **Adapt styles to the classes site design system.** Replace pottery's zinc-950 dark theme and `#b24a9a` accent with DaisyUI's `ceramics` color token (`#b34a9a` already defined in `tailwind.config.js`) and `base-100`/`base-content` semantic classes. Use DaisyUI `btn`, `card`, `input`, and `alert` components instead of raw Tailwind utility classes. + +### Further Considerations + +1. **Dependency version conflicts**: The classes app uses Tailwind v3 and ESLint v8; pottery uses Tailwind v4 and ESLint v9. Since we're rewriting into the classes stack, no new Tailwind or ESLint dependencies change—only `resend`, a second `@prisma/client` output (via `--output` flag), and optionally `@prisma/client` generator renaming are needed. + +2. **Database provisioning in Docker Compose**: The existing `compose.yaml` Postgres container serves one database. Add an init script (`docker-entrypoint-initdb.d`) to create the `pots` database with a restricted user that has **no access** to the classes database, and expose `POTS_DATABASE_URL` as a separate env var. + +3. **Cron jobs for pottery (audit, scheduled exports)**: The classes app has a `cron-service` container. Should pottery cron tasks (membership audit, roster export) be added there as additional jobs, or run as SvelteKit API cron endpoints hit by an external scheduler? Adding to cron-service is simpler but requires it to also have `POTS_DATABASE_URL`. + +--- + +### Pottery Cron Jobs + +The pottery-shelves app has three data-refresh jobs that mirror the classes cron-service pattern. All three are added **inside the existing `cron-service`** as a `pots/` subdirectory, using a **separate Prisma schema and client** pointed at `POTS_DATABASE_URL`. They never touch the classes database. + +#### New jobs + +| Job | Schedule | Trigger source | +|---|---|---| +| `potsMembershipAudit` | Configurable (daily/weekly/monthly, per `app_settings`) | AWS EventBridge rule, `cronType: "potsMembershipAudit"` | +| `potsRosterExport` | Configurable (weekly/monthly, per `app_settings`) | AWS EventBridge rule, `cronType: "potsRosterExport"` | +| `potsAutoVacateLapsed` | Weekly (fixed, Sunday 08:00 CT) | AWS EventBridge rule, `cronType: "potsAutoVacateLapsed"` | + +#### `potsMembershipAudit` (`cron-service/pots/membershipAudit.js`) + +Mirrors the existing `/api/admin/audit` endpoint logic, but runs unattended: + +1. Read `app_settings` row — skip execution if `audit_auto_run` is `false`. +2. Check whether today matches the configured `audit_frequency` + `audit_day_of_week`/`audit_day_of_month` to avoid running on off-days when the EventBridge trigger fires daily. +3. Query `current_assignments` for all non-vacated rows; deduplicate by `member_email`. +4. Call the Neon CRM API (`getMemberByEmail`) for each unique email — using the same `NEON_API_KEY` / `NEON_ORG_ID` SSM params already fetched for the classes Lambda, adding `/pots-db/dsn` as a new SSM path. +5. Write the resolved `neon_membership_status` back to the `assignments` table via the pots Prisma client. +6. Update `app_settings.last_audit_at`. +7. If `audit_auto_email` is `true` and `audit_recipients` is non-empty, build a CSV of the full roster and send it via Resend (reusing `RESEND_API_KEY` from SSM; add `/resend/api_key` as a new SSM path). + +#### `potsRosterExport` (`cron-service/pots/rosterExport.js`) + +1. Read `app_settings` — skip if `export_auto_email` is `false`. +2. Check `export_frequency` + `export_day_of_week`/`export_day_of_month` against today. +3. Query the full `shelves` table joined with `current_assignments` view. +4. Build a CSV (unit, slot, type, status, member name, email, claimed date, membership status). +5. Email the CSV to `export_recipients` via Resend; update `app_settings.last_export_at`. + +#### `potsAutoVacateLapsed` (`cron-service/pots/autoVacateLapsed.js`) + +Handles automatic cleanup of shelves held by members whose membership has lapsed beyond a configurable grace period (stored as `lapsed_grace_days` in `app_settings`, default 5): + +1. Query all current assignments where `neon_membership_status IN ('Lapsed', 'Inactive')` and `last_audit_at` is older than `lapsed_grace_days` days ago. +2. For each matching assignment, mark it vacated (`vacated_at = NOW()`, `vacated_by = 'system'`). +3. Insert a `change_log` row recording the auto-vacate action. +4. Send a `sendLapsedNotice` email to the member via Resend. +5. Log a summary of vacated slots to stdout (visible in AWS CloudWatch via the existing log group). + +#### Infrastructure changes + +**`cron-service/pots/prismaClient.js`** — a second Prisma client reading `POTS_DATABASE_URL`: + +```js +import { PrismaClient } from '@prisma/pots-client' // separate generator output +export const potsPrisma = new PrismaClient() +``` + +**`cron-service/prisma/pots/schema.prisma`** — mirrors the pottery Prisma schema (shelves, assignments, app_settings, change_log) with `output = "../../../node_modules/@prisma/pots-client"`. + +**`cron-service/package.json`** — add `resend` dependency; add a second `prisma generate --schema prisma/pots/schema.prisma` step to the Dockerfile `RUN` layer. + +**`cron-service/handler.js`** — add three new `cronType` branches and fetch `/pots-db/dsn` + `/resend/api_key` from SSM alongside the existing parameters. + +**`cron-service/crontab.txt`** (Docker Compose / local dev mode only) — add daily trigger lines; in production these are EventBridge rules: + +``` +0 6 * * * node /usr/src/app/pots/membershipAudit.js > /proc/1/fd/1 2>&1 +0 6 * * * node /usr/src/app/pots/rosterExport.js > /proc/1/fd/1 2>&1 +0 8 * * SUN node /usr/src/app/pots/autoVacateLapsed.js > /proc/1/fd/1 2>&1 +``` + +**AWS SSM** — add two new Parameter Store paths: +- `/pots-db/dsn` — connection string for the `pots` Postgres database +- `/resend/api_key` — Resend API key (if not already shared with the SvelteKit app env) + +**Isolation guarantee** — the pots Prisma client is generated into a distinct output path (`@prisma/pots-client`) so the classes `PrismaClient` import (`@prisma/client`) and the pots one can never be confused at the module level. The pots database user in Postgres has `CONNECT` privilege only on the `pots` database and no grants on the classes database schema. + + From d9c6c87354fb5551ff33e71608cbeb00105a3a67 Mon Sep 17 00:00:00 2001 From: epowell Date: Sat, 9 May 2026 14:05:03 -0500 Subject: [PATCH 2/4] Also move seed.js script --- {cron-service/prisma => classes-prisma}/seed.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {cron-service/prisma => classes-prisma}/seed.js (100%) diff --git a/cron-service/prisma/seed.js b/classes-prisma/seed.js similarity index 100% rename from cron-service/prisma/seed.js rename to classes-prisma/seed.js From fc1881b7dfb809b951788b72ce6b0dcb110e3ea8 Mon Sep 17 00:00:00 2001 From: epowell Date: Sat, 9 May 2026 14:23:24 -0500 Subject: [PATCH 3/4] move plan to correct repo --- plan-potteryShelves.prompt.md | 100 ---------------------------------- 1 file changed, 100 deletions(-) delete mode 100644 plan-potteryShelves.prompt.md diff --git a/plan-potteryShelves.prompt.md b/plan-potteryShelves.prompt.md deleted file mode 100644 index e094156..0000000 --- a/plan-potteryShelves.prompt.md +++ /dev/null @@ -1,100 +0,0 @@ -## Plan: Integrate Pottery Shelves into Asmbly Classes Site - -Rewrite the pottery-shelves Next.js/React app as a SvelteKit sub-route (`/pots`) within the asmbly-classes project. The pottery data lives in a **separate PostgreSQL database** on the same server (full isolation). All ~15 API routes and 3 pages are rewritten as Svelte components and SvelteKit endpoints. Auth reuses the existing Neon CRM flow; styles adopt Tailwind v3 + DaisyUI conventions. - -### Steps - -1. **Create a second Prisma client for pottery data isolation.** Add a second Prisma schema at `prisma/pots/schema.prisma` defining `shelves`, `assignments`, `current_assignments` (as a view), `settings`, and `change_log` tables—mirroring the Supabase schema and its migrations. Create a new `src/lib/pots/postgres.js` exporting a dedicated `PrismaClient` configured via a `POTS_DATABASE_URL` env var, keeping it fully separate from the classes `prisma` instance. - -2. **Add the `/pots` route group with its own layout.** Create `src/routes/(pots-app)/pots/+layout.svelte` with a simplified nav (ceramics wordmark, back-to-classes link) using DaisyUI classes. This layout **does not import or reference** any classes data models. Add sub-routes: `+page.svelte` (login), `member/+page.svelte`, and `admin/+page.svelte`, rewriting the React components (`MemberFlow.tsx`, `AdminDashboard.tsx`) as Svelte with reactive `$:` blocks replacing `useState`/`useMemo`. - -3. **Rewrite API routes as SvelteKit server endpoints.** Create `src/routes/(pots-app)/pots/api/` mirroring the pottery API structure (`shelves/+server.js`, `claim/+server.js`, `vacate/+server.js`, `check-member/+server.js`, `member/*/+server.js`, `admin/*/+server.js`). Each imports only from `$lib/pots/postgres.js` and `$lib/pots/` helpers—never from `$lib/postgres.js`. Port the Neon CRM lookup from `lib/neon.ts` into `$lib/pots/neon.js`, reusing env vars already available to the classes app. - -4. **Port email and changelog helpers.** Add `resend` to `package.json` dependencies. Rewrite `lib/email.ts` and `lib/changelog.ts` into `$lib/pots/email.js` and `$lib/pots/changelog.js`, using the pots Prisma client for changelog inserts and removing all Supabase/Next.js imports. - -5. **Integrate auth with existing Neon session system.** Pottery admin auth should reuse the classes app's `validateSessionToken` from `hooks.server.js`. Member auth (email-only Neon CRM check) needs no session—just the API call. Add an admin allow-list in the pots `settings` table. Update `hooks.server.js` to pass `event.locals` through to `/pots` routes without granting access to class Prisma models. - -6. **Adapt styles to the classes site design system.** Replace pottery's zinc-950 dark theme and `#b24a9a` accent with DaisyUI's `ceramics` color token (`#b34a9a` already defined in `tailwind.config.js`) and `base-100`/`base-content` semantic classes. Use DaisyUI `btn`, `card`, `input`, and `alert` components instead of raw Tailwind utility classes. - -### Further Considerations - -1. **Dependency version conflicts**: The classes app uses Tailwind v3 and ESLint v8; pottery uses Tailwind v4 and ESLint v9. Since we're rewriting into the classes stack, no new Tailwind or ESLint dependencies change—only `resend`, a second `@prisma/client` output (via `--output` flag), and optionally `@prisma/client` generator renaming are needed. - -2. **Database provisioning in Docker Compose**: The existing `compose.yaml` Postgres container serves one database. Add an init script (`docker-entrypoint-initdb.d`) to create the `pots` database with a restricted user that has **no access** to the classes database, and expose `POTS_DATABASE_URL` as a separate env var. - -3. **Cron jobs for pottery (audit, scheduled exports)**: The classes app has a `cron-service` container. Should pottery cron tasks (membership audit, roster export) be added there as additional jobs, or run as SvelteKit API cron endpoints hit by an external scheduler? Adding to cron-service is simpler but requires it to also have `POTS_DATABASE_URL`. - ---- - -### Pottery Cron Jobs - -The pottery-shelves app has three data-refresh jobs that mirror the classes cron-service pattern. All three are added **inside the existing `cron-service`** as a `pots/` subdirectory, using a **separate Prisma schema and client** pointed at `POTS_DATABASE_URL`. They never touch the classes database. - -#### New jobs - -| Job | Schedule | Trigger source | -|---|---|---| -| `potsMembershipAudit` | Configurable (daily/weekly/monthly, per `app_settings`) | AWS EventBridge rule, `cronType: "potsMembershipAudit"` | -| `potsRosterExport` | Configurable (weekly/monthly, per `app_settings`) | AWS EventBridge rule, `cronType: "potsRosterExport"` | -| `potsAutoVacateLapsed` | Weekly (fixed, Sunday 08:00 CT) | AWS EventBridge rule, `cronType: "potsAutoVacateLapsed"` | - -#### `potsMembershipAudit` (`cron-service/pots/membershipAudit.js`) - -Mirrors the existing `/api/admin/audit` endpoint logic, but runs unattended: - -1. Read `app_settings` row — skip execution if `audit_auto_run` is `false`. -2. Check whether today matches the configured `audit_frequency` + `audit_day_of_week`/`audit_day_of_month` to avoid running on off-days when the EventBridge trigger fires daily. -3. Query `current_assignments` for all non-vacated rows; deduplicate by `member_email`. -4. Call the Neon CRM API (`getMemberByEmail`) for each unique email — using the same `NEON_API_KEY` / `NEON_ORG_ID` SSM params already fetched for the classes Lambda, adding `/pots-db/dsn` as a new SSM path. -5. Write the resolved `neon_membership_status` back to the `assignments` table via the pots Prisma client. -6. Update `app_settings.last_audit_at`. -7. If `audit_auto_email` is `true` and `audit_recipients` is non-empty, build a CSV of the full roster and send it via Resend (reusing `RESEND_API_KEY` from SSM; add `/resend/api_key` as a new SSM path). - -#### `potsRosterExport` (`cron-service/pots/rosterExport.js`) - -1. Read `app_settings` — skip if `export_auto_email` is `false`. -2. Check `export_frequency` + `export_day_of_week`/`export_day_of_month` against today. -3. Query the full `shelves` table joined with `current_assignments` view. -4. Build a CSV (unit, slot, type, status, member name, email, claimed date, membership status). -5. Email the CSV to `export_recipients` via Resend; update `app_settings.last_export_at`. - -#### `potsAutoVacateLapsed` (`cron-service/pots/autoVacateLapsed.js`) - -Handles automatic cleanup of shelves held by members whose membership has lapsed beyond a configurable grace period (stored as `lapsed_grace_days` in `app_settings`, default 5): - -1. Query all current assignments where `neon_membership_status IN ('Lapsed', 'Inactive')` and `last_audit_at` is older than `lapsed_grace_days` days ago. -2. For each matching assignment, mark it vacated (`vacated_at = NOW()`, `vacated_by = 'system'`). -3. Insert a `change_log` row recording the auto-vacate action. -4. Send a `sendLapsedNotice` email to the member via Resend. -5. Log a summary of vacated slots to stdout (visible in AWS CloudWatch via the existing log group). - -#### Infrastructure changes - -**`cron-service/pots/prismaClient.js`** — a second Prisma client reading `POTS_DATABASE_URL`: - -```js -import { PrismaClient } from '@prisma/pots-client' // separate generator output -export const potsPrisma = new PrismaClient() -``` - -**`cron-service/prisma/pots/schema.prisma`** — mirrors the pottery Prisma schema (shelves, assignments, app_settings, change_log) with `output = "../../../node_modules/@prisma/pots-client"`. - -**`cron-service/package.json`** — add `resend` dependency; add a second `prisma generate --schema prisma/pots/schema.prisma` step to the Dockerfile `RUN` layer. - -**`cron-service/handler.js`** — add three new `cronType` branches and fetch `/pots-db/dsn` + `/resend/api_key` from SSM alongside the existing parameters. - -**`cron-service/crontab.txt`** (Docker Compose / local dev mode only) — add daily trigger lines; in production these are EventBridge rules: - -``` -0 6 * * * node /usr/src/app/pots/membershipAudit.js > /proc/1/fd/1 2>&1 -0 6 * * * node /usr/src/app/pots/rosterExport.js > /proc/1/fd/1 2>&1 -0 8 * * SUN node /usr/src/app/pots/autoVacateLapsed.js > /proc/1/fd/1 2>&1 -``` - -**AWS SSM** — add two new Parameter Store paths: -- `/pots-db/dsn` — connection string for the `pots` Postgres database -- `/resend/api_key` — Resend API key (if not already shared with the SvelteKit app env) - -**Isolation guarantee** — the pots Prisma client is generated into a distinct output path (`@prisma/pots-client`) so the classes `PrismaClient` import (`@prisma/client`) and the pots one can never be confused at the module level. The pots database user in Postgres has `CONNECT` privilege only on the `pots` database and no grants on the classes database schema. - - From c554b94c259081e0393fd1cdb0a77ca947d6756c Mon Sep 17 00:00:00 2001 From: epowell Date: Sat, 9 May 2026 21:40:15 -0500 Subject: [PATCH 4/4] Move final schema file missed by the agent --- .../20260425025414_update_event_name_length/migration.sql | 0 .../20260425025414_update_event_name_length/migration.sql | 2 -- 2 files changed, 2 deletions(-) rename {app/prisma => classes-prisma}/migrations/20260425025414_update_event_name_length/migration.sql (100%) delete mode 100644 cron-service/prisma/migrations/20260425025414_update_event_name_length/migration.sql diff --git a/app/prisma/migrations/20260425025414_update_event_name_length/migration.sql b/classes-prisma/migrations/20260425025414_update_event_name_length/migration.sql similarity index 100% rename from app/prisma/migrations/20260425025414_update_event_name_length/migration.sql rename to classes-prisma/migrations/20260425025414_update_event_name_length/migration.sql diff --git a/cron-service/prisma/migrations/20260425025414_update_event_name_length/migration.sql b/cron-service/prisma/migrations/20260425025414_update_event_name_length/migration.sql deleted file mode 100644 index 76f94ff..0000000 --- a/cron-service/prisma/migrations/20260425025414_update_event_name_length/migration.sql +++ /dev/null @@ -1,2 +0,0 @@ --- AlterTable -ALTER TABLE "NeonEventType" ALTER COLUMN "name" SET DATA TYPE VARCHAR(100);