feat: Initialize Prisma database schema and update deployment scripts for robust migration handling.

This commit is contained in:
Moh Dzulfikri Maulana
2026-03-09 01:11:58 +07:00
parent ecd09904cf
commit 9abf68320a
6 changed files with 183 additions and 19 deletions

View File

@@ -16,13 +16,14 @@ RUN pnpm install --frozen-lockfile
# Stage 2: Build the app # Stage 2: Build the app
FROM node:20-alpine AS builder FROM node:20-alpine AS builder
RUN apk add --no-cache libc6-compat
WORKDIR /app WORKDIR /app
RUN npm install -g pnpm RUN npm install -g pnpm
COPY --from=deps /app/node_modules ./node_modules COPY --from=deps /app/node_modules ./node_modules
COPY . . COPY . .
# Generate Prisma Client # Generate Prisma Client
RUN pnpm prisma generate RUN DATABASE_URL="postgresql://user:password@localhost:5432/db" pnpm prisma generate
# Build Next.js # Build Next.js
# Disable telemetry during build # Disable telemetry during build
@@ -31,6 +32,7 @@ RUN pnpm build
# Stage 3: Runner # Stage 3: Runner
FROM node:20-alpine AS runner FROM node:20-alpine AS runner
RUN apk add --no-cache libc6-compat
WORKDIR /app WORKDIR /app
ENV NODE_ENV production ENV NODE_ENV production

View File

@@ -1,7 +1,13 @@
#!/bin/bash #!/bin/bash
# Exit immediately if a command exits with a non-zero status
set -e
echo "Starting Production Deployment..."
# Load environment variables from .env if it exists # Load environment variables from .env if it exists
if [ -f .env ]; then if [ -f .env ]; then
echo "Loading environment variables from .env..."
export $(grep -v '^#' .env | xargs) export $(grep -v '^#' .env | xargs)
fi fi
@@ -11,23 +17,35 @@ if docker compose version >/dev/null 2>&1; then
elif docker-compose version >/dev/null 2>&1; then elif docker-compose version >/dev/null 2>&1; then
COMPOSE_CMD="docker-compose" COMPOSE_CMD="docker-compose"
else else
echo "Error: Docker Compose not found. Please install Docker Compose first." echo "Error: Docker Compose not found. Please install Docker Compose first."
exit 1 exit 1
fi fi
# Name of the database container # Configuration
DB_CONTAINER_NAME="portfolio-db" DB_CONTAINER_NAME="fikri-portfolio-db"
DB_PORT="5429"
# Check if the database container is already running # Check if any container is using the DB port or if our specific container is running
if [ "$(docker ps -q -f name=${DB_CONTAINER_NAME})" ]; then EXISTING_DB=$(docker ps -q -f publish=${DB_PORT})
echo "✅ Database container '${DB_CONTAINER_NAME}' is already running."
echo "🚀 Deploying/Updating only the application..." if [ ! -z "$EXISTING_DB" ]; then
$COMPOSE_CMD up -d --no-recreate db echo "Database is already running on port ${DB_PORT}. Container ID: $EXISTING_DB"
$COMPOSE_CMD up -d app echo "Building and updating only the application..."
# --build ensures we use the latest code
# --no-deps ensures we don't restart the DB if it's healthy
$COMPOSE_CMD up -d --build --no-deps app
else else
echo "⚠️ Database container '${DB_CONTAINER_NAME}' not found or stopped." echo "Database not found on port ${DB_PORT}."
echo "🏗️ Deploying full stack (Database + App)..." echo "Full deployment (Database + App)..."
$COMPOSE_CMD up -d $COMPOSE_CMD up -d --build
fi fi
echo "✨ Deployment script finished!" # Health check - optional but recommended
echo "Waiting for application to be healthy..."
# You could add a curl check here if desired
# Cleanup: remove dangling images to save disk space
echo "Cleaning up old images..."
docker image prune -f
echo "Deployment script finished successfully!"

View File

