Commit 02e43bce authored by Blockchain-vn's avatar Blockchain-vn

feat: api courses, students, schedules, booking

parent e11f811a
......@@ -15,6 +15,10 @@ PROJECT_NAME=Backend Template
PROJECT_VERSION=1.0.0
# Database Configuration
# For MongoDB (recommended for this project)
MONGODB_URI=mongodb+srv://username:password@cluster.mongodb.net/database_name?retryWrites=true&w=majority
# For PostgreSQL (legacy - not used in this project)
DB_HOST=localhost
DB_PORT=5432
DB_NAME=backend_template
......
......@@ -80,6 +80,7 @@
"license": "ISC",
"dependencies": {
"@types/joi": "^17.2.3",
"@types/mongoose": "^5.11.97",
"bcryptjs": "^3.0.3",
"cli-color": "^2.0.4",
"compression": "^1.8.1",
......@@ -93,6 +94,7 @@
"joi": "^18.0.2",
"jsonwebtoken": "^9.0.3",
"module-alias": "^2.2.3",
"mongoose": "^9.4.1",
"multer": "^2.0.2",
"mustache": "^4.2.0",
"mv": "^2.1.1",
......
This diff is collapsed.
import mongoose from 'mongoose';
import dotenv from 'dotenv';
dotenv.config();
const MONGODB_URI = process.env.MONGODB_URI || 'mongodb://localhost:27017/trainee-schedule';
export const connectDB = async (): Promise<void> => {
try {
const conn = await mongoose.connect(MONGODB_URI, {
// Connection options
});
console.log(`MongoDB Connected: ${conn.connection.host}`);
// Handle connection events
mongoose.connection.on('error', (err) => {
console.error('MongoDB connection error:', err);
});
mongoose.connection.on('disconnected', () => {
console.log('MongoDB disconnected');
});
// Graceful shutdown
process.on('SIGINT', async () => {
await mongoose.connection.close();
console.log('MongoDB connection closed through app termination');
process.exit(0);
});
} catch (error) {
console.error('Error connecting to MongoDB:', error);
process.exit(1);
}
};
export const disconnectDB = async (): Promise<void> => {
try {
await mongoose.connection.close();
console.log('MongoDB connection closed');
} catch (error) {
console.error('Error closing MongoDB connection:', error);
}
};
import { Application } from "express";
import { Resource } from "express-automatic-routes";
import { BookingProvider, BookingFindManyOptions } from "#providers/BookingProvider";
import { Req, Res } from "#interfaces/IApi";
import { queryModifier } from "#middlewares/query-modifier";
import { validateBookingCreate, validateBookingUpdate } from "../../../../middlewares/validators/booking";
export default (_express: Application) => {
const bookingProvider = new BookingProvider();
return <Resource>{
/**
* @openapi
* /bookings:
* get:
* tags: [Bookings]
* summary: Get all bookings
* description: Retrieve a list of bookings with pagination, filtering and sorting
* parameters:
* - $ref: '#/components/parameters/filters'
* - $ref: '#/components/parameters/sortField'
* - $ref: '#/components/parameters/sortOrder'
* - $ref: '#/components/parameters/page'
* - $ref: '#/components/parameters/pageSize'
* - in: query
* name: status
* schema:
* type: string
* enum: [confirmed, cancelled, completed]
* description: Filter by status
* - in: query
* name: studentId
* schema:
* type: string
* description: Filter by student ID
* - in: query
* name: scheduleId
* schema:
* type: string
* description: Filter by schedule ID
* - in: query
* name: courseId
* schema:
* type: string
* description: Filter by course ID
* responses:
* 200:
* description: Successfully retrieved bookings
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/responseGetAllData"
*/
get: {
middleware: [queryModifier],
handler: async (req: Req, res: Res) => {
try {
const options: BookingFindManyOptions = {
...req.payload,
sortOrder: (req.payload?.sortOrder as 'asc' | 'desc' | undefined) || 'desc'
};
const data = await bookingProvider.getAll(options);
return res.sendOk({ data });
} catch (error) {
await bookingProvider.logError(error as Error);
return res.error(error);
}
},
},
/**
* @openapi
* /bookings:
* post:
* tags: [Bookings]
* summary: Create a booking
* description: Create a new booking record
* requestBody:
* required: true
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/BookingMutate"
* responses:
* 200:
* description: Booking created successfully
* content:
* application/json:
* schema:
* allOf:
* - $ref: "#/components/schemas/ApiResponse"
* - type: object
* properties:
* responseData:
* $ref: "#/components/schemas/Booking"
*/
post: {
middleware: [queryModifier, validateBookingCreate],
handler: async (req: Req, res: Res) => {
try {
const createOptions = req.user?.id ? { createdBy: req.user.id } : {};
const data = await bookingProvider.create({ ...req.body }, createOptions);
return res.sendOk({ data, message: "Booking created successfully" });
} catch (error) {
await bookingProvider.logError(error as Error);
return res.error(error);
}
},
},
};
};
import { Application } from "express";
import { Resource } from "express-automatic-routes";
import { BookingProvider } from "#providers/BookingProvider";
import { Req, Res } from "#interfaces/IApi";
import { queryModifier } from "#middlewares/query-modifier";
import { validateId } from "#middlewares/validators";
import { validateBookingUpdate } from "../../../../middlewares/validators/booking";
export default (_express: Application) => {
const bookingProvider = new BookingProvider();
return <Resource>{
/**
* @openapi
* /bookings/{id}:
* get:
* tags: [Bookings]
* summary: Get booking by ID
* description: Retrieve a single booking record by its ID
* parameters:
* - name: id
* in: path
* required: true
* description: Booking ID
* schema:
* type: string
* responses:
* 200:
* description: Booking retrieved successfully
* content:
* application/json:
* schema:
* allOf:
* - $ref: "#/components/schemas/ApiResponse"
* - type: object
* properties:
* responseData:
* $ref: "#/components/schemas/Booking"
* 404:
* description: Booking not found
*/
get: {
middleware: [queryModifier, validateId],
handler: async (req: Req, res: Res) => {
try {
const data = await bookingProvider.getById(req.params.id!);
return res.sendOk({ data });
} catch (error) {
await bookingProvider.logError(error as Error);
return res.error(error);
}
},
},
/**
* @openapi
* /bookings/{id}:
* put:
* tags: [Bookings]
* summary: Update booking by ID
* description: Update a single booking record by its ID
* parameters:
* - name: id
* in: path
* required: true
* description: Booking ID
* schema:
* type: string
* requestBody:
* required: true
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/BookingMutate"
* responses:
* 200:
* description: Booking updated successfully
* content:
* application/json:
* schema:
* allOf:
* - $ref: "#/components/schemas/ApiResponse"
* - type: object
* properties:
* responseData:
* $ref: "#/components/schemas/Booking"
* 404:
* description: Booking not found
*/
put: {
middleware: [queryModifier, validateId, validateBookingUpdate],
handler: async (req: Req, res: Res) => {
try {
const updateOptions = req.user?.id ? { updatedBy: req.user.id } : {};
const data = await bookingProvider.update(req.params.id!, { ...req.body }, updateOptions);
return res.sendOk({ data, message: "Booking updated successfully" });
} catch (error) {
await bookingProvider.logError(error as Error);
return res.error(error);
}
},
},
/**
* @openapi
* /bookings/{id}:
* delete:
* tags: [Bookings]
* summary: Delete booking by ID
* description: Delete a single booking record by its ID
* parameters:
* - name: id
* in: path
* required: true
* description: Booking ID
* schema:
* type: string
* responses:
* 200:
* description: Booking deleted successfully
* content:
* application/json:
* schema:
* allOf:
* - $ref: "#/components/schemas/ApiResponse"
* - type: object
* properties:
* responseData:
* type: boolean
* example: true
* 404:
* description: Booking not found
*/
delete: {
middleware: [queryModifier, validateId],
handler: async (req: Req, res: Res) => {
try {
const data = await bookingProvider.delete(req.params.id!);
return res.sendOk({ data, message: "Booking deleted successfully" });
} catch (error) {
await bookingProvider.logError(error as Error);
return res.error(error);
}
},
},
};
};
import { Application } from "express";
import { Resource } from "express-automatic-routes";
import { CourseProvider, CourseFindManyOptions } from "#providers/CourseProvider";
import { Req, Res } from "#interfaces/IApi";
import { queryModifier } from "#middlewares/query-modifier";
import { validateCourseCreate, validateCourseUpdate } from "../../../../middlewares/validators/course";
export default (_express: Application) => {
const courseProvider = new CourseProvider();
return <Resource>{
/**
* @openapi
* /courses:
* get:
* tags: [Courses]
* summary: Get all courses
* description: Retrieve a list of courses with pagination, filtering and sorting
* parameters:
* - $ref: '#/components/parameters/filters'
* - $ref: '#/components/parameters/sortField'
* - $ref: '#/components/parameters/sortOrder'
* - $ref: '#/components/parameters/page'
* - $ref: '#/components/parameters/pageSize'
* - in: query
* name: status
* schema:
* type: string
* enum: [active, inactive, completed]
* description: Filter by status
* - in: query
* name: instructor
* schema:
* type: string
* description: Filter by instructor
* - in: query
* name: search
* schema:
* type: string
* description: Search by name or code
* responses:
* 200:
* description: Successfully retrieved courses
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/responseGetAllData"
*/
get: {
middleware: [queryModifier],
handler: async (req: Req, res: Res) => {
try {
const options: CourseFindManyOptions = {
...req.payload,
sortOrder: (req.payload?.sortOrder as 'asc' | 'desc' | undefined) || 'desc'
};
const data = await courseProvider.getAll(options);
return res.sendOk({ data });
} catch (error) {
await courseProvider.logError(error as Error);
return res.error(error);
}
},
},
/**
* @openapi
* /courses:
* post:
* tags: [Courses]
* summary: Create a course
* description: Create a new course record
* requestBody:
* required: true
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/CourseMutate"
* responses:
* 200:
* description: Course created successfully
* content:
* application/json:
* schema:
* allOf:
* - $ref: "#/components/schemas/ApiResponse"
* - type: object
* properties:
* responseData:
* $ref: "#/components/schemas/Course"
*/
post: {
middleware: [queryModifier, validateCourseCreate],
handler: async (req: Req, res: Res) => {
try {
const createOptions = req.user?.id ? { createdBy: req.user.id } : {};
const data = await courseProvider.create({ ...req.body }, createOptions);
return res.sendOk({ data, message: "Course created successfully" });
} catch (error) {
await courseProvider.logError(error as Error);
return res.error(error);
}
},
},
};
};
import { Application } from "express";
import { Resource } from "express-automatic-routes";
import { CourseProvider } from "#providers/CourseProvider";
import { Req, Res } from "#interfaces/IApi";
import { queryModifier } from "#middlewares/query-modifier";
import { validateId } from "#middlewares/validators";
import { validateCourseUpdate } from "../../../../middlewares/validators/course";
export default (_express: Application) => {
const courseProvider = new CourseProvider();
return <Resource>{
/**
* @openapi
* /courses/{id}:
* get:
* tags: [Courses]
* summary: Get course by ID
* description: Retrieve a single course record by its ID
* parameters:
* - name: id
* in: path
* required: true
* description: Course ID
* schema:
* type: string
* responses:
* 200:
* description: Course retrieved successfully
* content:
* application/json:
* schema:
* allOf:
* - $ref: "#/components/schemas/ApiResponse"
* - type: object
* properties:
* responseData:
* $ref: "#/components/schemas/Course"
* 404:
* description: Course not found
*/
get: {
middleware: [queryModifier, validateId],
handler: async (req: Req, res: Res) => {
try {
const data = await courseProvider.getById(req.params.id!);
return res.sendOk({ data });
} catch (error) {
await courseProvider.logError(error as Error);
return res.error(error);
}
},
},
/**
* @openapi
* /courses/{id}:
* put:
* tags: [Courses]
* summary: Update course by ID
* description: Update a single course record by its ID
* parameters:
* - name: id
* in: path
* required: true
* description: Course ID
* schema:
* type: string
* requestBody:
* required: true
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/CourseMutate"
* responses:
* 200:
* description: Course updated successfully
* content:
* application/json:
* schema:
* allOf:
* - $ref: "#/components/schemas/ApiResponse"
* - type: object
* properties:
* responseData:
* $ref: "#/components/schemas/Course"
* 404:
* description: Course not found
*/
put: {
middleware: [queryModifier, validateId, validateCourseUpdate],
handler: async (req: Req, res: Res) => {
try {
const updateOptions = req.user?.id ? { updatedBy: req.user.id } : {};
const data = await courseProvider.update(req.params.id!, { ...req.body }, updateOptions);
return res.sendOk({ data, message: "Course updated successfully" });
} catch (error) {
await courseProvider.logError(error as Error);
return res.error(error);
}
},
},
/**
* @openapi
* /courses/{id}:
* delete:
* tags: [Courses]
* summary: Delete course by ID
* description: Delete a single course record by its ID
* parameters:
* - name: id
* in: path
* required: true
* description: Course ID
* schema:
* type: string
* responses:
* 200:
* description: Course deleted successfully
* content:
* application/json:
* schema:
* allOf:
* - $ref: "#/components/schemas/ApiResponse"
* - type: object
* properties:
* responseData:
* type: boolean
* example: true
* 404:
* description: Course not found
*/
delete: {
middleware: [queryModifier, validateId],
handler: async (req: Req, res: Res) => {
try {
const data = await courseProvider.delete(req.params.id!);
return res.sendOk({ data, message: "Course deleted successfully" });
} catch (error) {
await courseProvider.logError(error as Error);
return res.error(error);
}
},
},
};
};
import { Application } from "express";
import { Resource } from "express-automatic-routes";
import { ScheduleProvider, ScheduleFindManyOptions } from "#providers/ScheduleProvider";
import { Req, Res } from "#interfaces/IApi";
import { queryModifier } from "#middlewares/query-modifier";
import { validateScheduleCreate, validateScheduleUpdate } from "../../../../middlewares/validators/schedule";
export default (_express: Application) => {
const scheduleProvider = new ScheduleProvider();
return <Resource>{
/**
* @openapi
* /schedules:
* get:
* tags: [Schedules]
* summary: Get all schedules
* description: Retrieve a list of schedules with pagination, filtering and sorting
* parameters:
* - $ref: '#/components/parameters/filters'
* - $ref: '#/components/parameters/sortField'
* - $ref: '#/components/parameters/sortOrder'
* - $ref: '#/components/parameters/page'
* - $ref: '#/components/parameters/pageSize'
* - in: query
* name: status
* schema:
* type: string
* enum: [active, cancelled, completed]
* description: Filter by status
* - in: query
* name: courseId
* schema:
* type: string
* description: Filter by course ID
* - in: query
* name: instructor
* schema:
* type: string
* description: Filter by instructor
* - in: query
* name: dayOfWeek
* schema:
* type: string
* enum: [Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday]
* description: Filter by day of week
* - in: query
* name: semester
* schema:
* type: string
* description: Filter by semester
* responses:
* 200:
* description: Successfully retrieved schedules
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/responseGetAllData"
*/
get: {
middleware: [queryModifier],
handler: async (req: Req, res: Res) => {
try {
const options: ScheduleFindManyOptions = {
...req.payload,
sortOrder: (req.payload?.sortOrder as 'asc' | 'desc' | undefined) || 'asc'
};
const data = await scheduleProvider.getAll(options);
return res.sendOk({ data });
} catch (error) {
await scheduleProvider.logError(error as Error);
return res.error(error);
}
},
},
/**
* @openapi
* /schedules:
* post:
* tags: [Schedules]
* summary: Create a schedule
* description: Create a new schedule record
* requestBody:
* required: true
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/ScheduleMutate"
* responses:
* 200:
* description: Schedule created successfully
* content:
* application/json:
* schema:
* allOf:
* - $ref: "#/components/schemas/ApiResponse"
* - type: object
* properties:
* responseData:
* $ref: "#/components/schemas/Schedule"
*/
post: {
middleware: [queryModifier, validateScheduleCreate],
handler: async (req: Req, res: Res) => {
try {
const createOptions = req.user?.id ? { createdBy: req.user.id } : {};
const data = await scheduleProvider.create({ ...req.body }, createOptions);
return res.sendOk({ data, message: "Schedule created successfully" });
} catch (error) {
await scheduleProvider.logError(error as Error);
return res.error(error);
}
},
},
};
};
import { Application } from "express";
import { Resource } from "express-automatic-routes";
import { ScheduleProvider } from "#providers/ScheduleProvider";
import { Req, Res } from "#interfaces/IApi";
import { queryModifier } from "#middlewares/query-modifier";
import { validateId } from "#middlewares/validators";
import { validateScheduleUpdate } from "../../../../middlewares/validators/schedule";
export default (_express: Application) => {
const scheduleProvider = new ScheduleProvider();
return <Resource>{
/**
* @openapi
* /schedules/{id}:
* get:
* tags: [Schedules]
* summary: Get schedule by ID
* description: Retrieve a single schedule record by its ID
* parameters:
* - name: id
* in: path
* required: true
* description: Schedule ID
* schema:
* type: string
* responses:
* 200:
* description: Schedule retrieved successfully
* content:
* application/json:
* schema:
* allOf:
* - $ref: "#/components/schemas/ApiResponse"
* - type: object
* properties:
* responseData:
* $ref: "#/components/schemas/Schedule"
* 404:
* description: Schedule not found
*/
get: {
middleware: [queryModifier, validateId],
handler: async (req: Req, res: Res) => {
try {
const data = await scheduleProvider.getById(req.params.id!);
return res.sendOk({ data });
} catch (error) {
await scheduleProvider.logError(error as Error);
return res.error(error);
}
},
},
/**
* @openapi
* /schedules/{id}:
* put:
* tags: [Schedules]
* summary: Update schedule by ID
* description: Update a single schedule record by its ID
* parameters:
* - name: id
* in: path
* required: true
* description: Schedule ID
* schema:
* type: string
* requestBody:
* required: true
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/ScheduleMutate"
* responses:
* 200:
* description: Schedule updated successfully
* content:
* application/json:
* schema:
* allOf:
* - $ref: "#/components/schemas/ApiResponse"
* - type: object
* properties:
* responseData:
* $ref: "#/components/schemas/Schedule"
* 404:
* description: Schedule not found
*/
put: {
middleware: [queryModifier, validateId, validateScheduleUpdate],
handler: async (req: Req, res: Res) => {
try {
const updateOptions = req.user?.id ? { updatedBy: req.user.id } : {};
const data = await scheduleProvider.update(req.params.id!, { ...req.body }, updateOptions);
return res.sendOk({ data, message: "Schedule updated successfully" });
} catch (error) {
await scheduleProvider.logError(error as Error);
return res.error(error);
}
},
},
/**
* @openapi
* /schedules/{id}:
* delete:
* tags: [Schedules]
* summary: Delete schedule by ID
* description: Delete a single schedule record by its ID
* parameters:
* - name: id
* in: path
* required: true
* description: Schedule ID
* schema:
* type: string
* responses:
* 200:
* description: Schedule deleted successfully
* content:
* application/json:
* schema:
* allOf:
* - $ref: "#/components/schemas/ApiResponse"
* - type: object
* properties:
* responseData:
* type: boolean
* example: true
* 404:
* description: Schedule not found
*/
delete: {
middleware: [queryModifier, validateId],
handler: async (req: Req, res: Res) => {
try {
const data = await scheduleProvider.delete(req.params.id!);
return res.sendOk({ data, message: "Schedule deleted successfully" });
} catch (error) {
await scheduleProvider.logError(error as Error);
return res.error(error);
}
},
},
};
};
import { Application } from "express";
import { Resource } from "express-automatic-routes";
import { StudentProvider, StudentFindManyOptions } from "#providers/StudentProvider";
import { Req, Res } from "#interfaces/IApi";
import { queryModifier } from "#middlewares/query-modifier";
import { validateStudentCreate, validateStudentUpdate } from "../../../../middlewares/validators/student";
export default (_express: Application) => {
const studentProvider = new StudentProvider();
return <Resource>{
/**
* @openapi
* /students:
* get:
* tags: [Students]
* summary: Get all students
* description: Retrieve a list of students with pagination, filtering and sorting
* parameters:
* - $ref: '#/components/parameters/filters'
* - $ref: '#/components/parameters/sortField'
* - $ref: '#/components/parameters/sortOrder'
* - $ref: '#/components/parameters/page'
* - $ref: '#/components/parameters/pageSize'
* - in: query
* name: status
* schema:
* type: string
* enum: [active, inactive, completed]
* description: Filter by status
* - in: query
* name: courseId
* schema:
* type: string
* description: Filter by course ID
* - in: query
* name: semester
* schema:
* type: string
* description: Filter by semester
* - in: query
* name: search
* schema:
* type: string
* description: Search by name or email
* responses:
* 200:
* description: Successfully retrieved students
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/responseGetAllData"
*/
get: {
middleware: [queryModifier],
handler: async (req: Req, res: Res) => {
try {
const options: StudentFindManyOptions = {
...req.payload,
sortOrder: (req.payload?.sortOrder as 'asc' | 'desc' | undefined) || 'desc'
};
const data = await studentProvider.getAll(options);
return res.sendOk({ data });
} catch (error) {
await studentProvider.logError(error as Error);
return res.error(error);
}
},
},
/**
* @openapi
* /students:
* post:
* tags: [Students]
* summary: Create a student
* description: Create a new student record
* requestBody:
* required: true
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/StudentMutate"
* responses:
* 200:
* description: Student created successfully
* content:
* application/json:
* schema:
* allOf:
* - $ref: "#/components/schemas/ApiResponse"
* - type: object
* properties:
* responseData:
* $ref: "#/components/schemas/Student"
*/
post: {
middleware: [queryModifier, validateStudentCreate],
handler: async (req: Req, res: Res) => {
try {
const createOptions = req.user?.id ? { createdBy: req.user.id } : {};
const data = await studentProvider.create({ ...req.body }, createOptions);
return res.sendOk({ data, message: "Student created successfully" });
} catch (error) {
await studentProvider.logError(error as Error);
return res.error(error);
}
},
},
};
};
import { Application } from "express";
import { Resource } from "express-automatic-routes";
import { StudentProvider } from "#providers/StudentProvider";
import { Req, Res } from "#interfaces/IApi";
import { queryModifier } from "#middlewares/query-modifier";
import { validateId } from "#middlewares/validators";
import { validateStudentUpdate } from "../../../../middlewares/validators/student";
export default (_express: Application) => {
const studentProvider = new StudentProvider();
return <Resource>{
/**
* @openapi
* /students/{id}:
* get:
* tags: [Students]
* summary: Get student by ID
* description: Retrieve a single student record by its ID
* parameters:
* - name: id
* in: path
* required: true
* description: Student ID
* schema:
* type: string
* responses:
* 200:
* description: Student retrieved successfully
* content:
* application/json:
* schema:
* allOf:
* - $ref: "#/components/schemas/ApiResponse"
* - type: object
* properties:
* responseData:
* $ref: "#/components/schemas/Student"
* 404:
* description: Student not found
*/
get: {
middleware: [queryModifier, validateId],
handler: async (req: Req, res: Res) => {
try {
const data = await studentProvider.getById(req.params.id!);
return res.sendOk({ data });
} catch (error) {
await studentProvider.logError(error as Error);
return res.error(error);
}
},
},
/**
* @openapi
* /students/{id}:
* put:
* tags: [Students]
* summary: Update student by ID
* description: Update a single student record by its ID
* parameters:
* - name: id
* in: path
* required: true
* description: Student ID
* schema:
* type: string
* requestBody:
* required: true
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/StudentMutate"
* responses:
* 200:
* description: Student updated successfully
* content:
* application/json:
* schema:
* allOf:
* - $ref: "#/components/schemas/ApiResponse"
* - type: object
* properties:
* responseData:
* $ref: "#/components/schemas/Student"
* 404:
* description: Student not found
*/
put: {
middleware: [queryModifier, validateId, validateStudentUpdate],
handler: async (req: Req, res: Res) => {
try {
const updateOptions = req.user?.id ? { updatedBy: req.user.id } : {};
const data = await studentProvider.update(req.params.id!, { ...req.body }, updateOptions);
return res.sendOk({ data, message: "Student updated successfully" });
} catch (error) {
await studentProvider.logError(error as Error);
return res.error(error);
}
},
},
/**
* @openapi
* /students/{id}:
* delete:
* tags: [Students]
* summary: Delete student by ID
* description: Delete a single student record by its ID
* parameters:
* - name: id
* in: path
* required: true
* description: Student ID
* schema:
* type: string
* responses:
* 200:
* description: Student deleted successfully
* content:
* application/json:
* schema:
* allOf:
* - $ref: "#/components/schemas/ApiResponse"
* - type: object
* properties:
* responseData:
* type: boolean
* example: true
* 404:
* description: Student not found
*/
delete: {
middleware: [queryModifier, validateId],
handler: async (req: Req, res: Res) => {
try {
const data = await studentProvider.delete(req.params.id!);
return res.sendOk({ data, message: "Student deleted successfully" });
} catch (error) {
await studentProvider.logError(error as Error);
return res.error(error);
}
},
},
};
};
import Joi from "joi";
import { validateJoi } from "./index";
const bookingSchema = Joi.object({
studentId: Joi.string().required().messages({
"string.empty": "Student ID is required",
"any.required": "Student ID is required",
}),
scheduleId: Joi.string().required().messages({
"string.empty": "Schedule ID is required",
"any.required": "Schedule ID is required",
}),
courseId: Joi.string().required().messages({
"string.empty": "Course ID is required",
"any.required": "Course ID is required",
}),
studentName: Joi.string().required().messages({
"string.empty": "Student name is required",
"any.required": "Student name is required",
}),
courseName: Joi.string().required().messages({
"string.empty": "Course name is required",
"any.required": "Course name is required",
}),
scheduleInfo: Joi.string().required().messages({
"string.empty": "Schedule info is required",
"any.required": "Schedule info is required",
}),
bookingDate: Joi.date().iso().optional().messages({
"date.format": "Invalid booking date format",
}),
status: Joi.string().valid("confirmed", "cancelled", "completed").optional().messages({
"any.only": "Status must be confirmed, cancelled, or completed",
}),
notes: Joi.string().max(500).optional().messages({
"string.max": "Notes cannot exceed 500 characters",
}),
});
const bookingUpdateSchema = Joi.object({
studentId: Joi.string().optional().messages({
"string.empty": "Student ID cannot be empty",
}),
scheduleId: Joi.string().optional().messages({
"string.empty": "Schedule ID cannot be empty",
}),
courseId: Joi.string().optional().messages({
"string.empty": "Course ID cannot be empty",
}),
studentName: Joi.string().optional().messages({
"string.empty": "Student name cannot be empty",
}),
courseName: Joi.string().optional().messages({
"string.empty": "Course name cannot be empty",
}),
scheduleInfo: Joi.string().optional().messages({
"string.empty": "Schedule info cannot be empty",
}),
bookingDate: Joi.date().iso().optional().messages({
"date.format": "Invalid booking date format",
}),
status: Joi.string().valid("confirmed", "cancelled", "completed").optional().messages({
"any.only": "Status must be confirmed, cancelled, or completed",
}),
notes: Joi.string().max(500).optional().messages({
"string.max": "Notes cannot exceed 500 characters",
}),
});
export const validateBookingCreate = validateJoi(
Joi.object({
body: bookingSchema,
params: Joi.object(),
query: Joi.object(),
})
);
export const validateBookingUpdate = validateJoi(
Joi.object({
body: bookingUpdateSchema,
params: Joi.object({
id: Joi.string().required().messages({
"any.required": "Booking ID is required",
}),
}),
query: Joi.object(),
})
);
export const validateBookingGetById = validateJoi(
Joi.object({
body: Joi.object(),
params: Joi.object({
id: Joi.string().required().messages({
"any.required": "Booking ID is required",
}),
}),
query: Joi.object(),
})
);
import Joi from "joi";
import { validateJoi } from ".";
const courseSchema = Joi.object({
name: Joi.string().required().max(200).messages({
"string.empty": "Course name is required",
"string.max": "Course name cannot exceed 200 characters",
"any.required": "Course name is required",
}),
code: Joi.string().required().max(20).messages({
"string.empty": "Course code is required",
"string.max": "Course code cannot exceed 20 characters",
"any.required": "Course code is required",
}),
description: Joi.string().required().max(1000).messages({
"string.empty": "Description is required",
"string.max": "Description cannot exceed 1000 characters",
"any.required": "Description is required",
}),
instructor: Joi.string().required().max(100).messages({
"string.empty": "Instructor is required",
"string.max": "Instructor name cannot exceed 100 characters",
"any.required": "Instructor is required",
}),
duration: Joi.number().integer().min(1).max(52).required().messages({
"number.base": "Duration must be a number",
"number.integer": "Duration must be an integer",
"number.min": "Duration must be at least 1 week",
"number.max": "Duration cannot exceed 52 weeks",
"any.required": "Duration is required",
}),
status: Joi.string().valid("active", "inactive", "completed").optional().messages({
"any.only": "Status must be active, inactive, or completed",
}),
startDate: Joi.date().iso().required().messages({
"date.format": "Invalid start date format",
"any.required": "Start date is required",
}),
endDate: Joi.date().iso().required().messages({
"date.format": "Invalid end date format",
"any.required": "End date is required",
}),
maxStudents: Joi.number().integer().min(1).max(100).required().messages({
"number.base": "Max students must be a number",
"number.integer": "Max students must be an integer",
"number.min": "Max students must be at least 1",
"number.max": "Max students cannot exceed 100",
"any.required": "Max students is required",
}),
currentStudents: Joi.number().integer().min(0).optional().messages({
"number.base": "Current students must be a number",
"number.integer": "Current students must be an integer",
"number.min": "Current students cannot be negative",
}),
scheduleIds: Joi.array().items(Joi.string()).optional().messages({
"array.base": "Schedule IDs must be an array",
}),
});
const courseUpdateSchema = Joi.object({
name: Joi.string().max(200).optional().messages({
"string.max": "Course name cannot exceed 200 characters",
}),
code: Joi.string().max(20).optional().messages({
"string.max": "Course code cannot exceed 20 characters",
}),
description: Joi.string().max(1000).optional().messages({
"string.max": "Description cannot exceed 1000 characters",
}),
instructor: Joi.string().max(100).optional().messages({
"string.max": "Instructor name cannot exceed 100 characters",
}),
duration: Joi.number().integer().min(1).max(52).optional().messages({
"number.base": "Duration must be a number",
"number.integer": "Duration must be an integer",
"number.min": "Duration must be at least 1 week",
"number.max": "Duration cannot exceed 52 weeks",
}),
status: Joi.string().valid("active", "inactive", "completed").optional().messages({
"any.only": "Status must be active, inactive, or completed",
}),
startDate: Joi.date().iso().optional().messages({
"date.format": "Invalid start date format",
}),
endDate: Joi.date().iso().optional().messages({
"date.format": "Invalid end date format",
}),
maxStudents: Joi.number().integer().min(1).max(100).optional().messages({
"number.base": "Max students must be a number",
"number.integer": "Max students must be an integer",
"number.min": "Max students must be at least 1",
"number.max": "Max students cannot exceed 100",
}),
currentStudents: Joi.number().integer().min(0).optional().messages({
"number.base": "Current students must be a number",
"number.integer": "Current students must be an integer",
"number.min": "Current students cannot be negative",
}),
scheduleIds: Joi.array().items(Joi.string()).optional().messages({
"array.base": "Schedule IDs must be an array",
}),
});
export const validateCourseCreate = validateJoi(
Joi.object({
body: courseSchema,
params: Joi.object(),
query: Joi.object(),
})
);
export const validateCourseUpdate = validateJoi(
Joi.object({
body: courseUpdateSchema,
params: Joi.object({
id: Joi.string().required().messages({
"any.required": "Course ID is required",
}),
}),
query: Joi.object(),
})
);
export const validateCourseGetById = validateJoi(
Joi.object({
body: Joi.object(),
params: Joi.object({
id: Joi.string().required().messages({
"any.required": "Course ID is required",
}),
}),
query: Joi.object(),
})
);
import Joi from "joi";
import { validateJoi } from "./index";
const scheduleSchema = Joi.object({
courseId: Joi.string().required().messages({
"string.empty": "Course ID is required",
"any.required": "Course ID is required",
}),
courseName: Joi.string().required().messages({
"string.empty": "Course name is required",
"any.required": "Course name is required",
}),
instructor: Joi.string().required().messages({
"string.empty": "Instructor is required",
"any.required": "Instructor is required",
}),
dayOfWeek: Joi.string().valid("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday").required().messages({
"any.only": "Day of week must be Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, or Sunday",
"any.required": "Day of week is required",
}),
startTime: Joi.date().iso().required().messages({
"date.format": "Invalid start time format",
"any.required": "Start time is required",
}),
endTime: Joi.date().iso().required().messages({
"date.format": "Invalid end time format",
"any.required": "End time is required",
}),
room: Joi.string().required().max(50).messages({
"string.empty": "Room is required",
"string.max": "Room name cannot exceed 50 characters",
"any.required": "Room is required",
}),
semester: Joi.string().required().messages({
"string.empty": "Semester is required",
"any.required": "Semester is required",
}),
status: Joi.string().valid("active", "cancelled", "completed").optional().messages({
"any.only": "Status must be active, cancelled, or completed",
}),
startDate: Joi.date().iso().required().messages({
"date.format": "Invalid start date format",
"any.required": "Start date is required",
}),
endDate: Joi.date().iso().required().messages({
"date.format": "Invalid end date format",
"any.required": "End date is required",
}),
studentIds: Joi.array().items(Joi.string()).optional().messages({
"array.base": "Student IDs must be an array",
}),
});
const scheduleUpdateSchema = Joi.object({
courseId: Joi.string().optional().messages({
"string.empty": "Course ID cannot be empty",
}),
courseName: Joi.string().optional().messages({
"string.empty": "Course name cannot be empty",
}),
instructor: Joi.string().optional().messages({
"string.empty": "Instructor cannot be empty",
}),
dayOfWeek: Joi.string().valid("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday").optional().messages({
"any.only": "Day of week must be Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, or Sunday",
}),
startTime: Joi.date().iso().optional().messages({
"date.format": "Invalid start time format",
}),
endTime: Joi.date().iso().optional().messages({
"date.format": "Invalid end time format",
}),
room: Joi.string().max(50).optional().messages({
"string.max": "Room name cannot exceed 50 characters",
}),
semester: Joi.string().optional().messages({
"string.empty": "Semester cannot be empty",
}),
status: Joi.string().valid("active", "cancelled", "completed").optional().messages({
"any.only": "Status must be active, cancelled, or completed",
}),
startDate: Joi.date().iso().optional().messages({
"date.format": "Invalid start date format",
}),
endDate: Joi.date().iso().optional().messages({
"date.format": "Invalid end date format",
}),
studentIds: Joi.array().items(Joi.string()).optional().messages({
"array.base": "Student IDs must be an array",
}),
});
export const validateScheduleCreate = validateJoi(
Joi.object({
body: scheduleSchema,
params: Joi.object(),
query: Joi.object(),
})
);
export const validateScheduleUpdate = validateJoi(
Joi.object({
body: scheduleUpdateSchema,
params: Joi.object({
id: Joi.string().required().messages({
"any.required": "Schedule ID is required",
}),
}),
query: Joi.object(),
})
);
export const validateScheduleGetById = validateJoi(
Joi.object({
body: Joi.object(),
params: Joi.object({
id: Joi.string().required().messages({
"any.required": "Schedule ID is required",
}),
}),
query: Joi.object(),
})
);
import Joi from "joi";
import { validateJoi } from ".";
const studentSchema = Joi.object({
fullName: Joi.string().required().max(100).messages({
"string.empty": "Full name is required",
"string.max": "Full name cannot exceed 100 characters",
"any.required": "Full name is required",
}),
email: Joi.string().email().required().messages({
"string.email": "Invalid email format",
"string.empty": "Email is required",
"any.required": "Email is required",
}),
phone: Joi.string().pattern(/^[0-9]{10,11}$/).required().messages({
"string.pattern.base": "Phone must be 10-11 digits",
"string.empty": "Phone is required",
"any.required": "Phone is required",
}),
courseId: Joi.string().required().messages({
"string.empty": "Course ID is required",
"any.required": "Course ID is required",
}),
courseName: Joi.string().required().max(200).messages({
"string.empty": "Course name is required",
"string.max": "Course name cannot exceed 200 characters",
"any.required": "Course name is required",
}),
semester: Joi.string().required().messages({
"string.empty": "Semester is required",
"any.required": "Semester is required",
}),
status: Joi.string().valid("active", "inactive", "completed").optional().messages({
"any.only": "Status must be active, inactive, or completed",
}),
startDate: Joi.date().iso().required().messages({
"date.format": "Invalid start date format",
"any.required": "Start date is required",
}),
endDate: Joi.date().iso().required().messages({
"date.format": "Invalid end date format",
"any.required": "End date is required",
}),
scheduleIds: Joi.array().items(Joi.string()).optional().messages({
"array.base": "Schedule IDs must be an array",
}),
});
const studentUpdateSchema = Joi.object({
fullName: Joi.string().max(100).optional().messages({
"string.max": "Full name cannot exceed 100 characters",
}),
email: Joi.string().email().optional().messages({
"string.email": "Invalid email format",
}),
phone: Joi.string().pattern(/^[0-9]{10,11}$/).optional().messages({
"string.pattern.base": "Phone must be 10-11 digits",
}),
courseId: Joi.string().optional().messages({
"string.empty": "Course ID cannot be empty",
}),
courseName: Joi.string().max(200).optional().messages({
"string.max": "Course name cannot exceed 200 characters",
}),
semester: Joi.string().optional().messages({
"string.empty": "Semester cannot be empty",
}),
status: Joi.string().valid("active", "inactive", "completed").optional().messages({
"any.only": "Status must be active, inactive, or completed",
}),
startDate: Joi.date().iso().optional().messages({
"date.format": "Invalid start date format",
}),
endDate: Joi.date().iso().optional().messages({
"date.format": "Invalid end date format",
}),
scheduleIds: Joi.array().items(Joi.string()).optional().messages({
"array.base": "Schedule IDs must be an array",
}),
});
export const validateStudentCreate = validateJoi(
Joi.object({
body: studentSchema,
params: Joi.object(),
query: Joi.object(),
})
);
export const validateStudentUpdate = validateJoi(
Joi.object({
body: studentUpdateSchema,
params: Joi.object({
id: Joi.string().required().messages({
"any.required": "Student ID is required",
}),
}),
query: Joi.object(),
})
);
export const validateStudentGetById = validateJoi(
Joi.object({
body: Joi.object(),
params: Joi.object({
id: Joi.string().required().messages({
"any.required": "Student ID is required",
}),
}),
query: Joi.object(),
})
);
import mongoose, { Document, Schema } from 'mongoose';
/**
* @typedef {Object} Booking
* @property {string} studentId - ID hýc viýn
* @property {string} scheduleId - ID lých hýc
* @property {string} courseId - ID khßa hýc
* @property {string} studentName - Týn hýc viýn
* @property {string} courseName - Týn khßa hýc
* @property {string} scheduleInfo - Thßng tin lých hýc
* @property {Date} bookingDate - Ngýy ßßt ký
* @property {string} status - Trýng thýi (confirmed, cancelled, completed)
* @property {string} notes - Ghi chß
* @property {Date} createdAt - Ngýy tßo
* @property {Date} updatedAt - Ngýy cßp nhßt
* @property {string} createdBy - Ngýi tßo
* @property {string} updatedBy - Ngýi cßp nhßt
*/
export interface IBooking extends Document {
studentId: string;
scheduleId: string;
courseId: string;
studentName: string;
courseName: string;
scheduleInfo: string;
bookingDate: Date;
status: 'confirmed' | 'cancelled' | 'completed';
notes: string;
createdAt: Date;
updatedAt: Date;
createdBy?: string;
updatedBy?: string;
}
const BookingSchema: Schema = new Schema({
studentId: {
type: String,
required: [true, 'Student ID is required'],
ref: 'Student'
},
scheduleId: {
type: String,
required: [true, 'Schedule ID is required'],
ref: 'Schedule'
},
courseId: {
type: String,
required: [true, 'Course ID is required'],
ref: 'Course'
},
studentName: {
type: String,
required: [true, 'Student name is required'],
trim: true
},
courseName: {
type: String,
required: [true, 'Course name is required'],
trim: true
},
scheduleInfo: {
type: String,
required: [true, 'Schedule info is required'],
trim: true
},
bookingDate: {
type: Date,
default: Date.now,
required: true
},
status: {
type: String,
enum: ['confirmed', 'cancelled', 'completed'],
default: 'confirmed',
required: true
},
notes: {
type: String,
trim: true,
maxlength: [500, 'Notes cannot exceed 500 characters']
},
createdBy: {
type: String,
ref: 'User'
},
updatedBy: {
type: String,
ref: 'User'
}
}, {
timestamps: true,
toJSON: { virtuals: false },
toObject: { virtuals: false }
});
// Index for better query performance
BookingSchema.index({ studentId: 1, status: 1 });
BookingSchema.index({ scheduleId: 1 });
BookingSchema.index({ courseId: 1 });
BookingSchema.index({ bookingDate: 1 });
// Compound index to prevent duplicate bookings
BookingSchema.index({ studentId: 1, scheduleId: 1 }, { unique: true });
export const Booking = mongoose.model<IBooking>('Booking', BookingSchema);
import mongoose, { Document, Schema } from 'mongoose';
/**
* @typedef {Object} Course
* @property {string} name - Týn khßa hýc
* @property {string} code - Mß khßa hýc
* @property {string} description - Mß tß khßa hýc
* @property {string} instructor - Gißng viýn
* @property {number} duration - Thýi lýýng (tuýn)
* @property {string} status - Trýng thýi (active, inactive, completed)
* @property {Date} startDate - Ngýy bßt áßu
* @property {Date} endDate - Ngýy kýt thýc
* @property {number} maxStudents - Sß hýc viýn tßi ßa
* @property {number} currentStudents - Sß hýc viýn hiýn tßi
* @property {string[]} scheduleIds - Danh sých ID lých hýc
* @property {Date} createdAt - Ngýy tßo
* @property {Date} updatedAt - Ngýy cßp nhßt
* @property {string} createdBy - Ngýi tßo
* @property {string} updatedBy - Ngýi cßp nhßt
*/
export interface ICourse extends Document {
name: string;
code: string;
description: string;
instructor: string;
duration: number;
status: 'active' | 'inactive' | 'completed';
startDate: Date;
endDate: Date;
maxStudents: number;
currentStudents: number;
scheduleIds: string[];
createdAt: Date;
updatedAt: Date;
createdBy?: string;
updatedBy?: string;
}
const CourseSchema: Schema = new Schema({
name: {
type: String,
required: [true, 'Týn khßa hýc lý bßt buýc'],
trim: true,
maxlength: [200, 'Týn khßa hýc khýng áßß výt quß 200 ký tß']
},
code: {
type: String,
required: [true, 'Mß khßa hýc lý bßt buýc'],
trim: true,
unique: true,
uppercase: true,
maxlength: [20, 'Mß khßa hýc khýng áßß výt quß 20 ký tß']
},
description: {
type: String,
required: [true, 'Mß tß lý bßt buýc'],
trim: true,
maxlength: [1000, 'Mß tß khýng áßß výt quß 1000 ký tß']
},
instructor: {
type: String,
required: [true, 'Gißng viýn lý bßt buýc'],
trim: true,
maxlength: [100, 'Týn gißng viýn khýng áßß výt quß 100 ký tß']
},
duration: {
type: Number,
required: [true, 'Thýi lýýng lý bßt buýc'],
min: [1, 'Thýi lýýng phßi it nhßt 1 tuýn'],
max: [52, 'Thýi lýýng khýng áßß výt quß 52 tuýn']
},
status: {
type: String,
enum: ['active', 'inactive', 'completed'],
default: 'active',
required: true
},
startDate: {
type: Date,
required: [true, 'Ngýy bßt áßu lý bßt buýc']
},
endDate: {
type: Date,
required: [true, 'Ngýy kýt thýc lý bßt buýc'],
validate: {
validator: function (this: ICourse, value: Date) {
return value > this.startDate;
},
message: 'Ngýy kýt thýc phßi sau ngýy bßt áßu'
}
},
maxStudents: {
type: Number,
required: [true, 'Sß hýc viýn tßi ßa lý bßt buýc'],
min: [1, 'Sß hýc viýn tßi ßa phßi it nhßt 1'],
max: [100, 'Sß hýc viýn tßi ßa khýng áßß výt quß 100']
},
currentStudents: {
type: Number,
default: 0,
min: [0, 'Sß hýc viýn hiýn tßi khýng áßß ßm']
},
scheduleIds: [{
type: String,
ref: 'Schedule'
}],
createdBy: {
type: String,
ref: 'User'
},
updatedBy: {
type: String,
ref: 'User'
}
}, {
timestamps: true,
toJSON: { virtuals: false },
toObject: { virtuals: false }
});
// Virtual for available slots
CourseSchema.virtual('availableSlots').get(function (this: ICourse) {
return this.maxStudents - this.currentStudents;
});
// Index for better query performance
CourseSchema.index({ code: 1 });
CourseSchema.index({ status: 1 });
CourseSchema.index({ instructor: 1 });
CourseSchema.index({ name: 'text' });
export const Course = mongoose.model<ICourse>('Course', CourseSchema);
import mongoose, { Document, Schema } from 'mongoose';
/**
* @typedef {Object} Schedule
* @property {string} courseId - ID khßa hýc
* @property {string} courseName - Týn khßa hýc
* @property {string} instructor - Gißng viýn
* @property {string} dayOfWeek - Thß trong tuýn (Monday, Tuesday, etc.)
* @property {Date} startTime - Thýi gian bßt áßu
* @property {Date} endTime - Thýi gian kýt thýc
* @property {string} room - Phßng hýc
* @property {string} semester - Ký hýc
* @property {string[]} studentIds - Danh sých ID hýc viýn
* @property {string} status - Trýng thýi (active, cancelled, completed)
* @property {Date} startDate - Ngýy bßt áßu khßa hýc
* @property {Date} endDate - Ngýy kýt thýc khßa hýc
* @property {Date} createdAt - Ngýy tßo
* @property {Date} updatedAt - Ngýy cßp nhßt
* @property {string} createdBy - Ngýi tßo
* @property {string} updatedBy - Ngýi cßp nhßt
*/
export interface ISchedule extends Document {
courseId: string;
courseName: string;
instructor: string;
dayOfWeek: string;
startTime: Date;
endTime: Date;
room: string;
semester: string;
studentIds: string[];
status: 'active' | 'cancelled' | 'completed';
startDate: Date;
endDate: Date;
createdAt: Date;
updatedAt: Date;
createdBy?: string;
updatedBy?: string;
}
const ScheduleSchema: Schema = new Schema({
courseId: {
type: String,
required: [true, 'ID khßa hýc lý bßt buýc'],
ref: 'Course'
},
courseName: {
type: String,
required: [true, 'Týn khßa hýc lý bßt buýc'],
trim: true
},
instructor: {
type: String,
required: [true, 'Gißng viýn lý bßt buýc'],
trim: true
},
dayOfWeek: {
type: String,
required: [true, 'Thß trong tuýn lý bßt buýc'],
enum: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'],
trim: true
},
startTime: {
type: Date,
required: [true, 'Thýi gian bßt áßu lý bßt buýc']
},
endTime: {
type: Date,
required: [true, 'Thýi gian kýt thýc lý bßt buýc'],
validate: {
validator: function (this: ISchedule, value: Date) {
return value > this.startTime;
},
message: 'Thýi gian kýt thýc phßi sau thýi gian bßt áßu'
}
},
room: {
type: String,
required: [true, 'Phßng hýc lý bßt buýc'],
trim: true,
maxlength: [50, 'Týn phßng khýng áßß výt quß 50 ký tß']
},
semester: {
type: String,
required: [true, 'Ký hýc lý bßt buýc'],
trim: true
},
studentIds: [{
type: String,
ref: 'Student'
}],
status: {
type: String,
enum: ['active', 'cancelled', 'completed'],
default: 'active',
required: true
},
startDate: {
type: Date,
required: [true, 'Ngýy bßt áßu lý bßt buýc']
},
endDate: {
type: Date,
required: [true, 'Ngýy kýt thýc lý bßt buýc'],
validate: {
validator: function (this: ISchedule, value: Date) {
return value > this.startDate;
},
message: 'Ngýy kýt thýc phßi sau ngýy bßt áßu'
}
},
createdBy: {
type: String,
ref: 'User'
},
updatedBy: {
type: String,
ref: 'User'
}
}, {
timestamps: true,
toJSON: { virtuals: false },
toObject: { virtuals: false }
});
// Index for better query performance
ScheduleSchema.index({ courseId: 1, semester: 1 });
ScheduleSchema.index({ instructor: 1 });
ScheduleSchema.index({ dayOfWeek: 1, startTime: 1 });
ScheduleSchema.index({ status: 1 });
ScheduleSchema.index({ room: 1 });
export const Schedule = mongoose.model<ISchedule>('Schedule', ScheduleSchema);
import mongoose, { Document, Schema } from 'mongoose';
/**
* @typedef {Object} Student
* @property {string} fullName - Hý và tên áy áß
* @property {string} email - Email hýc viýn
* @property {string} phone - Sß áiýn thoßi
* @property {string} courseId - ID khßa hýc
* @property {string} courseName - Týn khßa hýc
* @property {string} semester - Ký hýc
* @property {string} status - Trýng thýi (active, inactive, completed)
* @property {Date} startDate - Ngýy bßt áßu
* @property {Date} endDate - Ngýy kýt thýc
* @property {string[]} scheduleIds - Danh sých ID lých hýc
* @property {Date} createdAt - Ngýy tßo
* @property {Date} updatedAt - Ngýy cßp nhßt
* @property {string} createdBy - Ngýi tßo
* @property {string} updatedBy - Ngýi cßp nhßt
*/
export interface IStudent extends Document {
fullName: string;
email: string;
phone: string;
courseId: string;
courseName: string;
semester: string;
status: 'active' | 'inactive' | 'completed';
startDate: Date;
endDate: Date;
scheduleIds: string[];
createdAt: Date;
updatedAt: Date;
createdBy?: string;
updatedBy?: string;
}
const StudentSchema: Schema = new Schema({
fullName: {
type: String,
required: [true, 'Hý và tên lý bßt buýc'],
trim: true,
maxlength: [100, 'Hý và tên khýng áßß výt quß 100 ký tß']
},
email: {
type: String,
required: [true, 'Email lý bßt buýc'],
trim: true,
lowercase: true,
match: [/^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,3})+$/, 'Email khýng hýp lß']
},
phone: {
type: String,
required: [true, 'Sß áiýn thoßi lý bßt buýc'],
trim: true,
match: [/^[0-9]{10,11}$/, 'Sß áiýn thoßi khýng hýp lß']
},
courseId: {
type: String,
required: [true, 'ID khßa hýc lý bßt buýc'],
ref: 'Course'
},
courseName: {
type: String,
required: [true, 'Týn khßa hýc lý bßt buýc'],
trim: true
},
semester: {
type: String,
required: [true, 'Ký hýc lý bßt buýc'],
trim: true
},
status: {
type: String,
enum: ['active', 'inactive', 'completed'],
default: 'active',
required: true
},
startDate: {
type: Date,
required: [true, 'Ngýy bßt áßu lý bßt buýc']
},
endDate: {
type: Date,
required: [true, 'Ngýy kýt thýc lý bßt buýc'],
validate: {
validator: function (this: IStudent, value: Date) {
return value > this.startDate;
},
message: 'Ngýy kýt thýc phßi sau ngýy bßt áßu'
}
},
scheduleIds: [{
type: String,
ref: 'Schedule'
}],
createdBy: {
type: String,
ref: 'User'
},
updatedBy: {
type: String,
ref: 'User'
}
}, {
timestamps: true,
toJSON: { virtuals: false },
toObject: { virtuals: false }
});
// Index for better query performance
StudentSchema.index({ email: 1 });
StudentSchema.index({ courseId: 1, semester: 1 });
StudentSchema.index({ status: 1 });
StudentSchema.index({ fullName: 'text' });
export const Student = mongoose.model<IStudent>('Student', StudentSchema);
import { Booking, IBooking } from "#models/Booking";
import LoggingService from "#services/file-system-handlers/logService";
import { MeUError } from "#interfaces/IApi";
export interface BookingFindManyOptions {
page?: number;
pageSize?: number;
sortField?: string;
sortOrder?: 'asc' | 'desc';
status?: string;
studentId?: string;
scheduleId?: string;
courseId?: string;
}
export interface BookingFindManyReturnModel {
count: number;
rows: IBooking[];
page?: number;
pageSize?: number;
}
export class BookingProvider {
private logger: LoggingService;
constructor() {
this.logger = new LoggingService();
}
async getAll(opts: BookingFindManyOptions): Promise<BookingFindManyReturnModel> {
try {
const { page, pageSize, sortField, sortOrder, status, studentId, scheduleId, courseId, ...baseQuery } = opts;
// Build filter
const filter: any = {};
if (status) filter.status = status;
if (studentId) filter.studentId = studentId;
if (scheduleId) filter.scheduleId = scheduleId;
if (courseId) filter.courseId = courseId;
// Build query
let query = Booking.find(filter);
// Add sorting
if (sortField && sortOrder) {
const sort: any = {};
sort[sortField] = sortOrder === 'asc' ? 1 : -1;
query = query.sort(sort);
} else {
query = query.sort({ bookingDate: -1 });
}
// Add pagination
if (page !== undefined && pageSize !== undefined && pageSize > 0) {
const skip = ((page || 1) - 1) * pageSize;
query = query.skip(skip).limit(pageSize);
}
// Execute query
const [rows, count] = await Promise.all([
query.exec(),
Booking.countDocuments(filter)
]);
const result: BookingFindManyReturnModel = {
count,
rows,
};
if (page !== undefined) result.page = page;
if (pageSize !== undefined) result.pageSize = pageSize;
return result;
} catch (error) {
await this.logError(error as Error);
throw error;
}
}
async getById(id: string): Promise<IBooking> {
try {
const result = await Booking.findById(id);
if (!result) {
throw new MeUError(404, "DB", `Booking with id ${id} not found`);
}
return result;
} catch (error) {
await this.logError(error as Error);
throw error;
}
}
async getOne(where: any): Promise<IBooking | null> {
try {
return await Booking.findOne(where);
} catch (error) {
await this.logError(error as Error);
throw error;
}
}
async create(body: Partial<IBooking>, options?: { createdBy?: string | undefined }): Promise<IBooking> {
try {
const bookingData = {
...body,
createdBy: options?.createdBy
};
const result = new Booking(bookingData);
const saved = await result.save();
await this.logger.logInfoAsync("BookingProvider", new Error(`Created booking with id ${saved._id}`), null);
return saved;
} catch (error) {
await this.logError(error as Error);
throw error;
}
}
async update(id: string, body: Partial<IBooking>, options?: { updatedBy?: string | undefined }): Promise<IBooking> {
try {
const updateData = {
...body,
updatedBy: options?.updatedBy,
updatedAt: new Date()
};
const result = await Booking.findByIdAndUpdate(
id,
updateData,
{ new: true, runValidators: true }
);
if (!result) {
throw new MeUError(404, "DB", `Booking with id ${id} not found`);
}
await this.logger.logInfoAsync("BookingProvider", new Error(`Updated booking with id ${id}`), null);
return result;
} catch (error) {
await this.logError(error as Error);
throw error;
}
}
async delete(id: string): Promise<string> {
try {
const result = await Booking.findByIdAndDelete(id);
if (!result) {
throw new MeUError(404, "DB", `Booking with id ${id} not found`);
}
await this.logger.logInfoAsync("BookingProvider", new Error(`Deleted booking with id ${id}`), null);
return "Successfully deleted booking";
} catch (error) {
await this.logError(error as Error);
throw error;
}
}
async count(where?: any): Promise<number> {
try {
return await Booking.countDocuments(where || {});
} catch (error) {
await this.logError(error as Error);
throw error;
}
}
async exists(where: any): Promise<boolean> {
try {
const count = await Booking.countDocuments(where);
return count > 0;
} catch (error) {
await this.logError(error as Error);
throw error;
}
}
async logError(err: MeUError | Error) {
await this.logger.logErrorAsync("BookingProvider", err, null);
return;
}
}
import { Course, ICourse } from "#models/Course";
import LoggingService from "#services/file-system-handlers/logService";
import { MeUError } from "#interfaces/IApi";
export interface CourseFindManyOptions {
page?: number;
pageSize?: number;
sortField?: string;
sortOrder?: 'asc' | 'desc';
status?: string;
instructor?: string;
search?: string;
}
export interface CourseFindManyReturnModel {
count: number;
rows: ICourse[];
page?: number;
pageSize?: number;
}
export class CourseProvider {
private logger: LoggingService;
constructor() {
this.logger = new LoggingService();
}
async getAll(opts: CourseFindManyOptions): Promise<CourseFindManyReturnModel> {
try {
const { page, pageSize, sortField, sortOrder, status, instructor, search, ...baseQuery } = opts;
// Build filter
const filter: any = {};
if (status) filter.status = status;
if (instructor) filter.instructor = instructor;
if (search) {
filter.$or = [
{ name: { $regex: search, $options: 'i' } },
{ code: { $regex: search, $options: 'i' } }
];
}
// Build query
let query = Course.find(filter);
// Add sorting
if (sortField && sortOrder) {
const sort: any = {};
sort[sortField] = sortOrder === 'asc' ? 1 : -1;
query = query.sort(sort);
} else {
query = query.sort({ createdAt: -1 });
}
// Add pagination
if (page !== undefined && pageSize !== undefined && pageSize > 0) {
const skip = ((page || 1) - 1) * pageSize;
query = query.skip(skip).limit(pageSize);
}
// Execute query
const [rows, count] = await Promise.all([
query.exec(),
Course.countDocuments(filter)
]);
const result: CourseFindManyReturnModel = {
count,
rows,
};
if (page !== undefined) result.page = page;
if (pageSize !== undefined) result.pageSize = pageSize;
return result;
} catch (error) {
await this.logError(error as Error);
throw error;
}
}
async getById(id: string): Promise<ICourse> {
try {
const result = await Course.findById(id);
if (!result) {
throw new MeUError(404, "DB", `Course with id ${id} not found`);
}
return result;
} catch (error) {
await this.logError(error as Error);
throw error;
}
}
async getOne(where: any): Promise<ICourse | null> {
try {
return await Course.findOne(where);
} catch (error) {
await this.logError(error as Error);
throw error;
}
}
async create(body: Partial<ICourse>, options?: { createdBy?: string | undefined }): Promise<ICourse> {
try {
const courseData = {
...body,
createdBy: options?.createdBy
};
const result = new Course(courseData);
const saved = await result.save();
await this.logger.logInfoAsync("CourseProvider", new Error(`Created course with id ${saved._id}`), null);
return saved;
} catch (error) {
await this.logError(error as Error);
throw error;
}
}
async update(id: string, body: Partial<ICourse>, options?: { updatedBy?: string | undefined }): Promise<ICourse> {
try {
const updateData = {
...body,
updatedBy: options?.updatedBy,
updatedAt: new Date()
};
const result = await Course.findByIdAndUpdate(
id,
updateData,
{ new: true, runValidators: true }
);
if (!result) {
throw new MeUError(404, "DB", `Course with id ${id} not found`);
}
await this.logger.logInfoAsync("CourseProvider", new Error(`Updated course with id ${id}`), null);
return result;
} catch (error) {
await this.logError(error as Error);
throw error;
}
}
async delete(id: string): Promise<string> {
try {
const result = await Course.findByIdAndDelete(id);
if (!result) {
throw new MeUError(404, "DB", `Course with id ${id} not found`);
}
await this.logger.logInfoAsync("CourseProvider", new Error(`Deleted course with id ${id}`), null);
return "Successfully deleted course";
} catch (error) {
await this.logError(error as Error);
throw error;
}
}
async count(where?: any): Promise<number> {
try {
return await Course.countDocuments(where || {});
} catch (error) {
await this.logError(error as Error);
throw error;
}
}
async exists(where: any): Promise<boolean> {
try {
const count = await Course.countDocuments(where);
return count > 0;
} catch (error) {
await this.logError(error as Error);
throw error;
}
}
async logError(err: MeUError | Error) {
await this.logger.logErrorAsync("CourseProvider", err, null);
return;
}
}
import { Schedule, ISchedule } from "#models/Schedule";
import LoggingService from "#services/file-system-handlers/logService";
import { MeUError } from "#interfaces/IApi";
export interface ScheduleFindManyOptions {
page?: number;
pageSize?: number;
sortField?: string;
sortOrder?: 'asc' | 'desc';
status?: string;
courseId?: string;
instructor?: string;
dayOfWeek?: string;
semester?: string;
}
export interface ScheduleFindManyReturnModel {
count: number;
rows: ISchedule[];
page?: number;
pageSize?: number;
}
export class ScheduleProvider {
private logger: LoggingService;
constructor() {
this.logger = new LoggingService();
}
async getAll(opts: ScheduleFindManyOptions): Promise<ScheduleFindManyReturnModel> {
try {
const { page, pageSize, sortField, sortOrder, status, courseId, instructor, dayOfWeek, semester, ...baseQuery } = opts;
// Build filter
const filter: any = {};
if (status) filter.status = status;
if (courseId) filter.courseId = courseId;
if (instructor) filter.instructor = instructor;
if (dayOfWeek) filter.dayOfWeek = dayOfWeek;
if (semester) filter.semester = semester;
// Build query
let query = Schedule.find(filter);
// Add sorting
if (sortField && sortOrder) {
const sort: any = {};
sort[sortField] = sortOrder === 'asc' ? 1 : -1;
query = query.sort(sort);
} else {
query = query.sort({ dayOfWeek: 1, startTime: 1 });
}
// Add pagination
if (page !== undefined && pageSize !== undefined && pageSize > 0) {
const skip = ((page || 1) - 1) * pageSize;
query = query.skip(skip).limit(pageSize);
}
// Execute query
const [rows, count] = await Promise.all([
query.exec(),
Schedule.countDocuments(filter)
]);
const result: ScheduleFindManyReturnModel = {
count,
rows,
};
if (page !== undefined) result.page = page;
if (pageSize !== undefined) result.pageSize = pageSize;
return result;
} catch (error) {
await this.logError(error as Error);
throw error;
}
}
async getById(id: string): Promise<ISchedule> {
try {
const result = await Schedule.findById(id);
if (!result) {
throw new MeUError(404, "DB", `Schedule with id ${id} not found`);
}
return result;
} catch (error) {
await this.logError(error as Error);
throw error;
}
}
async getOne(where: any): Promise<ISchedule | null> {
try {
return await Schedule.findOne(where);
} catch (error) {
await this.logError(error as Error);
throw error;
}
}
async create(body: Partial<ISchedule>, options?: { createdBy?: string | undefined }): Promise<ISchedule> {
try {
const scheduleData = {
...body,
createdBy: options?.createdBy
};
const result = new Schedule(scheduleData);
const saved = await result.save();
await this.logger.logInfoAsync("ScheduleProvider", new Error(`Created schedule with id ${saved._id}`), null);
return saved;
} catch (error) {
await this.logError(error as Error);
throw error;
}
}
async update(id: string, body: Partial<ISchedule>, options?: { updatedBy?: string | undefined }): Promise<ISchedule> {
try {
const updateData = {
...body,
updatedBy: options?.updatedBy,
updatedAt: new Date()
};
const result = await Schedule.findByIdAndUpdate(
id,
updateData,
{ new: true, runValidators: true }
);
if (!result) {
throw new MeUError(404, "DB", `Schedule with id ${id} not found`);
}
await this.logger.logInfoAsync("ScheduleProvider", new Error(`Updated schedule with id ${id}`), null);
return result;
} catch (error) {
await this.logError(error as Error);
throw error;
}
}
async delete(id: string): Promise<string> {
try {
const result = await Schedule.findByIdAndDelete(id);
if (!result) {
throw new MeUError(404, "DB", `Schedule with id ${id} not found`);
}
await this.logger.logInfoAsync("ScheduleProvider", new Error(`Deleted schedule with id ${id}`), null);
return "Successfully deleted schedule";
} catch (error) {
await this.logError(error as Error);
throw error;
}
}
async count(where?: any): Promise<number> {
try {
return await Schedule.countDocuments(where || {});
} catch (error) {
await this.logError(error as Error);
throw error;
}
}
async exists(where: any): Promise<boolean> {
try {
const count = await Schedule.countDocuments(where);
return count > 0;
} catch (error) {
await this.logError(error as Error);
throw error;
}
}
async logError(err: MeUError | Error) {
await this.logger.logErrorAsync("ScheduleProvider", err, null);
return;
}
}
import { Student, IStudent } from "#models/Student";
import LoggingService from "#services/file-system-handlers/logService";
import { MeUError } from "#interfaces/IApi";
export interface StudentFindManyOptions {
page?: number;
pageSize?: number;
sortField?: string;
sortOrder?: 'asc' | 'desc';
status?: string;
courseId?: string;
semester?: string;
search?: string;
}
export interface StudentFindManyReturnModel {
count: number;
rows: IStudent[];
page?: number;
pageSize?: number;
}
export class StudentProvider {
private logger: LoggingService;
constructor() {
this.logger = new LoggingService();
}
async getAll(opts: StudentFindManyOptions): Promise<StudentFindManyReturnModel> {
try {
const { page, pageSize, sortField, sortOrder, status, courseId, semester, search, ...baseQuery } = opts;
// Build filter
const filter: any = {};
if (status) filter.status = status;
if (courseId) filter.courseId = courseId;
if (semester) filter.semester = semester;
if (search) {
filter.$or = [
{ fullName: { $regex: search, $options: 'i' } },
{ email: { $regex: search, $options: 'i' } }
];
}
// Build query
let query = Student.find(filter);
// Add sorting
if (sortField && sortOrder) {
const sort: any = {};
sort[sortField] = sortOrder === 'asc' ? 1 : -1;
query = query.sort(sort);
} else {
query = query.sort({ createdAt: -1 });
}
// Add pagination
if (page !== undefined && pageSize !== undefined && pageSize > 0) {
const skip = ((page || 1) - 1) * pageSize;
query = query.skip(skip).limit(pageSize);
}
// Execute query
const [rows, count] = await Promise.all([
query.exec(),
Student.countDocuments(filter)
]);
const result: StudentFindManyReturnModel = {
count,
rows,
};
if (page !== undefined) result.page = page;
if (pageSize !== undefined) result.pageSize = pageSize;
return result;
} catch (error) {
await this.logError(error as Error);
throw error;
}
}
async getById(id: string): Promise<IStudent> {
try {
const result = await Student.findById(id);
if (!result) {
throw new MeUError(404, "DB", `Student with id ${id} not found`);
}
return result;
} catch (error) {
await this.logError(error as Error);
throw error;
}
}
async getOne(where: any): Promise<IStudent | null> {
try {
return await Student.findOne(where);
} catch (error) {
await this.logError(error as Error);
throw error;
}
}
async create(body: Partial<IStudent>, options?: { createdBy?: string | undefined }): Promise<IStudent> {
try {
const studentData = {
...body,
createdBy: options?.createdBy
};
const result = new Student(studentData);
const saved = await result.save();
await this.logger.logInfoAsync("StudentProvider", new Error(`Created student with id ${saved._id}`), null);
return saved;
} catch (error) {
await this.logError(error as Error);
throw error;
}
}
async update(id: string, body: Partial<IStudent>, options?: { updatedBy?: string }): Promise<IStudent> {
try {
const updateData = {
...body,
updatedBy: options?.updatedBy,
updatedAt: new Date()
};
const result = await Student.findByIdAndUpdate(
id,
updateData,
{ new: true, runValidators: true }
);
if (!result) {
throw new MeUError(404, "DB", `Student with id ${id} not found`);
}
await this.logger.logInfoAsync("StudentProvider", new Error(`Updated student with id ${id}`), null);
return result;
} catch (error) {
await this.logError(error as Error);
throw error;
}
}
async delete(id: string): Promise<string> {
try {
const result = await Student.findByIdAndDelete(id);
if (!result) {
throw new MeUError(404, "DB", `Student with id ${id} not found`);
}
await this.logger.logInfoAsync("StudentProvider", new Error(`Deleted student with id ${id}`), null);
return "Successfully deleted student";
} catch (error) {
await this.logError(error as Error);
throw error;
}
}
async count(where?: any): Promise<number> {
try {
return await Student.countDocuments(where || {});
} catch (error) {
await this.logError(error as Error);
throw error;
}
}
async exists(where: any): Promise<boolean> {
try {
const count = await Student.countDocuments(where);
return count > 0;
} catch (error) {
await this.logError(error as Error);
throw error;
}
}
async logError(err: MeUError | Error) {
await this.logger.logErrorAsync("StudentProvider", err, null);
return;
}
}
......@@ -12,6 +12,8 @@ import { readFileSync, mkdirSync, writeFileSync } from "fs";
import { Environment } from "./interfaces/IEnv";
import { root } from "./root";
import constants from "./constants/index";
// Database
import { connectDB } from "./config/database";
// Middlewares & Schedulers
import responseTemplate from "./middlewares/response";
import { apiQueryModifier } from "./middlewares/mod/api-query-modifier";
......@@ -110,6 +112,9 @@ const // Server functions
startServer = async (env: Environment) => {
// Environment variables are loaded in root.ts via dotenv
// Connect to MongoDB first
await connectDB();
const // Path
port: number = parseInt(process.env.PORT || "3000"),
serverHost: string = process.env.BACKEND_URL || "http://localhost:3000",
......
module.exports = {
Booking: {
type: "object",
required: ["studentId", "scheduleId", "courseId", "studentName", "courseName", "scheduleInfo"],
properties: {
_id: {
type: "string",
description: "Booking ID",
example: "507f1f77bcf86cd799439011"
},
studentId: {
type: "string",
description: "Student ID",
example: "507f1f77bcf86cd799439012"
},
scheduleId: {
type: "string",
description: "Schedule ID",
example: "507f1f77bcf86cd799439013"
},
courseId: {
type: "string",
description: "Course ID",
example: "507f1f77bcf86cd799439014"
},
studentName: {
type: "string",
description: "Student's full name",
example: "Nguyen Van A"
},
courseName: {
type: "string",
description: "Course name",
example: "Advanced JavaScript"
},
scheduleInfo: {
type: "string",
description: "Schedule information",
example: "Monday 9:00-11:00, Room 301"
},
bookingDate: {
type: "string",
format: "date-time",
description: "Booking date",
example: "2024-08-15T10:30:00Z"
},
status: {
type: "string",
enum: ["confirmed", "cancelled", "completed"],
default: "confirmed",
description: "Booking status"
},
notes: {
type: "string",
description: "Additional notes",
example: "Student requested weekend materials"
},
createdAt: {
type: "string",
format: "date-time",
description: "Creation timestamp"
},
updatedAt: {
type: "string",
format: "date-time",
description: "Last update timestamp"
},
createdBy: {
type: "string",
description: "User who created this record"
},
updatedBy: {
type: "string",
description: "User who last updated this record"
}
}
},
BookingMutate: {
type: "object",
required: ["studentId", "scheduleId", "courseId", "studentName", "courseName", "scheduleInfo"],
properties: {
studentId: {
type: "string",
description: "Student ID",
example: "507f1f77bcf86cd799439012"
},
scheduleId: {
type: "string",
description: "Schedule ID",
example: "507f1f77bcf86cd799439013"
},
courseId: {
type: "string",
description: "Course ID",
example: "507f1f77bcf86cd799439014"
},
studentName: {
type: "string",
description: "Student's full name",
example: "Nguyen Van A"
},
courseName: {
type: "string",
description: "Course name",
example: "Advanced JavaScript"
},
scheduleInfo: {
type: "string",
description: "Schedule information",
example: "Monday 9:00-11:00, Room 301"
},
bookingDate: {
type: "string",
format: "date-time",
description: "Booking date",
example: "2024-08-15T10:30:00Z"
},
status: {
type: "string",
enum: ["confirmed", "cancelled", "completed"],
default: "confirmed",
description: "Booking status"
},
notes: {
type: "string",
description: "Additional notes",
example: "Student requested weekend materials"
}
}
}
};
module.exports = {
Course: {
type: "object",
required: ["name", "code", "description", "instructor", "duration", "startDate", "endDate", "maxStudents"],
properties: {
_id: {
type: "string",
description: "Course ID",
example: "507f1f77bcf86cd799439011"
},
name: {
type: "string",
description: "Course name",
example: "Advanced JavaScript"
},
code: {
type: "string",
description: "Course code",
example: "JS301"
},
description: {
type: "string",
description: "Course description",
example: "Advanced JavaScript programming course"
},
instructor: {
type: "string",
description: "Course instructor",
example: "Dr. John Smith"
},
duration: {
type: "integer",
description: "Course duration in weeks",
example: 12
},
status: {
type: "string",
enum: ["active", "inactive", "completed"],
default: "active",
description: "Course status"
},
startDate: {
type: "string",
format: "date",
description: "Course start date",
example: "2024-09-01"
},
endDate: {
type: "string",
format: "date",
description: "Course end date",
example: "2024-11-30"
},
maxStudents: {
type: "integer",
description: "Maximum number of students",
example: 30
},
currentStudents: {
type: "integer",
default: 0,
description: "Current number of enrolled students",
example: 15
},
scheduleIds: {
type: "array",
items: {
type: "string"
},
description: "Array of schedule IDs"
},
availableSlots: {
type: "integer",
description: "Available slots (virtual field)",
example: 15
},
createdAt: {
type: "string",
format: "date-time",
description: "Creation timestamp"
},
updatedAt: {
type: "string",
format: "date-time",
description: "Last update timestamp"
},
createdBy: {
type: "string",
description: "User who created this record"
},
updatedBy: {
type: "string",
description: "User who last updated this record"
}
}
},
CourseMutate: {
type: "object",
required: ["name", "code", "description", "instructor", "duration", "startDate", "endDate", "maxStudents"],
properties: {
name: {
type: "string",
description: "Course name",
example: "Advanced JavaScript"
},
code: {
type: "string",
description: "Course code",
example: "JS301"
},
description: {
type: "string",
description: "Course description",
example: "Advanced JavaScript programming course"
},
instructor: {
type: "string",
description: "Course instructor",
example: "Dr. John Smith"
},
duration: {
type: "integer",
description: "Course duration in weeks",
example: 12
},
status: {
type: "string",
enum: ["active", "inactive", "completed"],
default: "active",
description: "Course status"
},
startDate: {
type: "string",
format: "date",
description: "Course start date",
example: "2024-09-01"
},
endDate: {
type: "string",
format: "date",
description: "Course end date",
example: "2024-11-30"
},
maxStudents: {
type: "integer",
description: "Maximum number of students",
example: 30
},
currentStudents: {
type: "integer",
default: 0,
description: "Current number of enrolled students",
example: 15
},
scheduleIds: {
type: "array",
items: {
type: "string"
},
description: "Array of schedule IDs"
}
}
}
};
module.exports = {
Schedule: {
type: "object",
required: ["courseId", "courseName", "instructor", "dayOfWeek", "startTime", "endTime", "room", "semester", "startDate", "endDate"],
properties: {
_id: {
type: "string",
description: "Schedule ID",
example: "507f1f77bcf86cd799439011"
},
courseId: {
type: "string",
description: "Course ID",
example: "507f1f77bcf86cd799439012"
},
courseName: {
type: "string",
description: "Course name",
example: "Advanced JavaScript"
},
instructor: {
type: "string",
description: "Instructor name",
example: "Dr. John Smith"
},
dayOfWeek: {
type: "string",
enum: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"],
description: "Day of the week",
example: "Monday"
},
startTime: {
type: "string",
format: "date-time",
description: "Class start time",
example: "2024-09-01T09:00:00Z"
},
endTime: {
type: "string",
format: "date-time",
description: "Class end time",
example: "2024-09-01T11:00:00Z"
},
room: {
type: "string",
description: "Classroom",
example: "Room 301"
},
semester: {
type: "string",
description: "Semester",
example: "Fall 2024"
},
studentIds: {
type: "array",
items: {
type: "string"
},
description: "Array of enrolled student IDs"
},
status: {
type: "string",
enum: ["active", "cancelled", "completed"],
default: "active",
description: "Schedule status"
},
startDate: {
type: "string",
format: "date",
description: "Schedule start date",
example: "2024-09-01"
},
endDate: {
type: "string",
format: "date",
description: "Schedule end date",
example: "2024-12-31"
},
createdAt: {
type: "string",
format: "date-time",
description: "Creation timestamp"
},
updatedAt: {
type: "string",
format: "date-time",
description: "Last update timestamp"
},
createdBy: {
type: "string",
description: "User who created this record"
},
updatedBy: {
type: "string",
description: "User who last updated this record"
}
}
},
ScheduleMutate: {
type: "object",
required: ["courseId", "courseName", "instructor", "dayOfWeek", "startTime", "endTime", "room", "semester", "startDate", "endDate"],
properties: {
courseId: {
type: "string",
description: "Course ID",
example: "507f1f77bcf86cd799439012"
},
courseName: {
type: "string",
description: "Course name",
example: "Advanced JavaScript"
},
instructor: {
type: "string",
description: "Instructor name",
example: "Dr. John Smith"
},
dayOfWeek: {
type: "string",
enum: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"],
description: "Day of the week",
example: "Monday"
},
startTime: {
type: "string",
format: "date-time",
description: "Class start time",
example: "2024-09-01T09:00:00Z"
},
endTime: {
type: "string",
format: "date-time",
description: "Class end time",
example: "2024-09-01T11:00:00Z"
},
room: {
type: "string",
description: "Classroom",
example: "Room 301"
},
semester: {
type: "string",
description: "Semester",
example: "Fall 2024"
},
studentIds: {
type: "array",
items: {
type: "string"
},
description: "Array of enrolled student IDs"
},
status: {
type: "string",
enum: ["active", "cancelled", "completed"],
default: "active",
description: "Schedule status"
},
startDate: {
type: "string",
format: "date",
description: "Schedule start date",
example: "2024-09-01"
},
endDate: {
type: "string",
format: "date",
description: "Schedule end date",
example: "2024-12-31"
}
}
}
};
module.exports = {
Student: {
type: "object",
required: ["fullName", "email", "phone", "courseId", "courseName", "semester", "startDate", "endDate"],
properties: {
_id: {
type: "string",
description: "Student ID",
example: "507f1f77bcf86cd799439011"
},
fullName: {
type: "string",
description: "Student's full name",
example: "Nguyen Van A"
},
email: {
type: "string",
format: "email",
description: "Student's email",
example: "student@example.com"
},
phone: {
type: "string",
description: "Student's phone number",
example: "0912345678"
},
courseId: {
type: "string",
description: "Course ID",
example: "507f1f77bcf86cd799439012"
},
courseName: {
type: "string",
description: "Course name",
example: "Advanced JavaScript"
},
semester: {
type: "string",
description: "Semester",
example: "Fall 2024"
},
status: {
type: "string",
enum: ["active", "inactive", "completed"],
default: "active",
description: "Student status"
},
startDate: {
type: "string",
format: "date",
description: "Course start date",
example: "2024-09-01"
},
endDate: {
type: "string",
format: "date",
description: "Course end date",
example: "2024-12-31"
},
scheduleIds: {
type: "array",
items: {
type: "string"
},
description: "Array of schedule IDs"
},
createdAt: {
type: "string",
format: "date-time",
description: "Creation timestamp"
},
updatedAt: {
type: "string",
format: "date-time",
description: "Last update timestamp"
},
createdBy: {
type: "string",
description: "User who created this record"
},
updatedBy: {
type: "string",
description: "User who last updated this record"
}
}
},
StudentMutate: {
type: "object",
required: ["fullName", "email", "phone", "courseId", "courseName", "semester", "startDate", "endDate"],
properties: {
fullName: {
type: "string",
description: "Student's full name",
example: "Nguyen Van A"
},
email: {
type: "string",
format: "email",
description: "Student's email",
example: "student@example.com"
},
phone: {
type: "string",
description: "Student's phone number",
example: "0912345678"
},
courseId: {
type: "string",
description: "Course ID",
example: "507f1f77bcf86cd799439012"
},
courseName: {
type: "string",
description: "Course name",
example: "Advanced JavaScript"
},
semester: {
type: "string",
description: "Semester",
example: "Fall 2024"
},
status: {
type: "string",
enum: ["active", "inactive", "completed"],
default: "active",
description: "Student status"
},
startDate: {
type: "string",
format: "date",
description: "Course start date",
example: "2024-09-01"
},
endDate: {
type: "string",
format: "date",
description: "Course end date",
example: "2024-12-31"
},
scheduleIds: {
type: "array",
items: {
type: "string"
},
description: "Array of schedule IDs"
}
}
}
};
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment