Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
39a2e8a
Containerized API and NextJS
shotorouu Aug 6, 2026
1f5c4b8
cicd
shotorouu Aug 7, 2026
ca6bc16
cicd
shotorouu Aug 7, 2026
f6089c3
cicd
shotorouu Aug 7, 2026
5033147
cicd
shotorouu Aug 7, 2026
be5c8fa
cicd
shotorouu Aug 7, 2026
c0f7ce6
cicd
shotorouu Aug 7, 2026
77a9058
cicd
shotorouu Aug 7, 2026
7d242eb
cicd
shotorouu Aug 7, 2026
1787772
cicd
shotorouu Aug 7, 2026
70e1d24
cicd
shotorouu Aug 7, 2026
7c6be03
cicd
shotorouu Aug 7, 2026
e3bf426
cicd
shotorouu Aug 7, 2026
8abaec7
cicd
shotorouu Aug 7, 2026
ff3d18c
cicd
shotorouu Aug 7, 2026
a2b669b
removed hardcorded address
shotorouu Aug 7, 2026
4c5cb85
Fixing ImagePullBackOff issue
shotorouu Aug 7, 2026
2dd0f88
imagePullSecrets added
shotorouu Aug 8, 2026
958f527
Minor fixes
shotorouu Aug 9, 2026
7dbfb18
Fixing network issues
shotorouu Aug 10, 2026
7c3d116
Monitoring
shotorouu Aug 10, 2026
2c7407e
Flake fix
shotorouu Aug 10, 2026
3496267
Fix
shotorouu Aug 10, 2026
948dfe2
Minor Dockerfile fix
shotorouu Aug 10, 2026
abea0ff
14326
shotorouu Aug 10, 2026
d514a32
1432621
shotorouu Aug 10, 2026
1b16bea
1231
shotorouu Aug 10, 2026
82911bb
1231
shotorouu Aug 10, 2026
28192dd
1231
shotorouu Aug 10, 2026
4bacb57
12321
shotorouu Aug 10, 2026
d16ce47
asdsa
shotorouu Aug 10, 2026
82d441a
Added a pre-commit hook and monitoring
shotorouu Aug 10, 2026
780eb20
RBAC for prometheus
shotorouu Aug 10, 2026
09cde24
Minor fix
shotorouu Aug 10, 2026
1ddb8f1
Query fix
shotorouu Aug 10, 2026
ee2a967
123
shotorouu Aug 10, 2026
bc608c6
Added cAdvisor
shotorouu Aug 11, 2026
55851f5
Added cAdvisor
shotorouu Aug 11, 2026
ca1e4fa
Added cAdvisor
shotorouu Aug 11, 2026
228d5d4
Final commit
shotorouu Aug 11, 2026
d72e6d4
Final commit
shotorouu Aug 11, 2026
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
88 changes: 88 additions & 0 deletions .gitlab-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
stages:
- lint
- check
- test
- build
- deploy

default:
tags:
- qr

lint_pre_commit:
stage: lint
image: python:3.11-slim
before_script:
- apt-get update && apt-get install -y git curl
- pip install pre-commit
- curl -sS -L https://github.com/gitleaks/gitleaks/releases/download/v8.18.2/gitleaks_8.18.2_linux_x64.tar.gz | tar -xz -C /usr/local/bin gitleaks
script:
- pre-commit run --all-files
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

lint_code:
stage: check
image: python:3.10-slim
script:
- pip install flake8
- flake8 . --exclude=venv

test_api:
stage: test
image: python:3.10-slim
script:
- pip install -r api/requirements.txt
- python api/test_main.py

build_api:
stage: build
image: docker:29
services:
- docker:29-dind
variables:
DOCKER_TLS_CERTDIR: "/certs"
before_script:
- echo "$CI_JOB_TOKEN" | docker login -u "$CI_REGISTRY_USER" --password-stdin "$CI_REGISTRY"
script:
- docker build -t "$CI_REGISTRY_IMAGE/api:$CI_COMMIT_SHA" ./api
- docker push "$CI_REGISTRY_IMAGE/api:$CI_COMMIT_SHA"

build_front:
stage: build
image: docker:29
services:
- docker:29-dind
variables:
DOCKER_TLS_CERTDIR: "/certs"
before_script:
- echo "$CI_JOB_TOKEN" | docker login -u "$CI_REGISTRY_USER" --password-stdin "$CI_REGISTRY"
script:
- >-
docker build
--build-arg NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL
-t $CI_REGISTRY_IMAGE/front:$CI_COMMIT_SHA
./front-end-nextjs
- docker push $CI_REGISTRY_IMAGE/front:$CI_COMMIT_SHA

