import { Controller, Post, Delete, Route, Security, Tags, Body, Path, Request, Response, Get, } 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 { CreateApiKey, ApiKey } from "../entities/ApiKey"; import { In } from "typeorm"; import { RequestWithUser } from "../middlewares/user"; import { ApiName } from "../entities/ApiName"; import { ApiHistory } from "../entities/ApiHistory"; import { OrgRoot } from "../entities/OrgRoot"; import { OrgChild1 } from "../entities/OrgChild1"; import { OrgChild2 } from "../entities/OrgChild2"; import { OrgChild3 } from "../entities/OrgChild3"; import { OrgChild4 } from "../entities/OrgChild4"; import { OrgRevision } from "../entities/OrgRevision"; const jwt = require("jsonwebtoken"); @Route("api/v1/org/apiKey") @Tags("ApiKey") @Security("bearerAuth") @Response( HttpStatusCode.INTERNAL_SERVER_ERROR, "เกิดข้อผิดพลาด ไม่สามารถแสดงรายการได้ กรุณาลองใหม่ในภายหลัง", ) export class ApiKeyController extends Controller { private apiKeyRepository = AppDataSource.getRepository(ApiKey); private apiNameRepository = AppDataSource.getRepository(ApiName); private apiHistoryRepository = AppDataSource.getRepository(ApiHistory); private orgRootRepository = AppDataSource.getRepository(OrgRoot); private orgChild1Repository = AppDataSource.getRepository(OrgChild1); private orgChild2Repository = AppDataSource.getRepository(OrgChild2); private orgChild3Repository = AppDataSource.getRepository(OrgChild3); private orgChild4Repository = AppDataSource.getRepository(OrgChild4); private orgRevisionRepository = AppDataSource.getRepository(OrgRevision); /** * API ตรวจสอบและถอดรหัส JWT token * * @summary ตรวจสอบ JWT API Key */ @Post("verify") async verifyApiKey(@Body() requestBody: { token: string }) { try { const jwtSecret = process.env.JWT_SECRET || "your-default-secret-key"; console.log("JWT_SECRET from env:", process.env.JWT_SECRET ? "exists" : "not found"); console.log("Using secret:", jwtSecret); const decoded = jwt.verify(requestBody.token, jwtSecret); return new HttpSuccess({ valid: true, data: decoded, }); } catch (error: any) { console.error("JWT Verification Error:", error.message); return new HttpSuccess({ valid: false, error: error.message, }); } } /** * API สร้าง Api Key * * @summary สร้าง Api Key (ADMIN) * */ @Post() async createApiKey( @Body() requestBody: CreateApiKey, @Request() request: RequestWithUser, ) { const checkName = await this.apiKeyRepository.findOne({ where: { name: requestBody.name }, }); if (checkName) throw new HttpError(HttpStatusCode.NOT_FOUND, "ชื่อนี้มีอยู่ในระบบแล้ว"); const apiName = await this.apiNameRepository.find({ where: { id: In(requestBody.apiId) }, }); const apiKey = Object.assign(new ApiKey(), requestBody); // Create JWT token with embedded data const tokenPayload = { keyId: apiKey.id || require("crypto").randomUUID(), name: apiKey.name, accessType: apiKey.accessType, dnaRootId: apiKey.dnaRootId, dnaChild1Id: apiKey.dnaChild1Id, dnaChild2Id: apiKey.dnaChild2Id, dnaChild3Id: apiKey.dnaChild3Id, dnaChild4Id: apiKey.dnaChild4Id, apiIds: requestBody.apiId, createdBy: request.user.sub, createdAt: new Date().toISOString(), iat: Math.floor(Date.now() / 1000), }; // Sign JWT with secret (you should use environment variable for the secret) const jwtSecret = process.env.JWT_SECRET || "your-default-secret-key"; const jwtToken = jwt.sign(tokenPayload, jwtSecret, { expiresIn: "365d", // 1 year expiration }); apiKey.keyApi = jwtToken; apiKey.apiNames = apiName; apiKey.createdUserId = request.user.sub; apiKey.createdFullName = request.user.name; apiKey.lastUpdateUserId = request.user.sub; apiKey.lastUpdateFullName = request.user.name; apiKey.createdAt = new Date(); apiKey.lastUpdatedAt = new Date(); await this.apiKeyRepository.save(apiKey); return new HttpSuccess(apiKey.keyApi); } /** * API ลบ Api Key * * @summary ลบ Api Key (ADMIN) * * @param {string} id Id Api Key */ @Delete("{id}") async deleteApiKey(@Path() id: string, @Request() req: RequestWithUser) { const delApiKey = await this.apiKeyRepository.findOne({ where: { id }, relations: ["apiHistorys"], }); if (!delApiKey) { throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลตำแหน่งนี้"); } await this.apiHistoryRepository.remove(delApiKey.apiHistorys); await this.apiKeyRepository.remove(delApiKey); return new HttpSuccess(); } /** * API รายการ Api Key * * @summary รายการ Api Key (ADMIN) * */ @Get("list") async listApiKey() { const apiKey = await this.apiKeyRepository.find({ relations: ["apiNames", "apiHistorys"], order: { createdAt: "DESC", apiNames: { createdAt: "DESC" } }, }); const orgNames = await this.buildOrgNameBatch(apiKey); const data = apiKey.map((_data) => ({ id: _data.id, createdAt: _data.createdAt, createdUserId: _data.createdUserId, createdFullName: _data.createdFullName, name: _data.name, accessType: _data.accessType, dnaRootId: _data.dnaRootId, dnaChild1Id: _data.dnaChild1Id, dnaChild2Id: _data.dnaChild2Id, dnaChild3Id: _data.dnaChild3Id, dnaChild4Id: _data.dnaChild4Id, orgName: orgNames.get(_data.id), apiNames: _data.apiNames.map((x) => ({ id: x.id, name: x.name, pathApi: x.pathApi, methodApi: x.methodApi, })), amount: _data.apiHistorys.length, })); return new HttpSuccess(data); } private async buildOrgNameBatch(apiKeys: ApiKey[]): Promise> { const currentRevision = await this.orgRevisionRepository.findOne({ where: { orgRevisionIsCurrent: true, orgRevisionIsDraft: false }, }); if (!currentRevision) { return new Map(apiKeys.map((k) => [k.id, null])); } const currentRevisionId = currentRevision.id; const rootIds = [...new Set(apiKeys.map((k) => k.dnaRootId).filter(Boolean))]; const child1Ids = [...new Set(apiKeys.map((k) => k.dnaChild1Id).filter(Boolean))]; const child2Ids = [...new Set(apiKeys.map((k) => k.dnaChild2Id).filter(Boolean))]; const child3Ids = [...new Set(apiKeys.map((k) => k.dnaChild3Id).filter(Boolean))]; const child4Ids = [...new Set(apiKeys.map((k) => k.dnaChild4Id).filter(Boolean))]; const [roots, child1s, child2s, child3s, child4s] = await Promise.all([ rootIds.length > 0 ? this.orgRootRepository.find({ where: [ { id: In(rootIds), orgRevisionId: currentRevisionId }, { ancestorDNA: In(rootIds), orgRevisionId: currentRevisionId }, ], select: ["id", "ancestorDNA", "orgRootName"], }) : [], child1Ids.length > 0 ? this.orgChild1Repository.find({ where: [ { id: In(child1Ids), orgRevisionId: currentRevisionId }, { ancestorDNA: In(child1Ids), orgRevisionId: currentRevisionId }, ], select: ["id", "ancestorDNA", "orgChild1Name"], }) : [], child2Ids.length > 0 ? this.orgChild2Repository.find({ where: [ { id: In(child2Ids), orgRevisionId: currentRevisionId }, { ancestorDNA: In(child2Ids), orgRevisionId: currentRevisionId }, ], select: ["id", "ancestorDNA", "orgChild2Name"], }) : [], child3Ids.length > 0 ? this.orgChild3Repository.find({ where: [ { id: In(child3Ids), orgRevisionId: currentRevisionId }, { ancestorDNA: In(child3Ids), orgRevisionId: currentRevisionId }, ], select: ["id", "ancestorDNA", "orgChild3Name"], }) : [], child4Ids.length > 0 ? this.orgChild4Repository.find({ where: [ { id: In(child4Ids), orgRevisionId: currentRevisionId }, { ancestorDNA: In(child4Ids), orgRevisionId: currentRevisionId }, ], select: ["id", "ancestorDNA", "orgChild4Name"], }) : [], ]); const rootMap = new Map( roots.map((r) => [r.id, { name: r.orgRootName, ancestorDNA: r.ancestorDNA }]), ); const child1Map = new Map( child1s.map((c) => [c.id, { name: c.orgChild1Name, ancestorDNA: c.ancestorDNA }]), ); const child2Map = new Map( child2s.map((c) => [c.id, { name: c.orgChild2Name, ancestorDNA: c.ancestorDNA }]), ); const child3Map = new Map( child3s.map((c) => [c.id, { name: c.orgChild3Name, ancestorDNA: c.ancestorDNA }]), ); const child4Map = new Map( child4s.map((c) => [c.id, { name: c.orgChild4Name, ancestorDNA: c.ancestorDNA }]), ); const result = new Map(); for (const apiKey of apiKeys) { if (apiKey.accessType === "ALL") { result.set(apiKey.id, null); continue; } const parts: string[] = []; const getOrgName = ( dnaId: string, orgMap: Map, ): string | null => { const byId = orgMap.get(dnaId); if (byId) return byId.name; for (const [, value] of orgMap) { if (value.ancestorDNA === dnaId) return value.name; } return null; }; if (apiKey.dnaChild4Id) { const name = getOrgName(apiKey.dnaChild4Id, child4Map); if (name) parts.push(name); } if (apiKey.dnaChild3Id) { const name = getOrgName(apiKey.dnaChild3Id, child3Map); if (name) parts.push(name); } if (apiKey.dnaChild2Id) { const name = getOrgName(apiKey.dnaChild2Id, child2Map); if (name) parts.push(name); } if (apiKey.dnaChild1Id) { const name = getOrgName(apiKey.dnaChild1Id, child1Map); if (name) parts.push(name); } if (apiKey.dnaRootId) { const name = getOrgName(apiKey.dnaRootId, rootMap); if (name) parts.push(name); } result.set(apiKey.id, parts.length > 0 ? parts.join(" ") : null); } return result; } /** * API รายการ Api Name * * @summary รายการ Api Name (ADMIN) * */ @Get("name") async listApiName() { const apiName = await this.apiNameRepository.find({ where: { isActive: true }, order: { createdAt: "DESC" }, }); return new HttpSuccess(apiName); } /** * API สร้าง Api Key * * @summary สร้าง Api Key (ADMIN) * */ @Post("history") async getHistory( @Body() requestBody: { startDate: Date; endDate: Date; apiNameId: string | null; }, @Request() request: RequestWithUser, ) { if (requestBody.apiNameId) { const apiName = await this.apiNameRepository.findOne({ where: { id: requestBody.apiNameId }, }); if (!apiName) throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบรายการ Web Service นี้ในระบบ"); } const apiHistory = await AppDataSource.getRepository(ApiHistory) .createQueryBuilder("apiHistory") .leftJoinAndSelect("apiHistory.apiKey", "apiKey") .leftJoinAndSelect("apiHistory.apiName", "apiName") .andWhere(requestBody.apiNameId ? `apiHistory.apiNameId = :apiNameId` : "1=1", { apiNameId: requestBody.apiNameId, }) .andWhere( `apiHistory.createdAt >= DATE(:startDate) AND apiHistory.createdAt <= DATE(:endDate)`, { startDate: new Date(requestBody.startDate), endDate: new Date( requestBody.endDate.setDate(new Date(requestBody.endDate).getDate() + 1), ), }, ) .orderBy("apiHistory.createdAt", "DESC") .getMany(); const result = apiHistory.map((x) => ({ id: x.id, apiName: x.apiName.name, apiKey: x.apiKey.name, createdAt: x.createdAt, ipApi: x.ipApi, })); return new HttpSuccess(result); } }