diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 73d7dd8..047feb3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -224,3 +224,59 @@ jobs: cache-from: type=gha cache-to: type=gha,mode=max platforms: linux/amd64,linux/arm64 + + # ── Phase 5: GitOps – update preprod k8s manifest ───────────────────────── + update-k8s-manifest: + name: Update Preprod K8s Manifest + runs-on: ubuntu-latest + needs: [build] + if: github.ref == 'refs/heads/main' && github.event_name == 'push' + permissions: + contents: read + + steps: + - name: Compute image tags + id: tag + run: | + SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7) + echo "backend_image=ghcr.io/${{ github.repository_owner }}/inboxconverge/backend:main-${SHORT_SHA}" >> "$GITHUB_OUTPUT" + echo "frontend_image=ghcr.io/${{ github.repository_owner }}/inboxconverge/frontend:main-${SHORT_SHA}" >> "$GITHUB_OUTPUT" + echo "short_sha=${SHORT_SHA}" >> "$GITHUB_OUTPUT" + + - name: Checkout k8s-cluster-state + uses: actions/checkout@v4 + with: + repository: christianlouis/k8s-cluster-state + token: ${{ secrets.GH_PAT }} + path: k8s-cluster-state + + - name: Update backend image tag in preprod manifest + uses: mikefarah/yq@v4.44.6 + env: + IMAGE: ${{ steps.tag.outputs.backend_image }} + with: + cmd: | + yq -i '(.. | select(tag == "!!str") | select(test("^ghcr\\.io/christianlouis/inboxconverge/backend:"))) = strenv(IMAGE)' \ + k8s-cluster-state/apps/gmail-puller/preprod/gmail-puller-stack.yaml + + - name: Update frontend image tag in preprod manifest + uses: mikefarah/yq@v4.44.6 + env: + IMAGE: ${{ steps.tag.outputs.frontend_image }} + with: + cmd: | + yq -i '(.. | select(tag == "!!str") | select(test("^ghcr\\.io/christianlouis/inboxconverge/frontend:"))) = strenv(IMAGE)' \ + k8s-cluster-state/apps/gmail-puller/preprod/gmail-puller-stack.yaml + + - name: Commit and push manifest update + run: | + cd k8s-cluster-state + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add apps/gmail-puller/preprod/gmail-puller-stack.yaml + if git diff --staged --quiet; then + echo "No changes to commit" + else + git commit -m "chore(preprod): update inboxconverge images to ${{ steps.tag.outputs.short_sha }}" + git push + fi diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..5b8fdd7 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,68 @@ +name: Semantic Release + +on: + push: + branches: + - main + workflow_dispatch: + +permissions: + contents: write + issues: write + pull-requests: write + packages: write + +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + +jobs: + release: + name: Semantic Release + runs-on: ubuntu-latest + if: github.repository == 'christianlouis/InboxConverge' + + steps: + - name: Checkout Code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: '3.12' + cache: 'pip' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install python-semantic-release + + - name: Configure Git + run: | + git config --global user.name "github-actions[bot]" + git config --global user.email "github-actions[bot]@users.noreply.github.com" + + - name: Run Semantic Release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + semantic-release version --print + semantic-release version + semantic-release publish + + - name: Update changelog if no new version was released + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + if git diff --name-only HEAD~1 2>/dev/null | grep -q CHANGELOG.md; then + echo "CHANGELOG.md was already updated by semantic-release version" + else + semantic-release changelog + if ! git diff --quiet CHANGELOG.md; then + git add CHANGELOG.md + git commit -m "docs(changelog): update changelog [skip ci]" + git push + fi + fi diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e340f6..02df76c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed +- **Black formatting**: `backend/app/api/v1/endpoints/admin.py` was not formatted correctly; reformatted to pass `black --check`. +- **`/processing-runs` endpoint 404s**: Routes in `logs.py` had a redundant `/processing-runs` path segment (the router was already mounted at `/processing-runs` in `api.py`). All three user-facing log endpoints now return correct results: + - `GET /processing-runs` (was: `GET /processing-runs/processing-runs`) + - `GET /processing-runs/{id}` (was: `GET /processing-runs/processing-runs/{id}`) + - `GET /processing-runs/{id}/logs` (was: `GET /processing-runs/processing-runs/{id}/logs`) + +### Added +- **Semantic Release** (`release.yml`): Automated versioning and GitHub Release creation on every push to `main` using `python-semantic-release`. Reads conventional-commit prefixes (`feat:`, `fix:`, etc.) to determine the next version and updates `CHANGELOG.md`. +- **`pyproject.toml`**: Project metadata and `[tool.semantic_release]` configuration for `python-semantic-release`. +- **GitOps auto-deployment** (step in `ci.yml`): After a successful Docker build on `main`, a new `update-k8s-manifest` job checks out `christianlouis/k8s-cluster-state` (using the `GH_PAT` secret) and updates the backend and frontend image tags in `apps/gmail-puller/preprod/gmail-puller-stack.yaml` to the new `main-` image, then commits and pushes. + ### Changed - **Project renamed to InboxConverge**: All user-visible strings, Docker container names, database defaults, Docker image paths, monitoring job names, Grafana dashboard titles, and documentation updated from the legacy names (`POP3 to Gmail Forwarder`, `InboxRescue`, `gmail-puller`, `pop3_forwarder`, etc.) to **InboxConverge** / `inboxconverge`. - **Domain updated to `inboxconverge.com`**: All contact and administrative email addresses now default to `@inboxconverge.com` (e.g. `christian@inboxconverge.com`). diff --git a/backend/app/api/v1/endpoints/admin.py b/backend/app/api/v1/endpoints/admin.py index 13bb1cd..d91b377 100644 --- a/backend/app/api/v1/endpoints/admin.py +++ b/backend/app/api/v1/endpoints/admin.py @@ -407,6 +407,8 @@ async def test_admin_notification_config( """Test an admin notification channel by sending a test message (admin only)""" success, message = await test_notification(request.apprise_url) return NotificationTestResponse(success=success, message=message) + + # ── Admin Logs ───────────────────────────────────────────────────────────────── diff --git a/backend/app/api/v1/endpoints/logs.py b/backend/app/api/v1/endpoints/logs.py index 3583c70..7d42bc9 100644 --- a/backend/app/api/v1/endpoints/logs.py +++ b/backend/app/api/v1/endpoints/logs.py @@ -48,7 +48,7 @@ def _paginate(total: int, page: int, page_size: int) -> dict: @router.get( - "/processing-runs", + "", response_model=PaginatedProcessingRunsResponse, summary="List processing runs for the current user", ) @@ -117,7 +117,7 @@ async def list_processing_runs( @router.get( - "/processing-runs/{run_id}", + "/{run_id}", response_model=ProcessingRunDetailResponse, summary="Get a single processing run", ) @@ -160,7 +160,7 @@ async def get_processing_run( @router.get( - "/processing-runs/{run_id}/logs", + "/{run_id}/logs", response_model=PaginatedProcessingLogsResponse, summary="Get per-email logs for a processing run", ) diff --git a/docs/TODO.md b/docs/TODO.md index d41cfdb..455ccc6 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -7,6 +7,11 @@ Comprehensive task breakdown for repository improvements and production readines - [x] Rename entire project to **InboxConverge**: all user-visible strings, Docker container/image names, DB defaults, monitoring, and docs updated. - [x] Domain updated to `inboxconverge.com`; contact email defaults to `christian@inboxconverge.com`. - [x] New configurable env vars: `CONTACT_EMAIL`, `APP_URL`, `NEXT_PUBLIC_APP_NAME`. +- [x] Fixed Black formatting failure in CI (`admin.py` reformatted). +- [x] Fixed `/processing-runs` endpoint 404s caused by duplicate path prefix in `logs.py`. +- [x] Added Semantic Release workflow (`release.yml`) for automatic versioning and GitHub Releases. +- [x] Added `pyproject.toml` with `[tool.semantic_release]` configuration. +- [x] Added GitOps auto-deployment step in `ci.yml` to update preprod k8s manifest in `k8s-cluster-state` repo. ## 🔴 Critical - Security (In Progress) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..f35fabe --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,74 @@ +[build-system] +requires = ["setuptools>=45", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "inboxconverge" +dynamic = ["version"] +description = "Multi-account email forwarding and processing service" +readme = "README.md" +requires-python = ">=3.12" +license = {text = "MIT"} +authors = [ + {name = "Christian Louis", email = "christianlouis@users.noreply.github.com"} +] + +[project.urls] +Homepage = "https://github.com/christianlouis/InboxConverge" +Repository = "https://github.com/christianlouis/InboxConverge" +Issues = "https://github.com/christianlouis/InboxConverge/issues" +Changelog = "https://github.com/christianlouis/InboxConverge/blob/main/CHANGELOG.md" + +[tool.semantic_release] +version_source = "tag" +commit_version_number = true +tag_format = "v{version}" +major_on_zero = false +allow_zero_version = true + +[tool.semantic_release.branches.main] +match = "main" +prerelease = false + +[tool.semantic_release.changelog] +mode = "update" +exclude_commit_patterns = [ + "^chore\\(release\\):", + "^Merge", + "^Bump version", +] + +[tool.semantic_release.changelog.default_templates] +changelog_file = "CHANGELOG.md" +output_format = "md" + +[tool.semantic_release.commit_parser_options] +allowed_tags = [ + "feat", + "fix", + "docs", + "style", + "refactor", + "perf", + "test", + "build", + "ci", + "chore", +] +minor_tags = ["feat"] +patch_tags = ["fix", "perf"] +default_bump_level = 0 + +[tool.semantic_release.remote] +name = "origin" +type = "github" +ignore_token_for_push = false + +[tool.semantic_release.remote.token] +env = "GH_TOKEN" + +[tool.semantic_release.publish] +dist_glob_patterns = [] +upload_to_vcs_release = true +upload_to_pypi = false +upload_to_repository = false