[FA-11] Cleanup
All checks were successful
CI / build-backend (pull_request) Successful in 1m13s
CI / build-frontend (pull_request) Successful in 26s

This commit is contained in:
gamer147
2025-11-26 16:08:40 -05:00
parent 4635ed1b4e
commit 09ebdb1b2a
3 changed files with 147 additions and 99 deletions

View File

@@ -3,15 +3,8 @@ name: Build Gateway
on: on:
workflow_dispatch: workflow_dispatch:
push: push:
branches:
- master
tags: tags:
- 'v*.*.*' - 'v*.*.*'
paths:
- 'FictionArchive.Service.*/**'
- 'FictionArchive.Common/**'
- 'FictionArchive.Service.Shared/**'
- 'FictionArchive.API/**'
env: env:
REGISTRY: ${{ gitea.server_url }} REGISTRY: ${{ gitea.server_url }}

View File

@@ -7,9 +7,9 @@ This document describes the CI/CD pipeline configuration for FictionArchive usin
| Workflow | File | Trigger | Purpose | | Workflow | File | Trigger | Purpose |
|----------|------|---------|---------| |----------|------|---------|---------|
| CI | `build.yml` | Push/PR to master | Build and test all projects | | CI | `build.yml` | Push/PR to master | Build and test all projects |
| Build Subgraphs | `build-subgraphs.yml` | Push to master (service changes) | Build GraphQL subgraph packages | | Build Gateway | `build-gateway.yml` | Tag `v*.*.*` or manual | Build subgraphs, compose gateway, push API image |
| Build Gateway | `build-gateway.yml` | Manual or triggered by subgraphs | Compose gateway and build Docker image |
| Release | `release.yml` | Tag `v*.*.*` | Build and push all Docker images | | Release | `release.yml` | Tag `v*.*.*` | Build and push all Docker images |
| Claude PR Assistant | `claude_assistant.yml` | Issue/PR comments with @claude | AI-assisted code review and issue handling |
## Pipeline Architecture ## Pipeline Architecture
@@ -18,27 +18,32 @@ This document describes the CI/CD pipeline configuration for FictionArchive usin
│ Push to master │ │ Push to master │
└─────────────────────────────┬───────────────────────────────────────┘ └─────────────────────────────┬───────────────────────────────────────┘
┌───────────────┴───────────────┐
▼ ▼ ┌─────────────────────────┐
┌─────────────────────────┐ ┌─────────────────────────┐ │ build.yml │
build.yml│ build-subgraphs.yml (CI checks)
(CI checks - always) (if service changes) │ └─────────────────────────┘
└─────────────────────────┘ └────────────┬────────────┘
┌─────────────────────────┐
│ build-gateway.yml │
│ (compose & push API) │
└─────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────────────────────────┐
│ Push tag v*.*.* │ │ Push tag v*.*.* │
└─────────────────────────────┬───────────────────────────────────────┘
┌───────────────┴───────────────┐
▼ ▼
┌─────────────────────────┐ ┌─────────────────────────┐
│ release.yml │ │ build-gateway.yml │
│ (build & push all │ │ (build subgraphs & │
│ backend + frontend) │ │ push API gateway) │
└─────────────────────────┘ └─────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ Issue/PR comment containing @claude │
└─────────────────────────────┬───────────────────────────────────────┘ └─────────────────────────────┬───────────────────────────────────────┘
┌─────────────────────────┐ ┌─────────────────────────┐
release.yml claude_assistant.yml
│ (build & push all) │ (AI code assistance)
└─────────────────────────┘ └─────────────────────────┘
``` ```
@@ -51,14 +56,15 @@ Configure these in **Settings → Actions → Secrets**:
| Secret | Description | Required By | | Secret | Description | Required By |
|--------|-------------|-------------| |--------|-------------|-------------|
| `REGISTRY_TOKEN` | Gitea access token with `write:package` scope | `release.yml`, `build-gateway.yml` | | `REGISTRY_TOKEN` | Gitea access token with `write:package` scope | `release.yml`, `build-gateway.yml` |
| `GITEA_TOKEN` | Gitea access token for API calls | `build-subgraphs.yml` | | `CLAUDE_CODE_OAUTH_TOKEN` | Claude Code OAuth token | `claude_assistant.yml` |
| `CLAUDE_GITEA_TOKEN` | Gitea token for Claude assistant | `claude_assistant.yml` |
#### Creating Access Tokens #### Creating Access Tokens
1. Go to **Settings → Applications → Access Tokens** 1. Go to **Settings → Applications → Access Tokens**
2. Create a new token with the following scopes: 2. Create a new token with the following scopes:
- `write:package` - Push container images - `write:package` - Push container images
- `write:repository` - Trigger workflows via API - `write:repository` - For Claude assistant to push commits
3. Copy the token and add it as a repository secret 3. Copy the token and add it as a repository secret
### Repository Variables ### Repository Variables
@@ -85,42 +91,62 @@ Configure these in **Settings → Actions → Variables**:
**Requirements:** **Requirements:**
- .NET 8.0 SDK - .NET 8.0 SDK
- Python 3.12
- Node.js 20 - Node.js 20
- HotChocolate Fusion CLI
### Build Subgraphs (`build-subgraphs.yml`) **Steps (Backend):**
1. Checkout repository
2. Setup .NET 8.0
3. Restore dependencies
4. Build solution (Release, with `SkipFusionBuild=true`)
5. Run tests
**Trigger:** Push to `master` with changes in: **Steps (Frontend):**
- `FictionArchive.Service.*/**` 1. Checkout repository
- `FictionArchive.Common/**` 2. Setup Node.js 20
- `FictionArchive.Service.Shared/**` 3. Install dependencies (`npm ci`)
4. Run linter (`npm run lint`)
**Jobs:** 5. Build application (`npm run build`)
1. `build-subgraphs` - Matrix job building each service's `.fsp` package
2. `trigger-gateway` - Triggers gateway rebuild via API
**Subgraphs Built:**
- Novel Service
- Translation Service
- Scheduler Service
- User Service
- File Service
**Artifacts:** Each subgraph produces a `.fsp` file retained for 30 days.
### Build Gateway (`build-gateway.yml`) ### Build Gateway (`build-gateway.yml`)
**Trigger:** **Trigger:**
- Manual dispatch (`workflow_dispatch`) - Manual dispatch (`workflow_dispatch`)
- Push to `master` with changes in `FictionArchive.API/**` - Push tag matching `v*.*.*`
- Triggered by `build-subgraphs.yml` completion
**Process:** **Jobs:**
1. Downloads all subgraph `.fsp` artifacts
2. Configures Docker-internal URLs for each subgraph #### 1. `build-subgraphs` (Matrix Job)
3. Composes gateway schema using Fusion CLI Builds GraphQL subgraph packages for each service:
4. Builds and pushes API Docker image
| Service | Project | Subgraph Name |
|---------|---------|---------------|
| novel-service | FictionArchive.Service.NovelService | Novel |
| translation-service | FictionArchive.Service.TranslationService | Translation |
| scheduler-service | FictionArchive.Service.SchedulerService | Scheduler |
| user-service | FictionArchive.Service.UserService | User |
**Note:** File Service and Authentication Service are not subgraphs (no GraphQL schema).
**Steps:**
1. Checkout repository
2. Setup .NET 8.0
3. Install HotChocolate Fusion CLI
4. Restore and build service project
5. Export GraphQL schema (`schema export`)
6. Pack subgraph into `.fsp` file
7. Upload artifact (retained 30 days)
#### 2. `build-gateway` (Depends on `build-subgraphs`)
Composes the API gateway from subgraph packages.
**Steps:**
1. Checkout repository
2. Setup .NET 8.0 and Fusion CLI
3. Download all subgraph artifacts
4. Configure Docker-internal URLs (`http://{service}-service:8080/graphql`)
5. Compose gateway schema using Fusion CLI
6. Build gateway project
7. Build and push Docker image
**Image Tags:** **Image Tags:**
- `<registry>/<owner>/fictionarchive-api:latest` - `<registry>/<owner>/fictionarchive-api:latest`
@@ -131,23 +157,54 @@ Configure these in **Settings → Actions → Variables**:
**Trigger:** Push tag matching `v*.*.*` (e.g., `v1.0.0`) **Trigger:** Push tag matching `v*.*.*` (e.g., `v1.0.0`)
**Jobs:** **Jobs:**
1. `build-and-push` - Matrix job building all backend service images
2. `build-frontend` - Builds and pushes frontend image
**Services Built:** #### 1. `build-and-push` (Matrix Job)
- `fictionarchive-api` Builds and pushes all backend service images:
- `fictionarchive-novel-service`
- `fictionarchive-user-service` | Service | Dockerfile |
- `fictionarchive-translation-service` |---------|------------|
- `fictionarchive-file-service` | novel-service | FictionArchive.Service.NovelService/Dockerfile |
- `fictionarchive-scheduler-service` | user-service | FictionArchive.Service.UserService/Dockerfile |
- `fictionarchive-authentication-service` | translation-service | FictionArchive.Service.TranslationService/Dockerfile |
- `fictionarchive-frontend` | file-service | FictionArchive.Service.FileService/Dockerfile |
| scheduler-service | FictionArchive.Service.SchedulerService/Dockerfile |
| authentication-service | FictionArchive.Service.AuthenticationService/Dockerfile |
#### 2. `build-frontend`
Builds and pushes the frontend image with environment-specific build arguments.
**Build Args:**
- `VITE_GRAPHQL_URI`
- `VITE_OIDC_AUTHORITY`
- `VITE_OIDC_CLIENT_ID`
- `VITE_OIDC_REDIRECT_URI`
- `VITE_OIDC_POST_LOGOUT_REDIRECT_URI`
**Image Tags:** **Image Tags:**
- `<registry>/<owner>/fictionarchive-<service>:<version>` - `<registry>/<owner>/fictionarchive-<service>:<version>`
- `<registry>/<owner>/fictionarchive-<service>:latest` - `<registry>/<owner>/fictionarchive-<service>:latest`
### Claude PR Assistant (`claude_assistant.yml`)
**Trigger:** Comments or issues containing `@claude`:
- Issue comments
- Pull request review comments
- Pull request reviews
- New issues (opened or assigned)
**Permissions Required:**
- `contents: write`
- `pull-requests: write`
- `issues: write`
- `id-token: write`
**Usage:**
Mention `@claude` in any issue or PR comment to invoke the AI assistant for:
- Code review assistance
- Bug analysis
- Implementation suggestions
- Documentation help
## Container Registry ## Container Registry
Images are pushed to the Gitea Container Registry at: Images are pushed to the Gitea Container Registry at:
@@ -155,6 +212,19 @@ Images are pushed to the Gitea Container Registry at:
<gitea-server-url>/<repository-owner>/fictionarchive-<service>:<tag> <gitea-server-url>/<repository-owner>/fictionarchive-<service>:<tag>
``` ```
### Image Naming Convention
| Image | Description |
|-------|-------------|
| `fictionarchive-api` | API Gateway (GraphQL Federation) |
| `fictionarchive-novel-service` | Novel Service |
| `fictionarchive-user-service` | User Service |
| `fictionarchive-translation-service` | Translation Service |
| `fictionarchive-file-service` | File Service |
| `fictionarchive-scheduler-service` | Scheduler Service |
| `fictionarchive-authentication-service` | Authentication Service |
| `fictionarchive-frontend` | Web Frontend |
### Pulling Images ### Pulling Images
```bash ```bash
@@ -184,13 +254,13 @@ docker pull <gitea-server-url>/<owner>/fictionarchive-api:latest
- Ensure the `REGISTRY_TOKEN` secret is configured in repository settings - Ensure the `REGISTRY_TOKEN` secret is configured in repository settings
- Verify the token has `write:package` scope - Verify the token has `write:package` scope
**"Failed to trigger gateway workflow"**
- Ensure `GITEA_TOKEN` secret is configured
- Verify the token has `write:repository` scope
**"No subgraph artifacts found"** **"No subgraph artifacts found"**
- The gateway build requires subgraph artifacts from a previous `build-subgraphs` run - The gateway build requires subgraph artifacts from the `build-subgraphs` job
- Trigger `build-subgraphs.yml` manually or push a change to a service - If subgraph builds failed, check the matrix job logs for errors
**"Schema export failed"**
- Ensure the service project has a valid `subgraph-config.json`
- Check that the service starts correctly for schema export
### Frontend Build Failures ### Frontend Build Failures
@@ -204,6 +274,13 @@ docker pull <gitea-server-url>/<owner>/fictionarchive-api:latest
- Verify `REGISTRY_TOKEN` has correct permissions - Verify `REGISTRY_TOKEN` has correct permissions
- Check that the token hasn't expired - Check that the token hasn't expired
### Claude Assistant Failures
**"Claude assistant not responding"**
- Verify `CLAUDE_CODE_OAUTH_TOKEN` is configured
- Verify `CLAUDE_GITEA_TOKEN` is configured and has write permissions
- Check that the comment contains `@claude` mention
## Local Testing ## Local Testing
To test workflows locally before pushing: To test workflows locally before pushing:

