From 298ac6319a4023e3fe0f3027af9f7bf53c96ee24 Mon Sep 17 00:00:00 2001 From: Laura Cabantous-Taylor Date: Sun, 18 Jan 2026 19:36:19 +0000 Subject: [PATCH 1/7] Rename deploy-frontend.yml and update to deploy to Vercel Dev/Preview env --- .../{deploy-frontend.yml => deploy-admin-frontend-dev.yml} | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) rename .github/workflows/{deploy-frontend.yml => deploy-admin-frontend-dev.yml} (87%) diff --git a/.github/workflows/deploy-frontend.yml b/.github/workflows/deploy-admin-frontend-dev.yml similarity index 87% rename from .github/workflows/deploy-frontend.yml rename to .github/workflows/deploy-admin-frontend-dev.yml index 34804b11..19ba148a 100644 --- a/.github/workflows/deploy-frontend.yml +++ b/.github/workflows/deploy-admin-frontend-dev.yml @@ -1,11 +1,11 @@ -name: Deploy Frontend to Vercel +name: "Dev Deploy to Vercel: Admin App Frontend" on: push: branches: [ main ] paths: - 'admin-wcc-app/**' - - '.github/workflows/deploy-frontend.yml' + - '.github/workflows/deploy-admin-frontend-dev.yml' jobs: deploy: @@ -32,13 +32,12 @@ jobs: NEXT_PUBLIC_API_KEY: ${{ secrets.NEXT_PUBLIC_API_KEY }} NEXT_PUBLIC_APP_URL: ${{ secrets.NEXT_PUBLIC_APP_URL }} - - name: Deploy to Vercel + - name: Deploy to Vercel (Dev) uses: amondnet/vercel-action@v25 with: vercel-token: ${{ secrets.VERCEL_TOKEN }} vercel-org-id: ${{ secrets.VERCEL_ORG_ID }} vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }} working-directory: ./admin-wcc-app - vercel-args: '--prod' env: VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} From 75581fafc3e02bb5406eb12e92c3992a79ee1cd2 Mon Sep 17 00:00:00 2001 From: Laura Cabantous-Taylor Date: Sun, 18 Jan 2026 19:36:51 +0000 Subject: [PATCH 2/7] Create GH Actions for deployment of admin app to prod --- .../workflows/deploy-admin-frontend-prod.yml | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 .github/workflows/deploy-admin-frontend-prod.yml diff --git a/.github/workflows/deploy-admin-frontend-prod.yml b/.github/workflows/deploy-admin-frontend-prod.yml new file mode 100644 index 00000000..6a11f53a --- /dev/null +++ b/.github/workflows/deploy-admin-frontend-prod.yml @@ -0,0 +1,40 @@ +name: "Prod Deploy to Vercel: Admin App Frontend" + +on: + workflow_dispatch: + +jobs: + deploy: + runs-on: ubuntu-latest + defaults: + run: + working-directory: admin-wcc-app + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Install dependencies + run: npm ci + + - name: Build + run: npm run build + env: + NEXT_PUBLIC_API_BASE: ${{ secrets.NEXT_PUBLIC_API_BASE }} + NEXT_PUBLIC_API_KEY: ${{ secrets.NEXT_PUBLIC_API_KEY }} + NEXT_PUBLIC_APP_URL: ${{ secrets.NEXT_PUBLIC_APP_URL }} + + - name: Deploy to Vercel (Prod) + uses: amondnet/vercel-action@v25 + with: + vercel-token: ${{ secrets.VERCEL_TOKEN }} + vercel-org-id: ${{ secrets.VERCEL_ORG_ID }} + vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }} + working-directory: ./admin-wcc-app + vercel-args: '--prod' + env: + VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} From 94cf48eac7abc5ece2d0892b35f0a62affda0a33 Mon Sep 17 00:00:00 2001 From: Laura Cabantous-Taylor Date: Sun, 18 Jan 2026 19:46:42 +0000 Subject: [PATCH 3/7] Update references to deploy-frontend.yml to new file name --- CLAUDE.md | 142 +++++++++++++++++++++++++++++++++++++----------------- README.md | 5 +- 2 files changed, 100 insertions(+), 47 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 2496a2ca..6a70b642 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,10 +1,12 @@ # CLAUDE.md -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. +This file provides guidance to Claude Code (claude.ai/code) when working with code in this +repository. ## Project Overview WCC (Women Coding Community) Platform Backend - A Spring Boot 3.2.5 application (Java 21) providing: + - Public CMS API for website content (landing page, events, mentorship info) - Private platform API for managing members, mentors, and resources - Authentication system with API keys and JWT-like tokens @@ -15,27 +17,32 @@ WCC (Women Coding Community) Platform Backend - A Spring Boot 3.2.5 application ### Backend (Java/Gradle) **Build and run with Docker:** + ```bash ./gradlew clean bootJar docker compose -f docker/docker-compose.yml up --build ``` **Run tests:** + ```bash ./gradlew test # Unit tests ./gradlew testIntegration # Integration tests (requires Docker running) ``` **Run locally from IDE:** + 1. Start PostgreSQL: `docker compose -f docker/docker-compose.yml up postgres` 2. Run `PlatformApplication.java` from IntelliJ (right-click > Run/Debug) **Run with Gradle:** + ```bash ./gradlew bootRun ``` **Quality checks:** + ```bash ./gradlew pmdMain pmdTest # PMD static analysis ./gradlew test jacocoTestReport # Code coverage (minimum 70% required) @@ -43,6 +50,7 @@ docker compose -f docker/docker-compose.yml up --build ``` **Local SonarQube analysis:** + ```bash ./gradlew sonarQubeAnalysis -PlocalProfile ``` @@ -58,6 +66,7 @@ npm run build # Production build ``` **Default admin credentials (local):** + - Email: `admin@wcc.dev` - Password: `wcc-admin` @@ -86,6 +95,7 @@ The application uses a dual-source repository pattern with automatic fallback: - **Mechanism**: If database query returns empty, falls back to JSON files using `PageType` enum This allows: + - Development with static content (no database setup needed initially) - Production with dynamic content managed via admin API - Version-controlled default content @@ -97,12 +107,14 @@ Files in `init-data/`: `landingPage.json`, `eventsPage.json`, `mentorshipPage.js Two independent authentication mechanisms: **API Key Authentication** (`ApiKeyFilter`): + - For public CMS API (`/api/cms/v1/**`) and platform API (`/api/platform/v1/**`) - Header: `X-API-KEY` or query param `api_key` - Configured via `security.api.key` property - Can be disabled: `security.enabled=false` **Token Authentication** (`TokenAuthFilter`): + - For admin frontend authentication - Login: `POST /api/auth/login` with email/password - Returns token with expiry (configurable TTL) @@ -115,15 +127,15 @@ Two independent authentication mechanisms: Interface: `FileStorageRepository` with two implementations (selected by `storage.type` property): - **Google Drive** (`storage.type=google-drive`): - - Requires `credentials.json` in resources (see `docs/google_drive_setup.md`) - - Uploads to configured folder IDs - - Sets public read permissions - - Returns shareable web links + - Requires `credentials.json` in resources (see `docs/google_drive_setup.md`) + - Uploads to configured folder IDs + - Sets public read permissions + - Returns shareable web links - **Local Filesystem** (`storage.type=local`): - - Stores in `file.storage.directory` path - - Returns file:// URLs - - Good for development + - Stores in `file.storage.directory` path + - Returns file:// URLs + - Good for development Used for: Member profile pictures, mentor resources, event images, general uploads @@ -135,13 +147,14 @@ Spring Mail-based email service for sending transactional emails: - **Controller**: `EmailController` - REST endpoints at `/api/platform/v1/email` - **Configuration**: SMTP settings in `spring.mail.*` properties - **Features**: - - Single email sending (`POST /send`) - - Bulk email sending (`POST /send/bulk`) - - HTML and plain text support - - CC, BCC, and Reply-To support - - Comprehensive error handling + - Single email sending (`POST /send`) + - Bulk email sending (`POST /send/bulk`) + - HTML and plain text support + - CC, BCC, and Reply-To support + - Comprehensive error handling **Email Configuration (environment variables):** + ```yaml spring.mail.host: smtp.gmail.com # SMTP server host spring.mail.port: 587 # SMTP port @@ -152,6 +165,7 @@ spring.mail.properties.mail.smtp.starttls.enable: true ``` **Usage example:** + ```bash curl -X POST http://localhost:8080/api/platform/v1/email/send \ -H "Content-Type: application/json" \ @@ -165,11 +179,13 @@ curl -X POST http://localhost:8080/api/platform/v1/email/send \ ``` **Domain models:** + - `EmailRequest` - Input DTO for email requests - `EmailResponse` - Response containing success status and timestamp - `EmailSendException` - Custom exception for email failures **Testing:** + - Unit tests: `EmailServiceTest`, `EmailControllerTest` - Integration tests: `EmailServiceIntegrationTest` (uses GreenMail mock SMTP server) @@ -186,41 +202,43 @@ Database migrations managed by Flyway in `src/main/resources/db/migration/` ### Package Structure - `controller/` - REST endpoints, two main API namespaces: - - `/api/cms/v1/` - Public CMS content - - `/api/platform/v1/` - Internal CRUD operations - - `/api/auth/` - Authentication + - `/api/cms/v1/` - Public CMS content + - `/api/platform/v1/` - Internal CRUD operations + - `/api/auth/` - Authentication - `service/` - Business logic layer (CmsService, MemberService, AuthService, etc.) - `repository/` - Data access implementations - - `file/` - File-based repositories - - `spi/` - Repository interfaces - - `googledrive/` - Google Drive integration + - `file/` - File-based repositories + - `spi/` - Repository interfaces + - `googledrive/` - Google Drive integration - `domain/` - Domain models organized by concern: - - `cms/` - CMS page objects and attributes - - `platform/` - Core entities (Member, Mentor, Event, Programme) - - `auth/` - UserAccount, UserToken - - `email/` - Email request/response models - - `exceptions/` - Custom exceptions - - `resource/` - File storage models + - `cms/` - CMS page objects and attributes + - `platform/` - Core entities (Member, Mentor, Event, Programme) + - `auth/` - UserAccount, UserToken + - `email/` - Email request/response models + - `exceptions/` - Custom exceptions + - `resource/` - File storage models - `configuration/` - Spring configuration beans - - `SecurityConfig` - API key and token filters - - `CorsConfig` - CORS setup (origins from `app.cors.allowed-origins`) - - `DataSourceConfig` - Database configuration - - `ObjectMapperConfig` - Jackson customizations + - `SecurityConfig` - API key and token filters + - `CorsConfig` - CORS setup (origins from `app.cors.allowed-origins`) + - `DataSourceConfig` - Database configuration + - `ObjectMapperConfig` - Jackson customizations - `bootstrap/` - Application initialization and seeding ### Database Schema PostgreSQL with hybrid approach: + - Structured tables for core entities (member, mentor, user_account, user_token) - JSONB columns for flexible CMS content (page table) - Flyway migrations for version control Key tables: + - `page` - Dynamic CMS content (JSONB) - `member` - Community members with skills, links, images - `mentor` - Mentorship data with availability @@ -231,6 +249,7 @@ Key tables: ## Configuration Patterns **Profile-based configuration:** + - `application.yml` - Base configuration - `application-local.yml` - Local development - `application-flyio.yml` - Fly.io deployment @@ -263,36 +282,43 @@ app.seed.admin.password: wcc-admin ## Testing Patterns **Test structure:** + - `src/test/java` - Unit tests (Mockito, JUnit 5) - `src/testInt/java` - Integration tests (Testcontainers with PostgreSQL) **Integration tests require Docker daemon running:** + ```bash docker ps # Verify Docker is running before tests ``` **Coverage requirements:** + - Minimum 70% coverage enforced by `jacocoTestCoverageVerification` - Reports in `build/reports/jacoco/test/html/index.html` **Test naming and documentation:** + - Use `@DisplayName` annotation with Given-When-Then format for all tests - Format: `"Given [precondition], when [action], then [expected outcome]"` - Do NOT use `// Given`, `// When`, `// Then` comments in test body -- Test method names should be descriptive using `should` prefix (e.g., `shouldCreateCorsConfigurationSourceWithAllowedOrigins`) +- Test method names should be descriptive using `should` prefix (e.g., + `shouldCreateCorsConfigurationSourceWithAllowedOrigins`) - Use AssertJ assertions (`assertThat`) for all assertions Example: + ```java + @Test @DisplayName("Given allowed origins are configured, when creating CORS configuration source, then it should contain the allowed origins") void shouldCreateCorsConfigurationSourceWithAllowedOrigins() { - List allowedOrigins = List.of("http://localhost:3000"); - CorsConfig corsConfig = new CorsConfig(allowedOrigins); + List allowedOrigins = List.of("http://localhost:3000"); + CorsConfig corsConfig = new CorsConfig(allowedOrigins); - CorsConfigurationSource source = corsConfig.corsConfigurationSource(); + CorsConfigurationSource source = corsConfig.corsConfigurationSource(); - assertThat(source).isInstanceOf(UrlBasedCorsConfigurationSource.class); + assertThat(source).isInstanceOf(UrlBasedCorsConfigurationSource.class); } ``` @@ -306,13 +332,16 @@ void shouldCreateCorsConfigurationSourceWithAllowedOrigins() { - **OpenAPI/Swagger**: All endpoints documented, available at `/swagger-ui/index.html` - **Google Java Format**: Code formatting enforced (see README for IntelliJ setup) - **Configuration properties**: Avoid inline `@Value` annotations in services - - For single properties: Use constructor injection with `@Value` in the constructor parameter - - For multiple related properties: Create a `@ConfigurationProperties` class in the `configuration/` package - - Always prefer constructor injection over field injection for better testability and immutability + - For single properties: Use constructor injection with `@Value` in the constructor parameter + - For multiple related properties: Create a `@ConfigurationProperties` class in the + `configuration/` package + - Always prefer constructor injection over field injection for better testability and + immutability ## Common Development Tasks **Add a new CMS page:** + 1. Create JSON file in `src/main/resources/init-data/` 2. Add enum value to `PageType` mapping to file path 3. Create domain class in `domain.cms.pages/` @@ -320,6 +349,7 @@ void shouldCreateCorsConfigurationSourceWithAllowedOrigins() { 5. Service layer will automatically use hybrid repository pattern **Add a new database entity:** + 1. Create Flyway migration in `src/main/resources/db/migration/` 2. Create domain class in `domain.platform/` 3. Create repository interface extending `CrudRepository` @@ -328,11 +358,13 @@ void shouldCreateCorsConfigurationSourceWithAllowedOrigins() { 6. Add controller endpoints **Modify authentication:** + - API key: Update `SecurityConfig` and `ApiKeyFilter` - Token auth: Update `TokenAuthFilter` and `AuthService` - Token TTL: Change `security.token.ttl-minutes` property **Add file upload endpoint:** + 1. Inject `FileStorageRepository` in service 2. Use `uploadFile(fileName, inputStream, folderId)` method 3. Store returned `FileStored` object (contains URI, metadata) @@ -343,6 +375,7 @@ void shouldCreateCorsConfigurationSourceWithAllowedOrigins() { ### Backend Deployment (Fly.io) **Deploy to Fly.io:** + ```bash # Build the JAR ./gradlew clean bootJar @@ -361,6 +394,7 @@ fly secrets list -a wcc-backend ``` **Manage Fly.io Environment Variables:** + ```bash # Set a new secret fly secrets set SECURITY_API_KEY=your-api-key -a wcc-backend @@ -379,13 +413,15 @@ fly secrets unset SECRET_NAME -a wcc-backend **Important Fly.io Secrets:** Required environment variables (see `application-flyio.yml`): + - `SPRING_DATASOURCE_URL` - PostgreSQL connection URL - `SPRING_DATASOURCE_USERNAME` - Database username - `SPRING_DATASOURCE_PASSWORD` - Database password - `SPRING_DATASOURCE_DRIVER_CLASS_NAME` - `org.postgresql.Driver` - `SECURITY_API_KEY` - API key for authentication - `SECURITY_ENABLED` - `true` or `false` -- `APP_CORS_ALLOWED_ORIGINS` - Comma-separated frontend URLs (e.g., `https://dev-wcc-admin.vercel.app,https://prod-wcc-admin.vercel.app`) +- `APP_CORS_ALLOWED_ORIGINS` - Comma-separated frontend URLs (e.g., + `https://dev-wcc-admin.vercel.app,https://prod-wcc-admin.vercel.app`) - `APP_SEED_ADMIN_EMAIL` - Admin user email - `APP_SEED_ADMIN_PASSWORD` - Admin user password - `APP_SEED_ADMIN_ENABLED` - `true` or `false` @@ -393,6 +429,7 @@ Required environment variables (see `application-flyio.yml`): - `SPRING_FLYWAY_CLEAN_DISABLED` - `true` for production safety **Common Fly.io Commands:** + ```bash fly status -a wcc-backend # Check app status fly logs -a wcc-backend # View real-time logs @@ -403,12 +440,14 @@ fly postgres connect -a # Connect to PostgreSQL database ### Frontend Deployment (Vercel) **Prerequisites:** + - Vercel CLI installed: `npm install -g vercel` - Logged in: `vercel login` **Deploy/Redeploy Frontend:** **Option 1: Redeploy existing deployment (recommended for updates)** + ```bash # From the admin-wcc-app directory cd admin-wcc-app @@ -425,6 +464,7 @@ echo "y" | vercel redeploy https://dev-wcc-admin-dd1zukrcf-women-coding-communit ``` **Option 2: Fresh deployment** + ```bash # From the project root directory (NOT admin-wcc-app) cd /path/to/wcc-backend @@ -434,6 +474,7 @@ vercel --prod --yes ``` **Manage Vercel Environment Variables:** + ```bash # List all environment variables vercel env ls --cwd admin-wcc-app @@ -454,22 +495,25 @@ vercel env pull --cwd admin-wcc-app **Important Vercel Environment Variables:** Required for frontend (see `.env.example`): + - `NEXT_PUBLIC_API_BASE` - Backend URL (e.g., `https://wcc-backend.fly.dev`) - `NEXT_PUBLIC_API_KEY` - API key matching backend's `SECURITY_API_KEY` - `NEXT_PUBLIC_APP_URL` - Frontend URL (e.g., `https://dev-wcc-admin.vercel.app`) **GitHub Actions Deployment:** -Automated deployment via `.github/workflows/deploy-frontend.yml`: +Automated deployment via `.github/workflows/deploy-admin-frontend-dev.yml`: + - Triggers on push to `main` branch when `admin-wcc-app/**` files change - Requires GitHub secrets: - - `VERCEL_TOKEN` - Vercel API token - - `VERCEL_ORG_ID` - Vercel organization ID - - `VERCEL_PROJECT_ID` - Vercel project ID - - `NEXT_PUBLIC_API_BASE` - Backend URL - - `NEXT_PUBLIC_API_KEY` - API key - - `NEXT_PUBLIC_APP_URL` - Frontend URL + - `VERCEL_TOKEN` - Vercel API token + - `VERCEL_ORG_ID` - Vercel organization ID + - `VERCEL_PROJECT_ID` - Vercel project ID + - `NEXT_PUBLIC_API_BASE` - Backend URL + - `NEXT_PUBLIC_API_KEY` - API key + - `NEXT_PUBLIC_APP_URL` - Frontend URL **Verify Deployment:** + ```bash # Check deployment status vercel inspect --cwd admin-wcc-app @@ -484,6 +528,7 @@ curl https://dev-wcc-admin.vercel.app/api/health ### Local with Docker For local development with Docker: + ```bash # Build and run both backend and database ./gradlew clean bootJar @@ -496,6 +541,7 @@ docker compose -f docker/docker-compose.yml up postgres ### Deployment Checklist **Before deploying backend:** + 1. ✅ Run tests: `./gradlew test testIntegration` 2. ✅ Check code quality: `./gradlew check` 3. ✅ Build JAR: `./gradlew clean bootJar` @@ -503,6 +549,7 @@ docker compose -f docker/docker-compose.yml up postgres 5. ✅ Update CORS origins if frontend URL changed **Before deploying frontend:** + 1. ✅ Run tests: `cd admin-wcc-app && npm test` 2. ✅ Build locally: `npm run build` 3. ✅ Verify Vercel env vars match backend configuration @@ -510,6 +557,7 @@ docker compose -f docker/docker-compose.yml up postgres 5. ✅ Ensure backend CORS includes frontend URL **After deployment:** + 1. ✅ Check logs for errors: `fly logs -a wcc-backend` or Vercel dashboard 2. ✅ Test login at frontend URL 3. ✅ Verify API connectivity from frontend to backend @@ -518,11 +566,13 @@ docker compose -f docker/docker-compose.yml up postgres ### Troubleshooting Deployments **Backend issues:** + - **CORS errors**: Update `APP_CORS_ALLOWED_ORIGINS` in Fly.io secrets - **Database connection**: Check `SPRING_DATASOURCE_*` secrets - **500 errors**: Check `fly logs -a wcc-backend` for stack traces **Frontend issues:** + - **Can't connect to backend**: Verify `NEXT_PUBLIC_API_BASE` is correct - **401 Unauthorized**: Check `NEXT_PUBLIC_API_KEY` matches backend - **Deployment failed**: Check build logs in Vercel dashboard @@ -541,6 +591,7 @@ See `docs/resource_api.md` for detailed resource API documentation. **IMPORTANT: DO NOT include AI attribution in commit messages** When creating commits: + - ❌ **NEVER** add "Generated with Claude Code" or similar AI attribution lines - ❌ **NEVER** add "Co-Authored-By: Claude" or AI co-author tags - ✅ Use standard conventional commit format: `feat:`, `fix:`, `docs:`, etc. @@ -548,6 +599,7 @@ When creating commits: - ✅ Follow the existing commit style in the repository **Example of correct commit message:** + ``` feat: Add mentorship resources page endpoint diff --git a/README.md b/README.md index b80f47c5..24a16495 100644 --- a/README.md +++ b/README.md @@ -344,8 +344,9 @@ avoid CORS issues. ### CI/CD and deploy (Vercel) -A GitHub Actions workflow is provided at `.github/workflows/deploy-frontend.yml` to deploy the -frontend to Vercel on pushes to `main`. Configure the following repository secrets: +A GitHub Actions workflow is provided at `.github/workflows/deploy-admin-frontend-dev.yml` to deploy +the +frontend to Vercel Dev environment on pushes to `main`. Configure the following repository secrets: - VERCEL_TOKEN - VERCEL_ORG_ID From d9586bd04359063ea3183b138e47f6df65dba124 Mon Sep 17 00:00:00 2001 From: Laura Cabantous-Taylor Date: Sun, 18 Jan 2026 20:18:24 +0000 Subject: [PATCH 4/7] Undo linting in CLAUDE.md --- CLAUDE.md | 142 +++++++++++++++++------------------------------------- 1 file changed, 45 insertions(+), 97 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 6a70b642..2496a2ca 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,12 +1,10 @@ # CLAUDE.md -This file provides guidance to Claude Code (claude.ai/code) when working with code in this -repository. +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Project Overview WCC (Women Coding Community) Platform Backend - A Spring Boot 3.2.5 application (Java 21) providing: - - Public CMS API for website content (landing page, events, mentorship info) - Private platform API for managing members, mentors, and resources - Authentication system with API keys and JWT-like tokens @@ -17,32 +15,27 @@ WCC (Women Coding Community) Platform Backend - A Spring Boot 3.2.5 application ### Backend (Java/Gradle) **Build and run with Docker:** - ```bash ./gradlew clean bootJar docker compose -f docker/docker-compose.yml up --build ``` **Run tests:** - ```bash ./gradlew test # Unit tests ./gradlew testIntegration # Integration tests (requires Docker running) ``` **Run locally from IDE:** - 1. Start PostgreSQL: `docker compose -f docker/docker-compose.yml up postgres` 2. Run `PlatformApplication.java` from IntelliJ (right-click > Run/Debug) **Run with Gradle:** - ```bash ./gradlew bootRun ``` **Quality checks:** - ```bash ./gradlew pmdMain pmdTest # PMD static analysis ./gradlew test jacocoTestReport # Code coverage (minimum 70% required) @@ -50,7 +43,6 @@ docker compose -f docker/docker-compose.yml up --build ``` **Local SonarQube analysis:** - ```bash ./gradlew sonarQubeAnalysis -PlocalProfile ``` @@ -66,7 +58,6 @@ npm run build # Production build ``` **Default admin credentials (local):** - - Email: `admin@wcc.dev` - Password: `wcc-admin` @@ -95,7 +86,6 @@ The application uses a dual-source repository pattern with automatic fallback: - **Mechanism**: If database query returns empty, falls back to JSON files using `PageType` enum This allows: - - Development with static content (no database setup needed initially) - Production with dynamic content managed via admin API - Version-controlled default content @@ -107,14 +97,12 @@ Files in `init-data/`: `landingPage.json`, `eventsPage.json`, `mentorshipPage.js Two independent authentication mechanisms: **API Key Authentication** (`ApiKeyFilter`): - - For public CMS API (`/api/cms/v1/**`) and platform API (`/api/platform/v1/**`) - Header: `X-API-KEY` or query param `api_key` - Configured via `security.api.key` property - Can be disabled: `security.enabled=false` **Token Authentication** (`TokenAuthFilter`): - - For admin frontend authentication - Login: `POST /api/auth/login` with email/password - Returns token with expiry (configurable TTL) @@ -127,15 +115,15 @@ Two independent authentication mechanisms: Interface: `FileStorageRepository` with two implementations (selected by `storage.type` property): - **Google Drive** (`storage.type=google-drive`): - - Requires `credentials.json` in resources (see `docs/google_drive_setup.md`) - - Uploads to configured folder IDs - - Sets public read permissions - - Returns shareable web links + - Requires `credentials.json` in resources (see `docs/google_drive_setup.md`) + - Uploads to configured folder IDs + - Sets public read permissions + - Returns shareable web links - **Local Filesystem** (`storage.type=local`): - - Stores in `file.storage.directory` path - - Returns file:// URLs - - Good for development + - Stores in `file.storage.directory` path + - Returns file:// URLs + - Good for development Used for: Member profile pictures, mentor resources, event images, general uploads @@ -147,14 +135,13 @@ Spring Mail-based email service for sending transactional emails: - **Controller**: `EmailController` - REST endpoints at `/api/platform/v1/email` - **Configuration**: SMTP settings in `spring.mail.*` properties - **Features**: - - Single email sending (`POST /send`) - - Bulk email sending (`POST /send/bulk`) - - HTML and plain text support - - CC, BCC, and Reply-To support - - Comprehensive error handling + - Single email sending (`POST /send`) + - Bulk email sending (`POST /send/bulk`) + - HTML and plain text support + - CC, BCC, and Reply-To support + - Comprehensive error handling **Email Configuration (environment variables):** - ```yaml spring.mail.host: smtp.gmail.com # SMTP server host spring.mail.port: 587 # SMTP port @@ -165,7 +152,6 @@ spring.mail.properties.mail.smtp.starttls.enable: true ``` **Usage example:** - ```bash curl -X POST http://localhost:8080/api/platform/v1/email/send \ -H "Content-Type: application/json" \ @@ -179,13 +165,11 @@ curl -X POST http://localhost:8080/api/platform/v1/email/send \ ``` **Domain models:** - - `EmailRequest` - Input DTO for email requests - `EmailResponse` - Response containing success status and timestamp - `EmailSendException` - Custom exception for email failures **Testing:** - - Unit tests: `EmailServiceTest`, `EmailControllerTest` - Integration tests: `EmailServiceIntegrationTest` (uses GreenMail mock SMTP server) @@ -202,43 +186,41 @@ Database migrations managed by Flyway in `src/main/resources/db/migration/` ### Package Structure - `controller/` - REST endpoints, two main API namespaces: - - `/api/cms/v1/` - Public CMS content - - `/api/platform/v1/` - Internal CRUD operations - - `/api/auth/` - Authentication + - `/api/cms/v1/` - Public CMS content + - `/api/platform/v1/` - Internal CRUD operations + - `/api/auth/` - Authentication - `service/` - Business logic layer (CmsService, MemberService, AuthService, etc.) - `repository/` - Data access implementations - - `file/` - File-based repositories - - `spi/` - Repository interfaces - - `googledrive/` - Google Drive integration + - `file/` - File-based repositories + - `spi/` - Repository interfaces + - `googledrive/` - Google Drive integration - `domain/` - Domain models organized by concern: - - `cms/` - CMS page objects and attributes - - `platform/` - Core entities (Member, Mentor, Event, Programme) - - `auth/` - UserAccount, UserToken - - `email/` - Email request/response models - - `exceptions/` - Custom exceptions - - `resource/` - File storage models + - `cms/` - CMS page objects and attributes + - `platform/` - Core entities (Member, Mentor, Event, Programme) + - `auth/` - UserAccount, UserToken + - `email/` - Email request/response models + - `exceptions/` - Custom exceptions + - `resource/` - File storage models - `configuration/` - Spring configuration beans - - `SecurityConfig` - API key and token filters - - `CorsConfig` - CORS setup (origins from `app.cors.allowed-origins`) - - `DataSourceConfig` - Database configuration - - `ObjectMapperConfig` - Jackson customizations + - `SecurityConfig` - API key and token filters + - `CorsConfig` - CORS setup (origins from `app.cors.allowed-origins`) + - `DataSourceConfig` - Database configuration + - `ObjectMapperConfig` - Jackson customizations - `bootstrap/` - Application initialization and seeding ### Database Schema PostgreSQL with hybrid approach: - - Structured tables for core entities (member, mentor, user_account, user_token) - JSONB columns for flexible CMS content (page table) - Flyway migrations for version control Key tables: - - `page` - Dynamic CMS content (JSONB) - `member` - Community members with skills, links, images - `mentor` - Mentorship data with availability @@ -249,7 +231,6 @@ Key tables: ## Configuration Patterns **Profile-based configuration:** - - `application.yml` - Base configuration - `application-local.yml` - Local development - `application-flyio.yml` - Fly.io deployment @@ -282,43 +263,36 @@ app.seed.admin.password: wcc-admin ## Testing Patterns **Test structure:** - - `src/test/java` - Unit tests (Mockito, JUnit 5) - `src/testInt/java` - Integration tests (Testcontainers with PostgreSQL) **Integration tests require Docker daemon running:** - ```bash docker ps # Verify Docker is running before tests ``` **Coverage requirements:** - - Minimum 70% coverage enforced by `jacocoTestCoverageVerification` - Reports in `build/reports/jacoco/test/html/index.html` **Test naming and documentation:** - - Use `@DisplayName` annotation with Given-When-Then format for all tests - Format: `"Given [precondition], when [action], then [expected outcome]"` - Do NOT use `// Given`, `// When`, `// Then` comments in test body -- Test method names should be descriptive using `should` prefix (e.g., - `shouldCreateCorsConfigurationSourceWithAllowedOrigins`) +- Test method names should be descriptive using `should` prefix (e.g., `shouldCreateCorsConfigurationSourceWithAllowedOrigins`) - Use AssertJ assertions (`assertThat`) for all assertions Example: - ```java - @Test @DisplayName("Given allowed origins are configured, when creating CORS configuration source, then it should contain the allowed origins") void shouldCreateCorsConfigurationSourceWithAllowedOrigins() { - List allowedOrigins = List.of("http://localhost:3000"); - CorsConfig corsConfig = new CorsConfig(allowedOrigins); + List allowedOrigins = List.of("http://localhost:3000"); + CorsConfig corsConfig = new CorsConfig(allowedOrigins); - CorsConfigurationSource source = corsConfig.corsConfigurationSource(); + CorsConfigurationSource source = corsConfig.corsConfigurationSource(); - assertThat(source).isInstanceOf(UrlBasedCorsConfigurationSource.class); + assertThat(source).isInstanceOf(UrlBasedCorsConfigurationSource.class); } ``` @@ -332,16 +306,13 @@ void shouldCreateCorsConfigurationSourceWithAllowedOrigins() { - **OpenAPI/Swagger**: All endpoints documented, available at `/swagger-ui/index.html` - **Google Java Format**: Code formatting enforced (see README for IntelliJ setup) - **Configuration properties**: Avoid inline `@Value` annotations in services - - For single properties: Use constructor injection with `@Value` in the constructor parameter - - For multiple related properties: Create a `@ConfigurationProperties` class in the - `configuration/` package - - Always prefer constructor injection over field injection for better testability and - immutability + - For single properties: Use constructor injection with `@Value` in the constructor parameter + - For multiple related properties: Create a `@ConfigurationProperties` class in the `configuration/` package + - Always prefer constructor injection over field injection for better testability and immutability ## Common Development Tasks **Add a new CMS page:** - 1. Create JSON file in `src/main/resources/init-data/` 2. Add enum value to `PageType` mapping to file path 3. Create domain class in `domain.cms.pages/` @@ -349,7 +320,6 @@ void shouldCreateCorsConfigurationSourceWithAllowedOrigins() { 5. Service layer will automatically use hybrid repository pattern **Add a new database entity:** - 1. Create Flyway migration in `src/main/resources/db/migration/` 2. Create domain class in `domain.platform/` 3. Create repository interface extending `CrudRepository` @@ -358,13 +328,11 @@ void shouldCreateCorsConfigurationSourceWithAllowedOrigins() { 6. Add controller endpoints **Modify authentication:** - - API key: Update `SecurityConfig` and `ApiKeyFilter` - Token auth: Update `TokenAuthFilter` and `AuthService` - Token TTL: Change `security.token.ttl-minutes` property **Add file upload endpoint:** - 1. Inject `FileStorageRepository` in service 2. Use `uploadFile(fileName, inputStream, folderId)` method 3. Store returned `FileStored` object (contains URI, metadata) @@ -375,7 +343,6 @@ void shouldCreateCorsConfigurationSourceWithAllowedOrigins() { ### Backend Deployment (Fly.io) **Deploy to Fly.io:** - ```bash # Build the JAR ./gradlew clean bootJar @@ -394,7 +361,6 @@ fly secrets list -a wcc-backend ``` **Manage Fly.io Environment Variables:** - ```bash # Set a new secret fly secrets set SECURITY_API_KEY=your-api-key -a wcc-backend @@ -413,15 +379,13 @@ fly secrets unset SECRET_NAME -a wcc-backend **Important Fly.io Secrets:** Required environment variables (see `application-flyio.yml`): - - `SPRING_DATASOURCE_URL` - PostgreSQL connection URL - `SPRING_DATASOURCE_USERNAME` - Database username - `SPRING_DATASOURCE_PASSWORD` - Database password - `SPRING_DATASOURCE_DRIVER_CLASS_NAME` - `org.postgresql.Driver` - `SECURITY_API_KEY` - API key for authentication - `SECURITY_ENABLED` - `true` or `false` -- `APP_CORS_ALLOWED_ORIGINS` - Comma-separated frontend URLs (e.g., - `https://dev-wcc-admin.vercel.app,https://prod-wcc-admin.vercel.app`) +- `APP_CORS_ALLOWED_ORIGINS` - Comma-separated frontend URLs (e.g., `https://dev-wcc-admin.vercel.app,https://prod-wcc-admin.vercel.app`) - `APP_SEED_ADMIN_EMAIL` - Admin user email - `APP_SEED_ADMIN_PASSWORD` - Admin user password - `APP_SEED_ADMIN_ENABLED` - `true` or `false` @@ -429,7 +393,6 @@ Required environment variables (see `application-flyio.yml`): - `SPRING_FLYWAY_CLEAN_DISABLED` - `true` for production safety **Common Fly.io Commands:** - ```bash fly status -a wcc-backend # Check app status fly logs -a wcc-backend # View real-time logs @@ -440,14 +403,12 @@ fly postgres connect -a # Connect to PostgreSQL database ### Frontend Deployment (Vercel) **Prerequisites:** - - Vercel CLI installed: `npm install -g vercel` - Logged in: `vercel login` **Deploy/Redeploy Frontend:** **Option 1: Redeploy existing deployment (recommended for updates)** - ```bash # From the admin-wcc-app directory cd admin-wcc-app @@ -464,7 +425,6 @@ echo "y" | vercel redeploy https://dev-wcc-admin-dd1zukrcf-women-coding-communit ``` **Option 2: Fresh deployment** - ```bash # From the project root directory (NOT admin-wcc-app) cd /path/to/wcc-backend @@ -474,7 +434,6 @@ vercel --prod --yes ``` **Manage Vercel Environment Variables:** - ```bash # List all environment variables vercel env ls --cwd admin-wcc-app @@ -495,25 +454,22 @@ vercel env pull --cwd admin-wcc-app **Important Vercel Environment Variables:** Required for frontend (see `.env.example`): - - `NEXT_PUBLIC_API_BASE` - Backend URL (e.g., `https://wcc-backend.fly.dev`) - `NEXT_PUBLIC_API_KEY` - API key matching backend's `SECURITY_API_KEY` - `NEXT_PUBLIC_APP_URL` - Frontend URL (e.g., `https://dev-wcc-admin.vercel.app`) **GitHub Actions Deployment:** -Automated deployment via `.github/workflows/deploy-admin-frontend-dev.yml`: - +Automated deployment via `.github/workflows/deploy-frontend.yml`: - Triggers on push to `main` branch when `admin-wcc-app/**` files change - Requires GitHub secrets: - - `VERCEL_TOKEN` - Vercel API token - - `VERCEL_ORG_ID` - Vercel organization ID - - `VERCEL_PROJECT_ID` - Vercel project ID - - `NEXT_PUBLIC_API_BASE` - Backend URL - - `NEXT_PUBLIC_API_KEY` - API key - - `NEXT_PUBLIC_APP_URL` - Frontend URL + - `VERCEL_TOKEN` - Vercel API token + - `VERCEL_ORG_ID` - Vercel organization ID + - `VERCEL_PROJECT_ID` - Vercel project ID + - `NEXT_PUBLIC_API_BASE` - Backend URL + - `NEXT_PUBLIC_API_KEY` - API key + - `NEXT_PUBLIC_APP_URL` - Frontend URL **Verify Deployment:** - ```bash # Check deployment status vercel inspect --cwd admin-wcc-app @@ -528,7 +484,6 @@ curl https://dev-wcc-admin.vercel.app/api/health ### Local with Docker For local development with Docker: - ```bash # Build and run both backend and database ./gradlew clean bootJar @@ -541,7 +496,6 @@ docker compose -f docker/docker-compose.yml up postgres ### Deployment Checklist **Before deploying backend:** - 1. ✅ Run tests: `./gradlew test testIntegration` 2. ✅ Check code quality: `./gradlew check` 3. ✅ Build JAR: `./gradlew clean bootJar` @@ -549,7 +503,6 @@ docker compose -f docker/docker-compose.yml up postgres 5. ✅ Update CORS origins if frontend URL changed **Before deploying frontend:** - 1. ✅ Run tests: `cd admin-wcc-app && npm test` 2. ✅ Build locally: `npm run build` 3. ✅ Verify Vercel env vars match backend configuration @@ -557,7 +510,6 @@ docker compose -f docker/docker-compose.yml up postgres 5. ✅ Ensure backend CORS includes frontend URL **After deployment:** - 1. ✅ Check logs for errors: `fly logs -a wcc-backend` or Vercel dashboard 2. ✅ Test login at frontend URL 3. ✅ Verify API connectivity from frontend to backend @@ -566,13 +518,11 @@ docker compose -f docker/docker-compose.yml up postgres ### Troubleshooting Deployments **Backend issues:** - - **CORS errors**: Update `APP_CORS_ALLOWED_ORIGINS` in Fly.io secrets - **Database connection**: Check `SPRING_DATASOURCE_*` secrets - **500 errors**: Check `fly logs -a wcc-backend` for stack traces **Frontend issues:** - - **Can't connect to backend**: Verify `NEXT_PUBLIC_API_BASE` is correct - **401 Unauthorized**: Check `NEXT_PUBLIC_API_KEY` matches backend - **Deployment failed**: Check build logs in Vercel dashboard @@ -591,7 +541,6 @@ See `docs/resource_api.md` for detailed resource API documentation. **IMPORTANT: DO NOT include AI attribution in commit messages** When creating commits: - - ❌ **NEVER** add "Generated with Claude Code" or similar AI attribution lines - ❌ **NEVER** add "Co-Authored-By: Claude" or AI co-author tags - ✅ Use standard conventional commit format: `feat:`, `fix:`, `docs:`, etc. @@ -599,7 +548,6 @@ When creating commits: - ✅ Follow the existing commit style in the repository **Example of correct commit message:** - ``` feat: Add mentorship resources page endpoint From c5599f31100a6f7ead0ff760b9cb7c7a0cd814a0 Mon Sep 17 00:00:00 2001 From: Laura Cabantous-Taylor Date: Sun, 18 Jan 2026 20:20:12 +0000 Subject: [PATCH 5/7] Update reference to deploy-frontend.yml in CLAUDE.md --- CLAUDE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index 2496a2ca..114c916a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -459,7 +459,7 @@ Required for frontend (see `.env.example`): - `NEXT_PUBLIC_APP_URL` - Frontend URL (e.g., `https://dev-wcc-admin.vercel.app`) **GitHub Actions Deployment:** -Automated deployment via `.github/workflows/deploy-frontend.yml`: +Automated deployment via `.github/workflows/deploy-admin-frontend-dev.yml`: - Triggers on push to `main` branch when `admin-wcc-app/**` files change - Requires GitHub secrets: - `VERCEL_TOKEN` - Vercel API token From 2f5d78959a224f10e6605de5172d8fb6993d2d2e Mon Sep 17 00:00:00 2001 From: Laura Cabantous-Taylor Date: Sun, 18 Jan 2026 20:22:28 +0000 Subject: [PATCH 6/7] Undo linting in README.md --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 24a16495..fdeeabfd 100644 --- a/README.md +++ b/README.md @@ -344,9 +344,7 @@ avoid CORS issues. ### CI/CD and deploy (Vercel) -A GitHub Actions workflow is provided at `.github/workflows/deploy-admin-frontend-dev.yml` to deploy -the -frontend to Vercel Dev environment on pushes to `main`. Configure the following repository secrets: +A GitHub Actions workflow is provided at `.github/workflows/deploy-admin-frontend-dev.yml` to deploy the frontend to Vercel Dev environment on pushes to `main`. Configure the following repository secrets: - VERCEL_TOKEN - VERCEL_ORG_ID From 8c67d31e96f39f9bc140f71b65fc5e7757af7464 Mon Sep 17 00:00:00 2001 From: Laura Cabantous-Taylor Date: Tue, 20 Jan 2026 12:23:19 +0000 Subject: [PATCH 7/7] Set prod secrets names --- .github/workflows/deploy-admin-frontend-prod.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/deploy-admin-frontend-prod.yml b/.github/workflows/deploy-admin-frontend-prod.yml index 6a11f53a..4a5810c9 100644 --- a/.github/workflows/deploy-admin-frontend-prod.yml +++ b/.github/workflows/deploy-admin-frontend-prod.yml @@ -24,17 +24,17 @@ jobs: - name: Build run: npm run build env: - NEXT_PUBLIC_API_BASE: ${{ secrets.NEXT_PUBLIC_API_BASE }} - NEXT_PUBLIC_API_KEY: ${{ secrets.NEXT_PUBLIC_API_KEY }} - NEXT_PUBLIC_APP_URL: ${{ secrets.NEXT_PUBLIC_APP_URL }} + NEXT_PUBLIC_API_BASE: ${{ secrets.NEXT_PUBLIC_API_BASE_PROD }} + NEXT_PUBLIC_API_KEY: ${{ secrets.NEXT_PUBLIC_API_KEY_PROD }} + NEXT_PUBLIC_APP_URL: ${{ secrets.NEXT_PUBLIC_APP_URL_PROD }} - name: Deploy to Vercel (Prod) uses: amondnet/vercel-action@v25 with: - vercel-token: ${{ secrets.VERCEL_TOKEN }} - vercel-org-id: ${{ secrets.VERCEL_ORG_ID }} - vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }} + vercel-token: ${{ secrets.VERCEL_TOKEN_PROD }} + vercel-org-id: ${{ secrets.VERCEL_ORG_ID_PROD }} + vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID_PROD }} working-directory: ./admin-wcc-app vercel-args: '--prod' env: - VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} + VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN_PROD }}