|
| 1 | +flowchart TB |
| 2 | + %% ============================================ |
| 3 | + %% Internet & Client Layer |
| 4 | + %% ============================================ |
| 5 | + CLIENT["🌐 Client Browser"] |
| 6 | + DOMAIN["🔗 city-pulse.app<br/>SSL/TLS (Let's Encrypt)"] |
| 7 | + |
| 8 | + %% ============================================ |
| 9 | + %% CI/CD Pipeline (GitHub Actions) |
| 10 | + %% ============================================ |
| 11 | + subgraph CICD["⚡ GitHub Actions CI/CD Pipeline"] |
| 12 | + direction TB |
| 13 | + |
| 14 | + TRIGGER["🔔 Triggers<br/>• Push: main, production<br/>• PR: main"] |
| 15 | + |
| 16 | + subgraph JOB1["🧪 Job 1: Test & Quality"] |
| 17 | + direction LR |
| 18 | + T1["📦 Checkout<br/>actions/checkout@v4"] |
| 19 | + T2["🔧 Setup Node 20<br/>pnpm 10.10.0"] |
| 20 | + T3["📥 Install deps<br/>pnpm install"] |
| 21 | + T4["🔍 Lint<br/>ESLint + Prettier"] |
| 22 | + T5["🏗️ Build<br/>TypeScript + Vite"] |
| 23 | + end |
| 24 | + |
| 25 | + subgraph JOB2["🐳 Job 2: Build Images"] |
| 26 | + direction LR |
| 27 | + B1["🔡 Prepare names<br/>REPO_LOWER"] |
| 28 | + B2["🏗️ Docker Buildx<br/>linux/amd64"] |
| 29 | + B3["🔐 Login GHCR<br/>ghcr.io"] |
| 30 | + B4["📦 Build Backend<br/>backend/Dockerfile.prod"] |
| 31 | + B5["📦 Build Frontend<br/>frontend/Dockerfile.prod"] |
| 32 | + B6["⬆️ Push to GHCR<br/>ghcr.io/citypulse-backend:v1.2.3<br/>ghcr.io/citypulse-frontend:v1.2.3"] |
| 33 | + end |
| 34 | + |
| 35 | + subgraph JOB3["🚀 Job 3: Deploy"] |
| 36 | + direction LR |
| 37 | + D1["📁 Create package<br/>.env.prod + docker-compose.prod.yml"] |
| 38 | + D2["📦 Tar archive<br/>deploy-v1.2.3.tar.gz"] |
| 39 | + D3["📤 SCP Upload<br/>→ /opt/citypulse"] |
| 40 | + D4["🔐 SSH Deploy<br/>root@[Production Droplet]"] |
| 41 | + D5["🐳 Docker compose up<br/>--env-file .env.prod"] |
| 42 | + D6["✅ Health check<br/>timeout 300s"] |
| 43 | + end |
| 44 | + |
| 45 | + JOB4["📢 Job 4: Notify<br/>✅ city-pulse.app/health"] |
| 46 | + |
| 47 | + TRIGGER --> T1 |
| 48 | + T1 --> T2 --> T3 --> T4 --> T5 |
| 49 | + T5 -.-> B1 |
| 50 | + B1 --> B2 --> B3 --> B4 --> B5 --> B6 |
| 51 | + B6 -.-> D1 |
| 52 | + D1 --> D2 --> D3 --> D4 --> D5 --> D6 |
| 53 | + D6 -.-> JOB4 |
| 54 | + end |
| 55 | + |
| 56 | + %% ============================================ |
| 57 | + %% DigitalOcean Droplet Infrastructure |
| 58 | + %% ============================================ |
| 59 | + subgraph DROPLET["🟦 DigitalOcean Droplet<br/>Production Server • /opt/citypulse"] |
| 60 | + direction TB |
| 61 | + |
| 62 | + subgraph NGINX_LAYER["🔀 Nginx Reverse Proxy"] |
| 63 | + NGINX["citypulse-nginx-prod<br/>nginx:1.25-alpine<br/>Ports: 80, 443<br/>nginx-domain.conf<br/>Rate Limit: 10r/s"] |
| 64 | + end |
| 65 | + |
| 66 | + subgraph DOCKER_NET["🔗 Docker Network: citypulse-network"] |
| 67 | + direction TB |
| 68 | + |
| 69 | + subgraph APP_SERVICES["🎯 Application Services"] |
| 70 | + direction LR |
| 71 | + |
| 72 | + BACKEND["🔧 citypulse-backend-prod<br/>ghcr.io/citypulse-backend:v1.2.3<br/>Port: 5000<br/>JWT + WebSocket<br/>Health: /health"] |
| 73 | + |
| 74 | + FRONTEND["⚛️ citypulse-frontend-prod<br/>ghcr.io/citypulse-frontend:v1.2.3<br/>Port: 3001→80<br/>Vite + Tailwind<br/>Health: localhost:80"] |
| 75 | + end |
| 76 | + |
| 77 | + POSTGRES["🗄️ citypulse-postgres-prod<br/>postgres:15-alpine<br/>Port: 5432<br/>21+ Tables<br/>Health: pg_isready"] |
| 78 | + end |
| 79 | + |
| 80 | + subgraph VOLUMES["💾 Persistent Volumes"] |
| 81 | + VOL1["postgres_data"] |
| 82 | + VOL2["uploads_data"] |
| 83 | + VOL3["letsencrypt"] |
| 84 | + end |
| 85 | + |
| 86 | + NGINX --> BACKEND |
| 87 | + NGINX --> FRONTEND |
| 88 | + BACKEND <--> POSTGRES |
| 89 | + FRONTEND -.depends.-> BACKEND |
| 90 | + POSTGRES -.mount.-> VOL1 |
| 91 | + BACKEND -.mount.-> VOL2 |
| 92 | + NGINX -.mount.-> VOL3 |
| 93 | + end |
| 94 | + |
| 95 | + %% ============================================ |
| 96 | + %% External Services |
| 97 | + %% ============================================ |
| 98 | + subgraph EXTERNAL["🌍 External Services"] |
| 99 | + direction TB |
| 100 | + GOOGLE["🔐 Google OAuth"] |
| 101 | + EMAIL["📧 SendGrid API"] |
| 102 | + GHCR["📦 GitHub Container Registry<br/>ghcr.io/citypulse-*"] |
| 103 | + end |
| 104 | + |
| 105 | + %% ============================================ |
| 106 | + %% Main Flow Connections |
| 107 | + %% ============================================ |
| 108 | + CLIENT -->|HTTPS| DOMAIN |
| 109 | + DOMAIN -->|443| NGINX |
| 110 | + BACKEND -->|OAuth| GOOGLE |
| 111 | + BACKEND -->|Emails| EMAIL |
| 112 | + |
| 113 | + D4 -.SSH.-> DROPLET |
| 114 | + GHCR -.docker-pull.-> DOCKER_NET |
| 115 | + JOB2 -.images.-> GHCR |
| 116 | + |
| 117 | + %% ============================================ |
| 118 | + %% Environment & Health |
| 119 | + %% ============================================ |
| 120 | + ENVVARS["⚙️ .env.prod<br/>JWT_SECRET • POSTGRES_*<br/>GOOGLE_* • EMAIL_*"] |
| 121 | + HC["❤️ Health Dashboard<br/>pg_isready • /health<br/>wget localhost • nginx"] |
| 122 | + |
| 123 | + ENVVARS -.inject.-> BACKEND |
| 124 | + ENVVARS -.inject.-> FRONTEND |
| 125 | + ENVVARS -.inject.-> POSTGRES |
| 126 | + HC -.monitor.-> POSTGRES |
| 127 | + HC -.monitor.-> BACKEND |
| 128 | + HC -.monitor.-> FRONTEND |
| 129 | + HC -.monitor.-> NGINX |
| 130 | + |
| 131 | + %% ============================================ |
| 132 | + %% Production-Grade Color Scheme (High Contrast) |
| 133 | + %% ============================================ |
| 134 | + style CLIENT fill:#dbeafe,stroke:#1e40af,stroke-width:3px |
| 135 | + style DOMAIN fill:#fef3c7,stroke:#b45309,stroke-width:3px |
| 136 | + style CICD fill:#f8fafc,stroke:#1e293b,stroke-width:3px |
| 137 | + style DROPLET fill:#fefce8,stroke:#a16207,stroke-width:4px |
| 138 | + style NGINX_LAYER fill:#dcfce7,stroke:#166534,stroke-width:2px |
| 139 | + style DOCKER_NET fill:#eff6ff,stroke:#1d4ed8,stroke-width:2px |
| 140 | + style APP_SERVICES fill:#fef3c7,stroke:#b45309,stroke-width:2px |
| 141 | + style VOLUMES fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px |
| 142 | + style EXTERNAL fill:#fecaca,stroke:#b91c1c,stroke-width:2px |
| 143 | + |
| 144 | + style NGINX fill:#10b981,stroke:#047857,stroke-width:3px,color:#000 |
| 145 | + style BACKEND fill:#f97316,stroke:#c2410c,stroke-width:3px,color:#fff |
| 146 | + style FRONTEND fill:#3b82f6,stroke:#1d4ed8,stroke-width:3px,color:#fff |
| 147 | + style POSTGRES fill:#8b5cf6,stroke:#6d28d9,stroke-width:3px,color:#fff |
| 148 | + |
| 149 | + style GOOGLE fill:#ef4444,stroke:#dc2626,stroke-width:2px,color:#fff |
| 150 | + style EMAIL fill:#10b981,stroke:#047857,stroke-width:2px,color:#fff |
| 151 | + style GHCR fill:#1f2937,stroke:#111827,stroke-width:2px,color:#fff |
| 152 | + |
| 153 | + style JOB1 fill:#dcfce7,stroke:#166534,stroke-width:2px |
| 154 | + style JOB2 fill:#eff6ff,stroke:#1d4ed8,stroke-width:2px |
| 155 | + style JOB3 fill:#fef3c7,stroke:#b45309,stroke-width:2px |
| 156 | + style JOB4 fill:#10b981,stroke:#047857,stroke-width:2px,color:#fff |
| 157 | + |
| 158 | + style ENVVARS fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px |
| 159 | + style HC fill:#fecaca,stroke:#b91c1c,stroke-width:2px |
0 commit comments