deploy:
stage: deploy
image:
name: alpine/k8s:1.30.0
entrypoint: [""]
before_script:
- kubectl create secret docker-registry gitlab-registry-secret
--docker-server=$CI_REGISTRY
--docker-username=$CI_DEPLOY_USER
--docker-password=$CI_DEPLOY_PASSWORD
--dry-run=client -o yaml | kubectl apply --validate=false -f -
script:
- kubectl cluster-info
- kubectl delete validatingwebhookconfiguration ingress-nginx-admission --ignore-not-found
- kubectl kustomize k8s/ | envsubst '$CI_REGISTRY_IMAGE $CI_COMMIT_SHA' | kubectl apply -f -
- kubectl rollout status deployment/ingress-nginx-controller -n ingress-nginx --timeout=120s
- kubectl rollout status deployment/fastapi --timeout=180s
- kubectl rollout status deployment/frontend --timeout=180s
# rules:
# - if: $CI_COMMIT_BRANCH == "main"
36 changes: 36 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
repos:
# Standard Git hygiene checks
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
args: [--allow-multiple-documents]
- id: check-added-large-files
args: ['--maxkb=10000']
- id: check-merge-conflict

# GitLab CI Schema Validation
- repo: https://github.com/python-jsonschema/check-jsonschema
rev: 0.28.0
hooks:
- id: check-jsonschema
name: "Validate .gitlab-ci.yml Schema"
files: ^\.gitlab-ci.*\.yml$
args:
- "--schemafile"
- "https://gitlab.com/gitlab-org/gitlab/-/raw/master/app/assets/javascripts/editor/schema/ci.json"

# Secret Detection
- repo: https://github.com/gitleaks/gitleaks
rev: v8.18.2
hooks:
- id: gitleaks

# YAML Formatting
- repo: https://github.com/adrienverge/yamllint.git
rev: v1.35.0
hooks:
- id: yamllint
args: [-c, .yamllint.yml]
10 changes: 10 additions & 0 deletions .yamllint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
extends: default

rules:
line-length:
max: 160
level: warning
indentation:
spaces: 2
check-multi-line-strings: false
document-start: disable
2 changes: 1 addition & 1 deletion api/.env.example
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
AWS_ACCESS_KEY=Your-AWS-Access-Key
AWS_SECRET_KEY=Your-AWS-Secret-Access-Key
AWS_SECRET_KEY=Your-AWS-Secret-Access-Key
2 changes: 1 addition & 1 deletion api/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -157,4 +157,4 @@ cython_debug/
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
#.idea/
27 changes: 27 additions & 0 deletions api/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
FROM python:3.11-slim AS builder
WORKDIR /app

