Refactor Docker entrypoint to run Prisma migrations; update schema
Co-authored-by: dennis <dennis@konkol.net>
This commit is contained in:
@@ -66,6 +66,7 @@ RUN adduser --system --uid 1001 nextjs
|
|||||||
|
|
||||||
# Copy the built application
|
# Copy the built application
|
||||||
COPY --from=builder /app/public ./public
|
COPY --from=builder /app/public ./public
|
||||||
|
COPY --from=builder /app/scripts ./scripts
|
||||||
|
|
||||||
# Set the correct permission for prerender cache
|
# Set the correct permission for prerender cache
|
||||||
RUN mkdir .next
|
RUN mkdir .next
|
||||||
@@ -82,6 +83,8 @@ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
|||||||
# Copy Prisma files
|
# Copy Prisma files
|
||||||
COPY --from=builder /app/prisma ./prisma
|
COPY --from=builder /app/prisma ./prisma
|
||||||
COPY --from=builder /app/node_modules/.prisma ./node_modules/.prisma
|
COPY --from=builder /app/node_modules/.prisma ./node_modules/.prisma
|
||||||
|
COPY --from=builder /app/node_modules/prisma ./node_modules/prisma
|
||||||
|
COPY --from=builder /app/node_modules/@prisma ./node_modules/@prisma
|
||||||
|
|
||||||
# Note: Environment variables should be passed via docker-compose or runtime environment
|
# Note: Environment variables should be passed via docker-compose or runtime environment
|
||||||
# DO NOT copy .env files into the image for security reasons
|
# DO NOT copy .env files into the image for security reasons
|
||||||
@@ -97,4 +100,4 @@ ENV HOSTNAME="0.0.0.0"
|
|||||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
||||||
CMD curl -f http://localhost:3000/api/health || exit 1
|
CMD curl -f http://localhost:3000/api/health || exit 1
|
||||||
|
|
||||||
CMD ["node", "server.js"]
|
CMD ["node", "scripts/start-with-migrate.js"]
|
||||||
246
prisma/migrations/20260112150721_init/migration.sql
Normal file
246
prisma/migrations/20260112150721_init/migration.sql
Normal file
@@ -0,0 +1,246 @@
|
|||||||
|
-- CreateEnum
|
||||||
|
CREATE TYPE "ContentStatus" AS ENUM ('DRAFT', 'PUBLISHED');
|
||||||
|
|
||||||
|
-- CreateEnum
|
||||||
|
CREATE TYPE "Difficulty" AS ENUM ('BEGINNER', 'INTERMEDIATE', 'ADVANCED', 'EXPERT');
|
||||||
|
|
||||||
|
-- CreateEnum
|
||||||
|
CREATE TYPE "InteractionType" AS ENUM ('LIKE', 'SHARE', 'BOOKMARK', 'COMMENT');
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Project" (
|
||||||
|
"id" SERIAL NOT NULL,
|
||||||
|
"slug" VARCHAR(255) NOT NULL,
|
||||||
|
"defaultLocale" VARCHAR(10) NOT NULL DEFAULT 'en',
|
||||||
|
"title" VARCHAR(255) NOT NULL,
|
||||||
|
"description" TEXT NOT NULL,
|
||||||
|
"content" TEXT NOT NULL,
|
||||||
|
"tags" TEXT[] DEFAULT ARRAY[]::TEXT[],
|
||||||
|
"featured" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"category" VARCHAR(100) NOT NULL,
|
||||||
|
"date" VARCHAR(10) NOT NULL,
|
||||||
|
"github" VARCHAR(500),
|
||||||
|
"live" VARCHAR(500),
|
||||||
|
"published" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
"imageUrl" VARCHAR(500),
|
||||||
|
"metaDescription" TEXT,
|
||||||
|
"keywords" TEXT,
|
||||||
|
"ogImage" VARCHAR(500),
|
||||||
|
"schema" JSONB,
|
||||||
|
"difficulty" "Difficulty" NOT NULL DEFAULT 'INTERMEDIATE',
|
||||||
|
"timeToComplete" VARCHAR(100),
|
||||||
|
"technologies" TEXT[] DEFAULT ARRAY[]::TEXT[],
|
||||||
|
"challenges" TEXT[] DEFAULT ARRAY[]::TEXT[],
|
||||||
|
"lessonsLearned" TEXT[] DEFAULT ARRAY[]::TEXT[],
|
||||||
|
"futureImprovements" TEXT[] DEFAULT ARRAY[]::TEXT[],
|
||||||
|
"demoVideo" VARCHAR(500),
|
||||||
|
"screenshots" TEXT[] DEFAULT ARRAY[]::TEXT[],
|
||||||
|
"colorScheme" VARCHAR(100) NOT NULL DEFAULT 'Dark',
|
||||||
|
"accessibility" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
"performance" JSONB NOT NULL DEFAULT '{"loadTime": "1.5s", "bundleSize": "50KB", "lighthouse": 90}',
|
||||||
|
"analytics" JSONB NOT NULL DEFAULT '{"likes": 0, "views": 0, "shares": 0}',
|
||||||
|
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "Project_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "project_translations" (
|
||||||
|
"id" SERIAL NOT NULL,
|
||||||
|
"project_id" INTEGER NOT NULL,
|
||||||
|
"locale" VARCHAR(10) NOT NULL,
|
||||||
|
"title" VARCHAR(255) NOT NULL,
|
||||||
|
"description" TEXT NOT NULL,
|
||||||
|
"content" JSONB,
|
||||||
|
"metaDescription" TEXT,
|
||||||
|
"keywords" TEXT,
|
||||||
|
"ogImage" VARCHAR(500),
|
||||||
|
"schema" JSONB,
|
||||||
|
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "project_translations_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "content_pages" (
|
||||||
|
"id" SERIAL NOT NULL,
|
||||||
|
"key" VARCHAR(100) NOT NULL,
|
||||||
|
"status" "ContentStatus" NOT NULL DEFAULT 'PUBLISHED',
|
||||||
|
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "content_pages_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "content_page_translations" (
|
||||||
|
"id" SERIAL NOT NULL,
|
||||||
|
"page_id" INTEGER NOT NULL,
|
||||||
|
"locale" VARCHAR(10) NOT NULL,
|
||||||
|
"title" TEXT,
|
||||||
|
"slug" VARCHAR(255),
|
||||||
|
"content" JSONB NOT NULL,
|
||||||
|
"metaDescription" TEXT,
|
||||||
|
"keywords" TEXT,
|
||||||
|
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||||
|
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
CONSTRAINT "content_page_translations_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "site_settings" (
|
||||||
|
"id" INTEGER NOT NULL DEFAULT 1,
|
||||||
|
"defaultLocale" VARCHAR(10) NOT NULL DEFAULT 'en',
|
||||||
|
"locales" TEXT[] DEFAULT ARRAY['en', 'de']::TEXT[],
|
||||||
|
"theme" JSONB,
|
||||||
|
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "site_settings_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "PageView" (
|
||||||
|
"id" SERIAL NOT NULL,
|
||||||
|
"project_id" INTEGER,
|
||||||
|
"page" VARCHAR(100) NOT NULL,
|
||||||
|
"ip" VARCHAR(45),
|
||||||
|
"user_agent" TEXT,
|
||||||
|
"referrer" VARCHAR(500),
|
||||||
|
"timestamp" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
CONSTRAINT "PageView_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "UserInteraction" (
|
||||||
|
"id" SERIAL NOT NULL,
|
||||||
|
"project_id" INTEGER NOT NULL,
|
||||||
|
"type" "InteractionType" NOT NULL,
|
||||||
|
"ip" VARCHAR(45),
|
||||||
|
"user_agent" TEXT,
|
||||||
|
"timestamp" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
CONSTRAINT "UserInteraction_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Contact" (
|
||||||
|
"id" SERIAL NOT NULL,
|
||||||
|
"name" VARCHAR(255) NOT NULL,
|
||||||
|
"email" VARCHAR(255) NOT NULL,
|
||||||
|
"subject" VARCHAR(500) NOT NULL,
|
||||||
|
"message" TEXT NOT NULL,
|
||||||
|
"responded" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"response_template" VARCHAR(50),
|
||||||
|
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "Contact_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "activity_status" (
|
||||||
|
"id" INTEGER NOT NULL DEFAULT 1,
|
||||||
|
"activity_type" VARCHAR(50),
|
||||||
|
"activity_details" VARCHAR(255),
|
||||||
|
"activity_project" VARCHAR(255),
|
||||||
|
"activity_language" VARCHAR(50),
|
||||||
|
"activity_repo" VARCHAR(500),
|
||||||
|
"music_playing" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"music_track" VARCHAR(255),
|
||||||
|
"music_artist" VARCHAR(255),
|
||||||
|
"music_album" VARCHAR(255),
|
||||||
|
"music_platform" VARCHAR(50),
|
||||||
|
"music_progress" INTEGER,
|
||||||
|
"music_album_art" VARCHAR(500),
|
||||||
|
"watching_title" VARCHAR(255),
|
||||||
|
"watching_platform" VARCHAR(50),
|
||||||
|
"watching_type" VARCHAR(50),
|
||||||
|
"gaming_game" VARCHAR(255),
|
||||||
|
"gaming_platform" VARCHAR(50),
|
||||||
|
"gaming_status" VARCHAR(50),
|
||||||
|
"status_mood" VARCHAR(50),
|
||||||
|
"status_message" VARCHAR(500),
|
||||||
|
"updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
CONSTRAINT "activity_status_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "Project_slug_key" ON "Project"("slug");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "Project_category_idx" ON "Project"("category");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "Project_featured_idx" ON "Project"("featured");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "Project_published_idx" ON "Project"("published");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "Project_difficulty_idx" ON "Project"("difficulty");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "Project_created_at_idx" ON "Project"("created_at");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "Project_tags_idx" ON "Project"("tags");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "project_translations_locale_idx" ON "project_translations"("locale");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "project_translations_project_id_idx" ON "project_translations"("project_id");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "project_translations_project_id_locale_key" ON "project_translations"("project_id", "locale");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "content_pages_key_key" ON "content_pages"("key");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "content_page_translations_locale_idx" ON "content_page_translations"("locale");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "content_page_translations_slug_idx" ON "content_page_translations"("slug");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "content_page_translations_page_id_locale_key" ON "content_page_translations"("page_id", "locale");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "PageView_project_id_idx" ON "PageView"("project_id");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "PageView_timestamp_idx" ON "PageView"("timestamp");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "PageView_page_idx" ON "PageView"("page");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "UserInteraction_project_id_idx" ON "UserInteraction"("project_id");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "UserInteraction_type_idx" ON "UserInteraction"("type");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "UserInteraction_timestamp_idx" ON "UserInteraction"("timestamp");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "Contact_email_idx" ON "Contact"("email");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "Contact_responded_idx" ON "Contact"("responded");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "Contact_created_at_idx" ON "Contact"("created_at");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "project_translations" ADD CONSTRAINT "project_translations_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "content_page_translations" ADD CONSTRAINT "content_page_translations_page_id_fkey" FOREIGN KEY ("page_id") REFERENCES "content_pages"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
@@ -1,127 +0,0 @@
|
|||||||
# Database Migrations
|
|
||||||
|
|
||||||
This directory contains SQL migration scripts for manual database updates.
|
|
||||||
|
|
||||||
## Running Migrations
|
|
||||||
|
|
||||||
### Method 1: Using psql (Recommended)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Connect to your database
|
|
||||||
psql -d portfolio -f prisma/migrations/create_activity_status.sql
|
|
||||||
|
|
||||||
# Or with connection string
|
|
||||||
psql "postgresql://user:password@localhost:5432/portfolio" -f prisma/migrations/create_activity_status.sql
|
|
||||||
```
|
|
||||||
|
|
||||||
### Method 2: Using Docker
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# If your database is in Docker
|
|
||||||
docker exec -i postgres_container psql -U username -d portfolio < prisma/migrations/create_activity_status.sql
|
|
||||||
```
|
|
||||||
|
|
||||||
### Method 3: Using pgAdmin or Database GUI
|
|
||||||
|
|
||||||
1. Open pgAdmin or your database GUI
|
|
||||||
2. Connect to your `portfolio` database
|
|
||||||
3. Open Query Tool
|
|
||||||
4. Copy and paste the contents of `create_activity_status.sql`
|
|
||||||
5. Execute the query
|
|
||||||
|
|
||||||
## Verifying Migration
|
|
||||||
|
|
||||||
After running the migration, verify it was successful:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Check if table exists
|
|
||||||
psql -d portfolio -c "\dt activity_status"
|
|
||||||
|
|
||||||
# View table structure
|
|
||||||
psql -d portfolio -c "\d activity_status"
|
|
||||||
|
|
||||||
# Check if default row was inserted
|
|
||||||
psql -d portfolio -c "SELECT * FROM activity_status;"
|
|
||||||
```
|
|
||||||
|
|
||||||
Expected output:
|
|
||||||
```
|
|
||||||
id | activity_type | ... | updated_at
|
|
||||||
----+---------------+-----+---------------------------
|
|
||||||
1 | | ... | 2024-01-15 10:30:00+00
|
|
||||||
```
|
|
||||||
|
|
||||||
## Migration: create_activity_status.sql
|
|
||||||
|
|
||||||
**Purpose**: Creates the `activity_status` table for n8n activity feed integration.
|
|
||||||
|
|
||||||
**What it does**:
|
|
||||||
- Creates `activity_status` table with all necessary columns
|
|
||||||
- Inserts a default row with `id = 1`
|
|
||||||
- Sets up automatic `updated_at` timestamp trigger
|
|
||||||
- Adds table comment for documentation
|
|
||||||
|
|
||||||
**Required by**:
|
|
||||||
- `/api/n8n/status` endpoint
|
|
||||||
- `ActivityFeed` component
|
|
||||||
- n8n workflows for status updates
|
|
||||||
|
|
||||||
**Safe to run multiple times**: Yes (uses `IF NOT EXISTS` and `ON CONFLICT`)
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### "relation already exists"
|
|
||||||
Table already exists - migration is already applied. Safe to ignore.
|
|
||||||
|
|
||||||
### "permission denied"
|
|
||||||
Your database user needs CREATE TABLE permissions:
|
|
||||||
```sql
|
|
||||||
GRANT CREATE ON DATABASE portfolio TO your_user;
|
|
||||||
```
|
|
||||||
|
|
||||||
### "database does not exist"
|
|
||||||
Create the database first:
|
|
||||||
```bash
|
|
||||||
createdb portfolio
|
|
||||||
# Or
|
|
||||||
psql -c "CREATE DATABASE portfolio;"
|
|
||||||
```
|
|
||||||
|
|
||||||
### "connection refused"
|
|
||||||
Ensure PostgreSQL is running:
|
|
||||||
```bash
|
|
||||||
# Check status
|
|
||||||
pg_isready
|
|
||||||
|
|
||||||
# Start PostgreSQL (macOS)
|
|
||||||
brew services start postgresql
|
|
||||||
|
|
||||||
# Start PostgreSQL (Linux)
|
|
||||||
sudo systemctl start postgresql
|
|
||||||
```
|
|
||||||
|
|
||||||
## Rolling Back
|
|
||||||
|
|
||||||
To remove the activity_status table:
|
|
||||||
|
|
||||||
```sql
|
|
||||||
DROP TRIGGER IF EXISTS activity_status_updated_at ON activity_status;
|
|
||||||
DROP FUNCTION IF EXISTS update_activity_status_updated_at();
|
|
||||||
DROP TABLE IF EXISTS activity_status;
|
|
||||||
```
|
|
||||||
|
|
||||||
Save this as `rollback_activity_status.sql` and run if needed.
|
|
||||||
|
|
||||||
## Future Migrations
|
|
||||||
|
|
||||||
When adding new migrations:
|
|
||||||
1. Create a new `.sql` file with descriptive name
|
|
||||||
2. Use timestamps in filename: `YYYYMMDD_description.sql`
|
|
||||||
3. Document what it does in this README
|
|
||||||
4. Test on local database first
|
|
||||||
5. Mark as safe/unsafe for production
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Last Updated**: 2024-01-15
|
|
||||||
**Status**: Required for n8n integration
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
-- Create activity_status table for n8n integration
|
|
||||||
CREATE TABLE IF NOT EXISTS activity_status (
|
|
||||||
id INTEGER PRIMARY KEY DEFAULT 1,
|
|
||||||
activity_type VARCHAR(50),
|
|
||||||
activity_details VARCHAR(255),
|
|
||||||
activity_project VARCHAR(255),
|
|
||||||
activity_language VARCHAR(50),
|
|
||||||
activity_repo VARCHAR(500),
|
|
||||||
music_playing BOOLEAN DEFAULT FALSE,
|
|
||||||
music_track VARCHAR(255),
|
|
||||||
music_artist VARCHAR(255),
|
|
||||||
music_album VARCHAR(255),
|
|
||||||
music_platform VARCHAR(50),
|
|
||||||
music_progress INTEGER,
|
|
||||||
music_album_art VARCHAR(500),
|
|
||||||
watching_title VARCHAR(255),
|
|
||||||
watching_platform VARCHAR(50),
|
|
||||||
watching_type VARCHAR(50),
|
|
||||||
gaming_game VARCHAR(255),
|
|
||||||
gaming_platform VARCHAR(50),
|
|
||||||
gaming_status VARCHAR(50),
|
|
||||||
status_mood VARCHAR(50),
|
|
||||||
status_message VARCHAR(500),
|
|
||||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Insert default row
|
|
||||||
INSERT INTO activity_status (id, updated_at)
|
|
||||||
VALUES (1, NOW())
|
|
||||||
ON CONFLICT (id) DO NOTHING;
|
|
||||||
|
|
||||||
-- Create function to automatically update updated_at
|
|
||||||
CREATE OR REPLACE FUNCTION update_activity_status_updated_at()
|
|
||||||
RETURNS TRIGGER AS $$
|
|
||||||
BEGIN
|
|
||||||
NEW.updated_at = NOW();
|
|
||||||
RETURN NEW;
|
|
||||||
END;
|
|
||||||
$$ LANGUAGE plpgsql;
|
|
||||||
|
|
||||||
-- Create trigger for automatic timestamp updates
|
|
||||||
DROP TRIGGER IF EXISTS activity_status_updated_at ON activity_status;
|
|
||||||
CREATE TRIGGER activity_status_updated_at
|
|
||||||
BEFORE UPDATE ON activity_status
|
|
||||||
FOR EACH ROW
|
|
||||||
EXECUTE FUNCTION update_activity_status_updated_at();
|
|
||||||
|
|
||||||
-- Add helpful comment
|
|
||||||
COMMENT ON TABLE activity_status IS 'Stores real-time activity status from n8n workflows (coding, music, gaming, etc.)';
|
|
||||||
1
prisma/migrations/migration_lock.toml
Normal file
1
prisma/migrations/migration_lock.toml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
provider = "postgresql"
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Quick Fix Script for Portfolio Database
|
|
||||||
# This script creates the activity_status table needed for n8n integration
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
echo "🔧 Portfolio Database Quick Fix"
|
|
||||||
echo "================================"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Colors for output
|
|
||||||
RED='\033[0;31m'
|
|
||||||
GREEN='\033[0;32m'
|
|
||||||
YELLOW='\033[1;33m'
|
|
||||||
NC='\033[0m' # No Color
|
|
||||||
|
|
||||||
# Check if .env.local exists
|
|
||||||
if [ ! -f .env.local ]; then
|
|
||||||
echo -e "${RED}❌ Error: .env.local not found${NC}"
|
|
||||||
echo "Please create .env.local with DATABASE_URL"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Load DATABASE_URL from .env.local
|
|
||||||
export $(grep -v '^#' .env.local | xargs)
|
|
||||||
|
|
||||||
if [ -z "$DATABASE_URL" ]; then
|
|
||||||
echo -e "${RED}❌ Error: DATABASE_URL not found in .env.local${NC}"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo -e "${GREEN}✓ Found DATABASE_URL${NC}"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Extract database name from DATABASE_URL
|
|
||||||
DB_NAME=$(echo $DATABASE_URL | sed -n 's/.*\/\([^?]*\).*/\1/p')
|
|
||||||
echo "📦 Database: $DB_NAME"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Run the migration
|
|
||||||
echo "🚀 Creating activity_status table..."
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
psql "$DATABASE_URL" -f prisma/migrations/create_activity_status.sql
|
|
||||||
|
|
||||||
if [ $? -eq 0 ]; then
|
|
||||||
echo ""
|
|
||||||
echo -e "${GREEN}✅ SUCCESS! Migration completed${NC}"
|
|
||||||
echo ""
|
|
||||||
echo "Verifying table..."
|
|
||||||
psql "$DATABASE_URL" -c "\d activity_status" | head -20
|
|
||||||
echo ""
|
|
||||||
echo "Checking default row..."
|
|
||||||
psql "$DATABASE_URL" -c "SELECT id, updated_at FROM activity_status LIMIT 1;"
|
|
||||||
echo ""
|
|
||||||
echo -e "${GREEN}🎉 All done! Your database is ready.${NC}"
|
|
||||||
echo ""
|
|
||||||
echo "Next steps:"
|
|
||||||
echo " 1. Restart your Next.js dev server: npm run dev"
|
|
||||||
echo " 2. Visit http://localhost:3000"
|
|
||||||
echo " 3. The activity feed should now work without errors"
|
|
||||||
else
|
|
||||||
echo ""
|
|
||||||
echo -e "${RED}❌ Migration failed${NC}"
|
|
||||||
echo ""
|
|
||||||
echo "Troubleshooting:"
|
|
||||||
echo " 1. Ensure PostgreSQL is running: pg_isready"
|
|
||||||
echo " 2. Check your DATABASE_URL in .env.local"
|
|
||||||
echo " 3. Verify database exists: psql -l | grep $DB_NAME"
|
|
||||||
echo " 4. Try manual migration: psql $DB_NAME -f prisma/migrations/create_activity_status.sql"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
40
scripts/start-with-migrate.js
Normal file
40
scripts/start-with-migrate.js
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
/**
|
||||||
|
* Container entrypoint: apply Prisma migrations, then start Next server.
|
||||||
|
*
|
||||||
|
* Why:
|
||||||
|
* - In real deployments you want schema changes applied automatically per deploy.
|
||||||
|
* - `prisma migrate deploy` is safe to run multiple times (idempotent).
|
||||||
|
*
|
||||||
|
* Controls:
|
||||||
|
* - Set `SKIP_PRISMA_MIGRATE=true` to skip migrations (emergency / debugging).
|
||||||
|
*/
|
||||||
|
const { spawnSync } = require("node:child_process");
|
||||||
|
|
||||||
|
function run(cmd, args, opts = {}) {
|
||||||
|
const res = spawnSync(cmd, args, {
|
||||||
|
stdio: "inherit",
|
||||||
|
env: process.env,
|
||||||
|
...opts,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
throw res.error;
|
||||||
|
}
|
||||||
|
if (typeof res.status === "number" && res.status !== 0) {
|
||||||
|
// propagate exit code
|
||||||
|
process.exit(res.status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const skip = String(process.env.SKIP_PRISMA_MIGRATE || "").toLowerCase() === "true";
|
||||||
|
if (!skip) {
|
||||||
|
// Avoid relying on `npx` resolution in minimal runtimes.
|
||||||
|
// We copy `node_modules/prisma` into the runtime image.
|
||||||
|
run("node", ["node_modules/prisma/build/index.js", "migrate", "deploy"]);
|
||||||
|
} else {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log("SKIP_PRISMA_MIGRATE=true -> skipping prisma migrate deploy");
|
||||||
|
}
|
||||||
|
|
||||||
|
run("node", ["server.js"]);
|
||||||
|
|
||||||
Reference in New Issue
Block a user