From 9ef7c0d4322b7c45a253501d7c6e4a4f51fc9cb5 Mon Sep 17 00:00:00 2001 From: Flapjacck Date: Sun, 12 Oct 2025 12:10:59 -0400 Subject: [PATCH 01/11] feat: added auth routes with API response interfaces and utilities --- backend/src/interfaces/apiInterfaces.ts | 38 ++++++++ backend/src/routes/authRoutes.ts | 71 +++++++++++++++ backend/src/routes/index.ts | 30 +++--- backend/src/utils/apiResponse.ts | 116 ++++++++++++++++++++++++ 4 files changed, 243 insertions(+), 12 deletions(-) create mode 100644 backend/src/interfaces/apiInterfaces.ts create mode 100644 backend/src/routes/authRoutes.ts create mode 100644 backend/src/utils/apiResponse.ts diff --git a/backend/src/interfaces/apiInterfaces.ts b/backend/src/interfaces/apiInterfaces.ts new file mode 100644 index 0000000..4a2e9d3 --- /dev/null +++ b/backend/src/interfaces/apiInterfaces.ts @@ -0,0 +1,38 @@ +/** + * Interfaces for API requests and responses + */ + +// API response structure +export interface ApiResponse { + success: boolean; + message?: string; + data?: T; + error?: string; + errors?: any[]; + stack?: string; + meta?: { + pagination?: { + page: number; + limit: number; + total: number; + totalPages: number; + }; + [key: string]: any; + }; +} + +// Pagination parameters +export interface PaginationParams { + page?: number; + limit?: number; + sortBy?: string; + sortDirection?: 'asc' | 'desc'; +} + +// Error response interface +export interface ErrorResponse { + status: number; + message: string; + errors?: any[]; + stack?: string; +} \ No newline at end of file diff --git a/backend/src/routes/authRoutes.ts b/backend/src/routes/authRoutes.ts new file mode 100644 index 0000000..96e9da1 --- /dev/null +++ b/backend/src/routes/authRoutes.ts @@ -0,0 +1,71 @@ +import { Router } from 'express'; +import { ApiResponseUtil } from '../utils/apiResponse'; + +const router = Router(); + +/** + * @route POST /api/auth/register + * @desc Register a new user + * @access Public + */ +router.post('/register', (req, res) => { + // This will be implemented in issue #92 (JWT authentication system) + ApiResponseUtil.success(res, null, 'Register route - will be implemented in issue #92'); +}); + +/** + * @route POST /api/auth/login + * @desc Login a user + * @access Public + */ +router.post('/login', (req, res) => { + // This will be implemented in issue #92 (JWT authentication system) + ApiResponseUtil.success(res, null, 'Login route - will be implemented in issue #92'); +}); + +/** + * @route GET /api/auth/me + * @desc Get current user profile + * @access Private + */ +router.get('/me', (req, res) => { + ApiResponseUtil.success(res, null, 'Current user route - will be implemented in issue #92'); +}); + +/** + * @route POST /api/auth/logout + * @desc Logout a user + * @access Private + */ +router.post('/logout', (req, res) => { + ApiResponseUtil.success(res, null, 'Logout route - will be implemented in issue #92'); +}); + +/** + * @route POST /api/auth/refresh-token + * @desc Refresh access token using refresh token + * @access Public (with refresh token) + */ +router.post('/refresh-token', (req, res) => { + ApiResponseUtil.success(res, null, 'Refresh token route - will be implemented in issue #92'); +}); + +/** + * @route POST /api/auth/forgot-password + * @desc Send password reset email + * @access Public + */ +router.post('/forgot-password', (req, res) => { + ApiResponseUtil.success(res, null, 'Forgot password route - will be implemented in issue #92'); +}); + +/** + * @route POST /api/auth/reset-password + * @desc Reset password with token + * @access Public (with reset token) + */ +router.post('/reset-password', (req, res) => { + ApiResponseUtil.success(res, null, 'Reset password route - will be implemented in issue #92'); +}); + +export default router; \ No newline at end of file diff --git a/backend/src/routes/index.ts b/backend/src/routes/index.ts index 223e918..46d222a 100644 --- a/backend/src/routes/index.ts +++ b/backend/src/routes/index.ts @@ -3,21 +3,26 @@ import mongoose from 'mongoose'; import dotenv from 'dotenv'; import { Router } from 'express'; -// importing status routes +// importing routes import statusRoutes from './statusRoutes'; +import authRoutes from './authRoutes'; const router = Router(); // api root route - returns general api info router.get('/', (req, res) => { - res.status(200).json({ + res.status(200).json({ message: 'api is running', version: '1.0.0', endpoints: { + // System endpoints '/health': 'basic health check', '/test': 'test endpoint', '/status': 'system status check', - '/status/db': 'database connection status' + '/status/db': 'database connection status', + + // API endpoints + '/auth': 'authentication endpoints', } }); }); @@ -31,7 +36,7 @@ router.get('/health', (req, res) => { // mongodb status check router.get('/db-status', (req, res) => { console.log('mongodb status check endpoint hit'); - + try { const state = mongoose.connection.readyState; /* @@ -40,7 +45,7 @@ router.get('/db-status', (req, res) => { 2 = connecting 3 = disconnecting */ - + let status: { connected: boolean; state: string; @@ -50,8 +55,8 @@ router.get('/db-status', (req, res) => { connected: false, state: 'unknown', }; - - switch(state) { + + switch (state) { case 0: status.state = 'disconnected'; status.error = 'Not connected to MongoDB'; @@ -72,7 +77,7 @@ router.get('/db-status', (req, res) => { status.error = 'Disconnecting from MongoDB'; break; } - + res.status(200).json(status); } catch (error: any) { res.status(500).json({ @@ -85,7 +90,7 @@ router.get('/db-status', (req, res) => { // env variables check router.get('/env-check', (req, res) => { console.log('environment variables check endpoint hit'); - + // required env variables const requiredVars = [ 'PORT', @@ -93,9 +98,9 @@ router.get('/env-check', (req, res) => { 'JWT_SECRET', 'JWT_EXPIRES_IN' ]; - + const missingVars = requiredVars.filter(varName => !process.env[varName]); - + res.status(200).json({ valid: missingVars.length === 0, missing: missingVars.length > 0 ? missingVars : undefined, @@ -109,7 +114,8 @@ router.get('/test', (req, res) => { res.status(200).json({ message: 'test endpoint working' }); }); -// mounting status routes +// mounting routes router.use('/status', statusRoutes); +router.use('/auth', authRoutes); export default router; \ No newline at end of file diff --git a/backend/src/utils/apiResponse.ts b/backend/src/utils/apiResponse.ts new file mode 100644 index 0000000..40cef09 --- /dev/null +++ b/backend/src/utils/apiResponse.ts @@ -0,0 +1,116 @@ +import { Response } from 'express'; +import { ApiResponse } from '../interfaces/apiInterfaces'; + +/** + * Class for standardized API responses + */ +export class ApiResponseUtil { + /** + * Send success response + */ + static success( + res: Response, + data?: T, + message = 'Success', + statusCode = 200, + meta?: ApiResponse['meta'] + ): void { + const response: ApiResponse = { + success: true, + message, + data, + }; + + if (meta) { + response.meta = meta; + } + + res.status(statusCode).json(response); + } + + /** + * Send error response + */ + static error( + res: Response, + message = 'Error occurred', + statusCode = 500, + errors?: any[], + stack?: string + ): void { + const response: ApiResponse = { + success: false, + message, + error: message, + }; + + if (errors) { + response.errors = errors; + } + + // Only include stack trace in development + if (process.env.NODE_ENV !== 'production' && stack) { + response.stack = stack; + } + + res.status(statusCode).json(response); + } + + /** + * Send paginated response + */ + static paginated( + res: Response, + data: T[], + page: number, + limit: number, + total: number, + message = 'Success' + ): void { + const totalPages = Math.ceil(total / limit); + + this.success(res, data, message, 200, { + pagination: { + page, + limit, + total, + totalPages, + }, + }); + } + + /** + * Send not found response + */ + static notFound(res: Response, message = 'Resource not found'): void { + this.error(res, message, 404); + } + + /** + * Send unauthorized response + */ + static unauthorized(res: Response, message = 'Unauthorized'): void { + this.error(res, message, 401); + } + + /** + * Send forbidden response + */ + static forbidden(res: Response, message = 'Forbidden'): void { + this.error(res, message, 403); + } + + /** + * Send bad request response + */ + static badRequest(res: Response, message = 'Bad request', errors?: any[]): void { + this.error(res, message, 400, errors); + } + + /** + * Send validation error response + */ + static validationError(res: Response, errors: any[]): void { + this.error(res, 'Validation error', 422, errors); + } +} \ No newline at end of file From 9e6eb79a4065a4a5aa6757d6763b6435f136d4ea Mon Sep 17 00:00:00 2001 From: Flapjacck Date: Sun, 12 Oct 2025 14:17:48 -0400 Subject: [PATCH 02/11] Feat: Added userRoutes --- backend/src/routes/index.ts | 3 ++ backend/src/routes/userRoutes.ts | 66 ++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 backend/src/routes/userRoutes.ts diff --git a/backend/src/routes/index.ts b/backend/src/routes/index.ts index 46d222a..ff268e6 100644 --- a/backend/src/routes/index.ts +++ b/backend/src/routes/index.ts @@ -6,6 +6,7 @@ import { Router } from 'express'; // importing routes import statusRoutes from './statusRoutes'; import authRoutes from './authRoutes'; +import userRoutes from './userRoutes'; const router = Router(); @@ -23,6 +24,7 @@ router.get('/', (req, res) => { // API endpoints '/auth': 'authentication endpoints', + '/users': 'user management endpoints', } }); }); @@ -117,5 +119,6 @@ router.get('/test', (req, res) => { // mounting routes router.use('/status', statusRoutes); router.use('/auth', authRoutes); +router.use('/users', userRoutes); export default router; \ No newline at end of file diff --git a/backend/src/routes/userRoutes.ts b/backend/src/routes/userRoutes.ts new file mode 100644 index 0000000..f71579d --- /dev/null +++ b/backend/src/routes/userRoutes.ts @@ -0,0 +1,66 @@ +import { Router } from 'express'; +import { ApiResponseUtil } from '../utils/apiResponse'; + +const router = Router(); + +/** + * @route GET /api/users + * @desc Get all users (with pagination) + * @access Private (Admin) + */ +router.get('/', (req, res) => { + // Will be implemented in issue #94 (Enhance User model to support multiple roles) + // Mock data + const mockUsers = [ + { id: '1', username: 'user1', role: 'admin' }, + { id: '2', username: 'user2', role: 'interviewer' }, + { id: '3', username: 'user3', role: 'candidate' }, + ]; + + ApiResponseUtil.paginated( + res, + mockUsers, + 1, // page + 10, // limit + 3, // total + 'Get all users - will be implemented in issue #94' + ); +}); + +/** + * @route POST /api/users + * @desc Create a new user (by admin) + * @access Private (Admin) + */ +router.post('/', (req, res) => { + ApiResponseUtil.success(res, null, 'Create user route - will be implemented in issue #94'); +}); + +/** + * @route GET /api/users/:id + * @desc Get user by ID + * @access Private (Admin and Own User) + */ +router.get('/:id', (req, res) => { + ApiResponseUtil.success(res, null, `Get user ${req.params.id} - will be implemented in issue #94`); +}); + +/** + * @route PUT /api/users/:id + * @desc Update user by ID + * @access Private (Admin and Own User) + */ +router.put('/:id', (req, res) => { + ApiResponseUtil.success(res, null, `Update user ${req.params.id} - will be implemented in issue #94`); +}); + +/** + * @route DELETE /api/users/:id + * @desc Delete user by ID + * @access Private (Admin and Own User) + */ +router.delete('/:id', (req, res) => { + ApiResponseUtil.success(res, null, `Delete user ${req.params.id} - will be implemented in issue #94`); +}); + +export default router; \ No newline at end of file From e3fd432f440673c13c672a04565e7c4865d6c752 Mon Sep 17 00:00:00 2001 From: Flapjacck Date: Sun, 12 Oct 2025 14:54:07 -0400 Subject: [PATCH 03/11] Feat: Added teamRoutes --- backend/src/routes/index.ts | 3 ++ backend/src/routes/teamRoutes.ts | 83 ++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 backend/src/routes/teamRoutes.ts diff --git a/backend/src/routes/index.ts b/backend/src/routes/index.ts index ff268e6..7103c7f 100644 --- a/backend/src/routes/index.ts +++ b/backend/src/routes/index.ts @@ -7,6 +7,7 @@ import { Router } from 'express'; import statusRoutes from './statusRoutes'; import authRoutes from './authRoutes'; import userRoutes from './userRoutes'; +import teamRoutes from './teamRoutes'; const router = Router(); @@ -25,6 +26,7 @@ router.get('/', (req, res) => { // API endpoints '/auth': 'authentication endpoints', '/users': 'user management endpoints', + '/teams': 'team management endpoints', } }); }); @@ -120,5 +122,6 @@ router.get('/test', (req, res) => { router.use('/status', statusRoutes); router.use('/auth', authRoutes); router.use('/users', userRoutes); +router.use('/teams', teamRoutes); export default router; \ No newline at end of file diff --git a/backend/src/routes/teamRoutes.ts b/backend/src/routes/teamRoutes.ts new file mode 100644 index 0000000..0f3d89b --- /dev/null +++ b/backend/src/routes/teamRoutes.ts @@ -0,0 +1,83 @@ +import { Router } from 'express'; +import { ApiResponseUtil } from '../utils/apiResponse'; + +const router = Router(); + +/** + * @route GET /api/teams + * @desc Get all teams + * @access Private (Admin) + */ +router.get('/', (req, res) => { + // Will be implemented in issue #93 (Team model and CRUD operations) + ApiResponseUtil.success(res, [], 'Get all teams - will be implemented in issue #93'); +}); + +/** + * @route POST /api/teams + * @desc Create a new team + * @access Private (Admin) + */ +router.post('/', (req, res) => { + ApiResponseUtil.success(res, null, 'Create team route - will be implemented in issue #93'); +}); + +/** + * @route GET /api/teams/:id + * @desc Get team by ID + * @access Private (Admin and Team Members) + */ +router.get('/:id', (req, res) => { + ApiResponseUtil.success(res, null, `Get team ${req.params.id} - will be implemented in issue #93`); +}); + +/** + * @route PUT /api/teams/:id + * @desc Update team by ID + * @access Private (Admin and Team Owner) + */ +router.put('/:id', (req, res) => { + ApiResponseUtil.success(res, null, `Update team ${req.params.id} - will be implemented in issue #93`); +}); + +/** + * @route DELETE /api/teams/:id + * @desc Delete team by ID + * @access Private (Admin and Team Owner) + */ +router.delete('/:id', (req, res) => { + ApiResponseUtil.success(res, null, `Delete team ${req.params.id} - will be implemented in issue #93`); +}); + +/** + * @route GET /api/teams/:id/members + * @desc Get all team members + * @access Private (Admin and Team Members) + */ +router.get('/:id/members', (req, res) => { + ApiResponseUtil.success(res, [], `Get team ${req.params.id} members - will be implemented in issue #93`); +}); + +/** + * @route POST /api/teams/:id/members + * @desc Add member to team + * @access Private (Admin and Team Owner) + */ +router.post('/:id/members', (req, res) => { + ApiResponseUtil.success(res, null, `Add member to team ${req.params.id} - will be implemented in issue #93`); +}); + +/** + * @route DELETE /api/teams/:id/members/:userId + * @desc Remove member from team + * @access Private (Admin and Team Owner) + */ +router.delete('/:id/members/:userId', (req, res) => { + ApiResponseUtil.success( + res, + null, + `Remove member ${req.params.userId} from team ${req.params.id} - will be implemented in issue #93` + ); +}); + +export default router; \ No newline at end of file From 6161d6507e421477c3da3f5ccf477a2f875a24f1 Mon Sep 17 00:00:00 2001 From: Flapjacck Date: Sun, 12 Oct 2025 15:01:21 -0400 Subject: [PATCH 04/11] Feat: Added interviewerRoutes --- backend/src/routes/index.ts | 3 + backend/src/routes/interviewerRoutes.ts | 77 +++++++++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 backend/src/routes/interviewerRoutes.ts diff --git a/backend/src/routes/index.ts b/backend/src/routes/index.ts index 7103c7f..f6b901a 100644 --- a/backend/src/routes/index.ts +++ b/backend/src/routes/index.ts @@ -8,6 +8,7 @@ import statusRoutes from './statusRoutes'; import authRoutes from './authRoutes'; import userRoutes from './userRoutes'; import teamRoutes from './teamRoutes'; +import interviewRoutes from './interviewerRoutes'; const router = Router(); @@ -27,6 +28,7 @@ router.get('/', (req, res) => { '/auth': 'authentication endpoints', '/users': 'user management endpoints', '/teams': 'team management endpoints', + '/interviewers': 'interviewer management endpoints', } }); }); @@ -123,5 +125,6 @@ router.use('/status', statusRoutes); router.use('/auth', authRoutes); router.use('/users', userRoutes); router.use('/teams', teamRoutes); +router.use('/interviewers', interviewRoutes); export default router; \ No newline at end of file diff --git a/backend/src/routes/interviewerRoutes.ts b/backend/src/routes/interviewerRoutes.ts new file mode 100644 index 0000000..46900cd --- /dev/null +++ b/backend/src/routes/interviewerRoutes.ts @@ -0,0 +1,77 @@ +import { Router } from 'express'; +import { ApiResponseUtil } from '../utils/apiResponse'; + +const router = Router(); + +/** + * @route GET /api/interviewers + * @desc Get all interviewers (with pagination) + * @access Private (Admin) + */ +router.get('/', (req, res) => { + // Will be implemented in issue #94 (Enhance User model to support multiple roles) + const mockInterviewers = [ + { id: '1', name: 'Interviewer 1', expertise: ['JavaScript', 'React'] }, + { id: '2', name: 'Interviewer 2', expertise: ['Python', 'Django'] }, + ]; + + ApiResponseUtil.paginated( + res, + mockInterviewers, + 1, // page + 10, // limit + 2, // total + 'Get all interviewers - will be implemented in issue #94' + ); +}); + +/** + * @route POST /api/interviewers + * @desc Create a new interviewer + * @access Private (Admin) + */ +router.post('/', (req, res) => { + ApiResponseUtil.success(res, null, 'Create interviewer route - will be implemented in issue #94'); +}); + +/** + * @route GET /api/interviewers/:id + * @desc Get interviewer by ID + * @access Private (Admin and Own User) + */ +router.get('/:id', (req, res) => { + ApiResponseUtil.success(res, null, `Get interviewer ${req.params.id} - will be implemented in issue #94`); +}); + +/** + * @route PUT /api/interviewers/:id + * @desc Update interviewer by ID + * @access Private (Admin and Own User) + */ +router.put('/:id', (req, res) => { + ApiResponseUtil.success(res, null, `Update interviewer ${req.params.id} - will be implemented in issue #94`); +}); + +/** + * @route DELETE /api/interviewers/:id + * @desc Delete interviewer by ID + * @access Private (Admin) + */ +router.delete('/:id', (req, res) => { + ApiResponseUtil.success(res, null, `Delete interviewer ${req.params.id} - will be implemented in issue #94`); +}); + +/** + * @route GET /api/interviewers/:id/availability + * @desc Get interviewer availability + * @access Private (Admin and Own User) + */ +router.get('/:id/availability', (req, res) => { + ApiResponseUtil.success( + res, + [], + `Get interviewer ${req.params.id} availability - to be implemented` + ); +}); + +export default router; \ No newline at end of file From 156d559c948e1eb6daa0b7186c49f3c538c0ac3f Mon Sep 17 00:00:00 2001 From: Flapjacck Date: Sun, 12 Oct 2025 15:11:00 -0400 Subject: [PATCH 05/11] Feat: Added candidateRoutes --- backend/src/routes/candidateRoutes.ts | 76 +++++++++++++++++++++++++++ backend/src/routes/index.ts | 3 ++ 2 files changed, 79 insertions(+) create mode 100644 backend/src/routes/candidateRoutes.ts diff --git a/backend/src/routes/candidateRoutes.ts b/backend/src/routes/candidateRoutes.ts new file mode 100644 index 0000000..b25cb4b --- /dev/null +++ b/backend/src/routes/candidateRoutes.ts @@ -0,0 +1,76 @@ +import { Router } from 'express'; +import { ApiResponseUtil } from '../utils/apiResponse'; + +const router = Router(); + +/** + * @route GET /api/candidates + * @desc Get all candidates (with pagination) + * @access Private (Admin and Interviewers) + */ +router.get('/', (req, res) => { + const mockCandidates = [ + { id: '1', name: 'Candidate 1', position: 'Frontend Developer' }, + { id: '2', name: 'Candidate 2', position: 'Backend Developer' }, + ]; + + ApiResponseUtil.paginated( + res, + mockCandidates, + 1, // page + 10, // limit + 2, // total + 'Get all candidates - will be implemented in issue #94' + ); +}); + +/** + * @route POST /api/candidates + * @desc Create a new candidate + * @access Private (Admin) + */ +router.post('/', (req, res) => { + ApiResponseUtil.success(res, null, 'Create candidate route - will be implemented in issue #94'); +}); + +/** + * @route GET /api/candidates/:id + * @desc Get candidate by ID + * @access Private (Admin, Interviewer, and Own User) + */ +router.get('/:id', (req, res) => { + ApiResponseUtil.success(res, null, `Get candidate ${req.params.id} - will be implemented in issue #94`); +}); + +/** + * @route PUT /api/candidates/:id + * @desc Update candidate by ID + * @access Private (Admin and Own User) + */ +router.put('/:id', (req, res) => { + ApiResponseUtil.success(res, null, `Update candidate ${req.params.id} - will be implemented in issue #94`); +}); + +/** + * @route DELETE /api/candidates/:id + * @desc Delete candidate by ID + * @access Private (Admin) + */ +router.delete('/:id', (req, res) => { + ApiResponseUtil.success(res, null, `Delete candidate ${req.params.id} - will be implemented in issue #94`); +}); + +/** + * @route GET /api/candidates/:id/availability + * @desc Get candidate availability + * @access Private (Admin, Interviewer, and Own User) + */ +router.get('/:id/availability', (req, res) => { + ApiResponseUtil.success( + res, + [], + `Get candidate ${req.params.id} availability - to be implemented` + ); +}); + +export default router; \ No newline at end of file diff --git a/backend/src/routes/index.ts b/backend/src/routes/index.ts index f6b901a..b6bca16 100644 --- a/backend/src/routes/index.ts +++ b/backend/src/routes/index.ts @@ -9,6 +9,7 @@ import authRoutes from './authRoutes'; import userRoutes from './userRoutes'; import teamRoutes from './teamRoutes'; import interviewRoutes from './interviewerRoutes'; +import candidateRoutes from './candidateRoutes'; const router = Router(); @@ -29,6 +30,7 @@ router.get('/', (req, res) => { '/users': 'user management endpoints', '/teams': 'team management endpoints', '/interviewers': 'interviewer management endpoints', + '/candidates': 'candidate management endpoints', } }); }); @@ -126,5 +128,6 @@ router.use('/auth', authRoutes); router.use('/users', userRoutes); router.use('/teams', teamRoutes); router.use('/interviewers', interviewRoutes); +router.use('/candidates', candidateRoutes); export default router; \ No newline at end of file From efa4974974f9a80f727cfe0f498bfb591540dc74 Mon Sep 17 00:00:00 2001 From: Flapjacck Date: Sun, 12 Oct 2025 15:14:44 -0400 Subject: [PATCH 06/11] Feat: Added groupRoutes --- backend/src/routes/groupRoutes.ts | 94 +++++++++++++++++++++++++++++++ backend/src/routes/index.ts | 3 + 2 files changed, 97 insertions(+) create mode 100644 backend/src/routes/groupRoutes.ts diff --git a/backend/src/routes/groupRoutes.ts b/backend/src/routes/groupRoutes.ts new file mode 100644 index 0000000..0c52f14 --- /dev/null +++ b/backend/src/routes/groupRoutes.ts @@ -0,0 +1,94 @@ +import { Router } from 'express'; +import { ApiResponseUtil } from '../utils/apiResponse'; + +const router = Router(); + +/** + * @route GET /api/groups + * @desc Get all groups (with pagination) + * @access Private (Admin) + */ +router.get('/', (req, res) => { + const mockGroups = [ + { id: '1', name: 'Engineering Group', description: 'For engineering candidates' }, + { id: '2', name: 'Marketing Group', description: 'For marketing candidates' }, + ]; + + ApiResponseUtil.paginated( + res, + mockGroups, + 1, // page + 10, // limit + 2, // total + 'Get all groups' + ); +}); + +/** + * @route POST /api/groups + * @desc Create a new group + * @access Private (Admin) + */ +router.post('/', (req, res) => { + ApiResponseUtil.success(res, null, 'Create group route'); +}); + +/** + * @route GET /api/groups/:id + * @desc Get group by ID + * @access Private (Admin and Team Members) + */ +router.get('/:id', (req, res) => { + ApiResponseUtil.success(res, null, `Get group ${req.params.id}`); +}); + +/** + * @route PUT /api/groups/:id + * @desc Update group by ID + * @access Private (Admin) + */ +router.put('/:id', (req, res) => { + ApiResponseUtil.success(res, null, `Update group ${req.params.id}`); +}); + +/** + * @route DELETE /api/groups/:id + * @desc Delete group by ID + * @access Private (Admin) + */ +router.delete('/:id', (req, res) => { + ApiResponseUtil.success(res, null, `Delete group ${req.params.id}`); +}); + +/** + * @route GET /api/groups/:id/members + * @desc Get all group members + * @access Private (Admin and Team Members) + */ +router.get('/:id/members', (req, res) => { + ApiResponseUtil.success(res, [], `Get group ${req.params.id} members`); +}); + +/** + * @route POST /api/groups/:id/members + * @desc Add member to group + * @access Private (Admin) + */ +router.post('/:id/members', (req, res) => { + ApiResponseUtil.success(res, null, `Add member to group ${req.params.id}`); +}); + +/** + * @route DELETE /api/groups/:id/members/:userId + * @desc Remove member from group + * @access Private (Admin) + */ +router.delete('/:id/members/:userId', (req, res) => { + ApiResponseUtil.success( + res, + null, + `Remove member ${req.params.userId} from group ${req.params.id}` + ); +}); + +export default router; \ No newline at end of file diff --git a/backend/src/routes/index.ts b/backend/src/routes/index.ts index b6bca16..0c2f14f 100644 --- a/backend/src/routes/index.ts +++ b/backend/src/routes/index.ts @@ -10,6 +10,7 @@ import userRoutes from './userRoutes'; import teamRoutes from './teamRoutes'; import interviewRoutes from './interviewerRoutes'; import candidateRoutes from './candidateRoutes'; +import groupRoutes from './groupRoutes'; const router = Router(); @@ -31,6 +32,7 @@ router.get('/', (req, res) => { '/teams': 'team management endpoints', '/interviewers': 'interviewer management endpoints', '/candidates': 'candidate management endpoints', + '/groups': 'group management endpoints', } }); }); @@ -129,5 +131,6 @@ router.use('/users', userRoutes); router.use('/teams', teamRoutes); router.use('/interviewers', interviewRoutes); router.use('/candidates', candidateRoutes); +router.use('/groups', groupRoutes); export default router; \ No newline at end of file From 329f8eb0518890e40a223cadf0efddeb82a35725 Mon Sep 17 00:00:00 2001 From: Flapjacck Date: Sun, 12 Oct 2025 15:19:22 -0400 Subject: [PATCH 07/11] Feat: Added availabilityRoutes --- backend/src/routes/availabilityRoutes.ts | 70 ++++++++++++++++++++++++ backend/src/routes/index.ts | 3 + 2 files changed, 73 insertions(+) create mode 100644 backend/src/routes/availabilityRoutes.ts diff --git a/backend/src/routes/availabilityRoutes.ts b/backend/src/routes/availabilityRoutes.ts new file mode 100644 index 0000000..312c6b1 --- /dev/null +++ b/backend/src/routes/availabilityRoutes.ts @@ -0,0 +1,70 @@ +import { Router } from 'express'; +import { ApiResponseUtil } from '../utils/apiResponse'; + +const router = Router(); + +/** + * @route GET /api/availability + * @desc Get availability for a specific date range and user(s) + * @access Private + */ +router.get('/', (req, res) => { + // Will be implemented later + ApiResponseUtil.success(res, [], 'Get availability'); +}); + +/** + * @route POST /api/availability + * @desc Create or update user availability + * @access Private + */ +router.post('/', (req, res) => { + ApiResponseUtil.success(res, null, 'Create availability route'); +}); + +/** + * @route GET /api/availability/:id + * @desc Get availability by ID + * @access Private (Admin, Owner, and Authorized Team Members) + */ +router.get('/:id', (req, res) => { + ApiResponseUtil.success(res, null, `Get availability ${req.params.id}`); +}); + +/** + * @route PUT /api/availability/:id + * @desc Update availability by ID + * @access Private (Admin and Owner) + */ +router.put('/:id', (req, res) => { + ApiResponseUtil.success(res, null, `Update availability ${req.params.id}`); +}); + +/** + * @route DELETE /api/availability/:id + * @desc Delete availability by ID + * @access Private (Admin and Owner) + */ +router.delete('/:id', (req, res) => { + ApiResponseUtil.success(res, null, `Delete availability ${req.params.id}`); +}); + +/** + * @route GET /api/availability/team/:teamId + * @desc Get availability for an entire team + * @access Private (Admin and Team Members) + */ +router.get('/team/:teamId', (req, res) => { + ApiResponseUtil.success(res, [], `Get team ${req.params.teamId} availability`); +}); + +/** + * @route GET /api/availability/matches + * @desc Find matching availability between interviewers and candidates + * @access Private (Admin and Team Members) + */ +router.get('/matches', (req, res) => { + ApiResponseUtil.success(res, [], 'Get matching availabilities'); +}); + +export default router; \ No newline at end of file diff --git a/backend/src/routes/index.ts b/backend/src/routes/index.ts index 0c2f14f..4cdcc9c 100644 --- a/backend/src/routes/index.ts +++ b/backend/src/routes/index.ts @@ -11,6 +11,7 @@ import teamRoutes from './teamRoutes'; import interviewRoutes from './interviewerRoutes'; import candidateRoutes from './candidateRoutes'; import groupRoutes from './groupRoutes'; +import availablilityRoutes from './availabilityRoutes'; const router = Router(); @@ -33,6 +34,7 @@ router.get('/', (req, res) => { '/interviewers': 'interviewer management endpoints', '/candidates': 'candidate management endpoints', '/groups': 'group management endpoints', + '/availability': 'availability management endpoints', } }); }); @@ -132,5 +134,6 @@ router.use('/teams', teamRoutes); router.use('/interviewers', interviewRoutes); router.use('/candidates', candidateRoutes); router.use('/groups', groupRoutes); +router.use('/availability', availablilityRoutes); export default router; \ No newline at end of file From 2a63e46bef04aef2dd8f011f2fc992dadfddd91d Mon Sep 17 00:00:00 2001 From: Flapjacck Date: Sun, 12 Oct 2025 15:34:33 -0400 Subject: [PATCH 08/11] Feat: Added meetingRoutes --- backend/src/routes/index.ts | 3 + backend/src/routes/meetingRoutes.ts | 99 +++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+) create mode 100644 backend/src/routes/meetingRoutes.ts diff --git a/backend/src/routes/index.ts b/backend/src/routes/index.ts index 4cdcc9c..703b786 100644 --- a/backend/src/routes/index.ts +++ b/backend/src/routes/index.ts @@ -12,6 +12,7 @@ import interviewRoutes from './interviewerRoutes'; import candidateRoutes from './candidateRoutes'; import groupRoutes from './groupRoutes'; import availablilityRoutes from './availabilityRoutes'; +import meetingRoutes from './meetingRoutes'; const router = Router(); @@ -35,6 +36,7 @@ router.get('/', (req, res) => { '/candidates': 'candidate management endpoints', '/groups': 'group management endpoints', '/availability': 'availability management endpoints', + '/meetings': 'meeting management endpoints', } }); }); @@ -135,5 +137,6 @@ router.use('/interviewers', interviewRoutes); router.use('/candidates', candidateRoutes); router.use('/groups', groupRoutes); router.use('/availability', availablilityRoutes); +router.use('/meetings', meetingRoutes); export default router; \ No newline at end of file diff --git a/backend/src/routes/meetingRoutes.ts b/backend/src/routes/meetingRoutes.ts new file mode 100644 index 0000000..076ed32 --- /dev/null +++ b/backend/src/routes/meetingRoutes.ts @@ -0,0 +1,99 @@ +import { Router } from 'express'; +import { ApiResponseUtil } from '../utils/apiResponse'; + +const router = Router(); + +/** + * @route GET /api/meetings + * @desc Get all meetings (with pagination and filters) + * @access Private (Admin, Interviewer, or Candidate) + */ +router.get('/', (req, res) => { + const mockMeetings = [ + { id: '1', title: 'Technical Interview', startTime: '2025-10-15T14:00:00Z', endTime: '2025-10-15T15:00:00Z' }, + { id: '2', title: 'HR Interview', startTime: '2025-10-16T10:00:00Z', endTime: '2025-10-16T11:00:00Z' }, + ]; + + ApiResponseUtil.paginated( + res, + mockMeetings, + 1, // page + 10, // limit + 2, // total + 'Get all meetings' + ); +}); + +/** + * @route POST /api/meetings + * @desc Create a new meeting + * @access Private (Admin) + */ +router.post('/', (req, res) => { + ApiResponseUtil.success(res, null, 'Create meeting route'); +}); + +/** + * @route GET /api/meetings/:id + * @desc Get meeting by ID + * @access Private (Admin, Participants) + */ +router.get('/:id', (req, res) => { + ApiResponseUtil.success(res, null, `Get meeting ${req.params.id}`); +}); + +/** + * @route PUT /api/meetings/:id + * @desc Update meeting by ID + * @access Private (Admin) + */ +router.put('/:id', (req, res) => { + ApiResponseUtil.success(res, null, `Update meeting ${req.params.id}`); +}); + +/** + * @route DELETE /api/meetings/:id + * @desc Delete meeting by ID + * @access Private (Admin) + */ +router.delete('/:id', (req, res) => { + ApiResponseUtil.success(res, null, `Delete meeting ${req.params.id}`); +}); + +/** + * @route POST /api/meetings/:id/confirm + * @desc Confirm participation in a meeting + * @access Private (Meeting Participants) + */ +router.post('/:id/confirm', (req, res) => { + ApiResponseUtil.success(res, null, `Confirm meeting ${req.params.id}`); +}); + +/** + * @route POST /api/meetings/:id/reschedule + * @desc Request rescheduling for a meeting + * @access Private (Meeting Participants) + */ +router.post('/:id/reschedule', (req, res) => { + ApiResponseUtil.success(res, null, `Reschedule meeting ${req.params.id}`); +}); + +/** + * @route GET /api/meetings/user/:userId + * @desc Get all meetings for a specific user + * @access Private (Admin and Own User) + */ +router.get('/user/:userId', (req, res) => { + ApiResponseUtil.success(res, [], `Get meetings for user ${req.params.userId}`); +}); + +/** + * @route GET /api/meetings/team/:teamId + * @desc Get all meetings for a specific team + * @access Private (Admin and Team Members) + */ +router.get('/team/:teamId', (req, res) => { + ApiResponseUtil.success(res, [], `Get meetings for team ${req.params.teamId}`); +}); + +export default router; \ No newline at end of file From a94070a7dac47e0a97891373bf7a99bd7a7fc878 Mon Sep 17 00:00:00 2001 From: Flapjacck Date: Sun, 12 Oct 2025 15:37:00 -0400 Subject: [PATCH 09/11] Feat: Added scheduleRoutes --- backend/src/routes/index.ts | 3 ++ backend/src/routes/scheduleRoutes.ts | 51 ++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 backend/src/routes/scheduleRoutes.ts diff --git a/backend/src/routes/index.ts b/backend/src/routes/index.ts index 703b786..b8aa8d7 100644 --- a/backend/src/routes/index.ts +++ b/backend/src/routes/index.ts @@ -13,6 +13,7 @@ import candidateRoutes from './candidateRoutes'; import groupRoutes from './groupRoutes'; import availablilityRoutes from './availabilityRoutes'; import meetingRoutes from './meetingRoutes'; +import scheduleRoutes from './scheduleRoutes'; const router = Router(); @@ -37,6 +38,7 @@ router.get('/', (req, res) => { '/groups': 'group management endpoints', '/availability': 'availability management endpoints', '/meetings': 'meeting management endpoints', + '/schedules': 'schedule management endpoints' } }); }); @@ -138,5 +140,6 @@ router.use('/candidates', candidateRoutes); router.use('/groups', groupRoutes); router.use('/availability', availablilityRoutes); router.use('/meetings', meetingRoutes); +router.use('/schedules', scheduleRoutes); export default router; \ No newline at end of file diff --git a/backend/src/routes/scheduleRoutes.ts b/backend/src/routes/scheduleRoutes.ts new file mode 100644 index 0000000..7f15756 --- /dev/null +++ b/backend/src/routes/scheduleRoutes.ts @@ -0,0 +1,51 @@ +import { Router } from 'express'; +import { ApiResponseUtil } from '../utils/apiResponse'; + +const router = Router(); + +/** + * @route POST /api/schedule/generate + * @desc Auto-generate optimal schedule based on availabilities + * @access Private (Admin) + */ +router.post('/generate', (req, res) => { + ApiResponseUtil.success(res, null, 'Generate schedule route'); +}); + +/** + * @route GET /api/schedule/team/:teamId + * @desc Get schedule for a team + * @access Private (Admin and Team Members) + */ +router.get('/team/:teamId', (req, res) => { + ApiResponseUtil.success(res, [], `Get schedule for team ${req.params.teamId}`); +}); + +/** + * @route GET /api/schedule/conflicts + * @desc Check for scheduling conflicts + * @access Private (Admin) + */ +router.get('/conflicts', (req, res) => { + ApiResponseUtil.success(res, [], 'Get schedule conflicts'); +}); + +/** + * @route POST /api/schedule/optimize + * @desc Optimize existing schedule + * @access Private (Admin) + */ +router.post('/optimize', (req, res) => { + ApiResponseUtil.success(res, null, 'Optimize schedule route'); +}); + +/** + * @route POST /api/schedule/publish/:teamId + * @desc Publish schedule for a team + * @access Private (Admin) + */ +router.post('/publish/:teamId', (req, res) => { + ApiResponseUtil.success(res, null, `Publish schedule for team ${req.params.teamId}`); +}); + +export default router; \ No newline at end of file From 8664caa25799772e7b659d7d817a14406f0cb3bb Mon Sep 17 00:00:00 2001 From: Flapjacck Date: Wed, 15 Oct 2025 11:56:30 -0400 Subject: [PATCH 10/11] Feat: added api documentation md file --- backend/API_DOCUMENTATION.md | 126 +++++++++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 backend/API_DOCUMENTATION.md diff --git a/backend/API_DOCUMENTATION.md b/backend/API_DOCUMENTATION.md new file mode 100644 index 0000000..27f543d --- /dev/null +++ b/backend/API_DOCUMENTATION.md @@ -0,0 +1,126 @@ +# Schedule App API Documentation + +## Base URL + +`http://localhost:3000/api` + +## Authentication + +Most endpoints require authentication (except where noted as Public). + +## System Endpoints + +| Method | Endpoint | Description | Access | +|--------|----------|-------------|--------| +| GET | `/health` | Health check | Public | +| GET | `/test` | Test endpoint | Public | +| GET | `/status` | System status check | Public | +| GET | `/status/db` | Database connection status | Public | +| GET | `/env-check` | Environment variables check | Admin | +| GET | `/db-status` | MongoDB connection details | Admin | + +## Auth Endpoints + +| Method | Endpoint | Description | Access | +|--------|----------|-------------|--------| +| POST | `/auth/register` | Register a new user | Public | +| POST | `/auth/login` | Login a user | Public | +| GET | `/auth/me` | Get current user profile | Private | +| POST | `/auth/logout` | Logout a user | Private | +| POST | `/auth/refresh-token` | Refresh access token | Public (with refresh token) | +| POST | `/auth/forgot-password` | Send password reset email | Public | +| POST | `/auth/reset-password` | Reset password with token | Public (with reset token) | + +## User Endpoints + +| Method | Endpoint | Description | Access | +|--------|----------|-------------|--------| +| GET | `/users` | Get all users (paginated) | Admin | +| POST | `/users` | Create a new user | Admin | +| GET | `/users/:id` | Get user by ID | Admin, Own User | +| PUT | `/users/:id` | Update user | Admin, Own User | +| DELETE | `/users/:id` | Delete user | Admin, Own User | + +## Team Endpoints + +| Method | Endpoint | Description | Access | +|--------|----------|-------------|--------| +| GET | `/teams` | Get all teams | Admin | +| POST | `/teams` | Create a new team | Admin | +| GET | `/teams/:id` | Get team by ID | Admin, Team Members | +| PUT | `/teams/:id` | Update team | Admin, Team Owner | +| DELETE | `/teams/:id` | Delete team | Admin, Team Owner | +| GET | `/teams/:id/members` | Get team members | Admin, Team Members | +| POST | `/teams/:id/members` | Add member to team | Admin, Team Owner | +| DELETE | `/teams/:id/members/:userId` | Remove member from team | Admin, Team Owner | + +## Interviewer Endpoints + +| Method | Endpoint | Description | Access | +|--------|----------|-------------|--------| +| GET | `/interviewers` | Get all interviewers (paginated) | Admin | +| POST | `/interviewers` | Create a new interviewer | Admin | +| GET | `/interviewers/:id` | Get interviewer by ID | Admin, Own User | +| PUT | `/interviewers/:id` | Update interviewer | Admin, Own User | +| DELETE | `/interviewers/:id` | Delete interviewer | Admin | +| GET | `/interviewers/:id/availability` | Get interviewer availability | Admin, Own User | + +## Candidate Endpoints + +| Method | Endpoint | Description | Access | +|--------|----------|-------------|--------| +| GET | `/candidates` | Get all candidates (paginated) | Admin, Interviewers | +| POST | `/candidates` | Create a new candidate | Admin | +| GET | `/candidates/:id` | Get candidate by ID | Admin, Interviewer, Own User | +| PUT | `/candidates/:id` | Update candidate | Admin, Own User | +| DELETE | `/candidates/:id` | Delete candidate | Admin | +| GET | `/candidates/:id/availability` | Get candidate availability | Admin, Interviewer, Own User | + +## Group Endpoints + +| Method | Endpoint | Description | Access | +|--------|----------|-------------|--------| +| GET | `/groups` | Get all groups (paginated) | Admin | +| POST | `/groups` | Create a new group | Admin | +| GET | `/groups/:id` | Get group by ID | Admin, Team Members | +| PUT | `/groups/:id` | Update group | Admin | +| DELETE | `/groups/:id` | Delete group | Admin | +| GET | `/groups/:id/members` | Get group members | Admin, Team Members | +| POST | `/groups/:id/members` | Add member to group | Admin | +| DELETE | `/groups/:id/members/:userId` | Remove member from group | Admin | + +## Availability Endpoints + +| Method | Endpoint | Description | Access | +|--------|----------|-------------|--------| +| GET | `/availability` | Get availability for date range and user(s) | Private | +| POST | `/availability` | Create/update availability | Private | +| GET | `/availability/:id` | Get availability by ID | Admin, Owner, Team Members | +| PUT | `/availability/:id` | Update availability | Admin, Owner | +| DELETE | `/availability/:id` | Delete availability | Admin, Owner | +| GET | `/availability/team/:teamId` | Get team availability | Admin, Team Members | +| GET | `/availability/matches` | Find matching availability | Admin, Team Members | + +## Meeting Endpoints + +| Method | Endpoint | Description | Access | +|--------|----------|-------------|--------| +| GET | `/meetings` | Get all meetings (paginated) | Admin, Interviewer, Candidate | +| POST | `/meetings` | Create a new meeting | Admin | +| GET | `/meetings/:id` | Get meeting by ID | Admin, Participants | +| PUT | `/meetings/:id` | Update meeting | Admin | +| DELETE | `/meetings/:id` | Delete meeting | Admin | +| POST | `/meetings/:id/confirm` | Confirm participation | Meeting Participants | +| POST | `/meetings/:id/reschedule` | Request rescheduling | Meeting Participants | +| GET | `/meetings/user/:userId` | Get user meetings | Admin, Own User | +| GET | `/meetings/team/:teamId` | Get team meetings | Admin, Team Members | + +## Schedule Endpoints + +| Method | Endpoint | Description | Access | +|--------|----------|-------------|--------| +| POST | `/schedules/generate` | Auto-generate optimal schedule | Admin | +| GET | `/schedules/team/:teamId` | Get team schedule | Admin, Team Members | +| GET | `/schedules/conflicts` | Check for scheduling conflicts | Admin | +| POST | `/schedules/optimize` | Optimize existing schedule | Admin | +| POST | `/schedules/publish/:teamId` | Publish schedule for a team | Admin | From edebd29690fa1075870a9d89bd51b51f5e98e1c4 Mon Sep 17 00:00:00 2001 From: Spencer Kelly <126588898+Flapjacck@users.noreply.github.com> Date: Wed, 15 Oct 2025 16:14:57 -0400 Subject: [PATCH 11/11] Delete backend/API_DOCUMENTATION.md --- backend/API_DOCUMENTATION.md | 126 ----------------------------------- 1 file changed, 126 deletions(-) delete mode 100644 backend/API_DOCUMENTATION.md diff --git a/backend/API_DOCUMENTATION.md b/backend/API_DOCUMENTATION.md deleted file mode 100644 index 27f543d..0000000 --- a/backend/API_DOCUMENTATION.md +++ /dev/null @@ -1,126 +0,0 @@ -# Schedule App API Documentation - -## Base URL - -`http://localhost:3000/api` - -## Authentication - -Most endpoints require authentication (except where noted as Public). - -## System Endpoints - -| Method | Endpoint | Description | Access | -|--------|----------|-------------|--------| -| GET | `/health` | Health check | Public | -| GET | `/test` | Test endpoint | Public | -| GET | `/status` | System status check | Public | -| GET | `/status/db` | Database connection status | Public | -| GET | `/env-check` | Environment variables check | Admin | -| GET | `/db-status` | MongoDB connection details | Admin | - -## Auth Endpoints - -| Method | Endpoint | Description | Access | -|--------|----------|-------------|--------| -| POST | `/auth/register` | Register a new user | Public | -| POST | `/auth/login` | Login a user | Public | -| GET | `/auth/me` | Get current user profile | Private | -| POST | `/auth/logout` | Logout a user | Private | -| POST | `/auth/refresh-token` | Refresh access token | Public (with refresh token) | -| POST | `/auth/forgot-password` | Send password reset email | Public | -| POST | `/auth/reset-password` | Reset password with token | Public (with reset token) | - -## User Endpoints - -| Method | Endpoint | Description | Access | -|--------|----------|-------------|--------| -| GET | `/users` | Get all users (paginated) | Admin | -| POST | `/users` | Create a new user | Admin | -| GET | `/users/:id` | Get user by ID | Admin, Own User | -| PUT | `/users/:id` | Update user | Admin, Own User | -| DELETE | `/users/:id` | Delete user | Admin, Own User | - -## Team Endpoints - -| Method | Endpoint | Description | Access | -|--------|----------|-------------|--------| -| GET | `/teams` | Get all teams | Admin | -| POST | `/teams` | Create a new team | Admin | -| GET | `/teams/:id` | Get team by ID | Admin, Team Members | -| PUT | `/teams/:id` | Update team | Admin, Team Owner | -| DELETE | `/teams/:id` | Delete team | Admin, Team Owner | -| GET | `/teams/:id/members` | Get team members | Admin, Team Members | -| POST | `/teams/:id/members` | Add member to team | Admin, Team Owner | -| DELETE | `/teams/:id/members/:userId` | Remove member from team | Admin, Team Owner | - -## Interviewer Endpoints - -| Method | Endpoint | Description | Access | -|--------|----------|-------------|--------| -| GET | `/interviewers` | Get all interviewers (paginated) | Admin | -| POST | `/interviewers` | Create a new interviewer | Admin | -| GET | `/interviewers/:id` | Get interviewer by ID | Admin, Own User | -| PUT | `/interviewers/:id` | Update interviewer | Admin, Own User | -| DELETE | `/interviewers/:id` | Delete interviewer | Admin | -| GET | `/interviewers/:id/availability` | Get interviewer availability | Admin, Own User | - -## Candidate Endpoints - -| Method | Endpoint | Description | Access | -|--------|----------|-------------|--------| -| GET | `/candidates` | Get all candidates (paginated) | Admin, Interviewers | -| POST | `/candidates` | Create a new candidate | Admin | -| GET | `/candidates/:id` | Get candidate by ID | Admin, Interviewer, Own User | -| PUT | `/candidates/:id` | Update candidate | Admin, Own User | -| DELETE | `/candidates/:id` | Delete candidate | Admin | -| GET | `/candidates/:id/availability` | Get candidate availability | Admin, Interviewer, Own User | - -## Group Endpoints - -| Method | Endpoint | Description | Access | -|--------|----------|-------------|--------| -| GET | `/groups` | Get all groups (paginated) | Admin | -| POST | `/groups` | Create a new group | Admin | -| GET | `/groups/:id` | Get group by ID | Admin, Team Members | -| PUT | `/groups/:id` | Update group | Admin | -| DELETE | `/groups/:id` | Delete group | Admin | -| GET | `/groups/:id/members` | Get group members | Admin, Team Members | -| POST | `/groups/:id/members` | Add member to group | Admin | -| DELETE | `/groups/:id/members/:userId` | Remove member from group | Admin | - -## Availability Endpoints - -| Method | Endpoint | Description | Access | -|--------|----------|-------------|--------| -| GET | `/availability` | Get availability for date range and user(s) | Private | -| POST | `/availability` | Create/update availability | Private | -| GET | `/availability/:id` | Get availability by ID | Admin, Owner, Team Members | -| PUT | `/availability/:id` | Update availability | Admin, Owner | -| DELETE | `/availability/:id` | Delete availability | Admin, Owner | -| GET | `/availability/team/:teamId` | Get team availability | Admin, Team Members | -| GET | `/availability/matches` | Find matching availability | Admin, Team Members | - -## Meeting Endpoints - -| Method | Endpoint | Description | Access | -|--------|----------|-------------|--------| -| GET | `/meetings` | Get all meetings (paginated) | Admin, Interviewer, Candidate | -| POST | `/meetings` | Create a new meeting | Admin | -| GET | `/meetings/:id` | Get meeting by ID | Admin, Participants | -| PUT | `/meetings/:id` | Update meeting | Admin | -| DELETE | `/meetings/:id` | Delete meeting | Admin | -| POST | `/meetings/:id/confirm` | Confirm participation | Meeting Participants | -| POST | `/meetings/:id/reschedule` | Request rescheduling | Meeting Participants | -| GET | `/meetings/user/:userId` | Get user meetings | Admin, Own User | -| GET | `/meetings/team/:teamId` | Get team meetings | Admin, Team Members | - -## Schedule Endpoints - -| Method | Endpoint | Description | Access | -|--------|----------|-------------|--------| -| POST | `/schedules/generate` | Auto-generate optimal schedule | Admin | -| GET | `/schedules/team/:teamId` | Get team schedule | Admin, Team Members | -| GET | `/schedules/conflicts` | Check for scheduling conflicts | Admin | -| POST | `/schedules/optimize` | Optimize existing schedule | Admin | -| POST | `/schedules/publish/:teamId` | Publish schedule for a team | Admin |