From 97782fbda24bd3d13bf70af29fd24b5322a86c41 Mon Sep 17 00:00:00 2001 From: Loay Ahmed Date: Mon, 6 Oct 2025 19:58:19 +0300 Subject: [PATCH 01/10] feat: add git hooks configuration and setup - [CU-869aqa222] (#1) --- .githooks/commit-msg | 16 ++++++++++++++++ .githooks/pre-push | 16 ++++++++++++++++ .github/CODEOWNERS | 5 +---- githooks-setup.sh | 6 ++++++ 4 files changed, 39 insertions(+), 4 deletions(-) create mode 100644 .githooks/commit-msg create mode 100644 .githooks/pre-push create mode 100644 githooks-setup.sh diff --git a/.githooks/commit-msg b/.githooks/commit-msg new file mode 100644 index 0000000..a0a9f52 --- /dev/null +++ b/.githooks/commit-msg @@ -0,0 +1,16 @@ +#!/bin/sh + +commit_message_file=$1 +commit_message=$(cat "$commit_message_file") + +# Conventional commit pattern: type(scope?): description +conventional_regex="^(feat|fix|docs|build|style|refactor|perf|test|chore)(\([a-z0-9_-]+\))?: .+" + +if ! echo "$commit_message" | grep -Eq "$conventional_regex"; then + echo "Invalid commit message format: type(scope?): description. While (scope) is optional." + echo "Example: docs(openapi): update API documentation" + echo "Allowed types: feat, fix, docs, build, style, refactor, perf, test, chore" + exit 1 +fi + +exit 0 \ No newline at end of file diff --git a/.githooks/pre-push b/.githooks/pre-push new file mode 100644 index 0000000..cba20ce --- /dev/null +++ b/.githooks/pre-push @@ -0,0 +1,16 @@ +#!/bin/sh + +branch_name=$(git rev-parse --abbrev-ref HEAD) + +# Allowed format: type/feature-name (kebab-case) +branch_regex="^(feat|fix|build|chore|refactor|docs|perf|test)/[a-z0-9]+(-[a-z0-9]+)*$" + +if ! echo "$branch_name" | grep -Eq "$branch_regex"; then + echo "Invalid branch name. Expected format: type/feature-name" + echo "Allowed types: feat, fix, build, chore, refactor, docs, perf, test" + echo "Example: feat/login-page" + echo "Use 'git branch -m new-branch-name' to rename your branch and try again." + exit 1 +fi + +exit 0 \ No newline at end of file diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index e5c660f..322386b 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,4 +1 @@ -* @AhmedSobhy01 -* @AmrSamy59 -* @im-saif -* @LoayAhmed304 \ No newline at end of file +* @AhmedSobhy01 @LoayAhmed304 @AmrSamy59 @im-saif \ No newline at end of file diff --git a/githooks-setup.sh b/githooks-setup.sh new file mode 100644 index 0000000..1d5ab91 --- /dev/null +++ b/githooks-setup.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +echo "Setting up Git hooks..." +git config core.hooksPath .githooks +chmod +x .githooks/* +echo "Hooks installed successfully!" \ No newline at end of file From 3c96cbfd01201fc13f913b38b4558ec2d7da5f15 Mon Sep 17 00:00:00 2001 From: Loay Ahmed Date: Wed, 8 Oct 2025 11:16:11 +0300 Subject: [PATCH 02/10] feat: create PR validations workflow - [CU-869aqhaxm] (#2) --- .github/workflows/main-merge-protection.yaml | 22 ++++++++ .github/workflows/pr-validation-workflow.yaml | 56 +++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 .github/workflows/main-merge-protection.yaml create mode 100644 .github/workflows/pr-validation-workflow.yaml diff --git a/.github/workflows/main-merge-protection.yaml b/.github/workflows/main-merge-protection.yaml new file mode 100644 index 0000000..1842705 --- /dev/null +++ b/.github/workflows/main-merge-protection.yaml @@ -0,0 +1,22 @@ +name: Main Branch Protection +on: + pull_request: + types: [opened, reopened] + branches: [main] + +jobs: + validate-merge-permissions: + runs-on: ubuntu-latest + name: Validate Merge Permissions + steps: + - name: Verify Authorized User + run: | + PR_AUTHOR="${{ github.actor }}" + ALLOWED_USER="AhmedSobhy01" + + if [ "$PR_AUTHOR" != "$ALLOWED_USER" ]; then + echo "Unauthorized PR to main by $PR_AUTHOR, only $ALLOWED_USER is allowed." + exit 1 + fi + + echo "Authorized PR to main branch." diff --git a/.github/workflows/pr-validation-workflow.yaml b/.github/workflows/pr-validation-workflow.yaml new file mode 100644 index 0000000..c7288b7 --- /dev/null +++ b/.github/workflows/pr-validation-workflow.yaml @@ -0,0 +1,56 @@ +name: Pull Request Validations + +on: + pull_request: + types: [opened, edited, reopened, synchronize] + branches: [dev] + +jobs: + branch-name-validation: + name: Validate branch naming convention + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - name: Enforce kebab-case branch name + env: + BRANCH_NAME: ${{ github.head_ref }} + BRANCH_PATTERN: "^(feat|fix|build|chore|refactor|docs|perf|test)/[a-z0-9]+(-[a-z0-9]+)*$" + run: | + echo "Checking branch: $BRANCH_NAME" + if ! echo "$BRANCH_NAME" | grep -Eq "$BRANCH_PATTERN"; then + echo "Branch name doesn't match the required pattern. Please setup your git hooks locally to avoid this error." + exit 1 + fi + + echo "Branch name passed." + + validate-pr-title: + name: Validate PR title format + runs-on: ubuntu-latest + + permissions: + pull-requests: read + + steps: + - name: Check PR title format + env: + PR_TITLE: ${{github.event.pull_request.title}} + PREFIX_PATTERN: '^(feat|fix|build|chore|refactor|docs|perf|test)(\(.+\))?: .+' + POSTFIX_PATTERN: " - \\[CU-[a-zA-Z0-9]+\\]$" + run: | + CLEAN_TITLE=$(echo "$PR_TITLE" | tr -d '\r' | sed 's/[[:space:]]\+/ /g' | sed 's/^ *//;s/ *$//') # Normalize spaces + + echo "Checking PR title: $CLEAN_TITLE" + if ! echo "$CLEAN_TITLE" | grep -Eq "$PREFIX_PATTERN"; then + echo "PR title must start with a valid prefix (feat, fix, build, chore, refactor, docs, perf, test) followed by a colon and a space." + exit 1 + fi + + if ! echo "$CLEAN_TITLE" | grep -Eq "$POSTFIX_PATTERN"; then + echo "PR title must end with a valid Clickup PR ticket. (e.g., ' - [CU-1234]')" + exit 1 + fi + + echo "PR title passed." From 2b12b6d0028e53d09676ae6aea76510d6cf6a54f Mon Sep 17 00:00:00 2001 From: Loay Ahmed Date: Fri, 10 Oct 2025 23:43:14 +0300 Subject: [PATCH 03/10] feat: create code style and quality workflow - [CU-869aqhb12] (#3) --- .githooks/commit-msg | 0 .githooks/pre-push | 0 .../backend-code-quality-workflow.yaml | 59 +++++++++++++++++++ teams-workflows/code-quality-workflow.yaml | 40 +++++++++++++ 4 files changed, 99 insertions(+) mode change 100644 => 100755 .githooks/commit-msg mode change 100644 => 100755 .githooks/pre-push create mode 100644 teams-workflows/backend-code-quality-workflow.yaml create mode 100644 teams-workflows/code-quality-workflow.yaml diff --git a/.githooks/commit-msg b/.githooks/commit-msg old mode 100644 new mode 100755 diff --git a/.githooks/pre-push b/.githooks/pre-push old mode 100644 new mode 100755 diff --git a/teams-workflows/backend-code-quality-workflow.yaml b/teams-workflows/backend-code-quality-workflow.yaml new file mode 100644 index 0000000..c973d25 --- /dev/null +++ b/teams-workflows/backend-code-quality-workflow.yaml @@ -0,0 +1,59 @@ +name: Code Style & Quality Checks +on: + pull_request: + branches: [main, dev] + types: [opened, synchronize, reopened] + +jobs: + style-quality: + name: Code Style & Quality + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + + - run: corepack enable + - run: corepack prepare pnpm@latest --activate + + - name: Install dependencies + run: pnpm install + + - name: Run Formatter + run: pnpm run format:check + + - name: Run Linter + run: pnpm run lint:check + + - name: Run Unit Tests + run: pnpm run test:cov + + - name: Comment Coverage on PR + uses: romeovs/lcov-reporter-action@v0.4.0 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + lcov-file: ./coverage/lcov.info + filter-changed-files: true + delete-old-comments: true + title: Unit Tests Coverage Report + + - name: Checkout Base Branch + uses: actions/checkout@v4 + with: + ref: ${{ github.base_ref }} + path: base + + - name: Run OpenAPI Diff (from HEAD revision) + uses: LimeFlight/openapi-diff-action@master + with: + head_spec: ./openapi.json + base_spec: base/openapi.json + output_path: ./output + github_token: ${{ github.token }} + + - uses: actions/upload-artifact@v4 + with: + name: diff-reports + path: ./output diff --git a/teams-workflows/code-quality-workflow.yaml b/teams-workflows/code-quality-workflow.yaml new file mode 100644 index 0000000..63fadb4 --- /dev/null +++ b/teams-workflows/code-quality-workflow.yaml @@ -0,0 +1,40 @@ +name: Code Style & Quality Checks +on: + pull_request: + branches: [main, dev] + types: [opened, synchronize, reopened] + +jobs: + style-quality: + name: Code Style & Quality + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + + - run: corepack enable + - run: corepack prepare pnpm@latest --activate + + - name: Install dependencies + run: pnpm install + + - name: Run Formatter + run: pnpm run format:check + + - name: Run Linter + run: pnpm run lint:check + + - name: Run Unit Tests + run: pnpm run test:cov + + - name: Comment Coverage on PR + uses: romeovs/lcov-reporter-action@v0.4.0 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + lcov-file: ./coverage/lcov.info + filter-changed-files: true + delete-old-comments: true + title: Unit Tests Coverage Report From 3dce48373e5343fbe49a1d5e2709e41845fcbb37 Mon Sep 17 00:00:00 2001 From: Loay Ahmed Date: Mon, 13 Oct 2025 20:38:53 +0300 Subject: [PATCH 04/10] perf: cache pnpm dependencies for workflows - [CU-869aubq63] (#4) --- teams-workflows/backend-code-quality-workflow.yaml | 14 ++++++++++---- teams-workflows/code-quality-workflow.yaml | 14 ++++++++++---- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/teams-workflows/backend-code-quality-workflow.yaml b/teams-workflows/backend-code-quality-workflow.yaml index c973d25..a7bee2b 100644 --- a/teams-workflows/backend-code-quality-workflow.yaml +++ b/teams-workflows/backend-code-quality-workflow.yaml @@ -11,15 +11,21 @@ jobs: steps: - uses: actions/checkout@v4 + + - name: Setup Corepack + run: corepack enable + + - name: Setup pnpm + run: corepack prepare pnpm@latest --activate + - uses: actions/setup-node@v4 with: node-version: 22 - - - run: corepack enable - - run: corepack prepare pnpm@latest --activate + cache: 'pnpm' + cache-dependency-path: '**/pnpm-lock.yaml' - name: Install dependencies - run: pnpm install + run: pnpm install --frozen-lockfile - name: Run Formatter run: pnpm run format:check diff --git a/teams-workflows/code-quality-workflow.yaml b/teams-workflows/code-quality-workflow.yaml index 63fadb4..16e3965 100644 --- a/teams-workflows/code-quality-workflow.yaml +++ b/teams-workflows/code-quality-workflow.yaml @@ -11,15 +11,21 @@ jobs: steps: - uses: actions/checkout@v4 + + - name: Setup Corepack + run: corepack enable + + - name: Setup pnpm + run: corepack prepare pnpm@latest --activate + - uses: actions/setup-node@v4 with: node-version: 22 - - - run: corepack enable - - run: corepack prepare pnpm@latest --activate + cache: 'pnpm' + cache-dependency-path: '**/pnpm-lock.yaml' - name: Install dependencies - run: pnpm install + run: pnpm install --frozen-lockfile - name: Run Formatter run: pnpm run format:check From b467c56afb481481c1dc85d48eab17a6e7cd7ae9 Mon Sep 17 00:00:00 2001 From: Loay Ahmed Date: Wed, 15 Oct 2025 17:00:18 +0300 Subject: [PATCH 05/10] refactor: refactor development workflows and draft PRs support - [CU-869av1uqa] (#5) --- .github/workflows/main-merge-protection.yaml | 22 ------------ .github/workflows/pr-validation-workflow.yaml | 34 ++++++++++++++----- .../backend-code-quality-workflow.yaml | 3 +- teams-workflows/code-quality-workflow.yaml | 8 ++++- 4 files changed, 35 insertions(+), 32 deletions(-) delete mode 100644 .github/workflows/main-merge-protection.yaml diff --git a/.github/workflows/main-merge-protection.yaml b/.github/workflows/main-merge-protection.yaml deleted file mode 100644 index 1842705..0000000 --- a/.github/workflows/main-merge-protection.yaml +++ /dev/null @@ -1,22 +0,0 @@ -name: Main Branch Protection -on: - pull_request: - types: [opened, reopened] - branches: [main] - -jobs: - validate-merge-permissions: - runs-on: ubuntu-latest - name: Validate Merge Permissions - steps: - - name: Verify Authorized User - run: | - PR_AUTHOR="${{ github.actor }}" - ALLOWED_USER="AhmedSobhy01" - - if [ "$PR_AUTHOR" != "$ALLOWED_USER" ]; then - echo "Unauthorized PR to main by $PR_AUTHOR, only $ALLOWED_USER is allowed." - exit 1 - fi - - echo "Authorized PR to main branch." diff --git a/.github/workflows/pr-validation-workflow.yaml b/.github/workflows/pr-validation-workflow.yaml index c7288b7..aecf854 100644 --- a/.github/workflows/pr-validation-workflow.yaml +++ b/.github/workflows/pr-validation-workflow.yaml @@ -2,21 +2,42 @@ name: Pull Request Validations on: pull_request: - types: [opened, edited, reopened, synchronize] - branches: [dev] + types: [opened, edited, reopened, ready_for_review] + branches: [main, dev] + +permissions: + pull-requests: read + contents: read jobs: + validate-merge-permissions: + if: github.event.pull_request.base.ref == 'main' + runs-on: ubuntu-latest + name: Validate Merge Permissions + steps: + - name: Verify Authorized User + run: | + TARGET_BRANCH="${{ github.event.pull_request.base.ref }}" + PR_AUTHOR="${{ github.actor }}" + ALLOWED_USER="AhmedSobhy01" + + if [ "$PR_AUTHOR" != "$ALLOWED_USER" ] && [ "$TARGET_BRANCH" = "main" ]; then + echo "Unauthorized PR to main by $PR_AUTHOR, only $ALLOWED_USER is allowed." + exit 1 + fi + + echo "Authorized PR to main branch." + branch-name-validation: + if: github.event.pull_request.base.ref == 'dev' name: Validate branch naming convention runs-on: ubuntu-latest - permissions: - contents: read steps: - name: Enforce kebab-case branch name env: BRANCH_NAME: ${{ github.head_ref }} - BRANCH_PATTERN: "^(feat|fix|build|chore|refactor|docs|perf|test)/[a-z0-9]+(-[a-z0-9]+)*$" + BRANCH_PATTERN: '^(feat|fix|build|chore|refactor|docs|perf|test)/[a-z0-9]+(-[a-z0-9]+)*$' run: | echo "Checking branch: $BRANCH_NAME" if ! echo "$BRANCH_NAME" | grep -Eq "$BRANCH_PATTERN"; then @@ -30,9 +51,6 @@ jobs: name: Validate PR title format runs-on: ubuntu-latest - permissions: - pull-requests: read - steps: - name: Check PR title format env: diff --git a/teams-workflows/backend-code-quality-workflow.yaml b/teams-workflows/backend-code-quality-workflow.yaml index a7bee2b..9c8fd00 100644 --- a/teams-workflows/backend-code-quality-workflow.yaml +++ b/teams-workflows/backend-code-quality-workflow.yaml @@ -2,10 +2,11 @@ name: Code Style & Quality Checks on: pull_request: branches: [main, dev] - types: [opened, synchronize, reopened] + types: [opened, synchronize, reopened, ready_for_review] jobs: style-quality: + if: github.event.pull_request.draft == false name: Code Style & Quality runs-on: ubuntu-latest diff --git a/teams-workflows/code-quality-workflow.yaml b/teams-workflows/code-quality-workflow.yaml index 16e3965..f30da4f 100644 --- a/teams-workflows/code-quality-workflow.yaml +++ b/teams-workflows/code-quality-workflow.yaml @@ -2,10 +2,16 @@ name: Code Style & Quality Checks on: pull_request: branches: [main, dev] - types: [opened, synchronize, reopened] + types: [opened, synchronize, reopened, ready_for_review] + +permissions: + contents: read + pull-requests: write + issues: write jobs: style-quality: + if: github.event.pull_request.draft == false name: Code Style & Quality runs-on: ubuntu-latest From 886dedb7b3c28d6f3b6f127ad83c52de0efe67cd Mon Sep 17 00:00:00 2001 From: Loay Ahmed Date: Fri, 24 Oct 2025 01:23:27 +0300 Subject: [PATCH 06/10] feat: create testers checklist workflow - [CU-869ay51a9] (#8) --- .../workflows/testers-checklist-backend.yaml | 53 +++++++++++++++++++ .../workflows/testers-checklist-frontend.yaml | 45 ++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 .github/workflows/testers-checklist-backend.yaml create mode 100644 .github/workflows/testers-checklist-frontend.yaml diff --git a/.github/workflows/testers-checklist-backend.yaml b/.github/workflows/testers-checklist-backend.yaml new file mode 100644 index 0000000..41967d5 --- /dev/null +++ b/.github/workflows/testers-checklist-backend.yaml @@ -0,0 +1,53 @@ +name: BE PR Checklist Comment + +on: + pull_request: + types: [opened, reopened, synchronize, ready_for_review] + +jobs: + checklist: + if: github.event.pull_request.draft == false + runs-on: ubuntu-latest + + permissions: + pull-requests: write + + steps: + - name: Find existing checklist comment + id: find-comment + uses: peter-evans/find-comment@v4 + with: + issue-number: ${{ github.event.pull_request.number }} + comment-author: 'github-actions[bot]' + body-includes: '### Backend Testers Checklist' + + - name: Create or update PR checklist comment + uses: peter-evans/create-or-update-comment@v5 + with: + token: ${{ secrets.GITHUB_TOKEN }} + issue-number: ${{ github.event.pull_request.number }} + comment-id: ${{ steps.find-comment.outputs.comment-id }} + body: | + ### Backend Testers Checklist + + #### General + - [ ] Validation for every input + - [ ] Persistent try/catch blocks + - [ ] No lack of documentation + - [ ] Localization + + #### Backend + - [ ] Standard response + - [ ] Sanitization for all user input data + - [ ] Queries transactions for rollbacks + - [ ] Idempotency + - [ ] Monitoring & Logging + - [ ] Filtering and Pagination + - [ ] Documentation is updated correctly + - [ ] Stateless design + - [ ] OpenAPI spec is correctly updated and synced to other teams + - [ ] Designing indexes (if applicable) + - [ ] Rate limiting (if applicable) + - [ ] Soft deleting (if applicable) + + edit-mode: replace diff --git a/.github/workflows/testers-checklist-frontend.yaml b/.github/workflows/testers-checklist-frontend.yaml new file mode 100644 index 0000000..0a2f553 --- /dev/null +++ b/.github/workflows/testers-checklist-frontend.yaml @@ -0,0 +1,45 @@ +name: FE PR Checklist Comment + +on: + pull_request: + types: [opened, reopened, synchronize, ready_for_review] + +jobs: + checklist: + if: github.event.pull_request.draft == false + runs-on: ubuntu-latest + + permissions: + pull-requests: write + + steps: + - name: Find existing checklist comment + id: find-comment + uses: peter-evans/find-comment@v4 + with: + issue-number: ${{ github.event.pull_request.number }} + comment-author: 'github-actions[bot]' + body-includes: '### Frontend Testers Checklist' + + - name: Create or update PR checklist comment + uses: peter-evans/create-or-update-comment@v5 + with: + token: ${{ secrets.GITHUB_TOKEN }} + issue-number: ${{ github.event.pull_request.number }} + comment-id: ${{ steps.find-comment.outputs.comment-id }} + body: | + ### Frontend Testers Checklist + + #### General + - [ ] Validation for every input + - [ ] Persistent try/catch blocks + - [ ] No lack of documentation + - [ ] Localization + + #### Frontend + - [ ] Caching (if applicable) + - [ ] Responsiveness for all screens + - [ ] Loading indicators & skeleton loaders + - [ ] Optimistic loading (if applicable) + + edit-mode: replace From 4ede7d1085aad59d97bdfe5325dfd2b490973b37 Mon Sep 17 00:00:00 2001 From: Loay Ahmed Date: Fri, 24 Oct 2025 14:42:50 +0300 Subject: [PATCH 07/10] chore: revert workflows to not trigger here - [CU-869aw22yh] (#9) --- .../workflows => teams-workflows}/testers-checklist-backend.yaml | 0 .../workflows => teams-workflows}/testers-checklist-frontend.yaml | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {.github/workflows => teams-workflows}/testers-checklist-backend.yaml (100%) rename {.github/workflows => teams-workflows}/testers-checklist-frontend.yaml (100%) diff --git a/.github/workflows/testers-checklist-backend.yaml b/teams-workflows/testers-checklist-backend.yaml similarity index 100% rename from .github/workflows/testers-checklist-backend.yaml rename to teams-workflows/testers-checklist-backend.yaml diff --git a/.github/workflows/testers-checklist-frontend.yaml b/teams-workflows/testers-checklist-frontend.yaml similarity index 100% rename from .github/workflows/testers-checklist-frontend.yaml rename to teams-workflows/testers-checklist-frontend.yaml From 8a819f8fbb5264b5494f7f60e3bef7da3e6a8224 Mon Sep 17 00:00:00 2001 From: Loay Ahmed Date: Sun, 26 Oct 2025 18:12:58 +0300 Subject: [PATCH 08/10] feat: create auto deployment workflow - [CU-869aqhbq2] (#7) --- teams-workflows/deployment.yaml | 108 ++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 teams-workflows/deployment.yaml diff --git a/teams-workflows/deployment.yaml b/teams-workflows/deployment.yaml new file mode 100644 index 0000000..beae3f2 --- /dev/null +++ b/teams-workflows/deployment.yaml @@ -0,0 +1,108 @@ +name: Build and Push Docker Image +on: + pull_request: + branches: [dev, main] + types: [closed] + +jobs: + build-and-push: + if: github.event.pull_request.merged == true + name: Build and Push Docker Image + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Extract repository name + id: repo + run: | + REPO_NAME=$(basename "${{ github.repository }}") + echo "repo_name=$REPO_NAME" >> $GITHUB_OUTPUT + + - name: Determine bump type + id: bump + run: | + if [[ "${{ github.event.pull_request.base.ref }}" == "dev" ]]; then + echo "bump_type=minor" >> $GITHUB_OUTPUT + elif [[ "${{ github.event.pull_request.base.ref }}" == "main" ]]; then + echo "bump_type=major" >> $GITHUB_OUTPUT + else + echo "Unsupported base branch" + exit 1 + fi + + - name: Bump version and create tag + id: tag_version + uses: anothrNick/github-tag-action@1.75.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + DEFAULT_BUMP: ${{ steps.bump.outputs.bump_type }} + RELEASE_BRANCHES: main,dev + PRERELEASE: false + WITH_V: false + + - name: Set image name and tag + id: image + run: | + REGISTRY="${{ secrets.DOCKERHUB_USERNAME }}/${{ steps.repo.outputs.repo_name }}" + TAG="${{ steps.tag_version.outputs.new_tag }}" + LATEST_TAG=$([[ "${{ github.event.pull_request.base.ref }}" == "main" ]] && echo "latest" || echo "staging-latest") + DEPLOYMENT_URL=$([[ "${{ github.event.pull_request.base.ref }}" == "main" ]] && echo "https://raven.cmp27.space" || echo "https://staging.raven.cmp27.space") + DEPLOYMENT_TARGET=$([[ "${{ github.event.pull_request.base.ref }}" == "main" ]] && echo "Production" || echo "Staging") + + echo "registry=$REGISTRY" >> $GITHUB_OUTPUT + echo "tag=$TAG" >> $GITHUB_OUTPUT + echo "latest_tag=$LATEST_TAG" >> $GITHUB_OUTPUT + echo "deployment_url=$DEPLOYMENT_URL" >> $GITHUB_OUTPUT + echo "deployment_target=$DEPLOYMENT_TARGET" >> $GITHUB_OUTPUT + + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build and push Docker image + id: build-and-push + uses: docker/build-push-action@v6 + with: + context: . + push: true + tags: | + "${{ steps.image.outputs.registry }}:${{ steps.image.outputs.tag }}" + "${{ steps.image.outputs.registry }}:${{ steps.image.outputs.latest_tag }}" + + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Notify Discord Success + if: always() && steps.build-and-push.outcome == 'success' + uses: Ilshidur/action-discord@master + env: + DISCORD_WEBHOOK: ${{ secrets.DEPLOYMENT_DISCORD_WEBHOOK }} + with: + args: | + ✅ **${{steps.image.outputs.deployment_target}} Deployment Updated** + [View Deployment](${{steps.image.outputs.deployment_url}}) + Repository: `${{ steps.image.outputs.registry }}` + Version: `${{ steps.image.outputs.tag }}` + Branch: `${{ github.event.pull_request.base.ref }}` + Author: ${{ github.event.pull_request.user.login }} + PR: [${{ github.event.pull_request.title }}](${{ github.event.pull_request.html_url }}) + + - name: Notify Discord Failure + if: always() && steps.build-and-push.outcome != 'success' + uses: Ilshidur/action-discord@master + env: + DISCORD_WEBHOOK: ${{ secrets.DEPLOYMENT_DISCORD_WEBHOOK }} + with: + args: | + ❌ **${{steps.image.outputs.deployment_target}} Deployment Update Failed** + Reason: Docker image build or push failed. + Repository: `${{ steps.image.outputs.registry }}` + Branch: `${{ github.event.pull_request.base.ref }}` + Author: ${{ github.event.pull_request.user.login }} + PR: [${{ github.event.pull_request.title }}](${{ github.event.pull_request.html_url }}) From f35934a013030ed65528c1ea7897c1d773d15d76 Mon Sep 17 00:00:00 2001 From: Loay Ahmed Date: Mon, 3 Nov 2025 23:51:38 +0200 Subject: [PATCH 09/10] feat: add initial nginx and docker compose files - [CU-869b1p50h] (#10) --- docker-composes/docker-compose.prod.yaml | 100 +++++++++++++++ docker-composes/docker-compose.staging.yaml | 118 ++++++++++++++++++ nginx-configs/raven.cmp27.space.nginx | 66 ++++++++++ nginx-configs/staging.raven.cmp27.space.nginx | 85 +++++++++++++ 4 files changed, 369 insertions(+) create mode 100644 docker-composes/docker-compose.prod.yaml create mode 100644 docker-composes/docker-compose.staging.yaml create mode 100644 nginx-configs/raven.cmp27.space.nginx create mode 100644 nginx-configs/staging.raven.cmp27.space.nginx diff --git a/docker-composes/docker-compose.prod.yaml b/docker-composes/docker-compose.prod.yaml new file mode 100644 index 0000000..8e8f415 --- /dev/null +++ b/docker-composes/docker-compose.prod.yaml @@ -0,0 +1,100 @@ +services: + watchtower: + image: containrrr/watchtower + container_name: watchtower + restart: unless-stopped + volumes: + - /var/run/docker.sock:/var/run/docker.sock + environment: + - WATCHTOWER_POLL_INTERVAL=800 + - WATCHTOWER_CLEANUP=true + - WATCHTOWER_LABEL_ENABLE=true + - REPO_USER=loayahmed + - REPO_PASS=${DOCKER_HUB_PASS} + - WATCHTOWER_NOTIFICATIONS=shoutrrr + - WATCHTOWER_NOTIFICATION_URL={DISCORD_WEBHOOK_URL} + - WATCHTOWER_NOTIFICATION_TITLETAG=Raven Production + command: --interval 800 --cleanup --label-enable --debug + networks: + - default + + frontend: + image: loayahmed/raven-frontend:latest + container_name: frontend-production + labels: + - 'com.centurylinklabs.watchtower.enable=true' + ports: + - ${FRONTEND_PORT}:3000 + env_file: + - .env + depends_on: + - backend + networks: + - default + restart: on-failure + + backend: + image: loayahmed/raven-backend:latest + container_name: backend-production + labels: + - 'com.centurylinklabs.watchtower.enable=true' + ports: + - ${BACKEND_PORT}:5000 + env_file: + - .env + environment: + PORT: ${BACKEND_PORT} + depends_on: + - postgres + - redis + networks: + - default + restart: on-failure + healthcheck: + test: ['CMD', 'wget', '--spider', '-q', 'http://localhost:8000/health'] + interval: 30s + timeout: 5s + retries: 3 + start_period: 10s + + postgres: + image: postgres:18-alpine + container_name: pg-production + + env_file: + - .env + + volumes: + - pgdata:/var/lib/postgresql/data + healthcheck: + test: ['CMD-SHELL', 'pg_isready -U ${POSTGRES_USER}'] + interval: 10s + timeout: 5s + retries: 3 + networks: + - default + restart: unless-stopped + + redis: + image: redis:8-alpine + container_name: redis-production + volumes: + - redisdata:/data + command: ['redis-server', '--appendonly', 'yes'] + healthcheck: + test: ['CMD', 'redis-cli', 'ping'] + interval: 10s + timeout: 5s + retries: 3 + networks: + - default + restart: unless-stopped + +volumes: + pgdata: + redisdata: + +networks: + default: + driver: bridge + name: raven-network-prod diff --git a/docker-composes/docker-compose.staging.yaml b/docker-composes/docker-compose.staging.yaml new file mode 100644 index 0000000..54c45a2 --- /dev/null +++ b/docker-composes/docker-compose.staging.yaml @@ -0,0 +1,118 @@ +services: + watchtower: + image: containrrr/watchtower + container_name: watchtower + restart: unless-stopped + volumes: + - /var/run/docker.sock:/var/run/docker.sock + environment: + - WATCHTOWER_POLL_INTERVAL=800 + - WATCHTOWER_CLEANUP=true + - WATCHTOWER_LABEL_ENABLE=true + - REPO_USER=loayahmed + - REPO_PASS=dckr_pat_qJ5qP2YUlqtd6MU-4EUBu1_7fVE + - WATCHTOWER_NOTIFICATIONS=shoutrrr + - WATCHTOWER_NOTIFICATION_URL=discord://uj7G9_h5JKT3c2zVBY-TWzZswkBfBqnKYIw2PAp4hQkTPHa_N0msoXjsjS86WlcbPDEa@1432065651781931168 + - WATCHTOWER_NOTIFICATION_TITLETAG=Raven Staging + command: --interval 800 --cleanup --label-enable --debug + networks: + - default + + dozzle: + image: amir20/dozzle:latest + container_name: dozzle + ports: + - '6677:8080' + volumes: + - /var/run/docker.sock:/var/run/docker.sock + depends_on: + - frontend + - backend + networks: + - default + restart: unless-stopped + + frontend: + image: loayahmed/raven-frontend:staging-latest + container_name: frontend-staging + labels: + - 'com.centurylinklabs.watchtower.enable=true' + ports: + - ${FRONTEND_PORT}:3000 + env_file: + - .env + depends_on: + - backend + networks: + - default + restart: on-failure + + backend: + image: loayahmed/raven-backend:staging-latest + container_name: backend-staging + labels: + - 'com.centurylinklabs.watchtower.enable=true' + ports: + - ${BACKEND_PORT}:8000 + env_file: + - .env + environment: + PORT: ${BACKEND_PORT} + depends_on: + - postgres + - redis + networks: + - default + restart: on-failure + + healthcheck: + test: ['CMD', 'wget', '--spider', '-q', 'http://localhost:8000/health'] + interval: 30s + timeout: 5s + retries: 3 + start_period: 10s + postgres: + image: postgres:18-alpine + container_name: pg + + env_file: + - .env + + ports: + - ${POSTGRES_PORT}:5432 + volumes: + - pgdata:/var/lib/postgresql/data + healthcheck: + test: ['CMD-SHELL', 'pg_isready -U ${POSTGRES_USER}'] + interval: 10s + timeout: 5s + retries: 3 + networks: + - default + restart: unless-stopped + + redis: + image: redis:8-alpine + container_name: redis + ports: + - ${REDIS_PORT}:6379 + volumes: + - redisdata:/data + command: ['redis-server', '--appendonly', 'yes'] + healthcheck: + test: ['CMD', 'redis-cli', 'ping'] + interval: 10s + timeout: 5s + retries: 3 + networks: + - default + restart: unless-stopped + +volumes: + pgdata: + redisdata: + +networks: + default: + driver: bridge + name: raven-network-staging diff --git a/nginx-configs/raven.cmp27.space.nginx b/nginx-configs/raven.cmp27.space.nginx new file mode 100644 index 0000000..2081c7b --- /dev/null +++ b/nginx-configs/raven.cmp27.space.nginx @@ -0,0 +1,66 @@ +server { + listen 80; + server_name raven.cmp27.space; + return 301 https://$host$request_uri; +} + +server { + listen 80; + server_name api.raven.cmp27.space; + return 301 https://$host$request_uri; +} + +server { + + listen 443 ssl; + server_name api.raven.cmp27.space; + + ssl_certificate /etc/letsencrypt/live/raven.cmp27.space/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/raven.cmp27.space/privkey.pem; + + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + + location / { + proxy_pass http://127.0.0.1:5000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + + if ($host = www.api.raven.cmp27.space) { + return 301 https://api.raven.cmp27.space$request_uri; + } + + # websocket stuff + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } + +} + +server { + listen 443 ssl; + server_name raven.cmp27.space; + + ssl_certificate /etc/letsencrypt/live/raven.cmp27.space/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/raven.cmp27.space/privkey.pem; + + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + + if ($host = www.raven.cmp27.space) { + return 301 https://raven.cmp27.space$request_uri; + } + + + location / { + proxy_pass http://127.0.0.1:5174; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } +} \ No newline at end of file diff --git a/nginx-configs/staging.raven.cmp27.space.nginx b/nginx-configs/staging.raven.cmp27.space.nginx new file mode 100644 index 0000000..864590f --- /dev/null +++ b/nginx-configs/staging.raven.cmp27.space.nginx @@ -0,0 +1,85 @@ +server { + listen 80; + server_name staging.raven.cmp27.space; + return 301 https://$host$request_uri; +} + +server { + listen 80; + server_name staging.api.raven.cmp27.space; + return 301 https://$host$request_uri; +} + +server { + + listen 443 ssl; + server_name staging.api.raven.cmp27.space; + + ssl_certificate /etc/letsencrypt/live/raven.cmp27.space-0001/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/raven.cmp27.space-0001/privkey.pem; + + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + + location / { + proxy_pass http://127.0.0.1:8000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $http_x_real_ip; + proxy_set_header X-Forwarded-For $http_x_real_ip; + proxy_set_header X-Forwarded-Proto $scheme; + + + if ($host = www.staging.api.raven.cmp27.space) { + return 301 https://staging.api.raven.cmp27.space$request_uri; + } + + # websocket stuff + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } +} + +server { + listen 443 ssl; + server_name logs.raven.cmp27.space; + ssl_certificate /etc/letsencrypt/live/raven.cmp27.space-0001/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/raven.cmp27.space-0001/privkey.pem; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + + location / { + proxy_pass http://127.0.0.1:6677; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } +} + +server { + listen 443 ssl; + server_name staging.raven.cmp27.space; + + ssl_certificate /etc/letsencrypt/live/raven.cmp27.space-0001/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/raven.cmp27.space-0001/privkey.pem; + + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + + if ($host = www.staging.raven.cmp27.space) { + return 301 https://staging.raven.cmp27.space$request_uri; + } + + + location / { + proxy_pass http://127.0.0.1:5173; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } +} \ No newline at end of file From 6ce3461a30c0b2c6572378fcf77b90f3b11eeec4 Mon Sep 17 00:00:00 2001 From: Loay Ahmed Date: Mon, 24 Nov 2025 20:44:14 +0200 Subject: [PATCH 10/10] feat: update config files to match latest changes - [CU-869b940r1] (#11) --- docker-composes/docker-compose.staging.yaml | 118 ------------------ docker-composes/logging/docker-compose.yaml | 34 +++++ .../docker-compose.yaml} | 71 ++++------- docker-composes/production/nginx.conf | 36 ++++++ docker-composes/staging/docker-compose.yaml | 69 ++++++++++ docker-composes/testing/docker-compose.yaml | 84 +++++++++++++ nginx-configs/test.raven.cmp27.space.nginx | 64 ++++++++++ .../build-release.sh | 40 ++++++ .../cp-nightly-build-workflow/workflow.yaml | 80 ++++++++++++ .../web-e2e-workflow/docker-compose.yaml | 56 +++++++++ .../web-e2e-workflow/workflow.yaml | 97 ++++++++++++++ 11 files changed, 582 insertions(+), 167 deletions(-) delete mode 100644 docker-composes/docker-compose.staging.yaml create mode 100644 docker-composes/logging/docker-compose.yaml rename docker-composes/{docker-compose.prod.yaml => production/docker-compose.yaml} (51%) create mode 100644 docker-composes/production/nginx.conf create mode 100644 docker-composes/staging/docker-compose.yaml create mode 100644 docker-composes/testing/docker-compose.yaml create mode 100644 nginx-configs/test.raven.cmp27.space.nginx create mode 100644 teams-workflows/cp-nightly-build-workflow/build-release.sh create mode 100644 teams-workflows/cp-nightly-build-workflow/workflow.yaml create mode 100644 teams-workflows/web-e2e-workflow/docker-compose.yaml create mode 100644 teams-workflows/web-e2e-workflow/workflow.yaml diff --git a/docker-composes/docker-compose.staging.yaml b/docker-composes/docker-compose.staging.yaml deleted file mode 100644 index 54c45a2..0000000 --- a/docker-composes/docker-compose.staging.yaml +++ /dev/null @@ -1,118 +0,0 @@ -services: - watchtower: - image: containrrr/watchtower - container_name: watchtower - restart: unless-stopped - volumes: - - /var/run/docker.sock:/var/run/docker.sock - environment: - - WATCHTOWER_POLL_INTERVAL=800 - - WATCHTOWER_CLEANUP=true - - WATCHTOWER_LABEL_ENABLE=true - - REPO_USER=loayahmed - - REPO_PASS=dckr_pat_qJ5qP2YUlqtd6MU-4EUBu1_7fVE - - WATCHTOWER_NOTIFICATIONS=shoutrrr - - WATCHTOWER_NOTIFICATION_URL=discord://uj7G9_h5JKT3c2zVBY-TWzZswkBfBqnKYIw2PAp4hQkTPHa_N0msoXjsjS86WlcbPDEa@1432065651781931168 - - WATCHTOWER_NOTIFICATION_TITLETAG=Raven Staging - command: --interval 800 --cleanup --label-enable --debug - networks: - - default - - dozzle: - image: amir20/dozzle:latest - container_name: dozzle - ports: - - '6677:8080' - volumes: - - /var/run/docker.sock:/var/run/docker.sock - depends_on: - - frontend - - backend - networks: - - default - restart: unless-stopped - - frontend: - image: loayahmed/raven-frontend:staging-latest - container_name: frontend-staging - labels: - - 'com.centurylinklabs.watchtower.enable=true' - ports: - - ${FRONTEND_PORT}:3000 - env_file: - - .env - depends_on: - - backend - networks: - - default - restart: on-failure - - backend: - image: loayahmed/raven-backend:staging-latest - container_name: backend-staging - labels: - - 'com.centurylinklabs.watchtower.enable=true' - ports: - - ${BACKEND_PORT}:8000 - env_file: - - .env - environment: - PORT: ${BACKEND_PORT} - depends_on: - - postgres - - redis - networks: - - default - restart: on-failure - - healthcheck: - test: ['CMD', 'wget', '--spider', '-q', 'http://localhost:8000/health'] - interval: 30s - timeout: 5s - retries: 3 - start_period: 10s - postgres: - image: postgres:18-alpine - container_name: pg - - env_file: - - .env - - ports: - - ${POSTGRES_PORT}:5432 - volumes: - - pgdata:/var/lib/postgresql/data - healthcheck: - test: ['CMD-SHELL', 'pg_isready -U ${POSTGRES_USER}'] - interval: 10s - timeout: 5s - retries: 3 - networks: - - default - restart: unless-stopped - - redis: - image: redis:8-alpine - container_name: redis - ports: - - ${REDIS_PORT}:6379 - volumes: - - redisdata:/data - command: ['redis-server', '--appendonly', 'yes'] - healthcheck: - test: ['CMD', 'redis-cli', 'ping'] - interval: 10s - timeout: 5s - retries: 3 - networks: - - default - restart: unless-stopped - -volumes: - pgdata: - redisdata: - -networks: - default: - driver: bridge - name: raven-network-staging diff --git a/docker-composes/logging/docker-compose.yaml b/docker-composes/logging/docker-compose.yaml new file mode 100644 index 0000000..20b16c6 --- /dev/null +++ b/docker-composes/logging/docker-compose.yaml @@ -0,0 +1,34 @@ +services: + watchtower: + image: containrrr/watchtower + container_name: watchtower + volumes: + - /var/run/docker.sock:/var/run/docker. + env_file: + - .env + environment: + - WATCHTOWER_POLL_INTERVAL=800 + - WATCHTOWER_CLEANUP=true + - WATCHTOWER_LABEL_ENABLE=true + - REPO_USER=loayahmed + - WATCHTOWER_NOTIFICATIONS=shoutrrr + - WATCHTOWER_NOTIFICATION_TITLETAG=Raven + command: --interval 800 --cleanup --label-enable --debug + restart: unless-stopped + + dozzle: + image: amir20/dozzle:latest + container_name: dozzle + ports: + - '6677:8080' + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - ./users/:/data/ + environment: + DOZZLE_HOSTNAME: Raven + DOZZLE_AUTH_PROVIDER: simple + DOZZLE_AUTH_TTL: 48h + DOZZLE_ENABLE_ACTIONS: true + DOZZLE_ENABLE_SHELL: true + + restart: unless-stopped diff --git a/docker-composes/docker-compose.prod.yaml b/docker-composes/production/docker-compose.yaml similarity index 51% rename from docker-composes/docker-compose.prod.yaml rename to docker-composes/production/docker-compose.yaml index 8e8f415..641b11f 100644 --- a/docker-composes/docker-compose.prod.yaml +++ b/docker-composes/production/docker-compose.yaml @@ -1,23 +1,4 @@ services: - watchtower: - image: containrrr/watchtower - container_name: watchtower - restart: unless-stopped - volumes: - - /var/run/docker.sock:/var/run/docker.sock - environment: - - WATCHTOWER_POLL_INTERVAL=800 - - WATCHTOWER_CLEANUP=true - - WATCHTOWER_LABEL_ENABLE=true - - REPO_USER=loayahmed - - REPO_PASS=${DOCKER_HUB_PASS} - - WATCHTOWER_NOTIFICATIONS=shoutrrr - - WATCHTOWER_NOTIFICATION_URL={DISCORD_WEBHOOK_URL} - - WATCHTOWER_NOTIFICATION_TITLETAG=Raven Production - command: --interval 800 --cleanup --label-enable --debug - networks: - - default - frontend: image: loayahmed/raven-frontend:latest container_name: frontend-production @@ -27,59 +8,52 @@ services: - ${FRONTEND_PORT}:3000 env_file: - .env + environment: + BACKEND_URL: ${BACKEND_URL} depends_on: - backend networks: - - default + - default-prod restart: on-failure + load_balancer: + image: nginx:alpine + container_name: load-balancer + volumes: + - ./nginx.conf:/etc/nginx/nginx.conf:ro + ports: + - ${BACKEND_PORT}:80 + depends_on: + - backend + networks: + - default-prod + restart: unless-stopped + backend: image: loayahmed/raven-backend:latest - container_name: backend-production labels: - 'com.centurylinklabs.watchtower.enable=true' - ports: - - ${BACKEND_PORT}:5000 env_file: - .env environment: PORT: ${BACKEND_PORT} depends_on: - - postgres - redis networks: - - default + - default-prod restart: on-failure healthcheck: - test: ['CMD', 'wget', '--spider', '-q', 'http://localhost:8000/health'] + test: ['CMD', 'wget', '--spider', '-q', 'http://localhost:5000/health'] interval: 30s timeout: 5s retries: 3 start_period: 10s - postgres: - image: postgres:18-alpine - container_name: pg-production - - env_file: - - .env - - volumes: - - pgdata:/var/lib/postgresql/data - healthcheck: - test: ['CMD-SHELL', 'pg_isready -U ${POSTGRES_USER}'] - interval: 10s - timeout: 5s - retries: 3 - networks: - - default - restart: unless-stopped - redis: image: redis:8-alpine container_name: redis-production volumes: - - redisdata:/data + - redisdata-prod:/data command: ['redis-server', '--appendonly', 'yes'] healthcheck: test: ['CMD', 'redis-cli', 'ping'] @@ -87,14 +61,13 @@ services: timeout: 5s retries: 3 networks: - - default + - default-prod restart: unless-stopped volumes: - pgdata: - redisdata: + redisdata-prod: networks: - default: + default-prod: driver: bridge name: raven-network-prod diff --git a/docker-composes/production/nginx.conf b/docker-composes/production/nginx.conf new file mode 100644 index 0000000..afb3dff --- /dev/null +++ b/docker-composes/production/nginx.conf @@ -0,0 +1,36 @@ +events { + worker_connections auto; +} + +http { + upstream backend_upstream { + ip_hash; # for sticky sessions + server production-backend-1:5000; + server production-backend-2:5000; + server production-backend-3:5000; + + } + + server { + listen 80; + server_name api.raven.cmp27.space; + + location / { + proxy_pass http://backend_upstream; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + + # websocket + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + + # prevent timeout + proxy_read_timeout 3600; + proxy_send_timeout 3600; + } + } +} \ No newline at end of file diff --git a/docker-composes/staging/docker-compose.yaml b/docker-composes/staging/docker-compose.yaml new file mode 100644 index 0000000..42784a7 --- /dev/null +++ b/docker-composes/staging/docker-compose.yaml @@ -0,0 +1,69 @@ +services: + frontend: + image: loayahmed/raven-frontend:staging-latest + container_name: frontend-staging + labels: + - 'com.centurylinklabs.watchtower.enable=true' + ports: + - ${FRONTEND_PORT}:3000 + env_file: + - .env + depends_on: + - backend + networks: + - default + restart: on-failure + + backend: + image: loayahmed/raven-backend:staging-latest + container_name: backend-staging + ports: + - ${BACKEND_PORT}:8000 + logging: + driver: json-file + options: + max-size: '20m' + max-file: '3' + labels: + - 'com.centurylinklabs.watchtower.enable=true' + env_file: + - .env + environment: + PORT: ${BACKEND_PORT} + depends_on: + - redis + networks: + - default + restart: on-failure + + healthcheck: + test: ['CMD', 'wget', '--spider', '-q', 'http://localhost:8000/health'] + interval: 30s + timeout: 5s + retries: 3 + start_period: 10s + + redis: + image: redis:8-alpine + container_name: redis + ports: + - ${REDIS_PORT}:6379 + volumes: + - redisdata:/data + command: ['redis-server', '--appendonly', 'yes'] + healthcheck: + test: ['CMD', 'redis-cli', 'ping'] + interval: 10s + timeout: 5s + retries: 3 + networks: + - default + restart: unless-stopped + +volumes: + redisdata: + +networks: + default: + driver: bridge + name: raven-network-staging diff --git a/docker-composes/testing/docker-compose.yaml b/docker-composes/testing/docker-compose.yaml new file mode 100644 index 0000000..1831ca5 --- /dev/null +++ b/docker-composes/testing/docker-compose.yaml @@ -0,0 +1,84 @@ +services: + frontend: + image: loayahmed/raven-frontend:staging-latest + container_name: frontend-testing + labels: + - 'com.centurylinklabs.watchtower.enable=true' + ports: + - ${FRONTEND_PORT}:3000 + env_file: + - .env + depends_on: + - backend + networks: + - default-testing + restart: on-failure + + backend: + image: loayahmed/raven-backend:staging-latest + container_name: backend-testing + ports: + - ${BACKEND_PORT}:3000 + logging: + driver: json-file + options: + max-size: '20m' + max-file: '3' + labels: + - 'com.centurylinklabs.watchtower.enable=true' + env_file: + - .env + depends_on: + - redis + networks: + - default-testing + restart: on-failure + + healthcheck: + test: ['CMD', 'wget', '--spider', '-q', 'http://localhost:3000/health'] + interval: 30s + timeout: 5s + retries: 3 + start_period: 10s + + postgres: + image: postgres:18-alpine + container_name: pg-testing + environment: + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_DB: ${POSTGRES_DB} + + volumes: + - pgdata-testing:/var/lib/postgresql/data + healthcheck: + test: ['CMD-SHELL', 'pg_isready -U ${POSTGRES_USER}'] + interval: 10s + timeout: 5s + retries: 3 + networks: + - default-testing + + redis: + image: redis:8-alpine + container_name: redis-testing + volumes: + - redisdata-testing:/data + command: ['redis-server', '--appendonly', 'yes'] + healthcheck: + test: ['CMD', 'redis-cli', 'ping'] + interval: 10s + timeout: 5s + retries: 3 + networks: + - default-testing + restart: unless-stopped + +volumes: + pgdata-testing: + redisdata-testing: + +networks: + default-testing: + driver: bridge + name: raven-network-testing diff --git a/nginx-configs/test.raven.cmp27.space.nginx b/nginx-configs/test.raven.cmp27.space.nginx new file mode 100644 index 0000000..e9a43d5 --- /dev/null +++ b/nginx-configs/test.raven.cmp27.space.nginx @@ -0,0 +1,64 @@ +server { + listen 80; + server_name test.raven.cmp27.space; + return 301 https://$host$request_uri; +} +server { + listen 80; + server_name test.api.raven.cmp27.space; + return 301 https://$host$request_uri; +} + +server { + + listen 443 ssl; + server_name test.api.raven.cmp27.space; + ssl_certificate /etc/letsencrypt/live/test.api.raven.cmp27.space/fullchain.pem; # managed by Certbot + ssl_certificate_key /etc/letsencrypt/live/test.api.raven.cmp27.space/privkey.pem; # managed by Certbot + + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + + location / { + proxy_pass http://127.0.0.1:9000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $http_x_real_ip; + proxy_set_header X-Forwarded-Proto $scheme; + + + if ($host = www.test.api.raven.cmp27.space) { + return 301 https://test.api.raven.cmp27.space$request_uri; + } + + # websocket stuff + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } + + +} +server { + listen 443 ssl; + server_name test.raven.cmp27.space; + + ssl_certificate /etc/letsencrypt/live/raven.cmp27.space-0001/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/raven.cmp27.space-0001/privkey.pem; + + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + + if ($host = www.test.raven.cmp27.space) { + return 301 https://test.raven.cmp27.space$request_uri; + } + + + location / { + proxy_pass http://127.0.0.1:5179; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } +} \ No newline at end of file diff --git a/teams-workflows/cp-nightly-build-workflow/build-release.sh b/teams-workflows/cp-nightly-build-workflow/build-release.sh new file mode 100644 index 0000000..582a2b5 --- /dev/null +++ b/teams-workflows/cp-nightly-build-workflow/build-release.sh @@ -0,0 +1,40 @@ +#!/bin/bash + +# Raven APK Build Script +# This script builds an APK for local testing + +echo "Raven APK Build Script" +echo "================================" +echo "" +echo "Building APK for local testing..." +echo "" + +cd android || exit 1 + +# Clean previous builds +./gradlew clean + +# Build release APK (uses debug signing for easy installation) +./gradlew assembleRelease + +if [ $? -eq 0 ]; then + echo "" + echo "✅ SUCCESS! APK has been created." + echo "" + echo "📍 Location: android/app/build/outputs/apk/release/app-release.apk" + echo "" + echo "To install on your device:" + echo " 1. Connect your device via USB" + echo " 2. Run: adb install android/app/build/outputs/apk/release/app-release.apk" + echo " Or simply copy the APK to your device and install it manually" + echo "" + # Show file size + if [ -f "app/build/outputs/apk/release/app-release.apk" ]; then + SIZE=$(du -h app/build/outputs/apk/release/app-release.apk | cut -f1) + echo "📦 APK Size: $SIZE" + fi +else + echo "" + echo "❌ Build failed. Please check the error messages above." + exit 1 +fi \ No newline at end of file diff --git a/teams-workflows/cp-nightly-build-workflow/workflow.yaml b/teams-workflows/cp-nightly-build-workflow/workflow.yaml new file mode 100644 index 0000000..d6e1daa --- /dev/null +++ b/teams-workflows/cp-nightly-build-workflow/workflow.yaml @@ -0,0 +1,80 @@ +name: Nightly Release Workflow +on: + schedule: + - cron: '21 23 * * *' # (daily at 23:21 UTC) + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + issues: write + +jobs: + build: + runs-on: ubuntu-latest + environment: production + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: dev + + - name: Generate .env + run: | + echo "EXPO_PUBLIC_API_BASE_URL=${{ secrets.EXPO_PUBLIC_API_BASE_URL }}" >> .env + echo "EXPO_PUBLIC_MOCK_ENABLED=${{ secrets.EXPO_PUBLIC_MOCK_ENABLED }}" >> .env + echo "EXPO_PUBLIC_GOOGLE_OAUTH_CLIENT_ID=${{ secrets.EXPO_PUBLIC_GOOGLE_OAUTH_CLIENT_ID }}" >> .env + echo "EXPO_PUBLIC_GOOGLE_OAUTH_REDIRECT=${{ secrets.EXPO_PUBLIC_GOOGLE_OAUTH_REDIRECT }}" >> .env + echo "EXPO_PUBLIC_GITHUB_OAUTH_CLIENT_ID=${{ secrets.EXPO_PUBLIC_GITHUB_OAUTH_CLIENT_ID }}" >> .env + echo "EXPO_PUBLIC_GITHUB_OAUTH_REDIRECT=${{ secrets.EXPO_PUBLIC_GITHUB_OAUTH_REDIRECT }}" >> .env + echo "EXPO_PUBLIC_RECAPTCHA_SITE_KEY=${{ secrets.EXPO_PUBLIC_RECAPTCHA_SITE_KEY }}" >> .env + - name: Enable Corepack + run: corepack enable + + - name: Setup pnpm + run: corepack prepare pnpm@latest --activate + + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: 'pnpm' + cache-dependency-path: '**/pnpm-lock.yaml' + + - name: Install Babel Dev Dependency + run: pnpm add -D @babel/plugin-transform-react-jsx + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Setup JDK 17 + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 17 + + - name: Install Android SDK + uses: android-actions/setup-android@v3 + + - name: Install EAS + run: npm i -g eas-cli + + - name: Generate native Android project + run: npx expo prebuild --platform android + + - name: Build APK + run: ./build-release.sh + + - name: Upload APK artifact + uses: actions/upload-artifact@v4 + with: + name: apk + path: android/app/build/outputs/apk/release/app-release.apk + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + tag_name: nightly-${{ github.run_number }} + generate_release_notes: true + files: android/app/build/outputs/apk/release/app-release.apk + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/teams-workflows/web-e2e-workflow/docker-compose.yaml b/teams-workflows/web-e2e-workflow/docker-compose.yaml new file mode 100644 index 0000000..67c4e6d --- /dev/null +++ b/teams-workflows/web-e2e-workflow/docker-compose.yaml @@ -0,0 +1,56 @@ +services: + frontend: + image: loayahmed/raven-frontend:staging-latest + container_name: frontend + env_file: .env + ports: + - 3000:3000 + depends_on: + - backend + networks: + - default + backend: + image: loayahmed/raven-backend:staging-latest + container_name: backend + env_file: .env + ports: + - 3001:3000 + depends_on: + - postgres + - redis + networks: + - default + + postgres: + image: postgres:18-alpine + container_name: postgres + environment: + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_DB: ${POSTGRES_DB} + ports: + - ${POSTGRES_PORT}:5432 + volumes: + - pgdata:/var/lib/postgresql/data + networks: + - default + + redis: + image: redis:8-alpine + container_name: redis + ports: + - ${REDIS_PORT}:6379 + volumes: + - redisdata:/data + command: ['redis-server', '--appendonly', 'yes'] + networks: + - default + +volumes: + pgdata: + redisdata: + +networks: + default: + driver: bridge + name: raven-network-dev diff --git a/teams-workflows/web-e2e-workflow/workflow.yaml b/teams-workflows/web-e2e-workflow/workflow.yaml new file mode 100644 index 0000000..420cc9e --- /dev/null +++ b/teams-workflows/web-e2e-workflow/workflow.yaml @@ -0,0 +1,97 @@ +name: Daily Dev E2E Tests + +on: + schedule: + - cron: '30 23 * * *' # 11:30 PM UTC + workflow_dispatch: + +jobs: + e2e-tests: + runs-on: ubuntu-latest + environment: e2e + env: + APP_POSTGRES_USER: ${{ secrets.POSTGRES_USER }} + APP_POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }} + APP_POSTGRES_DB: ${{ secrets.POSTGRES_DB }} + APP_POSTGRES_PORT: ${{ secrets.POSTGRES_PORT }} + APP_DATABASE_URL: ${{ secrets.DATABASE_URL }} + APP_REDIS_HOST: ${{ secrets.REDIS_HOST }} + APP_REDIS_PORT: ${{ secrets.REDIS_PORT }} + APP_RECAPTCHA_SECRET_KEY: ${{ secrets.RECAPTCHA_SECRET_KEY }} + APP_RECAPTCHA_SECRET_KEY_TEST: ${{ secrets.RECAPTCHA_SECRET_KEY_TEST }} + APP_SMTP_USER: ${{ secrets.SMTP_USER }} + APP_SMTP_HOST: ${{ secrets.SMTP_HOST }} + APP_SMTP_PASS: ${{ secrets.SMTP_PASS }} + APP_SMTP_PORT: ${{ secrets.SMTP_PORT }} + APP_JWT_SECRET: ${{ secrets.JWT_SECRET }} + APP_TLS: ${{ secrets.TLS }} + APP_SSL: ${{ secrets.SSL }} + APP_MAIL_FROM: ${{ secrets.MAIL_FROM }} + APP_MAIL_NAME: ${{ secrets.MAIL_NAME }} + APP_GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }} + APP_GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }} + APP_GOOGLE_REDIRECT_URI: ${{ secrets.GOOGLE_REDIRECT_URI }} + APP_ACCESS_JWT_EXPIRES_IN: ${{ secrets.ACCESS_JWT_EXPIRES_IN }} + APP_ACCESS_TOKEN_EXPIRES_IN_SECONDS: ${{ secrets.ACCESS_TOKEN_EXPIRES_IN_SECONDS }} + APP_REFRESH_TOKEN_EXPIRES_IN_DAYS: ${{ secrets.REFRESH_TOKEN_EXPIRES_IN_DAYS }} + APP_NODE_ENV: ${{ secrets.NODE_ENV }} + APP_SPACES_KEY: ${{ secrets.SPACES_KEY }} + APP_SPACES_SECRET: ${{ secrets.SPACES_SECRET }} + APP_SPACES_ENDPOINT: ${{ secrets.SPACES_ENDPOINT }} + APP_SPACES_BUCKET: ${{ secrets.SPACES_BUCKET }} + APP_SPACES_REGION: ${{ secrets.SPACES_REGION }} + APP_CDN_URL: ${{ secrets.CDN_URL }} + APP_RATE_LIMIT_BYPASS_SECRET: ${{ secrets.RATE_LIMIT_BYPASS_SECRET }} + APP_PORT: ${{ secrets.PORT }} + APP_SEED_ENV: ${{ secrets.SEED_ENV }} + APP_BACKEND_URL: ${{ secrets.BACKEND_URL }} + APP_CLIENT_SECRET: ${{ secrets.CLIENT_SECRET }} + APP_NUXT_PUBLIC_GOOGLE_CLIENT_ID: ${{ secrets.NUXT_PUBLIC_GOOGLE_CLIENT_ID }} + APP_NUXT_PUBLIC_GITHUB_CLIENT_ID: ${{ secrets.NUXT_PUBLIC_GITHUB_CLIENT_ID }} + APP_NUXT_PUBLIC_GITHUB_REDIRECT_URI: ${{ secrets.NUXT_PUBLIC_GITHUB_REDIRECT_URI }} + APP_NUXT_PUBLIC_GITHUB_SCOPE: ${{ secrets.NUXT_PUBLIC_GITHUB_SCOPE }} + APP_NUXT_PUBLIC_RECAPTCHA_SITE_KEY: ${{ secrets.NUXT_PUBLIC_RECAPTCHA_SITE_KEY }} + APP_NUXT_PUBLIC_USE_MOCKS: ${{ secrets.NUXT_PUBLIC_USE_MOCKS }} + steps: + - uses: actions/checkout@v4 + + - name: Generate .env + run: | + printenv | grep '^APP_' | sed 's/^APP_//' > .env + - name: Set up Corepack + run: corepack enable + + - name: Set up pnpm + run: corepack prepare pnpm@latest --activate + + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: 'pnpm' + cache-dependency-path: '**/pnpm-lock.yaml' + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Install Cypress globally + run: npm i -g cypress + + - name: Install Cypress + run: pnpm cypress install + + - name: Start Docker Compose + run: | + docker compose -f ./e2e.docker-compose.yaml up -d + - name: Run E2E Tests + run: pnpm test:e2e + + - name: Check logs + if: failure() + run: | + echo "Fetching logs..." + docker compose -f ./e2e.docker-compose.yaml logs + - name: Cleanup docker + if: always() + run: | + echo "Stopping containers..." + docker compose -f ./e2e.docker-compose.yaml down -v --remove-orphans || true