From 42dc3c8097949e3ae6852139a6ff71bf606c6624 Mon Sep 17 00:00:00 2001 From: ReachableCEO Date: Thu, 16 Oct 2025 17:18:18 -0500 Subject: [PATCH] Prepare CI and deployment scaffolding --- .env.example | 10 +++ .gitea/workflows/ci.yml | 137 ++++++++++++++++++++++++++++++ .gitignore | 1 + README.md | 50 +++++++++-- deploy/coolify/docker-compose.yml | 45 ++++++++++ docs/COOLIFY_DEPLOYMENT.md | 88 +++++++++++++++++++ 6 files changed, 326 insertions(+), 5 deletions(-) create mode 100644 .env.example create mode 100644 .gitea/workflows/ci.yml create mode 100644 deploy/coolify/docker-compose.yml create mode 100644 docs/COOLIFY_DEPLOYMENT.md diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..9cfc8e4 --- /dev/null +++ b/.env.example @@ -0,0 +1,10 @@ +# Global defaults +NODE_ENV=development + +# Backend service +DATABASE_URL=postgresql://merchantsofhope_user:merchantsofhope_password@merchantsofhope-supplyanddemandportal-database:5432/merchantsofhope_supplyanddemandportal +JWT_SECRET=merchantsofhope_jwt_secret_key_2024 +PORT=3001 + +# Frontend service +REACT_APP_API_URL=http://localhost:3001 diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..83a4542 --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,137 @@ +name: CI + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + backend: + name: Backend Tests + runs-on: ubuntu-latest + services: + postgres: + image: postgres:15-alpine + env: + POSTGRES_DB: merchantsofhope_test + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + ports: + - 5432:5432 + options: >- + --health-cmd="pg_isready -U postgres" --health-interval=10s --health-timeout=5s --health-retries=5 + env: + DATABASE_URL: postgresql://postgres:postgres@postgres:5432/merchantsofhope_test + JWT_SECRET: merchantsofhope_test_secret + NODE_ENV: test + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 18 + cache: npm + cache-dependency-path: backend/package-lock.json + + - name: Install dependencies + run: npm ci + working-directory: backend + + - name: Run database migrations + run: npm run migrate + working-directory: backend + + - name: Run backend tests + run: npm test -- --runInBand + working-directory: backend + + frontend: + name: Frontend Tests + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 18 + cache: npm + cache-dependency-path: frontend/package-lock.json + + - name: Install dependencies + run: npm ci + working-directory: frontend + + - name: Run frontend tests + run: npm test -- --watchAll=false + working-directory: frontend + + docker-images: + name: Build Docker Images + runs-on: ubuntu-latest + needs: [backend, frontend] + if: github.ref == 'refs/heads/main' + env: + REGISTRY_HOST: ${{ secrets.REGISTRY_HOST }} + REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }} + REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }} + IMAGE_TAG: ${{ github.sha }} + BACKEND_IMAGE: merchantsofhope-supplyanddemandportal-backend + FRONTEND_IMAGE: merchantsofhope-supplyanddemandportal-frontend + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to registry + if: env.REGISTRY_HOST != '' + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY_HOST }} + username: ${{ env.REGISTRY_USERNAME }} + password: ${{ env.REGISTRY_PASSWORD }} + + - name: Determine image tags + id: meta + run: | + if [ -n "${REGISTRY_HOST}" ]; then + echo "push=true" >> $GITHUB_OUTPUT + echo "backend_tag=${REGISTRY_HOST}/${BACKEND_IMAGE}:${IMAGE_TAG}" >> $GITHUB_OUTPUT + echo "frontend_tag=${REGISTRY_HOST}/${FRONTEND_IMAGE}:${IMAGE_TAG}" >> $GITHUB_OUTPUT + else + echo "push=false" >> $GITHUB_OUTPUT + echo "backend_tag=${BACKEND_IMAGE}:${IMAGE_TAG}" >> $GITHUB_OUTPUT + echo "frontend_tag=${FRONTEND_IMAGE}:${IMAGE_TAG}" >> $GITHUB_OUTPUT + fi + + - name: Build backend image + uses: docker/build-push-action@v5 + with: + context: backend + file: backend/Dockerfile + push: ${{ steps.meta.outputs.push == 'true' }} + tags: ${{ steps.meta.outputs.backend_tag }} + + - name: Build frontend image + uses: docker/build-push-action@v5 + with: + context: frontend + file: frontend/Dockerfile + push: ${{ steps.meta.outputs.push == 'true' }} + tags: ${{ steps.meta.outputs.frontend_tag }} + + - name: Summary + run: | + echo "Backend image tag: ${{ steps.meta.outputs.backend_tag }}" >> $GITHUB_STEP_SUMMARY + echo "Frontend image tag: ${{ steps.meta.outputs.frontend_tag }}" >> $GITHUB_STEP_SUMMARY + if [ "${{ steps.meta.outputs.push }}" = 'true' ]; then + echo "Images pushed to ${{ env.REGISTRY_HOST }}" >> $GITHUB_STEP_SUMMARY + else + echo "Images built locally (not pushed). Set REGISTRY_* secrets to enable pushing." >> $GITHUB_STEP_SUMMARY + fi diff --git a/.gitignore b/.gitignore index 6daa83e..60546a2 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ node_modules/ dist/ .env .env.* +!.env.example *.log tmp/ .DS_Store diff --git a/README.md b/README.md index df9eff7..d441c13 100644 --- a/README.md +++ b/README.md @@ -55,12 +55,18 @@ A comprehensive SAAS application for managing recruiter workflows, built with mo cd MerchantsOfHope-SupplyANdDemandPortal ``` -2. **Start the application** +2. **Copy environment template** + ```bash + cp .env.example .env + ``` + The defaults support Docker-based development. Adjust values as needed for local tooling or deployment pipelines. + +3. **Start the application with Docker (recommended for parity)** ```bash docker-compose up --build ``` -3. **Initialize the database** +4. **Initialize the database** ```bash # Run database migrations docker-compose exec merchantsofhope-supplyanddemandportal-backend npm run migrate @@ -69,11 +75,31 @@ A comprehensive SAAS application for managing recruiter workflows, built with mo docker-compose exec merchantsofhope-supplyanddemandportal-backend npm run seed ``` -4. **Access the application** +5. **Access the application** - Frontend: http://localhost:3000 - Backend API: http://localhost:3001 - Database: localhost:5432 +### Alternative: Native Node.js workflow + +If you prefer running services outside Docker: + +```bash +# Install dependencies +cd backend && npm install +cd ../frontend && npm install + +# Start backend (uses .env) +cd ../backend +npm run dev + +# In a separate terminal start frontend +cd ../frontend +npm start +``` + +Ensure a PostgreSQL instance is running and the `DATABASE_URL` in `.env` points to it. + ### Demo Accounts The application comes with pre-seeded demo accounts: @@ -140,9 +166,23 @@ docker-compose exec merchantsofhope-supplyanddemandportal-backend npm run test:w docker-compose exec merchantsofhope-supplyanddemandportal-frontend npm test ``` -## Development +To run tests without Docker, execute `npm test` inside `backend/` or `frontend/` after installing dependencies. -### Project Structure +## Continuous Integration + +Gitea Actions configuration lives in `.gitea/workflows/ci.yml`. It: +- Runs backend and frontend unit tests on every push or pull request. +- Builds Docker images on pushes to the `main` branch, ready to publish to a registry (requires `REGISTRY_*` secrets). + +See inline comments in the workflow for required secrets. + +## Deployment + +### Coolify + +Follow `docs/COOLIFY_DEPLOYMENT.md` for guidance on connecting this repository to a Coolify environment, configuring secrets, and enabling automated deploys via Gitea CI. + +## Project Structure ``` MerchantsOfHope-SupplyANdDemandPortal/ ├── backend/ diff --git a/deploy/coolify/docker-compose.yml b/deploy/coolify/docker-compose.yml new file mode 100644 index 0000000..9ff635e --- /dev/null +++ b/deploy/coolify/docker-compose.yml @@ -0,0 +1,45 @@ +version: '3.9' + +services: + merchantsofhope-supplyanddemandportal-database: + image: postgres:15-alpine + container_name: merchantsofhope-supplyanddemandportal-database + environment: + POSTGRES_DB: ${POSTGRES_DB:-merchantsofhope_supplyanddemandportal} + POSTGRES_USER: ${POSTGRES_USER:-merchantsofhope} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD} + volumes: + - merchantsofhope-supplyanddemandportal-postgres-data:/var/lib/postgresql/data + healthcheck: + test: ["CMD", "pg_isready", "-U", "${POSTGRES_USER:-merchantsofhope}"] + interval: 10s + timeout: 5s + retries: 5 + + merchantsofhope-supplyanddemandportal-backend: + image: ${BACKEND_IMAGE:?set BACKEND_IMAGE} + container_name: merchantsofhope-supplyanddemandportal-backend + depends_on: + merchantsofhope-supplyanddemandportal-database: + condition: service_healthy + environment: + NODE_ENV: production + PORT: 3001 + DATABASE_URL: postgresql://${POSTGRES_USER:-merchantsofhope}:${POSTGRES_PASSWORD}@merchantsofhope-supplyanddemandportal-database:5432/${POSTGRES_DB:-merchantsofhope_supplyanddemandportal} + JWT_SECRET: ${JWT_SECRET:?set JWT_SECRET} + ports: + - "0.0.0.0:3001:3001" + + merchantsofhope-supplyanddemandportal-frontend: + image: ${FRONTEND_IMAGE:?set FRONTEND_IMAGE} + container_name: merchantsofhope-supplyanddemandportal-frontend + depends_on: + - merchantsofhope-supplyanddemandportal-backend + environment: + PORT: 3000 + REACT_APP_API_URL: ${REACT_APP_API_URL:-http://merchantsofhope-supplyanddemandportal-backend:3001} + ports: + - "0.0.0.0:3000:3000" + +volumes: + merchantsofhope-supplyanddemandportal-postgres-data: diff --git a/docs/COOLIFY_DEPLOYMENT.md b/docs/COOLIFY_DEPLOYMENT.md new file mode 100644 index 0000000..be201da --- /dev/null +++ b/docs/COOLIFY_DEPLOYMENT.md @@ -0,0 +1,88 @@ +# Coolify Deployment Guide + +This guide summarizes how to promote MerchantsOfHope-SupplyANdDemandPortal from Gitea CI to a Coolify-managed environment. + +## Prerequisites + +- A Coolify instance with access to your container registry. +- Gitea repository hosting this project, with Gitea Actions enabled and a runner that supports Docker builds. +- Registry credentials (username, password/access token, hostname) for publishing backend and frontend images. +- Optional: a managed PostgreSQL instance. The provided Compose file creates one automatically if you do not have an external database. + +## Overview + +1. Gitea Actions builds and (optionally) pushes backend and frontend images when changes land on the `main` branch. +2. Coolify pulls those images and runs the stack defined in `deploy/coolify/docker-compose.yml`. +3. Post-deploy hooks run database migrations so the schema matches the current code. + +## Configure Gitea CI + +Secrets required by `.gitea/workflows/ci.yml`: + +| Secret | Purpose | +| ------ | ------- | +| `REGISTRY_HOST` | Hostname of the registry (e.g., `registry.example.com`). Leave blank to skip pushing. | +| `REGISTRY_USERNAME` | Registry account used to push images. | +| `REGISTRY_PASSWORD` | Token/password for the account. | + +The workflow publishes two images using the commit SHA as the tag: + +- `${REGISTRY_HOST}/merchantsofhope-supplyanddemandportal-backend:` +- `${REGISTRY_HOST}/merchantsofhope-supplyanddemandportal-frontend:` + +Expose the tag you want Coolify to deploy by either: + +- Creating a Git tag/release and configuring Coolify to deploy tags, or +- Using Coolify's "Deploy on new commit" option and translating the latest SHA into the `BACKEND_IMAGE` / `FRONTEND_IMAGE` environment variables. + +## Prepare the Coolify Stack + +1. **Add the repository** + - In Coolify, create a new *Docker Compose Application*. + - Connect your Gitea account and select this repository. + - Set the Compose path to `deploy/coolify/docker-compose.yml`. + +2. **Set environment variables** (use the UI or a `.env` file uploaded to Coolify): + + | Variable | Description | + | -------- | ----------- | + | `BACKEND_IMAGE` | Fully qualified image tag published by CI (e.g., `registry.example.com/merchantsofhope-supplyanddemandportal-backend:abcd123`). | + | `FRONTEND_IMAGE` | Fully qualified image tag published by CI. | + | `POSTGRES_DB` | Database name (defaults to `merchantsofhope_supplyanddemandportal`). | + | `POSTGRES_USER` | Database user (defaults to `merchantsofhope`). | + | `POSTGRES_PASSWORD` | **Required.** Strong password for the database. | + | `JWT_SECRET` | **Required.** Secret key for backend token signing. | + | `REACT_APP_API_URL` | URL the frontend uses to reach the backend (defaults to the internal service URL). | + + Configure any additional secrets used by your environment (mail providers, analytics, etc.). + +3. **Networking and ports** + - Expose port `3000` externally for the frontend. + - Optionally expose `3001` if you want direct API access; otherwise rely on the frontend or internal services. + - Attach the application to an HTTPS domain using Coolify's built-in proxy configuration. + +4. **Database migrations** + - Add a post-deployment command in Coolify to run `npm run migrate` inside the backend container: + ```bash + docker compose exec merchantsofhope-supplyanddemandportal-backend npm run migrate + ``` + - If you maintain seed data, run `npm run seed` the same way. + +5. **Zero-downtime considerations** + - Enable health checks in Coolify so new backend containers pass readiness before switching traffic. + - Consider scaling the backend to more than one replica once load requires it. + +## Local Development Parity + +- Use `docker-compose up --build` to replicate the production stack locally. +- Keep `.env` aligned with the variables described above so Gitea CI, local development, and Coolify deployment share the same configuration keys. + +## Troubleshooting + +| Symptom | Likely Cause | Fix | +| ------- | ------------ | --- | +| Backend container exits immediately | Missing or incorrect `DATABASE_URL`/`POSTGRES_*` values | Verify Coolify environment variables and that the database service is healthy. | +| Frontend cannot reach API | `REACT_APP_API_URL` points to an unreachable host | Adjust to Coolify-provided domain or internal service URL. | +| CI cannot push images | Registry secrets not configured or incorrect permissions | Update `REGISTRY_*` secrets and ensure the runner has network access to the registry. | + +For additional customization (e.g., connecting to an external PostgreSQL cluster or adding Redis), copy `deploy/coolify/docker-compose.yml` and extend it with your required services.