From e390ad51d702485b5ea14b8c91d36647f65d3a79 Mon Sep 17 00:00:00 2001 From: wayanrivan Date: Mon, 27 Apr 2026 09:55:41 +0700 Subject: [PATCH] update: add signature and profile --- .gitignore | 3 +- package.json | 3 + src/controllers/auth.ts | 2 +- src/controllers/user.ts | 682 +++++++++++++++++++++------------- src/helpers/express/upload.ts | 12 + src/langs/lang.ts | 144 +++++-- src/server.ts | 2 + 7 files changed, 558 insertions(+), 290 deletions(-) create mode 100644 src/helpers/express/upload.ts diff --git a/.gitignore b/.gitignore index a1a0cbb..f186f66 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,5 @@ temp/ package-lock.json src/swagger/swagger.json swagger.json -config/ferro.json \ No newline at end of file +config/ferro.json +uploads/* \ No newline at end of file diff --git a/package.json b/package.json index 2c00a32..7970648 100644 --- a/package.json +++ b/package.json @@ -27,8 +27,10 @@ "entity": "file:../entity", "exceljs": "^4.4.0", "express": "^4.21.1", + "express-fileupload": "^1.5.2", "express-jwt": "^8.4.1", "fast-csv": "^5.0.2", + "fileupload": "^1.0.0", "helmet": "^8.0.0", "i": "^0.3.7", "joi": "^17.13.3", @@ -53,6 +55,7 @@ "@types/config": "^3.3.5", "@types/cors": "^2.8.17", "@types/express": "^4.17.21", + "@types/express-fileupload": "^1.5.1", "@types/morgan": "^1.9.9", "@types/swagger-ui-express": "^4.1.7" } diff --git a/src/controllers/auth.ts b/src/controllers/auth.ts index c0bec5e..5bd1aa0 100644 --- a/src/controllers/auth.ts +++ b/src/controllers/auth.ts @@ -270,7 +270,7 @@ export class AuthController { const repo = OrmHelper.DB.getRepository(UserRefreshToken) - const affected = (await repo.delete({ refresh_token: param.refresh_token })).affected + const affected = (await repo.delete({ refresh_token: param.refresh_token })).affected ?? 0 if (affected > 0) { return ReturnHelper.successResponseAny(res, 200, Language.lang.success_logout) diff --git a/src/controllers/user.ts b/src/controllers/user.ts index d965b0b..ac81220 100644 --- a/src/controllers/user.ts +++ b/src/controllers/user.ts @@ -1,15 +1,20 @@ import axios from "axios"; -import config from "config"; -import {HrmsEmployee, Paging, User, UserRefreshToken, UserRole} from "entity"; +import { HrmsEmployee, Paging, User, UserRefreshToken, UserRole } from "entity"; import exceljs from "exceljs"; -import {NextFunction, Response} from "express"; -import {Request} from "express-jwt"; +import { NextFunction, Response } from "express"; +import { Request } from "express-jwt"; import Joi from "joi"; -import {ILogObj, Logger} from "tslog"; +import { ILogObj, Logger } from "tslog"; import CommonHelper from "../helpers/common"; -import {ReturnHelper} from "../helpers/express/return"; -import {OrmHelper} from "../helpers/orm"; -import {Language} from "../langs/lang"; +import { ReturnHelper } from "../helpers/express/return"; +import { OrmHelper } from "../helpers/orm"; +import { Language } from "../langs/lang"; +import fs from "fs"; +import path from "path"; +import fileUpload from "express-fileupload"; +import { v4 as uuidv4 } from 'uuid'; +import config from '../../config/stag.json'; +import { profile } from "console"; const log: Logger = new Logger({ name: "[UserController]", @@ -19,41 +24,41 @@ const log: Logger = new Logger({ export class UserController { static async list(req: Request, res: Response, next: NextFunction): Promise { /* - #swagger.tags = ['User'] + #swagger.tags = ['User'] #swagger.security = [{ "bearerAuth": [] }] - #swagger.parameters['filter'] = { - description: 'Filter with 2 format :
  • Simple format use text plaint will filter eq:email or like %name% or like %username%
  • Advance format using field existing {status:\'Y\', any:\'SAME AS PLAINT LOGIC\', etc...}
', - in: 'query', - type: 'string' - } - #swagger.parameters['limit'] = { - in: 'query', - required: true, - type: 'number' - } - #swagger.parameters['page'] = { - in: 'query', - required: true, - type: 'number' - } - #swagger.parameters['with_deleted'] = { - in: 'query', - required: true, - type: 'boolean' - } - #swagger.parameters['order_field'] = { - in: 'query', - required: true, - type: 'string' - } - #swagger.parameters['order_direction'] = { - in: 'query', - required: true, - schema: { - '@enum': ['ASC', 'DESC'] - } - } - */ + #swagger.parameters['filter'] = { + description: 'Filter with 2 format :
  • Simple format use text plaint will filter eq:email or like %name% or like %username%
  • Advance format using field existing {status:\'Y\', any:\'SAME AS PLAINT LOGIC\', etc...}
', + in: 'query', + type: 'string' + } + #swagger.parameters['limit'] = { + in: 'query', + required: true, + type: 'number' + } + #swagger.parameters['page'] = { + in: 'query', + required: true, + type: 'number' + } + #swagger.parameters['with_deleted'] = { + in: 'query', + required: true, + type: 'boolean' + } + #swagger.parameters['order_field'] = { + in: 'query', + required: true, + type: 'string' + } + #swagger.parameters['order_direction'] = { + in: 'query', + required: true, + schema: { + '@enum': ['ASC', 'DESC'] + } + } + */ try { const schema = Joi.object().keys({ @@ -71,7 +76,7 @@ export class UserController { const offset = (param.page - 1) * param.limit; - const {whereAttr, whereVal} = CommonHelper.handleFilter({ + const { whereAttr, whereVal } = CommonHelper.handleFilter({ filter: param.filter, col_any_eq: ["email"], col_any_like: ["User.name", "User.username"], @@ -128,6 +133,15 @@ export class UserController { const list_data = await res_list.getMany(); const count_data = CommonHelper.countObject(list_data); + // ← Tambahkan mapping ini + const mapped_data = list_data.map((user) => ({ + ...user, + profile_picture: user.profile_picture ?? null, + file_profile_picture: user.profile_picture + ? `${config.server.host_swagger}uploads/profile-picture/${user.profile_picture}` + : null, + })); + return ReturnHelper.successResponselist( res, 200, @@ -135,7 +149,7 @@ export class UserController { count_data, current_page, total_count_data, - list_data, + mapped_data, ); } catch (e: unknown) { log.error(e); @@ -147,30 +161,30 @@ export class UserController { static async export(req: Request, res: Response, next: NextFunction): Promise { /* - #swagger.tags = ['User'] + #swagger.tags = ['User'] #swagger.security = [{ "bearerAuth": [] }] - #swagger.parameters['filter'] = { - description: 'Filter with 2 format :
  • Simple format use text plaint will filter eq:email or like %name% or like %username%
  • Advance format using field existing {status:\'Y\', any:\'SAME AS PLAINT LOGIC\', etc...}
', - in: 'query', - type: 'string' - } - #swagger.parameters['filter'] = { - in: 'query', - type: 'string' - } - #swagger.parameters['order_field'] = { - in: 'query', - required: true, - type: 'string' - } - #swagger.parameters['order_direction'] = { - in: 'query', - required: true, - schema: { - '@enum': ['ASC', 'DESC'] - } - } - */ + #swagger.parameters['filter'] = { + description: 'Filter with 2 format :
  • Simple format use text plaint will filter eq:email or like %name% or like %username%
  • Advance format using field existing {status:\'Y\', any:\'SAME AS PLAINT LOGIC\', etc...}
', + in: 'query', + type: 'string' + } + #swagger.parameters['filter'] = { + in: 'query', + type: 'string' + } + #swagger.parameters['order_field'] = { + in: 'query', + required: true, + type: 'string' + } + #swagger.parameters['order_direction'] = { + in: 'query', + required: true, + schema: { + '@enum': ['ASC', 'DESC'] + } + } + */ try { const schema = Joi.object().keys({ @@ -183,7 +197,7 @@ export class UserController { const userRepository = OrmHelper.DB.getRepository(User); - const {whereAttr, whereVal} = CommonHelper.handleFilter({ + const { whereAttr, whereVal } = CommonHelper.handleFilter({ filter: param.filter, col_any_eq: ["email"], col_any_like: ["name", "username"], @@ -197,18 +211,18 @@ export class UserController { ); res.setHeader("Content-Disposition", "attachment; filename=" + filename); - const workbook = new exceljs.stream.xlsx.WorkbookWriter({stream: res}); + const workbook = new exceljs.stream.xlsx.WorkbookWriter({ stream: res }); const sheet = workbook.addWorksheet("Data"); sheet.columns = [ - {header: "ID", key: "id", width: 10}, - {header: "Name", key: "name", width: 20}, - {header: "Email", key: "email", width: 20}, - {header: "Username", key: "username", width: 20}, - {header: "Status", key: "status", width: 10}, - {header: "Created At", key: "created_at", width: 15}, - {header: "Roles", key: "role_name", width: 15}, - {header: "Application", key: "application", width: 15}, + { header: "ID", key: "id", width: 10 }, + { header: "Name", key: "name", width: 20 }, + { header: "Email", key: "email", width: 20 }, + { header: "Username", key: "username", width: 20 }, + { header: "Status", key: "status", width: 10 }, + { header: "Created At", key: "created_at", width: 15 }, + { header: "Roles", key: "role_name", width: 15 }, + { header: "Application", key: "application", width: 15 }, ]; const limit = 50; @@ -257,22 +271,22 @@ export class UserController { static async create(req: Request, res: Response, next: NextFunction): Promise { /* - #swagger.tags = ['User'] - #swagger.security = [{ - "bearerAuth": [] - }] - - #swagger.requestBody = { - required: true, - content: { - "application/json": { - schema: { - $ref: "#/components/schemas/user" - } - } - } - } - */ + #swagger.tags = ['User'] + #swagger.security = [{ + "bearerAuth": [] + }] + + #swagger.requestBody = { + required: true, + content: { + "application/json": { + schema: { + $ref: "#/components/schemas/user" + } + } + } + } + */ try { const schema = Joi.object().keys({ @@ -301,14 +315,14 @@ export class UserController { if (param.employee_id && param.employee_id != "") { const employee = await OrmHelper.DB.manager .getRepository(HrmsEmployee) - .findOneByOrFail({id: param.employee_id}); + .findOneByOrFail({ id: param.employee_id }); data.employee = employee; } else { data.employee = null; } data.hashPassword(); - data.created_by = req.auth.data.name; + data.created_by = req.auth?.data.name; data.created_at = new Date(); await OrmHelper.DB.manager.save(data); @@ -324,25 +338,25 @@ export class UserController { static async addRole(req: Request, res: Response, next: NextFunction): Promise { /* - #swagger.tags = ['User'] - #swagger.security = [{ - "bearerAuth": [] - }] + #swagger.tags = ['User'] + #swagger.security = [{ + "bearerAuth": [] + }] - #swagger.parameters['id'] = { - in: 'path', - description: 'User ID.', - required: true, - type: 'string' - } + #swagger.parameters['id'] = { + in: 'path', + description: 'User ID.', + required: true, + type: 'string' + } - #swagger.parameters['id_role'] = { - in: 'path', - description: 'User Role ID.', - required: true, - type: 'string' - } - */ + #swagger.parameters['id_role'] = { + in: 'path', + description: 'User Role ID.', + required: true, + type: 'string' + } + */ try { const schema = Joi.object().keys({ @@ -350,14 +364,14 @@ export class UserController { id_role: Joi.string().uuid().required().label("ID"), }); - const param: {id: string; id_role: string} = await schema.validateAsync(req.params); + const param: { id: string; id_role: string } = await schema.validateAsync(req.params); const userRepository = OrmHelper.DB.getRepository(User); const repo_role = OrmHelper.DB.getRepository(UserRole); const data = await userRepository.findOne({ relations: ["roles", "roles.application"], - where: {id: param.id}, + where: { id: param.id }, }); if (data != null) { @@ -366,7 +380,7 @@ export class UserController { relations: { application: true, }, - where: {id: param.id_role}, + where: { id: param.id_role }, }); if (data.roles) { @@ -382,7 +396,7 @@ export class UserController { ); } - if (r.application.id == new_role.application.id) { + if (r.application.id == new_role?.application.id) { return ReturnHelper.errorResponse( res, 409, @@ -416,25 +430,25 @@ export class UserController { static async deleteRole(req: Request, res: Response, next: NextFunction): Promise { /* - #swagger.tags = ['User'] - #swagger.security = [{ - "bearerAuth": [] - }] + #swagger.tags = ['User'] + #swagger.security = [{ + "bearerAuth": [] + }] - #swagger.parameters['id'] = { - in: 'path', - description: 'User ID.', - required: true, - type: 'string' - } + #swagger.parameters['id'] = { + in: 'path', + description: 'User ID.', + required: true, + type: 'string' + } - #swagger.parameters['id_role'] = { - in: 'path', - description: 'User Role ID.', - required: true, - type: 'string' - } - */ + #swagger.parameters['id_role'] = { + in: 'path', + description: 'User Role ID.', + required: true, + type: 'string' + } + */ try { const schema = Joi.object().keys({ @@ -442,7 +456,7 @@ export class UserController { id_role: Joi.string().uuid().required().label("ID"), }); - const param: {id: string; id_role: string} = await schema.validateAsync(req.params); + const param: { id: string; id_role: string } = await schema.validateAsync(req.params); const userRepository = OrmHelper.DB.getRepository(User); @@ -450,7 +464,7 @@ export class UserController { relations: { roles: true, }, - where: {id: param.id}, + where: { id: param.id }, }); if (data != null && data.roles) { @@ -493,23 +507,23 @@ export class UserController { static async updateProfile(req: Request, res: Response, next: NextFunction): Promise { /* - #swagger.tags = ['User'] - #swagger.security = [{ - "bearerAuth": [] - }] + #swagger.tags = ['User'] + #swagger.security = [{ + "bearerAuth": [] + }] - #swagger.requestBody = { - required: true, - description: "This action will effect to user related token JWT logged", - content: { - "application/json": { - schema: { - $ref: "#/components/schemas/user_profile" - } - } - } - } - */ + #swagger.requestBody = { + required: true, + description: "This action will effect to user related token JWT logged", + content: { + "application/json": { + schema: { + $ref: "#/components/schemas/user_profile" + } + } + } + } + */ try { const schema = Joi.object().keys({ @@ -519,19 +533,19 @@ export class UserController { name: Joi.string().max(64).required().label("Name"), }); - req.body.id = req.auth.data.id; + req.body.id = req.auth?.data.id; const param: User = await schema.validateAsync(req.body); const userRepository = OrmHelper.DB.getRepository(User); - const data = await userRepository.findOneBy({id: param.id}); + const data = await userRepository.findOneBy({ id: param.id }); if (data != null) { data.name = param.name; data.username = param.username; data.email = param.email; - data.updated_by = req.auth.data.name; + data.updated_by = req.auth?.data.name; await userRepository.save(data); @@ -549,23 +563,23 @@ export class UserController { static async updatePassword(req: Request, res: Response, next: NextFunction): Promise { /* - #swagger.tags = ['User'] - #swagger.security = [{ - "bearerAuth": [] - }] + #swagger.tags = ['User'] + #swagger.security = [{ + "bearerAuth": [] + }] - #swagger.requestBody = { - required: true, - description: "This action will effect to user related token JWT logged", - content: { - "application/json": { - schema: { - $ref: "#/components/schemas/user_password" - } - } - } - } - */ + #swagger.requestBody = { + required: true, + description: "This action will effect to user related token JWT logged", + content: { + "application/json": { + schema: { + $ref: "#/components/schemas/user_password" + } + } + } + } + */ try { const schema = Joi.object().keys({ @@ -578,13 +592,13 @@ export class UserController { retype_password: Joi.ref("password"), }); - req.body.id = req.auth.data.id; + req.body.id = req.auth?.data.id; const param: User = await schema.validateAsync(req.body); const userRepository = OrmHelper.DB.getRepository(User); - const data = await userRepository.findOneBy({id: param.id}); + const data = await userRepository.findOneBy({ id: param.id }); if (data != null) { if (param.password) { @@ -606,30 +620,83 @@ export class UserController { } } + static async uploadFile( + files: fileUpload.FileArray | null | undefined, + fieldName: string, + folderPath: string + ): Promise { + if (!files || !files[fieldName]) return null; + + const file = files[fieldName] as fileUpload.UploadedFile; + + const maxSize = 1 * 1024 * 1024; // 1MB + if (file.size > maxSize) { + throw { message: `${fieldName}: File size exceeds 1MB limit` }; + } + + const allowedMimes = ['image/jpeg', 'image/jpg', 'image/png', 'image/webp']; + if (!allowedMimes.includes(file.mimetype)) { + throw { message: `${fieldName}: Invalid file type. Only JPG, PNG, and WEBP are allowed` }; + } + + const ext = file.name.split('.'); + const name = uuidv4() + '.' + ext[ext.length - 1]; + const file_path = path.join(folderPath, name); + + if (!fs.existsSync(folderPath)) { + fs.mkdirSync(folderPath, { recursive: true }); + } + + await file.mv(file_path); + log.info(`File uploaded successfully: ${file_path}`); + + return name; + } + + static async deleteFile(filePath: string): Promise { + try { + if (fs.existsSync(filePath)) { + fs.unlinkSync(filePath); + log.info(`File deleted successfully: ${filePath}`); + } + } catch (e) { + log.error(`Failed to delete file: ${filePath}`, e); + } + } + static async update(req: Request, res: Response, next: NextFunction): Promise { /* - #swagger.tags = ['User'] - #swagger.security = [{ - "bearerAuth": [] - }] - #swagger.parameters['id'] = { - in: 'path', - description: 'User ID.', - required: true, - type: 'string' - } - - #swagger.requestBody = { - required: true, - content: { - "application/json": { - schema: { - $ref: "#/components/schemas/user" - } - } - } - } - */ + #swagger.tags = ['User'] + #swagger.security = [{ + "bearerAuth": [] + }] + #swagger.consumes = ['multipart/form-data'] + #swagger.parameters['id'] = { + in: 'path', + description: 'User ID.', + required: true, + type: 'string' + } + + #swagger.requestBody = { + required: true, + content: { + "multipart/form-data": { + schema: { + type: "object", + properties: { + email: { type: "string", description: "Email" }, + username: { type: "string", description: "Username" }, + password: { type: "string", description: "Password" }, + name: { type: "string", description: "name" }, + status: { type: "string", description: "Status" }, + profile_picture: { type: "string", format: "binary", description: "Image file (max 1MB, jpg/png/webp)" } + } + } + } + } + } + */ try { const schema = Joi.object().keys({ @@ -637,10 +704,10 @@ export class UserController { email: Joi.string().email().max(64).required().label("Email"), username: Joi.string().max(64).required().label("Username"), password: Joi.string().allow("").optional().label("Password"), - retype_password: Joi.ref("password"), name: Joi.string().max(64).required().label("Name"), employee_id: Joi.string().uuid().optional().allow("").label("Employee ID"), status: Joi.string().required().label("Status"), + profile_picture: Joi.string().allow('', null).optional().label("Profile Picture"), }); req.body.id = req.params["id"]; @@ -649,14 +716,23 @@ export class UserController { const userRepository = OrmHelper.DB.getRepository(User); - const data = await userRepository.findOneBy({id: param.id}); + const data = await userRepository.findOneBy({ id: param.id }); if (data != null) { + const doctorprofile = await UserController.uploadFile(req.files, 'profile_picture', 'uploads/profile-picture'); + if (doctorprofile) { + if (data.profile_picture) { + const oldFilePath = path.join('uploads/profile-picture', data.profile_picture); + await UserController.deleteFile(oldFilePath); + } + data.profile_picture = doctorprofile; + } + data.name = param.name; data.username = param.username; data.email = param.email; data.status = param.status; - data.updated_by = req.auth.data.name; + data.updated_by = req.auth?.data.name; data.updated_at = new Date(); // if (param.employee_id && param.employee_id != "") { @@ -680,6 +756,104 @@ export class UserController { return ReturnHelper.errorResponse(res, 404, 401, Language.lang.failed_not_found, ""); } } catch (e: unknown) { + // ← Ganti dari log.error(e) menjadi ini + if (e instanceof Error) { + log.error(e.message, e.stack); + } else { + log.error(JSON.stringify(e)); + } + const err = e as Error; + + return ReturnHelper.errorResponse(res, 500, 402, Language.lang.failed_update, err.message); + } + + } + + static async updateUserSignature(req: Request, res: Response, next: NextFunction): Promise { + /* + #swagger.tags = ['User'] + #swagger.security = [{ + "bearerAuth": [] + }] + #swagger.consumes = ['multipart/form-data'] + #swagger.parameters['id'] = { + in: 'path', + description: 'User ID.', + required: true, + type: 'string' + } + + #swagger.requestBody = { + required: true, + content: { + "multipart/form-data": { + schema: { + type: "object", + required: ["user_signature"], + properties: { + user_signature: { type: "string", format: "binary", description: "Image file (max 1MB, jpg/png/webp)" } + } + } + } + } + } + */ + + try { + const schema = Joi.object().keys({ + id: Joi.string().uuid().required().label("ID"), + user_signature: Joi.string().allow('', null).optional().label("User Signature"), + }); + + req.body.id = req.params["id"]; + + const param: any = await schema.validateAsync(req.body); + + const userRepository = OrmHelper.DB.getRepository(User); + + const data = await userRepository.findOneBy({ id: param.id }); + + if (data != null) { + const usersignature = await UserController.uploadFile(req.files, 'user_signature', 'uploads/user-signature'); + + if (usersignature) { + // ← Hapus signature lama jika ada + if (data.signature) { + const oldFilePath = path.join('uploads/user-signature', data.signature); + await UserController.deleteFile(oldFilePath); + } + + data.signature = usersignature; + } + + // data.name = param.name; + // data.username = param.username; + // data.email = param.email; + // data.status = param.status; + // data.updated_by = req.auth?.data.name; + // data.updated_at = new Date(); + + // if (param.employee_id && param.employee_id != "") { + // const employee = await OrmHelper.DB.manager + // .getRepository(HrmsEmployee) + // .findOneByOrFail({id: param.employee_id}); + // data.employee = employee; + // } else { + // data.employee = null; + // } + + // if (param.password) { + // data.password = param.password; + // data.hashPassword(); + // } + + await userRepository.save(data); + + return ReturnHelper.successResponseAny(res, 200, Language.lang.success_update, data); + } else { + return ReturnHelper.errorResponse(res, 404, 401, Language.lang.failed_not_found, ""); + } + } catch (e: unknown) { log.error(e); const err = e as Error; @@ -689,43 +863,43 @@ export class UserController { static async delete(req: Request, res: Response, next: NextFunction): Promise { /* - #swagger.tags = ['User'] - #swagger.security = [{ - "bearerAuth": [] - }] - #swagger.parameters['id'] = { - in: 'path', - description: 'User ID.', - required: true, - type: 'string' - } - #swagger.parameters['hard'] = { - in: 'path', - description: 'Is Hard Delete', - required: false, - type: 'boolean' - } - */ + #swagger.tags = ['User'] + #swagger.security = [{ + "bearerAuth": [] + }] + #swagger.parameters['id'] = { + in: 'path', + description: 'User ID.', + required: true, + type: 'string' + } + #swagger.parameters['hard'] = { + in: 'path', + description: 'Is Hard Delete', + required: false, + type: 'boolean' + } + */ try { const schema = Joi.object().keys({ id: Joi.string().uuid().required().label("ID"), hard: Joi.bool().optional().allow("").label("Is hard delete?"), }); - const param: {id: string; hard: boolean} = await schema.validateAsync(req.params); + const param: { id: string; hard: boolean } = await schema.validateAsync(req.params); const userRepository = OrmHelper.DB.getRepository(User); const userRTRepository = OrmHelper.DB.getRepository(UserRefreshToken); const affected = ( !param.hard - ? await userRepository.softDelete({id: param.id}) - : await userRepository.delete({id: param.id}) - ).affected; + ? await userRepository.softDelete({ id: param.id }) + : await userRepository.delete({ id: param.id }) + ).affected ?? 0; const affected_rt = ( !param.hard - ? await userRTRepository.softDelete({id_user: param.id}) - : await userRTRepository.delete({id_user: param.id}) + ? await userRTRepository.softDelete({ id_user: param.id }) + : await userRTRepository.delete({ id_user: param.id }) ).affected; if (affected > 0) { @@ -743,17 +917,17 @@ export class UserController { static async detail(req: Request, res: Response, next: NextFunction): Promise { /* - #swagger.tags = ['User'] - #swagger.security = [{ - "bearerAuth": [] - }] - #swagger.parameters['id'] = { - in: 'path', - description: 'User ID.', - required: true, - type: 'string' - } - */ + #swagger.tags = ['User'] + #swagger.security = [{ + "bearerAuth": [] + }] + #swagger.parameters['id'] = { + in: 'path', + description: 'User ID.', + required: true, + type: 'string' + } + */ try { const schema = Joi.object().keys({ id: Joi.string().uuid().required().label("ID"), @@ -777,14 +951,22 @@ export class UserController { "employee.administrativu", "employee.munisipo", ], - where: {id: param.id}, + where: { id: param.id }, }); - const data = { - ...detail, - }; + if (detail != null) { + const data = { + ...detail, + signature: detail.signature ?? null, + file_signature: detail.signature + ? `${config.server.host_swagger}uploads/user-signature/${detail.signature}` + : null, + profile: detail.profile_picture ?? null, + profile_picture: detail.profile_picture + ? `${config.server.host_swagger}uploads/doctor-profile/${detail.profile_picture}` + : null, + }; - if (data != null) { return ReturnHelper.successResponseAny(res, 200, Language.lang.success_view, data); } else { return ReturnHelper.errorResponse(res, 404, 401, Language.lang.failed_not_found, ""); @@ -799,18 +981,18 @@ export class UserController { static async restore(req: Request, res: Response, next: NextFunction): Promise { /* - #swagger.tags = ['User'] - #swagger.security = [{ - "bearerAuth": [] - }] - - #swagger.parameters['id'] = { - in: 'path', - description: 'User ID.', - required: true, - type: 'string' - } - */ + #swagger.tags = ['User'] + #swagger.security = [{ + "bearerAuth": [] + }] + + #swagger.parameters['id'] = { + in: 'path', + description: 'User ID.', + required: true, + type: 'string' + } + */ try { const schema = Joi.object().keys({ id: Joi.string().uuid().required().label("ID"), @@ -821,8 +1003,8 @@ export class UserController { const userRepository = OrmHelper.DB.getRepository(User); const userRTRepository = OrmHelper.DB.getRepository(UserRefreshToken); - const affected = (await userRepository.restore({id: param.id})).affected; - const affected_rt = (await userRTRepository.restore({id_user: param.id})).affected; + const affected = (await userRepository.restore({ id: param.id })).affected ?? 0; + const affected_rt = (await userRTRepository.restore({ id_user: param.id })).affected; if (affected > 0) { return ReturnHelper.successResponseAny(res, 200, Language.lang.success_restore, {}); diff --git a/src/helpers/express/upload.ts b/src/helpers/express/upload.ts new file mode 100644 index 0000000..1b8820d --- /dev/null +++ b/src/helpers/express/upload.ts @@ -0,0 +1,12 @@ +import config from 'config'; +import express from 'express'; +import fileUpload from 'express-fileupload' + +export class UploadHelper { + static setup(app: express.Application) { + app.use(fileUpload({ + limits: { fileSize: 10485760 }, + abortOnLimit: true, + })); + } +} \ No newline at end of file diff --git a/src/langs/lang.ts b/src/langs/lang.ts index 5d52af3..637d969 100644 --- a/src/langs/lang.ts +++ b/src/langs/lang.ts @@ -2,51 +2,119 @@ import path from 'path'; import fs from 'fs'; import { NextFunction, Request, Response } from 'express'; -interface LanguageValue { - success_insert?: string; - success_update?: string; - success_delete?: string; - success_restore?: string; - success_view?: string; - success?: string; - success_valid_otp?: string; - success_logout?: string; +// interface LanguageValue { +// success_insert?: string; +// success_update?: string; +// success_delete?: string; +// success_restore?: string; +// success_view?: string; +// success?: string; +// success_valid_otp?: string; +// success_logout?: string; - failed?: string; - failed_access?: string; - failed_access_otp?: string; - failed_expired_otp?: string; - failed_access_pin?: string; - failed_try_pin?: string; - failed_insert?: string; - failed_update?: string; - failed_delete?: string; - failed_restore?: string; - failed_save?: string; - failed_view?: string; - failed_empty?: string; - failed_data?: string; - failed_duplicate?: string; - failed_not_found?: string; - failed_limit_otp?: string; - failed_limit_link_code?: string; - failed_expired_token?: string; - failed_token?: string; - failed_add_point?: string; +// failed?: string; +// failed_access?: string; +// failed_access_otp?: string; +// failed_expired_otp?: string; +// failed_access_pin?: string; +// failed_try_pin?: string; +// failed_insert?: string; +// failed_update?: string; +// failed_delete?: string; +// failed_restore?: string; +// failed_save?: string; +// failed_view?: string; +// failed_empty?: string; +// failed_data?: string; +// failed_duplicate?: string; +// failed_not_found?: string; +// failed_limit_otp?: string; +// failed_limit_link_code?: string; +// failed_expired_token?: string; +// failed_token?: string; +// failed_add_point?: string; +// failed_logout?: string; +// failed_related?: string; + +// //login +// success_login?: string; +// failed_password?: string; +// } + +// export class Language { +// static lang_init: LanguageValue = {}; +// static lang: LanguageValue = {}; + +// static setup() { +// const fileLang: any = fs.readFileSync( +// path.join(__dirname, '../langs/json/en.json') +// ); +// Language.lang_init['en'] = JSON.parse(fileLang.toString('utf8')); +// } + +// static apply = (req: Request, res: Response, next: NextFunction) => { +// const acceptLanguageHeader = req.get('Accept-Language') as string | null; +// if (!acceptLanguageHeader) { +// //default +// req.params.language = 'en'; +// } else { +// req.params.language = acceptLanguageHeader.substring(0, 2).toLowerCase(); +// } + +// Language.lang = Language.lang_init[req.params.language] ? Language.lang_init[req.params.language] : Language.lang_init['en']; + +// return next(); +// } +// } + +interface LanguageValue { + success_insert: string; + success_update: string; + success_delete: string; + success_restore: string; + success_view: string; + success: string; + success_valid_otp: string; + + failed: string; + failed_access: string; + failed_access_otp: string; + failed_expired_otp: string; + failed_access_pin: string; + failed_try_pin: string; + failed_insert: string; + failed_update: string; + failed_delete: string; + failed_restore: string; + failed_save: string; + failed_view: string; + failed_empty: string; + failed_data: string; + failed_duplicate: string; + failed_not_found: string; + failed_limit_otp: string; + failed_limit_link_code: string; + failed_expired_token: string; + failed_token: string; + failed_add_point: string; + success_login: string; + failed_password: string; + success_logout?: string; failed_logout?: string; failed_related?: string; +} - //login - success_login?: string; - failed_password?: string; +// Tambah index signature agar bisa akses dengan ['en'] +interface LanguageStore { + [key: string]: LanguageValue; } export class Language { - static lang_init: LanguageValue = {}; - static lang: LanguageValue = {}; + static lang_init: LanguageStore = {}; + static lang: LanguageValue = {} as LanguageValue; static setup() { - const fileLang: any = fs.readFileSync( + const fileLang = fs.readFileSync( path.join(__dirname, '../langs/json/en.json') ); Language.lang_init['en'] = JSON.parse(fileLang.toString('utf8')); @@ -55,13 +123,13 @@ export class Language { static apply = (req: Request, res: Response, next: NextFunction) => { const acceptLanguageHeader = req.get('Accept-Language') as string | null; if (!acceptLanguageHeader) { - //default req.params.language = 'en'; } else { req.params.language = acceptLanguageHeader.substring(0, 2).toLowerCase(); } - Language.lang = Language.lang_init[req.params.language] ? Language.lang_init[req.params.language] : Language.lang_init['en']; + Language.lang = Language.lang_init[req.params.language] + ?? Language.lang_init['en']; return next(); } diff --git a/src/server.ts b/src/server.ts index a499bd1..8f557b5 100644 --- a/src/server.ts +++ b/src/server.ts @@ -10,9 +10,11 @@ import { RoutePublic } from './routes/public'; import { Language } from './langs/lang'; import { SwaggerHelper } from './helpers/express/swagger'; import { TrustProxyHelper } from './helpers/express/trust_proxy'; +import { UploadHelper } from './helpers/express/upload' const app = express(); +UploadHelper.setup(app) CorsHelper.setup(app); CompressionHelper.setup(app); MorganHelper.setup(app);