Table of Contents
- Overview
- System Architecture
- Authentication
- Core Features
- API Endpoints
- Database Schemas
- Business Logic
- Error Handling
- Implementation Checklist
The Zenith Event Management System is a comprehensive platform for managing hackathon/competition events with three main components:
- Main Website: Participant registration, team formation, and submission
- Admin Portal: Participant and team management, evaluator assignment
- Evaluator Portal: Team evaluation and scoring
- Individual and team registration
- Dynamic team formation with unique team codes
- Team discovery (looking for members/teams)
- Submission management (video pitch + PDF)
- Multi-stage evaluation workflow
- RSVP tracking for selected teams
- Backend: Node.js/Express (recommended)
- Database: MongoDB
- Authentication: Firebase Auth
- File Storage: Cloudinary (resumes, profile pictures, submissions)
- Video Hosting: YouTube (validated links)
- Submission Deadline: Configurable deadline after which no submission updates are allowed
- RSVP Requirement: All team members must individually RSVP for team to be finalized
- Participant: Register, form/join teams, submit applications
- Admin: Manage participants, teams, evaluators, and final selections
- Evaluator: Review and score team submissions
All endpoints (except /register and /login) require Firebase authentication token in the request header:
Authorization: Bearer <firebase_token>
- User Registration: Create Firebase user → Store user data in MongoDB
- User Login: Authenticate with Firebase → Return Firebase token + user data
- Subsequent Requests: Include Firebase token in Authorization header
- Token Verification: Backend verifies token with Firebase Admin SDK on each request
// Pseudo-code for authentication middleware
async function authenticateUser(req, res, next) {
try {
const token = req.headers.authorization?.split('Bearer ')[1];
if (!token) {
return res.status(401).json({
success: false,
error: { code: 'AUTH_REQUIRED', message: 'Authentication required' }
});
}
// Verify token with Firebase Admin SDK
const decodedToken = await admin.auth().verifyIdToken(token);
// Fetch user from MongoDB using Firebase UID
const user = await User.findOne({ uid: decodedToken.uid });
if (!user) {
return res.status(404).json({
success: false,
error: { code: 'USER_NOT_FOUND', message: 'User not found' }
});
}
// Attach user to request
req.user = user;
req.firebaseUser = decodedToken;
next();
} catch (error) {
return res.status(401).json({
success: false,
error: { code: 'AUTH_INVALID', message: 'Invalid authentication token' }
});
}
}// Check if user is admin
function requireAdmin(req, res, next) {
if (req.user.role !== 'admin') {
return res.status(403).json({
success: false,
error: {
code: 'FORBIDDEN',
message: 'Admin access required'
}
});
}
next();
}
// Check if user is evaluator
function requireEvaluator(req, res, next) {
if (req.user.role !== 'evaluator') {
return res.status(403).json({
success: false,
error: {
code: 'FORBIDDEN',
message: 'Evaluator access required'
}
});
}
next();
}
// Check if user is team lead
async function requireTeamLead(req, res, next) {
const { teamCode } = req.params || req.body;
const team = await Team.findOne({ teamCode });
if (!team) {
return res.status(404).json({
success: false,
error: { code: 'NOT_FOUND', message: 'Team not found' }
});
}
if (team.teamLead !== req.user.uid) {
return res.status(403).json({
success: false,
error: {
code: 'NOT_TEAM_LEAD',
message: 'Only team lead can perform this action'
}
});
}
req.team = team;
next();
}
// Check if user is team member
async function requireTeamMember(req, res, next) {
const { teamCode } = req.params || req.body;
const team = await Team.findOne({ teamCode });
if (!team) {
return res.status(404).json({
success: false,
error: { code: 'NOT_FOUND', message: 'Team not found' }
});
}
const isMember = team.teamMembers.some(
member => member.uid === req.user.uid
);
if (!isMember) {
return res.status(403).json({
success: false,
error: {
code: 'FORBIDDEN',
message: 'Must be a team member'
}
});
}
req.team = team;
next();
}{
"name": "John Doe",
"bio": "Updated bio text",
"phone": "+1234567890",
"organisation": "Updated University",
"resume": "<base64_encoded_pdf>",
"profile_picture": "<base64_encoded_image>",
"github_link": "https://github.com/johndoe",
"linkedin_link": "https://linkedin.com/in/johndoe",
"portfolio_link": "https://johndoe.dev",
"isLooking": true
}// Public endpoints (no authentication)
app.post('/api/user/register', registerUser);
app.post('/api/user/login', loginUser);
app.get('/api/problem-statements', getProblemStatements);
// Authenticated user endpoints
app.get('/api/user/profile', authenticateUser, getUserProfile);
app.put('/api/user/profile', authenticateUser, updateUserProfile);
// Team lead only endpoints
app.post('/api/team/upload-submission',
authenticateUser,
requireTeamLead,
uploadSubmission
);
app.put('/api/team/remove-member',
authenticateUser,
requireTeamLead,
removeMember
);
// Team member endpoints
app.put('/api/user/rsvp',
authenticateUser,
requireTeamMember,
submitRSVP
);
// Admin only endpoints
app.get('/api/admin/participants',
authenticateUser,
requireAdmin,
getParticipants
);
app.put('/api/admin/finalize-teams',
authenticateUser,
requireAdmin,
finalizeTeams
);
// Evaluator only endpoints
app.get('/api/evaluator/teams',
authenticateUser,
requireEvaluator,
getAssignedTeams
);
app.put('/api/evaluator/evaluate',
authenticateUser,
requireEvaluator,
submitEvaluation
);User roles are stored in MongoDB and synchronized with Firebase custom claims:
// Setting role in MongoDB and Firebase
async function assignRole(uid, role) {
// Update MongoDB
await User.updateOne({ uid }, { role });
// Set Firebase custom claim
await admin.auth().setCustomUserClaims(uid, { role });
return { success: true };
}| Endpoint Category | Participant | Team Lead | Admin | Evaluator |
|---|---|---|---|---|
| Registration/Login | âś… | âś… | âś… | âś… |
| Profile Management | âś… (own) | âś… (own) | âś… (all) | âś… (own) |
| Team Creation | ✅ | - | ✅ | ❌ |
| Join Team | ✅ | - | ✅ | ❌ |
| Leave Team | ✅ | ✅ | ✅ | ❌ |
| Remove Member | ❌ | ✅ (own team) | ✅ | ❌ |
| Upload Submission | ❌ | ✅ (own team) | ✅ | ❌ |
| Submit Application | ❌ | ✅ (own team) | ✅ | ❌ |
| Individual RSVP | ✅ (if selected) | ✅ (if selected) | ✅ | ❌ |
| View All Participants | ❌ | ❌ | ✅ | ❌ |
| View All Teams | ❌ | ❌ | ✅ | ✅ (assigned) |
| Assign Evaluators | ❌ | ❌ | ✅ | ❌ |
| Evaluate Teams | ❌ | ❌ | ✅ | ✅ (assigned) |
| Finalize Selection | ❌ | ❌ | ✅ | ❌ |
- Display complete list with pagination (recommended: 50 per page)
- Individual participant details with expandable view
- Export functionality (CSV/Excel)
- Comprehensive team list with:
- Team submissions (video + PDF)
- Member details
- Evaluation status and scores
- Filtering:
- Status: Selected, Rejected, Waitlisted, Pending
- Problem statement
- RSVP status (Yes/No/Pending)
- Evaluation status (Evaluated/Not Evaluated)
- Sorting:
- By total score (descending)
- By problem statement
- By submission date
- Alphabetical by team name
- Search:
- Team name (fuzzy search)
- Participant name (searches across all team members)
- Team code
- Problem statement
- Single admin account with elevated privileges
- Multiple evaluator accounts
- Assign specific problem statements or teams to evaluators
- Track evaluation progress dashboard
- Override evaluation scores if needed
Team Creation:
- Unique team name validation (real-time check)
- Automatic team code generation (6-8 alphanumeric characters)
- Team lead designation (creator becomes lead)
Team Discovery:
- “Looking for Team” page (individuals seeking teams)
- “Looking for Team Members” page (teams seeking members)
- Discord integration link
Team Operations:
- Join team via encoded team code
- Leave team (updates
isLookingstatus) - Remove member (team lead only)
- Upload video pitch (YouTube link validation)
- Upload PDF submission
- Submit application
- View/edit personal details
- Resume upload/update
- Social profile links management
- Team status display
- Leave team option
- RSVP interface (if selected)
- Toggle “Looking for Team” status
https://api.zenith.example.com/v1
All responses follow this structure:
{
"success": true,
"message": "Operation successful",
"data": {},
"error": null,
"timestamp": "2024-12-26T10:30:00Z"
}Endpoint: POST /api/user/register
Status: âś… Implemented (requires modification)
Description: Register a new user with complete profile information
Request Headers:
Content-Type: application/json
Request Body:
{
"name": "John Doe",
"email": "john@example.com",
"phone": "+1234567890",
"password": "SecurePass123!",
"age": 21,
"organisation": "University XYZ",
"bio": "Passionate developer interested in AI/ML",
"resume": "<base64_encoded_pdf>",
"profile_picture": "<base64_encoded_image>",
"leetcode_profile": "https://leetcode.com/johndoe",
"github_link": "https://github.com/johndoe",
"linkedin_link": "https://linkedin.com/in/johndoe",
"codeforces_link": "https://codeforces.com/profile/johndoe",
"kaggle_link": "https://kaggle.com/johndoe",
"devfolio_link": "https://devfolio.co/@johndoe",
"portfolio_link": "https://johndoe.dev",
"ctf_profile": "https://ctftime.org/user/johndoe",
"isLooking": false
}Validation Rules:
name: Required, 2-100 charactersemail: Required, valid email format, uniquephone: Required, valid phone formatpassword: Required, min 8 characters, must contain uppercase, lowercase, number, special charage: Required, 13-100organisation: Required, 2-200 charactersresume: Optional, PDF format, max 5MBprofile_picture: Optional, JPG/PNG, max 2MB- All profile links: Optional, valid URL format
Response:
{
"message": "Registration successful",
"uid": "iSH69fkohjQlfsBA6fehQYT6f042",
"status": "pending_verification",
"user": {
"uid": "iSH69fkohjQlfsBA6fehQYT6f042",
"email": "john@example.com",
"name": "John Doe",
"isAdmin": false,
"profile_picture": null
}
}Error Responses:
400: Validation error (missing/invalid fields)409: Email already exists500: Server error
Endpoint: POST /api/user/login
{
"message": "Login successful",
"status": "success",
"user": {
"uid": "iSH69fkohjQlfsBA6fehQYT6f042",
"email": "john@example.com",
"name": "John Doe",
"isAdmin": false,
"profile_picture": null,
"status": "active"
},
"token": "eyJhbGciOiJSUzI1NiIsImtpZCI6Ijk4OGQ1YTM3OWI3OGJkZjFlNTBhNDA5MTEzZjJiMGM3NWU0NTJlNDciLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20vemVuaXRoLWZzIiwiYXVkIjoiemVuaXRoLWZzIiwiYXV0aF90aW1lIjoxNzY2ODU3MzQ2LCJ1c2VyX2lkIjoiaVNINjlma29oalFsZnNCQTZmZWhRWVQ2ZjA0MiIsInN1YiI6ImlTSDY5ZmtvaGpRbGZzQkE2ZmVoUVlUNmYwNDIiLCJpYXQiOjE3NjY4NTczNDYsImV4cCI6MTc2Njg2MDk0NiwiZW1haWwiOiJqb2huQGV4YW1wbGUuY29tIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJmaXJlYmFzZSI6eyJpZGVudGl0aWVzIjp7ImVtYWlsIjpbImpvaG5AZXhhbXBsZS5jb20iXX0sInNpZ25faW5fcHJvdmlkZXIiOiJwYXNzd29yZCJ9fQ.IJzRG4lEjLGk6JSenJTRbAf1ZFQi5KT2nQDyDG3ea93oeeAGXXF2_IiSV3a3ghZs_lNUdjLPyKrxDrlb51SpAjqKPoZpCtWkXTJ6IT_ul2OQwDd2d5HUHt6e3W57ir82egwuTxly3rCcTJJ9hzQUC0p7XoqfC6OUZ1PvSrMYs3Hd9kCzi624A-B9zKPltjjn-RDMu5JQrCnp1AWZDe1n2u1vtWiBCO1h1So6eWhjROmyVrTtKaT9TQ8YKfTdNv896vqvwC3jT6YxUVMUCF3Gsve2pyg4w3kFNaxv8wlnJh2J3K9WpTnTCBlyWGDNSOfO0O5ihvOPgh7-HvA4bmY9qA"
}
Status: âś… Implemented
Description: Authenticate user and receive Firebase token
Request Body:
{
"email": "john@example.com",
"password": "SecurePass123!"
}Response:
{
"message": "Login successful",
"status": "success",
"user": {
"uid": "iSH69fkohjQlfsBA6fehQYT6f042",
"email": "john@example.com",
"name": "John Doe",
"isAdmin": false,
"profile_picture": null,
"status": "active"
},
"token": "eyJhbGciOiJSUzI1NiIsImtpZCI6Ijk4OGQ1YTM3OWI3OGJkZjFlNTBhNDA5MTEzZjJiMGM3NWU0NTJlNDciLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20vemVuaXRoLWZzIiwiYXVkIjoiemVuaXRoLWZzIiwiYXV0aF90aW1lIjoxNzY2ODU3MzQ2LCJ1c2VyX2lkIjoiaVNINjlma29oalFsZnNCQTZmZWhRWVQ2ZjA0MiIsInN1YiI6ImlTSDY5ZmtvaGpRbGZzQkE2ZmVoUVlUNmYwNDIiLCJpYXQiOjE3NjY4NTczNDYsImV4cCI6MTc2Njg2MDk0NiwiZW1haWwiOiJqb2huQGV4YW1wbGUuY29tIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJmaXJlYmFzZSI6eyJpZGVudGl0aWVzIjp7ImVtYWlsIjpbImpvaG5AZXhhbXBsZS5jb20iXX0sInNpZ25faW5fcHJvdmlkZXIiOiJwYXNzd29yZCJ9fQ.IJzRG4lEjLGk6JSenJTRbAf1ZFQi5KT2nQDyDG3ea93oeeAGXXF2_IiSV3a3ghZs_lNUdjLPyKrxDrlb51SpAjqKPoZpCtWkXTJ6IT_ul2OQwDd2d5HUHt6e3W57ir82egwuTxly3rCcTJJ9hzQUC0p7XoqfC6OUZ1PvSrMYs3Hd9kCzi624A-B9zKPltjjn-RDMu5JQrCnp1AWZDe1n2u1vtWiBCO1h1So6eWhjROmyVrTtKaT9TQ8YKfTdNv896vqvwC3jT6YxUVMUCF3Gsve2pyg4w3kFNaxv8wlnJh2J3K9WpTnTCBlyWGDNSOfO0O5ihvOPgh7-HvA4bmY9qA"
}Error Responses:
401: Invalid credentials404: User not found500: Server error
Endpoint: GET /api/user/profile
Status: âś… Implemented
Authentication: Required
Description: Retrieve authenticated user’s complete profile
Response:
{
"uid": "AqlFsLz08URECXtKRkJ7iFh20Q32",
"name": "Test User 1766686366",
"email": "testuser1766686366@example.com",
"phone": "+91-9876506366",
"resume_link": "https://res.cloudinary.com/digfe37ob/raw/upload/v1766686368/resumes/a ymrbkp3zpjppvulyf.pdf",
"profile_picture": null,
"leetcode_profile": null,
"github_link": "https://github.com/test",
"linkedin_link": "https://linkedin.com/in/test",
"codeforces_link": null,
"kaggle_link": null,
"devfolio_link": null,
"portfolio_link": null,
"ctf_profile": null,
"bio": "Test bio",
"age": 25,
"organisation": "Test Org",
"role": "user",
"isLooking": false
}Endpoint: PUT /api/user/profile
Status: âś… Implemented
Authentication: Required
Description: Update user profile information
Request Body:
{
"name": "John Doe",
"bio": "Updated bio text",
"phone": "+1234567890",
"organisation": "Updated University",
"resume": "<base64_encoded_pdf>",
"profile_picture": "<base64_encoded_image>",
"github_link": "https://github.com/johndoe",
"linkedin_link": "https://linkedin.com/in/johndoe",
"portfolio_link": "https://johndoe.dev",
"isLooking": true
}Note: All fields are optional. Only provided fields will be updated.
Response:
{
"message": "User updated successfully",
"id": "695018baef60ad62faa46882",
"uid": "iSH69fkohjQlfsBA6fehQYT6f042",
"status": "success"
}Endpoint: GET /api/user/looking-for-team
Authentication: Required
Description: Get list of individuals looking for teams
Query Parameters:
page: Page number (default: 1)limit: Results per page (default: 20, max: 100)organisation: Filter by organisationskills: Filter by skills (comma-separated)
Response:
{
"success": true,
"data": {
"users": [
{
"id": "KcwYhPfmgpZlilYqQJLDF1yXfDK2",
"name": "Second User",
"email": "seconduser@example.com",
"organisation": "Another University",
"bio": "I am a second test user",
"profile_picture": null,
"github_link": "https://github.com/seconduser",
"linkedin_link": "https://linkedin.com/in/seconduser",
"leetcode_profile": null
},
{
"id": "tq6fDy9DDAOINEdrCPB6NuDLUKF2",
"name": "Updated Test User",
"email": "testuser@example.com",
"organisation": "Updated Test Org",
"bio": "This is an updated bio",
"profile_picture": null,
"github_link": null,
"linkedin_link": null,
"leetcode_profile": null
}
],
"pagination": {
"currentPage": 1,
"totalPages": 1,
"totalUsers": 2,
"limit": 20
}
}
}Endpoint: PUT /api/user/looking-for-team
Authentication: Required
Description: Toggle user’s “looking for team” status
Request Body:
{
"isLooking": true
}Validation:
- User must not be part of a team to set
isLooking: true - Automatically sets to
falsewhen user joins a team
Response:
{
"success": true,
"message": "Status updated successfully",
"data": {
"isLooking": true
}
}Endpoint: POST /api/team/create
Status: đź”´ To be implemented
Authentication: Required
Description: Create a new team with unique name and code
Request Body:
{
"teamName": "Code Warriors",
"appliedFor": "problem_statement_id",
"isLooking": false
}Validation:
- User must not be part of any team
- Team name must be unique (case-insensitive)
- Problem statement must exist
Response:
{
"success": true,
"message": "Team created successfully",
"data": {
"teamCode": "ZN7K4X",
"teamName": "Code Warriors",
"teamLead": "user_id",
"teamMembers": [
{
"id": "user_id",
"name": "John Doe",
"role": "Team Lead"
}
],
"appliedFor": "problem_statement_id",
"isLooking": false
}
}Error Responses:
400: User already in a team409: Team name already exists404: Problem statement not found
Endpoint: GET /api/team/looking-for-members
Authentication: Required
Description: Get list of teams looking for members
Query Parameters:
page: Page number (default: 1)limit: Results per page (default: 20, max: 100)appliedFor: Filter by problem statement ID
Response:
{
"success": true,
"data": {
"teams": [
{
"teamCode": "ZN7K4X",
"teamName": "Code Warriors",
"teamLead": {
"id": "user_id",
"name": "John Doe"
},
"teamMembers": [
{
"id": "user_id",
"name": "John Doe",
"organisation": "University XYZ"
}
],
"currentMemberCount": 1,
"maxMembers": 4,
"appliedFor": {
"id": "ps_id",
"title": "AI-Powered Healthcare"
},
"isLooking": true
}
],
"pagination": {
"currentPage": 1,
"totalPages": 3,
"totalTeams": 45,
"limit": 20
}
}
}Endpoint: PUT /api/team/looking-for-members
Authentication: Required
Description: Toggle team’s “looking for members” status (team lead only)
Request Body:
{
"teamCode": "ZN7K4X",
"isLooking": true
}Validation:
- User must be team lead
- Team must exist
Response:
{
"success": true,
"message": "Team status updated successfully",
"data": {
"teamCode": "ZN7K4X",
"isLooking": true
}
}Error Responses:
403: User is not team lead404: Team not found
Endpoint: PUT /api/team/join
Authentication: Required
Description: Join a team using team code
Request Body:
{
"teamCode": "ZN7K4X"
}Validation:
- User must not be part of any team
- Team must exist
- Team must not be full (max 4 members recommended)
- Team must not have submitted application
Response:
{
"success": true,
"message": "Successfully joined team",
"data": {
"teamCode": "ZN7K4X",
"teamName": "Code Warriors",
"teamMembers": [
{
"id": "user_id_1",
"name": "John Doe",
"role": "Team Lead"
},
{
"id": "user_id_2",
"name": "Jane Smith",
"role": "Member"
}
],
"memberCount": 2
}
}Error Responses:
400: User already in a team404: Invalid team code409: Team is full or already submitted
Endpoint: PUT /api/team/leave
Authentication: Required
Description: Leave current team
Request Body:
{
"setLookingStatus": true
}Business Logic:
- If user is team lead and team has other members: transfer leadership to next member
- If user is team lead and only member: delete team
- If team has submitted: prevent leaving (must be withdrawn first by admin)
- Automatically sets user’s
isLookingbased onsetLookingStatus
Response:
{
"success": true,
"message": "Successfully left team",
"data": {
"formerTeam": "Code Warriors",
"isLooking": true,
"newTeamLead": "user_id_2" // if leadership was transferred
}
}Error Responses:
400: User not part of any team403: Cannot leave after submission
Endpoint: PUT /api/team/remove-member
Authentication: Required
Description: Remove a member from team (team lead only)
Request Body:
{
"teamCode": "ZN7K4X",
"memberId": "user_id_to_remove",
"setTheirLookingStatus": true
}Validation:
- User must be team lead
- Cannot remove self (use leave team instead)
- Cannot remove after submission
Response:
{
"success": true,
"message": "Member removed successfully",
"data": {
"teamCode": "ZN7K4X",
"removedMember": {
"id": "user_id",
"name": "Jane Smith"
},
"currentMemberCount": 2
}
}Error Responses:
403: User is not team lead404: Member not found in team400: Cannot remove after submission
Endpoint: POST /api/team/upload-submission
Authentication: Required
Description: Upload video pitch and PDF submission (team lead only)
Request Headers:
Content-Type: multipart/form-data
Request Body (Form Data):
videoURL: https://www.youtube.com/watch?v=dQw4w9WgXcQ
submissionPDF: <file>
anyOtherLink: https://github.com/team/project
Validation:
- User must be team lead
- videoURL must be valid YouTube link (watch or youtu.be format)
- PDF file max size: 10MB
- Team must not have submitted yet
Response:
{
"success": true,
"message": "Submission uploaded successfully",
"data": {
"teamCode": "ZN7K4X",
"videoURL": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"submissionPDF": "https://res.cloudinary.com/zenith/submissions/team_submission.pdf",
"anyOtherLink": "https://github.com/team/project",
"uploadedAt": "2024-12-15T10:30:00Z"
}
}Endpoint: POST /api/team/submit-application
Authentication: Required
Description: Submit team application for evaluation (team lead only)
Request Body:
{
"teamCode": "ZN7K4X"
}Validation:
- User must be team lead
- Team must have at least 1 member (can be just lead)
- Video pitch and PDF must be uploaded
- Cannot submit if already submitted
Response:
{
"success": true,
"message": "Application submitted successfully",
"data": {
"teamCode": "ZN7K4X",
"teamName": "Code Warriors",
"teamStatus": "submitted",
"submittedAt": "2024-12-15T10:30:00Z",
"membersCount": 3
}
}Error Responses:
403: User is not team lead400: Missing required uploads or invalid team size409: Already submitted
Endpoint: PUT /api/team/update-submission
Authentication: Required (Team Lead only)
Authorization: requireTeamLead middleware
Description: Update submission after initial submission but before evaluation and deadline
Request Body:
{
"teamCode": "ZN7K4X",
"videoURL": "https://www.youtube.com/watch?v=newvideo",
"submissionPDF": "<base64_encoded_pdf>",
"anyOtherLink": "https://github.com/team/updated-project"
}Validation:
- User must be team lead of the team
- Can only update if
isEvaluated: false - Can only update if current date/time is before submission deadline
- Team status must be “submitted”
Deadline Check:
// Check if deadline has passed
const deadline = new Date(process.env.SUBMISSION_DEADLINE);
const now = new Date();
if (now > deadline) {
return res.status(403).json({
success: false,
error: {
code: 'DEADLINE_PASSED',
message: 'Submission deadline has passed. No updates allowed.',
deadline: deadline.toISOString()
}
});
}Response:
{
"success": true,
"message": "Submission updated successfully",
"data": {
"teamCode": "ZN7K4X",
"updatedAt": "2024-12-16T14:20:00Z",
"deadline": "2024-12-20T23:59:59Z"
}
}Error Responses:
403: User is not team lead403: Deadline has passed (DEADLINE_PASSED)403: Team already evaluated400: Invalid status for update
Endpoint: PUT /api/user/rsvp
Authentication: Required (Team Member)
Authorization: requireTeamMember middleware (must be part of shortlisted team)
Description: Individual team member confirms attendance after team is shortlisted. Team is only fully confirmed when ALL members RSVP.
Request Body:
{
"rsvpStatus": "confirmed"
}Validation:
- User must be part of a team
- Team must be shortlisted (
isShortlisted: true) - Valid rsvpStatus values: “confirmed”, “declined”
- User can only RSVP once (cannot change after submission)
Business Logic:
// 1. Verify team is shortlisted
if (!team.isShortlisted) {
return res.status(400).json({
success: false,
error: {
code: 'TEAM_NOT_SHORTLISTED',
message: 'Team must be shortlisted before RSVP'
}
});
}
// 2. Check if user already RSVPed
const existingRSVP = team.memberRSVPs.find(
rsvp => rsvp.uid === req.user.uid
);
if (existingRSVP) {
return res.status(409).json({
success: false,
error: {
code: 'ALREADY_RSVPED',
message: 'You have already submitted your RSVP'
}
});
}
// 3. Add user's RSVP to team
team.memberRSVPs.push({
uid: req.user.uid,
name: req.user.name,
rsvpStatus: rsvpStatus,
rsvpedAt: new Date()
});
// 4. Check if all members have RSVPed
const allRSVPed = team.teamMembers.every(member =>
team.memberRSVPs.some(rsvp => rsvp.uid === member.uid)
);
// 5. Check if all RSVPs are confirmed
const allConfirmed = allRSVPed && team.memberRSVPs.every(
rsvp => rsvp.rsvpStatus === 'confirmed'
);
// 6. Update team status if all members RSVPed and confirmed
if (allConfirmed) {
team.teamStatus = 'rsvped';
team.rsvpCompletedAt = new Date();
}
// 7. If any member declined, mark team as declined
const anyDeclined = team.memberRSVPs.some(
rsvp => rsvp.rsvpStatus === 'declined'
);
if (anyDeclined) {
team.teamStatus = 'rsvp_declined';
}
await team.save();Response:
{
"success": true,
"message": "RSVP submitted successfully",
"data": {
"userRSVP": {
"uid": "user_id",
"name": "John Doe",
"rsvpStatus": "confirmed",
"rsvpedAt": "2024-12-20T10:00:00Z"
},
"teamStatus": {
"teamCode": "ZN7K4X",
"teamName": "Code Warriors",
"totalMembers": 3,
"rsvpedMembers": 2,
"pendingRSVPs": 1,
"allRSVPed": false,
"teamStatus": "shortlisted",
"memberRSVPs": [
{
"uid": "user_id_1",
"name": "John Doe",
"rsvpStatus": "confirmed",
"rsvpedAt": "2024-12-20T10:00:00Z"
},
{
"uid": "user_id_2",
"name": "Jane Smith",
"rsvpStatus": "confirmed",
"rsvpedAt": "2024-12-20T09:30:00Z"
}
]
}
}
}Response when all members RSVP confirmed:
{
"success": true,
"message": "RSVP submitted successfully. Your team is now fully confirmed!",
"data": {
"userRSVP": {
"uid": "user_id_3",
"name": "Mike Wilson",
"rsvpStatus": "confirmed",
"rsvpedAt": "2024-12-20T11:00:00Z"
},
"teamStatus": {
"teamCode": "ZN7K4X",
"teamName": "Code Warriors",
"totalMembers": 3,
"rsvpedMembers": 3,
"pendingRSVPs": 0,
"allRSVPed": true,
"teamStatus": "rsvped",
"rsvpCompletedAt": "2024-12-20T11:00:00Z",
"memberRSVPs": [
{
"uid": "user_id_1",
"name": "John Doe",
"rsvpStatus": "confirmed",
"rsvpedAt": "2024-12-20T10:00:00Z"
},
{
"uid": "user_id_2",
"name": "Jane Smith",
"rsvpStatus": "confirmed",
"rsvpedAt": "2024-12-20T09:30:00Z"
},
{
"uid": "user_id_3",
"name": "Mike Wilson",
"rsvpStatus": "confirmed",
"rsvpedAt": "2024-12-20T11:00:00Z"
}
]
}
}
}Error Responses:
400: User not part of any team (USER_NOT_IN_TEAM)400: Team not shortlisted (TEAM_NOT_SHORTLISTED)400: Invalid RSVP status (INVALID_RSVP_STATUS)409: Already submitted RSVP (ALREADY_RSVPED)
Notes:
- Each team member must individually RSVP
- Team status changes to “rsvped” only when ALL members confirm
- If any member declines, team status becomes “rsvp_declined”
- Team lead receives notification when all members complete RSVP
- Admin can see RSVP progress for each team
Endpoint: GET /api/user/rsvp-status
Authentication: Required
Description: Get user’s RSVP status and team’s overall RSVP progress
Response:
{
"success": true,
"data": {
"hasRSVPed": true,
"userRSVP": {
"rsvpStatus": "confirmed",
"rsvpedAt": "2024-12-20T10:00:00Z"
},
"team": {
"teamCode": "ZN7K4X",
"teamName": "Code Warriors",
"isShortlisted": true,
"teamStatus": "shortlisted",
"totalMembers": 3,
"rsvpedMembers": 2,
"pendingRSVPs": 1,
"allRSVPed": false,
"memberRSVPs": [
{
"name": "John Doe",
"rsvpStatus": "confirmed",
"rsvpedAt": "2024-12-20T10:00:00Z"
},
{
"name": "Jane Smith",
"rsvpStatus": "confirmed",
"rsvpedAt": "2024-12-20T09:30:00Z"
},
{
"name": "Mike Wilson",
"rsvpStatus": null,
"rsvpedAt": null
}
]
}
}
}All admin endpoints require:
- Authentication:
authenticateUsermiddleware - Authorization:
requireAdminmiddleware
Endpoint: GET /api/admin/participants
Authentication: Required (Admin only)
Description: Get list of all registered participants with filtering and pagination
Query Parameters:
page: Page number (default: 1)limit: Results per page (default: 50, max: 200)search: Search by name or emailorganisation: Filter by organisationhasTeam: Filter by team status (true/false)isLooking: Filter by looking status (true/false)sortBy: Sort field (name, email, createdAt)sortOrder: asc/desc (default: desc)
Response:
{
"success": true,
"data": {
"participants": [
{
"id": "user_id",
"uid": "firebase_uid",
"name": "John Doe",
"email": "john@example.com",
"phone": "+1234567890",
"organisation": "University XYZ",
"age": 21,
"isLooking": false,
"teamCode": "ZN7K4X",
"teamName": "Code Warriors",
"resume_link": "https://res.cloudinary.com/zenith/resume.pdf",
"profile_picture": "https://res.cloudinary.com/zenith/profile.jpg",
"github_link": "https://github.com/johndoe",
"linkedin_link": "https://linkedin.com/in/johndoe",
"createdAt": "2024-12-01T10:30:00Z"
}
],
"pagination": {
"currentPage": 1,
"totalPages": 10,
"totalParticipants": 487,
"limit": 50
},
"stats": {
"totalParticipants": 487,
"withTeams": 312,
"lookingForTeam": 89,
"individual": 86
}
}
}Endpoint: GET /api/admin/participants/:id
Authentication: Required (Admin only)
Description: Get detailed information about specific participant
Response:
{
"success": true,
"data": {
"id": "user_id",
"uid": "firebase_uid",
"name": "John Doe",
"email": "john@example.com",
"phone": "+1234567890",
"age": 21,
"organisation": "University XYZ",
"bio": "Passionate developer...",
"resume_link": "https://res.cloudinary.com/zenith/resume.pdf",
"profile_picture": "https://res.cloudinary.com/zenith/profile.jpg",
"leetcode_profile": "https://leetcode.com/johndoe",
"github_link": "https://github.com/johndoe",
"linkedin_link": "https://linkedin.com/in/johndoe",
"codeforces_link": "https://codeforces.com/profile/johndoe",
"kaggle_link": "https://kaggle.com/johndoe",
"devfolio_link": "https://devfolio.co/@johndoe",
"portfolio_link": "https://johndoe.dev",
"ctf_profile": "https://ctftime.org/user/johndoe",
"isLooking": false,
"teamInfo": {
"teamCode": "ZN7K4X",
"teamName": "Code Warriors",
"role": "Team Lead",
"teamStatus": "submitted"
},
"createdAt": "2024-12-01T10:30:00Z",
"updatedAt": "2024-12-15T14:20:00Z"
}
}Endpoint: GET /api/admin/teams
Authentication: Required (Admin only)
Description: Get list of all teams with comprehensive filtering and sorting
Query Parameters:
page: Page number (default: 1)limit: Results per page (default: 50, max: 200)search: Search by team name or member nameteamStatus: Filter by status (submitted, pending, withdrawn, rsvped)appliedFor: Filter by problem statement IDisShortlisted: Filter by selection status (true/false)isEvaluated: Filter by evaluation status (true/false)evaluator: Filter by evaluator IDrsvpStatus: Filter by RSVP (confirmed/declined/pending)sortBy: Sort field (teamName, totalScore, submittedAt, evaluatedAt)sortOrder: asc/desc (default: desc)minScore: Minimum total score filtermaxScore: Maximum total score filter
Response:
{
"success": true,
"data": {
"teams": [
{
"teamCode": "ZN7K4X",
"teamName": "Code Warriors",
"teamLead": {
"id": "user_id",
"name": "John Doe",
"email": "john@example.com"
},
"teamMembers": [
{
"id": "user_id_1",
"name": "John Doe",
"organisation": "University XYZ",
"role": "Team Lead"
},
{
"id": "user_id_2",
"name": "Jane Smith",
"organisation": "College ABC",
"role": "Member"
}
],
"memberCount": 2,
"appliedFor": {
"id": "ps_id",
"title": "AI-Powered Healthcare"
},
"teamStatus": "submitted",
"isEvaluated": true,
"evaluator": {
"id": "eval_id",
"name": "Dr. Smith"
},
"scores": {
"tech": 85,
"ux": 78,
"presentation": 92,
"total": 255
},
"comments": "Excellent implementation with room for UI improvement",
"isShortlisted": true,
"rsvpStatus": "confirmed",
"videoURL": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"submissionPDF": "https://res.cloudinary.com/zenith/submissions/team.pdf",
"anyOtherLink": "https://github.com/team/project",
"submittedAt": "2024-12-15T10:30:00Z",
"evaluatedAt": "2024-12-18T15:45:00Z"
}
],
"pagination": {
"currentPage": 1,
"totalPages": 6,
"totalTeams": 156,
"limit": 50
},
"stats": {
"totalTeams": 156,
"submitted": 142,
"evaluated": 98,
"shortlisted": 24,
"rsvpConfirmed": 18,
"pendingEvaluation": 44
}
}
}Endpoint: GET /api/admin/teams/:teamCode
Authentication: Required (Admin only)
Description: Get comprehensive details about specific team
Response:
{
"success": true,
"data": {
"teamCode": "ZN7K4X",
"teamName": "Code Warriors",
"teamLead": {
"id": "user_id",
"name": "John Doe",
"email": "john@example.com",
"phone": "+1234567890",
"organisation": "University XYZ",
"resume_link": "https://res.cloudinary.com/zenith/resume.pdf",
"github_link": "https://github.com/johndoe"
},
"teamMembers": [
{
"id": "user_id_1",
"name": "John Doe",
"email": "john@example.com",
"organisation": "University XYZ",
"role": "Team Lead",
"joinedAt": "2024-12-05T10:00:00Z"
},
{
"id": "user_id_2",
"name": "Jane Smith",
"email": "jane@example.com",
"organisation": "College ABC",
"role": "Member",
"joinedAt": "2024-12-06T14:30:00Z"
}
],
"appliedFor": {
"id": "ps_id",
"title": "AI-Powered Healthcare",
"description": "Develop innovative healthcare solutions..."
},
"teamStatus": "submitted",
"isLooking": false,
"isEvaluated": true,
"evaluator": {
"id": "eval_id",
"name": "Dr. Smith",
"email": "drsmith@example.com"
},
"scores": {
"tech": 85,
"ux": 78,
"presentation": 92,
"total": 255
},
"comments": "Excellent implementation with room for UI improvement. Strong technical foundation.",
"isShortlisted": true,
"memberRSVPs": [
{
"uid": "user_id_1",
"name": "John Doe",
"rsvpStatus": "confirmed",
"rsvpedAt": "2024-12-20T10:00:00Z"
},
{
"uid": "user_id_2",
"name": "Jane Smith",
"rsvpStatus": "confirmed",
"rsvpedAt": "2024-12-20T09:30:00Z"
}
],
"rsvpProgress": {
"totalMembers": 2,
"rsvpedMembers": 2,
"pendingRSVPs": 0,
"allRSVPed": true
},
"videoURL": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"submissionPDF": "https://res.cloudinary.com/zenith/submissions/team.pdf",
"anyOtherLink": "https://github.com/team/project",
"createdAt": "2024-12-05T10:00:00Z",
"submittedAt": "2024-12-15T10:30:00Z",
"evaluatedAt": "2024-12-18T15:45:00Z",
"shortlistedAt": "2024-12-19T09:00:00Z",
"rsvpCompletedAt": "2024-12-20T10:00:00Z"
}
}Endpoint: PUT /api/admin/teams/:teamCode
Authentication: Required (Admin only)
Description: Update team status or shortlist status
Request Body:
{
"teamStatus": "withdrawn",
"isShortlisted": false,
"adminNotes": "Team withdrew due to scheduling conflict"
}Valid teamStatus values:
submitted: Initial submissionpending: Under reviewwithdrawn: Admin-withdrawn or team-withdrawnshortlisted: Shortlisted (enables RSVP)rsvped: All members confirmed RSVPrsvp_declined: One or more members declined RSVP
Response:
{
"success": true,
"message": "Team status updated successfully",
"data": {
"teamCode": "ZN7K4X",
"teamStatus": "withdrawn",
"isShortlisted": false,
"updatedAt": "2024-12-20T14:30:00Z"
}
}Endpoint: GET /api/admin/evaluators
Authentication: Required (Admin only)
Description: Get list of all evaluators with their assignment status
Query Parameters:
page: Page number (default: 1)limit: Results per page (default: 50)
Response:
{
"success": true,
"data": {
"evaluators": [
{
"id": "eval_id",
"uid": "firebase_uid",
"name": "Dr. Smith",
"email": "drsmith@example.com",
"assignedTeams": [
{
"teamCode": "ZN7K4X",
"teamName": "Code Warriors",
"isEvaluated": true,
"totalScore": 255
},
{
"teamCode": "AB8K9M",
"teamName": "Tech Innovators",
"isEvaluated": false,
"totalScore": null
}
],
"assignedCount": 2,
"evaluatedCount": 1,
"pendingCount": 1,
"averageScore": 255
}
],
"pagination": {
"currentPage": 1,
"totalPages": 1,
"totalEvaluators": 8,
"limit": 50
},
"stats": {
"totalEvaluators": 8,
"totalAssignments": 156,
"totalEvaluated": 98,
"totalPending": 58
}
}
}Endpoint: PUT /api/admin/evaluators/assign
Authentication: Required (Admin only)
Description: Assign teams to specific evaluator (can be by team codes or problem statement)
Request Body (Option 1 - Specific Teams):
{
"evaluatorId": "eval_id",
"teamCodes": ["ZN7K4X", "AB8K9M", "CD3P5Q"],
"action": "add"
}Request Body (Option 2 - By Problem Statement):
{
"evaluatorId": "eval_id",
"problemStatementId": "ps_id",
"action": "add"
}Valid action values:
add: Assign teams to evaluatorremove: Unassign teams from evaluatorreplace: Replace all current assignments
Response:
{
"success": true,
"message": "Teams assigned successfully",
"data": {
"evaluatorId": "eval_id",
"evaluatorName": "Dr. Smith",
"assignedTeams": ["ZN7K4X", "AB8K9M", "CD3P5Q"],
"totalAssigned": 3,
"previouslyAssigned": 0,
"newAssignments": 3
}
}Endpoint: PUT /api/admin/finalize-teams
Authentication: Required (Admin only)
Description: Finalize shortlist status for teams (typically after all evaluations)
Request Body:
{
"teamCodes": ["ZN7K4X", "AB8K9M", "CD3P5Q"],
"isShortlisted": true,
"notificationEmail": true
}Business Logic:
- Sends notification emails to ALL team members if
notificationEmail: true - Updates team status to “shortlisted” to enable RSVP functionality
- Can shortlist or reject multiple teams at once
- Email notifies members to individually RSVP
Response:
{
"success": true,
"message": "24 teams finalized successfully",
"data": {
"shortlisted": 24,
"rejected": 0,
"notificationsSent": 72,
"teams": [
{
"teamCode": "ZN7K4X",
"teamName": "Code Warriors",
"isShortlisted": true,
"memberCount": 3,
"notificationsSent": 3
}
]
}
}Endpoint: GET /api/admin/export
Authentication: Required (Admin only)
Description: Export participants or teams data to CSV/Excel
Query Parameters:
type: Export type (participants/teams/evaluations)format: File format (csv/xlsx)filters: JSON string of filters (same as list endpoints)
Response:
Content-Type: text/csv or application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
Content-Disposition: attachment; filename="zenith_teams_export_20241226.csv"
[CSV/Excel file content]
All evaluator endpoints require:
- Authentication:
authenticateUsermiddleware - Authorization:
requireEvaluatormiddleware
Additional Authorization:
- Evaluators can only access teams assigned to them
- Assignment verification is done in each endpoint
Endpoint: GET /api/evaluator/teams
Authentication: Required (Evaluator only)
Description: Get list of teams assigned to logged-in evaluator
Query Parameters:
page: Page number (default: 1)limit: Results per page (default: 50)isEvaluated: Filter by evaluation status (true/false)appliedFor: Filter by problem statement IDsortBy: Sort field (teamName, submittedAt, totalScore)sortOrder: asc/desc
Response:
{
"success": true,
"data": {
"teams": [
{
"teamCode": "ZN7K4X",
"teamName": "Code Warriors",
"teamMembers": [
{
"name": "John Doe",
"organisation": "University XYZ"
}
],
"memberCount": 2,
"appliedFor": {
"id": "ps_id",
"title": "AI-Powered Healthcare"
},
"isEvaluated": false,
"videoURL": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"submissionPDF": "https://res.cloudinary.com/zenith/submissions/team.pdf",
"anyOtherLink": "https://github.com/team/project",
"submittedAt": "2024-12-15T10:30:00Z"
}
],
"pagination": {
"currentPage": 1,
"totalPages": 1,
"totalTeams": 18,
"limit": 50
},
"stats": {
"totalAssigned": 18,
"evaluated": 12,
"pending": 6
}
}
}Endpoint: GET /api/evaluator/teams/:teamCode
Authentication: Required (Evaluator only)
Authorization: Team must be assigned to this evaluator
Description: Get detailed team information including complete member profiles for evaluation
Validation:
- Team must be assigned to this evaluator
- Returns error if team is not in evaluator’s assignment list
Authorization Check:
// Verify team is assigned to this evaluator
const evaluator = await Evaluator.findOne({ uid: req.user.uid });
const isAssigned = evaluator.assignedTeams.some(
team => team.teamCode === teamCode
);
if (!isAssigned) {
return res.status(403).json({
success: false,
error: {
code: 'TEAM_NOT_ASSIGNED',
message: 'This team is not assigned to you'
}
});
}Response:
{
"success": true,
"data": {
"teamCode": "ZN7K4X",
"teamName": "Code Warriors",
"teamMembers": [
{
"id": "user_id_1",
"name": "John Doe",
"email": "john@example.com",
"phone": "+1234567890",
"organisation": "University XYZ",
"age": 21,
"bio": "Passionate full-stack developer with 3 years of experience in React and Node.js. Interested in AI/ML applications.",
"role": "Team Lead",
"resume_link": "https://res.cloudinary.com/zenith/resumes/john_doe.pdf",
"profile_picture": "https://res.cloudinary.com/zenith/profiles/john_doe.jpg",
"leetcode_profile": "https://leetcode.com/johndoe",
"github_link": "https://github.com/johndoe",
"linkedin_link": "https://linkedin.com/in/johndoe",
"codeforces_link": "https://codeforces.com/profile/johndoe",
"kaggle_link": "https://kaggle.com/johndoe",
"devfolio_link": "https://devfolio.co/@johndoe",
"portfolio_link": "https://johndoe.dev",
"ctf_profile": "https://ctftime.org/user/johndoe",
"joinedAt": "2024-12-05T10:00:00Z"
},
{
"id": "user_id_2",
"name": "Jane Smith",
"email": "jane@example.com",
"phone": "+1234567891",
"organisation": "College ABC",
"age": 20,
"bio": "UI/UX designer and frontend developer. Specializing in creating intuitive user experiences.",
"role": "Member",
"resume_link": "https://res.cloudinary.com/zenith/resumes/jane_smith.pdf",
"profile_picture": "https://res.cloudinary.com/zenith/profiles/jane_smith.jpg",
"github_link": "https://github.com/janesmith",
"linkedin_link": "https://linkedin.com/in/janesmith",
"portfolio_link": "https://janesmith.design",
"joinedAt": "2024-12-06T14:30:00Z"
},
{
"id": "user_id_3",
"name": "Mike Wilson",
"email": "mike@example.com",
"phone": "+1234567892",
"organisation": "Tech Institute",
"age": 22,
"bio": "Backend engineer with expertise in system architecture and database optimization.",
"role": "Member",
"resume_link": "https://res.cloudinary.com/zenith/resumes/mike_wilson.pdf",
"github_link": "https://github.com/mikewilson",
"linkedin_link": "https://linkedin.com/in/mikewilson",
"codeforces_link": "https://codeforces.com/profile/mikewilson",
"joinedAt": "2024-12-07T09:15:00Z"
}
],
"memberCount": 3,
"appliedFor": {
"id": "ps_id",
"title": "AI-Powered Healthcare",
"description": "Develop innovative healthcare solutions using artificial intelligence to improve accessibility, diagnosis accuracy, or patient care delivery."
},
"videoURL": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"submissionPDF": "https://res.cloudinary.com/zenith/submissions/team.pdf",
"anyOtherLink": "https://github.com/team/project",
"isEvaluated": false,
"scores": null,
"comments": null,
"submittedAt": "2024-12-15T10:30:00Z",
"evaluationCriteria": {
"tech": {
"label": "Technical Implementation",
"maxScore": 100,
"description": "Code quality, architecture, innovation, scalability"
},
"ux": {
"label": "User Experience",
"maxScore": 100,
"description": "UI design, usability, accessibility, user flow"
},
"presentation": {
"label": "Presentation Quality",
"maxScore": 100,
"description": "Video pitch clarity, communication, demo quality"
}
}
}
}Note: Evaluators receive complete profile information for all team members including:
- Full contact details
- Educational background
- Bio and interests
- Resume links (can download and review)
- All social/coding profiles
- Portfolio links
This allows evaluators to:
- Assess team composition and diversity
- Review individual member backgrounds
- Verify technical skills through profile links
- Contact members if clarification is needed
Endpoint: PUT /api/evaluator/evaluate
Authentication: Required (Evaluator only)
Authorization: Team must be assigned to this evaluator
Description: Submit evaluation scores and comments for a team
Request Body:
{
"teamCode": "ZN7K4X",
"scores": {
"tech": 85,
"ux": 78,
"presentation": 92
},
"comments": "Excellent implementation with strong technical foundation. UI could be more polished but overall very impressive work. Video presentation was clear and well-structured."
}Validation:
- Team must be assigned to this evaluator
- Each score must be between 0-100
- Comments are required (min 50 characters recommended)
- Cannot re-evaluate unless admin resets evaluation
Authorization Check:
// Verify team is assigned to this evaluator
const evaluator = await Evaluator.findOne({ uid: req.user.uid });
const isAssigned = evaluator.assignedTeams.some(
team => team.teamCode === teamCode
);
if (!isAssigned) {
return res.status(403).json({
success: false,
error: {
code: 'TEAM_NOT_ASSIGNED',
message: 'This team is not assigned to you'
}
});
}
// Check if already evaluated
if (team.isEvaluated) {
return res.status(409).json({
success: false,
error: {
code: 'ALREADY_EVALUATED',
message: 'This team has already been evaluated by you'
}
});
}Response:
{
"success": true,
"message": "Evaluation submitted successfully",
"data": {
"teamCode": "ZN7K4X",
"teamName": "Code Warriors",
"scores": {
"tech": 85,
"ux": 78,
"presentation": 92,
"total": 255
},
"comments": "Excellent implementation...",
"isEvaluated": true,
"evaluatedAt": "2024-12-18T15:45:00Z",
"evaluatedBy": {
"id": "eval_id",
"name": "Dr. Smith"
}
}
}Error Responses:
403: Team not assigned to evaluator (TEAM_NOT_ASSIGNED)400: Invalid scores or missing comments (VALIDATION_ERROR)409: Already evaluated (ALREADY_EVALUATED)
Endpoint: PUT /api/evaluator/evaluate/:teamCode/update
Authentication: Required (Evaluator only)
Authorization: Must be the evaluator who submitted the original evaluation
Description: Update previously submitted evaluation
Request Body:
{
"scores": {
"tech": 88,
"ux": 80,
"presentation": 92
},
"comments": "Updated evaluation: Excellent implementation with strong technical foundation. After reconsideration, the technical score has been increased."
}Validation:
- Only evaluator who submitted original evaluation can update
- Can only update if admin hasn’t finalized selections
Response:
{
"success": true,
"message": "Evaluation updated successfully",
"data": {
"teamCode": "ZN7K4X",
"scores": {
"tech": 88,
"ux": 80,
"presentation": 92,
"total": 260
},
"previousTotal": 255,
"updatedAt": "2024-12-19T10:15:00Z"
}
}Endpoint: GET /api/problem-statements
Authentication: Optional (public endpoint)
Description: Get list of all active problem statements
Response:
{
"success": true,
"data": {
"problemStatements": [
{
"id": "ps_id_1",
"title": "AI-Powered Healthcare",
"description": "Develop innovative solutions that leverage AI to improve healthcare accessibility, diagnosis accuracy, or patient care delivery.",
"teamCount": 45,
"isActive": true
},
{
"id": "ps_id_2",
"title": "Sustainable Energy Solutions",
"description": "Create technology solutions for renewable energy management and consumption optimization.",
"teamCount": 32,
"isActive": true
},
{
"id": "ps_id_3",
"title": "Financial Inclusion Technology",
"description": "Build platforms that improve financial access and literacy for underserved communities.",
"teamCount": 28,
"isActive": true
}
],
"total": 3
}
}Endpoint: GET /api/problem-statements/:id
Authentication: Optional (public endpoint)
Description: Get detailed information about a specific problem statement
Response:
{
"success": true,
"data": {
"id": "ps_id_1",
"title": "AI-Powered Healthcare",
"description": "Develop innovative solutions that leverage AI to improve healthcare accessibility, diagnosis accuracy, or patient care delivery. Your solution should address real-world healthcare challenges and demonstrate practical implementation.",
"teamCount": 45,
"isActive": true,
"createdAt": "2024-11-01T00:00:00Z"
}
}Endpoint: POST /api/admin/problem-statements
Authentication: Required (Admin only)
Authorization: requireAdmin middleware
Description: Create new problem statement
Request Body:
{
"title": "AI-Powered Healthcare",
"description": "Develop innovative solutions that leverage AI to improve healthcare accessibility, diagnosis accuracy, or patient care delivery.",
"isActive": true
}Validation:
title: Required, unique, 10-200 charactersdescription: Required, 50-1000 charactersisActive: Optional, boolean (default: true)
Response:
{
"success": true,
"message": "Problem statement created successfully",
"data": {
"id": "ps_id_new",
"title": "AI-Powered Healthcare",
"description": "Develop innovative solutions...",
"isActive": true,
"teamCount": 0,
"createdAt": "2024-12-26T10:30:00Z"
}
}Endpoint: PUT /api/admin/problem-statements/:id
Authentication: Required (Admin only)
Authorization: requireAdmin middleware
Description: Update existing problem statement
Request Body:
{
"title": "AI-Powered Healthcare Solutions",
"description": "Updated description text...",
"isActive": false
}Note: All fields are optional. Only provided fields will be updated.
Response:
{
"success": true,
"message": "Problem statement updated successfully",
"data": {
"id": "ps_id_1",
"title": "AI-Powered Healthcare Solutions",
"description": "Updated description text...",
"isActive": false,
"updatedAt": "2024-12-26T11:00:00Z"
}
}{
_id: ObjectId,
uid: String, // Firebase UID (unique, indexed)
email: String, // Unique, indexed
name: String,
phone: String,
age: Number,
organisation: String,
bio: String,
// Profile links (all optional)
resume_link: String, // Cloudinary URL
profile_picture: String, // Cloudinary URL
leetcode_profile: String,
github_link: String,
linkedin_link: String,
codeforces_link: String,
kaggle_link: String,
devfolio_link: String,
portfolio_link: String,
ctf_profile: String,
// Team information
isLooking: Boolean, // Default: false
teamCode: String, // Null if not in team, indexed
// Timestamps
createdAt: Date,
updatedAt: Date,
// Indexes
indexes: [
{ uid: 1 }, // Unique
{ email: 1 }, // Unique
{ teamCode: 1 },
{ isLooking: 1 },
{ createdAt: -1 }
]
}{
_id: ObjectId,
teamCode: String, // Unique 6-8 char code (indexed, unique)
teamName: String, // Unique (indexed, unique)
teamLead: String, // User UID (indexed)
// Team status
isLooking: Boolean, // Looking for members
teamMembers: [ // Array of user UIDs
{
uid: String,
joinedAt: Date,
role: String // "Team Lead" or "Member"
}
],
memberCount: Number, // Denormalized for queries
// Application status
teamStatus: String, // Enum: submitted, pending, withdrawn, shortlisted, rsvped, rsvp_declined
appliedFor: String, // Problem statement ID (indexed)
// Submission
videoURL: String, // YouTube URL
submissionPDF: String, // Cloudinary URL
anyOtherLink: String, // Optional additional link
// Evaluation
isEvaluated: Boolean, // Default: false (indexed)
evaluator: String, // Evaluator UID (indexed)
scores: {
tech: Number, // 0-100
ux: Number, // 0-100
presentation: Number, // 0-100
total: Number // Auto-calculated
},
comments: String,
// Selection and RSVP
isShortlisted: Boolean, // Default: false (indexed)
memberRSVPs: [ // Individual member RSVPs
{
uid: String,
name: String,
rsvpStatus: String, // Enum: confirmed, declined
rsvpedAt: Date
}
],
// Timestamps
createdAt: Date,
submittedAt: Date,
evaluatedAt: Date,
shortlistedAt: Date,
rsvpCompletedAt: Date, // When all members complete RSVP
updatedAt: Date,
// Indexes
indexes: [
{ teamCode: 1 }, // Unique
{ teamName: 1 }, // Unique
{ teamLead: 1 },
{ appliedFor: 1 },
{ evaluator: 1 },
{ isEvaluated: 1 },
{ isShortlisted: 1 },
{ teamStatus: 1 },
{ 'scores.total': -1 }, // For leaderboard/sorting
{ 'memberRSVPs.uid': 1 }, // For RSVP queries
{ createdAt: -1 }
]
}{
_id: ObjectId,
uid: String, // Firebase UID (unique, indexed)
email: String, // Unique
name: String,
role: String, // "admin"
permissions: [String], // Array of permission strings
// Timestamps
createdAt: Date,
lastLoginAt: Date,
// Indexes
indexes: [
{ uid: 1 }, // Unique
{ email: 1 } // Unique
]
}{
_id: ObjectId,
uid: String, // Firebase UID (unique, indexed)
email: String, // Unique
name: String,
role: String, // "evaluator"
// Assignments
assignedTeams: [ // Array of team codes
{
teamCode: String,
assignedAt: Date,
isEvaluated: Boolean
}
],
assignedCount: Number, // Denormalized count
evaluatedCount: Number, // Denormalized count
// Statistics
stats: {
averageScore: Number,
evaluationsCompleted: Number,
evaluationsPending: Number
},
// Timestamps
createdAt: Date,
lastLoginAt: Date,
lastEvaluationAt: Date,
// Indexes
indexes: [
{ uid: 1 }, // Unique
{ email: 1 }, // Unique
{ 'assignedTeams.teamCode': 1 }
]
}{
_id: ObjectId,
title: String, // Unique, required
description: String, // Required (50-1000 characters)
// Metadata
teamCount: Number, // Current team count (denormalized)
isActive: Boolean, // Is accepting applications (default: true)
// Timestamps
createdAt: Date,
updatedAt: Date,
// Indexes
indexes: [
{ title: 1 }, // Unique
{ isActive: 1 },
{ createdAt: -1 }
]
}// Generate unique 6-8 character alphanumeric team code
function generateTeamCode() {
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
const length = Math.floor(Math.random() * 3) + 6; // 6-8 characters
let code = '';
for (let i = 0; i < length; i++) {
code += characters.charAt(Math.floor(Math.random() * characters.length));
}
// Check uniqueness in database
// If exists, regenerate
return code;
}Note: Do NOT use bcrypt for team codes. Use a simple random alphanumeric generator. Bcrypt is for password hashing, not for generating readable codes.
// Check team name uniqueness (case-insensitive)
async function isTeamNameUnique(teamName) {
const existingTeam = await Team.findOne({
teamName: { $regex: new RegExp(`^${teamName}$`, 'i') }
});
return !existingTeam;
}function validateYouTubeURL(url) {
const patterns = [
/^(https?:\/\/)?(www\.)?youtube\.com\/watch\?v=[\w-]+/,
/^(https?:\/\/)?(www\.)?youtu\.be\/[\w-]+/
];
return patterns.some(pattern => pattern.test(url));
}// Resume validation
const resumeValidation = {
allowedFormats: ['application/pdf'],
maxSize: 5 * 1024 * 1024, // 5MB
cloudinaryFolder: 'zenith/resumes'
};
// Profile picture validation
const profilePicValidation = {
allowedFormats: ['image/jpeg', 'image/png', 'image/jpg'],
maxSize: 2 * 1024 * 1024, // 2MB
cloudinaryFolder: 'zenith/profiles'
};
// Submission PDF validation
const submissionPDFValidation = {
allowedFormats: ['application/pdf'],
maxSize: 10 * 1024 * 1024, // 10MB
cloudinaryFolder: 'zenith/submissions'
};function calculateTotalScore(scores) {
const { tech, ux, presentation } = scores;
// Validate individual scores
if (tech < 0 || tech > 100 || ux < 0 || ux > 100 ||
presentation < 0 || presentation > 100) {
throw new Error('Scores must be between 0 and 100');
}
return tech + ux + presentation;
}async function transferTeamLeadership(teamCode, currentLeadUid) {
const team = await Team.findOne({ teamCode });
// Find next member who isn't the current lead
const nextMember = team.teamMembers.find(
member => member.uid !== currentLeadUid
);
if (!nextMember) {
// No other members, delete team
await Team.deleteOne({ teamCode });
return null;
}
// Update team lead
team.teamLead = nextMember.uid;
team.teamMembers = team.teamMembers.map(member => ({
...member,
role: member.uid === nextMember.uid ? 'Team Lead' : 'Member'
}));
await team.save();
return nextMember.uid;
}{
"success": false,
"message": "Error description",
"error": {
"code": "ERROR_CODE",
"details": "Detailed error information",
"field": "fieldName" // For validation errors
},
"timestamp": "2024-12-26T10:30:00Z"
}