전체 코드, 구조 검토 - P0 코드 수정 진행
This commit is contained in:
@@ -58,6 +58,12 @@ function onQuery(e: Prisma.QueryEvent): void {
|
||||
|
||||
const COL_MAX_WIDTH = 40;
|
||||
|
||||
const SENSITIVE_KEYS = new Set(["Password", "password"]);
|
||||
|
||||
function safeValue(key: string, value: unknown): string {
|
||||
return SENSITIVE_KEYS.has(key) ? "***" : formatValue(value);
|
||||
}
|
||||
|
||||
function formatValue(v: unknown): string {
|
||||
if (v === null || v === undefined) return "NULL";
|
||||
if (typeof v === "bigint") return v.toString();
|
||||
@@ -165,7 +171,7 @@ function buildResultLog(
|
||||
if (typeof preview[0] === "object" && preview[0] !== null) {
|
||||
const objRows = preview as Record<string, unknown>[];
|
||||
const headers = Object.keys(objRows[0]);
|
||||
const rows = objRows.map((row) => headers.map((h) => formatValue(row[h])));
|
||||
const rows = objRows.map((row) => headers.map((h) => safeValue(h, row[h])));
|
||||
return `${label} → ${countLabel}\n${renderTable(headers, rows)}${moreLabel}`;
|
||||
}
|
||||
|
||||
@@ -176,7 +182,7 @@ function buildResultLog(
|
||||
if (result !== null && typeof result === "object") {
|
||||
const rows = Object.entries(result as Record<string, unknown>).map(([k, v]) => [
|
||||
k,
|
||||
formatValue(v),
|
||||
safeValue(k, v),
|
||||
]);
|
||||
return `${label}\n${renderTable(["field", "value"], rows)}`;
|
||||
}
|
||||
|
||||
+64
-41
@@ -3,7 +3,7 @@ import { z } from "zod";
|
||||
import { db } from "@/lib/db";
|
||||
import { logger } from "@/lib/logger";
|
||||
import { ApiError } from "@/lib/errors";
|
||||
import { PAID_ACCOUNT_TYPE_VALUES, REQUEST_STATUSES } from "@/lib/constants";
|
||||
import { ACTIVATE_STATUSES, PAID_ACCOUNT_TYPE_VALUES, REQUEST_STATUSES } from "@/lib/constants";
|
||||
import type { Prisma } from "@/lib/generated/prisma/client";
|
||||
import {
|
||||
calculateExtendedAvailableDate,
|
||||
@@ -114,45 +114,70 @@ export async function approveExtensionRequest(
|
||||
maestroEmail: string;
|
||||
}> {
|
||||
return db.$transaction(async (tx) => {
|
||||
const request = await getActionTarget(tx, maestroExtensionID);
|
||||
// Atomically claim the request — prevents concurrent double-approval
|
||||
const claimed = await tx.maestro_extension.updateMany({
|
||||
where: { MaestroExtensionID: maestroExtensionID, Status: REQUEST_STATUSES.REQUESTED },
|
||||
data: { Status: REQUEST_STATUSES.APPLIED },
|
||||
});
|
||||
if (claimed.count === 0) {
|
||||
const exists = await tx.maestro_extension.count({
|
||||
where: { MaestroExtensionID: maestroExtensionID },
|
||||
});
|
||||
throw new ApiError(
|
||||
exists ? "이미 처리된 연장 신청입니다." : "연장 신청을 찾을 수 없습니다.",
|
||||
exists ? 409 : 404
|
||||
);
|
||||
}
|
||||
|
||||
const request = await tx.maestro_extension.findUnique({
|
||||
where: { MaestroExtensionID: maestroExtensionID },
|
||||
select: {
|
||||
MaestroID: true,
|
||||
AccountType: true,
|
||||
maestro: {
|
||||
select: {
|
||||
Name: true,
|
||||
Email: true,
|
||||
AvailableActivateDateTime: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const availableActivateDateTime = calculateExtendedAvailableDate(
|
||||
request.maestro.AvailableActivateDateTime
|
||||
request!.maestro.AvailableActivateDateTime
|
||||
);
|
||||
|
||||
await tx.maestro.update({
|
||||
where: { MaestroID: request.MaestroID },
|
||||
where: { MaestroID: request!.MaestroID },
|
||||
data: {
|
||||
ActivateStatus: 2,
|
||||
ActivateStatus: ACTIVATE_STATUSES.ACTIVE,
|
||||
AvailableActivateDateTime: availableActivateDateTime,
|
||||
},
|
||||
});
|
||||
await tx.maestro_extension.updateMany({
|
||||
where: {
|
||||
MaestroID: request.MaestroID,
|
||||
MaestroID: request!.MaestroID,
|
||||
Status: REQUEST_STATUSES.REQUESTED,
|
||||
},
|
||||
data: { Status: REQUEST_STATUSES.CLOSED },
|
||||
});
|
||||
await tx.maestro_extension.update({
|
||||
where: { MaestroExtensionID: request.MaestroExtensionID },
|
||||
data: { Status: REQUEST_STATUSES.APPLIED },
|
||||
});
|
||||
await tx.maestro_log.create({
|
||||
data: {
|
||||
Type: "extension_maestro",
|
||||
MaestroID: request.MaestroID,
|
||||
MaestroID: request!.MaestroID,
|
||||
LogDateTime: new Date(),
|
||||
Remark: buildExtensionLogRemark(
|
||||
request.maestro.Name,
|
||||
request.AccountType
|
||||
request!.maestro.Name,
|
||||
request!.AccountType
|
||||
),
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
availableActivateDateTime: availableActivateDateTime.toISOString(),
|
||||
maestroName: request.maestro.Name.trim(),
|
||||
maestroEmail: request.maestro.Email.trim(),
|
||||
maestroName: request!.maestro.Name.trim(),
|
||||
maestroEmail: request!.maestro.Email.trim(),
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -198,20 +223,37 @@ export async function cancelExtensionRequest(
|
||||
maestroExtensionID: number
|
||||
): Promise<void> {
|
||||
await db.$transaction(async (tx) => {
|
||||
const request = await getActionTarget(tx, maestroExtensionID);
|
||||
|
||||
await tx.maestro_extension.update({
|
||||
where: { MaestroExtensionID: request.MaestroExtensionID },
|
||||
const claimed = await tx.maestro_extension.updateMany({
|
||||
where: { MaestroExtensionID: maestroExtensionID, Status: REQUEST_STATUSES.REQUESTED },
|
||||
data: { Status: REQUEST_STATUSES.CLOSED },
|
||||
});
|
||||
if (claimed.count === 0) {
|
||||
const exists = await tx.maestro_extension.count({
|
||||
where: { MaestroExtensionID: maestroExtensionID },
|
||||
});
|
||||
throw new ApiError(
|
||||
exists ? "이미 처리된 연장 신청입니다." : "연장 신청을 찾을 수 없습니다.",
|
||||
exists ? 409 : 404
|
||||
);
|
||||
}
|
||||
|
||||
const request = await tx.maestro_extension.findUnique({
|
||||
where: { MaestroExtensionID: maestroExtensionID },
|
||||
select: {
|
||||
MaestroID: true,
|
||||
AccountType: true,
|
||||
maestro: { select: { Name: true } },
|
||||
},
|
||||
});
|
||||
|
||||
await tx.maestro_log.create({
|
||||
data: {
|
||||
Type: "cancel_extension_maestro",
|
||||
MaestroID: request.MaestroID,
|
||||
MaestroID: request!.MaestroID,
|
||||
LogDateTime: new Date(),
|
||||
Remark: buildExtensionLogRemark(
|
||||
request.maestro.Name,
|
||||
request.AccountType
|
||||
request!.maestro.Name,
|
||||
request!.AccountType
|
||||
),
|
||||
},
|
||||
});
|
||||
@@ -226,25 +268,6 @@ export function parseExtensionRequestSearchParams(
|
||||
);
|
||||
}
|
||||
|
||||
async function getActionTarget(
|
||||
tx: Prisma.TransactionClient,
|
||||
maestroExtensionID: number
|
||||
) {
|
||||
const request = await tx.maestro_extension.findUnique({
|
||||
where: { MaestroExtensionID: maestroExtensionID },
|
||||
include: { maestro: true },
|
||||
});
|
||||
|
||||
if (!request) {
|
||||
throw new ApiError("연장 신청을 찾을 수 없습니다.", 404);
|
||||
}
|
||||
|
||||
if (request.Status !== REQUEST_STATUSES.REQUESTED) {
|
||||
throw new ApiError("이미 처리된 연장 신청입니다.", 409);
|
||||
}
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
function buildExtensionRequestWhere(
|
||||
params: ExtensionRequestSearchParams
|
||||
|
||||
+3
-5
@@ -5,6 +5,7 @@ import { logger } from "@/lib/logger";
|
||||
import { ApiError } from "@/lib/errors";
|
||||
import { ACTIVATE_STATUSES, PAID_ACCOUNT_TYPE_VALUES } from "@/lib/constants";
|
||||
import type { Prisma } from "@/lib/generated/prisma/client";
|
||||
import { formatDate } from "@/lib/utils";
|
||||
|
||||
const pageSizeOptions = [10, 20, 50, 100] as const;
|
||||
const sortOptions = [
|
||||
@@ -213,11 +214,8 @@ export async function updateMaestro(
|
||||
const newMin = Math.floor(newDate.getTime() / 60000);
|
||||
if (prevMin !== newMin) {
|
||||
updates.AvailableActivateDateTime = newDate;
|
||||
const prevStr = maestro.AvailableActivateDateTime.toISOString().slice(
|
||||
0,
|
||||
10
|
||||
);
|
||||
const newStr = newDate.toISOString().slice(0, 10);
|
||||
const prevStr = formatDate(maestro.AvailableActivateDateTime);
|
||||
const newStr = formatDate(newDate);
|
||||
logs.push({
|
||||
type: "update_maestro_available_date",
|
||||
remark: `${prevStr} -> ${newStr}`,
|
||||
|
||||
+64
-50
@@ -121,48 +121,69 @@ export async function approveUpgradeRequest(
|
||||
requestedAccountType: number;
|
||||
}> {
|
||||
return db.$transaction(async (tx) => {
|
||||
const request = await getActionTarget(tx, maestroUpgradeID);
|
||||
// Atomically claim the request — prevents concurrent double-approval
|
||||
const claimed = await tx.maestro_upgrade.updateMany({
|
||||
where: { MaestroUpgradeID: maestroUpgradeID, Status: REQUEST_STATUSES.REQUESTED },
|
||||
data: { Status: REQUEST_STATUSES.APPLIED },
|
||||
});
|
||||
if (claimed.count === 0) {
|
||||
const exists = await tx.maestro_upgrade.count({
|
||||
where: { MaestroUpgradeID: maestroUpgradeID },
|
||||
});
|
||||
throw new ApiError(
|
||||
exists ? "이미 처리된 업그레이드 신청입니다." : "업그레이드 신청을 찾을 수 없습니다.",
|
||||
exists ? 409 : 404
|
||||
);
|
||||
}
|
||||
|
||||
const request = await tx.maestro_upgrade.findUnique({
|
||||
where: { MaestroUpgradeID: maestroUpgradeID },
|
||||
select: {
|
||||
MaestroID: true,
|
||||
RequestedAccountType: true,
|
||||
RegisteredActivateStatus: true,
|
||||
RegisteredAccountType: true,
|
||||
maestro: { select: { Name: true, Email: true } },
|
||||
},
|
||||
});
|
||||
|
||||
const availableActivateDateTime = calculateUpgradeAvailableDate();
|
||||
|
||||
await tx.maestro.update({
|
||||
where: { MaestroID: request.MaestroID },
|
||||
where: { MaestroID: request!.MaestroID },
|
||||
data: {
|
||||
AccountType: request.RequestedAccountType,
|
||||
AccountType: request!.RequestedAccountType,
|
||||
ActivateStatus: ACTIVATE_STATUSES.ACTIVE,
|
||||
AvailableActivateDateTime: availableActivateDateTime,
|
||||
},
|
||||
});
|
||||
await tx.maestro_upgrade.updateMany({
|
||||
where: {
|
||||
MaestroID: request.MaestroID,
|
||||
MaestroID: request!.MaestroID,
|
||||
Status: REQUEST_STATUSES.REQUESTED,
|
||||
},
|
||||
data: { Status: REQUEST_STATUSES.CLOSED },
|
||||
});
|
||||
await tx.maestro_upgrade.update({
|
||||
where: { MaestroUpgradeID: request.MaestroUpgradeID },
|
||||
data: { Status: REQUEST_STATUSES.APPLIED },
|
||||
});
|
||||
await tx.maestro_log.create({
|
||||
data: {
|
||||
Type: "upgrade_maestro",
|
||||
MaestroID: request.MaestroID,
|
||||
MaestroID: request!.MaestroID,
|
||||
LogDateTime: new Date(),
|
||||
Remark: buildUpgradeLogRemark(
|
||||
request.RegisteredActivateStatus,
|
||||
request.RegisteredAccountType,
|
||||
request.RequestedAccountType
|
||||
request!.RegisteredActivateStatus,
|
||||
request!.RegisteredAccountType,
|
||||
request!.RequestedAccountType
|
||||
),
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
availableActivateDateTime: availableActivateDateTime.toISOString(),
|
||||
maestroName: request.maestro.Name.trim(),
|
||||
maestroEmail: request.maestro.Email.trim(),
|
||||
registeredActivateStatus: request.RegisteredActivateStatus,
|
||||
registeredAccountType: request.RegisteredAccountType,
|
||||
requestedAccountType: request.RequestedAccountType,
|
||||
maestroName: request!.maestro.Name.trim(),
|
||||
maestroEmail: request!.maestro.Email.trim(),
|
||||
registeredActivateStatus: request!.RegisteredActivateStatus,
|
||||
registeredAccountType: request!.RegisteredAccountType,
|
||||
requestedAccountType: request!.RequestedAccountType,
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -215,21 +236,39 @@ export async function rejectUpgradeRequest(
|
||||
maestroUpgradeID: number
|
||||
): Promise<void> {
|
||||
await db.$transaction(async (tx) => {
|
||||
const request = await getActionTarget(tx, maestroUpgradeID);
|
||||
|
||||
await tx.maestro_upgrade.update({
|
||||
where: { MaestroUpgradeID: request.MaestroUpgradeID },
|
||||
const claimed = await tx.maestro_upgrade.updateMany({
|
||||
where: { MaestroUpgradeID: maestroUpgradeID, Status: REQUEST_STATUSES.REQUESTED },
|
||||
data: { Status: REQUEST_STATUSES.CLOSED },
|
||||
});
|
||||
if (claimed.count === 0) {
|
||||
const exists = await tx.maestro_upgrade.count({
|
||||
where: { MaestroUpgradeID: maestroUpgradeID },
|
||||
});
|
||||
throw new ApiError(
|
||||
exists ? "이미 처리된 업그레이드 신청입니다." : "업그레이드 신청을 찾을 수 없습니다.",
|
||||
exists ? 409 : 404
|
||||
);
|
||||
}
|
||||
|
||||
const request = await tx.maestro_upgrade.findUnique({
|
||||
where: { MaestroUpgradeID: maestroUpgradeID },
|
||||
select: {
|
||||
MaestroID: true,
|
||||
RegisteredActivateStatus: true,
|
||||
RegisteredAccountType: true,
|
||||
RequestedAccountType: true,
|
||||
},
|
||||
});
|
||||
|
||||
await tx.maestro_log.create({
|
||||
data: {
|
||||
Type: "reject_upgrade_maestro",
|
||||
MaestroID: request.MaestroID,
|
||||
MaestroID: request!.MaestroID,
|
||||
LogDateTime: new Date(),
|
||||
Remark: buildUpgradeLogRemark(
|
||||
request.RegisteredActivateStatus,
|
||||
request.RegisteredAccountType,
|
||||
request.RequestedAccountType
|
||||
request!.RegisteredActivateStatus,
|
||||
request!.RegisteredAccountType,
|
||||
request!.RequestedAccountType
|
||||
),
|
||||
},
|
||||
});
|
||||
@@ -244,31 +283,6 @@ export function parseUpgradeRequestSearchParams(
|
||||
);
|
||||
}
|
||||
|
||||
async function getActionTarget(
|
||||
tx: Prisma.TransactionClient,
|
||||
maestroUpgradeID: number
|
||||
) {
|
||||
const request = await tx.maestro_upgrade.findUnique({
|
||||
where: { MaestroUpgradeID: maestroUpgradeID },
|
||||
include: { maestro: { select: { Name: true, Email: true } } },
|
||||
});
|
||||
|
||||
if (!request) {
|
||||
throw new ApiError(
|
||||
"업그레이드 신청을 찾을 수 없습니다.",
|
||||
404
|
||||
);
|
||||
}
|
||||
|
||||
if (request.Status !== REQUEST_STATUSES.REQUESTED) {
|
||||
throw new ApiError(
|
||||
"이미 처리된 업그레이드 신청입니다.",
|
||||
409
|
||||
);
|
||||
}
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
function buildUpgradeRequestWhere(
|
||||
params: UpgradeRequestSearchParams
|
||||
|
||||
Reference in New Issue
Block a user