All checks were successful
Build & Deploy on Dev / build (push) Successful in 1m4s
388 lines
13 KiB
TypeScript
388 lines
13 KiB
TypeScript
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<Map<string, string | null>> {
|
|
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<string, string | null>();
|
|
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, { name: string; ancestorDNA: string }>,
|
|
): 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);
|
|
}
|
|
}
|