RUN apt-get update && apt-get install -y \
gcc \
libpq-dev \
&& rm -rf /var/lib/apt/lists/*

COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt

FROM python:3.11-slim AS runner
WORKDIR /app

RUN apt-get update && apt-get install -y \
libpq5 \
&& rm -rf /var/lib/apt/lists/*

RUN groupadd -g 10001 appgroup && \
useradd -u 10001 -g appgroup -m -s /bin/bash appuser

COPY --from=builder /install /usr/local
COPY --chown=python:pythongr . .

USER 10001
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
49 changes: 37 additions & 12 deletions api/main.py
Original file line number Diff line number Diff line change
@@ -1,35 +1,55 @@
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from prometheus_fastapi_instrumentator import Instrumentator
import qrcode
import boto3
import os
from io import BytesIO

# Loading Environment variable (AWS Access Key and Secret Key)
from dotenv import load_dotenv

load_dotenv()

app = FastAPI()


# Expose /metrics for Prometheus
Instrumentator().instrument(app).expose(app)


@app.get("/")
def read_root():
return {"message": "API is running"}


# Allowing CORS for local testing
origins = [
"http://localhost:3000"
]
origins = ["*"]

app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)

# AWS S3 Configuration
s3 = boto3.client(
's3',
aws_access_key_id= os.getenv("AWS_ACCESS_KEY"),
aws_secret_access_key= os.getenv("AWS_SECRET_KEY"))
"s3",
aws_access_key_id=os.getenv("AWS_ACCESS_KEY"),
aws_secret_access_key=os.getenv("AWS_SECRET_KEY"),
)

bucket_name = "YOUR_BUCKET_NAME" # Add your bucket name here


@app.get("/healthz/startup")
@app.get("/healthz/ready")
@app.get("/healthz/liveness")
async def health_check():
return {"status": "ok"}

bucket_name = 'YOUR_BUCKET_NAME' # Add your bucket name here

@app.post("/generate-qr/")
async def generate_qr(url: str):
Expand All @@ -44,22 +64,27 @@ async def generate_qr(url: str):
qr.make(fit=True)

img = qr.make_image(fill_color="black", back_color="white")

# Save QR Code to BytesIO object
img_byte_arr = BytesIO()
img.save(img_byte_arr, format='PNG')
img.save(img_byte_arr, format="PNG")
img_byte_arr.seek(0)

# Generate file name for S3
file_name = f"qr_codes/{url.split('//')[-1]}.png"

try:
# Upload to S3
s3.put_object(Bucket=bucket_name, Key=file_name, Body=img_byte_arr, ContentType='image/png', ACL='public-read')

s3.put_object(
Bucket=bucket_name,
Key=file_name,
Body=img_byte_arr,
ContentType="image/png",
ACL="public-read",
)

# Generate the S3 URL
s3_url = f"https://{bucket_name}.s3.amazonaws.com/{file_name}"
return {"qr_code_url": s3_url}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))

2 changes: 2 additions & 0 deletions api/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
httpx<0.28.0
prometheus-fastapi-instrumentator>=6.1.0
annotated-types==0.6.0
anyio==3.7.1
boto3==1.34.11
Expand Down
4 changes: 3 additions & 1 deletion api/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@

client = TestClient(app)


def test_generate_qr():
url = "http://example.com"
response = client.post("/generate-qr/", json={"url": url})

assert response.status_code == 200
assert "qr_code_url" in response.json()


def test_generate_qr_invalid_url():
url = "invalid-url"
response = client.post("/generate-qr/", json={"url": url})

assert response.status_code == 422 # FastAPI validation error
assert response.status_code == 422 # FastAPI validation error
25 changes: 25 additions & 0 deletions front-end-nextjs/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
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 --if-present

FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production

RUN addgroup -g 10001 appgroup && \
adduser -u 10001 -G appgroup -s /bin/sh -D appuser

COPY --chown=10001:10001 package*.json ./
COPY --chown=10001:10001 --from=builder /app/.next ./.next
COPY --chown=10001:10001 --from=builder /app/public ./public
COPY --chown=10001:10001 --from=builder /app/node_modules ./node_modules

EXPOSE 3000
CMD ["npm", "run", "start"]
7 changes: 5 additions & 2 deletions front-end-nextjs/next.config.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
/** @type {import('next').NextConfig} */
const nextConfig = {}
const nextConfig = {
serverExternalPackages: ['prom-client'],
};

module.exports = nextConfig

module.exports = nextConfig;
1 change: 1 addition & 0 deletions front-end-nextjs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"dependencies": {
"axios": "^1.6.3",
"next": "14.0.4",
"prom-client": "^15.1.0",
"react": "^18",
"react-dom": "^18"
},
Expand Down
2 changes: 1 addition & 1 deletion front-end-nextjs/public/next.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion front-end-nextjs/public/vercel.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 16 additions & 0 deletions front-end-nextjs/src/app/metrics/route.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { collectDefaultMetrics, register } from 'prom-client';

export const dynamic = 'force-dynamic';

// Initialize default node/runtime metrics
if (!global._hasInitPrometheus) {
collectDefaultMetrics();
global._hasInitPrometheus = true;
}

export async function GET() {
const metrics = await register.metrics();
return new Response(metrics, {
headers: { 'Content-Type': register.contentType },
});
}
3 changes: 2 additions & 1 deletion front-end-nextjs/src/app/page.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ export default function Home() {
const handleSubmit = async (e) => {
e.preventDefault();
try {
const response = await axios.post(`http://localhost:8000/generate-qr/?url=${url}`);
const apiUrl = process.env.NEXT_PUBLIC_API_URL || '/api';
const response = await axios.post(`${apiUrl}/generate-qr/?url=${encodeURIComponent(url)}`);
setQrCodeUrl(response.data.qr_code_url);
} catch (error) {
console.error('Error generating QR Code:', error);
Expand Down
Loading