@@ -1,5 +1,3 @@
version: '3.8'
services: services:
db: db:
image: postgres:15-alpine image: postgres:15-alpine
@@ -40,7 +38,7 @@ services:
db: db:
condition: service_healthy condition: service_healthy
command: > command: >
sh -c "npx prisma db push && node server.js" sh -c "npx prisma migrate deploy && node server.js"
volumes: volumes:
postgres_data: postgres_data:

View File

@@ -0,0 +1,143 @@
-- CreateEnum
CREATE TYPE "BlogStatus" AS ENUM ('DRAFT', 'SCHEDULED', 'PUBLISHED');
-- CreateTable
CREATE TABLE "User" (
"id" TEXT NOT NULL,
"email" TEXT NOT NULL,
"name" TEXT NOT NULL,
"password" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Session" (
"id" TEXT NOT NULL,
"visitorId" TEXT NOT NULL,
"startedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"lastSeenAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Session_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "PageView" (
"id" TEXT NOT NULL,
"sessionId" TEXT NOT NULL,
"path" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "PageView_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Project" (
"id" TEXT NOT NULL,
"title" TEXT NOT NULL,
"description" TEXT NOT NULL,
"imageUrl" TEXT,
"liveUrl" TEXT,
"githubUrl" TEXT,
"techStack" TEXT[],
"featured" BOOLEAN NOT NULL DEFAULT false,
"order" INTEGER NOT NULL DEFAULT 0,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Project_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Experience" (
"id" TEXT NOT NULL,
"company" TEXT NOT NULL,
"role" TEXT NOT NULL,
"startDate" TIMESTAMP(3) NOT NULL,
"endDate" TIMESTAMP(3),
"current" BOOLEAN NOT NULL DEFAULT false,
"description" TEXT NOT NULL,
"highlights" TEXT[],
"techStack" TEXT[],
"order" INTEGER NOT NULL DEFAULT 0,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Experience_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ContactMessage" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"email" TEXT NOT NULL,
"subject" TEXT NOT NULL,
"message" TEXT NOT NULL,
"read" BOOLEAN NOT NULL DEFAULT false,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "ContactMessage_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Blog" (
"id" TEXT NOT NULL,
"title" TEXT NOT NULL,
"slug" TEXT NOT NULL,
"content" TEXT NOT NULL,
"excerpt" TEXT,
"status" "BlogStatus" NOT NULL DEFAULT 'DRAFT',
"scheduledAt" TIMESTAMP(3),
"publishedAt" TIMESTAMP(3),
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Blog_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Tag" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Tag_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "_BlogToTag" (
"A" TEXT NOT NULL,
"B" TEXT NOT NULL,
CONSTRAINT "_BlogToTag_AB_pkey" PRIMARY KEY ("A","B")
);
-- CreateIndex
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
-- CreateIndex
CREATE UNIQUE INDEX "Blog_slug_key" ON "Blog"("slug");
-- CreateIndex
CREATE INDEX "Blog_status_idx" ON "Blog"("status");
-- CreateIndex
CREATE INDEX "Blog_scheduledAt_idx" ON "Blog"("scheduledAt");
-- CreateIndex
CREATE UNIQUE INDEX "Tag_name_key" ON "Tag"("name");
-- CreateIndex
CREATE INDEX "_BlogToTag_B_index" ON "_BlogToTag"("B");
-- AddForeignKey
ALTER TABLE "PageView" ADD CONSTRAINT "PageView_sessionId_fkey" FOREIGN KEY ("sessionId") REFERENCES "Session"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_BlogToTag" ADD CONSTRAINT "_BlogToTag_A_fkey" FOREIGN KEY ("A") REFERENCES "Blog"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "_BlogToTag" ADD CONSTRAINT "_BlogToTag_B_fkey" FOREIGN KEY ("B") REFERENCES "Tag"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View 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"

View File

@@ -1,11 +1,11 @@
import { prisma } from "@/lib/prisma"; import { prisma } from "@/lib/prisma";
export async function POST(req: Request) { export async function POST(req: Request) {
const { visitorId, path } = await req.json(); const { visitorId: sessionId, path } = await req.json();
await prisma.pageView.create({ await prisma.pageView.create({
data: { data: {
visitorId, sessionId,
path, path,
}, },
}); });