Skip to content
Open
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
35 changes: 35 additions & 0 deletions .github/workflows/backend-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: Backend CI

on:
push:
branches: [develop]
paths:
- backend/**

jobs:
backend-test-build:
runs-on: ubuntu-latest

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

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'

- name: Install dependencies
run: |
cd backend
pip install -r requirements.txt

- name: Run tests
run: |
cd backend
pytest

- name: Build Docker image
run: |
docker build -t backend:${{ github.sha }} backend

27 changes: 27 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: Deploy to AWS & GCP

on:
push:
branches: [main]

jobs:
deploy:
runs-on: ubuntu-latest

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

- name: Setup Terraform
uses: hashicorp/setup-terraform@v3

- name: Terraform Init
run: |
cd terraform
terraform init

- name: Terraform Apply
run: |
cd terraform
terraform apply -auto-approve

35 changes: 35 additions & 0 deletions .github/workflows/frontend-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: Frontend CI

on:
push:
branches: [develop]
paths:
- frontend/**

jobs:
frontend-test-build:
runs-on: ubuntu-latest

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

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 20

- name: Install dependencies
run: |
cd frontend
npm install

- name: Run tests
run: |
cd frontend
npm test -- --watch=false

- name: Build Docker image
run: |
docker build -t frontend:${{ github.sha }} frontend

22 changes: 22 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
jobs:
test-build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Backend tests
run: |
cd backend
pip install -r requirements.txt
pytest

- name: Frontend tests
run: |
cd frontend
npm install
npm run test

- name: Build & Push Docker Images
run: |
docker build -t backend:${{ github.sha }} backend
docker build -t frontend:${{ github.sha }} frontend
41 changes: 41 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# OS / IDE
.DS_Store
.idea
.vscode

# Python
__pycache__/
*.pyc
venv/

# Node
node_modules/

# Terraform
*.tfstate
*.tfstate.backup
.terraform/

# AWS CLI / binaries
aws/
awscliv2.zip

# Secrets
.env
.env.*
terraform.tfvars

# AWS CLI
aws/
awscliv2.zip

# Terraform
.terraform/
terraform.tfstate
terraform.tfstate.backup

# Node / Python
node_modules/
venv/
__pycache__/

33 changes: 33 additions & 0 deletions backend/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# -------- Builder stage --------
FROM python:3.12-slim AS builder

WORKDIR /app

ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1

RUN apt-get update && apt-get install -y --no-install-recommends build-essential \
&& rm -rf /var/lib/apt/lists/*

COPY requirements.txt .
RUN pip install --upgrade pip \
&& pip wheel --no-cache-dir --no-deps --wheel-dir /wheels -r requirements.txt


# -------- Runtime stage --------
FROM python:3.12-slim

WORKDIR /app

RUN addgroup --system app && adduser --system --group app

COPY --from=builder /wheels /wheels
RUN pip install --no-cache /wheels/*

COPY app ./app

USER app

EXPOSE 8080

CMD ["sh", "-c", "uvicorn app.main:app --host 0.0.0.0 --port ${PORT}"]
25 changes: 15 additions & 10 deletions backend/app/main.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,31 @@
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
import os
from dotenv import load_dotenv

# Load environment variables
load_dotenv()

app = FastAPI()

# Configure CORS
# Allow all origins for Cloud Run
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)

@app.get("/api/health")
async def health_check():
@app.get("/health")
def health():
return {"status": "healthy", "message": "Backend is running successfully"}

@app.get("/api/message")
async def get_message():
def message():
return {"message": "You've successfully integrated the backend!"}

# Add a root route
@app.get("/")
def root():
return {"message": "Backend root endpoint is working!"}

@app.get("/api/secret")
def read_secret():
secret = os.getenv("APP_SECRET_KEY", "NOT_SET")
return {
"secret_loaded": secret != "NOT_SET"
13 changes: 13 additions & 0 deletions backend/app/test_main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from fastapi.testclient import TestClient
from app.main import app

client = TestClient(app)

def test_health():
response = client.get("/health")
assert response.status_code == 200

def test_message():
response = client.get("/api/message")
assert "message" in response.json()

2 changes: 2 additions & 0 deletions backend/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ fastapi==0.104.1
uvicorn==0.24.0
pydantic==2.4.2
python-dotenv==1.0.0
httpx==0.25.2

26 changes: 26 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
version: "3.9"

services:
backend:
image: devops-backend:latest
container_name: backend
build:
context: ./backend
ports:
- "8000:8000"
restart: unless-stopped

frontend:
build:
context: ./frontend
args:
NEXT_PUBLIC_API_URL: http://backend:8000
container_name: frontend
ports:
- "3000:3000"
depends_on:
- backend
environment:
- NEXT_PUBLIC_API_URL=http://backend:8000
restart: unless-stopped

32 changes: 32 additions & 0 deletions frontend/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# ---------- Builder ----------
FROM node:20-alpine AS builder
WORKDIR /app

COPY package*.json ./
RUN npm install

COPY . .

ARG NEXT_PUBLIC_API_URL
ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL

RUN npm run build


# ---------- Runner ----------
FROM node:20-alpine
WORKDIR /app

ENV NODE_ENV=production

COPY package*.json ./
RUN npm install --omit=dev

COPY --from=builder /app/.next ./.next
COPY --from=builder /app/public ./public
COPY --from=builder /app/next.config.js ./

EXPOSE 3000

CMD ["npm", "run", "start", "--", "-H", "0.0.0.0"]

45 changes: 45 additions & 0 deletions frontend/__tests__/app.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { render, screen, waitFor } from '@testing-library/react';
import Home from '../pages/index';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';

// Create a new Axios mock instance
const mock = new MockAdapter(axios);

// Mock backend responses
mock.onGet(`${process.env.NEXT_PUBLIC_API_URL}/api/health`).reply(200, {
status: 'healthy',
message: 'Backend is running successfully',
});

mock.onGet(`${process.env.NEXT_PUBLIC_API_URL}/api/message`).reply(200, {
message: "You've successfully integrated the backend!",
});

describe('Home page', () => {
test('renders integration message', async () => {
render(<Home />);
await waitFor(() =>
expect(screen.getByText(/Backend is connected/i)).toBeInTheDocument()
);
});

test('renders backend URL', async () => {
render(<Home />);
await waitFor(() =>
expect(
screen.getByText(new RegExp(process.env.NEXT_PUBLIC_API_URL, 'i'))
).toBeInTheDocument()
);
});

test('renders backend message', async () => {
render(<Home />);
await waitFor(() =>
expect(
screen.getByText(/You've successfully integrated the backend!/i)
).toBeInTheDocument()
);
});
});

4 changes: 4 additions & 0 deletions frontend/babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module.exports = {
presets: ['next/babel'],
};

13 changes: 13 additions & 0 deletions frontend/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module.exports = {
testEnvironment: 'jest-environment-jsdom',
moduleFileExtensions: ['js', 'jsx'],
setupFilesAfterEnv: [
'@testing-library/jest-dom',
'<rootDir>/jest.setup.js' // updated path
],
transform: {
'^.+\\.(js|jsx)$': 'babel-jest',
},
testPathIgnorePatterns: ['/node_modules/', '/.next/'],
};

3 changes: 3 additions & 0 deletions frontend/jest.setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// __tests__/jest.setup.js
process.env.NEXT_PUBLIC_API_URL = 'http://localhost:8000';

8 changes: 8 additions & 0 deletions frontend/next.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
};

module.exports = nextConfig;

Loading