Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

44 Commits
 
 
 
 
 
 
 
 

Repository files navigation

University ID Replacement Portal

A full-stack web application that allows university students to request student ID card replacements online. Admins review, approve, or reject requests through a dedicated dashboard. The system is built with Django on the backend, Next.js on the frontend, and deployed on AWS.


Table of Contents

  1. Project Overview
  2. Tech Stack
  3. Backend Architecture
  4. AWS Infrastructure
  5. Frontend Architecture
  6. Authentication Flow
  7. Getting Started — Backend
  8. Getting Started — Frontend
  9. Environment Variables
  10. API Reference
  11. Folder Structure

Project Overview

Students often lose or damage their ID cards and need a formal replacement process. This portal replaces paper-based requests with a digital workflow:

  • A student logs in, fills out a replacement form, and uploads a passport photo.
  • An admin reviews the request, views the student's details and photo, then approves or rejects it with a reason.
  • The student can log back in at any time to check the status of their request.

Tech Stack

Layer Technology
Backend API Django 4.x + Django REST Framework
Database PostgreSQL (via AWS RDS)
File Storage AWS S3 (passport photos)
Backend Hosting AWS EC2 (Ubuntu)
Process Manager Gunicorn + Nginx
Frontend Next.js 14 (App Router) + TypeScript
Styling Tailwind CSS
Frontend Hosting Vercel (or EC2)
Auth JWT (djangorestframework-simplejwt)

Backend Architecture

The Django backend follows a standard REST API pattern. It exposes endpoints that the Next.js frontend consumes. Here is how the key pieces fit together:

Browser / Next.js Frontend
        │
        ▼
   Nginx (reverse proxy)
        │
        ▼
  Gunicorn (WSGI server)
        │
        ▼
  Django Application
   ├── REST Framework (API views)
   ├── SimpleJWT (authentication)
   ├── django-storages (S3 uploads)
   └── psycopg2 (PostgreSQL connection)
        │
        ├──▶ PostgreSQL (AWS RDS)
        └──▶ S3 Bucket (photo uploads)

Django Apps

accounts — Handles user registration, login, and JWT token management.

  • Custom user model with a role field (student or admin).
  • Students register with email, student number, and password.
  • Admins are created via the Django admin panel or management command.

id_replace — The core replacement request logic.

  • A student can create a new replacement request.
  • Students can only view their own requests.
  • Admins can view all requests and approve or reject them.
  • Rejection stores a rejection_reason string on the record.

Key Models

# accounts/models.py
class User(AbstractBaseUser):
    email         = models.EmailField(unique=True)
    student_number = models.CharField(max_length=50)
    role          = models.CharField(choices=['student', 'admin'], default='student')

# id_replace/models.py
class IDReplacement(models.Model):
    student       = models.ForeignKey(User, on_delete=models.CASCADE)
    full_name     = models.CharField(max_length=200)
    student_number = models.CharField(max_length=50)
    course        = models.CharField(max_length=200)
    year_of_study = models.IntegerField()
    reason        = models.TextField(blank=True)
    id_photo      = models.ImageField(upload_to='id_photos/')
    status        = models.CharField(choices=['pending','approved','rejected'], default='pending')
    rejection_reason = models.TextField(blank=True)
    created_at    = models.DateTimeField(auto_now_add=True)

API Permissions

  • IsAuthenticated — all routes require a valid JWT token.
  • IsAdminUser — admin-only routes check user.role == 'admin'.
  • Students hitting admin endpoints receive a 403 Forbidden.

AWS Infrastructure

The backend lives entirely on AWS. Here is the setup:

┌─────────────────────────────────────────┐
│              AWS Account                │
│                                         │
│  ┌──────────┐      ┌─────────────────┐  │
│  │  EC2     │      │   RDS           │  │
│  │ (Ubuntu) │─────▶│ (PostgreSQL)    │  │
│  │ Nginx    │      │ Private subnet  │  │
│  │ Gunicorn │      └─────────────────┘  │
│  │ Django   │                           │
│  └────┬─────┘      ┌─────────────────┐  │
│       │            │   S3 Bucket     │  │
│       └───────────▶│ (ID photos)     │  │
│                    └─────────────────┘  │
└─────────────────────────────────────────┘

