A REST API backend for the SkillBridge state-level skilling programme attendance management system. Built with FastAPI, PostgreSQL (Neon), and deployed on Railway.
cd /Users/avinashgiri/Desktop/submission python -m uvicorn src.main:app --host 0.0.0.0 --port 8000
cd /Users/avinashgiri/Desktop/submission DATABASE_URL="sqlite:///./skillbridge.db" python -m uvicorn src.main:app --host 0.0.0.0 --port 8000
Base URL:
https://YOUR-APP-NAME.railway.app(Replace this with your actual Railway/Render URL after deployment)
Interactive Docs: https://YOUR-APP-NAME.railway.app/docs
Assumes Python 3.10+ and pip are installed.
# 1. Clone the repo
git clone https://github.com/YOUR_USERNAME/skillbridge-api.git
cd skillbridge-api
# 2. Create and activate virtualenv
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
# 3. Install dependencies
pip install -r requirements.txt
# 4. Configure environment
cp .env.example .env
# Edit .env and set your DATABASE_URL (Neon PostgreSQL or leave as SQLite for local dev)
# 5. Seed the database
python seed.py
# 6. Run the server
uvicorn src.main:app --reload
# API is now available at http://localhost:8000
# Docs at http://localhost:8000/docsFor local development without PostgreSQL, the app defaults to SQLite (skillbridge_test.db) automatically.
| Role | Password | |
|---|---|---|
| student | student1@example.com | Student@123 |
| trainer | rajan@example.com | Trainer@123 |
| institution | institution1@example.com | Inst@1234 |
| programme_manager | pm@example.com | Manager@123 |
| monitoring_officer | monitor@example.com | Monitor@123 |
pytest tests/ -vTests use a separate SQLite test database and are self-contained. At least two tests (student attendance marking and monitoring token flow) hit a real test database.
Replace BASE with your live URL or http://localhost:8000 for local.
# Signup
curl -X POST $BASE/auth/signup \
-H "Content-Type: application/json" \
-d '{"name":"Test User","email":"test@example.com","password":"Pass@123","role":"student"}'
# Login (returns JWT)
curl -X POST $BASE/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"student1@example.com","password":"Student@123"}'
# Save the token
TOKEN=$(curl -s -X POST $BASE/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"rajan@example.com","password":"Trainer@123"}' | python3 -c "import sys,json;print(json.load(sys.stdin)['access_token'])")
# Get monitoring-scoped token (monitoring officer only)
STANDARD_TOKEN=$(curl -s -X POST $BASE/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"monitor@example.com","password":"Monitor@123"}' | python3 -c "import sys,json;print(json.load(sys.stdin)['access_token'])")
MONITORING_TOKEN=$(curl -s -X POST $BASE/auth/monitoring-token \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $STANDARD_TOKEN" \
-d '{"key":"skillbridge-monitoring-key-2024"}' | python3 -c "import sys,json;print(json.load(sys.stdin)['access_token'])")# Create batch (trainer or institution)
TRAINER_TOKEN=<trainer_token>
curl -X POST $BASE/batches \
-H "Authorization: Bearer $TRAINER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"New Batch","institution_id":1}'
# Generate invite link (trainer)
curl -X POST $BASE/batches/1/invite \
-H "Authorization: Bearer $TRAINER_TOKEN"
# Student joins batch with token
STUDENT_TOKEN=<student_token>
curl -X POST $BASE/batches/join \
-H "Authorization: Bearer $STUDENT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"token":"<invite_token>"}'
# Batch summary (institution)
INST_TOKEN=<institution_token>
curl $BASE/batches/1/summary \
-H "Authorization: Bearer $INST_TOKEN"# Create session (trainer)
curl -X POST $BASE/sessions \
-H "Authorization: Bearer $TRAINER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"title":"HTML Basics","date":"2025-07-01","start_time":"09:00:00","end_time":"11:00:00","batch_id":1}'
# View session attendance (trainer)
curl $BASE/sessions/1/attendance \
-H "Authorization: Bearer $TRAINER_TOKEN"# Mark attendance (student)
curl -X POST $BASE/attendance/mark \
-H "Authorization: Bearer $STUDENT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"session_id":1,"status":"present"}'PM_TOKEN=<pm_token>
# Institution summary
curl $BASE/institutions/1/summary \
-H "Authorization: Bearer $PM_TOKEN"
# Programme-wide summary
curl $BASE/programme/summary \
-H "Authorization: Bearer $PM_TOKEN"# Read-only monitoring attendance (requires scoped token)
curl $BASE/monitoring/attendance \
-H "Authorization: Bearer $MONITORING_TOKEN"{
"sub": "42",
"role": "trainer",
"iat": 1718000000,
"exp": 1718086400
}Expiry: 24 hours (1440 minutes).
{
"sub": "7",
"role": "monitoring_officer",
"token_type": "monitoring_scoped",
"iat": 1718000000,
"exp": 1718003600
}Expiry: 1 hour. Only accepted by GET /monitoring/attendance. All other endpoints reject it with 401.
In a real deployment:
- Store a
token_versionorjti(JWT ID) column on the users table. - On logout or key rotation, increment
token_version. Any token with an older version is rejected. - For the monitoring API key, store it hashed in the database so it can be rotated by updating the record without redeploying.
- Use short-lived access tokens (15 min) + refresh tokens stored server-side for standard roles.
Issue: The MONITORING_API_KEY is hardcoded in .env and compared using a plain string equality check. This is vulnerable to timing attacks.
Fix: Use hmac.compare_digest(provided_key, expected_key) instead of ==, and hash the stored key with bcrypt so even if the .env is leaked the raw key isn't exposed.
A batch can have multiple trainers (e.g., one lead, one assistant). A trainer can also teach multiple batches. The junction table batch_trainers avoids data duplication and supports this flexibility cleanly.
Invite tokens are one-time-use (used boolean) with an expiry (expires_at). This prevents link sharing abuse. The token is a cryptographically random URL-safe string generated by secrets.token_urlsafe(32). Each invite is tied to a specific batch, so a student can't use an invite for the wrong batch.
The Monitoring Officer gets two tokens:
- A standard login token (24h) — cannot access
/monitoring/*. - A scoped monitoring token (1h) — obtained by presenting the standard token + a secret API key. Only this scoped token works on monitoring endpoints.
This design means even if a monitoring officer's login token is stolen, it gives no access to monitoring data without the API key. The 1-hour expiry limits the blast radius if the scoped token is leaked.
A student can only mark attendance once per session. This is enforced in application logic (returning 422 if duplicate), with a future improvement being a DB-level unique constraint on (session_id, student_id).
# Install Railway CLI
npm install -g @railway/cli
# Login and deploy
railway login
railway init
railway up
# Set environment variables
railway variables set DATABASE_URL=postgresql://...
railway variables set SECRET_KEY=your-secret
railway variables set MONITORING_API_KEY=skillbridge-monitoring-key-2024
# Run seed on the deployed instance
railway run python seed.py- Build command:
pip install -r requirements.txt - Start command:
uvicorn src.main:app --host 0.0.0.0 --port $PORT - Add environment variables in the Render dashboard.
Sign up at neon.tech, create a project, copy the connection string and set it as DATABASE_URL.
- All 5 user roles with JWT-based RBAC on every endpoint
POST /auth/signup,POST /auth/login,POST /auth/monitoring-tokenPOST /batches,POST /batches/{id}/invite,POST /batches/joinPOST /sessions,POST /attendance/markGET /sessions/{id}/attendanceGET /batches/{id}/summaryGET /institutions/{id}/summaryGET /programme/summaryGET /monitoring/attendance(scoped token only)- 405 on non-GET
/monitoring/attendance - 422 with descriptive errors for all invalid POST bodies
- 404 for missing foreign keys (batch_id, session_id, institution_id)
- 403 for student marking attendance in unenrolled session
- Seed script with 2 institutions, 4 trainers, 15 students, 3 batches, 8 sessions
- 7 pytest tests (5 required + 2 bonus), 2+ hitting real test DB
- Password hashing with bcrypt via passlib
- Deployment instructions are complete but the live URL requires you to deploy (see above). The app is fully ready to deploy with one
railway upcommand.
- Refresh token rotation (currently single-token, 24h expiry)
- DB-level unique constraint on
attendance(session_id, student_id) - Pagination on list endpoints (monitoring/attendance could be large)
- Rate limiting on auth endpoints
I would add a DB-level unique constraint on (session_id, student_id) in the attendance table from the start. Currently, duplicate attendance is caught in application code, but a race condition (two simultaneous requests) could bypass it. A DB constraint is the only safe guarantee.
/submission
├── CONTACT.txt
├── Procfile
├── railway.json
├── requirements.txt
├── runtime.txt
├── .env.example
├── seed.py
├── README.md
├── /src
│ ├── __init__.py
│ ├── main.py # FastAPI app, router registration, error handlers
│ ├── database.py # SQLAlchemy engine, session, Base
│ ├── models.py # ORM models (User, Batch, Session, Attendance, etc.)
│ ├── schemas.py # Pydantic request/response schemas
│ ├── auth.py # JWT creation/validation, password hashing, dependencies
│ └── /routers
│ ├── auth_router.py
│ ├── batch_router.py
│ ├── session_router.py
│ ├── attendance_router.py
│ ├── institution_router.py
│ ├── programme_router.py
│ └── monitoring_router.py
└── /tests
├── __init__.py
├── conftest.py # TestClient, test DB setup, seeded_db fixture
└── test_api.py # 7 pytest tests