diff --git a/.gitea/workflows/ci-cd-dev-staging.yml b/.gitea/workflows/ci-cd-dev-staging.yml
deleted file mode 100644
index 1a0004e..0000000
--- a/.gitea/workflows/ci-cd-dev-staging.yml
+++ /dev/null
@@ -1,209 +0,0 @@
-name: CI/CD Pipeline (Dev/Staging)
-
-on:
- push:
- branches: [dev, main]
-
-env:
- NODE_VERSION: '20'
- DOCKER_IMAGE: portfolio-app
- CONTAINER_NAME: portfolio-app-staging
-
-jobs:
- staging:
- runs-on: ubuntu-latest
- steps:
- - name: Checkout code
- uses: actions/checkout@v3
-
- - name: Setup Node.js
- uses: actions/setup-node@v4
- with:
- node-version: ${{ env.NODE_VERSION }}
- cache: 'npm'
-
- - name: Install dependencies
- run: npm ci
-
- - name: Run linting
- run: npm run lint
-
- - name: Run tests
- run: npm run test:production
-
- - name: Build application
- run: npm run build
-
- - name: Run security scan
- run: |
- echo "π Running npm audit..."
- npm audit --audit-level=high || echo "β οΈ Some vulnerabilities found, but continuing..."
-
- - name: Verify Gitea Variables and Secrets
- run: |
- echo "π Verifying Gitea Variables and Secrets..."
-
- # Check Variables
- if [ -z "${{ vars.NEXT_PUBLIC_BASE_URL }}" ]; then
- echo "β NEXT_PUBLIC_BASE_URL variable is missing!"
- echo "Please set this variable in Gitea repository settings"
- exit 1
- fi
- if [ -z "${{ vars.MY_EMAIL }}" ]; then
- echo "β MY_EMAIL variable is missing!"
- echo "Please set this variable in Gitea repository settings"
- exit 1
- fi
- if [ -z "${{ vars.MY_INFO_EMAIL }}" ]; then
- echo "β MY_INFO_EMAIL variable is missing!"
- echo "Please set this variable in Gitea repository settings"
- exit 1
- fi
-
- # Check Secrets
- if [ -z "${{ secrets.MY_PASSWORD }}" ]; then
- echo "β MY_PASSWORD secret is missing!"
- echo "Please set this secret in Gitea repository settings"
- exit 1
- fi
- if [ -z "${{ secrets.MY_INFO_PASSWORD }}" ]; then
- echo "β MY_INFO_PASSWORD secret is missing!"
- echo "Please set this secret in Gitea repository settings"
- exit 1
- fi
- if [ -z "${{ secrets.ADMIN_BASIC_AUTH }}" ]; then
- echo "β ADMIN_BASIC_AUTH secret is missing!"
- echo "Please set this secret in Gitea repository settings"
- exit 1
- fi
-
- echo "β
All required Gitea variables and secrets are present"
- echo "π Variables found:"
- echo " - NEXT_PUBLIC_BASE_URL: ${{ vars.NEXT_PUBLIC_BASE_URL }}"
- echo " - MY_EMAIL: ${{ vars.MY_EMAIL }}"
- echo " - MY_INFO_EMAIL: ${{ vars.MY_INFO_EMAIL }}"
- echo " - NODE_ENV: staging"
- echo " - LOG_LEVEL: ${{ vars.LOG_LEVEL }}"
-
- - name: Build Docker image
- run: |
- echo "ποΈ Building Docker image..."
- docker build -t ${{ env.DOCKER_IMAGE }}:staging .
- docker tag ${{ env.DOCKER_IMAGE }}:staging ${{ env.DOCKER_IMAGE }}:staging-$(date +%Y%m%d-%H%M%S)
- echo "β
Docker image built successfully"
-
- - name: Deploy Staging using Gitea Variables and Secrets
- run: |
- echo "π Deploying Staging using Gitea Variables and Secrets..."
-
- echo "π Using Gitea Variables and Secrets:"
- echo " - NODE_ENV: staging"
- echo " - LOG_LEVEL: ${LOG_LEVEL}"
- echo " - NEXT_PUBLIC_BASE_URL: ${NEXT_PUBLIC_BASE_URL}"
- echo " - MY_EMAIL: ${MY_EMAIL}"
- echo " - MY_INFO_EMAIL: ${MY_INFO_EMAIL}"
- echo " - MY_PASSWORD: [SET FROM GITEA SECRET]"
- echo " - MY_INFO_PASSWORD: [SET FROM GITEA SECRET]"
- echo " - ADMIN_BASIC_AUTH: [SET FROM GITEA SECRET]"
- echo " - N8N_WEBHOOK_URL: ${N8N_WEBHOOK_URL}"
-
- # Stop old staging containers
- echo "π Stopping old staging containers..."
- docker compose -f docker-compose.staging.yml down || true
-
- # Clean up orphaned containers
- echo "π§Ή Cleaning up orphaned containers..."
- docker compose -f docker-compose.staging.yml down --remove-orphans || true
-
- # Start new staging containers
- echo "π Starting new staging containers..."
- docker compose -f docker-compose.staging.yml up -d
-
- # Wait a moment for containers to start
- echo "β³ Waiting for containers to start..."
- sleep 10
-
- # Check container logs for debugging
- echo "π Container logs (first 20 lines):"
- docker compose -f docker-compose.staging.yml logs --tail=20
-
- echo "β
Staging deployment completed!"
- env:
- NODE_ENV: staging
- LOG_LEVEL: ${{ vars.LOG_LEVEL }}
- NEXT_PUBLIC_BASE_URL: ${{ vars.NEXT_PUBLIC_BASE_URL }}
- NEXT_PUBLIC_UMAMI_URL: ${{ vars.NEXT_PUBLIC_UMAMI_URL }}
- NEXT_PUBLIC_UMAMI_WEBSITE_ID: ${{ vars.NEXT_PUBLIC_UMAMI_WEBSITE_ID }}
- MY_EMAIL: ${{ vars.MY_EMAIL }}
- MY_INFO_EMAIL: ${{ vars.MY_INFO_EMAIL }}
- MY_PASSWORD: ${{ secrets.MY_PASSWORD }}
- MY_INFO_PASSWORD: ${{ secrets.MY_INFO_PASSWORD }}
- ADMIN_BASIC_AUTH: ${{ secrets.ADMIN_BASIC_AUTH }}
- N8N_WEBHOOK_URL: ${{ vars.N8N_WEBHOOK_URL }}
- N8N_API_KEY: ${{ secrets.N8N_API_KEY }}
-
- - name: Wait for containers to be ready
- run: |
- echo "β³ Waiting for containers to be ready..."
- sleep 45
-
- # Check if all containers are running
- echo "π Checking container status..."
- docker compose -f docker-compose.staging.yml ps
-
- # Wait for application container to be healthy
- echo "π₯ Waiting for application container to be healthy..."
- for i in {1..60}; do
- if docker exec portfolio-app-staging curl -f http://localhost:3000/api/health > /dev/null 2>&1; then
- echo "β
Application container is healthy!"
- break
- fi
- echo "β³ Waiting for application container... ($i/60)"
- sleep 5
- done
-
- # Additional wait for main page to be accessible
- echo "π Waiting for main page to be accessible..."
- for i in {1..30}; do
- if curl -f http://localhost:3001/ > /dev/null 2>&1; then
- echo "β
Main page is accessible!"
- break
- fi
- echo "β³ Waiting for main page... ($i/30)"
- sleep 3
- done
-
- - name: Health check
- run: |
- echo "π Running comprehensive health checks..."
-
- # Check container status
- echo "π Container status:"
- docker compose -f docker-compose.staging.yml ps
-
- # Check application container
- echo "π₯ Checking application container..."
- if docker exec portfolio-app-staging curl -f http://localhost:3000/api/health; then
- echo "β
Application health check passed!"
- else
- echo "β Application health check failed!"
- docker logs portfolio-app-staging --tail=50
- exit 1
- fi
-
- # Check main page
- if curl -f http://localhost:3001/ > /dev/null; then
- echo "β
Main page is accessible!"
- else
- echo "β Main page is not accessible!"
- exit 1
- fi
-
- echo "β
All health checks passed! Staging deployment successful!"
-
- - name: Cleanup old images
- run: |
- echo "π§Ή Cleaning up old images..."
- docker image prune -f
- docker system prune -f
- echo "β
Cleanup completed"
diff --git a/.gitea/workflows/ci-cd-with-gitea-vars.yml b/.gitea/workflows/ci-cd-with-gitea-vars.yml.disabled
similarity index 100%
rename from .gitea/workflows/ci-cd-with-gitea-vars.yml
rename to .gitea/workflows/ci-cd-with-gitea-vars.yml.disabled
diff --git a/.gitea/workflows/ci-cd-woodpecker.yml b/.gitea/workflows/ci-cd-woodpecker.yml
deleted file mode 100644
index f4cd42a..0000000
--- a/.gitea/workflows/ci-cd-woodpecker.yml
+++ /dev/null
@@ -1,232 +0,0 @@
-name: CI/CD Pipeline (Woodpecker)
-
-when:
- event: push
- branch: production
-
-steps:
- build:
- image: node:20-alpine
- commands:
- - echo "π Starting CI/CD Pipeline"
- - echo "π Step 1: Installing dependencies..."
- - npm ci --prefer-offline --no-audit
- - echo "π Step 2: Running linting..."
- - npm run lint
- - echo "π§ͺ Step 3: Running tests..."
- - npm run test
- - echo "ποΈ Step 4: Building application..."
- - npm run build
- - echo "π Step 5: Running security scan..."
- - npm audit --audit-level=high || echo "β οΈ Some vulnerabilities found, but continuing..."
- volumes:
- - node_modules:/app/node_modules
-
- docker-build:
- image: docker:latest
- commands:
- - echo "π³ Building Docker image..."
- - docker build -t portfolio-app:latest .
- - docker tag portfolio-app:latest portfolio-app:$(date +%Y%m%d-%H%M%S)
- volumes:
- - /var/run/docker.sock:/var/run/docker.sock
-
- deploy:
- image: docker:latest
- commands:
- - echo "π Deploying application..."
-
- # Verify secrets and variables
- - echo "π Verifying secrets and variables..."
- - |
- if [ -z "$NEXT_PUBLIC_BASE_URL" ]; then
- echo "β NEXT_PUBLIC_BASE_URL variable is missing!"
- exit 1
- fi
- if [ -z "$MY_EMAIL" ]; then
- echo "β MY_EMAIL variable is missing!"
- exit 1
- fi
- if [ -z "$MY_INFO_EMAIL" ]; then
- echo "β MY_INFO_EMAIL variable is missing!"
- exit 1
- fi
- if [ -z "$MY_PASSWORD" ]; then
- echo "β MY_PASSWORD secret is missing!"
- exit 1
- fi
- if [ -z "$MY_INFO_PASSWORD" ]; then
- echo "β MY_INFO_PASSWORD secret is missing!"
- exit 1
- fi
- if [ -z "$ADMIN_BASIC_AUTH" ]; then
- echo "β ADMIN_BASIC_AUTH secret is missing!"
- exit 1
- fi
- echo "β
All required secrets and variables are present"
-
- # Check if current container is running
- - |
- if docker ps -q -f name=portfolio-app | grep -q .; then
- echo "π Current container is running, proceeding with zero-downtime update"
- CURRENT_CONTAINER_RUNNING=true
- else
- echo "π No current container running, doing fresh deployment"
- CURRENT_CONTAINER_RUNNING=false
- fi
-
- # Ensure database and redis are running
- - echo "π§ Ensuring database and redis are running..."
- - docker compose up -d postgres redis
- - sleep 10
-
- # Deploy with zero downtime
- - |
- if [ "$CURRENT_CONTAINER_RUNNING" = "true" ]; then
- echo "π Performing rolling update..."
-
- # Generate unique container name
- TIMESTAMP=$(date +%s)
- TEMP_CONTAINER_NAME="portfolio-app-temp-$TIMESTAMP"
- echo "π§ Using temporary container name: $TEMP_CONTAINER_NAME"
-
- # Clean up any existing temporary containers
- echo "π§Ή Cleaning up any existing temporary containers..."
- docker rm -f portfolio-app-new portfolio-app-temp-* portfolio-app-backup || true
-
- # Find and remove any containers with portfolio-app in the name (except the main one)
- EXISTING_CONTAINERS=$(docker ps -a --format "table {{.Names}}" | grep "portfolio-app" | grep -v "^portfolio-app$" || true)
- if [ -n "$EXISTING_CONTAINERS" ]; then
- echo "ποΈ Removing existing portfolio-app containers:"
- echo "$EXISTING_CONTAINERS"
- echo "$EXISTING_CONTAINERS" | xargs -r docker rm -f || true
- fi
-
- # Also clean up any stopped containers
- docker container prune -f || true
-
- # Start new container with unique temporary name
- docker run -d \
- --name $TEMP_CONTAINER_NAME \
- --restart unless-stopped \
- --network portfolio_net \
- -e NODE_ENV=$NODE_ENV \
- -e LOG_LEVEL=$LOG_LEVEL \
- -e DATABASE_URL=postgresql://portfolio_user:portfolio_pass@postgres:5432/portfolio_db?schema=public \
- -e REDIS_URL=redis://redis:6379 \
- -e NEXT_PUBLIC_BASE_URL="$NEXT_PUBLIC_BASE_URL" \
- -e NEXT_PUBLIC_UMAMI_URL="$NEXT_PUBLIC_UMAMI_URL" \
- -e NEXT_PUBLIC_UMAMI_WEBSITE_ID="$NEXT_PUBLIC_UMAMI_WEBSITE_ID" \
- -e MY_EMAIL="$MY_EMAIL" \
- -e MY_INFO_EMAIL="$MY_INFO_EMAIL" \
- -e MY_PASSWORD="$MY_PASSWORD" \
- -e MY_INFO_PASSWORD="$MY_INFO_PASSWORD" \
- -e ADMIN_BASIC_AUTH="$ADMIN_BASIC_AUTH" \
- portfolio-app:latest
-
- # Wait for new container to be ready
- echo "β³ Waiting for new container to be ready..."
- sleep 15
-
- # Health check new container
- for i in {1..20}; do
- if docker exec $TEMP_CONTAINER_NAME curl -f http://localhost:3000/api/health > /dev/null 2>&1; then
- echo "β
New container is healthy!"
- break
- fi
- echo "β³ Health check attempt $i/20..."
- sleep 3
- done
-
- # Stop old container
- echo "π Stopping old container..."
- docker stop portfolio-app || true
- docker rm portfolio-app || true
-
- # Rename new container
- docker rename $TEMP_CONTAINER_NAME portfolio-app
-
- # Update port mapping
- docker stop portfolio-app
- docker rm portfolio-app
-
- # Start with correct port
- docker run -d \
- --name portfolio-app \
- --restart unless-stopped \
- --network portfolio_net \
- -p 3000:3000 \
- -e NODE_ENV=$NODE_ENV \
- -e LOG_LEVEL=$LOG_LEVEL \
- -e DATABASE_URL=postgresql://portfolio_user:portfolio_pass@postgres:5432/portfolio_db?schema=public \
- -e REDIS_URL=redis://redis:6379 \
- -e NEXT_PUBLIC_BASE_URL="$NEXT_PUBLIC_BASE_URL" \
- -e NEXT_PUBLIC_UMAMI_URL="$NEXT_PUBLIC_UMAMI_URL" \
- -e NEXT_PUBLIC_UMAMI_WEBSITE_ID="$NEXT_PUBLIC_UMAMI_WEBSITE_ID" \
- -e MY_EMAIL="$MY_EMAIL" \
- -e MY_INFO_EMAIL="$MY_INFO_EMAIL" \
- -e MY_PASSWORD="$MY_PASSWORD" \
- -e MY_INFO_PASSWORD="$MY_INFO_PASSWORD" \
- -e ADMIN_BASIC_AUTH="$ADMIN_BASIC_AUTH" \
- portfolio-app:latest
-
- echo "β
Rolling update completed!"
- else
- echo "π Fresh deployment..."
- docker compose up -d
- fi
-
- # Wait for container to be ready
- - echo "β³ Waiting for container to be ready..."
- - sleep 15
-
- # Health check
- - |
- echo "π₯ Performing health check..."
- for i in {1..40}; do
- if curl -f http://localhost:3000/api/health > /dev/null 2>&1; then
- echo "β
Application is healthy!"
- break
- fi
- echo "β³ Health check attempt $i/40..."
- sleep 3
- done
-
- # Final verification
- - echo "π Final health verification..."
- - docker ps --filter "name=portfolio-app" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
- - |
- if curl -f http://localhost:3000/api/health; then
- echo "β
Health endpoint accessible"
- else
- echo "β Health endpoint not accessible"
- exit 1
- fi
- - |
- if curl -f http://localhost:3000/ > /dev/null; then
- echo "β
Main page is accessible"
- else
- echo "β Main page is not accessible"
- exit 1
- fi
- - echo "β
Deployment successful!"
-
- # Cleanup
- - docker image prune -f
- - docker system prune -f
- volumes:
- - /var/run/docker.sock:/var/run/docker.sock
- environment:
- - NODE_ENV
- - LOG_LEVEL
- - NEXT_PUBLIC_BASE_URL
- - NEXT_PUBLIC_UMAMI_URL
- - NEXT_PUBLIC_UMAMI_WEBSITE_ID
- - MY_EMAIL
- - MY_INFO_EMAIL
- - MY_PASSWORD
- - MY_INFO_PASSWORD
- - ADMIN_BASIC_AUTH
-
-volumes:
- node_modules:
diff --git a/.gitea/workflows/debug-secrets.yml b/.gitea/workflows/debug-secrets.yml
deleted file mode 100644
index 7825c7a..0000000
--- a/.gitea/workflows/debug-secrets.yml
+++ /dev/null
@@ -1,123 +0,0 @@
-name: Debug Secrets
-
-on:
- workflow_dispatch:
- push:
- branches: [ main ]
-
-jobs:
- debug-secrets:
- runs-on: ubuntu-latest
- steps:
- - name: Checkout code
- uses: actions/checkout@v3
-
- - name: Debug Environment Variables
- run: |
- echo "π Checking if secrets are available..."
- echo ""
-
- echo "π VARIABLES:"
- echo "β
NODE_ENV: ${{ vars.NODE_ENV }}"
- echo "β
LOG_LEVEL: ${{ vars.LOG_LEVEL }}"
- echo "β
NEXT_PUBLIC_BASE_URL: ${{ vars.NEXT_PUBLIC_BASE_URL }}"
- echo "β
NEXT_PUBLIC_UMAMI_URL: ${{ vars.NEXT_PUBLIC_UMAMI_URL }}"
- echo "β
NEXT_PUBLIC_UMAMI_WEBSITE_ID: ${{ vars.NEXT_PUBLIC_UMAMI_WEBSITE_ID }}"
- echo "β
MY_EMAIL: ${{ vars.MY_EMAIL }}"
- echo "β
MY_INFO_EMAIL: ${{ vars.MY_INFO_EMAIL }}"
-
- echo ""
- echo "π SECRETS:"
- if [ -n "${{ secrets.MY_PASSWORD }}" ]; then
- echo "β
MY_PASSWORD: Set (length: ${#MY_PASSWORD})"
- else
- echo "β MY_PASSWORD: Not set"
- fi
-
- if [ -n "${{ secrets.MY_INFO_PASSWORD }}" ]; then
- echo "β
MY_INFO_PASSWORD: Set (length: ${#MY_INFO_PASSWORD})"
- else
- echo "β MY_INFO_PASSWORD: Not set"
- fi
-
- if [ -n "${{ secrets.ADMIN_BASIC_AUTH }}" ]; then
- echo "β
ADMIN_BASIC_AUTH: Set (length: ${#ADMIN_BASIC_AUTH})"
- else
- echo "β ADMIN_BASIC_AUTH: Not set"
- fi
-
- echo ""
- echo "π Summary:"
- echo "Variables: 7 configured"
- echo "Secrets: 3 configured"
- echo "Total environment variables: 10"
- env:
- NODE_ENV: ${{ vars.NODE_ENV }}
- LOG_LEVEL: ${{ vars.LOG_LEVEL }}
- NEXT_PUBLIC_BASE_URL: ${{ vars.NEXT_PUBLIC_BASE_URL }}
- NEXT_PUBLIC_UMAMI_URL: ${{ vars.NEXT_PUBLIC_UMAMI_URL }}
- NEXT_PUBLIC_UMAMI_WEBSITE_ID: ${{ vars.NEXT_PUBLIC_UMAMI_WEBSITE_ID }}
- MY_EMAIL: ${{ vars.MY_EMAIL }}
- MY_INFO_EMAIL: ${{ vars.MY_INFO_EMAIL }}
- MY_PASSWORD: ${{ secrets.MY_PASSWORD }}
- MY_INFO_PASSWORD: ${{ secrets.MY_INFO_PASSWORD }}
- ADMIN_BASIC_AUTH: ${{ secrets.ADMIN_BASIC_AUTH }}
-
- - name: Test Docker Environment
- run: |
- echo "π³ Testing Docker environment with secrets..."
-
- # Create a test container to verify environment variables
- docker run --rm \
- -e NODE_ENV=production \
- -e DATABASE_URL=postgresql://portfolio_user:portfolio_pass@postgres:5432/portfolio_db?schema=public \
- -e REDIS_URL=redis://redis:6379 \
- -e NEXT_PUBLIC_BASE_URL="${{ secrets.NEXT_PUBLIC_BASE_URL }}" \
- -e MY_EMAIL="${{ secrets.MY_EMAIL }}" \
- -e MY_INFO_EMAIL="${{ secrets.MY_INFO_EMAIL }}" \
- -e MY_PASSWORD="${{ secrets.MY_PASSWORD }}" \
- -e MY_INFO_PASSWORD="${{ secrets.MY_INFO_PASSWORD }}" \
- -e ADMIN_BASIC_AUTH="${{ secrets.ADMIN_BASIC_AUTH }}" \
- alpine:latest sh -c '
- echo "Environment variables in container:"
- echo "NODE_ENV: $NODE_ENV"
- echo "DATABASE_URL: $DATABASE_URL"
- echo "REDIS_URL: $REDIS_URL"
- echo "NEXT_PUBLIC_BASE_URL: $NEXT_PUBLIC_BASE_URL"
- echo "MY_EMAIL: $MY_EMAIL"
- echo "MY_INFO_EMAIL: $MY_INFO_EMAIL"
- echo "MY_PASSWORD: [HIDDEN - length: ${#MY_PASSWORD}]"
- echo "MY_INFO_PASSWORD: [HIDDEN - length: ${#MY_INFO_PASSWORD}]"
- echo "ADMIN_BASIC_AUTH: [HIDDEN - length: ${#ADMIN_BASIC_AUTH}]"
- '
-
- - name: Validate Secret Formats
- run: |
- echo "π Validating secret formats..."
-
- # Check NEXT_PUBLIC_BASE_URL format
- if [[ "${{ secrets.NEXT_PUBLIC_BASE_URL }}" =~ ^https?:// ]]; then
- echo "β
NEXT_PUBLIC_BASE_URL: Valid URL format"
- else
- echo "β NEXT_PUBLIC_BASE_URL: Invalid URL format (should start with http:// or https://)"
- fi
-
- # Check email formats
- if [[ "${{ secrets.MY_EMAIL }}" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then
- echo "β
MY_EMAIL: Valid email format"
- else
- echo "β MY_EMAIL: Invalid email format"
- fi
-
- if [[ "${{ secrets.MY_INFO_EMAIL }}" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then
- echo "β
MY_INFO_EMAIL: Valid email format"
- else
- echo "β MY_INFO_EMAIL: Invalid email format"
- fi
-
- # Check ADMIN_BASIC_AUTH format (should be username:password)
- if [[ "${{ secrets.ADMIN_BASIC_AUTH }}" =~ ^[^:]+:.+$ ]]; then
- echo "β
ADMIN_BASIC_AUTH: Valid format (username:password)"
- else
- echo "β ADMIN_BASIC_AUTH: Invalid format (should be username:password)"
- fi
\ No newline at end of file
diff --git a/.gitea/workflows/dev-deploy.yml b/.gitea/workflows/dev-deploy.yml
new file mode 100644
index 0000000..39563a4
--- /dev/null
+++ b/.gitea/workflows/dev-deploy.yml
@@ -0,0 +1,126 @@
+name: Dev Deployment (Zero Downtime)
+
+on:
+ push:
+ branches: [ dev ]
+
+env:
+ NODE_VERSION: '20'
+ DOCKER_IMAGE: portfolio-app
+ IMAGE_TAG: staging
+
+jobs:
+ deploy-dev:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v3
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: ${{ env.NODE_VERSION }}
+ cache: 'npm'
+
+ - name: Install dependencies
+ run: npm ci
+
+ - name: Run linting
+ run: npm run lint
+
+ - name: Run tests
+ run: npm run test
+
+ - name: Build application
+ run: npm run build
+
+ - name: Build Docker image
+ run: |
+ echo "ποΈ Building dev Docker image..."
+ docker build -t ${{ env.DOCKER_IMAGE }}:${{ env.IMAGE_TAG }} .
+ echo "β
Docker image built successfully"
+
+ - name: Zero-Downtime Dev Deployment
+ run: |
+ echo "π Starting zero-downtime dev deployment..."
+
+ COMPOSE_FILE="docker-compose.staging.yml"
+ CONTAINER_NAME="portfolio-app-staging"
+ HEALTH_PORT="3002"
+
+ # Backup current container ID if running
+ OLD_CONTAINER=$(docker ps -q -f name=$CONTAINER_NAME || echo "")
+
+ # Start new container with updated image
+ echo "π Starting new dev container..."
+ docker compose -f $COMPOSE_FILE up -d --no-deps --build portfolio-staging
+
+ # Wait for new container to be healthy
+ echo "β³ Waiting for new container to be healthy..."
+ for i in {1..60}; do
+ NEW_CONTAINER=$(docker ps -q -f name=$CONTAINER_NAME)
+ if [ ! -z "$NEW_CONTAINER" ]; then
+ # Check health status
+ HEALTH=$(docker inspect $NEW_CONTAINER --format='{{.State.Health.Status}}' 2>/dev/null || echo "starting")
+ if [ "$HEALTH" == "healthy" ]; then
+ echo "β
New container is healthy!"
+ break
+ fi
+ # Also check HTTP health endpoint
+ if curl -f http://localhost:$HEALTH_PORT/api/health > /dev/null 2>&1; then
+ echo "β
New container is responding!"
+ break
+ fi
+ fi
+ echo "β³ Waiting... ($i/60)"
+ sleep 2
+ done
+
+ # Verify new container is working
+ if ! curl -f http://localhost:$HEALTH_PORT/api/health > /dev/null 2>&1; then
+ echo "β οΈ New dev container health check failed, but continuing (non-blocking)..."
+ docker compose -f $COMPOSE_FILE logs --tail=50 portfolio-staging
+ fi
+
+ # Remove old container if it exists and is different
+ if [ ! -z "$OLD_CONTAINER" ]; then
+ NEW_CONTAINER=$(docker ps -q -f name=$CONTAINER_NAME)
+ if [ "$OLD_CONTAINER" != "$NEW_CONTAINER" ]; then
+ echo "π§Ή Removing old container..."
+ docker stop $OLD_CONTAINER 2>/dev/null || true
+ docker rm $OLD_CONTAINER 2>/dev/null || true
+ fi
+ fi
+
+ echo "β
Dev deployment completed!"
+ env:
+ NODE_ENV: staging
+ LOG_LEVEL: ${{ vars.LOG_LEVEL || 'debug' }}
+ NEXT_PUBLIC_BASE_URL: ${{ vars.NEXT_PUBLIC_BASE_URL || 'https://dev.dk0.dev' }}
+ MY_EMAIL: ${{ vars.MY_EMAIL }}
+ MY_INFO_EMAIL: ${{ vars.MY_INFO_EMAIL }}
+ MY_PASSWORD: ${{ secrets.MY_PASSWORD }}
+ MY_INFO_PASSWORD: ${{ secrets.MY_INFO_PASSWORD }}
+ ADMIN_BASIC_AUTH: ${{ secrets.ADMIN_BASIC_AUTH }}
+ N8N_WEBHOOK_URL: ${{ vars.N8N_WEBHOOK_URL || '' }}
+ N8N_SECRET_TOKEN: ${{ secrets.N8N_SECRET_TOKEN || '' }}
+
+ - name: Dev Health Check
+ run: |
+ echo "π Running dev health checks..."
+ for i in {1..20}; do
+ if curl -f http://localhost:3002/api/health && curl -f http://localhost:3002/ > /dev/null; then
+ echo "β
Dev is fully operational!"
+ exit 0
+ fi
+ echo "β³ Waiting for dev... ($i/20)"
+ sleep 3
+ done
+ echo "β οΈ Dev health check failed, but continuing (non-blocking)..."
+ docker compose -f docker-compose.staging.yml logs --tail=50
+
+ - name: Cleanup
+ run: |
+ echo "π§Ή Cleaning up old images..."
+ docker image prune -f
+ echo "β
Cleanup completed"
diff --git a/.gitea/workflows/production-deploy.yml b/.gitea/workflows/production-deploy.yml
new file mode 100644
index 0000000..9cdbc9d
--- /dev/null
+++ b/.gitea/workflows/production-deploy.yml
@@ -0,0 +1,129 @@
+name: Production Deployment (Zero Downtime)
+
+on:
+ push:
+ branches: [ production ]
+
+env:
+ NODE_VERSION: '20'
+ DOCKER_IMAGE: portfolio-app
+ IMAGE_TAG: production
+
+jobs:
+ deploy-production:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v3
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: ${{ env.NODE_VERSION }}
+ cache: 'npm'
+
+ - name: Install dependencies
+ run: npm ci
+
+ - name: Run linting
+ run: npm run lint
+
+ - name: Run tests
+ run: npm run test:production
+
+ - name: Build application
+ run: npm run build
+
+ - name: Build Docker image
+ run: |
+ echo "ποΈ Building production Docker image..."
+ docker build -t ${{ env.DOCKER_IMAGE }}:${{ env.IMAGE_TAG }} .
+ docker tag ${{ env.DOCKER_IMAGE }}:${{ env.IMAGE_TAG }} ${{ env.DOCKER_IMAGE }}:latest
+ echo "β
Docker image built successfully"
+
+ - name: Zero-Downtime Production Deployment
+ run: |
+ echo "π Starting zero-downtime production deployment..."
+
+ COMPOSE_FILE="docker-compose.production.yml"
+ CONTAINER_NAME="portfolio-app"
+ HEALTH_PORT="3000"
+
+ # Backup current container ID if running
+ OLD_CONTAINER=$(docker ps -q -f name=$CONTAINER_NAME || echo "")
+
+ # Start new container with updated image (docker-compose will handle this)
+ echo "π Starting new production container..."
+ docker compose -f $COMPOSE_FILE up -d --no-deps --build portfolio
+
+ # Wait for new container to be healthy
+ echo "β³ Waiting for new container to be healthy..."
+ for i in {1..60}; do
+ NEW_CONTAINER=$(docker ps -q -f name=$CONTAINER_NAME)
+ if [ ! -z "$NEW_CONTAINER" ]; then
+ # Check health status
+ HEALTH=$(docker inspect $NEW_CONTAINER --format='{{.State.Health.Status}}' 2>/dev/null || echo "starting")
+ if [ "$HEALTH" == "healthy" ]; then
+ echo "β
New container is healthy!"
+ break
+ fi
+ # Also check HTTP health endpoint
+ if curl -f http://localhost:$HEALTH_PORT/api/health > /dev/null 2>&1; then
+ echo "β
New container is responding!"
+ break
+ fi
+ fi
+ echo "β³ Waiting... ($i/60)"
+ sleep 2
+ done
+
+ # Verify new container is working
+ if ! curl -f http://localhost:$HEALTH_PORT/api/health > /dev/null 2>&1; then
+ echo "β New container failed health check!"
+ docker compose -f $COMPOSE_FILE logs --tail=50 portfolio
+ exit 1
+ fi
+
+ # Remove old container if it exists and is different
+ if [ ! -z "$OLD_CONTAINER" ]; then
+ NEW_CONTAINER=$(docker ps -q -f name=$CONTAINER_NAME)
+ if [ "$OLD_CONTAINER" != "$NEW_CONTAINER" ]; then
+ echo "π§Ή Removing old container..."
+ docker stop $OLD_CONTAINER 2>/dev/null || true
+ docker rm $OLD_CONTAINER 2>/dev/null || true
+ fi
+ fi
+
+ echo "β
Production deployment completed with zero downtime!"
+ env:
+ NODE_ENV: production
+ LOG_LEVEL: ${{ vars.LOG_LEVEL || 'info' }}
+ NEXT_PUBLIC_BASE_URL: ${{ vars.NEXT_PUBLIC_BASE_URL || 'https://dk0.dev' }}
+ MY_EMAIL: ${{ vars.MY_EMAIL }}
+ MY_INFO_EMAIL: ${{ vars.MY_INFO_EMAIL }}
+ MY_PASSWORD: ${{ secrets.MY_PASSWORD }}
+ MY_INFO_PASSWORD: ${{ secrets.MY_INFO_PASSWORD }}
+ ADMIN_BASIC_AUTH: ${{ secrets.ADMIN_BASIC_AUTH }}
+ N8N_WEBHOOK_URL: ${{ vars.N8N_WEBHOOK_URL || '' }}
+ N8N_SECRET_TOKEN: ${{ secrets.N8N_SECRET_TOKEN || '' }}
+
+ - name: Production Health Check
+ run: |
+ echo "π Running production health checks..."
+ for i in {1..20}; do
+ if curl -f http://localhost:3000/api/health && curl -f http://localhost:3000/ > /dev/null; then
+ echo "β
Production is fully operational!"
+ exit 0
+ fi
+ echo "β³ Waiting for production... ($i/20)"
+ sleep 3
+ done
+ echo "β Production health check failed!"
+ docker compose -f docker-compose.production.yml logs --tail=50
+ exit 1
+
+ - name: Cleanup
+ run: |
+ echo "π§Ή Cleaning up old images..."
+ docker image prune -f
+ echo "β
Cleanup completed"
diff --git a/.gitea/workflows/staging-deploy.yml b/.gitea/workflows/staging-deploy.yml.disabled
similarity index 100%
rename from .gitea/workflows/staging-deploy.yml
rename to .gitea/workflows/staging-deploy.yml.disabled
diff --git a/.gitea/workflows/test-and-build.yml b/.gitea/workflows/test-and-build.yml
deleted file mode 100644
index 8a1db70..0000000
--- a/.gitea/workflows/test-and-build.yml
+++ /dev/null
@@ -1,41 +0,0 @@
-name: Test and Build
-
-on:
- push:
- branches: [ main ]
- pull_request:
- branches: [ main ]
-
-env:
- NODE_VERSION: '20'
-
-jobs:
- test-and-build:
- runs-on: ubuntu-latest
- steps:
- - name: Checkout code
- uses: actions/checkout@v3
-
- - name: Setup Node.js
- uses: actions/setup-node@v4
- with:
- node-version: ${{ env.NODE_VERSION }}
- cache: 'npm'
- cache-dependency-path: 'package-lock.json'
-
- - name: Install dependencies
- run: npm ci
-
- - name: Run linting
- run: npm run lint
-
- - name: Run tests
- run: npm run test
-
- - name: Build application
- run: npm run build
-
- - name: Run security scan
- run: |
- echo "π Running npm audit..."
- npm audit --audit-level=high || echo "β οΈ Some vulnerabilities found, but continuing..."
\ No newline at end of file
diff --git a/.gitea/workflows/test-gitea-variables.yml b/.gitea/workflows/test-gitea-variables.yml
deleted file mode 100644
index 0f4ac08..0000000
--- a/.gitea/workflows/test-gitea-variables.yml
+++ /dev/null
@@ -1,105 +0,0 @@
-name: Test Gitea Variables and Secrets
-
-on:
- push:
- branches: [ production ]
- workflow_dispatch:
-
-jobs:
- test-variables:
- runs-on: ubuntu-latest
- steps:
- - name: Checkout code
- uses: actions/checkout@v3
-
- - name: Test Variables and Secrets Access
- run: |
- echo "π Testing Gitea Variables and Secrets access..."
-
- # Test Variables
- echo "π Testing Variables:"
- echo "NEXT_PUBLIC_BASE_URL: '${{ vars.NEXT_PUBLIC_BASE_URL }}'"
- echo "MY_EMAIL: '${{ vars.MY_EMAIL }}'"
- echo "MY_INFO_EMAIL: '${{ vars.MY_INFO_EMAIL }}'"
- echo "NODE_ENV: '${{ vars.NODE_ENV }}'"
- echo "LOG_LEVEL: '${{ vars.LOG_LEVEL }}'"
- echo "NEXT_PUBLIC_UMAMI_URL: '${{ vars.NEXT_PUBLIC_UMAMI_URL }}'"
- echo "NEXT_PUBLIC_UMAMI_WEBSITE_ID: '${{ vars.NEXT_PUBLIC_UMAMI_WEBSITE_ID }}'"
-
- # Test Secrets (without revealing values)
- echo ""
- echo "π Testing Secrets:"
- echo "MY_PASSWORD: '$([ -n "${{ secrets.MY_PASSWORD }}" ] && echo "[SET]" || echo "[NOT SET]")'"
- echo "MY_INFO_PASSWORD: '$([ -n "${{ secrets.MY_INFO_PASSWORD }}" ] && echo "[SET]" || echo "[NOT SET]")'"
- echo "ADMIN_BASIC_AUTH: '$([ -n "${{ secrets.ADMIN_BASIC_AUTH }}" ] && echo "[SET]" || echo "[NOT SET]")'"
-
- # Check if variables are empty
- echo ""
- echo "π Checking for empty variables:"
- if [ -z "${{ vars.NEXT_PUBLIC_BASE_URL }}" ]; then
- echo "β NEXT_PUBLIC_BASE_URL is empty or not set"
- else
- echo "β
NEXT_PUBLIC_BASE_URL is set"
- fi
-
- if [ -z "${{ vars.MY_EMAIL }}" ]; then
- echo "β MY_EMAIL is empty or not set"
- else
- echo "β
MY_EMAIL is set"
- fi
-
- if [ -z "${{ vars.MY_INFO_EMAIL }}" ]; then
- echo "β MY_INFO_EMAIL is empty or not set"
- else
- echo "β
MY_INFO_EMAIL is set"
- fi
-
- # Check secrets
- if [ -z "${{ secrets.MY_PASSWORD }}" ]; then
- echo "β MY_PASSWORD secret is empty or not set"
- else
- echo "β
MY_PASSWORD secret is set"
- fi
-
- if [ -z "${{ secrets.MY_INFO_PASSWORD }}" ]; then
- echo "β MY_INFO_PASSWORD secret is empty or not set"
- else
- echo "β
MY_INFO_PASSWORD secret is set"
- fi
-
- if [ -z "${{ secrets.ADMIN_BASIC_AUTH }}" ]; then
- echo "β ADMIN_BASIC_AUTH secret is empty or not set"
- else
- echo "β
ADMIN_BASIC_AUTH secret is set"
- fi
-
- echo ""
- echo "π Summary:"
- echo "Variables set: $(echo '${{ vars.NEXT_PUBLIC_BASE_URL }}' | wc -c)"
- echo "Secrets set: $(echo '${{ secrets.MY_PASSWORD }}' | wc -c)"
-
- - name: Test Environment Variable Export
- run: |
- echo "π§ͺ Testing environment variable export..."
-
- # Export variables as environment variables
- export NODE_ENV="${{ vars.NODE_ENV }}"
- export LOG_LEVEL="${{ vars.LOG_LEVEL }}"
- export NEXT_PUBLIC_BASE_URL="${{ vars.NEXT_PUBLIC_BASE_URL }}"
- export NEXT_PUBLIC_UMAMI_URL="${{ vars.NEXT_PUBLIC_UMAMI_URL }}"
- export NEXT_PUBLIC_UMAMI_WEBSITE_ID="${{ vars.NEXT_PUBLIC_UMAMI_WEBSITE_ID }}"
- export MY_EMAIL="${{ vars.MY_EMAIL }}"
- export MY_INFO_EMAIL="${{ vars.MY_INFO_EMAIL }}"
- export MY_PASSWORD="${{ secrets.MY_PASSWORD }}"
- export MY_INFO_PASSWORD="${{ secrets.MY_INFO_PASSWORD }}"
- export ADMIN_BASIC_AUTH="${{ secrets.ADMIN_BASIC_AUTH }}"
-
- echo "π Exported environment variables:"
- echo "NODE_ENV: ${NODE_ENV:-[NOT SET]}"
- echo "LOG_LEVEL: ${LOG_LEVEL:-[NOT SET]}"
- echo "NEXT_PUBLIC_BASE_URL: ${NEXT_PUBLIC_BASE_URL:-[NOT SET]}"
- echo "MY_EMAIL: ${MY_EMAIL:-[NOT SET]}"
- echo "MY_INFO_EMAIL: ${MY_INFO_EMAIL:-[NOT SET]}"
- echo "MY_PASSWORD: $([ -n "${MY_PASSWORD}" ] && echo "[SET]" || echo "[NOT SET]")"
- echo "MY_INFO_PASSWORD: $([ -n "${MY_INFO_PASSWORD}" ] && echo "[SET]" || echo "[NOT SET]")"
- echo "ADMIN_BASIC_AUTH: $([ -n "${ADMIN_BASIC_AUTH}" ] && echo "[SET]" || echo "[NOT SET]")"
diff --git a/AUTO_DEPLOYMENT_STATUS.md b/AUTO_DEPLOYMENT_STATUS.md
deleted file mode 100644
index 9b45c7a..0000000
--- a/AUTO_DEPLOYMENT_STATUS.md
+++ /dev/null
@@ -1,85 +0,0 @@
-# π Auto-Deployment Status
-
-## Current Setup
-
-### GitHub Actions Workflow (`.github/workflows/ci-cd.yml`)
-
-**Triggers on**: Push to `main` OR `production` branches
-
-**What happens on `main` branch**:
-- β
Runs tests
-- β
Runs linting
-- β
Builds Docker image
-- β
Pushes image to registry
-- β **Does NOT deploy to server**
-
-**What happens on `production` branch**:
-- β
Runs tests
-- β
Runs linting
-- β
Builds Docker image
-- β
Pushes image to registry
-- β
**Deploys to server automatically**
-
-### Key Line in Workflow
-
-```yaml
-# Line 159 in .github/workflows/ci-cd.yml
-if: github.event_name == 'push' && github.ref == 'refs/heads/production'
-```
-
-This means deployment **only** happens on `production` branch.
-
-## Answer: Can you merge to main and auto-deploy?
-
-**β NO** - Merging to `main` will:
-- Build and test everything
-- Create Docker image
-- **But NOT deploy to your server**
-
-**β
YES** - Merging to `production` will:
-- Build and test everything
-- Create Docker image
-- **AND deploy to your server automatically**
-
-## Options
-
-### Option 1: Use Production Branch (Current Setup)
-```bash
-# Merge dev β main (tests/build only)
-git checkout main
-git merge dev
-git push origin main
-
-# Then merge main β production (auto-deploys)
-git checkout production
-git merge main
-git push origin production # β This triggers deployment
-```
-
-### Option 2: Enable Auto-Deploy on Main
-If you want `main` to auto-deploy, I can update the workflow to deploy on `main` as well.
-
-### Option 3: Manual Deployment
-After merging to `main`, manually run:
-```bash
-./scripts/gitea-deploy.sh
-# or
-./scripts/auto-deploy.sh
-```
-
-## Recommendation
-
-**Keep current setup** (deploy only on `production`):
-- β
Safer: `main` is for testing builds
-- β
`production` is explicitly for deployments
-- β
Can test on `main` without deploying
-- β
Clear separation of concerns
-
-**Workflow**:
-1. Merge `dev` β `main` (validates build works)
-2. Test the built image if needed
-3. Merge `main` β `production` (auto-deploys)
-
----
-
-**Current Status**: Auto-deployment is configured, but only for `production` branch.
diff --git a/CLEANUP_PLAN.md b/CLEANUP_PLAN.md
deleted file mode 100644
index 61c34f5..0000000
--- a/CLEANUP_PLAN.md
+++ /dev/null
@@ -1,66 +0,0 @@
-# π§Ή Codebase Cleanup Plan
-
-## MD Files Analysis
-
-### β
KEEP (Essential Documentation)
-1. **README.md** - Main project documentation
-2. **docs/ai-image-generation/README.md** - AI feature docs
-3. **docs/ai-image-generation/SETUP.md** - Setup guide
-4. **docs/ai-image-generation/QUICKSTART.md** - Quick start
-5. **docs/ai-image-generation/WEBHOOK_SETUP.md** - Webhook setup (just created)
-6. **TESTING_GUIDE.md** - Testing documentation
-7. **SAFE_PUSH_TO_MAIN.md** - Deployment guide
-8. **AUTO_DEPLOYMENT_STATUS.md** - Deployment status (just created)
-
-### β REMOVE (Old/Duplicate/Outdated)
-1. **CHANGELOG_DEV.md** - Old changelog, can be in git history
-2. **PUSH_READY.md** - One-time status file
-3. **COMMIT_MESSAGE.txt** - One-time commit message
-4. **DEPLOYMENT-FIXES.md** - Old fixes, should be in git
-5. **DEPLOYMENT-IMPROVEMENTS.md** - Old improvements
-6. **DEPLOYMENT.md** - Duplicate of PRODUCTION-DEPLOYMENT.md
-7. **AFTER_PUSH_SETUP.md** - One-time setup guide
-8. **PRE_PUSH_CHECKLIST.md** - Can merge into SAFE_PUSH_TO_MAIN.md
-9. **TEST_FIXES.md** - One-time fix notes
-10. **AUTOMATED_TESTING_SETUP.md** - Info now in TESTING_GUIDE.md
-11. **SECURITY-UPDATE.md** - Old update notes
-12. **SECURITY-CHECKLIST.md** - Can merge into SECURITY.md
-13. **ANALYTICS.md** - If not actively used
-14. **PRODUCTION-DEPLOYMENT.md** - If DEPLOYMENT.md covers it
-
-### π CONSOLIDATE (Merge into main docs)
-- **docs/IMPROVEMENTS_SUMMARY.md** β Merge into README or remove
-- **docs/CODING_DETECTION_DEBUG.md** β Remove if not needed
-- **docs/DYNAMIC_ACTIVITY_MANAGEMENT.md** β Keep if actively used
-- **docs/ACTIVITY_FEATURES.md** β Keep if actively used
-- **docs/N8N_CHAT_SETUP.md** β Keep if using n8n chat
-- **docs/N8N_INTEGRATION.md** β Keep if using n8n
-
-## Old/Unused Files to Remove
-
-### Scripts (Many duplicates)
-- `scripts/test-fix.sh` - One-time fix
-- `scripts/test-deployment.sh` - One-time test
-- `scripts/quick-health-fix.sh` - One-time fix
-- `scripts/fix-connection.sh` - One-time fix
-- `scripts/debug-gitea-actions.sh` - Debug script, not needed
-- Multiple docker-compose files (keep only needed ones)
-
-### Disabled Workflows
-- `.gitea/workflows/*.disabled` - Remove all disabled workflows
-
-### Old Test Results
-- `test-results/` - Can be regenerated
-- `playwright-report/` - Can be regenerated
-
-### Logs
-- `logs/*.log` - Should be in .gitignore
-
-## Git Remote Issue
-Current: `https://git.dk0.dev/denshooter/portfolio`
-Issue: Can't connect to git.dk0.dev:443
-
-Options:
-1. Check if server is up
-2. Use SSH instead: `git@git.dk0.dev:denshooter/portfolio.git`
-3. Check if URL changed
diff --git a/CLEANUP_SUMMARY.md b/CLEANUP_SUMMARY.md
deleted file mode 100644
index ef9d920..0000000
--- a/CLEANUP_SUMMARY.md
+++ /dev/null
@@ -1,95 +0,0 @@
-# π§Ή Cleanup Summary
-
-## Files Removed
-
-### Documentation (15 files)
-- β
CHANGELOG_DEV.md - Old changelog
-- β
PUSH_READY.md - One-time status
-- β
COMMIT_MESSAGE.txt - One-time commit message
-- β
DEPLOYMENT-FIXES.md - Old fixes
-- β
DEPLOYMENT-IMPROVEMENTS.md - Old improvements
-- β
DEPLOYMENT.md - Duplicate
-- β
AFTER_PUSH_SETUP.md - One-time setup
-- β
PRE_PUSH_CHECKLIST.md - Merged into SAFE_PUSH_TO_MAIN.md
-- β
TEST_FIXES.md - One-time fixes
-- β
AUTOMATED_TESTING_SETUP.md - Info in TESTING_GUIDE.md
-- β
SECURITY-UPDATE.md - Old update
-- β
SECURITY-CHECKLIST.md - Merged into SECURITY.md
-- β
PRODUCTION-DEPLOYMENT.md - Duplicate
-- β
ANALYTICS.md - Not actively used
-- β
docs/IMPROVEMENTS_SUMMARY.md - Old summary
-- β
docs/CODING_DETECTION_DEBUG.md - Debug notes
-
-### Scripts (4 files)
-- β
scripts/quick-health-fix.sh - One-time fix
-- β
scripts/fix-connection.sh - One-time fix
-- β
scripts/debug-gitea-actions.sh - Debug script
-
-### Workflows (7 files)
-- β
.gitea/workflows/*.disabled - All disabled workflows removed
-
-### Docker Configs (2 files)
-- β
docker-compose.zero-downtime.yml - Old version
-- β
docker-compose.zero-downtime-fixed.yml - Old version
-- β
nginx-zero-downtime.conf - Unused
-
-## Files Kept (Essential)
-
-### Documentation
-- β
README.md - Main docs
-- β
DEV-SETUP.md - Setup guide
-- β
SECURITY.md - Security info
-- β
TESTING_GUIDE.md - Testing docs
-- β
SAFE_PUSH_TO_MAIN.md - Deployment guide
-- β
AUTO_DEPLOYMENT_STATUS.md - Deployment status
-- β
docs/ai-image-generation/* - AI feature docs
-- β
docs/ACTIVITY_FEATURES.md - Activity features
-- β
docs/DYNAMIC_ACTIVITY_MANAGEMENT.md - Activity management
-- β
docs/N8N_CHAT_SETUP.md - n8n chat setup
-- β
docs/N8N_INTEGRATION.md - n8n integration
-
-### Docker Configs
-- β
docker-compose.yml - Main config
-- β
docker-compose.production.yml - Production
-- β
docker-compose.dev.minimal.yml - Dev minimal
-
-## Git Remote Fixed
-
-**Before**: `https://git.dk0.dev/denshooter/portfolio` (HTTPS - connection issues)
-**After**: `git@git.dk0.dev:denshooter/portfolio.git` (SSH - more reliable)
-
-## .gitignore Updated
-
-Added:
-- `logs/*.log` - Log files
-- `test-results/` - Test results
-- `playwright-report/` - Playwright reports
-- `coverage/` - Coverage reports
-- `.idea/` - IDE files
-- `.vscode/` - IDE files
-
-## Next Steps
-
-1. **Test Git connection**:
- ```bash
- git fetch
- ```
-
-2. **If SSH doesn't work**, switch back to HTTPS:
- ```bash
- git remote set-url origin https://git.dk0.dev/denshooter/portfolio.git
- ```
-
-3. **Commit cleanup**:
- ```bash
- git add .
- git commit -m "chore: Clean up old documentation and unused files"
- git push origin dev
- ```
-
-## Result
-
-- **Removed**: ~30 files
-- **Kept**: Essential documentation and configs
-- **Fixed**: Git remote connection
-- **Updated**: .gitignore for better file management
diff --git a/DEPLOYMENT_FIX.md b/DEPLOYMENT_FIX.md
deleted file mode 100644
index ecc3bf4..0000000
--- a/DEPLOYMENT_FIX.md
+++ /dev/null
@@ -1,89 +0,0 @@
-# π§ Deployment Fixes Applied
-
-## Issues Fixed
-
-### 1. Port 3001 Already Allocated β β β
-**Problem**: Port 3001 was already in use, causing staging deployment to fail.
-
-**Fix**:
-- Changed staging port from `3001` to `3002`
-- Changed PostgreSQL staging port from `5433` to `5434`
-- Changed Redis staging port from `6380` to `6381`
-
-### 2. Docker Compose Version Warning β β β
-**Problem**: `version: '3.8'` is obsolete in newer Docker Compose.
-
-**Fix**: Removed `version` line from `docker-compose.staging.yml`
-
-### 3. Missing N8N Environment Variables β β β
-**Problem**: `N8N_SECRET_TOKEN` warning appeared.
-
-**Fix**: Added `N8N_WEBHOOK_URL` and `N8N_SECRET_TOKEN` to staging compose file
-
-### 4. Wrong Compose File Used β β β
-**Problem**: Gitea workflow was using wrong compose file (stopping production containers).
-
-**Fix**:
-- Updated `ci-cd-with-gitea-vars.yml` to detect branch and use correct compose file
-- Created dedicated `staging-deploy.yml` workflow
-- Staging now uses `docker-compose.staging.yml`
-- Production uses `docker-compose.production.yml`
-
-## Updated Ports
-
-| Service | Staging | Production |
-|---------|---------|------------|
-| App | **3002** β
| **3000** |
-| PostgreSQL | **5434** β
| **5432** |
-| Redis | **6381** β
| **6379** |
-
-## How It Works Now
-
-### Staging (dev/main branch)
-```bash
-git push origin dev
-# β Uses docker-compose.staging.yml
-# β Deploys to port 3002
-# β Does NOT touch production containers
-```
-
-### Production (production branch)
-```bash
-git push origin production
-# β Uses docker-compose.production.yml
-# β Deploys to port 3000
-# β Zero-downtime deployment
-# β Does NOT touch staging containers
-```
-
-## Files Updated
-
-- β
`docker-compose.staging.yml` - Fixed ports, removed version, added N8N vars
-- β
`.gitea/workflows/ci-cd-with-gitea-vars.yml` - Branch detection, correct compose files
-- β
`.gitea/workflows/staging-deploy.yml` - New dedicated staging workflow
-- β
`STAGING_SETUP.md` - Updated port references
-
-## Next Steps
-
-1. **Test staging deployment**:
- ```bash
- git push origin dev
- # Should deploy to port 3002 without errors
- ```
-
-2. **Verify staging**:
- ```bash
- curl http://localhost:3002/api/health
- ```
-
-3. **When ready for production**:
- ```bash
- git checkout production
- git merge main
- git push origin production
- # Deploys safely to port 3000
- ```
-
----
-
-**All fixes applied!** Staging and production are now completely isolated. π
diff --git a/DEPLOYMENT_SETUP.md b/DEPLOYMENT_SETUP.md
new file mode 100644
index 0000000..20636a2
--- /dev/null
+++ b/DEPLOYMENT_SETUP.md
@@ -0,0 +1,200 @@
+# π Deployment Setup Guide
+
+## Overview
+
+This project uses a **dual-branch deployment strategy** with zero-downtime deployments:
+
+- **Production Branch** (`production`) β Serves `https://dk0.dev` on port 3000
+- **Dev Branch** (`dev`) β Serves `https://dev.dk0.dev` on port 3002
+
+Both environments are completely isolated with separate:
+- Docker containers
+- Databases (PostgreSQL)
+- Redis instances
+- Networks
+- Volumes
+
+## Branch Strategy
+
+### Production Branch
+- **Branch**: `production`
+- **Domain**: `https://dk0.dev`
+- **Port**: `3000`
+- **Container**: `portfolio-app`
+- **Database**: `portfolio_db` (port 5432)
+- **Redis**: `portfolio-redis` (port 6379)
+- **Image Tag**: `portfolio-app:production` / `portfolio-app:latest`
+
+### Dev Branch
+- **Branch**: `dev`
+- **Domain**: `https://dev.dk0.dev`
+- **Port**: `3002`
+- **Container**: `portfolio-app-staging`
+- **Database**: `portfolio_staging_db` (port 5434)
+- **Redis**: `portfolio-redis-staging` (port 6381)
+- **Image Tag**: `portfolio-app:staging`
+
+## Automatic Deployment
+
+### How It Works
+
+1. **Push to `production` branch**:
+ - Triggers `.gitea/workflows/production-deploy.yml`
+ - Runs tests, builds, and deploys to production
+ - Zero-downtime deployment (starts new container, waits for health, removes old)
+
+2. **Push to `dev` branch**:
+ - Triggers `.gitea/workflows/dev-deploy.yml`
+ - Runs tests, builds, and deploys to dev/staging
+ - Zero-downtime deployment
+
+### Zero-Downtime Process
+
+1. Build new Docker image
+2. Start new container with updated image
+3. Wait for new container to be healthy (health checks)
+4. Verify HTTP endpoints respond correctly
+5. Remove old container (if different)
+6. Cleanup old images
+
+## Manual Deployment
+
+### Production
+```bash
+# Build and deploy production
+docker build -t portfolio-app:latest .
+docker compose -f docker-compose.production.yml up -d --build
+```
+
+### Dev/Staging
+```bash
+# Build and deploy dev
+docker build -t portfolio-app:staging .
+docker compose -f docker-compose.staging.yml up -d --build
+```
+
+## Environment Variables
+
+### Required Gitea Variables
+- `NEXT_PUBLIC_BASE_URL` - Base URL for the application
+- `MY_EMAIL` - Email address for contact
+- `MY_INFO_EMAIL` - Info email address
+- `LOG_LEVEL` - Logging level (info/debug)
+
+### Required Gitea Secrets
+- `MY_PASSWORD` - Email password
+- `MY_INFO_PASSWORD` - Info email password
+- `ADMIN_BASIC_AUTH` - Admin basic auth credentials
+- `N8N_SECRET_TOKEN` - Optional: n8n webhook secret
+
+### Optional Variables
+- `N8N_WEBHOOK_URL` - n8n webhook URL for automation
+
+## Health Checks
+
+Both environments have health check endpoints:
+- Production: `http://localhost:3000/api/health`
+- Dev: `http://localhost:3002/api/health`
+
+## Monitoring
+
+### Check Container Status
+```bash
+# Production
+docker compose -f docker-compose.production.yml ps
+
+# Dev
+docker compose -f docker-compose.staging.yml ps
+```
+
+### View Logs
+```bash
+# Production
+docker logs portfolio-app --tail=100 -f
+
+# Dev
+docker logs portfolio-app-staging --tail=100 -f
+```
+
+### Health Check
+```bash
+# Production
+curl http://localhost:3000/api/health
+
+# Dev
+curl http://localhost:3002/api/health
+```
+
+## Troubleshooting
+
+### Container Won't Start
+1. Check logs: `docker logs : null;
},
diff --git a/components/GhostEditor.tsx b/components/GhostEditor.tsx
deleted file mode 100644
index 4ee96fe..0000000
--- a/components/GhostEditor.tsx
+++ /dev/null
@@ -1,733 +0,0 @@
-'use client';
-
-import React, { useState, useRef, useEffect, useCallback } from 'react';
-import { motion, AnimatePresence } from 'framer-motion';
-import {
- Save,
- X,
- Eye,
- Settings,
- Globe,
- Github,
- Image as ImageIcon,
- Bold,
- Italic,
- List,
- Quote,
- Code,
- Link2,
- ListOrdered,
- Underline,
- Strikethrough,
- Type,
- Columns
-} from 'lucide-react';
-
-interface Project {
- id: string;
- title: string;
- description: string;
- content?: string;
- category: string;
- difficulty?: string;
- tags?: string[];
- featured: boolean;
- published: boolean;
- github?: string;
- live?: string;
- image?: string;
- createdAt: string;
- updatedAt: string;
-}
-
-interface GhostEditorProps {
- isOpen: boolean;
- onClose: () => void;
- project?: Project | null;
- onSave: (projectData: Partial
$1
')
- .replace(/^## (.*$)/gim, '$1
')
- .replace(/^# (.*$)/gim, '$1
')
- // Bold and Italic
- .replace(/\*\*(.*?)\*\*/g, '$1')
- .replace(/\*(.*?)\*/g, '$1')
- // Underline and Strikethrough
- .replace(/(.*?)<\/u>/g, '$1')
- .replace(/~~(.*?)~~/g, '$1')
- // Code
- .replace(/```([^`]+)```/g, '
')
- .replace(/`([^`]+)`/g, '$1$1')
- // Lists
- .replace(/^\- (.*$)/gim, '')
- // Quotes
- .replace(/^> (.*$)/gim, '
$1
')
- // Dividers
- .replace(/^---$/gim, '
')
- // Paragraphs
- .replace(/\n\n/g, '
')
- .replace(/\n/g, '
');
-
- return `
${html}
- {description || 'Brief description of your project...'} -
-$1')
- .replace(/`([^`]+)`/g, '$1')
- // Lists - WHITE TEXT
- .replace(/^\- (.*$)/gim, '$1') - // Dividers - .replace(/^---$/gim, '
')
- .replace(/\n/g, '
');
-
- return `
${html}
- {description || 'Brief description of your project...'} -
-