import { Position } from "./../entities/Position"; import { ApiKey } from "./../entities/ApiKey"; import { ApiAttribute } from "./../entities/ApiAttribute"; import { CreateApi } from "./../entities/ApiName"; import { PosMaster } from "../entities/PosMaster"; import { OrgChild4 } from "../entities/OrgChild4"; import { OrgChild3 } from "../entities/OrgChild3"; import { OrgChild2 } from "../entities/OrgChild2"; import { OrgChild1 } from "../entities/OrgChild1"; import { OrgRoot } from "../entities/OrgRoot"; import { ProfileEmployee } from "../entities/ProfileEmployee"; import { ProfileSalary } from "../entities/ProfileSalary"; import { ProfileOther } from "../entities/ProfileOther"; import { ProfileNopaid } from "../entities/ProfileNopaid"; import { ProfileLeave } from "../entities/ProfileLeave"; import { ProfileInsignia } from "../entities/ProfileInsignia"; import { ProfileHonor } from "../entities/ProfileHonor"; import { ProfileGovernment } from "../entities/ProfileGovernment"; import { ProfileFamilyMother } from "../entities/ProfileFamilyMother"; import { ProfileFamilyFather } from "../entities/ProfileFamilyFather"; import { ProfileFamilyCouple } from "../entities/ProfileFamilyCouple"; import { ProfileEducation } from "../entities/ProfileEducation"; import { ProfileDuty } from "../entities/ProfileDuty"; import { ProfileDiscipline } from "../entities/ProfileDiscipline"; import { ProfileDevelopment } from "../entities/ProfileDevelopment"; import { ProfileChildren } from "../entities/ProfileChildren"; import { ProfileChangeName } from "../entities/ProfileChangeName"; import { ProfileCertificate } from "../entities/ProfileCertificate"; import { ProfileAssessment } from "../entities/ProfileAssessment"; import { ProfileAbility } from "../entities/ProfileAbility"; import { Controller, Post, Delete, Route, Security, Tags, Body, Path, Request, Response, Get, Put, Query, } from "tsoa"; import { AppDataSource } from "../database/data-source"; import HttpSuccess from "../interfaces/http-success"; import HttpStatusCode from "../interfaces/http-status"; import HttpError from "../interfaces/http-error"; import { RequestWithUser } from "../middlewares/user"; import { ApiName } from "../entities/ApiName"; import { Profile } from "../entities/Profile"; import { SystemDefinition, EntityDefinition, SystemCode } from "./../interfaces/api-type"; @Route("api/v1/org/api-manage") @Tags("ApiManage") @Security("bearerAuth") @Response( HttpStatusCode.INTERNAL_SERVER_ERROR, "เกิดข้อผิดพลาด ไม่สามารถแสดงรายการได้ กรุณาลองใหม่ในภายหลัง", ) export class ApiManageController extends Controller { private readonly profileRepository = AppDataSource.getRepository(Profile); private readonly profileEmployeeRepository = AppDataSource.getRepository(ProfileEmployee); private readonly profileGovernmentRepository = AppDataSource.getRepository(ProfileGovernment); private readonly profileProfileSalaryRepository = AppDataSource.getRepository(ProfileSalary); private readonly profileAbilityRepository = AppDataSource.getRepository(ProfileAbility); private readonly profileAssessmentRepository = AppDataSource.getRepository(ProfileAssessment); private readonly profileCertificateRepository = AppDataSource.getRepository(ProfileCertificate); private readonly profileHonorRepository = AppDataSource.getRepository(ProfileHonor); private readonly profileInsigniaRepository = AppDataSource.getRepository(ProfileInsignia); private readonly profileLeaveRepository = AppDataSource.getRepository(ProfileLeave); private readonly profileChangeNameRepository = AppDataSource.getRepository(ProfileChangeName); private readonly profileDevelopmentRepository = AppDataSource.getRepository(ProfileDevelopment); private readonly profileDisciplineRepository = AppDataSource.getRepository(ProfileDiscipline); private readonly profileDutyRepository = AppDataSource.getRepository(ProfileDuty); private readonly profileEducationRepository = AppDataSource.getRepository(ProfileEducation); private readonly profileFamilyCoupleRepository = AppDataSource.getRepository(ProfileFamilyCouple); private readonly profileFamilyFatherRepository = AppDataSource.getRepository(ProfileFamilyFather); private readonly profileFamilyMotherRepository = AppDataSource.getRepository(ProfileFamilyMother); private readonly profileChildrenRepository = AppDataSource.getRepository(ProfileChildren); private readonly profileNopaidRepository = AppDataSource.getRepository(ProfileNopaid); private readonly profileOtherRepository = AppDataSource.getRepository(ProfileOther); private readonly orgRootRepository = AppDataSource.getRepository(OrgRoot); private readonly orgChild1Repository = AppDataSource.getRepository(OrgChild1); private readonly orgChild2Repository = AppDataSource.getRepository(OrgChild2); private readonly orgChild3Repository = AppDataSource.getRepository(OrgChild3); private readonly orgChild4Repository = AppDataSource.getRepository(OrgChild4); private readonly posMasterRepository = AppDataSource.getRepository(PosMaster); private readonly positionRepository = AppDataSource.getRepository(Position); // รายการระบบ private readonly systems: SystemDefinition[] = [ { code: "registry", name: "ข้อมูลทะเบียนประวัติข้าราชการ", }, { code: "registry_emp", name: "ข้อมูลทะเบียนประวัติลูกจ้างประจำ", }, { code: "registry_temp", name: "ข้อมูลทะเบียนประวัติลูกจ้างชั่วคราว", }, { code: "organization", name: "ข้อมูลโครงสร้าง", }, { code: "position", name: "ข้อมูลอัตรากำลัง", }, ]; // รายการเอนทิตีทั้งหมด private readonly entities: EntityDefinition[] = [ { name: "Profile", repository: this.profileRepository, description: "ข้อมูลหลัก", isMain: true, system: ["registry"], }, { name: "ProfileEmployee", repository: this.profileEmployeeRepository, description: "ข้อมูลหลัก", isMain: true, system: ["registry_emp", "registry_temp"], }, { name: "ProfileGovernment", repository: this.profileGovernmentRepository, description: "ข้อมูลราชการ", system: ["registry", "registry_emp", "registry_temp"], }, { name: "ProfileSalary", repository: this.profileProfileSalaryRepository, description: "ข้อมูลตำแหน่ง/เงินเดือน", system: ["registry", "registry_emp", "registry_temp"], }, { name: "ProfileAbility", repository: this.profileAbilityRepository, description: "ข้อมูลความสามารถ", system: ["registry", "registry_emp", "registry_temp"], }, { name: "ProfileAssessment", repository: this.profileAssessmentRepository, description: "ข้อมูลการประเมิน", system: ["registry", "registry_emp", "registry_temp"], }, { name: "ProfileCertificate", repository: this.profileCertificateRepository, description: "ข้อมูลใบรับรอง", system: ["registry", "registry_emp", "registry_temp"], }, { name: "ProfileHonor", repository: this.profileHonorRepository, description: "ข้อมูลเกียรติบัตร", system: ["registry", "registry_emp", "registry_temp"], }, { name: "ProfileInsignia", repository: this.profileInsigniaRepository, description: "ข้อมูลเครื่องหมาย", system: ["registry", "registry_emp", "registry_temp"], }, { name: "ProfileLeave", repository: this.profileLeaveRepository, description: "ข้อมูลการลา", system: ["registry", "registry_emp", "registry_temp"], }, { name: "ProfileChangeName", repository: this.profileChangeNameRepository, description: "ข้อมูลการเปลี่ยนชื่อ", system: ["registry", "registry_emp", "registry_temp"], }, { name: "ProfileDevelopment", repository: this.profileDevelopmentRepository, description: "ข้อมูลการพัฒนา", system: ["registry", "registry_emp", "registry_temp"], }, { name: "ProfileDiscipline", repository: this.profileDisciplineRepository, description: "ข้อมูลวินัย", system: ["registry", "registry_emp", "registry_temp"], }, { name: "ProfileDuty", repository: this.profileDutyRepository, description: "ข้อมูลหน้าที่ปฏิบัติราชการ", system: ["registry", "registry_emp", "registry_temp"], }, { name: "ProfileEducation", repository: this.profileEducationRepository, description: "ข้อมูลการศึกษา", system: ["registry", "registry_emp", "registry_temp"], }, { name: "ProfileFamilyCouple", repository: this.profileFamilyCoupleRepository, description: "ข้อมูลคู่สมรส", system: ["registry", "registry_emp", "registry_temp"], }, { name: "ProfileFamilyFather", repository: this.profileFamilyFatherRepository, description: "ข้อมูลบิดา", system: ["registry", "registry_emp", "registry_temp"], }, { name: "ProfileFamilyMother", repository: this.profileFamilyMotherRepository, description: "ข้อมูลมารดา", system: ["registry", "registry_emp", "registry_temp"], }, { name: "ProfileChildren", repository: this.profileChildrenRepository, description: "ข้อมูลบุตร", system: ["registry", "registry_emp", "registry_temp"], }, { name: "ProfileNopaid", repository: this.profileNopaidRepository, description: "ข้อมูลการไม่จ่ายเงินเดือน", system: ["registry", "registry_emp", "registry_temp"], }, { name: "ProfileOther", repository: this.profileOtherRepository, description: "ข้อมูลอื่นๆ", system: ["registry", "registry_emp", "registry_temp"], }, { name: "OrgRoot", repository: this.orgRootRepository, description: "ข้อมูลหน่วยงาน", isMain: true, system: ["organization"], }, { name: "OrgChild1", repository: this.orgChild1Repository, description: "ข้อมูลส่วนราชการ ระดับที่ 1", system: ["organization"], }, { name: "OrgChild2", repository: this.orgChild2Repository, description: "ข้อมูลส่วนราชการ ระดับที่ 2", system: ["organization"], }, { name: "OrgChild3", repository: this.orgChild3Repository, description: "ข้อมูลส่วนราชการ ระดับที่ 3", system: ["organization"], }, { name: "OrgChild4", repository: this.orgChild4Repository, description: "ข้อมูลส่วนราชการ ระดับที่ 4", system: ["organization"], }, { name: "PosMaster", repository: this.posMasterRepository, description: "ข้อมูลอัตรากำลัง", isMain: true, system: ["position"], }, { name: "Position", repository: this.positionRepository, description: "ข้อมูลตำแหน่ง", system: ["position"], }, { name: "OrgRoot", repository: this.orgRootRepository, description: "ข้อมูลหน่วยงาน", system: ["position"], }, { name: "OrgChild1", repository: this.orgChild1Repository, description: "ข้อมูลส่วนราชการ ระดับที่ 1", system: ["position"], }, { name: "OrgChild2", repository: this.orgChild2Repository, description: "ข้อมูลส่วนราชการ ระดับที่ 2", system: ["position"], }, { name: "OrgChild3", repository: this.orgChild3Repository, description: "ข้อมูลส่วนราชการ ระดับที่ 3", system: ["position"], }, { name: "OrgChild4", repository: this.orgChild4Repository, description: "ข้อมูลส่วนราชการ ระดับที่ 4", system: ["position"], }, { name: "Profile", repository: this.profileRepository, description: "ข้อมูลคนครอง", system: ["position"], }, ]; private readonly DEFAULT_PAGE_SIZE = 10; // ขนาดหน้าเริ่มต้น private readonly EXCLUDED_COLUMNS = ["createdUserId", "lastUpdateUserId"]; // ฟิลด์ที่ไม่ต้องการแสดงในผลลัพธ์ private validateSuperAdminRole(user: any): void { if (!user.role.includes("SUPER_ADMIN")) { throw new HttpError(HttpStatusCode.FORBIDDEN, "คุณไม่มีสิทธิ์ในการเข้าถึงข้อมูลนี้"); } } private generateApiCode(): string { return Math.random().toString(36).substring(2, 10).toUpperCase(); } private createApiPath(system: SystemCode = "registry", code: string): string { return `/api/v1/org/api-service/${system}/${code}`; } /** * list systems * @summary รายการ systems */ @Get("/systems") async listSystems(): Promise { return new HttpSuccess(this.systems); } /** * list fields by systems * @summary รายการ fields ตาม systems */ @Get("/:system/fields") async listAttribute( @Request() req: RequestWithUser, @Path("system") system: SystemCode, ): Promise { try { this.validateSuperAdminRole(req.user); const result = this.entities .filter((entity) => entity.system.includes(system)) .map(({ name, repository, description, isMain }) => ({ tb: name, description, isMain: isMain || false, propertys: repository.metadata.columns .filter( (column: any) => !column.isPrimary && !this.EXCLUDED_COLUMNS.includes(column.propertyName), ) .map((column: any) => ({ propertyName: column.propertyName, type: typeof column.type === "string" ? column.type : "string", comment: column.comment, key: column.propertyName, })), })); return new HttpSuccess(result); } catch (error) { throw new HttpError( HttpStatusCode.INTERNAL_SERVER_ERROR, (error instanceof Error ? error.message : String(error)) || "เกิดข้อผิดพลาด ไม่สามารถแสดงรายการได้ กรุณาลองใหม่ในภายหลัง", ); } } /** * list api * @summary รายการ api */ @Get("/lists") async listApi( @Request() req: RequestWithUser, @Query("page") page: number = 1, @Query("pageSize") pageSize: number = this.DEFAULT_PAGE_SIZE, @Query("keyword") keyword: string = "", @Query("system") system: SystemCode | "" = "", @Query("isActive") isActive: boolean = true, ): Promise { try { this.validateSuperAdminRole(req.user); const offset = (page - 1) * pageSize; const queryBuilder = AppDataSource.getRepository(ApiName) .createQueryBuilder("apiName") .where("apiName.isActive = :isActive", { isActive }) .select([ "apiName.id", "apiName.name", "apiName.code", "apiName.pathApi", "apiName.methodApi", "apiName.system", "apiName.isActive", "apiName.createdAt", "apiName.lastUpdatedAt", ]); if (keyword?.trim()) { queryBuilder.andWhere( "(apiName.name LIKE :keyword OR apiName.code LIKE :keyword OR apiName.pathApi LIKE :keyword)", { keyword: `%${keyword.trim()}%` }, ); } if (system) { queryBuilder.andWhere("apiName.system = :system", { system }); } const [apiNames, total] = await queryBuilder .skip(offset) .take(pageSize) .orderBy("apiName.lastUpdatedAt", "DESC") .addOrderBy("apiName.createdAt", "ASC") .getManyAndCount(); return new HttpSuccess({ data: apiNames.map((api) => ({ ...api, system: this.systems.find((s) => s.code === api.system)?.name || api.system, })), total, }); } catch (error) { throw new HttpError( HttpStatusCode.INTERNAL_SERVER_ERROR, (error instanceof Error ? error.message : String(error)) || "เกิดข้อผิดพลาด ไม่สามารถแสดงรายการได้ กรุณาลองใหม่ในภายหลัง", ); } } /** * สร้าง api * @summary สร้าง api */ @Post("") async createApi( @Request() req: RequestWithUser, @Body() apiData: CreateApi, ): Promise { const queryRunner = AppDataSource.createQueryRunner(); await queryRunner.connect(); await queryRunner.startTransaction(); try { this.validateSuperAdminRole(req.user); const code = this.generateApiCode(); const postData = { name: apiData.name, code, pathApi: this.createApiPath(apiData.system as SystemCode, code), methodApi: apiData.methodApi || "GET", system: apiData.system || "registry", isActive: apiData.isActive || false, createdUserId: req.user?.sub, createdFullName: req.user?.name || "", }; const apiName = await queryRunner.manager.getRepository(ApiName).save(postData); if (apiData.apiAttributes?.length) { let orderingCounter = 0; const attributesToSave = apiData.apiAttributes.flatMap((attr) => attr.propertyKey.map((propertyKey) => ({ apiNameId: apiName.id, tbName: attr.tbName, propertyKey, ordering: orderingCounter++, createdUserId: req.user?.sub, createdFullName: req.user?.name || "", })), ); await queryRunner.manager.getRepository(ApiAttribute).save(attributesToSave); } await queryRunner.commitTransaction(); return new HttpSuccess(apiName.id); } catch (error) { await queryRunner.rollbackTransaction(); throw new HttpError( HttpStatusCode.INTERNAL_SERVER_ERROR, (error instanceof Error ? error.message : String(error)) || "เกิดข้อผิดพลาด ไม่สามารถบันทึกข้อมูลได้ กรุณาลองใหม่ในภายหลัง", ); } finally { await queryRunner.release(); } } /** * รายละเอียด api ตามไอดี * @summary รายละเอียด api ตามไอดี */ @Get("/{id}") async getApiById( @Request() req: RequestWithUser, @Path("id") id: string, ): Promise { try { this.validateSuperAdminRole(req.user); const apiName = await AppDataSource.getRepository(ApiName).findOne({ select: { id: true, name: true, code: true, pathApi: true, methodApi: true, system: true, isActive: true, apiAttributes: true, }, where: { id }, relations: ["apiAttributes"], order: { apiAttributes: { ordering: "ASC", }, }, }); if (!apiName) { throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบ API ที่ต้องการแก้ไข"); } const items = { ...apiName, apiAttributes: apiName.apiAttributes.map((attr) => ({ tbName: attr.tbName, propertyKey: attr.propertyKey, })), }; return new HttpSuccess(items); } catch (error) { throw new HttpError( HttpStatusCode.INTERNAL_SERVER_ERROR, (error instanceof Error ? error.message : String(error)) || "เกิดข้อผิดพลาด ไม่สามารถบันทึกข้อมูลได้ กรุณาลองใหม่ในภายหลัง", ); } } /** * แก้ไข api * @summary แก้ไขข้อมูล api */ @Put("/{id}") async editApi( @Request() req: RequestWithUser, @Path("id") id: string, @Body() apiData: CreateApi, ): Promise { const queryRunner = AppDataSource.createQueryRunner(); await queryRunner.connect(); await queryRunner.startTransaction(); try { this.validateSuperAdminRole(req.user); const apiName = await queryRunner.manager.getRepository(ApiName).findOneBy({ id }); if (!apiName) { throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบ API ที่ต้องการแก้ไข"); } const updateData = { name: apiData.name, pathApi: this.createApiPath(apiData.system as SystemCode, apiName.code), methodApi: apiData.methodApi || "GET", system: apiData.system || "registry", isActive: apiData.isActive || false, lastUpdateUserId: req.user?.sub, lastUpdateFullName: req.user?.name || "", }; await queryRunner.manager.getRepository(ApiName).update(id, updateData); await queryRunner.manager.getRepository(ApiAttribute).delete({ apiNameId: id }); if (apiData.apiAttributes?.length) { let orderingCounter = 0; const attributesToSave = apiData.apiAttributes.flatMap((attr) => attr.propertyKey.map((propertyKey) => ({ apiNameId: apiName.id, tbName: attr.tbName, propertyKey, ordering: orderingCounter++, lastUpdateUserId: req.user?.sub, lastUpdateFullName: req.user?.name || "", })), ); await queryRunner.manager.getRepository(ApiAttribute).save(attributesToSave); } await queryRunner.commitTransaction(); return new HttpSuccess(); } catch (error) { await queryRunner.rollbackTransaction(); throw new HttpError( HttpStatusCode.INTERNAL_SERVER_ERROR, (error instanceof Error ? error.message : String(error)) || "เกิดข้อผิดพลาด ไม่สามารถบันทึกข้อมูลได้ กรุณาลองใหม่ในภายหลัง", ); } finally { await queryRunner.release(); } } /** * ลบ api * @summary ลบข้อมูล api */ @Delete("/{id}") async deleteApi( @Request() req: RequestWithUser, @Path("id") id: string, ): Promise { const queryRunner = AppDataSource.createQueryRunner(); await queryRunner.connect(); await queryRunner.startTransaction(); try { this.validateSuperAdminRole(req.user); const checkUsed = await AppDataSource.getRepository(ApiKey) .createQueryBuilder("apiKey") .innerJoin("apiKey.apiNames", "apiName") .where("apiName.id = :id", { id }) .getCount(); if (checkUsed > 0) { throw new HttpError( HttpStatusCode.BAD_REQUEST, "ไม่สามารถลบ API นี้ได้ เนื่องจากมีการใช้งานอยู่", ); } await queryRunner.manager.getRepository(ApiAttribute).delete({ apiNameId: id }); await queryRunner.manager.getRepository(ApiName).delete({ id }); await queryRunner.commitTransaction(); return new HttpSuccess(); } catch (error) { await queryRunner.rollbackTransaction(); throw new HttpError( HttpStatusCode.INTERNAL_SERVER_ERROR, (error instanceof Error ? error.message : String(error)) || "เกิดข้อผิดพลาด ไม่สามารถบันทึกข้อมูลได้ กรุณาลองใหม่ในภายหลัง", ); } finally { await queryRunner.release(); } } /** * list systems * @summary รายการ systems */ @Get("/manual/swagger") async getManualRequestWebService(): Promise { const json = { openapi: "3.0.0", info: { title: "Request Web Service", version: "1.0.0", description: "This is a manual request web service.", }, servers: [ { url: process.env.API_URL?.replace("/api/v1", "") || "http://localhost:13009", }, { url: "http://localhost:13009", description: "Local server", }, ], paths: { "/api/v1/org/api-service/{system}/{code}": { get: { summary: "Get Registry Data", parameters: [ { name: "system", in: "path", required: true, schema: { type: "string", enum: ["registry", "registry_emp", "registry_temp", "organization", "position"], }, }, { name: "code", in: "path", required: true, schema: { type: "string", }, }, { name: "page", in: "query", required: false, schema: { type: "integer", default: 1, }, }, { name: "pageSize", in: "query", required: false, schema: { type: "integer", default: 100, }, }, ], responses: { 200: { description: "Successful response", }, 400: { description: "Bad request", }, 500: { description: "Internal server error", }, }, }, }, }, components: { securitySchemes: { ApiKeyAuth: { type: "apiKey", in: "header", name: "X-API-Key", }, }, }, security: [ { ApiKeyAuth: [], }, ], }; return new HttpSuccess(json); } }