Merge branch 'mhkim-node' of http://39.117.244.52:3000/kjs/ERP-node into jskim-node

This commit is contained in:
kjs
2026-03-26 09:30:17 +09:00
parent 348da95823
commit 1bf91bf043
5 changed files with 696 additions and 625 deletions

View File

@@ -191,18 +191,30 @@ export const getLangKeys = async (
): Promise<void> => {
try {
const { companyCode, menuCode, keyType, searchText, categoryId } = req.query;
const userCompanyCode = req.user?.companyCode;
logger.info("다국어 키 목록 조회 요청", {
query: req.query,
user: req.user,
});
// company_code 필터링: 비관리자는 자기 회사 + 공통(*) 키만 조회 가능
let effectiveCompanyCode = companyCode as string;
if (userCompanyCode !== "*") {
// 비관리자가 다른 회사의 데이터를 요청하면 자기 회사로 제한
if (companyCode && companyCode !== userCompanyCode && companyCode !== "*") {
effectiveCompanyCode = userCompanyCode || "";
}
}
const multiLangService = new MultiLangService();
const langKeys = await multiLangService.getLangKeys({
companyCode: companyCode as string,
companyCode: effectiveCompanyCode,
menuCode: menuCode as string,
keyType: keyType as string,
searchText: searchText as string,
categoryId: categoryId ? parseInt(categoryId as string, 10) : undefined,
// 비관리자: companyCode 필터가 없으면 자기 회사 + 공통(*) 키만 반환
userCompanyCode: userCompanyCode,
});
const response: ApiResponse<any[]> = {
@@ -235,9 +247,24 @@ export const getLangTexts = async (
): Promise<void> => {
try {
const { keyId } = req.params;
const userCompanyCode = req.user?.companyCode;
logger.info("다국어 텍스트 조회 요청", { keyId, user: req.user });
const multiLangService = new MultiLangService();
// 비관리자: 해당 키가 자기 회사 또는 공통(*) 키인지 검증
if (userCompanyCode !== "*") {
const keyOwner = await multiLangService.getKeyCompanyCode(parseInt(keyId));
if (keyOwner && keyOwner !== "*" && keyOwner !== userCompanyCode) {
res.status(403).json({
success: false,
message: "다른 회사의 다국어 키에 접근할 권한이 없습니다.",
error: { code: "PERMISSION_DENIED" },
});
return;
}
}
const langTexts = await multiLangService.getLangTexts(parseInt(keyId));
const response: ApiResponse<any[]> = {
@@ -270,6 +297,7 @@ export const createLangKey = async (
): Promise<void> => {
try {
const keyData: CreateLangKeyRequest = req.body;
const userCompanyCode = req.user?.companyCode;
logger.info("다국어 키 생성 요청", { keyData, user: req.user });
// 필수 입력값 검증
@@ -285,6 +313,26 @@ export const createLangKey = async (
return;
}
// 권한 검사: 공통 키(*)는 최고 관리자만 생성 가능
if (keyData.companyCode === "*" && userCompanyCode !== "*") {
res.status(403).json({
success: false,
message: "공통 키는 최고 관리자만 생성할 수 있습니다.",
error: { code: "PERMISSION_DENIED" },
});
return;
}
// 비관리자: 자기 회사 키만 생성 가능
if (userCompanyCode !== "*" && keyData.companyCode !== userCompanyCode) {
res.status(403).json({
success: false,
message: "다른 회사의 키를 생성할 권한이 없습니다.",
error: { code: "PERMISSION_DENIED" },
});
return;
}
const multiLangService = new MultiLangService();
const keyId = await multiLangService.createLangKey({
...keyData,
@@ -323,10 +371,33 @@ export const updateLangKey = async (
try {
const { keyId } = req.params;
const keyData: UpdateLangKeyRequest = req.body;
const userCompanyCode = req.user?.companyCode;
logger.info("다국어 키 수정 요청", { keyId, keyData, user: req.user });
const multiLangService = new MultiLangService();
// 비관리자: 해당 키가 자기 회사 키인지 검증 (공통 키는 수정 불가)
if (userCompanyCode !== "*") {
const keyOwner = await multiLangService.getKeyCompanyCode(parseInt(keyId));
if (!keyOwner) {
res.status(404).json({
success: false,
message: "다국어 키를 찾을 수 없습니다.",
error: { code: "KEY_NOT_FOUND" },
});
return;
}
if (keyOwner !== userCompanyCode) {
res.status(403).json({
success: false,
message: "다른 회사의 다국어 키를 수정할 권한이 없습니다.",
error: { code: "PERMISSION_DENIED" },
});
return;
}
}
await multiLangService.updateLangKey(parseInt(keyId), {
...keyData,
updatedBy: req.user?.userId || "system",
@@ -362,9 +433,32 @@ export const deleteLangKey = async (
): Promise<void> => {
try {
const { keyId } = req.params;
const userCompanyCode = req.user?.companyCode;
logger.info("다국어 키 삭제 요청", { keyId, user: req.user });
const multiLangService = new MultiLangService();
// 비관리자: 해당 키가 자기 회사 키인지 검증 (공통 키 삭제 불가)
if (userCompanyCode !== "*") {
const keyOwner = await multiLangService.getKeyCompanyCode(parseInt(keyId));
if (!keyOwner) {
res.status(404).json({
success: false,
message: "다국어 키를 찾을 수 없습니다.",
error: { code: "KEY_NOT_FOUND" },
});
return;
}
if (keyOwner !== userCompanyCode) {
res.status(403).json({
success: false,
message: "다른 회사의 다국어 키를 삭제할 권한이 없습니다.",
error: { code: "PERMISSION_DENIED" },
});
return;
}
}
await multiLangService.deleteLangKey(parseInt(keyId));
const response: ApiResponse<string> = {
@@ -397,9 +491,32 @@ export const toggleLangKey = async (
): Promise<void> => {
try {
const { keyId } = req.params;
const userCompanyCode = req.user?.companyCode;
logger.info("다국어 키 상태 토글 요청", { keyId, user: req.user });
const multiLangService = new MultiLangService();
// 비관리자: 해당 키가 자기 회사 키인지 검증
if (userCompanyCode !== "*") {
const keyOwner = await multiLangService.getKeyCompanyCode(parseInt(keyId));
if (!keyOwner) {
res.status(404).json({
success: false,
message: "다국어 키를 찾을 수 없습니다.",
error: { code: "KEY_NOT_FOUND" },
});
return;
}
if (keyOwner !== userCompanyCode) {
res.status(403).json({
success: false,
message: "다른 회사의 다국어 키를 변경할 권한이 없습니다.",
error: { code: "PERMISSION_DENIED" },
});
return;
}
}
const result = await multiLangService.toggleLangKey(parseInt(keyId));
const response: ApiResponse<string> = {
@@ -433,6 +550,7 @@ export const saveLangTexts = async (
try {
const { keyId } = req.params;
const textData: SaveLangTextsRequest = req.body;
const userCompanyCode = req.user?.companyCode;
logger.info("다국어 텍스트 저장 요청", { keyId, textData, user: req.user });
@@ -454,6 +572,28 @@ export const saveLangTexts = async (
}
const multiLangService = new MultiLangService();
// 비관리자: 해당 키가 자기 회사 또는 공통(*) 키인지 검증
if (userCompanyCode !== "*") {
const keyOwner = await multiLangService.getKeyCompanyCode(parseInt(keyId));
if (!keyOwner) {
res.status(404).json({
success: false,
message: "다국어 키를 찾을 수 없습니다.",
error: { code: "KEY_NOT_FOUND" },
});
return;
}
if (keyOwner !== userCompanyCode) {
res.status(403).json({
success: false,
message: "다른 회사의 다국어 텍스트를 수정할 권한이 없습니다.",
error: { code: "PERMISSION_DENIED" },
});
return;
}
}
await multiLangService.saveLangTexts(parseInt(keyId), {
texts: textData.texts.map((text) => ({
...text,

View File

@@ -673,6 +673,22 @@ export class MultiLangService {
}
}
/**
* 키의 소유 회사 코드 조회 (권한 검증용)
*/
async getKeyCompanyCode(keyId: number): Promise<string | null> {
try {
const result = await queryOne<{ company_code: string }>(
`SELECT company_code FROM multi_lang_key_master WHERE key_id = $1`,
[keyId]
);
return result?.company_code || null;
} catch (error) {
logger.error("키 소유 회사 코드 조회 실패:", error);
return null;
}
}
/**
* 다국어 키 목록 조회
*/
@@ -688,6 +704,10 @@ export class MultiLangService {
if (params.companyCode) {
whereConditions.push(`company_code = $${paramIndex++}`);
values.push(params.companyCode);
} else if (params.userCompanyCode && params.userCompanyCode !== "*") {
// 비관리자: companyCode 필터가 없으면 자기 회사 + 공통(*) 키만 반환
whereConditions.push(`company_code IN ($${paramIndex++}, '*')`);
values.push(params.userCompanyCode);
}
// 메뉴 코드 필터

View File

@@ -140,6 +140,7 @@ export interface GetLangKeysParams {
includeOverrides?: boolean;
page?: number;
limit?: number;
userCompanyCode?: string; // 요청 사용자의 회사 코드 (비관리자 필터링용)
}
export interface GetUserTextParams {