EC2 (Application Server)

  • OS: Ubuntu 22.04 LTS
  • Instance type: t3.micro (or t3.small for production load)
  • Security group: Allows inbound HTTP (80), HTTPS (443), and SSH (22) only
  • Nginx sits in front and proxies requests to Gunicorn on localhost:8000
  • Gunicorn runs as a systemd service so it restarts automatically on crash or reboot

Nginx config (simplified):

server {
    listen 80;
    server_name your-domain.com;

    location /api/ {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }

    location /media/ {
        # Serve directly from S3 or local media folder
        proxy_pass http://127.0.0.1:8000;
    }
}

Gunicorn systemd service (/etc/systemd/system/gunicorn.service):

[Unit]
Description=Gunicorn daemon for Django
After=network.target

[Service]
User=ubuntu
WorkingDirectory=/home/ubuntu/id-portal-backend
ExecStart=/home/ubuntu/id-portal-backend/venv/bin/gunicorn \
          --workers 3 \
          --bind 127.0.0.1:8000 \
          core.wsgi:application

[Install]
WantedBy=multi-user.target

RDS (Database)

  • Engine: PostgreSQL 15
  • Deployment: Single-AZ (upgrade to Multi-AZ for production)
  • Access: EC2 connects to RDS via a private VPC endpoint — the database is not exposed to the internet
  • Backups: Automated daily snapshots enabled (7-day retention)

S3 (File Storage)

  • Stores all uploaded passport photos
  • Django uses django-storages with the S3Boto3Storage backend to handle uploads automatically
  • Bucket has public read blocked — photos are accessed via presigned URLs or served through the Django API
  • CORS is configured to allow uploads from your frontend domain

Frontend Architecture

The frontend is a Next.js 14 application using the App Router. All pages are client components that talk to the Django API over HTTP.

app/
 ├── page.tsx                  ← Public homepage
 ├── auth/
 │    ├── login/page.tsx       ← Login form
 │    └── register/page.tsx    ← Registration form
 ├── student/
 │    └── replacement/
 │         ├── page.tsx        ← Student's request list
 │         └── new/page.tsx    ← New replacement request form
 └── admin/
      ├── dashboard/page.tsx   ← Admin overview
      └── replacements/
           ├── page.tsx        ← All requests list
           └── [id]/page.tsx   ← Review a single request

State Management

There is no global state library (Redux, Zustand, etc.). Auth state is managed through a custom AuthContext:

AuthContext (React Context)
  ├── user         — the logged-in user object (or null)
  ├── loading      — true while rehydrating from localStorage
  ├── login()      — calls API, stores token + user in localStorage
  ├── logout()     — clears localStorage, resets user to null
  └── register()   — calls API to create a new student account

On every page load, AuthContext reads the token from localStorage and restores the session. The loading flag prevents route guards from firing before this rehydration is complete, which stops the "redirected to login on refresh" problem.

Axios Instance

A shared Axios instance (lib/axios.ts) is configured with:

  • baseURL pointing to the Django API
  • A request interceptor that automatically attaches the JWT token from localStorage to every request header
// lib/axios.ts
const api = axios.create({
  baseURL: process.env.NEXT_PUBLIC_API_URL,
});

api.interceptors.request.use((config) => {
  const token = localStorage.getItem('token');
  if (token) config.headers.Authorization = `Bearer ${token}`;
  return config;
});

Form Validation

Both the Login and Register forms use client-side validation before any API call is made:

  • Required field checks
  • Email format (regex)
  • Student number format (regex)
  • Password minimum length, uppercase, and number requirements
  • Password confirmation match
  • Errors appear field-by-field on blur, not all at once on submit

Authentication Flow

1. Student visits /auth/login
2. Submits email + password
3. Django validates credentials → returns { token, user }
4. Frontend stores token in localStorage
5. AuthContext sets user in React state
6. Router pushes to /student/replacement

--- On page refresh ---

7. AuthContext useEffect reads token from localStorage
8. Sets user back into React state
9. loading becomes false
10. Route guard sees user → stays on protected page

Getting Started — Backend

Prerequisites

  • Python 3.11+
  • PostgreSQL running locally (or use SQLite for development)
  • An AWS account (for S3; can use local media storage for dev)

