Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 19 additions & 6 deletions .github/workflows/backend-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ jobs:
INSTANCE_ID="${{ steps.cf.outputs.instance_id }}"
IMAGE_URI="${{ steps.cf.outputs.ecr_repo_uri }}:${{ github.sha }}"
REGION="us-east-1"
MONGO_PARAM="${{ steps.cf.outputs.mongo_param_name }}"

# <= 100 chars
COMMENT="Deploy backend ${GITHUB_SHA::7}"
Expand All @@ -128,32 +129,44 @@ jobs:
HOST_PORT="3000"
CONTAINER_PORT="3000"
IMAGE_URI="__IMAGE_URI__"
MONGO_PARAM="__MONGO_PARAM__"
NODE_ENV_VALUE="production"

echo "[1/6] Ensure docker is running"
echo "[1/7] Fetch Mongo URI from SSM"
MONGO_URI="$(aws ssm get-parameter --name "$MONGO_PARAM" --with-decryption --query 'Parameter.Value' --output text)"
if [ -z "$MONGO_URI" ] || [ "$MONGO_URI" = "None" ]; then
echo "Mongo URI is empty; aborting deploy"
exit 1
fi

echo "[2/7] Ensure docker is running"
sudo systemctl start docker || true

echo "[2/6] Login to ECR"
echo "[3/7] Login to ECR"
aws ecr get-login-password --region "$REGION" | sudo docker login --username AWS --password-stdin "$(echo "$IMAGE_URI" | cut -d/ -f1)"

echo "[3/6] Pull image"
echo "[4/7] Pull image"
sudo docker pull "$IMAGE_URI"

echo "[4/6] Stop/remove old container if exists"
echo "[5/7] Stop/remove old container if exists"
sudo docker rm -f "$APP_NAME" || true

echo "[5/6] Run new container"
echo "[6/7] Run new container"
sudo docker run -d \
--name "$APP_NAME" \
--restart unless-stopped \
-p "${HOST_PORT}:${CONTAINER_PORT}" \
-e MONGO_URI="$MONGO_URI" \
-e NODE_ENV="$NODE_ENV_VALUE" \
"$IMAGE_URI"

echo "[6/6] Show running container"
echo "[7/7] Show running container"
sudo docker ps --filter "name=$APP_NAME" --format "table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}"
EOF
)

SCRIPT="${SCRIPT/__IMAGE_URI__/$IMAGE_URI}"
SCRIPT="${SCRIPT/__MONGO_PARAM__/$MONGO_PARAM}"
PARAMS="$(jq -n --arg cmd "$SCRIPT" '{commands:[$cmd]}')"

CMD_ID="$(aws ssm send-command \
Expand Down
3 changes: 1 addition & 2 deletions .github/workflows/backend-ci.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: CI - Backend (PR Validation)
name: CI Backend

on:
pull_request:
Expand All @@ -17,7 +17,6 @@ permissions:
contents: read
jobs:
backend:
name: backend checks
runs-on: ubuntu-latest
timeout-minutes: 20

Expand Down
3 changes: 1 addition & 2 deletions .github/workflows/cdk-synth.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: CI - Infra (CDK Synth)
name: CI Infra

on:
pull_request:
Expand All @@ -17,7 +17,6 @@ permissions:
contents: read
jobs:
synth:
name: cdk synth (localSynth)
runs-on: ubuntu-latest
timeout-minutes: 20

Expand Down
231 changes: 120 additions & 111 deletions .github/workflows/frontend-cd.yml
Original file line number Diff line number Diff line change
@@ -1,120 +1,129 @@
name: CD - Frontend (S3 + CloudFront Deploy) [Manual]

on:
workflow_dispatch:
push:
branches: [master]
paths:
- "frontend/**"
- "package.json"
- "package-lock.json"
- ".github/workflows/frontend-cd.yml"
workflow_dispatch:
push:
branches: [master]
paths:
- "frontend/**"
- "package.json"
- "package-lock.json"
- ".github/workflows/frontend-cd.yml"

concurrency:
group: cd-frontend-${{ github.ref }}
cancel-in-progress: true
group: cd-frontend-${{ github.ref }}
cancel-in-progress: true

permissions:
id-token: write
contents: read
id-token: write
contents: read

jobs:
deploy:
name: Deploy frontend to S3 + invalidate CloudFront
runs-on: ubuntu-latest
timeout-minutes: 30

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Check if frontend exists
id: fe
run: |
if [ -f "frontend/package.json" ]; then
echo "exists=true" >> "$GITHUB_OUTPUT"
else
echo "exists=false" >> "$GITHUB_OUTPUT"
fi

