Skip to content

Reconcile #16

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 22 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
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
Binary file added .DS_Store
Binary file not shown.
49 changes: 49 additions & 0 deletions .github/workflows/build-docker.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: Build and Publish Docker Images

on:
push:
branches:
- main
paths:
- 'api/Dockerfile'
- 'front-end-nextjs/Dockerfile'

jobs:
publish_images:
runs-on: ubuntu-latest
steps:
# Step 1: Checkout the repository
- name: Checkout repository
uses: actions/checkout@v4

# Step 2: Log in to Docker Hub
- name: Log in to Docker Hub
run: echo "${{ secrets.DOCKER_HUB_TOKEN }}" | docker login -u "echelonkay" --password-stdin

# Step 3: Build API Docker image
- name: Build API Docker image
run: docker build ./api/ -t echelonkay/devops-qr-code-api:latest --no-cache

# Step 4: Install dependencies for Frontend
- name: Install dependencies for Frontend
run: |
cd front-end-nextjs
npm install

# Step 5: Build Frontend
- name: Build Frontend
run: |
cd front-end-nextjs
npm run build

# Step 6: Build Frontend Docker image
- name: Build Frontend Docker image
run: docker build ./front-end-nextjs/ -t echelonkay/devops-qr-code-frontend:latest --no-cache

# Step 7: Push API Docker image to Docker Hub
- name: Push API Docker image
run: docker push echelonkay/devops-qr-code-api:latest

# Step 8: Push Frontend Docker image to Docker Hub
- name: Push Frontend Docker image
run: docker push echelonkay/devops-qr-code-frontend:latest
30 changes: 28 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,40 @@ It generates QR Codes for the provided URL, the front-end is in NextJS and the A
The API code exists in the `api` directory. You can run the API server locally:

- Clone this repo
- Create a AWS User with AmazonS3FullAccess
- Create a S3 Bucket and select ACL enable for public access, Uncheck block bucket.
- Make sure you are in the `api` directory
- Create a virtualenv by typing in the following command: `python -m venv .venv`
- Install the required packages: `pip install -r requirements.txt`
- Create a virtualenv by typing in the following command: `python3 -m venv .venv`
- Run command `source .venv/bin`
- Run command `source .venv/bin/activate`
<<<<<<< HEAD
- Install the required packages: `pip install -r requirements.txt` 'or pip install fastapi uvicorn boto3 python-dotenv pytest qrcode'
=======
- Install the required packages: `pip install -r requirements.txt` or `pip install fastapi uvicorn boto3 python-dotenv pytest qrcode`
>>>>>>> 9db5a81 (Moved Terraform files into the infrastructure folder)
- Create a `.env` file, and add you AWS Access and Secret key, check `.env.example`
- and Save the Access & secret key, cat the `.env` to verify.
- Also, change the BUCKET_NAME to your S3 bucket name in `main.py`
- Run the API server: `uvicorn main:app --reload`
- Your API Server should be running on port `http://localhost:8000`

### optional- Troubleshoting S3 bucket
### you might add Bucket policy

{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadGetObject",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::YOUR_BUCKET_NAME/*" # Add bucket name
}
]
}


### Front-end

