Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
c0d81d0
Update release.yml
christiantaylorcyber Jul 24, 2025
41749ea
Update release.yml
christiantaylorcyber Jul 26, 2025
426e3a0
Made sure we correctly capture ip output and apply it in the EC2 depl…
christiantaylorcyber Jul 26, 2025
fe742d6
Update release.yml
christiantaylorcyber Jul 26, 2025
0318a5c
Update release.yml
christiantaylorcyber Jul 26, 2025
e6b937a
Went for a different approach
christiantaylorcyber Jul 26, 2025
81441c6
simplified.... hopefully works!
christiantaylorcyber Jul 26, 2025
64c8133
Fixed the way we get the ec2 ip (hopefully)
christiantaylorcyber Jul 26, 2025
a445c74
Update release.yml
christiantaylorcyber Jul 26, 2025
7775277
Update release.yml
christiantaylorcyber Jul 26, 2025
d0695b5
uploaded project
Jul 26, 2025
80c8b01
Update Dockerfile
christiantaylorcyber Jul 26, 2025
2fdc8b2
added requirements.txt
Jul 26, 2025
af1d7ae
Merge branch 'main' of https://github.com/byui-devops/Group-D
Jul 26, 2025
61a2ee1
Update release.yml
christiantaylorcyber Jul 26, 2025
7d8cfce
Update release.yml
christiantaylorcyber Jul 26, 2025
84f2d7e
Update Dockerfile
christiantaylorcyber Jul 26, 2025
6090d9a
Update main.tf to reflect new project
christiantaylorcyber Jul 26, 2025
193ca53
Update release.yml
christiantaylorcyber Jul 26, 2025
12b3cbe
Update release.yml to make sure EC2 deployment works
christiantaylorcyber Jul 26, 2025
c6b9577
reverted to a previous version of the release.yml
christiantaylorcyber Jul 26, 2025
ad202ae
Update release.yml
christiantaylorcyber Jul 26, 2025
0ac31f6
Update tests.py to contain unit and integration tests
christiantaylorcyber Jul 26, 2025
40cbddc
Create requirements.txt
christiantaylorcyber Jul 26, 2025
3d8bb7a
Update build.yml
christiantaylorcyber Jul 26, 2025
05bcb48
Update build.yml
christiantaylorcyber Jul 26, 2025
b23bf5a
Update build.yml
christiantaylorcyber Jul 26, 2025
94163b5
Delete directory that is no longer needed
christiantaylorcyber Jul 26, 2025
92a13ef
Update build.yml with accurate tests
christiantaylorcyber Jul 26, 2025
20de7c9
fixed the build?
christiantaylorcyber Jul 26, 2025
b68491f
finally fixed?
christiantaylorcyber Jul 26, 2025
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
27 changes: 18 additions & 9 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on:
push:
branches:
- develop
- feature/*
- main
pull_request:
branches:
- develop
Expand All @@ -15,22 +15,31 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Node.js
uses: actions/setup-node@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
node-version: '16'
python-version: '3.9'
- name: Install dependencies
run: npm install
working-directory: scr/CashCat/cashcat_project
run: |
pip install -r requirements.txt
- name: Run tests
run: npm test
continue-on-error: true
working-directory: scr/CashCat/cashcat_project/cashcat
run: |
pytest tests.py
- name: Build Docker image
run: docker build -t group-d .
continue-on-error: true
working-directory: scr/CashCat/cashcat_project
run: |
docker build -t anhmeo/group-d:latest -f Dockerfile .
- name: Log in to Docker Hub
continue-on-error: true
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Tag and push Docker image
- name: Push Docker image
continue-on-error: true
run: |
docker tag group-d anhmeo/group-d:latest
docker push anhmeo/group-d:latest
194 changes: 123 additions & 71 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,73 +1,125 @@
name: Release and Deploy

name: Release and Deploy
on:
push:
branches:
- main
workflow_dispatch:
inputs:
aws_access_key_id:
description: 'AWS Access Key ID'
required: true
type: string
aws_secret_access_key:
description: 'AWS Secret Access Key'
required: true
type: string
aws_session_token:
description: 'AWS Session Token'
required: false
type: string

on:
push:
branches:
- main
workflow_dispatch:
inputs:
aws_access_key_id:
description: 'AWS Access Key ID'
required: true
type: string
aws_secret_access_key:
description: 'AWS Secret Access Key'
required: true
type: string
aws_session_token:
description: 'AWS Session Token'
required: false
type: string

jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: '16'
- name: Install Terraform
uses: hashicorp/setup-terraform@v2
with:
terraform_version: 1.5.0
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ github.event.inputs.aws_access_key_id || secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ github.event.inputs.aws_secret_access_key || secrets.AWS_SECRET_ACCESS_KEY }}
aws-session-token: ${{ github.event.inputs.aws_session_token || secrets.AWS_SESSION_TOKEN }}
aws-region: us-east-1
- name: Terraform Init
working-directory: ./terraform
run: terraform init
- name: Terraform Apply
id: apply
working-directory: ./terraform
run: terraform apply -auto-approve
- name: Log in to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}

- name: Build Docker Image
run: docker build -t anhmeo/group-d:latest .

- name: Push Docker Image
run: docker push anhmeo/group-d:latest

- name: Deploy to EC2
run: |
echo "${{ secrets.EC2_SSH_KEY }}" > key.pem
chmod 600 key.pem
docker pull anhmeo/group-d:latest
ssh -o StrictHostKeyChecking=no -i key.pem ec2-user@${{ steps.apply.outputs.instance_public_ip }} << 'EOF'
docker stop task-api || true
docker rm task-api || true
docker run -d --name task-api -p 80:3000 anhmeo/group-d:latest
EOF
env:
TF_OUTPUT_instance_public_ip: ${{ steps.apply.outputs.instance_public_ip }}
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.9'
- name: Install Terraform
uses: hashicorp/setup-terraform@v2
with:
terraform_version: 1.5.0
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ github.event.inputs.aws_access_key_id || secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ github.event.inputs.aws_secret_access_key || secrets.AWS_SECRET_ACCESS_KEY }}
aws-session-token: ${{ github.event.inputs.aws_session_token || secrets.AWS_SESSION_TOKEN }}
aws-region: us-east-1
- name: Terraform Init
working-directory: ./terraform
run: terraform init
- name: Terraform Apply
id: apply
working-directory: ./terraform
run: terraform apply -auto-approve
- name: Debug Terraform Output
run: echo "Instance IP:$(terraform -chdir=./terraform output -raw instance_public_ip 2>/dev/null)"
- name: Log in to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build Docker Image
run: |
docker build -t anhmeo/group-d:latest -f Dockerfile .
- name: Push Docker Image
run: docker push anhmeo/group-d:latest
- name: Deploy to EC2
run: |
# Extract the IP and filter out debug output
INSTANCE_IP=$(terraform -chdir=./terraform output -raw instance_public_ip 2>/dev/null | grep -oE '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+')
if [ -z "$INSTANCE_IP" ]; then
echo "Error: Failed to extract instance_public_ip"
exit 1
fi
echo "Deploying to IP: $INSTANCE_IP"
# Write SSH key
echo "${{ secrets.EC2_SSH_KEY }}" > key.pem
chmod 600 key.pem
# Pull Docker image
docker pull anhmeo/group-d:latest
# Wait for instance to be reachable
for i in {1..60}; do # Increased to 60 attempts (10 minutes)
if ssh -o StrictHostKeyChecking=no -o ConnectTimeout=5 -i key.pem ec2-user@$INSTANCE_IP exit 0 2>/dev/null; then
echo "SSH port open"
break
fi
echo "Waiting for instance $INSTANCE_IP... ($i/60)"
sleep 10
done
# Deploy via SSH with keep-alive and logging
ssh -o StrictHostKeyChecking=no -o ServerAliveInterval=60 -i key.pem ec2-user@$INSTANCE_IP << 'EOF'
set -e
echo "Starting deployment on $(hostname) at $(date)"
# Install Docker if not present
if ! command -v docker &> /dev/null; then
echo "Installing Docker..."
sudo yum update -y
sudo yum install -y docker
sudo service docker start
sudo usermod -aG docker ec2-user
newgrp docker
else
echo "Docker is already installed"
fi
# Verify Docker daemon
echo "Checking Docker daemon..."
if ! docker info &> /dev/null; then
echo "Error: Cannot connect to Docker daemon"
exit 1
fi
echo "Stopping and removing old CashCat container..."
docker stop CashCat || true
docker rm CashCat || true
echo "Pulling and running new container..."
docker run -d --name CashCat -p 80:8000 anhmeo/group-d:latest
# Check container logs
echo "Container logs:"
docker logs CashCat
# Wait for port 80 to be available
echo "Waiting for port 80..."
for i in {1..30}; do
if nc -z 127.0.0.1 80; then
echo "Port 80 is open"
break
fi
echo "Waiting for port 80... ($i/30)"
sleep 5
done
echo "Deployment complete at $(date)"
EOF
working-directory: ${{ github.workspace }}
22 changes: 16 additions & 6 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
FROM node:16-alpine
# Use an official Python runtime as a parent image
FROM python:3.9-slim

# Set working directory
WORKDIR /app
COPY package.json .
RUN npm install
COPY scr/ ./scr/
EXPOSE 3000
CMD ["npm", "start"]

# Copy the CashCat project
COPY scr/CashCat /app

# Install dependencies
RUN pip install --no-cache-dir -r requirements.txt
RUN pip install gunicorn

# Expose port (Django typically uses 8000, adjust if needed)
EXPOSE 8000

# Run Gunicorn
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "CashCat.wsgi:application"]
78 changes: 78 additions & 0 deletions scr/CashCat/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Overview

As a software engineer, I developed CashCat to deepen my expertise in full-stack web development, particularly in building secure, user-friendly applications with Django and Python. This project allowed me to explore user authentication, database management, and dynamic front-end rendering while applying best practices in software design and project management.

CashCat is a web application designed to help users track their income and expenses. It features a login/signup page, a dashboard displaying financial summaries (balance, total income, total expenses, and category breakdowns), and forms to add, edit, or delete transactions. The app uses Django’s authentication system to ensure user-specific data privacy and SQLite as a lightweight database for storing transactions. The front end is styled with Bootstrap for a responsive and modern interface.

To start a test server on your computer:
1. Ensure Python 3.8+ and Django 5.2+ are installed (`pip install django`).
2. Clone or set up the project directory with the provided files.
3. Navigate to the project directory: `cd cashcat_project`.
4. Apply migrations to set up the database: `python manage.py makemigrations` and `python manage.py migrate`.
5. Create a superuser for admin access: `python manage.py createsuperuser`.
6. Run the development server: `python manage.py runserver`.
7. Open `http://localhost:8000` in a web browser to access the login/signup page, which is the first page of the app.

My purpose for writing CashCat was to build a practical, user-focused application that reinforces my skills in Django’s ORM, authentication, and template rendering, while also learning to integrate front-end frameworks like Bootstrap. This project challenged me to manage database migrations, implement secure user authentication, and create a dynamic, interactive user interface, all of which are critical skills for developing scalable web applications.

[Software Demo Video](https://youtu.be/PXb_4TPBAvA)

# Web Pages

CashCat includes four main web pages, each dynamically generated and interconnected through Django’s URL routing and authentication system:

1. **Login/Signup Page (`http://localhost:8000/`)**:
- **Description**: The entry point of the app, featuring side-by-side forms for users to log in or sign up. It uses Django’s authentication system to validate credentials or create new user accounts. Error messages (e.g., “Invalid username or password”) are dynamically displayed using Django’s messages framework.
- **Dynamic Content**: Form inputs for username and password, error/success messages, and Bootstrap-styled alerts.
- **Transitions**: On successful login, users are redirected to the Dashboard (`/dashboard/`). On signup, a success message prompts users to log in. Unauthenticated users accessing other pages are redirected here.

2. **Dashboard (`http://localhost:8000/dashboard/`)**:
- **Description**: Displays a user’s financial overview, including total balance, income, expenses, a category breakdown table, and a list of recent transactions with edit and delete buttons.
- **Dynamic Content**: The balance, income, and expense totals are calculated using Django’s ORM (`aggregate(Sum('amount'))`). The category table shows aggregated totals per category, and the transactions table lists user-specific transactions with action buttons. Empty states are handled with “No transactions yet” messages.
- **Transitions**: Accessible only to authenticated users (via `@login_required`). Links to “Add Transaction” (`/add/`), “Edit Transaction” (`/edit/<id>/`), “Delete Transaction” (`/delete/<id>/`), and “Logout” (`/logout/`).

3. **Add Transaction (`http://localhost:8000/add/`)**:
- **Description**: A form to add new income or expense transactions, including fields for type, category, amount, and description.
- **Dynamic Content**: The form is generated using Django’s `TransactionForm`, with validation errors displayed dynamically. On successful submission, users are redirected to the Dashboard.
- **Transitions**: Accessible from the Dashboard’s “Add Transaction” link. Includes a “Cancel” button to return to the Dashboard.

4. **Edit Transaction (`http://localhost:8000/edit/<id>/`)**:
- **Description**: A form to edit an existing transaction, pre-filled with the transaction’s current data.
- **Dynamic Content**: The form is populated using `TransactionForm(instance=transaction)`, ensuring user-specific data. Validation errors are shown if inputs are invalid.
- **Transitions**: Accessed via the “Edit” button on the Dashboard. “Save Changes” redirects to the Dashboard, and “Cancel” returns to the Dashboard.

**Navigation**: The navbar, defined in `base.html`, dynamically updates based on authentication status, showing “Login/Signup” for unauthenticated users and “Dashboard”, “Add Transaction”, and “Logout” for authenticated users. Delete actions use a POST request with a confirmation prompt to prevent accidental deletions.

# Development Environment

**Tools**:
- **Visual Studio Code**: Used for writing and debugging Python, HTML, and CSS code, with extensions for Python linting and Django template support.
- **Git**: For version control to track changes and manage project iterations.
- **Command Prompt/PowerShell**: For running Django commands (`manage.py`) and managing the development server.
- **Google Chrome**: For testing the web app and using Developer Tools to inspect HTML/CSS and debug JavaScript.

**Programming Language and Libraries**:
- **Python 3.13.4**: The core programming language for the Django backend.
- **Django 5.2.4**: A high-level Python web framework for rapid development, handling URL routing, ORM, authentication, and template rendering.
- **SQLite**: A lightweight, serverless database included with Django for storing user and transaction data.
- **Bootstrap 5.3**: A front-end framework (loaded via CDN) for responsive styling and components like navbars, forms, and tables.
- **Django’s Built-in Libraries**:
- `django.contrib.auth`: For user authentication and session management.
- `django.contrib.messages`: For displaying success/error messages.
- `django.db.models`: For database queries and aggregations.
- `django.forms`: For form handling and validation.

# Useful Websites

* [Django Documentation](https://docs.djangoproject.com/en/5.2/)
* [Bootstrap Documentation](https://getbootstrap.com/docs/5.3/getting-started/introduction/)
* [Python Documentation](https://docs.python.org/3.13/)
* [Stack Overflow](https://stackoverflow.com/questions/tagged/django)

# Future Work

* **Enhanced Signup Form**: Add fields like email and password confirmation, with stronger validation (e.g., minimum password length, email format).
* **Category Management**: Allow users to create and manage custom categories for transactions, stored in a separate database model.
* **Data Visualization**: Add charts (e.g., using Chart.js) to the Dashboard for visual representation of category breakdowns and spending trends.
* **Password Recovery**: Implement a password reset feature using Django’s built-in password reset views and email functionality.
* **Mobile Optimization**: Improve responsiveness for smaller screens, adjusting form layouts and table displays.
Empty file.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
10 changes: 10 additions & 0 deletions scr/CashCat/cashcat_project/cashcat/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from django.contrib import admin

# Register your models here.
from .models import Transaction

@admin.register(Transaction)
class TransactionAdmin(admin.ModelAdmin):
list_display = ('user', 'transaction_type', 'category', 'amount', 'description', 'date')
list_filter = ('transaction_type', 'category', 'user')
search_fields = ('category', 'description', 'user__username')
6 changes: 6 additions & 0 deletions scr/CashCat/cashcat_project/cashcat/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class CashcatConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'cashcat'
10 changes: 10 additions & 0 deletions scr/CashCat/cashcat_project/cashcat/forms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from django import forms
from .models import Transaction

class TransactionForm(forms.ModelForm):
class Meta:
model = Transaction
fields = ['transaction_type', 'category', 'amount', 'description']
widgets = {
'description': forms.Textarea(attrs={'rows': 4}),
}
Loading
Loading