ci: automate Prisma migrations in pipeline and deploy [skip-deploy]
Some checks failed
Basil CI/CD Pipeline / Shared Package Tests (push) Successful in 3m18s
Basil CI/CD Pipeline / Code Linting (push) Successful in 3m39s
Basil CI/CD Pipeline / Web Tests (push) Successful in 4m1s
Basil CI/CD Pipeline / API Tests (push) Failing after 4m7s
Basil CI/CD Pipeline / Trigger Deployment (push) Has been skipped
Basil CI/CD Pipeline / Security Scanning (push) Successful in 3m42s
Basil CI/CD Pipeline / Build All Packages (push) Has been skipped
Basil CI/CD Pipeline / E2E Tests (push) Has been skipped
Basil CI/CD Pipeline / Build & Push Docker Images (push) Has been skipped
Some checks failed
Basil CI/CD Pipeline / Shared Package Tests (push) Successful in 3m18s
Basil CI/CD Pipeline / Code Linting (push) Successful in 3m39s
Basil CI/CD Pipeline / Web Tests (push) Successful in 4m1s
Basil CI/CD Pipeline / API Tests (push) Failing after 4m7s
Basil CI/CD Pipeline / Trigger Deployment (push) Has been skipped
Basil CI/CD Pipeline / Security Scanning (push) Successful in 3m42s
Basil CI/CD Pipeline / Build All Packages (push) Has been skipped
Basil CI/CD Pipeline / E2E Tests (push) Has been skipped
Basil CI/CD Pipeline / Build & Push Docker Images (push) Has been skipped
Moves migration handling into the pipeline and production deploy so schema changes ship atomically with the code that depends on them. Previously migrations were manual and the migrations/ directory was gitignored, which caused silent drift between environments. - Track packages/api/prisma/migrations/ in git (including the baseline 20260416000000_init and the family-tenant delta). - Add `prisma:deploy` script that runs `prisma migrate deploy` (the non-interactive, CI-safe command). `prisma:migrate` still maps to `migrate dev` for local authoring. - Pipeline test-api and e2e-tests jobs now use `prisma:deploy` and test-api adds a drift check (`prisma migrate diff --exit-code`) that fails the build if schema.prisma has changes without a corresponding migration. - deploy.sh runs migrations against prod using `docker run --rm` with the freshly pulled API image before restarting containers, so a failing migration aborts the deploy with the old containers still serving traffic. The [skip-deploy] tag avoids re-triggering deployment for this infrastructure commit; the updated deploy.sh must be pulled to the production host out-of-band before the next deployment benefits from the new migration step. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -87,8 +87,17 @@ jobs:
|
||||
- name: Generate Prisma Client
|
||||
run: cd packages/api && npm run prisma:generate
|
||||
|
||||
- name: Run database migrations
|
||||
run: cd packages/api && npm run prisma:migrate
|
||||
- name: Apply database migrations
|
||||
run: cd packages/api && npm run prisma:deploy
|
||||
env:
|
||||
DATABASE_URL: postgresql://basil:basil@postgres:5432/basil_test?schema=public
|
||||
|
||||
- name: Check for schema drift
|
||||
run: |
|
||||
cd packages/api && npx prisma migrate diff \
|
||||
--from-url "$DATABASE_URL" \
|
||||
--to-schema-datamodel ./prisma/schema.prisma \
|
||||
--exit-code && echo "✓ schema.prisma matches applied migrations"
|
||||
env:
|
||||
DATABASE_URL: postgresql://basil:basil@postgres:5432/basil_test?schema=public
|
||||
|
||||
@@ -276,8 +285,8 @@ jobs:
|
||||
- name: Build application
|
||||
run: npm run build
|
||||
|
||||
- name: Run database migrations
|
||||
run: cd packages/api && npm run prisma:migrate
|
||||
- name: Apply database migrations
|
||||
run: cd packages/api && npm run prisma:deploy
|
||||
env:
|
||||
DATABASE_URL: postgresql://basil:basil@postgres:5432/basil?schema=public
|
||||
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -62,5 +62,5 @@ backups/
|
||||
docker-compose.override.yml
|
||||
|
||||
# Prisma
|
||||
packages/api/prisma/migrations/
|
||||
# Migrations are tracked. Applied automatically by deploy.sh (via `prisma migrate deploy`).
|
||||
# Pipeline Test
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"test:coverage": "vitest run --coverage",
|
||||
"prisma:generate": "prisma generate",
|
||||
"prisma:migrate": "prisma migrate dev",
|
||||
"prisma:deploy": "prisma migrate deploy",
|
||||
"prisma:studio": "prisma studio",
|
||||
"create-admin": "tsx src/scripts/create-admin.ts",
|
||||
"lint": "eslint src --ext .ts"
|
||||
|
||||
455
packages/api/prisma/migrations/20260416000000_init/migration.sql
Normal file
455
packages/api/prisma/migrations/20260416000000_init/migration.sql
Normal file
@@ -0,0 +1,455 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "TokenType" AS ENUM ('EMAIL_VERIFICATION', 'PASSWORD_RESET');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "Role" AS ENUM ('USER', 'ADMIN');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "Visibility" AS ENUM ('PRIVATE', 'SHARED', 'PUBLIC');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "MealType" AS ENUM ('BREAKFAST', 'LUNCH', 'DINNER', 'SNACK', 'DESSERT', 'OTHER');
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "User" (
|
||||
"id" TEXT NOT NULL,
|
||||
"email" TEXT NOT NULL,
|
||||
"username" TEXT,
|
||||
"passwordHash" TEXT,
|
||||
"name" TEXT,
|
||||
"avatar" TEXT,
|
||||
"provider" TEXT NOT NULL DEFAULT 'local',
|
||||
"providerId" TEXT,
|
||||
"role" "Role" NOT NULL DEFAULT 'USER',
|
||||
"emailVerified" BOOLEAN NOT NULL DEFAULT false,
|
||||
"emailVerifiedAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "VerificationToken" (
|
||||
"id" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"token" TEXT NOT NULL,
|
||||
"type" "TokenType" NOT NULL,
|
||||
"expiresAt" TIMESTAMP(3) NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "VerificationToken_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "RefreshToken" (
|
||||
"id" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"token" TEXT NOT NULL,
|
||||
"expiresAt" TIMESTAMP(3) NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "RefreshToken_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Recipe" (
|
||||
"id" TEXT NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"prepTime" INTEGER,
|
||||
"cookTime" INTEGER,
|
||||
"totalTime" INTEGER,
|
||||
"servings" INTEGER,
|
||||
"imageUrl" TEXT,
|
||||
"sourceUrl" TEXT,
|
||||
"author" TEXT,
|
||||
"cuisine" TEXT,
|
||||
"categories" TEXT[] DEFAULT ARRAY[]::TEXT[],
|
||||
"rating" DOUBLE PRECISION,
|
||||
"userId" TEXT,
|
||||
"visibility" "Visibility" NOT NULL DEFAULT 'PRIVATE',
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Recipe_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "RecipeSection" (
|
||||
"id" TEXT NOT NULL,
|
||||
"recipeId" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"order" INTEGER NOT NULL,
|
||||
"timing" TEXT,
|
||||
|
||||
CONSTRAINT "RecipeSection_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Ingredient" (
|
||||
"id" TEXT NOT NULL,
|
||||
"recipeId" TEXT,
|
||||
"sectionId" TEXT,
|
||||
"name" TEXT NOT NULL,
|
||||
"amount" TEXT,
|
||||
"unit" TEXT,
|
||||
"notes" TEXT,
|
||||
"order" INTEGER NOT NULL,
|
||||
|
||||
CONSTRAINT "Ingredient_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Instruction" (
|
||||
"id" TEXT NOT NULL,
|
||||
"recipeId" TEXT,
|
||||
"sectionId" TEXT,
|
||||
"step" INTEGER NOT NULL,
|
||||
"text" TEXT NOT NULL,
|
||||
"imageUrl" TEXT,
|
||||
"timing" TEXT,
|
||||
|
||||
CONSTRAINT "Instruction_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "IngredientInstructionMapping" (
|
||||
"id" TEXT NOT NULL,
|
||||
"ingredientId" TEXT NOT NULL,
|
||||
"instructionId" TEXT NOT NULL,
|
||||
"order" INTEGER NOT NULL,
|
||||
|
||||
CONSTRAINT "IngredientInstructionMapping_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "RecipeImage" (
|
||||
"id" TEXT NOT NULL,
|
||||
"recipeId" TEXT NOT NULL,
|
||||
"url" TEXT NOT NULL,
|
||||
"order" INTEGER NOT NULL,
|
||||
|
||||
CONSTRAINT "RecipeImage_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Tag" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "Tag_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "RecipeTag" (
|
||||
"recipeId" TEXT NOT NULL,
|
||||
"tagId" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "RecipeTag_pkey" PRIMARY KEY ("recipeId","tagId")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "CookbookTag" (
|
||||
"cookbookId" TEXT NOT NULL,
|
||||
"tagId" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "CookbookTag_pkey" PRIMARY KEY ("cookbookId","tagId")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "RecipeShare" (
|
||||
"id" TEXT NOT NULL,
|
||||
"recipeId" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "RecipeShare_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Cookbook" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"coverImageUrl" TEXT,
|
||||
"userId" TEXT,
|
||||
"autoFilterCategories" TEXT[] DEFAULT ARRAY[]::TEXT[],
|
||||
"autoFilterTags" TEXT[] DEFAULT ARRAY[]::TEXT[],
|
||||
"autoFilterCookbookTags" TEXT[] DEFAULT ARRAY[]::TEXT[],
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Cookbook_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "CookbookRecipe" (
|
||||
"id" TEXT NOT NULL,
|
||||
"cookbookId" TEXT NOT NULL,
|
||||
"recipeId" TEXT NOT NULL,
|
||||
"addedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "CookbookRecipe_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "CookbookInclusion" (
|
||||
"id" TEXT NOT NULL,
|
||||
"parentCookbookId" TEXT NOT NULL,
|
||||
"childCookbookId" TEXT NOT NULL,
|
||||
"addedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "CookbookInclusion_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "MealPlan" (
|
||||
"id" TEXT NOT NULL,
|
||||
"userId" TEXT,
|
||||
"date" TIMESTAMP(3) NOT NULL,
|
||||
"notes" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "MealPlan_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Meal" (
|
||||
"id" TEXT NOT NULL,
|
||||
"mealPlanId" TEXT NOT NULL,
|
||||
"mealType" "MealType" NOT NULL,
|
||||
"order" INTEGER NOT NULL,
|
||||
"servings" INTEGER,
|
||||
"notes" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Meal_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "MealRecipe" (
|
||||
"mealId" TEXT NOT NULL,
|
||||
"recipeId" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "MealRecipe_pkey" PRIMARY KEY ("mealId")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "User_username_key" ON "User"("username");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "User_email_idx" ON "User"("email");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "User_provider_providerId_idx" ON "User"("provider", "providerId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "VerificationToken_token_key" ON "VerificationToken"("token");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "VerificationToken_userId_idx" ON "VerificationToken"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "VerificationToken_token_idx" ON "VerificationToken"("token");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "RefreshToken_token_key" ON "RefreshToken"("token");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "RefreshToken_userId_idx" ON "RefreshToken"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "RefreshToken_token_idx" ON "RefreshToken"("token");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Recipe_title_idx" ON "Recipe"("title");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Recipe_cuisine_idx" ON "Recipe"("cuisine");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Recipe_userId_idx" ON "Recipe"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Recipe_visibility_idx" ON "Recipe"("visibility");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "RecipeSection_recipeId_idx" ON "RecipeSection"("recipeId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Ingredient_recipeId_idx" ON "Ingredient"("recipeId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Ingredient_sectionId_idx" ON "Ingredient"("sectionId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Instruction_recipeId_idx" ON "Instruction"("recipeId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Instruction_sectionId_idx" ON "Instruction"("sectionId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "IngredientInstructionMapping_instructionId_idx" ON "IngredientInstructionMapping"("instructionId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "IngredientInstructionMapping_ingredientId_idx" ON "IngredientInstructionMapping"("ingredientId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "IngredientInstructionMapping_ingredientId_instructionId_key" ON "IngredientInstructionMapping"("ingredientId", "instructionId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "RecipeImage_recipeId_idx" ON "RecipeImage"("recipeId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Tag_name_key" ON "Tag"("name");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "RecipeTag_recipeId_idx" ON "RecipeTag"("recipeId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "RecipeTag_tagId_idx" ON "RecipeTag"("tagId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "CookbookTag_cookbookId_idx" ON "CookbookTag"("cookbookId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "CookbookTag_tagId_idx" ON "CookbookTag"("tagId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "RecipeShare_recipeId_idx" ON "RecipeShare"("recipeId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "RecipeShare_userId_idx" ON "RecipeShare"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "RecipeShare_recipeId_userId_key" ON "RecipeShare"("recipeId", "userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Cookbook_name_idx" ON "Cookbook"("name");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Cookbook_userId_idx" ON "Cookbook"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "CookbookRecipe_cookbookId_idx" ON "CookbookRecipe"("cookbookId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "CookbookRecipe_recipeId_idx" ON "CookbookRecipe"("recipeId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "CookbookRecipe_cookbookId_recipeId_key" ON "CookbookRecipe"("cookbookId", "recipeId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "CookbookInclusion_parentCookbookId_idx" ON "CookbookInclusion"("parentCookbookId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "CookbookInclusion_childCookbookId_idx" ON "CookbookInclusion"("childCookbookId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "CookbookInclusion_parentCookbookId_childCookbookId_key" ON "CookbookInclusion"("parentCookbookId", "childCookbookId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "MealPlan_userId_idx" ON "MealPlan"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "MealPlan_date_idx" ON "MealPlan"("date");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "MealPlan_userId_date_idx" ON "MealPlan"("userId", "date");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "MealPlan_userId_date_key" ON "MealPlan"("userId", "date");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Meal_mealPlanId_idx" ON "Meal"("mealPlanId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Meal_mealType_idx" ON "Meal"("mealType");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "MealRecipe_recipeId_idx" ON "MealRecipe"("recipeId");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "VerificationToken" ADD CONSTRAINT "VerificationToken_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "RefreshToken" ADD CONSTRAINT "RefreshToken_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Recipe" ADD CONSTRAINT "Recipe_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "RecipeSection" ADD CONSTRAINT "RecipeSection_recipeId_fkey" FOREIGN KEY ("recipeId") REFERENCES "Recipe"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Ingredient" ADD CONSTRAINT "Ingredient_recipeId_fkey" FOREIGN KEY ("recipeId") REFERENCES "Recipe"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Ingredient" ADD CONSTRAINT "Ingredient_sectionId_fkey" FOREIGN KEY ("sectionId") REFERENCES "RecipeSection"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Instruction" ADD CONSTRAINT "Instruction_recipeId_fkey" FOREIGN KEY ("recipeId") REFERENCES "Recipe"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Instruction" ADD CONSTRAINT "Instruction_sectionId_fkey" FOREIGN KEY ("sectionId") REFERENCES "RecipeSection"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "IngredientInstructionMapping" ADD CONSTRAINT "IngredientInstructionMapping_ingredientId_fkey" FOREIGN KEY ("ingredientId") REFERENCES "Ingredient"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "IngredientInstructionMapping" ADD CONSTRAINT "IngredientInstructionMapping_instructionId_fkey" FOREIGN KEY ("instructionId") REFERENCES "Instruction"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "RecipeImage" ADD CONSTRAINT "RecipeImage_recipeId_fkey" FOREIGN KEY ("recipeId") REFERENCES "Recipe"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "RecipeTag" ADD CONSTRAINT "RecipeTag_recipeId_fkey" FOREIGN KEY ("recipeId") REFERENCES "Recipe"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "RecipeTag" ADD CONSTRAINT "RecipeTag_tagId_fkey" FOREIGN KEY ("tagId") REFERENCES "Tag"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "CookbookTag" ADD CONSTRAINT "CookbookTag_cookbookId_fkey" FOREIGN KEY ("cookbookId") REFERENCES "Cookbook"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "CookbookTag" ADD CONSTRAINT "CookbookTag_tagId_fkey" FOREIGN KEY ("tagId") REFERENCES "Tag"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "RecipeShare" ADD CONSTRAINT "RecipeShare_recipeId_fkey" FOREIGN KEY ("recipeId") REFERENCES "Recipe"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "RecipeShare" ADD CONSTRAINT "RecipeShare_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Cookbook" ADD CONSTRAINT "Cookbook_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "CookbookRecipe" ADD CONSTRAINT "CookbookRecipe_cookbookId_fkey" FOREIGN KEY ("cookbookId") REFERENCES "Cookbook"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "CookbookRecipe" ADD CONSTRAINT "CookbookRecipe_recipeId_fkey" FOREIGN KEY ("recipeId") REFERENCES "Recipe"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "CookbookInclusion" ADD CONSTRAINT "CookbookInclusion_parentCookbookId_fkey" FOREIGN KEY ("parentCookbookId") REFERENCES "Cookbook"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "CookbookInclusion" ADD CONSTRAINT "CookbookInclusion_childCookbookId_fkey" FOREIGN KEY ("childCookbookId") REFERENCES "Cookbook"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "MealPlan" ADD CONSTRAINT "MealPlan_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Meal" ADD CONSTRAINT "Meal_mealPlanId_fkey" FOREIGN KEY ("mealPlanId") REFERENCES "MealPlan"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "MealRecipe" ADD CONSTRAINT "MealRecipe_mealId_fkey" FOREIGN KEY ("mealId") REFERENCES "Meal"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "MealRecipe" ADD CONSTRAINT "MealRecipe_recipeId_fkey" FOREIGN KEY ("recipeId") REFERENCES "Recipe"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "FamilyRole" AS ENUM ('OWNER', 'MEMBER');
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Cookbook" ADD COLUMN "familyId" TEXT;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Recipe" ADD COLUMN "familyId" TEXT;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Family" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Family_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "FamilyMember" (
|
||||
"id" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"familyId" TEXT NOT NULL,
|
||||
"role" "FamilyRole" NOT NULL DEFAULT 'MEMBER',
|
||||
"joinedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "FamilyMember_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Family_name_idx" ON "Family"("name");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "FamilyMember_userId_idx" ON "FamilyMember"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "FamilyMember_familyId_idx" ON "FamilyMember"("familyId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "FamilyMember_userId_familyId_key" ON "FamilyMember"("userId", "familyId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Cookbook_familyId_idx" ON "Cookbook"("familyId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Recipe_familyId_idx" ON "Recipe"("familyId");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "FamilyMember" ADD CONSTRAINT "FamilyMember_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "FamilyMember" ADD CONSTRAINT "FamilyMember_familyId_fkey" FOREIGN KEY ("familyId") REFERENCES "Family"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Recipe" ADD CONSTRAINT "Recipe_familyId_fkey" FOREIGN KEY ("familyId") REFERENCES "Family"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Cookbook" ADD CONSTRAINT "Cookbook_familyId_fkey" FOREIGN KEY ("familyId") REFERENCES "Family"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
3
packages/api/prisma/migrations/migration_lock.toml
Normal file
3
packages/api/prisma/migrations/migration_lock.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
# Please do not edit this file manually
|
||||
# It should be added in your version-control system (e.g., Git)
|
||||
provider = "postgresql"
|
||||
@@ -131,6 +131,32 @@ EOF
|
||||
log "Docker Compose override file created"
|
||||
}
|
||||
|
||||
# Apply database migrations using the newly-pulled API image.
|
||||
# Runs before restart so a failed migration leaves the old containers running.
|
||||
run_migrations() {
|
||||
log "Applying database migrations..."
|
||||
|
||||
if [ -z "$DATABASE_URL" ]; then
|
||||
error "DATABASE_URL not set in .env — cannot apply migrations"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local API_IMAGE="${DOCKER_REGISTRY}/${DOCKER_USERNAME}/basil-api:${IMAGE_TAG}"
|
||||
|
||||
# Use --network=host so the container can reach the same DB host the app uses.
|
||||
# schema.prisma and migrations/ ship inside the API image.
|
||||
docker run --rm \
|
||||
--network host \
|
||||
-e DATABASE_URL="$DATABASE_URL" \
|
||||
"$API_IMAGE" \
|
||||
npx prisma migrate deploy || {
|
||||
error "Migration failed — aborting deploy. Old containers are still running."
|
||||
exit 1
|
||||
}
|
||||
|
||||
log "Migrations applied successfully"
|
||||
}
|
||||
|
||||
# Restart containers
|
||||
restart_containers() {
|
||||
log "Restarting containers..."
|
||||
@@ -219,6 +245,7 @@ main() {
|
||||
login_to_harbor
|
||||
create_backup
|
||||
pull_images
|
||||
run_migrations
|
||||
update_docker_compose
|
||||
restart_containers
|
||||
health_check
|
||||
|
||||
Reference in New Issue
Block a user