View File

@@ -34,9 +34,7 @@ services:
# Backend Services # Backend Services
# =========================================== # ===========================================
novel-service: novel-service:
build: image: git.orfl.xyz/conco/fictionarchive-novel-service:latest
context: .
dockerfile: FictionArchive.Service.NovelService/Dockerfile
environment: environment:
ConnectionStrings__DefaultConnection: Host=postgres;Database=FictionArchive_NovelService;Username=${POSTGRES_USER:-postgres};Password=${POSTGRES_PASSWORD:-postgres} ConnectionStrings__DefaultConnection: Host=postgres;Database=FictionArchive_NovelService;Username=${POSTGRES_USER:-postgres};Password=${POSTGRES_PASSWORD:-postgres}
ConnectionStrings__RabbitMQ: amqp://${RABBITMQ_USER:-guest}:${RABBITMQ_PASSWORD:-guest}@rabbitmq ConnectionStrings__RabbitMQ: amqp://${RABBITMQ_USER:-guest}:${RABBITMQ_PASSWORD:-guest}@rabbitmq
@@ -51,9 +49,7 @@ services:
restart: unless-stopped restart: unless-stopped
translation-service: translation-service:
build: image: git.orfl.xyz/conco/fictionarchive-translation-service:latest
context: .
dockerfile: FictionArchive.Service.TranslationService/Dockerfile
environment: environment:
ConnectionStrings__DefaultConnection: Host=postgres;Database=FictionArchive_TranslationService;Username=${POSTGRES_USER:-postgres};Password=${POSTGRES_PASSWORD:-postgres} ConnectionStrings__DefaultConnection: Host=postgres;Database=FictionArchive_TranslationService;Username=${POSTGRES_USER:-postgres};Password=${POSTGRES_PASSWORD:-postgres}
ConnectionStrings__RabbitMQ: amqp://${RABBITMQ_USER:-guest}:${RABBITMQ_PASSWORD:-guest}@rabbitmq ConnectionStrings__RabbitMQ: amqp://${RABBITMQ_USER:-guest}:${RABBITMQ_PASSWORD:-guest}@rabbitmq
@@ -66,9 +62,7 @@ services:
restart: unless-stopped restart: unless-stopped
scheduler-service: scheduler-service:
build: image: git.orfl.xyz/conco/fictionarchive-scheduler-service:latest
context: .
dockerfile: FictionArchive.Service.SchedulerService/Dockerfile
environment: environment:
ConnectionStrings__DefaultConnection: Host=postgres;Database=FictionArchive_SchedulerService;Username=${POSTGRES_USER:-postgres};Password=${POSTGRES_PASSWORD:-postgres} ConnectionStrings__DefaultConnection: Host=postgres;Database=FictionArchive_SchedulerService;Username=${POSTGRES_USER:-postgres};Password=${POSTGRES_PASSWORD:-postgres}
ConnectionStrings__RabbitMQ: amqp://${RABBITMQ_USER:-guest}:${RABBITMQ_PASSWORD:-guest}@rabbitmq ConnectionStrings__RabbitMQ: amqp://${RABBITMQ_USER:-guest}:${RABBITMQ_PASSWORD:-guest}@rabbitmq
@@ -80,9 +74,7 @@ services:
restart: unless-stopped restart: unless-stopped
user-service: user-service:
build: image: git.orfl.xyz/conco/fictionarchive-user-service:latest
context: .
dockerfile: FictionArchive.Service.UserService/Dockerfile
environment: environment:
ConnectionStrings__DefaultConnection: Host=postgres;Database=FictionArchive_UserService;Username=${POSTGRES_USER:-postgres};Password=${POSTGRES_PASSWORD:-postgres} ConnectionStrings__DefaultConnection: Host=postgres;Database=FictionArchive_UserService;Username=${POSTGRES_USER:-postgres};Password=${POSTGRES_PASSWORD:-postgres}
ConnectionStrings__RabbitMQ: amqp://${RABBITMQ_USER:-guest}:${RABBITMQ_PASSWORD:-guest}@rabbitmq ConnectionStrings__RabbitMQ: amqp://${RABBITMQ_USER:-guest}:${RABBITMQ_PASSWORD:-guest}@rabbitmq
@@ -94,9 +86,7 @@ services:
restart: unless-stopped restart: unless-stopped
authentication-service: authentication-service:
build: image: git.orfl.xyz/conco/fictionarchive-authentication-service:latest
context: .
dockerfile: FictionArchive.Service.AuthenticationService/Dockerfile
environment: environment:
ConnectionStrings__RabbitMQ: amqp://${RABBITMQ_USER:-guest}:${RABBITMQ_PASSWORD:-guest}@rabbitmq ConnectionStrings__RabbitMQ: amqp://${RABBITMQ_USER:-guest}:${RABBITMQ_PASSWORD:-guest}@rabbitmq
depends_on: depends_on:
@@ -105,9 +95,7 @@ services:
restart: unless-stopped restart: unless-stopped
file-service: file-service:
build: image: git.orfl.xyz/conco/fictionarchive-file-service:latest
context: .
dockerfile: FictionArchive.Service.FileService/Dockerfile
environment: environment:
ConnectionStrings__RabbitMQ: amqp://${RABBITMQ_USER:-guest}:${RABBITMQ_PASSWORD:-guest}@rabbitmq ConnectionStrings__RabbitMQ: amqp://${RABBITMQ_USER:-guest}:${RABBITMQ_PASSWORD:-guest}@rabbitmq
S3__Endpoint: ${S3_ENDPOINT:-https://s3.orfl.xyz} S3__Endpoint: ${S3_ENDPOINT:-https://s3.orfl.xyz}
@@ -130,9 +118,7 @@ services:
# API Gateway # API Gateway
# =========================================== # ===========================================
api-gateway: api-gateway:
build: image: git.orfl.xyz/conco/fictionarchive-api:latest
context: .
dockerfile: FictionArchive.API/Dockerfile
environment: environment:
ConnectionStrings__RabbitMQ: amqp://${RABBITMQ_USER:-guest}:${RABBITMQ_PASSWORD:-guest}@rabbitmq ConnectionStrings__RabbitMQ: amqp://${RABBITMQ_USER:-guest}:${RABBITMQ_PASSWORD:-guest}@rabbitmq
labels: labels:
@@ -154,15 +140,7 @@ services:
# Frontend # Frontend
# =========================================== # ===========================================
frontend: frontend:
build: image: git.orfl.xyz/conco/fictionarchive-frontend:latest
context: ./fictionarchive-web
dockerfile: Dockerfile
args:
VITE_GRAPHQL_URI: https://api.fictionarchive.orfl.xyz/graphql/
VITE_OIDC_AUTHORITY: ${OIDC_AUTHORITY:-https://auth.orfl.xyz/application/o/fiction-archive/}
VITE_OIDC_CLIENT_ID: ${OIDC_CLIENT_ID}
VITE_OIDC_REDIRECT_URI: https://fictionarchive.orfl.xyz/
VITE_OIDC_POST_LOGOUT_REDIRECT_URI: https://fictionarchive.orfl.xyz/
labels: labels:
- "traefik.enable=true" - "traefik.enable=true"
- "traefik.http.routers.frontend.rule=Host(`fictionarchive.orfl.xyz`)" - "traefik.http.routers.frontend.rule=Host(`fictionarchive.orfl.xyz`)"