From 82245699fb5c0c33f2b719ecbd0331e4cab19aed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Ti=E1=BA=BFn=20T=C3=A0i?= <63393170+fdhhhdjd@users.noreply.github.com> Date: Wed, 5 Apr 2023 00:24:19 +0700 Subject: [PATCH] #247 [Backend] Library Feedback 01/04/2023 50% --- backend-manager-student/.eslintrc.js | 2 +- .../v1/Swagger/admin_api/borrow.swagger.yaml | 69 ++++- .../borrow_book.controller.js | 267 +++++++++++++++++- .../student.controllers/student.controller.js | 4 +- .../borrow_book_private.route.js | 19 +- backend-manager-student/src/cron_job/cron.js | 7 +- .../controllers/cron_borrow_book.controler.js | 59 ++++ .../src/share/configs/constants.js | 7 +- .../src/share/configs/message.js | 15 +- .../src/share/models/book.model.js | 4 + .../src/share/models/book_borrowed.model.js | 78 ++++- .../src/share/models/favorite.model.js | 58 ++++ .../src/share/models/rating.model.js | 38 +++ .../cron_service/borrow_book_service.js | 17 ++ .../services/user_service/rating_service.js | 18 ++ .../v1/Swagger/user_api/favorite.swagger.yaml | 228 +++++++++++++++ .../v1/Swagger/user_api/rating.swagger.yaml | 176 ++++++++++++ .../v1/Swagger/user_api/user.swagger.yaml | 89 ++++++ .../borrow_book.controller.js | 12 + .../favorite.controller.js | 174 ++++++++++++ .../rating.controllers/rating.controller.js | 165 +++++++++++ .../user.controllers/user.controller.js | 145 +++++++++- .../favorite.routes/favorite.private.route.js | 23 ++ .../src/user_api/v1/routes/private.routes.js | 18 +- .../rating.routes/rating.private.route.js | 15 + .../v1/routes/users.routes/user.route.js | 8 + 26 files changed, 1689 insertions(+), 26 deletions(-) create mode 100644 backend-manager-student/src/cron_job/v1/cron_student/controllers/cron_borrow_book.controler.js create mode 100644 backend-manager-student/src/share/models/favorite.model.js create mode 100644 backend-manager-student/src/share/models/rating.model.js create mode 100644 backend-manager-student/src/share/services/cron_service/borrow_book_service.js create mode 100644 backend-manager-student/src/share/services/user_service/rating_service.js create mode 100644 backend-manager-student/src/user_api/v1/Swagger/user_api/favorite.swagger.yaml create mode 100644 backend-manager-student/src/user_api/v1/Swagger/user_api/rating.swagger.yaml create mode 100644 backend-manager-student/src/user_api/v1/controllers/favorite.controllers/favorite.controller.js create mode 100644 backend-manager-student/src/user_api/v1/controllers/rating.controllers/rating.controller.js create mode 100644 backend-manager-student/src/user_api/v1/routes/favorite.routes/favorite.private.route.js create mode 100644 backend-manager-student/src/user_api/v1/routes/rating.routes/rating.private.route.js diff --git a/backend-manager-student/.eslintrc.js b/backend-manager-student/.eslintrc.js index 8712782..4ebb044 100644 --- a/backend-manager-student/.eslintrc.js +++ b/backend-manager-student/.eslintrc.js @@ -38,7 +38,7 @@ module.exports = { allow: [], }, ], - 'operator-linebreak': ['error', 'before', { overrides: { '?': 'after' } }], + 'operator-linebreak': ['error', 'after', { overrides: { '?': 'before', ':': 'before' } }], 'import/prefer-default-export': 'off', 'import/no-unresolved': 'off', 'linebreak-style': ['error', process.platform === 'win64' && 'win32' ? 'windows' : 'unix'], diff --git a/backend-manager-student/src/admin_api/v1/Swagger/admin_api/borrow.swagger.yaml b/backend-manager-student/src/admin_api/v1/Swagger/admin_api/borrow.swagger.yaml index d0d8365..fac2528 100644 --- a/backend-manager-student/src/admin_api/v1/Swagger/admin_api/borrow.swagger.yaml +++ b/backend-manager-student/src/admin_api/v1/Swagger/admin_api/borrow.swagger.yaml @@ -101,9 +101,76 @@ components: example: Out Of Service #!@author Nguyễn Tiến Tài + #!created_at 04/04/2023 + #!description: Create borrow book +paths: + /v1/admin/private/borrow_book/create: + post: + summary: Create Borrow_book + tags: [Create Borrow_book] + operationId: createBorrowBook + parameters: + - $ref: '#/components/parameters/COOKIE-CLIENT' + - $ref: '#/components/parameters/X-DEVICE-ID' + - $ref: '#/components/parameters/X-OS-TYPE' + - $ref: '#/components/parameters/X-OS-VERSION' + - $ref: '#/components/parameters/X-APP-VERSION' + - $ref: '#/components/parameters/X-DEVICE-NAME' + - $ref: '#/components/parameters/TOKEN' + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + input: + type: object + properties: + borrow_book_input: + type: object + properties: + book_id: + type: string + example: '1475825111725506562' + user_id: + type: string + example: '1475825111725506562' + required: + - book_id + - user_id + responses: + 200: + description: update success + content: + application/json: + schema: + type: object + properties: + status: + type: integer + example: 200 + message: + type: string + example: Success + 400: + description: Invalid input + content: + application/json: + schema: + type: object + properties: + status: + type: integer + example: 400 + message: + type: string + example: Bad Request + 503: + $ref: '#/components/responses/ServerFail' + #!@author Nguyễn Tiến Tài #!created_at 08/03/2023 #!description: update borrow book -paths: /v1/admin/private/borrow_book/update: post: summary: Update Borrow_book diff --git a/backend-manager-student/src/admin_api/v1/controllers/borrow_book.controllers/borrow_book.controller.js b/backend-manager-student/src/admin_api/v1/controllers/borrow_book.controllers/borrow_book.controller.js index 8184638..2a71e20 100644 --- a/backend-manager-student/src/admin_api/v1/controllers/borrow_book.controllers/borrow_book.controller.js +++ b/backend-manager-student/src/admin_api/v1/controllers/borrow_book.controllers/borrow_book.controller.js @@ -2,6 +2,7 @@ const HELPER = require('../../../../share/utils/helper'); const CONSTANTS = require('../../../../share/configs/constants'); const MESSAGES = require('../../../../share/configs/message'); +const RANDOMS = require('../../../../share/utils/random'); //! MIDDLEWARE const { returnReasons } = require('../../../../share/middleware/handle_error'); @@ -12,20 +13,163 @@ const book_model = require('../../../../share/models/book.model'); //! SERVICE const book_admin_service = require('../../../../share/services/admin_service/book_service'); +const book_service = require('../../../../share/services/user_service/book_service'); const BorrowBookController = { /** * @author Nguyễn Tiến Tài - * @created_at 08/02/2022 + * @created_at 04/04/2023 + * @description create BorrowBook + * @function updateBorrowBook + * @return {Object:{Number,String}} + */ + createBorrowBook: async (req, res) => { + // Book input + const { book_id, user_id } = req.body.input.borrow_book_input; + + if (!HELPER.validateBigInt(book_id) || !HELPER.validateBigInt(user_id)) { + return res.status(CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST).json({ + status: CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST, + message: returnReasons(CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST), + element: { + result: MESSAGES.GENERAL.INVALID_INPUT, + }, + }); + } + try { + // Check Student tow a borrow book + const check_borrow_book = await borrow_book_model.checkBorrowBook( + { user_id, isdeleted: CONSTANTS.DELETED_DISABLE }, + { book_id: 'book_id', status: 'status' }, + ); + if (check_borrow_book.length >= 2) { + return res.status(CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST).json({ + status: CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST, + message: returnReasons(CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST), + element: { + result: MESSAGES.GENERAL.EXITS_CAN_ONLY_TOW_BORROW, + }, + }); + } + // check student already borrow book + const data_borrow_book = await borrow_book_model.getBorrowBookById( + { book_id, isdeleted: CONSTANTS.DELETED_DISABLE, user_id }, + '*', + ); + if (data_borrow_book.length > 0 && data_borrow_book[0].status !== CONSTANTS.STATUS_BORROW.DONE) { + let result_borrow; + switch (data_borrow_book[0].status) { + case CONSTANTS.STATUS_BORROW.PENDING: + result_borrow = MESSAGES.GENERAL.ALREADY_BOOK_BORROW; + break; + case CONSTANTS.STATUS_BORROW.BORROWING: + result_borrow = MESSAGES.GENERAL.PLEASE_REFUND_BOOK; + break; + default: + result_borrow = MESSAGES.GENERAL.BORROW_FAIL; + break; + } + return res.status(CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST).json({ + status: CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST, + message: returnReasons(CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST), + element: { + result: result_borrow, + }, + }); + } + + // Check data book exits + const data_book = await book_model.getBookById( + { book_id, isdeleted: CONSTANTS.DELETED_DISABLE }, + { quantity: 'quantity' }, + ); + + if (!data_book.length) { + return res.status(CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST).json({ + status: CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST, + message: returnReasons(CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST), + }); + } + + // Check quantity book + const check_quantity_book = await book_service.handleCheckQuantityBook(data_book[0]); + if (check_quantity_book) { + return res.status(CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST).json({ + status: CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST, + message: returnReasons(CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST), + element: { + result: MESSAGES.GENERAL.BOOK_OUT_OF_STOCK, + }, + }); + } + + // create book_borrowed database + const data_insert = { + borrowed_book_id: RANDOMS.createID(), + book_id, + user_id, + status: CONSTANTS.STATUS_BORROW.PENDING, + }; + + // update quantity book database + const data_update = { + quantity: data_book[0].quantity - 1, + }; + + let err; + let result; + [err, result] = await HELPER.handleRequest( + borrow_book_model.transactionBorrowBook(data_insert, data_update), + ); + + // Insert or update success + if (result) { + // Create key Cache + const key_cache_book_detail = HELPER.getURIFromTemplate(CONSTANTS.KEY_REDIS.DETAIL_BOOK, { + book_id, + }); + + // Delete Cache + book_admin_service.handleDeleteCache(key_cache_book_detail, CONSTANTS.KEY_REDIS.ALL_BOOK); + + return res.status(CONSTANTS.HTTP.STATUS_2XX_OK).json({ + status: CONSTANTS.HTTP.STATUS_2XX_OK, + message: returnReasons(CONSTANTS.HTTP.STATUS_2XX_OK), + element: { + result: MESSAGES.GENERAL.SUCCESS_BORROW_BOOK_ADMIN_SUCCESS, + }, + }); + } + // Insert or update error + if (err) { + return res.status(CONSTANTS.HTTP.STATUS_5XX_INTERNAL_SERVER_ERROR).json({ + status: CONSTANTS.HTTP.STATUS_5XX_INTERNAL_SERVER_ERROR, + message: returnReasons(CONSTANTS.HTTP.STATUS_5XX_INTERNAL_SERVER_ERROR), + }); + } + } catch (error) { + console.error(error); + return res.status(CONSTANTS.HTTP.STATUS_5XX_SERVICE_UNAVAILABLE).json({ + status: CONSTANTS.HTTP.STATUS_5XX_SERVICE_UNAVAILABLE, + message: returnReasons(CONSTANTS.HTTP.STATUS_5XX_SERVICE_UNAVAILABLE), + element: { + result: MESSAGES.GENERAL.SERVER_OUT_OF_SERVICE, + }, + }); + } + }, + /** + * @author Nguyễn Tiến Tài + * @created_at 08/02/2023 * @description update BorrowBook * @function updateBorrowBook - * @return {Object:{Number,String} + * @return {Object:{Number,String}} */ updateBorrowBook: async (req, res) => { const { book_id, user_id, start_date, due_date, status } = req.body.input.borrow_book_input; // Check input - if (!book_id || !user_id || !start_date || !due_date || !status) { + if (!HELPER.validateBigInt(book_id) || !HELPER.validateBigInt(user_id) || !start_date || !due_date || !status) { return res.status(CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST).json({ status: CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST, message: returnReasons(CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST), @@ -156,7 +300,7 @@ const BorrowBookController = { }, /** * @author Nguyễn Tiến Tài - * @created_at 09/03/2022 + * @created_at 09/03/2023 * @description Get All Borrowed Book * @function borrowBook * @return {Object:{Number,String}} @@ -187,7 +331,7 @@ const BorrowBookController = { }, /** * @author Nguyễn Tiến Tài - * @created_at 09/03/2022 + * @created_at 09/03/2023 * @description Detail Borrowed Book * @function borrowBook * @return {Object:{Number,String} @@ -196,7 +340,7 @@ const BorrowBookController = { const borrowed_book_id = req.params.borrowed_book_id; // Check input - if (!borrowed_book_id) { + if (!HELPER.validateBigInt(borrowed_book_id)) { return res.status(CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST).json({ status: CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST, message: returnReasons(CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST), @@ -228,5 +372,116 @@ const BorrowBookController = { }); } }, + + /** + * @author Nguyễn Tiến Tài + * @created_at 04/04/2023 + * @description Delete Borrowed processing Book + * @function deleteBorrowBookProcessing + * @return {Object:{Number,String} + */ + deleteBorrowBookProcessing: async (req, res) => { + const borrowed_book_id = req.body.input.borrowed_book_id; + + // Check input + if (!HELPER.validateBigInt(borrowed_book_id)) { + return res.status(CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST).json({ + status: CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST, + message: returnReasons(CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST), + element: { + result: MESSAGES.GENERAL.INVALID_INPUT, + }, + }); + } + try { + // Take data db + const result_borrow_book_detail = await borrow_book_model.getBorrowBookById( + { + borrowed_book_id, + status: CONSTANTS.STATUS_BORROW.PENDING, + isdeleted: CONSTANTS.DELETED_DISABLE, + }, + { + borrowed_book_id: 'borrowed_book_id', + book_id: 'book_id', + user_id: 'user_id', + }, + ); + if (!result_borrow_book_detail || !result_borrow_book_detail.length) { + return res.status(CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST).json({ + status: CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST, + message: returnReasons(CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST), + element: { + result: MESSAGES.GENERAL.PLEASE_DELETE_BORROW_BOOK_PENDING, + }, + }); + } else if (result_borrow_book_detail) { + const book_id = result_borrow_book_detail[0].book_id; + const user_id = result_borrow_book_detail[0].user_id; + + const data_book = await book_model.getBookById( + { + book_id, + isdeleted: CONSTANTS.DELETED_DISABLE, + }, + { quantity: 'quantity' }, + ); + const data_update_book = { + book_id, + quantity: data_book[0].quantity + 1, + }; + const data_update_borrow = { + borrowed_book_id, + book_id, + user_id, + status: CONSTANTS.STATUS_BORROW.CANCEL, + }; + let err; + let result; + // update book database + [err, result] = await HELPER.handleRequest( + borrow_book_model.transactionUpdateBorrowBook(data_update_book, data_update_borrow), + ); + if (result) { + // Create key Cache + const key_cache_book_detail = HELPER.getURIFromTemplate(CONSTANTS.KEY_REDIS.DETAIL_BOOK, { + book_id, + }); + + // Delete Cache + book_admin_service.handleDeleteCache(key_cache_book_detail, CONSTANTS.KEY_REDIS.ALL_BOOK); + + return res.status(CONSTANTS.HTTP.STATUS_2XX_OK).json({ + status: CONSTANTS.HTTP.STATUS_2XX_OK, + message: returnReasons(CONSTANTS.HTTP.STATUS_2XX_OK), + element: { + result: MESSAGES.GENERAL.SUCCESS_DELETE_BORROW_BOOK, + }, + }); + } + if (err) { + return res.status(CONSTANTS.HTTP.STATUS_5XX_INTERNAL_SERVER_ERROR).json({ + status: CONSTANTS.HTTP.STATUS_5XX_INTERNAL_SERVER_ERROR, + message: returnReasons(CONSTANTS.HTTP.STATUS_5XX_INTERNAL_SERVER_ERROR), + }); + } + } + } catch (error) { + return res.status(CONSTANTS.HTTP.STATUS_5XX_SERVICE_UNAVAILABLE).json({ + status: CONSTANTS.HTTP.STATUS_5XX_SERVICE_UNAVAILABLE, + message: returnReasons(CONSTANTS.HTTP.STATUS_5XX_SERVICE_UNAVAILABLE), + element: { + result: MESSAGES.GENERAL.SERVER_OUT_OF_SERVICE, + }, + }); + } + }, + /** + * @author Nguyễn Tiến Tài + * @created_at 04/04/2023 + * @description update BorrowBook Lost + * @function updateBorrowBook + * @return {Object:{Number,String}} + */ }; module.exports = BorrowBookController; diff --git a/backend-manager-student/src/admin_api/v1/controllers/student.controllers/student.controller.js b/backend-manager-student/src/admin_api/v1/controllers/student.controllers/student.controller.js index cbfe6c0..e3c13f4 100644 --- a/backend-manager-student/src/admin_api/v1/controllers/student.controllers/student.controller.js +++ b/backend-manager-student/src/admin_api/v1/controllers/student.controllers/student.controller.js @@ -113,8 +113,8 @@ const StudentController = { */ updateStudent: async (req, res) => { // Input body - const { student_id, name, avatar_uri, public_id_avatar, address, dob, gender } - = req.body.input.update_student_input; + const { student_id, name, avatar_uri, public_id_avatar, address, dob, gender } = + req.body.input.update_student_input; // Check input if (!student_id || !HELPER.validateBigInt(student_id)) { diff --git a/backend-manager-student/src/admin_api/v1/routes/borrow_book_routes/borrow_book_private.route.js b/backend-manager-student/src/admin_api/v1/routes/borrow_book_routes/borrow_book_private.route.js index 790c542..a85a20b 100644 --- a/backend-manager-student/src/admin_api/v1/routes/borrow_book_routes/borrow_book_private.route.js +++ b/backend-manager-student/src/admin_api/v1/routes/borrow_book_routes/borrow_book_private.route.js @@ -1,16 +1,31 @@ -//! LIBARY +//! LIBRARY const router = require('express').Router(); //! CONTROLLER USER const borrowBookController = require('../../controllers/borrow_book.controllers/borrow_book.controller'); +/** + * @author Nguyễn Tiến Tài + * @created_at 04/04/2023 + * @description Route Update Borrow book student + * @param {('POST')} [method='POST'] The request's method + */ +router.post('/create', borrowBookController.createBorrowBook); + /** * @author Nguyễn Tiến Tài * @created_at 08/03/2023 - * @description Route Update Borrow book sutdent + * @description Route Update Borrow book student * @param {('POST')} [method='POST'] The request's method */ router.post('/update', borrowBookController.updateBorrowBook); +/** + * @author Nguyễn Tiến Tài + * @created_at 04/04/2023 + * @description Route Delete Borrow book student + * @param {('POST')} [method='POST'] The request's method + */ +router.post('/delete', borrowBookController.deleteBorrowBookProcessing); /** * @author Nguyễn Tiến Tài diff --git a/backend-manager-student/src/cron_job/cron.js b/backend-manager-student/src/cron_job/cron.js index e266646..2fa73bd 100644 --- a/backend-manager-student/src/cron_job/cron.js +++ b/backend-manager-student/src/cron_job/cron.js @@ -7,12 +7,13 @@ const cron = require('node-cron'); //! MODEL const cron_verification_student = require('./v1/cron_student/controllers/cron_verification.controller'); const cron_reset_password_student = require('./v1/cron_student/controllers/cron_reset_password.controller'); +const cron_borrow_book_student = require('./v1/cron_student/controllers/cron_borrow_book.controler'); /** * @author Nguyễn Tiến Tài * @created_at 16/12/2022 - * @updated_at 28/02/2022, 30/03/2023 - * @description Run Cron delete verification + * @updated_at 28/02/2022, 30/03/2023, 04/04/2023 + * @description Run All Cron */ cron.schedule(CONSTANTS._5_MINUTE_CRON, async () => { await Promise.all([ @@ -20,6 +21,8 @@ cron.schedule(CONSTANTS._5_MINUTE_CRON, async () => { cron_verification_student.cron_deleted_verification_student(), // Delete reset password cron_reset_password_student.cron_deleted_reset_password_student(), + // Change status borrow book + cron_borrow_book_student.cron_change_status_borrow_book_student(), ]); console.info('Both tasks completed successfully'); }); diff --git a/backend-manager-student/src/cron_job/v1/cron_student/controllers/cron_borrow_book.controler.js b/backend-manager-student/src/cron_job/v1/cron_student/controllers/cron_borrow_book.controler.js new file mode 100644 index 0000000..5fadb26 --- /dev/null +++ b/backend-manager-student/src/cron_job/v1/cron_student/controllers/cron_borrow_book.controler.js @@ -0,0 +1,59 @@ +//! Share +const CONSTANTS = require('../../../../share/configs/constants'); +const { handleException } = require('../../../../share/utils/redis_pub_sub_helper'); + +//! Model +const borrow_book_model = require('../../../../share/models/book_borrowed.model'); + +//! SERVICE +const borrow_book_service = require('../../../../share/services/cron_service/borrow_book_service'); + +module.exports = { + /** + * @author Nguyễn Tiến Tài + * @created_at 03/04/2023 + * @description cron_change_status_borrow_book_student + * @function cron_change_status_borrow_book_student + */ + async cron_change_status_borrow_book_student() { + try { + // Get all table borrow book + const borrowBookList = await borrow_book_model.getBorrowBook(); + + // borrowBookList exit data + if (borrowBookList.length > 0) { + // Check if the created_at date is greater than 1 day + borrowBookList.forEach(async (borrowBook) => { + // Take time date now + const diffDays = borrow_book_service.handleChangeStatusBorrowBook(borrowBook); + // Check status equa peding + const status_pending = borrowBook.status === CONSTANTS.STATUS_BORROW.PENDING; + if (diffDays > 1 && status_pending) { + // Delete book_borrowed database + const data_delete_borrow_book = { + borrowed_book_id: borrowBook.borrowed_book_id, + isdeleted: CONSTANTS.DELETED_ENABLE, + }; + // update quantity book database + const data_update_book = { + quantity: borrowBook.quantity + 1, + book_id: borrowBook.book_id, + }; + // Start transaction + borrow_book_model.transactionDeleteBorrowAndUpdateBook( + data_delete_borrow_book, + data_update_book, + ); + } + }); + + console.info('CRON CHANGE STATUS BORROW BOOK DONE !'); + } else { + console.error('NO CHANGE STATUS BORROW BOOK!!!'); + } + } catch (error) { + console.error('UPDATE FAIL!', error); + handleException(error, CONSTANTS.NAME_SERVER.CRON); + } + }, +}; diff --git a/backend-manager-student/src/share/configs/constants.js b/backend-manager-student/src/share/configs/constants.js index 3bff2f3..534112e 100644 --- a/backend-manager-student/src/share/configs/constants.js +++ b/backend-manager-student/src/share/configs/constants.js @@ -17,7 +17,7 @@ module.exports = { _5_MINUTES: 5 * 60 * 1000, _10_MINUTES: 10 * 60 * 1000, _15_MINUTES: 15 * 60 * 1000, - _1_DAY_S: 1 * 60 * 60 * 1000, + _1_DAY_S: 1000 * 60 * 60 * 24, _7_DAY_S: 7 * 60 * 60 * 1000, _1_HOURS_S: 60 * 60 * 1000, _1_YEAR: 365 * 24 * 60 * 60 * 1000, @@ -409,7 +409,10 @@ module.exports = { PENDING: 10, // Chờ xác nhận BORROWING: 20, // Đang mượn DONE: 30, // Đã trả - EXPIRED: 40, // Hết hạn + EXPIRED: 40, // Hết hạn, + CANCEL: 50, // Hủy + LOST_BOOK_PROCESSING: 60, // Mất sách chưa được sử lý. + LOST_BOOK_PROCESSED: 70, // Mất sách đã được sử lý. }, /** * @author Nguyễn Tiến Tài diff --git a/backend-manager-student/src/share/configs/message.js b/backend-manager-student/src/share/configs/message.js index 00dc906..893b2a2 100644 --- a/backend-manager-student/src/share/configs/message.js +++ b/backend-manager-student/src/share/configs/message.js @@ -36,10 +36,12 @@ module.exports = { INVALID_DATE: 'Invalid date of birth', INVALID_MUTILP_FIELD: 'Please provide non-empty values for all fields', INVALID_UNAUTHORIZED: 'Unauthorized', + INVALID_RATING: 'Invalid Rating', // ? EXITS EXITS_EMAIL_PHONE: 'Email or Phone or Email or Mssv exits !', EXITS_NOT_BOOK: 'Book Not Found!', + EXITS_NOT_BORROW_BOOK: 'Borrow Book Not Found!', EXITS_DELETE_BOOK: 'Book already delete!', EXITS_DELETE_AUTHOR: 'Author already delete!', EXITS_DELETE_CATEGORIES: 'Categories already delete !', @@ -47,18 +49,25 @@ module.exports = { EXITS_CAN_ONLY_TOW_BORROW: 'You can only borrow tow book!', EXITS_EMAIL: 'Email not exit!', EXITS_PHONE: 'Phone not exit!', - EXITS_MSSV: 'mssv not exit!', + EXITS_MSSV: 'Mssv not exit!', + EXITS_FAVORITE: 'Favorite not exit!', // ? ALREADY ALREADY_BOOK_BORROW: 'Book already borrow !!', ALREADY_EMAIL_CHECK_LINK: 'Link reset Exit Please check Email !', + ALREADY_RATING_BOOK: 'You already rating book !', + ALREADY_FAVORITE: 'Favorite already exits!', + ALREADY_ACCOUNT_STUDENT: 'Account student already exits !', // ? PLEASE PLEASE_REFUND_BOOK: 'Please refund the book !!', PLEASE_CHECK_EMAIL: 'Please check Email!', + PLEASE_LOST_BOOK_PROCESSING: 'Please process book lost before borrow book !', + PLEASE_DELETE_BORROW_BOOK_PENDING: 'You can only delete borrow_book pending!', // ? FAIL BORROW_FAIL: 'Borrow Fail!', + RATING_FAIL: 'Rating Fail!', RESET_PASSWORD_FAIL: 'Reset Password Fail', ERROR_UNKNOWN: 'Error unknown', @@ -93,5 +102,9 @@ module.exports = { SUCCESS_UPDATE_STUDENT: 'Update Student success !', SUCCESS_UPDATE_BORROW_STUDENT_REFUND: 'Update Student refund book success !', SUCCESS_BORROW_BOOK_SUCCESS: 'Invite you go to Library confirm,Thank', + SUCCESS_BORROW_BOOK_ADMIN_SUCCESS: 'Create borrow student Success', + SUCCESS_RATING_SUCCESS: 'Rating book success!', + SUCCESS_DELETE_BORROW_BOOK: 'Delete success borrow book!', + SUCCESS_REGISTER_STUDENT: 'Register student success!', }, }; diff --git a/backend-manager-student/src/share/models/book.model.js b/backend-manager-student/src/share/models/book.model.js index 026fa6f..6dcf14b 100644 --- a/backend-manager-student/src/share/models/book.model.js +++ b/backend-manager-student/src/share/models/book.model.js @@ -66,6 +66,7 @@ module.exports = { getAllBook: async () => { const result = await knex('books') .join('authors', 'books.author_id', '=', 'authors.author_id') + .join('book_rates', 'books.book_id', '=', 'book_rates.book_id') .where('books.isdeleted', '=', CONSTANTS.DELETED_DISABLE) .select( { @@ -74,6 +75,9 @@ module.exports = { gender_author: 'authors.gender', image_author: 'authors.avatar_uri', }, + { + star: 'book_rates.rating', + }, 'books.*', ) .orderBy('books.updated_at', 'desc'); diff --git a/backend-manager-student/src/share/models/book_borrowed.model.js b/backend-manager-student/src/share/models/book_borrowed.model.js index 13c6c1a..745209a 100644 --- a/backend-manager-student/src/share/models/book_borrowed.model.js +++ b/backend-manager-student/src/share/models/book_borrowed.model.js @@ -69,12 +69,20 @@ module.exports = { queryBuilder.where('user_id', user_id); } }) - .select('books.name', 'books.image_uri', 'books.description', 'books.page_number', 'borrowed_book.*', { - name_author: 'authors.name', - dob_author: 'authors.dob', - gender_author: 'authors.gender', - image_author: 'authors.avatar_uri', - }) + .select( + 'books.name', + 'books.image_uri', + 'books.description', + 'books.quantity', + 'books.page_number', + 'borrowed_book.*', + { + name_author: 'authors.name', + dob_author: 'authors.dob', + gender_author: 'authors.gender', + image_author: 'authors.avatar_uri', + }, + ) .orderBy('borrowed_book.updated_at', 'desc'); return result; }, @@ -91,7 +99,15 @@ module.exports = { // Query 1: createBorrowBook const borrowBookId = await trx('borrowed_book') .update({ status: data_update_borrow.status }) - .where({ book_id: data_update_borrow.book_id, user_id: data_update_borrow.user_id }) + .where({ + book_id: data_update_borrow.book_id, + user_id: data_update_borrow.user_id, + }) + .modify((queryBuilder) => { + if (data_update_borrow.borrowed_book_id) { + queryBuilder.where('borrowed_book_id', data_update_borrow.borrowed_book_id); + } + }) .returning(['borrowed_book_id']); // Query 2: updateBorrowBook @@ -151,4 +167,52 @@ module.exports = { .whereNot('status', CONSTANTS.STATUS_BORROW.DONE); return result; }, + + /** + * @author Nguyễn Tiến Tài + * @created_at 03/04/2023 + * @description Transaction Delete Borrow Book and Update book + */ + transactionDeleteBorrowAndUpdateBook: async (data_delete_borrow_book, data_update_borrow) => + new Promise(async (resolve, reject) => { + // start transaction + const trx = await knex.transaction(); + try { + // Query 1: createBorrowBook + const borrowBookId = await trx('borrowed_book') + .update({ isdeleted: data_delete_borrow_book.isdeleted }) + .where({ borrowed_book_id: data_delete_borrow_book.borrowed_book_id }) + .returning(['borrowed_book_id']); + + // Query 2: updateBorrowBook + const updatedData = await trx('books') + .update({ quantity: data_update_borrow.quantity }) + .where({ book_id: data_update_borrow.book_id }) + .returning(['book_id']); + + // Commit transaction + await trx.commit(); + return resolve(borrowBookId, updatedData); + } catch (error) { + trx.rollback(); + reject(error); + } + }), + /** + * @author Nguyễn Tiến Tài + * @created_at 04/04/2023 + * @description get rating and borrowed status done + */ + getRatingsAndBorrowBookStatusDone: async (student_query) => { + const result = await knex('borrowed_book') + .join('book_rates', 'book_rates.book_id', '=', 'borrowed_book.book_id') + .where('borrowed_book.isdeleted', '=', student_query.isdeleted) + .where('book_rates.isdeleted', '=', student_query.isdeleted) + .where('borrowed_book.borrowed_book_id', '=', student_query.borrowed_book_id) + .where('book_rates.user_id', '=', student_query.user_id) + .where('borrowed_book.status', '=', student_query.status) + .where('book_rates.book_id', '=', student_query.book_id) + .select('book_rates.*'); + return result; + }, }; diff --git a/backend-manager-student/src/share/models/favorite.model.js b/backend-manager-student/src/share/models/favorite.model.js new file mode 100644 index 0000000..62c8374 --- /dev/null +++ b/backend-manager-student/src/share/models/favorite.model.js @@ -0,0 +1,58 @@ +//! DATABASE +const knex = require('../db/postgresql'); + +module.exports = { + /** + * @author Nguyễn Tiến Tài + * @created_at 04/04/2023 + * @description create favorite_book + */ + createFavorite: (data) => + new Promise((resolve, reject) => { + try { + const result = knex('favorite_book') + .insert(data) + .onConflict('favorite_book_id') + .merge() + .returning(['favorite_book_id']); + resolve(result); + } catch (error) { + reject(error); + } + }), + /** + * @author Nguyễn Tiến Tài + * @created_at 04/04/2023 + * @description Update Favorite + */ + updateFavorite: async (data, student_query, return_data) => + new Promise((resolve, reject) => { + try { + const result = knex('favorite_book').update(data).where(student_query).returning(return_data); + resolve(result); + } catch (error) { + reject(error); + } + }), + /** + * @author Nguyễn Tiến Tài + * @created_at 04/04/2023 + * @description Get all Favorite + */ + getAllFavorite: async (student_query, return_data) => { + const result = await knex('favorite_book') + .select(return_data) + .where(student_query) + .orderBy('updated_at', 'desc'); + return result; + }, + /** + * @author Nguyễn Tiến Tài + * @created_at 04/04/2023 + * @description get Favorite id + */ + getFavoriteById: async (student_query, return_data) => { + const result = await knex('favorite_book').select(return_data).where(student_query); + return result; + }, +}; diff --git a/backend-manager-student/src/share/models/rating.model.js b/backend-manager-student/src/share/models/rating.model.js new file mode 100644 index 0000000..b1e0fa1 --- /dev/null +++ b/backend-manager-student/src/share/models/rating.model.js @@ -0,0 +1,38 @@ +//! DATABASE +const knex = require('../db/postgresql'); + +module.exports = { + /** + * @author Nguyễn Tiến Tài + * @created_at 04/04/2023 + * @description create Rating + */ + createRatings: (data) => + new Promise((resolve, reject) => { + try { + const result = knex('book_rates').insert(data).onConflict('rate_id').merge().returning(['rate_id']); + resolve(result); + } catch (error) { + reject(error); + } + }), + + /** + * @author Nguyễn Tiến Tài + * @created_at 04/04/2023 + * @description get rating id + */ + getRatingsById: async (student_query, return_data) => { + const result = await knex('book_rates').select(return_data).where(student_query); + return result; + }, + /** + * @author Nguyễn Tiến Tài + * @created_at 04/04/2023 + * @description Get all Rating + */ + getAllRatings: async (student_query, return_data) => { + const result = await knex('book_rates').select(return_data).where(student_query).orderBy('updated_at', 'desc'); + return result; + }, +}; diff --git a/backend-manager-student/src/share/services/cron_service/borrow_book_service.js b/backend-manager-student/src/share/services/cron_service/borrow_book_service.js new file mode 100644 index 0000000..a51db1a --- /dev/null +++ b/backend-manager-student/src/share/services/cron_service/borrow_book_service.js @@ -0,0 +1,17 @@ +//! SHARE +const CONSTANTS = require('../../configs/constants'); + +module.exports = { + /** + * @author Nguyễn Tiến Tài + * @created_at 04/03/2023 + * @description handle Change Status Borrow Book + * @function handleChangeStatusBorrowBook + */ + handleChangeStatusBorrowBook: (borrowBook) => { + const createdAt = new Date(borrowBook.created_at); + const now = new Date(); + const diffTime = Math.abs(now - createdAt); + return Math.ceil(diffTime / CONSTANTS._1_DAY_S); + }, +}; diff --git a/backend-manager-student/src/share/services/user_service/rating_service.js b/backend-manager-student/src/share/services/user_service/rating_service.js new file mode 100644 index 0000000..bf2c5a8 --- /dev/null +++ b/backend-manager-student/src/share/services/user_service/rating_service.js @@ -0,0 +1,18 @@ +module.exports = { + /** + * @author Nguyễn Tiến Tài + * @created_at 04/04/2023 + * @description handle rating + * @function Caculation rating + */ + handleCalculationRating: (ratings, rating_input) => { + ratings.push({ rating: rating_input }); + + const sum = ratings.reduce((acc, cur) => acc + parseFloat(cur.rating), 0); + + // Tính trung bình cộng + const avg = sum / ratings.length; + const roundedAvg = Math.round(avg * 2) / 2; + return roundedAvg; + }, +}; diff --git a/backend-manager-student/src/user_api/v1/Swagger/user_api/favorite.swagger.yaml b/backend-manager-student/src/user_api/v1/Swagger/user_api/favorite.swagger.yaml new file mode 100644 index 0000000..2b30797 --- /dev/null +++ b/backend-manager-student/src/user_api/v1/Swagger/user_api/favorite.swagger.yaml @@ -0,0 +1,228 @@ +#!@author Nguyễn Tiến Tài +#!created_at 08/02/2023 +#!description: General +# Info Swagger User APi +info: + title: Document Student + description: CURD FAVORITE + version: 1.0.0 +servers: + - url: http://localhost:5001 + description: Local development server user api +# General +components: + # Parameter + parameters: + X-DEVICE-ID: + in: header + name: X-DEVICE-ID + required: true + schema: + type: string + X-OS-TYPE: + in: header + name: X-OS-TYPE + required: true + schema: + type: string + X-OS-VERSION: + in: header + name: X-OS-VERSION + required: true + schema: + type: string + X-APP-VERSION: + in: header + name: X-APP-VERSION + required: true + schema: + type: string + X-DEVICE-NAME: + in: header + name: X-DEVICE-NAME + required: true + schema: + type: string + TOKEN: + in: header + name: Authorization + type: string + description: Bearer Token + required: true + schema: + type: bearerToken + # Unauthorized + responses: + UnauthorizedError: + description: Access token is missing or invalid, or the user does not have access to perform the action,or or Device Invalid + content: + application/json: + schema: + type: object + properties: + status: + type: integer + example: 400 + message: + type: string + example: 'Unauthorized' + # Fail CURD Fail + ServerCURDFail: + description: CURD Fail !!! + content: + application/json: + schema: + type: object + properties: + status: + type: integer + example: 500 + message: + type: string + example: Service Unavailable + element: + type: object + example: Internal Server Error + ServerFail: + description: Service die + content: + application/json: + schema: + type: object + properties: + status: + type: integer + example: 503 + message: + type: string + example: Service Unavailable + element: + type: object + example: Out Of Service +paths: + #!@author Nguyễn Tiến Tài + #!created_at 04/04/2023 + #!description: CREATE FAVORITE + /v1/user/private/favorite/create: + get: + summary: Detail favorite + tags: [Detail favorite] + operationId: detailFavorite + parameters: + - $ref: '#/components/parameters/COOKIE-CLIENT' + - $ref: '#/components/parameters/X-DEVICE-ID' + - $ref: '#/components/parameters/X-OS-TYPE' + - $ref: '#/components/parameters/X-OS-VERSION' + - $ref: '#/components/parameters/X-APP-VERSION' + - $ref: '#/components/parameters/X-DEVICE-NAME' + - $ref: '#/components/parameters/TOKEN' + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + input: + type: object + properties: + favorite_input: + type: object + properties: + book_id: + type: number + example: 1486024816199729154 + required: + - book_id + responses: + 200: + description: Detail success + content: + application/json: + schema: + type: object + properties: + status: + type: integer + example: 200 + message: + type: string + example: Success + 400: + description: Bad request + content: + application/json: + schema: + type: object + properties: + status: + type: integer + example: 400 + message: + type: string + example: Success + 503: + $ref: '#/components/responses/ServerFail' + #!@author Nguyễn Tiến Tài + #!created_at 04/04/2023 + #!description: DEL FAVORITE + /v1/user/private/favorite/delete: + get: + summary: Get all favorite + tags: [Get all favorite] + operationId: getAllFavorite + parameters: + - $ref: '#/components/parameters/COOKIE-CLIENT' + - $ref: '#/components/parameters/X-DEVICE-ID' + - $ref: '#/components/parameters/X-OS-TYPE' + - $ref: '#/components/parameters/X-OS-VERSION' + - $ref: '#/components/parameters/X-APP-VERSION' + - $ref: '#/components/parameters/X-DEVICE-NAME' + - $ref: '#/components/parameters/TOKEN' + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + input: + type: object + properties: + favorite_input: + type: object + properties: + favorite_book_id: + type: number + example: 1486024816199729154 + required: + - favorite_book_id + responses: + 200: + description: success + content: + application/json: + schema: + type: object + properties: + status: + type: integer + example: 200 + message: + type: string + example: Success + 400: + description: Bad Request + content: + application/json: + schema: + type: object + properties: + status: + type: integer + example: 400 + message: + type: string + example: Success + 503: + $ref: '#/components/responses/ServerFail' diff --git a/backend-manager-student/src/user_api/v1/Swagger/user_api/rating.swagger.yaml b/backend-manager-student/src/user_api/v1/Swagger/user_api/rating.swagger.yaml new file mode 100644 index 0000000..6f780ec --- /dev/null +++ b/backend-manager-student/src/user_api/v1/Swagger/user_api/rating.swagger.yaml @@ -0,0 +1,176 @@ +#!@author Nguyễn Tiến Tài +#!created_at 08/02/2023 +#!description: General +# Info Swagger User APi +info: + title: Document Student + description: CURD RATING + version: 1.0.0 +servers: + - url: http://localhost:5001 + description: Local development server user api +# General +components: + # Parameter + parameters: + X-DEVICE-ID: + in: header + name: X-DEVICE-ID + required: true + schema: + type: string + X-OS-TYPE: + in: header + name: X-OS-TYPE + required: true + schema: + type: string + X-OS-VERSION: + in: header + name: X-OS-VERSION + required: true + schema: + type: string + X-APP-VERSION: + in: header + name: X-APP-VERSION + required: true + schema: + type: string + X-DEVICE-NAME: + in: header + name: X-DEVICE-NAME + required: true + schema: + type: string + TOKEN: + in: header + name: Authorization + type: string + description: Bearer Token + required: true + schema: + type: bearerToken + # Unauthorized + responses: + UnauthorizedError: + description: Access token is missing or invalid, or the user does not have access to perform the action,or or Device Invalid + content: + application/json: + schema: + type: object + properties: + status: + type: integer + example: 400 + message: + type: string + example: 'Unauthorized' + # Fail CURD Fail + ServerCURDFail: + description: CURD Fail !!! + content: + application/json: + schema: + type: object + properties: + status: + type: integer + example: 500 + message: + type: string + example: Service Unavailable + element: + type: object + example: Internal Server Error + ServerFail: + description: Service die + content: + application/json: + schema: + type: object + properties: + status: + type: integer + example: 503 + message: + type: string + example: Service Unavailable + element: + type: object + example: Out Of Service +paths: + #!@author Nguyễn Tiến Tài + #!created_at 04/0342023 + #!description: Detail RATING + /v1/user/private/rating/create: + get: + summary: Detail ratings + tags: [Detail ratings] + operationId: detailRating + parameters: + - $ref: '#/components/parameters/COOKIE-CLIENT' + - $ref: '#/components/parameters/X-DEVICE-ID' + - $ref: '#/components/parameters/X-OS-TYPE' + - $ref: '#/components/parameters/X-OS-VERSION' + - $ref: '#/components/parameters/X-APP-VERSION' + - $ref: '#/components/parameters/X-DEVICE-NAME' + - $ref: '#/components/parameters/TOKEN' + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + input: + type: object + properties: + rating_input: + type: object + properties: + borrowed_book_id: + type: string + example: '1475825111725506562' + book_id: + type: string + example: '1475843125892087810' + rating: + type: number + example: 5 + required: + - borrowed_book_id + - book_id + - rating + responses: + 200: + description: Create rating success + content: + application/json: + schema: + type: object + properties: + status: + type: integer + example: 200 + message: + type: string + example: Success + 400: + description: Bad request + content: + application/json: + schema: + type: object + properties: + status: + type: integer + example: 400 + message: + type: string + example: bad request + element: + type: object + example: {} + 503: + $ref: '#/components/responses/ServerFail' diff --git a/backend-manager-student/src/user_api/v1/Swagger/user_api/user.swagger.yaml b/backend-manager-student/src/user_api/v1/Swagger/user_api/user.swagger.yaml index 7ad6aa6..c6c7d5b 100644 --- a/backend-manager-student/src/user_api/v1/Swagger/user_api/user.swagger.yaml +++ b/backend-manager-student/src/user_api/v1/Swagger/user_api/user.swagger.yaml @@ -209,6 +209,95 @@ paths: $ref: '#/components/responses/isMatchPassword' 503: $ref: '#/components/responses/ServerFail' + #!@author Nguyễn Tiến Tài + #!created_at 04/04/2023 + #!description: Register Student + /api/v1/user/register: + post: + summary: Register User + tags: [Register User] + operationId: registerStudent + parameters: + - $ref: '#/components/parameters/X-DEVICE-ID' + - $ref: '#/components/parameters/X-OS-TYPE' + - $ref: '#/components/parameters/X-OS-VERSION' + - $ref: '#/components/parameters/X-APP-VERSION' + - $ref: '#/components/parameters/X-DEVICE-NAME' + schema: + $ref: '#/components/headers/AuthHeader' + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + input: + type: object + properties: + user_register_input: + type: object + properties: + mssv: + type: number + example: 60137255 + password: + type: string + example: '20000531' + name: + type: string + example: 'name' + phone_number: + type: number + example: 0123456789 + email: + type: string + example: tai@gmail.com + dob: + type: date + example: 2000-02-02 + address: + type: string + example: 'nha trang' + gender: + type: number + example: 1 + class_room: + type: string + example: 'CNTT-2' + required: + - mssv + - password + - name + - phone_number + - email + - dob + - address + - gender + - class_room + responses: + 200: + description: Register Success + headers: + $ref: '#/components/SaveCookie' + content: + application/json: + schema: + type: object + properties: + status: + type: integer + example: 200 + message: + type: string + example: Success + element: + type: object + example: { result: 'register success' } + 401: + $ref: '#/components/responses/isMatchPassword' + 503: + $ref: '#/components/responses/ServerFail' #!@author Nguyễn Tiến Tài #!created_at 05/02/2023 diff --git a/backend-manager-student/src/user_api/v1/controllers/borrow_book.controllers/borrow_book.controller.js b/backend-manager-student/src/user_api/v1/controllers/borrow_book.controllers/borrow_book.controller.js index 9bffa1b..cbb86d9 100644 --- a/backend-manager-student/src/user_api/v1/controllers/borrow_book.controllers/borrow_book.controller.js +++ b/backend-manager-student/src/user_api/v1/controllers/borrow_book.controllers/borrow_book.controller.js @@ -45,6 +45,18 @@ const BorrowBookController = { { user_id: id, isdeleted: CONSTANTS.DELETED_DISABLE }, { book_id: 'book_id', status: 'status' }, ); + // Check book lost processing + const checkArray = (arr) => !arr.some((obj) => obj.status === CONSTANTS.STATUS_BORROW.LOST_BOOK_PROCESSING); + if (!checkArray(check_borrow_book)) { + return res.status(CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST).json({ + status: CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST, + message: returnReasons(CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST), + element: { + result: MESSAGES.GENERAL.PLEASE_LOST_BOOK_PROCESSING, + }, + }); + } + // Check count total if (check_borrow_book.length >= 2) { return res.status(CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST).json({ status: CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST, diff --git a/backend-manager-student/src/user_api/v1/controllers/favorite.controllers/favorite.controller.js b/backend-manager-student/src/user_api/v1/controllers/favorite.controllers/favorite.controller.js new file mode 100644 index 0000000..3a3da3c --- /dev/null +++ b/backend-manager-student/src/user_api/v1/controllers/favorite.controllers/favorite.controller.js @@ -0,0 +1,174 @@ +//! SHARE +const HELPER = require('../../../../share/utils/helper'); +const CONSTANTS = require('../../../../share/configs/constants'); +const RANDOMS = require('../../../../share/utils/random'); +const MESSAGES = require('../../../../share/configs/message'); + +//! MIDDLEWARE +const { returnReasons } = require('../../../../share/middleware/handle_error'); + +//! MODEL +const favorite_model = require('../../../../share/models/favorite.model'); + +const FavoriteController = { + /** + * @author Nguyễn Tiến Tài + * @created_at 04/04/2023 + * @description create Favorite + * @function createFavorites + * @return {Object:{Number,String}} + */ + createFavorites: async (req, res) => { + const { book_id } = req.body.input.favorite_input; + + // Take user Id + const { id } = req.auth_user; + + // Check input + if (!HELPER.validateBigInt(book_id) || !HELPER.validateBigInt(id)) { + return res.status(CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST).json({ + status: CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST, + message: returnReasons(CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST), + element: { + result: MESSAGES.GENERAL.INVALID_INPUT, + }, + }); + } + try { + const check_favorite_exits = await favorite_model.getFavoriteById( + { + book_id, + user_id: id, + isdeleted: CONSTANTS.DELETED_DISABLE, + }, + { + favorite_book_id: 'favorite_book_id', + }, + ); + if (check_favorite_exits[0]) { + return res.status(CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST).json({ + status: CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST, + message: returnReasons(CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST), + element: { + result: MESSAGES.GENERAL.ALREADY_FAVORITE, + }, + }); + } + // create favorite database + let err; + let result; + [err, result] = await HELPER.handleRequest( + favorite_model.createFavorite({ + favorite_book_id: RANDOMS.createID(), + book_id, + user_id: id, + }), + ); + if (result) { + return res.status(CONSTANTS.HTTP.STATUS_2XX_OK).json({ + status: CONSTANTS.HTTP.STATUS_2XX_OK, + message: returnReasons(CONSTANTS.HTTP.STATUS_2XX_OK), + element: { + result: result[0].favorite_book_id, + }, + }); + } + if (err) { + return res.status(CONSTANTS.HTTP.STATUS_5XX_INTERNAL_SERVER_ERROR).json({ + status: CONSTANTS.HTTP.STATUS_5XX_INTERNAL_SERVER_ERROR, + message: returnReasons(CONSTANTS.HTTP.STATUS_5XX_INTERNAL_SERVER_ERROR), + }); + } + } catch (error) { + return res.status(CONSTANTS.HTTP.STATUS_5XX_SERVICE_UNAVAILABLE).json({ + status: CONSTANTS.HTTP.STATUS_5XX_SERVICE_UNAVAILABLE, + message: returnReasons(CONSTANTS.HTTP.STATUS_5XX_SERVICE_UNAVAILABLE), + element: { + result: MESSAGES.GENERAL.SERVER_OUT_OF_SERVICE, + }, + }); + } + }, + /** + * @author Nguyễn Tiến Tài + * @created_at 04/04/2023 + * @description Delete Favorite + * @function deleteFavorites + * @return {Object:{Number,String}} + */ + deleteFavorites: async (req, res) => { + const { favorite_book_id } = req.body.input.favorite_input; + + // Check input + if (!HELPER.validateBigInt(favorite_book_id)) { + return res.status(CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST).json({ + status: CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST, + message: returnReasons(CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST), + element: { + result: MESSAGES.GENERAL.INVALID_INPUT, + }, + }); + } + try { + const check_favorite_exits = await favorite_model.getFavoriteById( + { + favorite_book_id, + isdeleted: CONSTANTS.DELETED_DISABLE, + }, + { + favorite_book_id: 'favorite_book_id', + }, + ); + if (!check_favorite_exits || !check_favorite_exits.length) { + return res.status(CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST).json({ + status: CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST, + message: returnReasons(CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST), + element: { + result: MESSAGES.GENERAL.EXITS_FAVORITE, + }, + }); + } + // create favorite database + let err; + let result; + [err, result] = await HELPER.handleRequest( + favorite_model.updateFavorite( + { + isdeleted: CONSTANTS.DELETED_ENABLE, + }, + { + favorite_book_id, + isdeleted: CONSTANTS.DELETED_DISABLE, + }, + { + favorite_book_id: 'favorite_book_id', + }, + ), + ); + if (result) { + return res.status(CONSTANTS.HTTP.STATUS_2XX_OK).json({ + status: CONSTANTS.HTTP.STATUS_2XX_OK, + message: returnReasons(CONSTANTS.HTTP.STATUS_2XX_OK), + element: { + result: result[0].favorite_book_id, + }, + }); + } + if (err) { + return res.status(CONSTANTS.HTTP.STATUS_5XX_INTERNAL_SERVER_ERROR).json({ + status: CONSTANTS.HTTP.STATUS_5XX_INTERNAL_SERVER_ERROR, + message: returnReasons(CONSTANTS.HTTP.STATUS_5XX_INTERNAL_SERVER_ERROR), + }); + } + } catch (error) { + return res.status(CONSTANTS.HTTP.STATUS_5XX_SERVICE_UNAVAILABLE).json({ + status: CONSTANTS.HTTP.STATUS_5XX_SERVICE_UNAVAILABLE, + message: returnReasons(CONSTANTS.HTTP.STATUS_5XX_SERVICE_UNAVAILABLE), + element: { + result: MESSAGES.GENERAL.SERVER_OUT_OF_SERVICE, + }, + }); + } + }, +}; +module.exports = FavoriteController; diff --git a/backend-manager-student/src/user_api/v1/controllers/rating.controllers/rating.controller.js b/backend-manager-student/src/user_api/v1/controllers/rating.controllers/rating.controller.js new file mode 100644 index 0000000..91f9e42 --- /dev/null +++ b/backend-manager-student/src/user_api/v1/controllers/rating.controllers/rating.controller.js @@ -0,0 +1,165 @@ +//! SHARE +const HELPER = require('../../../../share/utils/helper'); +const RANDOMS = require('../../../../share/utils/random'); +const CONSTANTS = require('../../../../share/configs/constants'); +const MESSAGES = require('../../../../share/configs/message'); +const MEMORY_CACHE = require('../../../../share/utils/limited_redis'); + +//! MIDDLEWARE +const { returnReasons } = require('../../../../share/middleware/handle_error'); +const { globalCache } = require('../../../../share/patterns/LRU_Strategy.patterns'); + +//! MODEL +const rating_model = require('../../../../share/models/rating.model'); +const borrow_book_model = require('../../../../share/models/book_borrowed.model'); + +//! SERVICE +const rating_service = require('../../../../share/services/user_service/rating_service'); + +const ratingController = { + /** + * @author Nguyễn Tiến Tài + * @created_at 04/04/2023 + * @description create rating + * @function CreateRating + * @return {Object:{Number,String}} + */ + createRating: async (req, res) => { + const { borrowed_book_id, book_id, rating } = req.body.input.rating_input; + + // Take user Id + const { id } = req.auth_user; + // Check input + if ( + !HELPER.validateBigInt(book_id) + || !HELPER.validateBigInt(rating) + || !HELPER.validateBigInt(id) + || !HELPER.validateBigInt(borrowed_book_id) + ) { + return res.status(CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST).json({ + status: CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST, + message: returnReasons(CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST), + element: { + result: MESSAGES.GENERAL.INVALID_INPUT, + }, + }); + } + if (rating > 5 || rating % 1 !== 0) { + return res.status(CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST).json({ + status: CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST, + message: returnReasons(CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST), + element: { + result: MESSAGES.GENERAL.INVALID_RATING, + }, + }); + } + + try { + // Check Borrow book user_id + const getBorrowBook = await borrow_book_model.getBorrowBookById( + { + borrowed_book_id, + user_id: id, + status: CONSTANTS.STATUS_BORROW.DONE, + isdeleted: CONSTANTS.DELETED_DISABLE, + }, + { + borrowed_book_id: 'borrowed_book_id', + }, + ); + if (!getBorrowBook || !getBorrowBook.length) { + return res.status(CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST).json({ + status: CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST, + message: returnReasons(CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST), + element: { + result: MESSAGES.GENERAL.EXITS_NOT_BORROW_BOOK, + }, + }); + } + + // Check student already comment + const getRatingStudent = await rating_model.getRatingsById( + { + user_id: id, + book_id, + isdeleted: CONSTANTS.DELETED_DISABLE, + }, + { + rate_id: 'rate_id', + }, + ); + if (getRatingStudent[0]) { + return res.status(CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST).json({ + status: CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST, + message: returnReasons(CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST), + element: { + result: MESSAGES.GENERAL.ALREADY_RATING_BOOK, + }, + }); + } + + const rating_db = await rating_model.getAllRatings( + { + book_id, + isdeleted: CONSTANTS.DELETED_DISABLE, + }, + { rating: 'rating' }, + ); + let star; + if (rating_db) { + star = rating_service.handleCalculationRating(rating_db, rating); + } else { + star = rating; + } + + // create rating database + let err; + let result; + [err, result] = await HELPER.handleRequest( + rating_model.createRatings({ + rate_id: RANDOMS.createID(), + book_id, + user_id: id, + rating: star, + }), + ); + if (result) { + // Create key Cache + const key_cache_book_detail = HELPER.getURIFromTemplate(CONSTANTS.KEY_REDIS.DETAIL_BOOK, { + book_id, + }); + + // Delete Cache book + MEMORY_CACHE.delKeyCache(CONSTANTS.KEY_REDIS.ALL_BOOK); + + // Delete cache detail + globalCache.delCache(key_cache_book_detail); + + return res.status(CONSTANTS.HTTP.STATUS_2XX_OK).json({ + status: CONSTANTS.HTTP.STATUS_2XX_OK, + message: returnReasons(CONSTANTS.HTTP.STATUS_2XX_OK), + element: { + result: MESSAGES.GENERAL.SUCCESS_RATING_SUCCESS, + }, + }); + } else if (err) { + return res.status(CONSTANTS.HTTP.STATUS_5XX_INTERNAL_SERVER_ERROR).json({ + status: CONSTANTS.HTTP.STATUS_5XX_INTERNAL_SERVER_ERROR, + message: returnReasons(CONSTANTS.HTTP.STATUS_5XX_INTERNAL_SERVER_ERROR), + element: { + result: MESSAGES.GENERAL.RATING_FAIL, + }, + }); + } + } catch (error) { + return res.status(CONSTANTS.HTTP.STATUS_5XX_SERVICE_UNAVAILABLE).json({ + status: CONSTANTS.HTTP.STATUS_5XX_SERVICE_UNAVAILABLE, + message: returnReasons(CONSTANTS.HTTP.STATUS_5XX_SERVICE_UNAVAILABLE), + element: { + result: MESSAGES.GENERAL.SERVER_OUT_OF_SERVICE, + }, + }); + } + }, +}; +module.exports = ratingController; diff --git a/backend-manager-student/src/user_api/v1/controllers/user.controllers/user.controller.js b/backend-manager-student/src/user_api/v1/controllers/user.controllers/user.controller.js index 6e74a44..86872bd 100644 --- a/backend-manager-student/src/user_api/v1/controllers/user.controllers/user.controller.js +++ b/backend-manager-student/src/user_api/v1/controllers/user.controllers/user.controller.js @@ -21,6 +21,9 @@ const geo_service = require('../../../../share/services/geo.service'); const user_service = require('../../../../share/services/user_service/user_service'); const verification_service = require('../../../../share/services/user_service/verification.service'); +//! DATABASE +const knex = require('../../../../share/db/postgresql'); + //! MIDDLAWARE const { returnReasons, returnDuplicate } = require('../../../../share/middleware/handle_error'); @@ -36,7 +39,6 @@ const userController = { */ loginStudent: async (req, res) => { const { mssv, password } = req.body.input.user_login_input; - // Take device id header let { device_id } = req.device; @@ -249,6 +251,147 @@ const userController = { }); } }, + /** + * @author Nguyễn Tiến Tài + * @created_at 04/04/2023 + * @description Register student + * @function registerStudent + * @param { mssv,password } + * @return { Object } + */ + registerStudent: async (req, res) => { + const { + name, + mssv, + avatar_uri, + public_id_avatar, + password, + phone_number, + email, + dob, + address, + gender, + class_room, + } = req.body.input.user_register_input; + + // Check input register + if ( + !mssv + || !password + || !HELPER.isNumeric(mssv) + || !name + || !phone_number + || !email + || !dob + || !address + || !gender + || !class_room + ) { + return res.status(CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST).json({ + status: CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST, + message: returnReasons(CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST), + element: { + result: MESSAGES.GENERAL.INVALID_INPUT, + }, + }); + } + const check_email = HELPER.validateEmail(email); + + const check_phone = HELPER.validatePhone(phone_number); + + if (!check_email || !check_phone) { + return res.status(CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST).json({ + status: CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST, + message: returnReasons(CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST), + element: { + result: MESSAGES.GENERAL.INVALID_EMAIL_PHONE, + }, + }); + } + try { + // Check student exit database + let users = await user_model.getStudentById( + { + mssv, + isdeleted: CONSTANTS.DELETED_DISABLE, + }, + { user_id: 'user_id' }, + ); + if (users[0]) { + return res.status(CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST).json({ + status: CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST, + message: returnReasons(CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST), + element: { + result: MESSAGES.GENERAL.ALREADY_ACCOUNT_STUDENT, + }, + }); + } + + // Phone hide + const phone_hide = HELPER.maskLastPhoneNumber(phone_number); + // Encode Password + const password_student = await PASSWORD.encodePassword(password); + const data_register = { + user_id: RANDOMS.createID(), + name, + mssv, + password: password_student, + phone_number, + phone_hidden: phone_hide, + dob, + class: class_room, + email, + gender, + avatar_uri: + avatar_uri || gender.toLowerCase() === CONSTANTS.GENDER_MALE_STRING + ? CONSTANTS.GENDER_IMAGE_MALE + : CONSTANTS.GENDER_IMAGE_FEMALE, + public_id_avatar: + public_id_avatar || gender.toLowerCase() === CONSTANTS.GENDER_MALE_STRING + ? CONSTANTS.GENDER_IMAGE_MALE + : CONSTANTS.GENDER_IMAGE_FEMALE, + }; + let err; + let data; + // start transaction + const trx = await knex.transaction(); + + // insert student object into database + [err, data] = await HELPER.handleRequest(user_model.createStudent(data_register)); + + // error rollback data + if (err) { + trx.rollback(); + return res.status(CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST).json({ + status: CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST, + message: returnReasons(CONSTANTS.HTTP.STATUS_4XX_BAD_REQUEST), + element: { + result: returnDuplicate(err), + }, + }); + } + if (data) { + // commit transaction succcess + trx.commit(); + return res.status(CONSTANTS.HTTP.STATUS_2XX_OK).json({ + status: CONSTANTS.HTTP.STATUS_2XX_OK, + message: returnReasons(CONSTANTS.HTTP.STATUS_2XX_OK), + element: { + result: MESSAGES.GENERAL.SUCCESS_REGISTER_STUDENT, + }, + }); + } + } catch (error) { + console.error(error); + return res.status(CONSTANTS.HTTP.STATUS_5XX_SERVICE_UNAVAILABLE).json({ + status: CONSTANTS.HTTP.STATUS_5XX_SERVICE_UNAVAILABLE, + message: returnReasons(CONSTANTS.HTTP.STATUS_5XX_SERVICE_UNAVAILABLE), + element: { + result: MESSAGES.GENERAL.SERVER_OUT_OF_SERVICE, + }, + }); + } + }, /** * @author Nguyễn Tiến Tài * @created_at 04/02/2023 diff --git a/backend-manager-student/src/user_api/v1/routes/favorite.routes/favorite.private.route.js b/backend-manager-student/src/user_api/v1/routes/favorite.routes/favorite.private.route.js new file mode 100644 index 0000000..a426f93 --- /dev/null +++ b/backend-manager-student/src/user_api/v1/routes/favorite.routes/favorite.private.route.js @@ -0,0 +1,23 @@ +//! LIBARY +const router = require('express').Router(); + +//! CONTROLLER +const favoriteBookController = require('../../controllers/favorite.controllers/favorite.controller'); + +/** + * @author Nguyễn Tiến Tài + * @created_at 04/04/2023 + * @description Route Create Favorite + * @param {('POST')} [method='POST'] The request's method + */ +router.post('/create', favoriteBookController.createFavorites); + +/** + * @author Nguyễn Tiến Tài + * @created_at 04/04/2023 + * @description Route Delete Favorite + * @param {('POST')} [method='POST'] The request's method + */ +router.post('/delete', favoriteBookController.deleteFavorites); + +module.exports = router; diff --git a/backend-manager-student/src/user_api/v1/routes/private.routes.js b/backend-manager-student/src/user_api/v1/routes/private.routes.js index f543c07..ab16c51 100644 --- a/backend-manager-student/src/user_api/v1/routes/private.routes.js +++ b/backend-manager-student/src/user_api/v1/routes/private.routes.js @@ -4,6 +4,8 @@ const router_private = require('express').Router(); //! ROUTES const userPrivateRouter = require('./users.routes/user.private.route'); const borrowBookPrivateRouter = require('./borrow_book.routes/borrow_book_private.route'); +const ratingBookPrivateRouter = require('./rating.routes/rating.private.route'); +const favoritePrivateRouter = require('./favorite.routes/favorite.private.route'); /** * @author Nguyễn Tiến Tài @@ -15,8 +17,22 @@ router_private.use('/', userPrivateRouter); /** * @author Nguyễn Tiến Tài * @created_at 07/03/2023 - * @description Route categories + * @description Route borrow book */ router_private.use('/borrow_book', borrowBookPrivateRouter); +/** + * @author Nguyễn Tiến Tài + * @created_at 04/04/2023 + * @description Route Ratings + */ +router_private.use('/rating', ratingBookPrivateRouter); + +/** + * @author Nguyễn Tiến Tài + * @created_at 04/04/2023 + * @description Route Favorites + */ +router_private.use('/favorite', favoritePrivateRouter); + module.exports = router_private; diff --git a/backend-manager-student/src/user_api/v1/routes/rating.routes/rating.private.route.js b/backend-manager-student/src/user_api/v1/routes/rating.routes/rating.private.route.js new file mode 100644 index 0000000..8a0b20c --- /dev/null +++ b/backend-manager-student/src/user_api/v1/routes/rating.routes/rating.private.route.js @@ -0,0 +1,15 @@ +//! LIBRARY +const router = require('express').Router(); + +//! CONTROLLER RATING +const ratingController = require('../../controllers/rating.controllers/rating.controller'); + +/** + * @author Nguyễn Tiến Tài + * @created_at 04/04/2023 + * @description Route Create rating book + * @param {('POST')} [method='POST'] The request's method + */ +router.post('/create', ratingController.createRating); + +module.exports = router; diff --git a/backend-manager-student/src/user_api/v1/routes/users.routes/user.route.js b/backend-manager-student/src/user_api/v1/routes/users.routes/user.route.js index 891e4c8..cb33954 100644 --- a/backend-manager-student/src/user_api/v1/routes/users.routes/user.route.js +++ b/backend-manager-student/src/user_api/v1/routes/users.routes/user.route.js @@ -12,6 +12,14 @@ const userController = require('../../controllers/user.controllers/user.controll */ router.post('/login', userController.loginStudent); +/** + * @author Nguyễn Tiến Tài + * @created_at 04/04/2023 + * @description Route Register Student + * @param {('POST')} [method='POST'] The request's method + */ +router.post('/register', userController.registerStudent); + /** * @author Nguyễn Tiến Tài * @created_at 04/02/2023