Setup

# 1. Clone the repository
git clone https://github.com/your-username/id-portal-backend.git
cd id-portal-backend

# 2. Create and activate a virtual environment
python -m venv venv
source venv/bin/activate        # Windows: venv\Scripts\activate

# 3. Install dependencies
pip install -r requirements.txt

# 4. Copy the environment file and fill in your values
cp .env.example .env

# 5. Run database migrations
python manage.py migrate

# 6. Create a superuser (this becomes your first admin)
python manage.py createsuperuser

# 7. Start the development server
python manage.py runserver

The API will be running at http://localhost:8000.


Getting Started — Frontend

Prerequisites

  • Node.js 18+
  • The backend API running (locally or on AWS)

Setup

# 1. Clone the repository
git clone https://github.com/your-username/id-portal-frontend.git
cd id-portal-frontend

# 2. Install dependencies
npm install

# 3. Copy the environment file and fill in your API URL
cp .env.example .env.local

# 4. Start the development server
npm run dev

The app will be running at http://localhost:3000.


Environment Variables

Backend (.env)

SECRET_KEY=your-django-secret-key
DEBUG=False

DATABASE_NAME=id_portal
DATABASE_USER=postgres
DATABASE_PASSWORD=your-db-password
DATABASE_HOST=your-rds-endpoint.amazonaws.com
DATABASE_PORT=5432

AWS_ACCESS_KEY_ID=your-access-key
AWS_SECRET_ACCESS_KEY=your-secret-key
AWS_STORAGE_BUCKET_NAME=your-s3-bucket-name
AWS_S3_REGION_NAME=us-east-1

ALLOWED_HOSTS=your-ec2-ip,your-domain.com
CORS_ALLOWED_ORIGINS=https://your-frontend-domain.com

Frontend (.env.local)

NEXT_PUBLIC_API_URL=https://your-ec2-domain.com/api

API Reference

Auth

Method Endpoint Description Auth
POST /api/auth/register/ Register a new student No
POST /api/auth/login/ Login and receive JWT token No

Student — Replacement Requests

Method Endpoint Description Auth
GET /api/id_replace/ List own requests Student
POST /api/id_replace/ Submit a new request Student

Admin — Replacement Requests

Method Endpoint Description Auth
GET /api/admin/id_replace/ List all requests Admin
GET /api/admin/id_replace/:id/ View a single request Admin
PATCH /api/admin/id_replace/:id/approve/ Approve a request Admin
PATCH /api/admin/id_replace/:id/reject/ Reject with a reason Admin

Folder Structure

Backend

id-portal-backend/
 ├── core/                  ← Django project settings and URL root
 │    ├── settings.py
 │    ├── urls.py
 │    └── wsgi.py
 ├── accounts/              ← Custom user model, register, login
 │    ├── models.py
 │    ├── serializers.py
 │    └── views.py
 ├── id_replace/            ← Replacement request logic
 │    ├── models.py
 │    ├── serializers.py
 │    └── views.py
 ├── requirements.txt
 └── manage.py

Frontend

id-portal-frontend/
 ├── app/                   ← Next.js App Router pages
 ├── components/            ← Shared UI components (Navbar, etc.)
 ├── context/
 │    └── AuthContext.tsx   ← Global auth state
 ├── lib/
 │    └── axios.ts          ← Configured Axios instance
 ├── types/
 │    └── replace.ts        ← TypeScript interfaces
 └── public/                ← Static assets

Deployment Notes

  • Always run python manage.py collectstatic before deploying backend changes to EC2.
  • Restart Gunicorn after any backend code change: sudo systemctl restart gunicorn.
  • Set DEBUG=False and a strong SECRET_KEY in production.
  • Enable HTTPS on EC2 using Certbot (Let's Encrypt) — JWT tokens must not travel over plain HTTP.
  • Set CORS_ALLOWED_ORIGINS to your exact frontend domain — do not use * in production.

Built by Alexander · ga.nyaga7@gmail.com

About

full-stack web application that allows university students to request student ID card replacements online. Admins review, approve, or reject requests through a dedicated dashboard. The system is built with Django on the backend, Next.js on the frontend, and deployed on AWS.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages