A comprehensive exam management and student counseling platform with real-time performance tracking, OTP-based authentication, and an intuitive admin dashboard.
- Getting Started
- Installation
- Running the Application
- Development
- Features
- API Endpoints
- Configuration
- License
- Docker and Docker Compose
- OR: Go 1.19+, Node.js 16+, and pnpm
- Copy
.env.exampleto.env:
cp .env.example .env- Update the values in
.envas needed for your environment.
docker compose up --buildThis command will:
- Build all Docker images from their respective Dockerfiles
- Start all services (backend, frontend, database, etc.)
- Apply necessary configurations from
.env
If you've already built the images:
docker compose updocker compose up -d --builddocker compose downdocker compose logs -fTo follow logs from a specific service:
docker compose logs -f backend
docker compose logs -f frontendcd backend
go mod download
go run ./cmd/serverThe backend will start on the configured port (typically http://localhost:8080 or similar, check your .env).
cd frontend
pnpm install
pnpm devThe frontend will start on http://localhost:5173 (or the configured dev server port).
Once services are running:
- Web Application: http://localhost
- API Proxy: http://localhost/api
- Backend API: http://localhost:8000 (or configured port)
- Frontend: http://localhost:5173 (or dev server port)
- Backend API: http://localhost:8080 (or configured port)
cd backend
go run ./cmd/serverThe server will auto-reload on file changes if you have a file watcher set up, or use a tool like air:
cd backend
aircd frontend
pnpm install
pnpm dev# Backend tests
cd backend
go test ./...
# Frontend tests
cd frontend
pnpm test- Phone OTP authentication with secure token verification
- Student dashboard with exam history, mistake analysis, and performance metrics
- Admin panel with dynamic model fields and comprehensive management tools
- RTL-ready UI with Vazirmatn font for Persian language support
- Jalali calendar integration for Persian date handling and reporting
- Real-time performance tracking and analytics
- Responsive design for mobile and desktop devices
- Per-exam negative marking support (configurable per exam)
- Rate limiting with response headers
- Strict CORS configuration with development/production awareness
- Atomic exam updates for data consistency
- Blog management system with publish/unpublish functionality
- Comprehensive file upload system (single and multiple files)
- Environment-aware database logging
All endpoints are prefixed with /api/v1/ when accessed through the Nginx proxy.
POST /auth/request-otp- Request one-time password for authenticationPOST /auth/verify-otp- Verify OTP and obtain access tokensPOST /auth/refresh- Refresh access token using refresh token
GET /blog- Get all published blog postsGET /blog/:slug- Get a specific blog post by slug
GET /subjects?major=<major_name>- Get subjects by majorGET /majors- Get all available majors
GET /students/profile- Get current student profile informationPOST /students/profile- Update student profile
GET /exams- Get list of examsGET /exams/:id- Get exam detailsPOST /exams- Create new examPUT /exams/:id- Update exam (supports per-exam negative marking)DELETE /exams/:id- Delete exam
GET /mistakes- Get student mistakes historyPOST /mistakes- Record student mistakesPUT /mistakes/:id- Update a mistake recordDELETE /mistakes/:id- Delete a mistake record
GET /students/performance- Get student performance metricsGET /students/statistics- Get detailed student statisticsGET /students/dashboard- Get dashboard summary
POST /upload- Upload single filePOST /upload/multiple- Upload multiple files
GET /admin/students- Get all studentsGET /admin/students/with-stats- Get all students with statisticsGET /admin/students/:id- Get specific student detailsGET /admin/students/:id/exams- Get student's examsGET /admin/students/:id/mistakes- Get student's mistakesGET /admin/students/:id/performance- Get student's performance recordsGET /admin/students/:id/statistics- Get student's statisticsPUT /admin/students/:id- Update student informationPUT /admin/students/:id/approve- Approve/verify studentDELETE /admin/students/:id- Delete student
POST /admin/students/:id/performance- Create performance record for studentPUT /admin/performance/:id- Update performance recordDELETE /admin/performance/:id- Delete performance record
GET /admin/dynamic-fields- Get all dynamic fieldsPOST /admin/dynamic-fields- Create new dynamic fieldPUT /admin/dynamic-fields/:id- Update dynamic fieldDELETE /admin/dynamic-fields/:id- Delete dynamic field
GET /admin/blog- Get all blog posts (admin view)POST /admin/blog- Create new blog postPUT /admin/blog/:id- Update blog postPUT /admin/blog/:id/publish- Publish/unpublish blog postDELETE /admin/blog/:id- Delete blog post
Required variables in .env:
# JWT Configuration
JWT_SECRET=your_secret_key
JWT_REFRESH_SECRET=your_refresh_secret
JWT_ACCESS_TTL=15m
JWT_REFRESH_TTL=7d
# Environment / OTP
ENVIRONMENT=development
OTP_PROVIDER=mock
EXPOSE_MOCK_OTP=true
# Database Configuration
DATABASE_URL=postgresql://user:password@localhost:5432/noshirvani
# Server Configuration
PORT=8080
ENVIRONMENT=development
JWT_ACCESS_TTL and JWT_REFRESH_TTL accept seconds or duration strings like 15m, 24h, and 7d.
For fully self-hosted fonts without external dependencies:
- Place Vazirmatn font files under
frontend/public/fonts/ - Add @font-face rules in your CSS:
@font-face {
font-family: 'Vazirmatn';
src: url('/fonts/Vazirmatn-Regular.woff2') format('woff2');
}# Copy and update production environment variables
cp .env.example .env.productionEnsure these are set for production:
ENVIRONMENT=productionJWT_SECRET- Use a strong, random secretJWT_REFRESH_SECRET- Use a strong, random secretJWT_ACCESS_TTL- Adjust token lifetime as seconds or duration string like15mJWT_REFRESH_TTL- Adjust refresh token lifetime as seconds or duration string like7dOTP_PROVIDER=smsir-mockis blocked in productionEXPOSE_MOCK_OTP=false- startup should fail if true in productionCORS_ORIGINS- required outside development- Database connection with production credentials
- API keys and external service credentials
OTP_PROVIDER=mockis for local/test only- OTP codes are returned in API responses only when
EXPOSE_MOCK_OTP=true ENVIRONMENT=productionblocks bothOTP_PROVIDER=mockandEXPOSE_MOCK_OTP=true
GET /health- Health check endpoint (returns{"status": "ok"})GET /swagger-doc/doc.json- OpenAPI/Swagger documentation in JSON formatGET /swagger/*- Swagger UI interface
The API implements rate limiting on all routes with response headers:
X-RateLimit-Limit- Maximum requests allowedX-RateLimit-Remaining- Requests remaining in current windowX-RateLimit-Reset- Unix timestamp when the limit resets
400 Bad Request- Invalid request parameters or validation errors401 Unauthorized- Missing or invalid authentication token403 Forbidden- Insufficient permissions (e.g., non-admin accessing admin routes)404 Not Found- Resource not found (for missing resources in write operations)429 Too Many Requests- Rate limit exceeded500 Internal Server Error- Server-side error
CORS is automatically configured based on environment:
- Development/Test: All origins allowed
- Production: Only specified origins in
CORS_ORIGINSenvironment variable are allowed
Separate multiple origins with commas: CORS_ORIGINS=https://example.com,https://app.example.com
-
Request OTP: Call
POST /auth/request-otpwith phone number- Response includes OTP if
EXPOSE_MOCK_OTP=true(development only)
- Response includes OTP if
-
Verify OTP: Call
POST /auth/verify-otpwith phone and OTP code- Response includes
access_token(JWT) andrefresh_token
- Response includes
-
Use Token: Include token in
Authorization: Bearer <token>header for protected routes -
Refresh Token: When access token expires, call
POST /auth/refreshwithrefresh_token- Returns new
access_token
- Returns new
Tokens are JWT-based with configurable TTL:
JWT_ACCESS_TTL- How long access tokens are valid (default: 15m)JWT_REFRESH_TTL- How long refresh tokens are valid (default: 7d)- Both accept duration strings:
15m,24h,7d, or seconds as string
All responses are JSON. Successful responses include data at root level:
{
"id": 1,
"name": "John Doe"
}Error responses include error message:
{
"error": "User not found"
}- Direct backend routes use paths like
/auth/request-otpand/students/profile - Nginx proxy exposes same routes under
/api/v1/* - Swagger UI is accessible at
/swagger/*or through proxy at/api/v1/swagger/*
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --buildProprietary