diff --git a/.gitea/workflows/deploy-to-prod.yml b/.gitea/workflows/deploy-to-prod.yml index adeae76..7f1d517 100644 --- a/.gitea/workflows/deploy-to-prod.yml +++ b/.gitea/workflows/deploy-to-prod.yml @@ -47,6 +47,19 @@ jobs: - name: Install Dependencies run: npm ci + - name: Bump Minor Version and Push + run: | + # Configure git for the commit. + git config --global user.name 'Gitea Actions' + git config --global user.email 'actions@gitea.projectium.com' + + # Bump the minor version number. This creates a new commit and a new tag. + # The commit message includes [skip ci] to prevent this push from triggering another workflow run. + npm version minor -m "ci: Bump version to %s for production release [skip ci]" + + # Push the new commit and the new tag back to the main branch. + git push --follow-tags + - name: Check for Production Database Schema Changes env: DB_HOST: ${{ secrets.DB_HOST }} @@ -61,9 +74,10 @@ jobs: echo "--- Checking for production schema changes ---" CURRENT_HASH=$(cat sql/master_schema_rollup.sql | dos2unix | sha256sum | awk '{ print $1 }') echo "Current Git Schema Hash: $CURRENT_HASH" - DEPLOYED_HASH=$(PGPASSWORD="$DB_PASSWORD" psql -v ON_ERROR_STOP=1 -h "$DB_HOST" -p 5432 -U "$DB_USER" -d "$DB_NAME" -c "SELECT schema_hash FROM public.schema_info WHERE environment = 'production';" -t -A || echo "none") + # The psql command will now fail the step if the query errors (e.g., column missing), preventing deployment on a bad schema. + DEPLOYED_HASH=$(PGPASSWORD="$DB_PASSWORD" psql -v ON_ERROR_STOP=1 -h "$DB_HOST" -p 5432 -U "$DB_USER" -d "$DB_NAME" -c "SELECT schema_hash FROM public.schema_info WHERE environment = 'production';" -t -A) echo "Deployed DB Schema Hash: $DEPLOYED_HASH" - if [ "$DEPLOYED_HASH" = "none" ] || [ -z "$DEPLOYED_HASH" ]; then + if [ -z "$DEPLOYED_HASH" ]; then echo "WARNING: No schema hash found in the production database. This is expected for a first-time deployment." elif [ "$CURRENT_HASH" != "$DEPLOYED_HASH" ]; then echo "ERROR: Database schema mismatch detected! A manual database migration is required." diff --git a/.gitea/workflows/deploy-to-test.yml b/.gitea/workflows/deploy-to-test.yml index ca8772c..e513a01 100644 --- a/.gitea/workflows/deploy-to-test.yml +++ b/.gitea/workflows/deploy-to-test.yml @@ -143,9 +143,9 @@ jobs: npm run test:integration -- --coverage --reporter=verbose --includeTaskLocation --testTimeout=10000 --silent=passed-only || true echo "--- Running E2E Tests ---" - # Run E2E tests using the integration config (for DB setup) but output coverage separately. - # We increase timeout significantly (120s) for E2E flows that involve AI processing. - npx vitest run src/tests/e2e --config vitest.config.integration.ts --coverage --coverage.reportsDirectory=.coverage/e2e --reporter=verbose --testTimeout=120000 --no-file-parallelism || true + # Run E2E tests using the dedicated E2E config which inherits from integration config. + # We still pass --coverage to enable it, but directory and timeout are now in the config. + npx vitest run --config vitest.config.e2e.ts --coverage --reporter=verbose --no-file-parallelism || true # Re-enable secret masking for subsequent steps. echo "::secret-masking::" @@ -264,16 +264,14 @@ jobs: # We normalize line endings to ensure the hash is consistent across different OS environments. CURRENT_HASH=$(cat sql/master_schema_rollup.sql | dos2unix | sha256sum | awk '{ print $1 }') echo "Current Git Schema Hash: $CURRENT_HASH" - # Query the production database to get the hash of the deployed schema. # The `psql` command requires PGPASSWORD to be set. # `\t` sets tuples-only mode and `\A` unaligns output to get just the raw value. - # The `|| echo "none"` ensures the command doesn't fail if the table or row doesn't exist yet. - DEPLOYED_HASH=$(PGPASSWORD="$DB_PASSWORD" psql -v ON_ERROR_STOP=1 -h "$DB_HOST" -p 5432 -U "$DB_USER" -d "$DB_NAME" -c "SELECT schema_hash FROM public.schema_info WHERE environment = 'test';" -t -A || echo "none") + # The psql command will now fail the step if the query errors (e.g., column missing), preventing deployment on a bad schema. + DEPLOYED_HASH=$(PGPASSWORD="$DB_PASSWORD" psql -v ON_ERROR_STOP=1 -h "$DB_HOST" -p 5432 -U "$DB_USER" -d "$DB_NAME" -c "SELECT schema_hash FROM public.schema_info WHERE environment = 'test';" -t -A) echo "Deployed DB Schema Hash: $DEPLOYED_HASH" - # Check if the hash is "none" (command failed) OR if it's an empty string (table exists but is empty). - if [ "$DEPLOYED_HASH" = "none" ] || [ -z "$DEPLOYED_HASH" ]; then + if [ -z "$DEPLOYED_HASH" ]; then echo "WARNING: No schema hash found in the test database." echo "This is expected for a first-time deployment. The hash will be set after a successful deployment." # We allow the deployment to continue, but a manual schema update is required. diff --git a/.gitea/workflows/manual-deploy-major.yml b/.gitea/workflows/manual-deploy-major.yml new file mode 100644 index 0000000..8a0bada --- /dev/null +++ b/.gitea/workflows/manual-deploy-major.yml @@ -0,0 +1,180 @@ +# .gitea/workflows/manual-deploy-major.yml +# +# This workflow provides a MANUAL trigger to perform a MAJOR version bump +# and deploy the application to the PRODUCTION environment. +name: Manual - Deploy Major Version to Production + +on: + workflow_dispatch: + inputs: + confirmation: + description: 'Type "deploy-major-to-prod" to confirm you want to deploy a new major version.' + required: true + default: 'do-not-run' + force_reload: + description: 'Force PM2 reload even if version matches (true/false).' + required: false + type: boolean + default: false + +jobs: + deploy-production-major: + runs-on: projectium.com + + steps: + - name: Verify Confirmation Phrase + run: | + if [ "${{ gitea.event.inputs.confirmation }}" != "deploy-major-to-prod" ]; then + echo "ERROR: Confirmation phrase did not match. Aborting deployment." + exit 1 + fi + echo "✅ Confirmation accepted. Proceeding with major version production deployment." + + - name: Checkout Code from 'main' branch + uses: actions/checkout@v3 + with: + ref: 'main' # Explicitly check out the main branch for production deployment + fetch-depth: 0 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '20' + cache: 'npm' + cache-dependency-path: '**/package-lock.json' + + - name: Install Dependencies + run: npm ci + + - name: Bump Major Version and Push + run: | + # Configure git for the commit. + git config --global user.name 'Gitea Actions' + git config --global user.email 'actions@gitea.projectium.com' + + # Bump the major version number. This creates a new commit and a new tag. + # The commit message includes [skip ci] to prevent this push from triggering another workflow run. + npm version major -m "ci: Bump version to %s for major release [skip ci]" + + # Push the new commit and the new tag back to the main branch. + git push --follow-tags + + - name: Check for Production Database Schema Changes + env: + DB_HOST: ${{ secrets.DB_HOST }} + DB_USER: ${{ secrets.DB_USER }} + DB_PASSWORD: ${{ secrets.DB_PASSWORD }} + DB_NAME: ${{ secrets.DB_DATABASE_PROD }} + run: | + if [ -z "$DB_HOST" ] || [ -z "$DB_USER" ] || [ -z "$DB_PASSWORD" ] || [ -z "$DB_NAME" ]; then + echo "ERROR: One or more production database secrets (DB_HOST, DB_USER, DB_PASSWORD, DB_DATABASE_PROD) are not set." + exit 1 + fi + echo "--- Checking for production schema changes ---" + CURRENT_HASH=$(cat sql/master_schema_rollup.sql | dos2unix | sha256sum | awk '{ print $1 }') + echo "Current Git Schema Hash: $CURRENT_HASH" + # The psql command will now fail the step if the query errors (e.g., column missing), preventing deployment on a bad schema. + DEPLOYED_HASH=$(PGPASSWORD="$DB_PASSWORD" psql -v ON_ERROR_STOP=1 -h "$DB_HOST" -p 5432 -U "$DB_USER" -d "$DB_NAME" -c "SELECT schema_hash FROM public.schema_info WHERE environment = 'production';" -t -A) + echo "Deployed DB Schema Hash: $DEPLOYED_HASH" + if [ -z "$DEPLOYED_HASH" ]; then + echo "WARNING: No schema hash found in the production database. This is expected for a first-time deployment." + elif [ "$CURRENT_HASH" != "$DEPLOYED_HASH" ]; then + echo "ERROR: Database schema mismatch detected! A manual database migration is required." + exit 1 + else + echo "✅ Schema is up to date. No changes detected." + fi + + - name: Build React Application for Production + run: | + if [ -z "${{ secrets.VITE_GOOGLE_GENAI_API_KEY }}" ]; then + echo "ERROR: The VITE_GOOGLE_GENAI_API_KEY secret is not set." + exit 1 + fi + GITEA_SERVER_URL="https://gitea.projectium.com" + COMMIT_MESSAGE=$(git log -1 --pretty=%s) + VITE_APP_VERSION="$(date +'%Y%m%d-%H%M'):$(git rev-parse --short HEAD)" \ + VITE_APP_COMMIT_URL="$GITEA_SERVER_URL/${{ gitea.repository }}/commit/${{ gitea.sha }}" \ + VITE_APP_COMMIT_MESSAGE="$COMMIT_MESSAGE" \ + VITE_API_BASE_URL=/api VITE_API_KEY=${{ secrets.VITE_GOOGLE_GENAI_API_KEY }} npm run build + + - name: Deploy Application to Production Server + run: | + echo "Deploying application files to /var/www/flyer-crawler.projectium.com..." + APP_PATH="/var/www/flyer-crawler.projectium.com" + mkdir -p "$APP_PATH" + mkdir -p "$APP_PATH/flyer-images/icons" "$APP_PATH/flyer-images/archive" + rsync -avz --delete --exclude 'node_modules' --exclude '.git' --exclude 'dist' --exclude 'flyer-images' ./ "$APP_PATH/" + rsync -avz dist/ "$APP_PATH" + echo "Application deployment complete." + + - name: Install Backend Dependencies and Restart Production Server + env: + # --- Production Secrets Injection --- + DB_HOST: ${{ secrets.DB_HOST }} + DB_USER: ${{ secrets.DB_USER }} + DB_PASSWORD: ${{ secrets.DB_PASSWORD }} + DB_NAME: ${{ secrets.DB_DATABASE_PROD }} + REDIS_URL: 'redis://localhost:6379' + REDIS_PASSWORD: ${{ secrets.REDIS_PASSWORD_PROD }} + FRONTEND_URL: 'https://flyer-crawler.projectium.com' + JWT_SECRET: ${{ secrets.JWT_SECRET }} + GEMINI_API_KEY: ${{ secrets.VITE_GOOGLE_GENAI_API_KEY }} + GOOGLE_MAPS_API_KEY: ${{ secrets.GOOGLE_MAPS_API_KEY }} + SMTP_HOST: 'localhost' + SMTP_PORT: '1025' + SMTP_SECURE: 'false' + SMTP_USER: '' + SMTP_PASS: '' + SMTP_FROM_EMAIL: 'noreply@flyer-crawler.projectium.com' + run: | + if [ -z "$DB_HOST" ] || [ -z "$DB_USER" ] || [ -z "$DB_PASSWORD" ] || [ -z "$DB_NAME" ]; then + echo "ERROR: One or more production database secrets (DB_HOST, DB_USER, DB_PASSWORD, DB_DATABASE_PROD) are not set." + exit 1 + fi + echo "Installing production dependencies and restarting server..." + cd /var/www/flyer-crawler.projectium.com + npm install --omit=dev + + # --- Version Check Logic --- + # Get the version from the newly deployed package.json + NEW_VERSION=$(node -p "require('./package.json').version") + echo "Deployed Package Version: $NEW_VERSION" + + # Get the running version from PM2 for the main API process + # We use a small node script to parse the JSON output from pm2 jlist + RUNNING_VERSION=$(pm2 jlist | node -e "try { const list = JSON.parse(require('fs').readFileSync(0, 'utf-8')); const app = list.find(p => p.name === 'flyer-crawler-api'); console.log(app ? app.pm2_env.version : ''); } catch(e) { console.log(''); }") + echo "Running PM2 Version: $RUNNING_VERSION" + + if [ "${{ gitea.event.inputs.force_reload }}" == "true" ] || [ "$NEW_VERSION" != "$RUNNING_VERSION" ] || [ -z "$RUNNING_VERSION" ]; then + if [ "${{ gitea.event.inputs.force_reload }}" == "true" ]; then + echo "Force reload triggered by manual input. Reloading PM2..." + else + echo "Version mismatch (Running: $RUNNING_VERSION -> Deployed: $NEW_VERSION) or app not running. Reloading PM2..." + fi + pm2 startOrReload ecosystem.config.cjs --env production && pm2 save + echo "Production backend server reloaded successfully." + else + echo "Version $NEW_VERSION is already running. Skipping PM2 reload." + fi + + echo "Updating schema hash in production database..." + CURRENT_HASH=$(cat sql/master_schema_rollup.sql | dos2unix | sha256sum | awk '{ print $1 }') + PGPASSWORD="$DB_PASSWORD" psql -v ON_ERROR_STOP=1 -h "$DB_HOST" -p 5432 -U "$DB_USER" -d "$DB_NAME" -c \ + "INSERT INTO public.schema_info (environment, schema_hash, deployed_at) VALUES ('production', '$CURRENT_HASH', NOW()) + ON CONFLICT (environment) DO UPDATE SET schema_hash = EXCLUDED.schema_hash, deployed_at = NOW();" + + UPDATED_HASH=$(PGPASSWORD="$DB_PASSWORD" psql -v ON_ERROR_STOP=1 -h "$DB_HOST" -p 5432 -U "$DB_USER" -d "$DB_NAME" -c "SELECT schema_hash FROM public.schema_info WHERE environment = 'production';" -t -A) + if [ "$CURRENT_HASH" = "$UPDATED_HASH" ]; then + echo "✅ Schema hash successfully updated in the database to: $UPDATED_HASH" + else + echo "ERROR: Failed to update schema hash in the database." + fi + + - name: Show PM2 Environment for Production + run: | + echo "--- Displaying recent PM2 logs for flyer-crawler-api ---" + sleep 5 + pm2 describe flyer-crawler-api || echo "Could not find production pm2 process." + pm2 logs flyer-crawler-api --lines 20 --nostream || echo "Could not find production pm2 process." + pm2 env flyer-crawler-api || echo "Could not find production pm2 process." \ No newline at end of file diff --git a/vitest.config.e2e.ts b/vitest.config.e2e.ts new file mode 100644 index 0000000..154418c --- /dev/null +++ b/vitest.config.e2e.ts @@ -0,0 +1,26 @@ +import { defineConfig, mergeConfig } from 'vitest/config'; +import integrationConfig from './vitest.config.integration'; + +const e2eConfig = mergeConfig( + integrationConfig, + defineConfig({ + test: { + name: 'e2e', + // Point specifically to E2E tests + include: ['src/tests/e2e/**/*.e2e.test.ts'], + // Increase timeout for E2E flows that involve AI or full API chains + testTimeout: 120000, + coverage: { + reportsDirectory: '.coverage/e2e', + }, + }, + }), +); + +// Explicitly override the include array to ensure we don't inherit integration tests +// (mergeConfig might concatenate arrays by default) +if (e2eConfig.test) { + e2eConfig.test.include = ['src/tests/e2e/**/*.e2e.test.ts']; +} + +export default e2eConfig; \ No newline at end of file