- name: Configure AWS credentials (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_GHA_ROLE_ARN }}
aws-region: us-east-1

- name: Resolve CloudFormation outputs (bucket + distribution)
id: cf
run: |
set -euo pipefail
STACK="ComptaleyesFrontendStack"

BUCKET_NAME="$(aws cloudformation describe-stacks \
--stack-name "$STACK" \
--query "Stacks[0].Outputs[?OutputKey=='FrontendBucketName'].OutputValue | [0]" \
--output text)"

DIST_ID="$(aws cloudformation describe-stacks \
--stack-name "$STACK" \
--query "Stacks[0].Outputs[?OutputKey=='CloudFrontDistributionId'].OutputValue | [0]" \
--output text)"

if [ -z "$BUCKET_NAME" ] || [ "$BUCKET_NAME" = "None" ]; then
echo "Missing FrontendBucketName output in $STACK"
exit 1
fi

if [ -z "$DIST_ID" ] || [ "$DIST_ID" = "None" ]; then
echo "Missing CloudFrontDistributionId output in $STACK"
exit 1
fi

echo "bucket_name=$BUCKET_NAME" >> "$GITHUB_OUTPUT"
echo "distribution_id=$DIST_ID" >> "$GITHUB_OUTPUT"

# This will only run once frontend/package.json exists.
- name: Setup Node
if: steps.fe.outputs.exists == 'true'
uses: actions/setup-node@v4
with:
node-version: "22"
cache: "npm"

- name: Install (workspaces)
if: steps.fe.outputs.exists == 'true'
run: npm ci

- name: Build frontend (placeholder)
if: steps.fe.outputs.exists == 'true'
run: npm -w frontend run build

- name: Verify build output exists (placeholder expects frontend/dist)
if: steps.fe.outputs.exists == 'true'
run: |
set -euo pipefail
if [ ! -d "frontend/dist" ]; then
echo "Expected build output folder frontend/dist not found."
echo "When you create the frontend, ensure the build outputs to frontend/dist, or update this workflow."
exit 1
fi

- name: Deploy to S3 (sync)
if: steps.fe.outputs.exists == 'true'
run: |
set -euo pipefail
aws s3 sync "frontend/dist" "s3://${{ steps.cf.outputs.bucket_name }}" --delete

- name: Invalidate CloudFront
if: steps.fe.outputs.exists == 'true'
run: |
set -euo pipefail
aws cloudfront create-invalidation \
--distribution-id "${{ steps.cf.outputs.distribution_id }}" \
--paths "/*"

- name: Skip (frontend not initialized yet)
if: steps.fe.outputs.exists != 'true'
run: |
echo "frontend/package.json not found; frontend CD is intentionally a placeholder."
echo "CloudFormation lookup succeeded (infra is ready)."
echo "Once frontend exists, re-run this workflow."
deploy:
name: Deploy frontend to S3 + invalidate CloudFront
runs-on: ubuntu-latest
timeout-minutes: 30
env:
API_BASE_URL: ${{ secrets.FRONTEND_API_BASE_URL }}

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Check if frontend exists
id: fe
run: |
if [ -f "frontend/package.json" ]; then
echo "exists=true" >> "$GITHUB_OUTPUT"
else
echo "exists=false" >> "$GITHUB_OUTPUT"
fi

- name: Configure AWS credentials (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_GHA_ROLE_ARN }}
aws-region: us-east-1

- name: Resolve CloudFormation outputs (bucket + distribution)
id: cf
run: |
set -euo pipefail
STACK="ComptaleyesFrontendStack"

BUCKET_NAME="$(aws cloudformation describe-stacks \
--stack-name "$STACK" \
--query "Stacks[0].Outputs[?OutputKey=='FrontendBucketName'].OutputValue | [0]" \
--output text)"

DIST_ID="$(aws cloudformation describe-stacks \
--stack-name "$STACK" \
--query "Stacks[0].Outputs[?OutputKey=='CloudFrontDistributionId'].OutputValue | [0]" \
--output text)"

if [ -z "$BUCKET_NAME" ] || [ "$BUCKET_NAME" = "None" ]; then
echo "Missing FrontendBucketName output in $STACK"
exit 1
fi

if [ -z "$DIST_ID" ] || [ "$DIST_ID" = "None" ]; then
echo "Missing CloudFrontDistributionId output in $STACK"
exit 1
fi

