Commit fb9be9d0 authored by Nguyễn Thị Nguyệt Quế's avatar Nguyễn Thị Nguyệt Quế

Merge branch 'feat/challenge_6_api_enrollment' into 'develop'

feat(challenge_6): add api enrollment and fix auth

See merge request !6
parents 59dd600b 4bfb7be6
import { authMiddleware } from "#middlewares/authentication"; import { authenticate } from "#middlewares/authentication";
import { AuthService } from "#services/authService"; import { AuthService } from "#services/authService";
import { Application } from "express"; import { Application } from "express";
import { Resource } from "express-automatic-routes"; import { Resource } from "express-automatic-routes";
...@@ -24,7 +24,7 @@ export default (_express: Application) => { ...@@ -24,7 +24,7 @@ export default (_express: Application) => {
* $ref: "#/components/schemas/logoutResponse" * $ref: "#/components/schemas/logoutResponse"
*/ */
post: { post: {
middleware: [authMiddleware], middleware: [authenticate],
handler: async (req, res) => { handler: async (req, res) => {
try { try {
......
import { authMiddleware } from "#middlewares/authentication"; import { authenticate } from "#middlewares/authentication";
import { AuthService } from "#services/authService"; import { AuthService } from "#services/authService";
import { Application } from "express"; import { Application } from "express";
import { Resource } from "express-automatic-routes"; import { Resource } from "express-automatic-routes";
...@@ -24,7 +24,7 @@ export default (_express: Application) => { ...@@ -24,7 +24,7 @@ export default (_express: Application) => {
* $ref: "#/components/schemas/Profile" * $ref: "#/components/schemas/Profile"
*/ */
get: { get: {
middleware: [authMiddleware], middleware: [authenticate],
handler: async (req, res) => { handler: async (req, res) => {
try { try {
......
import { authMiddleware } from "#middlewares/authentication"; import { authenticate } from "#middlewares/authentication";
import { AuthService } from "#services/authService.js"; import { AuthService } from "#services/authService.js";
import { MailService } from "#services/mailService.js"; import { MailService } from "#services/mailService.js";
import { Application } from "express"; import { Application } from "express";
...@@ -28,7 +28,7 @@ export default (_express: Application) => { ...@@ -28,7 +28,7 @@ export default (_express: Application) => {
* $ref: "#/components/schemas/sendOtpResponse" * $ref: "#/components/schemas/sendOtpResponse"
*/ */
post: { post: {
middleware: [authMiddleware], middleware: [authenticate],
handler: async (req, res) => { handler: async (req, res) => {
try { try {
......
import { authMiddleware } from "#middlewares/authentication"; import { authenticate } from "#middlewares/authentication";
import { AuthService } from "#services/authService.js"; import { AuthService } from "#services/authService.js";
import { Application } from "express"; import { Application } from "express";
import { Resource } from "express-automatic-routes"; import { Resource } from "express-automatic-routes";
...@@ -32,7 +32,7 @@ export default (_express: Application) => { ...@@ -32,7 +32,7 @@ export default (_express: Application) => {
* $ref: "#/components/schemas/verifyOtpResponse" * $ref: "#/components/schemas/verifyOtpResponse"
*/ */
post: { post: {
middleware: [authMiddleware], middleware: [authenticate],
handler: async (req, res) => { handler: async (req, res) => {
try { try {
......
import type { Application } from "express";
import type { Resource } from "express-automatic-routes";
import { EnrollProvider } from "#providers/EnrollProvider.js";
export default (_express: Application) => {
const enrollProvider = new EnrollProvider();
return <Resource>{
/**
* @openapi
* /api/v1.0/enrollments/all-student-in-class:
* post:
* tags: [enrollments]
* description: Xem tất cả học sinh có trong lớp học
* requestBody:
* required: true
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/AllStudentInClassInput"
* responses:
* 200:
* description: Trả về danh sách học sinh trong lớp học
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/AllStudentInClassOutput"
*/
post: {
handler: async (req, res) => {
try {
const students = await enrollProvider.getAllStudentInClass(req.body.class_id);
return res.status(200).json(students);
} catch (error) {
console.error('Error fetching students in class:', error);
return res.status(400).json({ error: (error as Error).message });
}
}
}
};
};
\ No newline at end of file
import type { Application } from "express";
import type { Resource } from "express-automatic-routes";
import { EnrollProvider } from "#providers/EnrollProvider.js";
import { authorize } from "#middlewares/authorization";
import { login } from "#middlewares/authentication";
export default (_express: Application) => {
const enrollProvider = new EnrollProvider();
return <Resource>{
/**
* @openapi
* /api/v1.0/enrollments/enroll:
* post:
* tags: [enrollments]
* security:
* - bearerAuth: []
* description: Tham gia khoá học
* requestBody:
* required: true
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/EnrollmentInput"
* responses:
* 201:
* description: Đăng ký khóa học thành công
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Enrollment"
*/
post: {
middleware: [login, authorize("student")],
handler: async (req, res) => {
try {
const userId = (req as any).user.id;
const enroll = await enrollProvider.enroll(userId, req.body.class_id);
return res.status(201).json(enroll);
} catch (error) {
console.error('Error creating enrollment:', error);
return res.status(400).json({ error: (error as Error).message });
}
}
}
};
};
\ No newline at end of file
import type { Application } from "express";
import type { Resource } from "express-automatic-routes";
import { EnrollProvider } from "#providers/EnrollProvider.js";
import { authorize } from "#middlewares/authorization";
export default (_express: Application) => {
const enrollProvider = new EnrollProvider();
return <Resource>{
/**
* @openapi
* /api/v1.0/enrollments/unenroll:
* post:
* tags: [enrollments]
* description: Hủy đăng ký khóa học
* requestBody:
* required: true
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/UnenrollmentInput"
* responses:
* 201:
* description: Hủy đăng ký khóa học thành công
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Unenrollment"
*/
post: {
handler: async (req, res) => {
try {
const enroll = await enrollProvider.unEnroll(req.body.userId, req.body.classId);
return res.status(201).json(enroll);
} catch (error) {
console.error('Error unenrolling:', error);
return res.status(400).json({ error: (error as Error).message });
}
}
}
};
};
\ No newline at end of file
...@@ -358,6 +358,97 @@ ...@@ -358,6 +358,97 @@
"type": "string" "type": "string"
} }
} }
},
"EnrollmentInput": {
"type": "object",
"example": {
"class_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
},
"properties": {
"class_id": {
"type": "string",
"example": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}
}
},
"UnenrollmentInput": {
"type": "object",
"example": {
"userId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"classId": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
},
"properties": {
"userId": {
"type": "string",
"example": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
},
"classId": {
"type": "string",
"example": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}
}
},
"Unenrollment": {
"type": "object",
"example": {
"message": "Hủy đăng ký khóa học thành công"
}
},
"Enrollment": {
"type": "object",
"example": {
"user_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"class_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"status": "active"
},
"properties": {
"user_id": {
"type": "string",
"example": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
},
"class_id": {
"type": "string",
"example": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
},
"status": {
"type": "string",
"example": "active"
}
}
},
"AllStudentInClassInput": {
"type": "object",
"example": {
"class_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
},
"properties": {
"class_id": {
"type": "string",
"example": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}
}
},
"AllStudentInClassOutput": {
"type": "object",
"example": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"name": "Nguyen Van A",
"email": "nguyenvana@example.com"
},
"properties": {
"id": {
"type": "string",
"example": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
},
"name": {
"type": "string",
"example": "Nguyen Van A"
},
"email": {
"type": "string",
"example": "nguyenvana@example.com"
}
}
} }
}, },
"parameters": { "parameters": {
...@@ -913,6 +1004,110 @@ ...@@ -913,6 +1004,110 @@
} }
} }
} }
},
"/api/v1.0/enrollments/all-student-in-class": {
"post": {
"tags": [
"enrollments"
],
"description": "Xem tất cả học sinh có trong lớp học",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/AllStudentInClassInput"
}
}
}
},
"responses": {
"200": {
"description": "Trả về danh sách học sinh trong lớp học",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/AllStudentInClassOutput"
}
}
}
}
}
}
}
},
"/api/v1.0/enrollments/enroll": {
"post": {
"tags": [
"enrollments"
],
"security": [
{
"bearerAuth": []
}
],
"description": "Tham gia khoá học",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EnrollmentInput"
}
}
}
},
"responses": {
"201": {
"description": "Đăng ký khóa học thành công",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Enrollment"
}
}
}
}
}
}
}
},
"/api/v1.0/enrollments/unenroll": {
"post": {
"tags": [
"enrollments"
],
"description": "Hủy đăng ký khóa học",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UnenrollmentInput"
}
}
}
},
"responses": {
"201": {
"description": "Hủy đăng ký khóa học thành công",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Unenrollment"
}
}
}
}
}
}
}
} }
}, },
"tags": [] "tags": []
......
import jwt from 'jsonwebtoken'; import jwt from 'jsonwebtoken';
import { Request, Response, NextFunction } from 'express'; import { Response, NextFunction } from 'express';
//demo //demo
const JWT_SECRET = process.env.JWT_SECRET || ''; const JWT_SECRET = process.env.JWT_SECRET || '';
export const authMiddleware = (req: any, res: Response, next: NextFunction) => { export const authenticate = (req: any, res: Response, next: NextFunction) => {
const authHeader = req.headers.authorization; const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) { if (!authHeader || !authHeader.startsWith('Bearer ')) {
......
import jwt from 'jsonwebtoken';
import { Request, Response, NextFunction } from 'express';
import { models } from '#models/sequelize-config.js';
export const authorize = (...allowedRoles: string[]) => {
return async (req: any, res: Response, next: NextFunction) => {
const roleId = req.user?.role_id || [];
const role = await models.roles.findOne({
where: { id: roleId }
});
const hasPermission = allowedRoles.includes(role?.name || '');
if (!hasPermission) {
return res.status(403).json({ error: 'Bạn không có quyền truy cập tài nguyên này' });
}
next();
};
};
\ No newline at end of file
import { models } from "#models/sequelize-config.js";
export class EnrollProvider {
async enroll(userId: string, classId: string) {
const checkEnrollment = await models.enrollments.findOne({
where: {
user_id: userId,
class_id: classId,
status: "active"
}
});
if (checkEnrollment) {
throw new Error("User is already enrolled in this class");
} else {
const enrollment = await models.enrollments.create(
{
user_id: userId,
class_id: classId,
status: "active"
}
);
return enrollment;
}
}
async unEnroll(userId: string, classId: string) {
const checkEnrollment = await models.enrollments.findOne({
where: {
user_id: userId,
class_id: classId,
status: "active"
}
});
if (!checkEnrollment) {
throw new Error("User is not enrolled in this class");
} else {
checkEnrollment.status = "inactive";
await checkEnrollment.save();
return { message: "Hủy đăng ký khóa học thành công" };
}
}
async getAllStudentInClass(classId: string) {
const enrollments = await models.enrollments.findAll({
where: { class_id: classId, status: "active" },
include: [
{ model: models.users, as: "user", attributes: ["id", "name", "email"] }
]
});
const users = enrollments
.map((e: any) => e.user)
.filter((u: any) => u != null);
return users;
}
}
\ No newline at end of file
...@@ -9,6 +9,7 @@ import authProfileSchemas from './authProfile/schema.js'; ...@@ -9,6 +9,7 @@ import authProfileSchemas from './authProfile/schema.js';
import sendOTPSchemas from './sendOTP/schema.js'; import sendOTPSchemas from './sendOTP/schema.js';
import verifyOTPSchemas from './verifyOTP/schema.js'; import verifyOTPSchemas from './verifyOTP/schema.js';
import logoutSchemas from './logout/schema.js'; import logoutSchemas from './logout/schema.js';
import enrollmentSchemas from './enrollment/schema.js';
const swaggerOptions: Options = { const swaggerOptions: Options = {
definition: { definition: {
...@@ -34,6 +35,7 @@ const swaggerOptions: Options = { ...@@ -34,6 +35,7 @@ const swaggerOptions: Options = {
...sendOTPSchemas, ...sendOTPSchemas,
...verifyOTPSchemas, ...verifyOTPSchemas,
...logoutSchemas, ...logoutSchemas,
...enrollmentSchemas,
}, },
parameters: { parameters: {
filters: { filters: {
......
const enrollmentSchemas = {
EnrollmentInput: {
type: 'object',
example: {
class_id: '3fa85f64-5717-4562-b3fc-2c963f66afa6',
},
properties: {
class_id: {
type: 'string',
example: '3fa85f64-5717-4562-b3fc-2c963f66afa6',
},
},
},
UnenrollmentInput: {
type: 'object',
example: {
userId: '3fa85f64-5717-4562-b3fc-2c963f66afa6',
classId: '3fa85f64-5717-4562-b3fc-2c963f66afa6',
},
properties: {
userId: {
type: 'string',
example: '3fa85f64-5717-4562-b3fc-2c963f66afa6',
},
classId: {
type: 'string',
example: '3fa85f64-5717-4562-b3fc-2c963f66afa6',
},
},
},
Unenrollment: {
type: 'object',
example: {
message: 'Hủy đăng ký khóa học thành công',
},
},
Enrollment: {
type: 'object',
example: {
user_id: '3fa85f64-5717-4562-b3fc-2c963f66afa6',
class_id: '3fa85f64-5717-4562-b3fc-2c963f66afa6',
status: 'active',
},
properties: {
user_id: {
type: 'string',
example: '3fa85f64-5717-4562-b3fc-2c963f66afa6',
},
class_id: {
type: 'string',
example: '3fa85f64-5717-4562-b3fc-2c963f66afa6',
},
status: {
type: 'string',
example: 'active',
},
},
},
AllStudentInClassInput: {
type: 'object',
example: {
class_id: '3fa85f64-5717-4562-b3fc-2c963f66afa6',
},
properties: {
class_id: {
type: 'string',
example: '3fa85f64-5717-4562-b3fc-2c963f66afa6',
},
},
},
AllStudentInClassOutput: {
type: 'object',
example: {
id: '3fa85f64-5717-4562-b3fc-2c963f66afa6',
name: 'Nguyen Van A',
email: 'nguyenvana@example.com',
},
properties: {
id: {
type: 'string',
example: '3fa85f64-5717-4562-b3fc-2c963f66afa6',
},
name: {
type: 'string',
example: 'Nguyen Van A',
},
email: {
type: 'string',
example: 'nguyenvana@example.com',
},
},
},
};
export default enrollmentSchemas;
\ No newline at end of file
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