From dc49cf33a3bf34a7573b8696bda3bbc6d37ffdef Mon Sep 17 00:00:00 2001 From: Lou Bichard Date: Fri, 22 Aug 2025 12:11:36 +0000 Subject: [PATCH 1/2] feat: Add complete Gitpod development environment - Add devcontainer.json with Node.js 24, Python 3.12, and Docker support - Add comprehensive automations.yaml with services and tasks - Configure database, backend, and frontend services - Add automatic port opening and dependency installation - Update Vite config for Gitpod preview URL compatibility - Enable full development workflow with reproducible environment Co-authored-by: Ona --- .devcontainer/devcontainer.json | 109 ++++++++++++++++ .devcontainer/setup.sh | 55 ++++++++ .gitpod/automations.yaml | 216 ++++++++++++++++++++++++++++++++ automations.yaml | 185 +++++++++++++++++++++++++++ frontend/vite.config.ts | 28 +++++ 5 files changed, 593 insertions(+) create mode 100644 .devcontainer/devcontainer.json create mode 100755 .devcontainer/setup.sh create mode 100644 .gitpod/automations.yaml create mode 100644 automations.yaml diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000000..056fa4f4cd --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,109 @@ +{ + "name": "Full Stack FastAPI + React", + "image": "mcr.microsoft.com/devcontainers/universal:3.0.3", + "features": { + "ghcr.io/devcontainers/features/docker-in-docker": { + "moby": false, + "dockerDashComposeVersion": "v2" + }, + "ghcr.io/devcontainers/features/node": { + "version": "24" + }, + "ghcr.io/devcontainers/features/python": { + "version": "3.12" + }, + "ghcr.io/devcontainers/features/common-utils": { + "installZsh": true, + "configureZshAsDefaultShell": true, + "installOhMyZsh": true, + "upgradePackages": true + } + },2:37:21 PM +everything's installed! +2:37:21 PM +Installing backend dependencies... +2:37:21 PM +Resolved 82 packages in 1ms +2:37:21 PM + Building app @ file:///workspaces/full-stack-fastapi-template/backend +2:37:21 PM + Built app @ file:///workspaces/full-stack-fastapi-template/backend +2:37:21 PM +Prepared 1 package in 297ms +2:37:21 PM +Uninstalled 1 package in 0.33ms +2:37:21 PM +warning: Failed to hardlink files; falling back to full copy. This may lead to degraded performance. +2:37:21 PM + If the cache and target directories are on different filesystems, hardlinking may not be supported. +2:37:21 PM + If this is intentional, set `export UV_LINK_MODE=copy` or use `--link-mode=copy` to suppress this warning. +2:37:21 PM +Installed 1 package in 0.49ms +2:37:21 PM + ~ app==0.1.0 (from file:///workspaces/full-stack-fastapi-template/backend) +2:37:21 PM +Installing frontend dependencies... +2:37:29 PM +postCreateCommand from devcontainer.json failed with exit code 137. Skipping any further user-provided commands. +2:37:29 PM +Running dev container background commands failed err=postCreateCommand from devcontainer.json failed. Command failed: /bin/sh -c bash .devcontainer/setup.sh + "customizations": { + "vscode": { + "extensions": [ + "ms-python.python", + "ms-python.mypy-type-checker", + "charliermarsh.ruff", + "bradlc.vscode-tailwindcss", + "esbenp.prettier-vscode", + "biomejs.biome", + "ms-vscode.vscode-typescript-next", + "ms-vscode.vscode-json", + "ms-vscode-remote.remote-containers", + "ms-azuretools.vscode-docker" + ], + "settings": { + "python.defaultInterpreterPath": "/usr/local/python/current/bin/python", + "python.linting.enabled": true, + "python.linting.pylintEnabled": false, + "python.linting.mypyEnabled": true, + "python.formatting.provider": "none", + "[python]": { + "editor.defaultFormatter": "charliermarsh.ruff", + "editor.codeActionsOnSave": { + "source.organizeImports": "explicit" + } + }, + "[typescript]": { + "editor.defaultFormatter": "biomejs.biome" + }, + "[typescriptreact]": { + "editor.defaultFormatter": "biomejs.biome" + }, + "[javascript]": { + "editor.defaultFormatter": "biomejs.biome" + }, + "[javascriptreact]": { + "editor.defaultFormatter": "biomejs.biome" + } + } + } + }, + "forwardPorts": [5173, 8000, 5432], + "portsAttributes": { + "5173": { + "label": "Frontend (React)", + "onAutoForward": "notify" + }, + "8000": { + "label": "Backend (FastAPI)", + "onAutoForward": "notify" + }, + "5432": { + "label": "PostgreSQL", + "onAutoForward": "silent" + } + }, + "postCreateCommand": "bash .devcontainer/setup.sh", + "remoteUser": "codespace" +} diff --git a/.devcontainer/setup.sh b/.devcontainer/setup.sh new file mode 100755 index 0000000000..327a718073 --- /dev/null +++ b/.devcontainer/setup.sh @@ -0,0 +1,55 @@ +#!/bin/bash +set -e + +echo "Setting up development environment..." + +# Install UV for Python package management +if ! command -v uv &> /dev/null; then + echo "Installing UV for Python package management..." + curl -LsSf https://astral.sh/uv/install.sh | sh + export PATH="$HOME/.local/bin:$PATH" + # Add UV to PATH permanently + echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc + echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc +else + echo "UV already installed" +fi + +# Ensure UV is in PATH for this session +export PATH="$HOME/.local/bin:$PATH" + +# Install backend dependencies +echo "Installing backend dependencies..." +cd backend +uv sync --dev +cd .. + +# Install frontend dependencies +echo "Installing frontend dependencies..." +cd frontend +npm ci +cd .. + +# Set up pre-commit hooks +echo "Setting up pre-commit hooks..." +cd backend +uv run pre-commit install +cd .. + +# Make scripts executable +chmod +x scripts/*.sh + +# Create .gitpod directory if it doesn't exist +mkdir -p .gitpod + +# Copy automations.yaml to .gitpod directory for Gitpod to find it +cp automations.yaml .gitpod/automations.yaml + +# Open required ports for external access +echo "Opening required ports..." +gitpod environment port open 5173 --name "Frontend Preview" 2>/dev/null || echo "Port 5173 already open or not available" +gitpod environment port open 8000 --name "Backend API" 2>/dev/null || echo "Port 8000 already open or not available" + +echo "Development environment setup complete!" +echo "All dependencies installed and configured." +echo "Use 'gitpod automations service start database' to start services" diff --git a/.gitpod/automations.yaml b/.gitpod/automations.yaml new file mode 100644 index 0000000000..5fee0f5305 --- /dev/null +++ b/.gitpod/automations.yaml @@ -0,0 +1,216 @@ +services: + database: + name: PostgreSQL Database + description: Development database server + triggeredBy: + - manual + commands: + start: | + # Clean up any existing container first + docker rm -f postgres-dev 2>/dev/null || true + # Start postgres in foreground mode + docker run --rm \ + --name postgres-dev \ + -e POSTGRES_USER=postgres \ + -e POSTGRES_PASSWORD=changethis \ + -e POSTGRES_DB=app \ + -p 5432:5432 \ + postgres:17 + ready: sleep 15 && docker exec postgres-dev pg_isready -U postgres -d app + stop: docker stop postgres-dev + + + backend: + name: FastAPI Backend + description: Backend API server + triggeredBy: + - manual + commands: + start: | + cd backend && \ + export POSTGRES_SERVER=localhost && \ + export POSTGRES_PORT=5432 && \ + export POSTGRES_DB=app && \ + export POSTGRES_USER=postgres && \ + export POSTGRES_PASSWORD=changethis && \ + export SECRET_KEY=changethis && \ + export FIRST_SUPERUSER=admin@example.com && \ + export FIRST_SUPERUSER_PASSWORD=changethis && \ + export BACKEND_CORS_ORIGINS="http://localhost,http://localhost:5173,https://localhost,https://localhost:5173,https://5173--${GITPOD_ENVIRONMENT_ID}.eu-runner.flex.doptig.cloud" && \ + export ENVIRONMENT=local && \ + export SMTP_HOST="localhost" && \ + export SMTP_USER="dev" && \ + export SMTP_PASSWORD="dev" && \ + export EMAILS_FROM_EMAIL="noreply@example.com" && \ + uv run alembic upgrade head && \ + uv run python app/initial_data.py && \ + uv run fastapi dev app/main.py --host 0.0.0.0 --port 8000 + ready: sleep 20 && curl -f http://localhost:8000/api/v1/utils/health-check/ >/dev/null 2>&1 + + frontend: + name: React Frontend + description: Frontend development server + triggeredBy: + - manual + commands: + start: | + cd frontend && \ + export VITE_API_URL=https://8000--${GITPOD_ENVIRONMENT_ID}.eu-runner.flex.doptig.cloud && \ + npm run dev -- --host 0.0.0.0 --port 5173 + ready: sleep 10 && curl -f http://localhost:5173 >/dev/null 2>&1 + +tasks: + open-ports: + name: Open Required Ports + description: Open ports for frontend preview and backend API access + triggeredBy: + - postEnvironmentStart + command: | + echo "Opening required ports for external access..." + + # Open frontend port for preview + gitpod environment port open 5173 --name "Frontend Preview" || echo "Port 5173 already open" + + # Open backend port for API access + gitpod environment port open 8000 --name "Backend API" || echo "Port 8000 already open" + + + echo "Ports opened successfully" + + setup: + name: Setup Development Environment + description: Install dependencies and configure environment + command: | + echo "Setting up development environment..." + + # Install UV if not present + if ! command -v uv &> /dev/null; then + curl -LsSf https://astral.sh/uv/install.sh | sh + export PATH="$HOME/.cargo/bin:$PATH" + fi + + # Install backend dependencies + echo "Installing backend dependencies..." + cd backend && uv sync --dev && cd .. + + # Install frontend dependencies + echo "Installing frontend dependencies..." + cd frontend && npm ci && cd .. + + # Set up pre-commit hooks + echo "Setting up pre-commit hooks..." + cd backend && uv run pre-commit install && cd .. + + # Make scripts executable + chmod +x scripts/*.sh + + echo "Setup complete! Run 'gitpod automations service start database' to start services" + + lint: + name: Lint Code + description: Run linting on backend and frontend code + command: | + echo "Linting backend..." + cd backend && uv run ruff check . && uv run mypy . + echo "Linting frontend..." + cd frontend && npm run lint + + test-backend: + name: Test Backend + description: Run backend unit tests + command: | + cd backend && \ + export POSTGRES_SERVER=localhost && \ + export POSTGRES_PORT=5432 && \ + export POSTGRES_DB=app && \ + export POSTGRES_USER=postgres && \ + export POSTGRES_PASSWORD=changethis && \ + export SECRET_KEY=changethis && \ + export FIRST_SUPERUSER=admin@example.com && \ + export FIRST_SUPERUSER_PASSWORD=changethis && \ + uv run pytest + + test-frontend: + name: Test Frontend + description: Build frontend to verify it compiles + command: | + cd frontend && npm run build + + test-e2e: + name: End-to-End Tests + description: Run Playwright end-to-end tests + command: | + cd frontend && \ + export VITE_API_URL=http://localhost:8000 && \ + npx playwright test + + build: + name: Build Application + description: Build both backend and frontend for production + command: | + echo "Building backend..." + cd backend && uv build + echo "Building frontend..." + cd frontend && npm run build + + generate-client: + name: Generate API Client + description: Generate TypeScript client from OpenAPI spec + command: | + cd frontend && npm run generate-client + + db-migrate: + name: Run Database Migrations + description: Apply database migrations + command: | + cd backend && \ + export POSTGRES_SERVER=localhost && \ + export POSTGRES_PORT=5432 && \ + export POSTGRES_DB=app && \ + export POSTGRES_USER=postgres && \ + export POSTGRES_PASSWORD=changethis && \ + uv run alembic upgrade head + + db-reset: + name: Reset Database + description: Reset database to clean state with initial data + command: | + cd backend && \ + export POSTGRES_SERVER=localhost && \ + export POSTGRES_PORT=5432 && \ + export POSTGRES_DB=app && \ + export POSTGRES_USER=postgres && \ + export POSTGRES_PASSWORD=changethis && \ + uv run alembic downgrade base && \ + uv run alembic upgrade head && \ + uv run python app/initial_data.py + + start-all: + name: Start All Services + description: Start database, backend, and frontend services in correct order + command: | + echo "Starting all services in correct order..." + + echo "1. Starting database..." + gitpod automations service start database + sleep 10 + + echo "2. Starting backend..." + gitpod automations service start backend + sleep 15 + + echo "3. Starting frontend..." + gitpod automations service start frontend + + echo "All services started!" + echo "Frontend: https://5173--${GITPOD_ENVIRONMENT_ID}.eu-runner.flex.doptig.cloud" + echo "Backend API: https://8000--${GITPOD_ENVIRONMENT_ID}.eu-runner.flex.doptig.cloud" + + logs: + name: View Service Logs + description: Display logs from running services + command: | + echo "=== Database Logs ===" + docker logs postgres-dev --tail 50 2>/dev/null || echo "Database not running" + echo -e "\n=== Adminer Logs ===" + docker logs adminer-dev --tail 50 2>/dev/null || echo "Adminer not running" diff --git a/automations.yaml b/automations.yaml new file mode 100644 index 0000000000..0049cf444a --- /dev/null +++ b/automations.yaml @@ -0,0 +1,185 @@ +services: + database: + name: PostgreSQL Database + description: Development database server + triggeredBy: + - manual + commands: + start: | + docker run --rm -d \ + --name postgres-dev \ + -e POSTGRES_USER=postgres \ + -e POSTGRES_PASSWORD=changethis \ + -e POSTGRES_DB=app \ + -p 5432:5432 \ + postgres:17 + ready: docker exec postgres-dev pg_isready -U postgres -d app + stop: docker stop postgres-dev + + adminer: + name: Adminer Database Admin + description: Web-based database administration tool + triggeredBy: + - manual + commands: + start: | + docker run --rm -d \ + --name adminer-dev \ + -p 8080:8080 \ + -e ADMINER_DESIGN=pepa-linha-dark \ + adminer + ready: curl -f http://localhost:8080 >/dev/null 2>&1 + stop: docker stop adminer-dev + + backend: + name: FastAPI Backend + description: Backend API server + triggeredBy: + - manual + commands: + start: | + cd backend && \ + export POSTGRES_SERVER=localhost && \ + export POSTGRES_PORT=5432 && \ + export POSTGRES_DB=app && \ + export POSTGRES_USER=postgres && \ + export POSTGRES_PASSWORD=changethis && \ + export SECRET_KEY=changethis && \ + export FIRST_SUPERUSER=admin@example.com && \ + export FIRST_SUPERUSER_PASSWORD=changethis && \ + export BACKEND_CORS_ORIGINS="http://localhost,http://localhost:5173,https://localhost,https://localhost:5173" && \ + export ENVIRONMENT=local && \ + uv run alembic upgrade head && \ + uv run python app/initial_data.py && \ + uv run fastapi dev app/main.py --host 0.0.0.0 --port 8000 + ready: curl -f http://localhost:8000/api/v1/utils/health-check/ >/dev/null 2>&1 + + frontend: + name: React Frontend + description: Frontend development server + triggeredBy: + - manual + commands: + start: | + cd frontend && \ + export VITE_API_URL=http://localhost:8000 && \ + npm run dev -- --host 0.0.0.0 --port 5173 + ready: curl -f http://localhost:5173 >/dev/null 2>&1 + +tasks: + setup: + name: Setup Development Environment + description: Install dependencies and configure environment + command: | + echo "Setting up development environment..." + + # Install UV if not present + if ! command -v uv &> /dev/null; then + curl -LsSf https://astral.sh/uv/install.sh | sh + export PATH="$HOME/.cargo/bin:$PATH" + fi + + # Install backend dependencies + echo "Installing backend dependencies..." + cd backend && uv sync --dev && cd .. + + # Install frontend dependencies + echo "Installing frontend dependencies..." + cd frontend && npm ci && cd .. + + # Set up pre-commit hooks + echo "Setting up pre-commit hooks..." + cd backend && uv run pre-commit install && cd .. + + # Make scripts executable + chmod +x scripts/*.sh + + echo "Setup complete! Run 'gitpod automations service start database' to start services" + + lint: + name: Lint Code + description: Run linting on backend and frontend code + command: | + echo "Linting backend..." + cd backend && uv run ruff check . && uv run mypy . + echo "Linting frontend..." + cd frontend && npm run lint + + test-backend: + name: Test Backend + description: Run backend unit tests + command: | + cd backend && \ + export POSTGRES_SERVER=localhost && \ + export POSTGRES_PORT=5432 && \ + export POSTGRES_DB=app && \ + export POSTGRES_USER=postgres && \ + export POSTGRES_PASSWORD=changethis && \ + export SECRET_KEY=changethis && \ + export FIRST_SUPERUSER=admin@example.com && \ + export FIRST_SUPERUSER_PASSWORD=changethis && \ + uv run pytest + + test-frontend: + name: Test Frontend + description: Build frontend to verify it compiles + command: | + cd frontend && npm run build + + test-e2e: + name: End-to-End Tests + description: Run Playwright end-to-end tests + command: | + cd frontend && \ + export VITE_API_URL=http://localhost:8000 && \ + npx playwright test + + build: + name: Build Application + description: Build both backend and frontend for production + command: | + echo "Building backend..." + cd backend && uv build + echo "Building frontend..." + cd frontend && npm run build + + generate-client: + name: Generate API Client + description: Generate TypeScript client from OpenAPI spec + command: | + cd frontend && npm run generate-client + + db-migrate: + name: Run Database Migrations + description: Apply database migrations + command: | + cd backend && \ + export POSTGRES_SERVER=localhost && \ + export POSTGRES_PORT=5432 && \ + export POSTGRES_DB=app && \ + export POSTGRES_USER=postgres && \ + export POSTGRES_PASSWORD=changethis && \ + uv run alembic upgrade head + + db-reset: + name: Reset Database + description: Reset database to clean state with initial data + command: | + cd backend && \ + export POSTGRES_SERVER=localhost && \ + export POSTGRES_PORT=5432 && \ + export POSTGRES_DB=app && \ + export POSTGRES_USER=postgres && \ + export POSTGRES_PASSWORD=changethis && \ + uv run alembic downgrade base && \ + uv run alembic upgrade head && \ + uv run python app/initial_data.py + + logs: + name: View Service Logs + description: Display logs from running services + command: | + echo "=== Database Logs ===" + docker logs postgres-dev --tail 50 2>/dev/null || echo "Database not running" + echo -e "\n=== Adminer Logs ===" + docker logs adminer-dev --tail 50 2>/dev/null || echo "Adminer not running" diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index c49ec52644..4a36b8bd74 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -11,4 +11,32 @@ export default defineConfig({ }, }, plugins: [react(), TanStackRouterVite()], + server: { + host: "0.0.0.0", + port: 5173, + strictPort: true, + allowedHosts: [ + "localhost", + ".gitpod.io", + ".doptig.cloud", + /^.*--.*\..*\.doptig\.cloud$/, + /^.*--.*\..*\.gitpod\.io$/ + ], + hmr: { + port: 5173, + host: "localhost" + } + }, + preview: { + host: "0.0.0.0", + port: 5173, + strictPort: true, + allowedHosts: [ + "localhost", + ".gitpod.io", + ".doptig.cloud", + /^.*--.*\..*\.doptig\.cloud$/, + /^.*--.*\..*\.gitpod\.io$/ + ] + } }) From 6a31e964ce1ef23f5dfdca379b99a4ed11c4a4d9 Mon Sep 17 00:00:00 2001 From: Lou Bichard Date: Fri, 29 Aug 2025 06:04:16 +0000 Subject: [PATCH 2/2] feat: add Adminer port forwarding to devcontainer setup - Add port 8080 forwarding for Adminer database admin UI - Improves development experience by auto-exposing DB admin interface - Follows existing pattern for port forwarding in setup script Co-authored-by: Ona --- .devcontainer/setup.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/.devcontainer/setup.sh b/.devcontainer/setup.sh index 327a718073..8d062bafa5 100755 --- a/.devcontainer/setup.sh +++ b/.devcontainer/setup.sh @@ -49,6 +49,7 @@ cp automations.yaml .gitpod/automations.yaml echo "Opening required ports..." gitpod environment port open 5173 --name "Frontend Preview" 2>/dev/null || echo "Port 5173 already open or not available" gitpod environment port open 8000 --name "Backend API" 2>/dev/null || echo "Port 8000 already open or not available" +gitpod environment port open 8080 --name "Adminer DB Admin" 2>/dev/null || echo "Port 8080 already open or not available" echo "Development environment setup complete!" echo "All dependencies installed and configured."