테이블 추가기능 수정사항

This commit is contained in:
kjs
2025-09-23 10:40:21 +09:00
parent 474cc33aee
commit e653effac0
19 changed files with 1931 additions and 201 deletions

View File

@@ -42,11 +42,17 @@ export class DDLController {
ip: req.ip,
});
// inputType을 webType으로 변환 (레거시 호환성)
const processedColumns = columns.map((col) => ({
...col,
webType: (col.inputType || col.webType || "text") as any,
}));
// DDL 실행 서비스 호출
const ddlService = new DDLExecutionService();
const result = await ddlService.createTable(
tableName,
columns,
processedColumns,
userCompanyCode,
userId,
description
@@ -112,12 +118,12 @@ export class DDLController {
return;
}
if (!column || !column.name || !column.webType) {
if (!column || !column.name || (!column.inputType && !column.webType)) {
res.status(400).json({
success: false,
error: {
code: "INVALID_INPUT",
details: "컬럼명과 타입이 필요합니다.",
details: "컬럼명과 입력타입이 필요합니다.",
},
});
return;
@@ -131,11 +137,17 @@ export class DDLController {
ip: req.ip,
});
// inputType을 webType으로 변환 (레거시 호환성)
const processedColumn = {
...column,
webType: (column.inputType || column.webType || "text") as any,
};
// DDL 실행 서비스 호출
const ddlService = new DDLExecutionService();
const result = await ddlService.addColumn(
tableName,
column,
processedColumn,
userCompanyCode,
userId
);

View File

@@ -443,24 +443,24 @@ export async function updateTableLabel(
}
/**
* 컬럼 타입 설정
* 컬럼 입력 타입 설정
*/
export async function updateColumnWebType(
export async function updateColumnInputType(
req: AuthenticatedRequest,
res: Response
): Promise<void> {
try {
const { tableName, columnName } = req.params;
const { webType, detailSettings, inputType } = req.body;
const { inputType, detailSettings } = req.body;
logger.info(
`=== 컬럼 타입 설정 시작: ${tableName}.${columnName} = ${webType} ===`
`=== 컬럼 입력 타입 설정 시작: ${tableName}.${columnName} = ${inputType} ===`
);
if (!tableName || !columnName || !webType) {
if (!tableName || !columnName || !inputType) {
const response: ApiResponse<null> = {
success: false,
message: "테이블명, 컬럼명, 타입이 모두 필요합니다.",
message: "테이블명, 컬럼명, 입력 타입이 모두 필요합니다.",
error: {
code: "MISSING_PARAMETERS",
details: "필수 파라미터가 누락되었습니다.",
@@ -471,33 +471,32 @@ export async function updateColumnWebType(
}
const tableManagementService = new TableManagementService();
await tableManagementService.updateColumnWebType(
await tableManagementService.updateColumnInputType(
tableName,
columnName,
webType,
detailSettings,
inputType
inputType,
detailSettings
);
logger.info(
`컬럼 타입 설정 완료: ${tableName}.${columnName} = ${webType}`
`컬럼 입력 타입 설정 완료: ${tableName}.${columnName} = ${inputType}`
);
const response: ApiResponse<null> = {
success: true,
message: "컬럼 타입이 성공적으로 설정되었습니다.",
message: "컬럼 입력 타입이 성공적으로 설정되었습니다.",
data: null,
};
res.status(200).json(response);
} catch (error) {
logger.error("컬럼 타입 설정 중 오류 발생:", error);
logger.error("컬럼 입력 타입 설정 중 오류 발생:", error);
const response: ApiResponse<null> = {
success: false,
message: "컬럼 타입 설정 중 오류가 발생했습니다.",
message: "컬럼 입력 타입 설정 중 오류가 발생했습니다.",
error: {
code: "WEB_TYPE_UPDATE_ERROR",
code: "INPUT_TYPE_UPDATE_ERROR",
details: error instanceof Error ? error.message : "Unknown error",
},
};
@@ -866,16 +865,17 @@ export async function getColumnWebTypes(
}
const tableManagementService = new TableManagementService();
const webTypes = await tableManagementService.getColumnWebTypes(tableName);
const inputTypes =
await tableManagementService.getColumnInputTypes(tableName);
logger.info(
`컬럼 타입 정보 조회 완료: ${tableName}, ${webTypes.length}개 컬럼`
`컬럼 입력타입 정보 조회 완료: ${tableName}, ${inputTypes.length}개 컬럼`
);
const response: ApiResponse<ColumnTypeInfo[]> = {
success: true,
message: "컬럼 타입 정보를 성공적으로 조회했습니다.",
data: webTypes,
message: "컬럼 입력타입 정보를 성공적으로 조회했습니다.",
data: inputTypes,
};
res.status(200).json(response);
@@ -1010,3 +1010,41 @@ export async function deleteTableData(
res.status(500).json(response);
}
}
/**
* 컬럼 웹 타입 설정 (레거시 지원)
* @deprecated updateColumnInputType 사용 권장
*/
export async function updateColumnWebType(
req: AuthenticatedRequest,
res: Response
): Promise<void> {
try {
const { tableName, columnName } = req.params;
const { webType, detailSettings, inputType } = req.body;
logger.warn(
`레거시 API 사용: updateColumnWebType → updateColumnInputType 사용 권장`
);
// webType을 inputType으로 변환
const convertedInputType = inputType || webType || "text";
// 새로운 메서드 호출
req.body = { inputType: convertedInputType, detailSettings };
await updateColumnInputType(req, res);
} catch (error) {
logger.error("레거시 컬럼 웹 타입 설정 중 오류 발생:", error);
const response: ApiResponse<null> = {
success: false,
message: "컬럼 웹 타입 설정 중 오류가 발생했습니다.",
error: {
code: "WEB_TYPE_UPDATE_ERROR",
details: error instanceof Error ? error.message : "Unknown error",
},
};
res.status(500).json(response);
}
}

View File

@@ -8,6 +8,7 @@ import {
getTableLabels,
getColumnLabels,
updateColumnWebType,
updateColumnInputType,
updateTableLabel,
getTableData,
addTableData,
@@ -70,7 +71,7 @@ router.get("/tables/:tableName/labels", getTableLabels);
router.get("/tables/:tableName/columns/:columnName/labels", getColumnLabels);
/**
* 컬럼 웹 타입 설정
* 컬럼 웹 타입 설정 (레거시 지원)
* PUT /api/table-management/tables/:tableName/columns/:columnName/web-type
*/
router.put(
@@ -78,6 +79,15 @@ router.put(
updateColumnWebType
);
/**
* 컬럼 입력 타입 설정 (새로운 시스템)
* PUT /api/table-management/tables/:tableName/columns/:columnName/input-type
*/
router.put(
"/tables/:tableName/columns/:columnName/input-type",
updateColumnInputType
);
/**
* 개별 컬럼 설정 업데이트 (PUT 방식)
* PUT /api/table-management/tables/:tableName/columns/:columnName

View File

@@ -342,14 +342,11 @@ export class DDLExecutionService {
tableName: string,
columns: CreateColumnDefinition[]
): string {
// 사용자 정의 컬럼들
// 사용자 정의 컬럼들 - 모두 VARCHAR(500)로 통일
const columnDefinitions = columns
.map((col) => {
const postgresType = this.mapWebTypeToPostgresType(
col.webType,
col.length
);
let definition = `"${col.name}" ${postgresType}`;
// 입력 타입과 관계없이 모든 컬럼을 VARCHAR(500)로 생성
let definition = `"${col.name}" varchar(500)`;
if (!col.nullable) {
definition += " NOT NULL";
@@ -363,13 +360,13 @@ export class DDLExecutionService {
})
.join(",\n ");
// 기본 컬럼들 (시스템 필수 컬럼)
// 기본 컬럼들 (날짜는 TIMESTAMP, 나머지는 VARCHAR)
const baseColumns = `
"id" serial PRIMARY KEY,
"id" varchar(500) PRIMARY KEY DEFAULT gen_random_uuid()::text,
"created_date" timestamp DEFAULT now(),
"updated_date" timestamp DEFAULT now(),
"writer" varchar(100),
"company_code" varchar(50) DEFAULT '*'`;
"writer" varchar(500),
"company_code" varchar(500)`;
// 최종 CREATE TABLE 쿼리
return `
@@ -385,11 +382,8 @@ CREATE TABLE "${tableName}" (${baseColumns},
tableName: string,
column: CreateColumnDefinition
): string {
const postgresType = this.mapWebTypeToPostgresType(
column.webType,
column.length
);
let definition = `"${column.name}" ${postgresType}`;
// 새로 추가되는 컬럼도 VARCHAR(500)로 통일
let definition = `"${column.name}" varchar(500)`;
if (!column.nullable) {
definition += " NOT NULL";
@@ -403,23 +397,27 @@ CREATE TABLE "${tableName}" (${baseColumns},
}
/**
* 타입을 PostgreSQL 타입으로 매핑
* 입력타입을 PostgreSQL 타입으로 매핑 (날짜는 TIMESTAMP, 나머지는 VARCHAR)
* 날짜 타입만 TIMESTAMP로, 나머지는 VARCHAR(500)로 통일
*/
private mapInputTypeToPostgresType(inputType?: string): string {
switch (inputType) {
case "date":
return "timestamp";
default:
// 날짜 외의 모든 타입은 VARCHAR(500)로 통일
return "varchar(500)";
}
}
/**
* 레거시 지원: 웹타입을 PostgreSQL 타입으로 매핑
* @deprecated 새로운 시스템에서는 mapInputTypeToPostgresType 사용
*/
private mapWebTypeToPostgresType(webType: WebType, length?: number): string {
const mapping = WEB_TYPE_TO_POSTGRES_MAP[webType];
if (!mapping) {
logger.warn(`알 수 없는 웹타입: ${webType}, text로 대체`);
return "text";
}
if (mapping.supportsLength && length && length > 0) {
if (mapping.postgresType === "varchar") {
return `varchar(${length})`;
}
}
return mapping.postgresType;
// 레거시 지원을 위해 유지하되, VARCHAR(500)로 통일
logger.info(`레거시 웹타입 사용: ${webType} → varchar(500)로 변환`);
return "varchar(500)";
}
/**
@@ -472,6 +470,127 @@ CREATE TABLE "${tableName}" (${baseColumns},
},
});
// 기본 컬럼들 정의 (모든 테이블에 자동으로 추가되는 시스템 컬럼)
const defaultColumns = [
{
name: "id",
label: "ID",
inputType: "text",
description: "기본키 (자동생성)",
order: -5,
isVisible: true,
},
{
name: "created_date",
label: "생성일시",
inputType: "date",
description: "레코드 생성일시",
order: -4,
isVisible: true,
},
{
name: "updated_date",
label: "수정일시",
inputType: "date",
description: "레코드 수정일시",
order: -3,
isVisible: true,
},
{
name: "writer",
label: "작성자",
inputType: "text",
description: "레코드 작성자",
order: -2,
isVisible: true,
},
{
name: "company_code",
label: "회사코드",
inputType: "text",
description: "회사 구분 코드",
order: -1,
isVisible: true,
},
];
// 기본 컬럼들을 table_type_columns에 등록
for (const defaultCol of defaultColumns) {
await tx.$executeRaw`
INSERT INTO table_type_columns (
table_name, column_name, input_type, detail_settings,
is_nullable, display_order, created_date, updated_date
) VALUES (
${tableName}, ${defaultCol.name}, ${defaultCol.inputType}, '{}',
'Y', ${defaultCol.order}, now(), now()
)
ON CONFLICT (table_name, column_name)
DO UPDATE SET
input_type = ${defaultCol.inputType},
display_order = ${defaultCol.order},
updated_date = now();
`;
}
// 사용자 정의 컬럼들을 table_type_columns에 등록
for (let i = 0; i < columns.length; i++) {
const column = columns[i];
const inputType = this.convertWebTypeToInputType(
column.webType || "text"
);
await tx.$executeRaw`
INSERT INTO table_type_columns (
table_name, column_name, input_type, detail_settings,
is_nullable, display_order, created_date, updated_date
) VALUES (
${tableName}, ${column.name}, ${inputType}, ${JSON.stringify(column.detailSettings || {})},
'Y', ${i}, now(), now()
)
ON CONFLICT (table_name, column_name)
DO UPDATE SET
input_type = ${inputType},
detail_settings = ${JSON.stringify(column.detailSettings || {})},
display_order = ${i},
updated_date = now();
`;
}
// 레거시 지원: column_labels 테이블에도 등록 (기존 시스템 호환성)
// 1. 기본 컬럼들을 column_labels에 등록
for (const defaultCol of defaultColumns) {
await tx.column_labels.upsert({
where: {
table_name_column_name: {
table_name: tableName,
column_name: defaultCol.name,
},
},
update: {
column_label: defaultCol.label,
input_type: defaultCol.inputType,
detail_settings: JSON.stringify({}),
description: defaultCol.description,
display_order: defaultCol.order,
is_visible: defaultCol.isVisible,
updated_date: new Date(),
},
create: {
table_name: tableName,
column_name: defaultCol.name,
column_label: defaultCol.label,
input_type: defaultCol.inputType,
detail_settings: JSON.stringify({}),
description: defaultCol.description,
display_order: defaultCol.order,
is_visible: defaultCol.isVisible,
created_date: new Date(),
updated_date: new Date(),
},
});
}
// 2. 사용자 정의 컬럼들을 column_labels에 등록
for (const column of columns) {
await tx.column_labels.upsert({
where: {
@@ -482,7 +601,7 @@ CREATE TABLE "${tableName}" (${baseColumns},
},
update: {
column_label: column.label || column.name,
web_type: column.webType,
input_type: this.convertWebTypeToInputType(column.webType || "text"),
detail_settings: JSON.stringify(column.detailSettings || {}),
description: column.description,
display_order: column.order || 0,
@@ -493,7 +612,7 @@ CREATE TABLE "${tableName}" (${baseColumns},
table_name: tableName,
column_name: column.name,
column_label: column.label || column.name,
web_type: column.webType,
input_type: this.convertWebTypeToInputType(column.webType || "text"),
detail_settings: JSON.stringify(column.detailSettings || {}),
description: column.description,
display_order: column.order || 0,
@@ -505,6 +624,47 @@ CREATE TABLE "${tableName}" (${baseColumns},
}
}
/**
* 웹 타입을 입력 타입으로 변환
*/
private convertWebTypeToInputType(webType: string): string {
const webTypeToInputTypeMap: Record<string, string> = {
// 텍스트 관련
text: "text",
textarea: "text",
email: "text",
tel: "text",
url: "text",
password: "text",
// 숫자 관련
number: "number",
decimal: "number",
// 날짜 관련
date: "date",
datetime: "date",
time: "date",
// 선택 관련
select: "select",
dropdown: "select",
checkbox: "checkbox",
boolean: "checkbox",
radio: "radio",
// 참조 관련
code: "code",
entity: "entity",
// 기타
file: "text",
button: "text",
};
return webTypeToInputTypeMap[webType] || "text";
}
/**
* 권한 검증 (슈퍼관리자 확인)
*/

View File

@@ -256,11 +256,11 @@ export class DDLSafetyValidator {
if (column.length !== undefined) {
if (
!["text", "code", "email", "tel", "select", "radio"].includes(
column.webType
column.webType || "text"
)
) {
warnings.push(
`${prefix}${column.webType} 타입에서는 길이 설정이 무시됩니다.`
`${prefix}${column.webType || "text"} 타입에서는 길이 설정이 무시됩니다.`
);
} else if (column.length <= 0 || column.length > 65535) {
errors.push(`${prefix}길이는 1 이상 65535 이하여야 합니다.`);

View File

@@ -0,0 +1,282 @@
/**
* 입력 타입 처리 서비스
* VARCHAR 통일 방식에서 입력 타입별 형변환 및 검증 처리
*/
import { InputType } from "../types/input-types";
import { logger } from "../utils/logger";
export interface ValidationResult {
isValid: boolean;
message?: string;
convertedValue?: string;
}
export class InputTypeService {
/**
* 데이터 저장 전 형변환 (화면 입력값 → DB 저장값)
* 모든 값을 VARCHAR(500)에 저장하기 위해 문자열로 변환
*/
static convertForStorage(value: any, inputType: InputType): string {
if (value === null || value === undefined) {
return "";
}
try {
switch (inputType) {
case "text":
case "select":
case "radio":
return String(value).trim();
case "number":
if (value === "" || value === null || value === undefined) {
return "0";
}
const num = parseFloat(String(value));
return isNaN(num) ? "0" : String(num);
case "date":
if (!value || value === "") {
return "";
}
const date = new Date(value);
if (isNaN(date.getTime())) {
logger.warn(`Invalid date value: ${value}`);
return "";
}
return date.toISOString().split("T")[0]; // YYYY-MM-DD 형식
case "checkbox":
// 다양한 형태의 true 값을 "Y"로, 나머지는 "N"으로 변환
const truthyValues = ["true", "1", "Y", "yes", "on", true, 1];
return truthyValues.includes(value) ? "Y" : "N";
case "code":
case "entity":
return String(value || "").trim();
default:
return String(value);
}
} catch (error) {
logger.error(`Error converting value for storage: ${error}`, {
value,
inputType,
});
return String(value || "");
}
}
/**
* 화면 표시용 형변환 (DB 저장값 → 화면 표시값)
* VARCHAR에서 읽어온 문자열을 적절한 타입으로 변환
*/
static convertForDisplay(value: string, inputType: InputType): any {
if (!value && value !== "0") {
// 빈 값 처리
switch (inputType) {
case "number":
return 0;
case "checkbox":
return false;
default:
return "";
}
}
try {
switch (inputType) {
case "text":
case "select":
case "radio":
case "code":
case "entity":
return value;
case "number":
const num = parseFloat(value);
return isNaN(num) ? 0 : num;
case "date":
// YYYY-MM-DD 형식 그대로 반환 (HTML date input 호환)
return value;
case "checkbox":
return value === "Y" || value === "true" || value === "1";
default:
return value;
}
} catch (error) {
logger.error(`Error converting value for display: ${error}`, {
value,
inputType,
});
return value;
}
}
/**
* 입력값 검증
* 저장 전에 값이 해당 입력 타입에 적합한지 검증
*/
static validate(value: any, inputType: InputType): ValidationResult {
// 빈 값은 일반적으로 허용 (필수 여부는 별도 검증)
if (!value && value !== 0 && value !== false) {
return {
isValid: true,
convertedValue: this.convertForStorage(value, inputType),
};
}
try {
switch (inputType) {
case "text":
case "select":
case "radio":
case "code":
case "entity":
const strValue = String(value).trim();
if (strValue.length > 500) {
return {
isValid: false,
message: "입력값이 너무 깁니다. (최대 500자)",
};
}
return {
isValid: true,
convertedValue: this.convertForStorage(value, inputType),
};
case "number":
const num = parseFloat(String(value));
if (isNaN(num)) {
return {
isValid: false,
message: "숫자 형식이 올바르지 않습니다.",
};
}
return {
isValid: true,
convertedValue: this.convertForStorage(value, inputType),
};
case "date":
if (!value) {
return { isValid: true, convertedValue: "" };
}
const date = new Date(value);
if (isNaN(date.getTime())) {
return {
isValid: false,
message: "날짜 형식이 올바르지 않습니다.",
};
}
return {
isValid: true,
convertedValue: this.convertForStorage(value, inputType),
};
case "checkbox":
// 체크박스는 모든 값을 허용 (Y/N으로 변환)
return {
isValid: true,
convertedValue: this.convertForStorage(value, inputType),
};
default:
return {
isValid: true,
convertedValue: this.convertForStorage(value, inputType),
};
}
} catch (error) {
logger.error(`Error validating value: ${error}`, { value, inputType });
return {
isValid: false,
message: "값 검증 중 오류가 발생했습니다.",
};
}
}
/**
* 배치 데이터 변환 (여러 필드를 한번에 처리)
*/
static convertBatchForStorage(
data: Record<string, any>,
columnTypes: Record<string, InputType>
): Record<string, string> {
const converted: Record<string, string> = {};
for (const [columnName, value] of Object.entries(data)) {
const inputType = columnTypes[columnName];
if (inputType) {
converted[columnName] = this.convertForStorage(value, inputType);
} else {
// 입력 타입이 정의되지 않은 경우 기본적으로 text로 처리
converted[columnName] = this.convertForStorage(value, "text");
}
}
return converted;
}
/**
* 배치 데이터 표시용 변환
*/
static convertBatchForDisplay(
data: Record<string, string>,
columnTypes: Record<string, InputType>
): Record<string, any> {
const converted: Record<string, any> = {};
for (const [columnName, value] of Object.entries(data)) {
const inputType = columnTypes[columnName];
if (inputType) {
converted[columnName] = this.convertForDisplay(value, inputType);
} else {
// 입력 타입이 정의되지 않은 경우 문자열 그대로 반환
converted[columnName] = value;
}
}
return converted;
}
/**
* 배치 데이터 검증
*/
static validateBatch(
data: Record<string, any>,
columnTypes: Record<string, InputType>
): {
isValid: boolean;
errors: Record<string, string>;
convertedData: Record<string, string>;
} {
const errors: Record<string, string> = {};
const convertedData: Record<string, string> = {};
for (const [columnName, value] of Object.entries(data)) {
const inputType = columnTypes[columnName];
if (inputType) {
const result = this.validate(value, inputType);
if (!result.isValid) {
errors[columnName] = result.message || "검증 실패";
} else {
convertedData[columnName] = result.convertedValue || "";
}
} else {
// 입력 타입이 정의되지 않은 경우 기본적으로 text로 처리
convertedData[columnName] = this.convertForStorage(value, "text");
}
}
return {
isValid: Object.keys(errors).length === 0,
errors,
convertedData,
};
}
}

View File

@@ -329,7 +329,7 @@ export class TableManagementService {
},
update: {
column_label: settings.columnLabel,
web_type: settings.webType,
input_type: settings.inputType,
detail_settings: settings.detailSettings,
code_category: settings.codeCategory,
code_value: settings.codeValue,
@@ -345,7 +345,7 @@ export class TableManagementService {
table_name: tableName,
column_name: columnName,
column_label: settings.columnLabel,
web_type: settings.webType,
input_type: settings.inputType,
detail_settings: settings.detailSettings,
code_category: settings.codeCategory,
code_value: settings.codeValue,
@@ -626,7 +626,123 @@ export class TableManagementService {
}
/**
* 웹 타입별 기본 상세 설정 생성
* 컬럼 입력 타입 설정 (새로운 시스템)
*/
async updateColumnInputType(
tableName: string,
columnName: string,
inputType: string,
detailSettings?: Record<string, any>
): Promise<void> {
try {
logger.info(
`컬럼 입력 타입 설정 시작: ${tableName}.${columnName} = ${inputType}`
);
// 입력 타입별 기본 상세 설정 생성
const defaultDetailSettings =
this.generateDefaultInputTypeSettings(inputType);
// 사용자 정의 설정과 기본 설정 병합
const finalDetailSettings = {
...defaultDetailSettings,
...detailSettings,
};
// table_type_columns 테이블에서 업데이트
await prisma.$executeRaw`
INSERT INTO table_type_columns (
table_name, column_name, input_type, detail_settings,
is_nullable, display_order, created_date, updated_date
) VALUES (
${tableName}, ${columnName}, ${inputType}, ${JSON.stringify(finalDetailSettings)},
'Y', 0, now(), now()
)
ON CONFLICT (table_name, column_name)
DO UPDATE SET
input_type = ${inputType},
detail_settings = ${JSON.stringify(finalDetailSettings)},
updated_date = now();
`;
logger.info(
`컬럼 입력 타입 설정 완료: ${tableName}.${columnName} = ${inputType}`
);
} catch (error) {
logger.error(
`컬럼 입력 타입 설정 실패: ${tableName}.${columnName}`,
error
);
throw new Error(
`컬럼 입력 타입 설정 실패: ${error instanceof Error ? error.message : "Unknown error"}`
);
}
}
/**
* 입력 타입별 기본 상세 설정 생성
*/
private generateDefaultInputTypeSettings(
inputType: string
): Record<string, any> {
switch (inputType) {
case "text":
return {
maxLength: 500,
placeholder: "텍스트를 입력하세요",
};
case "number":
return {
min: 0,
step: 1,
placeholder: "숫자를 입력하세요",
};
case "date":
return {
format: "YYYY-MM-DD",
placeholder: "날짜를 선택하세요",
};
case "code":
return {
placeholder: "코드를 선택하세요",
searchable: true,
};
case "entity":
return {
placeholder: "항목을 선택하세요",
searchable: true,
};
case "select":
return {
placeholder: "선택하세요",
searchable: false,
};
case "checkbox":
return {
defaultChecked: false,
trueValue: "Y",
falseValue: "N",
};
case "radio":
return {
inline: false,
};
default:
return {};
}
}
/**
* 웹 타입별 기본 상세 설정 생성 (레거시 지원)
* @deprecated generateDefaultInputTypeSettings 사용 권장
*/
private generateDefaultDetailSettings(webType: string): Record<string, any> {
switch (webType) {
@@ -2363,22 +2479,21 @@ export class TableManagementService {
}
/**
* 컬럼 타입 정보 조회 (화면관리 연동용)
* 컬럼 입력타입 정보 조회 (화면관리 연동용)
*/
async getColumnWebTypes(tableName: string): Promise<ColumnTypeInfo[]> {
async getColumnInputTypes(tableName: string): Promise<ColumnTypeInfo[]> {
try {
logger.info(`컬럼 타입 정보 조회: ${tableName}`);
logger.info(`컬럼 입력타입 정보 조회: ${tableName}`);
// table_type_columns에서 타입 정보 조회
const rawWebTypes = await prisma.$queryRaw<any[]>`
// table_type_columns에서 입력타입 정보 조회
const rawInputTypes = await prisma.$queryRaw<any[]>`
SELECT
ttc.column_name as "columnName",
ttc.column_name as "displayName",
COALESCE(ttc.web_type, 'text') as "webType",
COALESCE(ttc.input_type, 'text') as "inputType",
COALESCE(ttc.detail_settings, '{}') as "detailSettings",
ttc.is_nullable as "isNullable",
ic.data_type as "dataType",
ic.udt_name as "dbType"
ic.data_type as "dataType"
FROM table_type_columns ttc
LEFT JOIN information_schema.columns ic
ON ttc.table_name = ic.table_name AND ttc.column_name = ic.column_name
@@ -2386,14 +2501,12 @@ export class TableManagementService {
ORDER BY ttc.display_order, ttc.column_name
`;
const webTypes: ColumnTypeInfo[] = rawWebTypes.map((col) => ({
const inputTypes: ColumnTypeInfo[] = rawInputTypes.map((col) => ({
tableName: tableName,
columnName: col.columnName,
displayName: col.displayName,
dataType: col.dataType || "text",
dbType: col.dbType || "text",
webType: col.webType,
inputType: "direct",
dataType: col.dataType || "varchar",
inputType: col.inputType,
detailSettings: col.detailSettings,
description: "", // 필수 필드 추가
isNullable: col.isNullable,
@@ -2403,15 +2516,26 @@ export class TableManagementService {
}));
logger.info(
`컬럼 타입 정보 조회 완료: ${tableName}, ${webTypes.length}개 컬럼`
`컬럼 입력타입 정보 조회 완료: ${tableName}, ${inputTypes.length}개 컬럼`
);
return webTypes;
return inputTypes;
} catch (error) {
logger.error(`컬럼 타입 정보 조회 실패: ${tableName}`, error);
logger.error(`컬럼 입력타입 정보 조회 실패: ${tableName}`, error);
throw error;
}
}
/**
* 레거시 지원: 컬럼 웹타입 정보 조회
* @deprecated getColumnInputTypes 사용 권장
*/
async getColumnWebTypes(tableName: string): Promise<ColumnTypeInfo[]> {
logger.warn(
`레거시 메서드 사용: getColumnWebTypes → getColumnInputTypes 사용 권장`
);
return this.getColumnInputTypes(tableName);
}
/**
* 데이터베이스 연결 상태 확인
*/

View File

@@ -26,8 +26,10 @@ export interface CreateColumnDefinition {
name: string;
/** 컬럼 라벨 (화면 표시용) */
label?: string;
/** 타입 */
webType: WebType;
/** 입력타입 */
inputType?: string;
/** 웹타입 (레거시 호환용) */
webType?: WebType;
/** NULL 허용 여부 */
nullable?: boolean;
/** 컬럼 길이 (text, code 타입에서 사용) */

View File

@@ -0,0 +1,115 @@
/**
* 백엔드 입력 타입 정의 (테이블 타입 관리 개선)
* 프론트엔드와 동일한 8개 핵심 입력 타입 정의
*/
// 8개 핵심 입력 타입 (프론트엔드와 동일)
export type InputType =
| "text" // 텍스트
| "number" // 숫자
| "date" // 날짜
| "code" // 코드
| "entity" // 엔티티
| "select" // 선택박스
| "checkbox" // 체크박스
| "radio"; // 라디오버튼
// 입력 타입 옵션 정의
export interface InputTypeOption {
value: InputType;
label: string;
description: string;
category: "basic" | "reference" | "selection";
}
// 입력 타입 옵션 목록
export const INPUT_TYPE_OPTIONS: InputTypeOption[] = [
{
value: "text",
label: "텍스트",
description: "일반 텍스트 입력",
category: "basic",
},
{
value: "number",
label: "숫자",
description: "숫자 입력 (정수/소수)",
category: "basic",
},
{ value: "date", label: "날짜", description: "날짜 선택", category: "basic" },
{
value: "code",
label: "코드",
description: "공통코드 참조",
category: "reference",
},
{
value: "entity",
label: "엔티티",
description: "다른 테이블 참조",
category: "reference",
},
{
value: "select",
label: "선택박스",
description: "드롭다운 선택",
category: "selection",
},
{
value: "checkbox",
label: "체크박스",
description: "체크박스 입력",
category: "selection",
},
{
value: "radio",
label: "라디오버튼",
description: "단일 선택",
category: "selection",
},
];
// 입력 타입 검증 함수
export const isValidInputType = (inputType: string): inputType is InputType => {
return INPUT_TYPE_OPTIONS.some((option) => option.value === inputType);
};
// 레거시 웹 타입 → 입력 타입 매핑
export const WEB_TYPE_TO_INPUT_TYPE: Record<string, InputType> = {
// 텍스트 관련
text: "text",
textarea: "text",
email: "text",
tel: "text",
url: "text",
password: "text",
// 숫자 관련
number: "number",
decimal: "number",
// 날짜 관련
date: "date",
datetime: "date",
time: "date",
// 선택 관련
select: "select",
dropdown: "select",
checkbox: "checkbox",
boolean: "checkbox",
radio: "radio",
// 참조 관련
code: "code",
entity: "entity",
// 기타 (기본값: text)
file: "text",
button: "text",
};
// 입력 타입 변환 함수
export const convertWebTypeToInputType = (webType: string): InputType => {
return WEB_TYPE_TO_INPUT_TYPE[webType] || "text";
};

View File

@@ -8,16 +8,15 @@ export interface TableInfo {
}
export interface ColumnTypeInfo {
tableName?: string;
columnName: string;
displayName: string;
dataType: string; // 추가: 데이터 타입 (dbType과 동일하지만 별도 필드)
dbType: string;
webType: string;
inputType?: "direct" | "auto";
dataType: string; // DB 데이터 타입 (varchar, integer 등)
inputType: string; // 입력 타입 (text, number, date, code, entity, select, checkbox, radio)
detailSettings: string;
description: string;
isNullable: string;
isPrimaryKey: boolean; // 추가: 기본키 여부
isPrimaryKey: boolean;
defaultValue?: string;
maxLength?: number;
numericPrecision?: number;
@@ -34,7 +33,7 @@ export interface ColumnTypeInfo {
export interface ColumnSettings {
columnName?: string; // 컬럼명 (업데이트 시 필요)
columnLabel: string; // 컬럼 표시명
webType: string; // 입력 타입 (text, number, date, code, entity)
inputType: string; // 입력 타입 (text, number, date, code, entity, select, checkbox, radio)
detailSettings: string; // 상세 설정
codeCategory: string; // 코드 카테고리
codeValue: string; // 코드 값