681 lines
22 KiB
TypeScript
681 lines
22 KiB
TypeScript
import prisma from "../config/database";
|
|
import { Prisma } from "@prisma/client";
|
|
import { EventTriggerService } from "./eventTriggerService";
|
|
|
|
export interface FormDataResult {
|
|
id: number;
|
|
screenId: number;
|
|
tableName: string;
|
|
data: Record<string, any>;
|
|
createdAt: Date | null;
|
|
updatedAt: Date | null;
|
|
createdBy: string;
|
|
updatedBy: string;
|
|
}
|
|
|
|
export interface PaginatedFormData {
|
|
content: FormDataResult[];
|
|
totalElements: number;
|
|
totalPages: number;
|
|
currentPage: number;
|
|
size: number;
|
|
}
|
|
|
|
export interface ValidationError {
|
|
field: string;
|
|
message: string;
|
|
code: string;
|
|
}
|
|
|
|
export interface ValidationResult {
|
|
valid: boolean;
|
|
errors: ValidationError[];
|
|
}
|
|
|
|
export interface TableColumn {
|
|
columnName: string;
|
|
dataType: string;
|
|
nullable: boolean;
|
|
primaryKey: boolean;
|
|
maxLength?: number;
|
|
defaultValue?: any;
|
|
}
|
|
|
|
export class DynamicFormService {
|
|
/**
|
|
* 테이블의 컬럼명 목록 조회 (간단 버전)
|
|
*/
|
|
private async getTableColumnNames(tableName: string): Promise<string[]> {
|
|
try {
|
|
const result = (await prisma.$queryRawUnsafe(`
|
|
SELECT column_name
|
|
FROM information_schema.columns
|
|
WHERE table_name = '${tableName}'
|
|
AND table_schema = 'public'
|
|
`)) as any[];
|
|
|
|
return result.map((row) => row.column_name);
|
|
} catch (error) {
|
|
console.error(`❌ 테이블 ${tableName} 컬럼 정보 조회 실패:`, error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 테이블의 Primary Key 컬럼 조회
|
|
*/
|
|
private async getTablePrimaryKeys(tableName: string): Promise<string[]> {
|
|
try {
|
|
const result = (await prisma.$queryRawUnsafe(`
|
|
SELECT kcu.column_name
|
|
FROM information_schema.table_constraints tc
|
|
JOIN information_schema.key_column_usage kcu
|
|
ON tc.constraint_name = kcu.constraint_name
|
|
WHERE tc.table_name = '${tableName}'
|
|
AND tc.constraint_type = 'PRIMARY KEY'
|
|
AND tc.table_schema = 'public'
|
|
`)) as any[];
|
|
|
|
return result.map((row) => row.column_name);
|
|
} catch (error) {
|
|
console.error(`❌ 테이블 ${tableName} Primary Key 조회 실패:`, error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 폼 데이터 저장 (실제 테이블에 직접 저장)
|
|
*/
|
|
async saveFormData(
|
|
screenId: number,
|
|
tableName: string,
|
|
data: Record<string, any>
|
|
): Promise<FormDataResult> {
|
|
try {
|
|
console.log("💾 서비스: 실제 테이블에 폼 데이터 저장 시작:", {
|
|
screenId,
|
|
tableName,
|
|
data,
|
|
});
|
|
|
|
// 테이블의 실제 컬럼 정보와 Primary Key 조회
|
|
const tableColumns = await this.getTableColumnNames(tableName);
|
|
const primaryKeys = await this.getTablePrimaryKeys(tableName);
|
|
console.log(`📋 테이블 ${tableName}의 컬럼:`, tableColumns);
|
|
console.log(`🔑 테이블 ${tableName}의 Primary Key:`, primaryKeys);
|
|
|
|
// 메타데이터 제거 (실제 테이블 컬럼이 아님)
|
|
const { created_by, updated_by, company_code, screen_id, ...actualData } =
|
|
data;
|
|
|
|
// 기본 데이터 준비
|
|
const dataToInsert: any = { ...actualData };
|
|
|
|
// 테이블에 존재하는 공통 필드들만 추가
|
|
if (tableColumns.includes("created_at")) {
|
|
dataToInsert.created_at = new Date();
|
|
}
|
|
if (tableColumns.includes("updated_at")) {
|
|
dataToInsert.updated_at = new Date();
|
|
}
|
|
if (tableColumns.includes("regdate") && !dataToInsert.regdate) {
|
|
dataToInsert.regdate = new Date();
|
|
}
|
|
|
|
// 생성자/수정자 정보가 있고 해당 컬럼이 존재한다면 추가
|
|
if (created_by && tableColumns.includes("created_by")) {
|
|
dataToInsert.created_by = created_by;
|
|
}
|
|
if (updated_by && tableColumns.includes("updated_by")) {
|
|
dataToInsert.updated_by = updated_by;
|
|
}
|
|
if (company_code && tableColumns.includes("company_code")) {
|
|
// company_code가 UUID 형태(36자)라면 하이픈 제거하여 32자로 만듦
|
|
let processedCompanyCode = company_code;
|
|
if (
|
|
typeof company_code === "string" &&
|
|
company_code.length === 36 &&
|
|
company_code.includes("-")
|
|
) {
|
|
processedCompanyCode = company_code.replace(/-/g, "");
|
|
console.log(
|
|
`🔧 company_code 길이 조정: "${company_code}" -> "${processedCompanyCode}" (${processedCompanyCode.length}자)`
|
|
);
|
|
}
|
|
// 여전히 32자를 초과하면 앞의 32자만 사용
|
|
if (
|
|
typeof processedCompanyCode === "string" &&
|
|
processedCompanyCode.length > 32
|
|
) {
|
|
processedCompanyCode = processedCompanyCode.substring(0, 32);
|
|
console.log(
|
|
`⚠️ company_code 길이 제한: 앞의 32자로 자름 -> "${processedCompanyCode}"`
|
|
);
|
|
}
|
|
dataToInsert.company_code = processedCompanyCode;
|
|
}
|
|
|
|
// 날짜/시간 문자열을 적절한 형태로 변환
|
|
Object.keys(dataToInsert).forEach((key) => {
|
|
const value = dataToInsert[key];
|
|
|
|
// 날짜/시간 관련 컬럼명 패턴 체크 (regdate, created_at, updated_at 등)
|
|
if (
|
|
typeof value === "string" &&
|
|
(key.toLowerCase().includes("date") ||
|
|
key.toLowerCase().includes("time") ||
|
|
key.toLowerCase().includes("created") ||
|
|
key.toLowerCase().includes("updated") ||
|
|
key.toLowerCase().includes("reg"))
|
|
) {
|
|
// YYYY-MM-DD HH:mm:ss 형태의 문자열을 Date 객체로 변환
|
|
if (value.match(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/)) {
|
|
console.log(`📅 날짜 변환: ${key} = "${value}" -> Date 객체`);
|
|
dataToInsert[key] = new Date(value);
|
|
}
|
|
// YYYY-MM-DD 형태의 문자열을 Date 객체로 변환
|
|
else if (value.match(/^\d{4}-\d{2}-\d{2}$/)) {
|
|
console.log(`📅 날짜 변환: ${key} = "${value}" -> Date 객체`);
|
|
dataToInsert[key] = new Date(value + "T00:00:00");
|
|
}
|
|
}
|
|
});
|
|
|
|
// 존재하지 않는 컬럼 제거
|
|
Object.keys(dataToInsert).forEach((key) => {
|
|
if (!tableColumns.includes(key)) {
|
|
console.log(
|
|
`⚠️ 컬럼 ${key}는 테이블 ${tableName}에 존재하지 않아 제거됨`
|
|
);
|
|
delete dataToInsert[key];
|
|
}
|
|
});
|
|
|
|
console.log("🎯 실제 테이블에 삽입할 데이터:", {
|
|
tableName,
|
|
dataToInsert,
|
|
});
|
|
|
|
// 동적 SQL을 사용하여 실제 테이블에 UPSERT
|
|
const columns = Object.keys(dataToInsert);
|
|
const values: any[] = Object.values(dataToInsert);
|
|
const placeholders = values.map((_, index) => `$${index + 1}`).join(", ");
|
|
|
|
let upsertQuery: string;
|
|
|
|
if (primaryKeys.length > 0) {
|
|
// Primary Key가 있는 경우 UPSERT 사용
|
|
const conflictColumns = primaryKeys.join(", ");
|
|
const updateSet = columns
|
|
.filter((col) => !primaryKeys.includes(col)) // Primary Key는 UPDATE에서 제외
|
|
.map((col) => `${col} = EXCLUDED.${col}`)
|
|
.join(", ");
|
|
|
|
if (updateSet) {
|
|
upsertQuery = `
|
|
INSERT INTO ${tableName} (${columns.join(", ")})
|
|
VALUES (${placeholders})
|
|
ON CONFLICT (${conflictColumns})
|
|
DO UPDATE SET ${updateSet}
|
|
RETURNING *
|
|
`;
|
|
} else {
|
|
// 업데이트할 컬럼이 없는 경우 (Primary Key만 있는 테이블)
|
|
upsertQuery = `
|
|
INSERT INTO ${tableName} (${columns.join(", ")})
|
|
VALUES (${placeholders})
|
|
ON CONFLICT (${conflictColumns})
|
|
DO NOTHING
|
|
RETURNING *
|
|
`;
|
|
}
|
|
} else {
|
|
// Primary Key가 없는 경우 일반 INSERT
|
|
upsertQuery = `
|
|
INSERT INTO ${tableName} (${columns.join(", ")})
|
|
VALUES (${placeholders})
|
|
RETURNING *
|
|
`;
|
|
}
|
|
|
|
console.log("📝 실행할 UPSERT SQL:", upsertQuery);
|
|
console.log("📊 SQL 파라미터:", values);
|
|
|
|
const result = await prisma.$queryRawUnsafe(upsertQuery, ...values);
|
|
|
|
console.log("✅ 서비스: 실제 테이블 저장 성공:", result);
|
|
|
|
// 결과를 표준 형식으로 변환
|
|
const insertedRecord = Array.isArray(result) ? result[0] : result;
|
|
|
|
// 🔥 조건부 연결 실행 (INSERT 트리거)
|
|
try {
|
|
if (company_code) {
|
|
await EventTriggerService.executeEventTriggers(
|
|
"insert",
|
|
tableName,
|
|
insertedRecord as Record<string, any>,
|
|
company_code
|
|
);
|
|
console.log("🚀 조건부 연결 트리거 실행 완료 (INSERT)");
|
|
}
|
|
} catch (triggerError) {
|
|
console.error("⚠️ 조건부 연결 트리거 실행 오류:", triggerError);
|
|
// 트리거 오류는 로그만 남기고 메인 저장 프로세스는 계속 진행
|
|
}
|
|
|
|
return {
|
|
id: insertedRecord.id || insertedRecord.objid || 0,
|
|
screenId: screenId,
|
|
tableName: tableName,
|
|
data: insertedRecord as Record<string, any>,
|
|
createdAt: insertedRecord.created_at || new Date(),
|
|
updatedAt: insertedRecord.updated_at || new Date(),
|
|
createdBy: insertedRecord.created_by || created_by || "system",
|
|
updatedBy: insertedRecord.updated_by || updated_by || "system",
|
|
};
|
|
} catch (error) {
|
|
console.error("❌ 서비스: 실제 테이블 저장 실패:", error);
|
|
throw new Error(`실제 테이블 저장 실패: ${error}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 폼 데이터 업데이트 (실제 테이블에서 직접 업데이트)
|
|
*/
|
|
async updateFormData(
|
|
id: number,
|
|
tableName: string,
|
|
data: Record<string, any>
|
|
): Promise<FormDataResult> {
|
|
try {
|
|
console.log("🔄 서비스: 실제 테이블에서 폼 데이터 업데이트 시작:", {
|
|
id,
|
|
tableName,
|
|
data,
|
|
});
|
|
|
|
// 테이블의 실제 컬럼 정보 조회
|
|
const tableColumns = await this.getTableColumnNames(tableName);
|
|
console.log(`📋 테이블 ${tableName}의 컬럼:`, tableColumns);
|
|
|
|
// 메타데이터 제거
|
|
const { created_by, updated_by, company_code, screen_id, ...actualData } =
|
|
data;
|
|
|
|
// 기본 데이터 준비
|
|
const dataToUpdate: any = { ...actualData };
|
|
|
|
// 테이블에 존재하는 업데이트 관련 필드들만 추가
|
|
if (tableColumns.includes("updated_at")) {
|
|
dataToUpdate.updated_at = new Date();
|
|
}
|
|
if (tableColumns.includes("regdate") && !dataToUpdate.regdate) {
|
|
dataToUpdate.regdate = new Date();
|
|
}
|
|
|
|
// 수정자 정보가 있고 해당 컬럼이 존재한다면 추가
|
|
if (updated_by && tableColumns.includes("updated_by")) {
|
|
dataToUpdate.updated_by = updated_by;
|
|
}
|
|
|
|
// 존재하지 않는 컬럼 제거
|
|
Object.keys(dataToUpdate).forEach((key) => {
|
|
if (!tableColumns.includes(key)) {
|
|
console.log(
|
|
`⚠️ 컬럼 ${key}는 테이블 ${tableName}에 존재하지 않아 제거됨`
|
|
);
|
|
delete dataToUpdate[key];
|
|
}
|
|
});
|
|
|
|
console.log("🎯 실제 테이블에서 업데이트할 데이터:", {
|
|
tableName,
|
|
id,
|
|
dataToUpdate,
|
|
});
|
|
|
|
// 동적 UPDATE SQL 생성
|
|
const setClause = Object.keys(dataToUpdate)
|
|
.map((key, index) => `${key} = $${index + 1}`)
|
|
.join(", ");
|
|
|
|
const values: any[] = Object.values(dataToUpdate);
|
|
values.push(id); // WHERE 조건용 ID 추가
|
|
|
|
// ID 또는 objid로 찾기 시도
|
|
const updateQuery = `
|
|
UPDATE ${tableName}
|
|
SET ${setClause}
|
|
WHERE (id = $${values.length} OR objid = $${values.length})
|
|
RETURNING *
|
|
`;
|
|
|
|
console.log("📝 실행할 UPDATE SQL:", updateQuery);
|
|
console.log("📊 SQL 파라미터:", values);
|
|
|
|
const result = await prisma.$queryRawUnsafe(updateQuery, ...values);
|
|
|
|
console.log("✅ 서비스: 실제 테이블 업데이트 성공:", result);
|
|
|
|
const updatedRecord = Array.isArray(result) ? result[0] : result;
|
|
|
|
// 🔥 조건부 연결 실행 (UPDATE 트리거)
|
|
try {
|
|
if (company_code) {
|
|
await EventTriggerService.executeEventTriggers(
|
|
"update",
|
|
tableName,
|
|
updatedRecord as Record<string, any>,
|
|
company_code
|
|
);
|
|
console.log("🚀 조건부 연결 트리거 실행 완료 (UPDATE)");
|
|
}
|
|
} catch (triggerError) {
|
|
console.error("⚠️ 조건부 연결 트리거 실행 오류:", triggerError);
|
|
// 트리거 오류는 로그만 남기고 메인 업데이트 프로세스는 계속 진행
|
|
}
|
|
|
|
return {
|
|
id: updatedRecord.id || updatedRecord.objid || id,
|
|
screenId: 0, // 실제 테이블에는 screenId가 없으므로 0으로 설정
|
|
tableName: tableName,
|
|
data: updatedRecord as Record<string, any>,
|
|
createdAt: updatedRecord.created_at || new Date(),
|
|
updatedAt: updatedRecord.updated_at || new Date(),
|
|
createdBy: updatedRecord.created_by || "system",
|
|
updatedBy: updatedRecord.updated_by || updated_by || "system",
|
|
};
|
|
} catch (error) {
|
|
console.error("❌ 서비스: 실제 테이블 업데이트 실패:", error);
|
|
throw new Error(`실제 테이블 업데이트 실패: ${error}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 폼 데이터 삭제 (실제 테이블에서 직접 삭제)
|
|
*/
|
|
async deleteFormData(
|
|
id: number,
|
|
tableName: string,
|
|
companyCode?: string
|
|
): Promise<void> {
|
|
try {
|
|
console.log("🗑️ 서비스: 실제 테이블에서 폼 데이터 삭제 시작:", {
|
|
id,
|
|
tableName,
|
|
});
|
|
|
|
// 동적 DELETE SQL 생성
|
|
const deleteQuery = `
|
|
DELETE FROM ${tableName}
|
|
WHERE (id = $1 OR objid = $1)
|
|
RETURNING *
|
|
`;
|
|
|
|
console.log("📝 실행할 DELETE SQL:", deleteQuery);
|
|
console.log("📊 SQL 파라미터:", [id]);
|
|
|
|
const result = await prisma.$queryRawUnsafe(deleteQuery, id);
|
|
|
|
console.log("✅ 서비스: 실제 테이블 삭제 성공:", result);
|
|
|
|
// 🔥 조건부 연결 실행 (DELETE 트리거)
|
|
try {
|
|
if (
|
|
companyCode &&
|
|
result &&
|
|
Array.isArray(result) &&
|
|
result.length > 0
|
|
) {
|
|
const deletedRecord = result[0] as Record<string, any>;
|
|
await EventTriggerService.executeEventTriggers(
|
|
"delete",
|
|
tableName,
|
|
deletedRecord,
|
|
companyCode
|
|
);
|
|
console.log("🚀 조건부 연결 트리거 실행 완료 (DELETE)");
|
|
}
|
|
} catch (triggerError) {
|
|
console.error("⚠️ 조건부 연결 트리거 실행 오류:", triggerError);
|
|
// 트리거 오류는 로그만 남기고 메인 삭제 프로세스는 계속 진행
|
|
}
|
|
} catch (error) {
|
|
console.error("❌ 서비스: 실제 테이블 삭제 실패:", error);
|
|
throw new Error(`실제 테이블 삭제 실패: ${error}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 단일 폼 데이터 조회
|
|
*/
|
|
async getFormData(id: number): Promise<FormDataResult | null> {
|
|
try {
|
|
console.log("📄 서비스: 폼 데이터 단건 조회 시작:", { id });
|
|
|
|
const result = await prisma.dynamic_form_data.findUnique({
|
|
where: { id },
|
|
});
|
|
|
|
if (!result) {
|
|
console.log("❌ 서비스: 폼 데이터를 찾을 수 없음");
|
|
return null;
|
|
}
|
|
|
|
console.log("✅ 서비스: 폼 데이터 단건 조회 성공");
|
|
|
|
return {
|
|
id: result.id,
|
|
screenId: result.screen_id,
|
|
tableName: result.table_name,
|
|
data: result.form_data as Record<string, any>,
|
|
createdAt: result.created_at,
|
|
updatedAt: result.updated_at,
|
|
createdBy: result.created_by,
|
|
updatedBy: result.updated_by,
|
|
};
|
|
} catch (error) {
|
|
console.error("❌ 서비스: 폼 데이터 단건 조회 실패:", error);
|
|
throw new Error(`폼 데이터 조회 실패: ${error}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 화면별 폼 데이터 목록 조회 (페이징)
|
|
*/
|
|
async getFormDataList(
|
|
screenId: number,
|
|
params: {
|
|
page: number;
|
|
size: number;
|
|
search?: string;
|
|
sortBy?: string;
|
|
sortOrder?: "asc" | "desc";
|
|
}
|
|
): Promise<PaginatedFormData> {
|
|
try {
|
|
console.log("📋 서비스: 폼 데이터 목록 조회 시작:", { screenId, params });
|
|
|
|
const {
|
|
page,
|
|
size,
|
|
search,
|
|
sortBy = "created_at",
|
|
sortOrder = "desc",
|
|
} = params;
|
|
const skip = (page - 1) * size;
|
|
|
|
// 검색 조건 구성
|
|
const where: Prisma.dynamic_form_dataWhereInput = {
|
|
screen_id: screenId,
|
|
};
|
|
|
|
// 검색어가 있는 경우 form_data 필드에서 검색
|
|
if (search) {
|
|
where.OR = [
|
|
{
|
|
form_data: {
|
|
path: [],
|
|
string_contains: search,
|
|
},
|
|
},
|
|
{
|
|
table_name: {
|
|
contains: search,
|
|
mode: "insensitive",
|
|
},
|
|
},
|
|
];
|
|
}
|
|
|
|
// 정렬 조건 구성
|
|
const orderBy: Prisma.dynamic_form_dataOrderByWithRelationInput = {};
|
|
if (sortBy === "created_at" || sortBy === "updated_at") {
|
|
orderBy[sortBy] = sortOrder;
|
|
} else {
|
|
orderBy.created_at = "desc"; // 기본값
|
|
}
|
|
|
|
// 데이터 조회
|
|
const [results, totalCount] = await Promise.all([
|
|
prisma.dynamic_form_data.findMany({
|
|
where,
|
|
orderBy,
|
|
skip,
|
|
take: size,
|
|
}),
|
|
prisma.dynamic_form_data.count({ where }),
|
|
]);
|
|
|
|
const formDataResults: FormDataResult[] = results.map((result) => ({
|
|
id: result.id,
|
|
screenId: result.screen_id,
|
|
tableName: result.table_name,
|
|
data: result.form_data as Record<string, any>,
|
|
createdAt: result.created_at,
|
|
updatedAt: result.updated_at,
|
|
createdBy: result.created_by,
|
|
updatedBy: result.updated_by,
|
|
}));
|
|
|
|
const totalPages = Math.ceil(totalCount / size);
|
|
|
|
console.log("✅ 서비스: 폼 데이터 목록 조회 성공:", {
|
|
totalCount,
|
|
totalPages,
|
|
currentPage: page,
|
|
});
|
|
|
|
return {
|
|
content: formDataResults,
|
|
totalElements: totalCount,
|
|
totalPages,
|
|
currentPage: page,
|
|
size,
|
|
};
|
|
} catch (error) {
|
|
console.error("❌ 서비스: 폼 데이터 목록 조회 실패:", error);
|
|
throw new Error(`폼 데이터 목록 조회 실패: ${error}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 폼 데이터 검증
|
|
*/
|
|
async validateFormData(
|
|
tableName: string,
|
|
data: Record<string, any>
|
|
): Promise<ValidationResult> {
|
|
try {
|
|
console.log("✅ 서비스: 폼 데이터 검증 시작:", { tableName, data });
|
|
|
|
const errors: ValidationError[] = [];
|
|
|
|
// 기본 검증 로직 (실제로는 테이블 스키마를 확인해야 함)
|
|
Object.entries(data).forEach(([key, value]) => {
|
|
// 예시: 빈 값 검증
|
|
if (value === null || value === undefined || value === "") {
|
|
// 특정 필드가 required인지 확인하는 로직이 필요
|
|
// 지금은 간단히 모든 필드를 선택사항으로 처리
|
|
}
|
|
|
|
// 예시: 데이터 타입 검증
|
|
// 실제로는 테이블 스키마의 컬럼 타입과 비교해야 함
|
|
});
|
|
|
|
const result: ValidationResult = {
|
|
valid: errors.length === 0,
|
|
errors,
|
|
};
|
|
|
|
console.log("✅ 서비스: 폼 데이터 검증 완료:", result);
|
|
|
|
return result;
|
|
} catch (error) {
|
|
console.error("❌ 서비스: 폼 데이터 검증 실패:", error);
|
|
throw new Error(`폼 데이터 검증 실패: ${error}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 테이블 컬럼 정보 조회 (PostgreSQL 시스템 테이블 활용)
|
|
*/
|
|
async getTableColumns(tableName: string): Promise<TableColumn[]> {
|
|
try {
|
|
console.log("📊 서비스: 테이블 컬럼 정보 조회 시작:", { tableName });
|
|
|
|
// PostgreSQL의 information_schema를 사용하여 컬럼 정보 조회
|
|
const columns = await prisma.$queryRaw<any[]>`
|
|
SELECT
|
|
column_name,
|
|
data_type,
|
|
is_nullable,
|
|
column_default,
|
|
character_maximum_length
|
|
FROM information_schema.columns
|
|
WHERE table_name = ${tableName}
|
|
AND table_schema = 'public'
|
|
ORDER BY ordinal_position
|
|
`;
|
|
|
|
// Primary key 정보 조회
|
|
const primaryKeys = await prisma.$queryRaw<any[]>`
|
|
SELECT
|
|
kcu.column_name
|
|
FROM
|
|
information_schema.table_constraints tc
|
|
JOIN information_schema.key_column_usage kcu
|
|
ON tc.constraint_name = kcu.constraint_name
|
|
WHERE
|
|
tc.constraint_type = 'PRIMARY KEY'
|
|
AND tc.table_name = ${tableName}
|
|
AND tc.table_schema = 'public'
|
|
`;
|
|
|
|
const primaryKeyColumns = new Set(
|
|
primaryKeys.map((pk) => pk.column_name)
|
|
);
|
|
|
|
const result: TableColumn[] = columns.map((col) => ({
|
|
columnName: col.column_name,
|
|
dataType: col.data_type,
|
|
nullable: col.is_nullable === "YES",
|
|
primaryKey: primaryKeyColumns.has(col.column_name),
|
|
maxLength: col.character_maximum_length,
|
|
defaultValue: col.column_default,
|
|
}));
|
|
|
|
console.log("✅ 서비스: 테이블 컬럼 정보 조회 성공:", result);
|
|
|
|
return result;
|
|
} catch (error) {
|
|
console.error("❌ 서비스: 테이블 컬럼 정보 조회 실패:", error);
|
|
throw new Error(`테이블 컬럼 정보 조회 실패: ${error}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 싱글톤 인스턴스 생성 및 export
|
|
export const dynamicFormService = new DynamicFormService();
|