echo "bucket_name=$BUCKET_NAME" >> "$GITHUB_OUTPUT"
echo "distribution_id=$DIST_ID" >> "$GITHUB_OUTPUT"

# This will only run once frontend/package.json exists.
- name: Setup Node
if: steps.fe.outputs.exists == 'true'
uses: actions/setup-node@v4
with:
node-version: "22"
cache: "npm"

- name: Install (workspaces)
if: steps.fe.outputs.exists == 'true'
run: npm ci

- name: Build frontend (placeholder)
if: steps.fe.outputs.exists == 'true'
run: npm -w frontend run build

- name: Write frontend env (Vite)
if: steps.fe.outputs.exists == 'true'
run: |
set -euo pipefail
: "${API_BASE_URL:?Set FRONTEND_API_BASE_URL secret to your backend URL}"
echo "VITE_BACKEND_URL=${API_BASE_URL}" >> frontend/.env.production

- name: Verify build output exists (placeholder expects frontend/dist)
if: steps.fe.outputs.exists == 'true'
run: |
set -euo pipefail
if [ ! -d "frontend/dist" ]; then
echo "Expected build output folder frontend/dist not found."
echo "When you create the frontend, ensure the build outputs to frontend/dist, or update this workflow."
exit 1
fi

- name: Deploy to S3 (sync)
if: steps.fe.outputs.exists == 'true'
run: |
set -euo pipefail
aws s3 sync "frontend/dist" "s3://${{ steps.cf.outputs.bucket_name }}" --delete

- name: Invalidate CloudFront
if: steps.fe.outputs.exists == 'true'
run: |
set -euo pipefail
aws cloudfront create-invalidation \
--distribution-id "${{ steps.cf.outputs.distribution_id }}" \
--paths "/*"

- name: Skip (frontend not initialized yet)
if: steps.fe.outputs.exists != 'true'
run: |
echo "frontend/package.json not found; frontend CD is intentionally a placeholder."
echo "CloudFormation lookup succeeded (infra is ready)."
echo "Once frontend exists, re-run this workflow."
3 changes: 1 addition & 2 deletions .github/workflows/frontend-ci.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: CI - Frontend (PR Validation)
name: CI Frontend

on:
pull_request:
Expand All @@ -17,7 +17,6 @@ permissions:
contents: read
jobs:
frontend:
name: frontend checks
runs-on: ubuntu-latest
timeout-minutes: 20

Expand Down
4 changes: 2 additions & 2 deletions frontend/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<link rel="icon" type="image/png" href="/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>frontend</title>
<title>Comptaleyes</title>
</head>
<body>
<div id="root"></div>
Expand Down
Binary file added frontend/public/favicon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added frontend/public/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 0 additions & 1 deletion frontend/public/vite.svg

This file was deleted.

1 change: 0 additions & 1 deletion frontend/src/assets/react.svg

This file was deleted.

12 changes: 11 additions & 1 deletion frontend/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { resources } from './i18n/loadAll';
const authConfig = {
baseUrl: import.meta.env.VITE_BACKEND_URL,
brandName: 'Comptaleyes',
logoUrl: 'https://cdn-icons-png.flaticon.com/512/11027/11027014.png',
logoUrl: '/favicon.png',
oauthProviders: ['google', 'microsoft'],
colors: {
bg: 'bg-neutral-900', // ~ #1F1F1F
Expand All @@ -37,5 +37,15 @@ createRoot(document.getElementById('root')!).render(
</I18nProvider>
</CookiesProvider>
</BrowserRouter>
<BrowserRouter>
<CookiesProvider>
<I18nProvider resources={resources}>
<LanguageSelectedLang />
<AuthProvider config={authConfig}>
<App />
</AuthProvider>
</I18nProvider>
</CookiesProvider>
</BrowserRouter>
</StrictMode>,
);
7 changes: 1 addition & 6 deletions frontend/src/pages/Loading.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,7 @@ export default function Loading() {
style={{ backgroundColor: 'rgb(23, 23, 23)' }}
className="min-h-screen flex flex-col items-center justify-center"
>
<img
src="https://cdn-icons-png.flaticon.com/512/11027/11027014.png"
className="w-36"
alt="logo"
loading="lazy"
/>
<img src="/logo.png" className="w-36" alt="logo" loading="lazy" />
<h1 className="mt-4 font-bold text-red-500 uppercase font-mono tracking-wide">COMPTALEYES</h1>
</div>
);
Expand Down
Loading