The front-end code exits in the `front-end-nextjs` directory. You can run the front-end server locally:
Expand Down
65 changes: 65 additions & 0 deletions api/*
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
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()

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

app.add_middleware(
CORSMiddleware,
allow_origins=origins,
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"))

bucket_name = 'capstone-qr-code-bucket' # Add your bucket name here

@app.post("/generate-qr/")
async def generate_qr(url: str):
# Generate QR Code
qr = qrcode.QRCode(
version=1,
error_correction=qrcode.constants.ERROR_CORRECT_L,
box_size=10,
border=4,
)
qr.add_data(url)
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_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')

# 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))

Binary file added api/.DS_Store
Binary file not shown.
4 changes: 2 additions & 2 deletions 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_ACCESS_KEY=Your-AWS-Access-Key
#AWS_SECRET_KEY=Your-AWS-Secret-Access-Key
17 changes: 17 additions & 0 deletions api/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Use the official image as a parent image
FROM python:3.9

# Set the working directory in the container
WORKDIR /urs/src/app

# Copy the dependencies file to the working directory
COPY requirements.txt ./

# Install any needed packages specified in requirements.txt
RUN pip install --no-cache-dir -r requirements.txt

# Copy the content of the local src directory to the working directory
COPY . .

# Run the application
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "80"]
27 changes: 17 additions & 10 deletions api/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@
import boto3
import os
from io import BytesIO

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

# Load environment variables (AWS Access Key and Secret Key)
load_dotenv()

app = FastAPI()

# Allowing CORS for local testing
origins = [
"http://localhost:3000"
"http://localhost:3000" # Update with your frontend URL if needed
]

app.add_middleware(
Expand All @@ -26,10 +26,11 @@
# 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"))
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
bucket_name = 'capstone-qr-code-bucket' # Replace with your S3 bucket name

@app.post("/generate-qr/")
async def generate_qr(url: str):
Expand All @@ -54,12 +55,18 @@ async def generate_qr(url: str):
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')
# Upload to S3 (removed ACL='public-read' as it caused the error)
s3.put_object(
Bucket=bucket_name,
Key=file_name,
Body=img_byte_arr,
ContentType='image/png'
)

# 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))

# Log error and raise HTTPException
raise HTTPException(status_code=500, detail=f"Error generating QR code: {str(e)}")
12 changes: 8 additions & 4 deletions api/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,18 @@
client = TestClient(app)

def test_generate_qr():
url = "http://example.com"
url = "https://example.com" # Use a valid URL here
response = client.post("/generate-qr/", json={"url": url})


# Check that the response status code is 200 (OK)
assert response.status_code == 200

# Check that the response contains the 'qr_code_url' field
assert "qr_code_url" in response.json()

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

assert response.status_code == 422 # FastAPI validation error
# FastAPI validation should return status 422 for invalid input
assert response.status_code == 422
32 changes: 32 additions & 0 deletions front-end-nextjs/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Use the official lightweight Node.js 18 image
FROM node:18-alpine AS base

# Set the working directory in the container
WORKDIR /app

# Copy the package files
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./

# Copy the install-dependencies.sh script
COPY install-dependencies.sh ./

# Install dependencies using the script
RUN chmod +x ./install-dependencies.sh && ./install-dependencies.sh

# Copy the rest of the application code
COPY . .

# Run the Next.js build step
RUN npm run build

# Expose the build folder
RUN mkdir -p .next

# Copy the .next folder (to avoid context issues)
COPY .next ./.next

# Expose the application port
EXPOSE 3000

# Start the application
CMD ["npm", "start"]
17 changes: 17 additions & 0 deletions front-end-nextjs/install-dependencies.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/bin/sh

# Check and install dependencies based on the lockfile available
if [ -f yarn.lock ]; then
echo "Installing dependencies with Yarn..."
yarn install --frozen-lockfile
elif [ -f package-lock.json ]; then
echo "Installing dependencies with npm..."
npm ci
elif [ -f pnpm-lock.yaml ]; then
echo "Installing dependencies with pnpm..."
corepack enable pnpm
pnpm install --frozen-lockfile
else
echo "Error: Lockfile not found. Cannot determine package manager."
exit 1
fi
37 changes: 37 additions & 0 deletions infrastructure/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Local .terraform directories
**/.terraform/*

# .tfstate files
*.tfstate
*.tfstate.*

# Crash log files
crash.log
crash.*.log

# Exclude all .tfvars files, which are likely to contain sensitive data, such as
# password, private keys, and other secrets. These should not be part of version
# control as they are data points which are potentially sensitive and subject
# to change depending on the environment.
*.tfvars
*.tfvars.json

# Ignore override files as they are usually used to override resources locally and so
# are not checked in
override.tf
override.tf.json
*_override.tf
*_override.tf.json

# Ignore transient lock info files created by terraform apply
.terraform.tfstate.lock.info

# Include override files you do wish to add to version control using negated pattern
# !example_override.tf

# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan
# example: *tfplan*

# Ignore CLI configuration files
.terraformrc
terraform.rc
Loading