/** * πŸ”₯ λ°μ΄ν„°ν”Œλ‘œμš° μ‹€ν–‰ 컨트둀러 * * λ²„νŠΌ μ œμ–΄μ—μ„œ 관계 μ‹€ν–‰ μ‹œ μ‚¬μš©λ˜λŠ” 컨트둀러 */ import { Request, Response } from "express"; import { AuthenticatedRequest } from "../types/auth"; import { query } from "../database/db"; import logger from "../utils/logger"; /** * 데이터 μ•‘μ…˜ μ‹€ν–‰ */ export async function executeDataAction( req: AuthenticatedRequest, res: Response ): Promise { try { const { tableName, data, actionType, connection } = req.body; const companyCode = req.user?.companyCode || "*"; logger.info(`데이터 μ•‘μ…˜ μ‹€ν–‰ μ‹œμž‘: ${actionType} on ${tableName}`, { tableName, actionType, dataKeys: Object.keys(data), connection: connection?.name, }); // μ—°κ²° 정보에 따라 λ‹€λ₯Έ λ°μ΄ν„°λ² μ΄μŠ€μ— μ €μž₯ let result; if (connection && connection.id !== 0) { // μ™ΈλΆ€ λ°μ΄ν„°λ² μ΄μŠ€ μ—°κ²° result = await executeExternalDatabaseAction( tableName, data, actionType, connection ); } else { // 메인 λ°μ΄ν„°λ² μ΄μŠ€ (ν˜„μž¬ μ‹œμŠ€ν…œ) result = await executeMainDatabaseAction( tableName, data, actionType, companyCode ); } logger.info(`데이터 μ•‘μ…˜ μ‹€ν–‰ μ™„λ£Œ: ${actionType} on ${tableName}`, result); res.json({ success: true, message: `데이터 μ•‘μ…˜ μ‹€ν–‰ μ™„λ£Œ: ${actionType}`, data: result, }); } catch (error: any) { logger.error("데이터 μ•‘μ…˜ μ‹€ν–‰ μ‹€νŒ¨:", error); res.status(500).json({ success: false, message: `데이터 μ•‘μ…˜ μ‹€ν–‰ μ‹€νŒ¨: ${error.message}`, errorCode: "DATA_ACTION_EXECUTION_ERROR", }); } } /** * 메인 λ°μ΄ν„°λ² μ΄μŠ€μ—μ„œ 데이터 μ•‘μ…˜ μ‹€ν–‰ */ async function executeMainDatabaseAction( tableName: string, data: Record, actionType: string, companyCode: string ): Promise { try { // νšŒμ‚¬ μ½”λ“œ μΆ”κ°€ const dataWithCompany = { ...data, company_code: companyCode, }; switch (actionType.toLowerCase()) { case "insert": return await executeInsert(tableName, dataWithCompany); case "update": return await executeUpdate(tableName, dataWithCompany); case "upsert": return await executeUpsert(tableName, dataWithCompany); case "delete": return await executeDelete(tableName, dataWithCompany); default: throw new Error(`μ§€μ›ν•˜μ§€ μ•ŠλŠ” μ•‘μ…˜ νƒ€μž…: ${actionType}`); } } catch (error) { logger.error(`메인 DB μ•‘μ…˜ μ‹€ν–‰ 였λ₯˜ (${actionType}):`, error); throw error; } } /** * μ™ΈλΆ€ λ°μ΄ν„°λ² μ΄μŠ€μ—μ„œ 데이터 μ•‘μ…˜ μ‹€ν–‰ */ async function executeExternalDatabaseAction( tableName: string, data: Record, actionType: string, connection: any ): Promise { try { logger.info( `μ™ΈλΆ€ DB μ•‘μ…˜ μ‹€ν–‰: ${connection.name} (${connection.host}:${connection.port})` ); logger.info(`ν…Œμ΄λΈ”: ${tableName}, μ•‘μ…˜: ${actionType}`, data); // πŸ”₯ μ‹€μ œ μ™ΈλΆ€ DB μ—°κ²° 및 μ‹€ν–‰ 둜직 κ΅¬ν˜„ const { MultiConnectionQueryService } = await import( "../services/multiConnectionQueryService" ); const queryService = new MultiConnectionQueryService(); let result; switch (actionType.toLowerCase()) { case "insert": result = await queryService.insertDataToConnection( connection.id, tableName, data ); logger.info(`μ™ΈλΆ€ DB INSERT 성곡:`, result); break; case "update": // TODO: UPDATE 둜직 κ΅¬ν˜„ (쑰건 ν•„μš”) throw new Error( "UPDATE μ•‘μ…˜μ€ 아직 μ§€μ›λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. 쑰건 섀정이 ν•„μš”ν•©λ‹ˆλ‹€." ); case "delete": // TODO: DELETE 둜직 κ΅¬ν˜„ (쑰건 ν•„μš”) throw new Error( "DELETE μ•‘μ…˜μ€ 아직 μ§€μ›λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. 쑰건 섀정이 ν•„μš”ν•©λ‹ˆλ‹€." ); default: throw new Error(`μ§€μ›ν•˜μ§€ μ•ŠλŠ” μ•‘μ…˜ νƒ€μž…: ${actionType}`); } return { success: true, message: `μ™ΈλΆ€ DB μ•‘μ…˜ μ‹€ν–‰ μ™„λ£Œ: ${actionType} on ${tableName}`, connection: connection.name, data: result, affectedRows: 1, }; } catch (error) { logger.error(`μ™ΈλΆ€ DB μ•‘μ…˜ μ‹€ν–‰ 였λ₯˜ (${actionType}):`, error); throw error; } } /** * INSERT μ‹€ν–‰ */ async function executeInsert( tableName: string, data: Record ): Promise { try { // 동적 ν…Œμ΄λΈ” 접근을 μœ„ν•œ raw query μ‚¬μš© const columns = Object.keys(data).join(", "); const values = Object.values(data); const placeholders = values.map((_, index) => `$${index + 1}`).join(", "); const insertQuery = `INSERT INTO ${tableName} (${columns}) VALUES (${placeholders}) RETURNING *`; logger.info(`INSERT 쿼리 μ‹€ν–‰:`, { query: insertQuery, values }); const result = await query(insertQuery, values); return { success: true, action: "insert", tableName, data: result, affectedRows: result.length, }; } catch (error) { logger.error(`INSERT μ‹€ν–‰ 였λ₯˜:`, error); throw error; } } /** * UPDATE μ‹€ν–‰ */ async function executeUpdate( tableName: string, data: Record ): Promise { try { logger.info(`UPDATE μ•‘μ…˜ μ‹œμž‘:`, { tableName, receivedData: data }); // 1. ν…Œμ΄λΈ”μ˜ μ‹€μ œ κΈ°λ³Έν‚€ 쑰회 const primaryKeyQuery = ` SELECT a.attname as column_name FROM pg_index i JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = ANY(i.indkey) WHERE i.indrelid = $1::regclass AND i.indisprimary `; const pkResult = await query<{ column_name: string }>(primaryKeyQuery, [ tableName, ]); if (!pkResult || pkResult.length === 0) { throw new Error(`ν…Œμ΄λΈ” ${tableName}의 κΈ°λ³Έν‚€λ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€`); } const primaryKeyColumn = pkResult[0].column_name; logger.info(`ν…Œμ΄λΈ” ${tableName}의 κΈ°λ³Έν‚€:`, primaryKeyColumn); // 2. κΈ°λ³Έν‚€ κ°’ μΆ”μΆœ const primaryKeyValue = data[primaryKeyColumn]; if (!primaryKeyValue && primaryKeyValue !== 0) { logger.error(`UPDATE μ‹€νŒ¨: κΈ°λ³Έν‚€ 값이 μ—†μŒ`, { primaryKeyColumn, receivedData: data, availableKeys: Object.keys(data), }); throw new Error( `UPDATEλ₯Ό μœ„ν•œ κΈ°λ³Έν‚€ 값이 ν•„μš”ν•©λ‹ˆλ‹€ (${primaryKeyColumn})` ); } // 3. μ—…λ°μ΄νŠΈν•  λ°μ΄ν„°μ—μ„œ κΈ°λ³Έν‚€ μ œμ™Έ const updateData = { ...data }; delete updateData[primaryKeyColumn]; logger.info(`UPDATE 데이터 μ€€λΉ„:`, { primaryKeyColumn, primaryKeyValue, updateFields: Object.keys(updateData), }); // 4. 동적 UPDATE 쿼리 생성 const setClause = Object.keys(updateData) .map((key, index) => `${key} = $${index + 1}`) .join(", "); const values = Object.values(updateData); const updateQuery = `UPDATE ${tableName} SET ${setClause} WHERE ${primaryKeyColumn} = $${values.length + 1} RETURNING *`; logger.info(`UPDATE 쿼리 μ‹€ν–‰:`, { query: updateQuery, values: [...values, primaryKeyValue], }); const result = await query(updateQuery, [...values, primaryKeyValue]); logger.info(`UPDATE 성곡:`, { affectedRows: result.length }); return { success: true, action: "update", tableName, data: result, affectedRows: result.length, }; } catch (error) { logger.error(`UPDATE μ‹€ν–‰ 였λ₯˜:`, error); throw error; } } /** * UPSERT μ‹€ν–‰ */ async function executeUpsert( tableName: string, data: Record ): Promise { try { // λ¨Όμ € INSERTλ₯Ό μ‹œλ„ν•˜κ³ , μ‹€νŒ¨ν•˜λ©΄ UPDATE try { return await executeInsert(tableName, data); } catch (insertError) { // INSERT μ‹€νŒ¨ μ‹œ UPDATE μ‹œλ„ logger.info(`INSERT μ‹€νŒ¨, UPDATE μ‹œλ„:`, insertError); return await executeUpdate(tableName, data); } } catch (error) { logger.error(`UPSERT μ‹€ν–‰ 였λ₯˜:`, error); throw error; } } /** * DELETE μ‹€ν–‰ */ async function executeDelete( tableName: string, data: Record ): Promise { try { const { id } = data; if (!id) { throw new Error("DELETEλ₯Ό μœ„ν•œ IDκ°€ ν•„μš”ν•©λ‹ˆλ‹€"); } const deleteQuery = `DELETE FROM ${tableName} WHERE id = $1 RETURNING *`; logger.info(`DELETE 쿼리 μ‹€ν–‰:`, { query: deleteQuery, values: [id] }); const result = await query(deleteQuery, [id]); return { success: true, action: "delete", tableName, data: result, affectedRows: result.length, }; } catch (error) { logger.error(`DELETE μ‹€ν–‰ 였λ₯˜:`, error); throw error; } }