feat: Phase 3.6 CollectionService 전환 완료 및 Phase 3.7-3.9 계획서 작성

CollectionService 전환 완료:
- 11개 Prisma 호출을 모두 Raw Query로 전환
- 수집 설정 CRUD (getCollectionConfigs, getCollectionConfigById, createCollectionConfig, updateCollectionConfig, deleteCollectionConfig)
- 수집 작업 관리 (executeCollection, getCollectionJobs, getCollectionHistory)
- 동적 WHERE 조건 생성 (ILIKE 검색, OR 조건)
- 동적 UPDATE 쿼리 (변경된 필드만 업데이트)
- JSON 필드 처리 (collection_options)
- LEFT JOIN (작업 목록 조회 시 설정 정보 포함)
- 비동기 작업 처리 (setTimeout 내 query 사용)
- 필드명 수정 (schedule_expression → schedule_cron)
- TypeScript 컴파일 성공
- Prisma import 완전 제거

Phase 3 남은 서비스 계획서 작성:
- PHASE3.7_LAYOUT_SERVICE_MIGRATION.md (10개 호출)
  - 레이아웃 표준 관리 (CRUD, 통계, JSON 필드)
- PHASE3.8_DB_TYPE_CATEGORY_SERVICE_MIGRATION.md (10개 호출)
  - DB 타입 카테고리 관리 (CRUD, 통계, UPSERT)
- PHASE3.9_TEMPLATE_STANDARD_SERVICE_MIGRATION.md (6개 호출)
  - 템플릿 표준 관리 (복합 키, JSON 필드, DISTINCT)

Phase 3 진행률: 87/162 (53.7%)
전체 진행률: 338/444 (76.1%)
This commit is contained in:
kjs
2025-10-01 11:20:21 +09:00
parent 7fb2ce582c
commit 45ec38790b
5 changed files with 1395 additions and 98 deletions

View File

@@ -1,7 +1,7 @@
// 수집 관리 서비스
// 작성일: 2024-12-23
import { PrismaClient } from "@prisma/client";
import { query, queryOne, transaction } from "../database/db";
import {
DataCollectionConfig,
CollectionFilter,
@@ -9,8 +9,6 @@ import {
CollectionHistory,
} from "../types/collectionManagement";
const prisma = new PrismaClient();
export class CollectionService {
/**
* 수집 설정 목록 조회
@@ -18,40 +16,44 @@ export class CollectionService {
static async getCollectionConfigs(
filter: CollectionFilter
): Promise<DataCollectionConfig[]> {
const whereCondition: any = {
company_code: filter.company_code || "*",
};
const whereConditions: string[] = ["company_code = $1"];
const values: any[] = [filter.company_code || "*"];
let paramIndex = 2;
if (filter.config_name) {
whereCondition.config_name = {
contains: filter.config_name,
mode: "insensitive",
};
whereConditions.push(`config_name ILIKE $${paramIndex++}`);
values.push(`%${filter.config_name}%`);
}
if (filter.source_connection_id) {
whereCondition.source_connection_id = filter.source_connection_id;
whereConditions.push(`source_connection_id = $${paramIndex++}`);
values.push(filter.source_connection_id);
}
if (filter.collection_type) {
whereCondition.collection_type = filter.collection_type;
whereConditions.push(`collection_type = $${paramIndex++}`);
values.push(filter.collection_type);
}
if (filter.is_active) {
whereCondition.is_active = filter.is_active === "Y";
whereConditions.push(`is_active = $${paramIndex++}`);
values.push(filter.is_active === "Y");
}
if (filter.search) {
whereCondition.OR = [
{ config_name: { contains: filter.search, mode: "insensitive" } },
{ description: { contains: filter.search, mode: "insensitive" } },
];
whereConditions.push(
`(config_name ILIKE $${paramIndex} OR description ILIKE $${paramIndex})`
);
values.push(`%${filter.search}%`);
paramIndex++;
}
const configs = await prisma.data_collection_configs.findMany({
where: whereCondition,
orderBy: { created_date: "desc" },
});
const configs = await query<any>(
`SELECT * FROM data_collection_configs
WHERE ${whereConditions.join(" AND ")}
ORDER BY created_date DESC`,
values
);
return configs.map((config: any) => ({
...config,
@@ -65,9 +67,10 @@ export class CollectionService {
static async getCollectionConfigById(
id: number
): Promise<DataCollectionConfig | null> {
const config = await prisma.data_collection_configs.findUnique({
where: { id },
});
const config = await queryOne<any>(
`SELECT * FROM data_collection_configs WHERE id = $1`,
[id]
);
if (!config) return null;
@@ -84,15 +87,26 @@ export class CollectionService {
data: DataCollectionConfig
): Promise<DataCollectionConfig> {
const { id, collection_options, ...createData } = data;
const config = await prisma.data_collection_configs.create({
data: {
...createData,
is_active: data.is_active,
collection_options: collection_options || undefined,
created_date: new Date(),
updated_date: new Date(),
},
});
const config = await queryOne<any>(
`INSERT INTO data_collection_configs
(config_name, company_code, source_connection_id, collection_type,
collection_options, schedule_cron, is_active, description,
created_by, updated_by, created_date, updated_date)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, NOW(), NOW())
RETURNING *`,
[
createData.config_name,
createData.company_code,
createData.source_connection_id,
createData.collection_type,
collection_options ? JSON.stringify(collection_options) : null,
createData.schedule_cron,
data.is_active,
createData.description,
createData.created_by,
createData.updated_by,
]
);
return {
...config,
@@ -107,19 +121,52 @@ export class CollectionService {
id: number,
data: Partial<DataCollectionConfig>
): Promise<DataCollectionConfig> {
const updateData: any = {
...data,
updated_date: new Date(),
};
const updateFields: string[] = ["updated_date = NOW()"];
const values: any[] = [];
let paramIndex = 1;
if (data.config_name !== undefined) {
updateFields.push(`config_name = $${paramIndex++}`);
values.push(data.config_name);
}
if (data.source_connection_id !== undefined) {
updateFields.push(`source_connection_id = $${paramIndex++}`);
values.push(data.source_connection_id);
}
if (data.collection_type !== undefined) {
updateFields.push(`collection_type = $${paramIndex++}`);
values.push(data.collection_type);
}
if (data.collection_options !== undefined) {
updateFields.push(`collection_options = $${paramIndex++}`);
values.push(
data.collection_options ? JSON.stringify(data.collection_options) : null
);
}
if (data.schedule_cron !== undefined) {
updateFields.push(`schedule_cron = $${paramIndex++}`);
values.push(data.schedule_cron);
}
if (data.is_active !== undefined) {
updateData.is_active = data.is_active;
updateFields.push(`is_active = $${paramIndex++}`);
values.push(data.is_active);
}
if (data.description !== undefined) {
updateFields.push(`description = $${paramIndex++}`);
values.push(data.description);
}
if (data.updated_by !== undefined) {
updateFields.push(`updated_by = $${paramIndex++}`);
values.push(data.updated_by);
}
const config = await prisma.data_collection_configs.update({
where: { id },
data: updateData,
});
const config = await queryOne<any>(
`UPDATE data_collection_configs
SET ${updateFields.join(", ")}
WHERE id = $${paramIndex}
RETURNING *`,
[...values, id]
);
return {
...config,
@@ -131,18 +178,17 @@ export class CollectionService {
* 수집 설정 삭제
*/
static async deleteCollectionConfig(id: number): Promise<void> {
await prisma.data_collection_configs.delete({
where: { id },
});
await query(`DELETE FROM data_collection_configs WHERE id = $1`, [id]);
}
/**
* 수집 작업 실행
*/
static async executeCollection(configId: number): Promise<CollectionJob> {
const config = await prisma.data_collection_configs.findUnique({
where: { id: configId },
});
const config = await queryOne<any>(
`SELECT * FROM data_collection_configs WHERE id = $1`,
[configId]
);
if (!config) {
throw new Error("수집 설정을 찾을 수 없습니다.");
@@ -153,14 +199,13 @@ export class CollectionService {
}
// 수집 작업 기록 생성
const job = await prisma.data_collection_jobs.create({
data: {
config_id: configId,
job_status: "running",
started_at: new Date(),
created_date: new Date(),
},
});
const job = await queryOne<any>(
`INSERT INTO data_collection_jobs
(config_id, job_status, started_at, created_date)
VALUES ($1, $2, NOW(), NOW())
RETURNING *`,
[configId, "running"]
);
// 실제 수집 작업 실행 로직은 여기에 구현
// 현재는 시뮬레이션으로 처리
@@ -171,24 +216,23 @@ export class CollectionService {
const recordsCollected = Math.floor(Math.random() * 1000) + 100;
await prisma.data_collection_jobs.update({
where: { id: job.id },
data: {
job_status: "completed",
completed_at: new Date(),
records_processed: recordsCollected,
},
});
await query(
`UPDATE data_collection_jobs
SET job_status = $1, completed_at = NOW(), records_processed = $2
WHERE id = $3`,
["completed", recordsCollected, job.id]
);
} catch (error) {
await prisma.data_collection_jobs.update({
where: { id: job.id },
data: {
job_status: "failed",
completed_at: new Date(),
error_message:
error instanceof Error ? error.message : "알 수 없는 오류",
},
});
await query(
`UPDATE data_collection_jobs
SET job_status = $1, completed_at = NOW(), error_message = $2
WHERE id = $3`,
[
"failed",
error instanceof Error ? error.message : "알 수 없는 오류",
job.id,
]
);
}
}, 0);
@@ -199,24 +243,21 @@ export class CollectionService {
* 수집 작업 목록 조회
*/
static async getCollectionJobs(configId?: number): Promise<CollectionJob[]> {
const whereCondition: any = {};
let sql = `
SELECT j.*, c.config_name, c.collection_type
FROM data_collection_jobs j
LEFT JOIN data_collection_configs c ON j.config_id = c.id
`;
const values: any[] = [];
if (configId) {
whereCondition.config_id = configId;
sql += ` WHERE j.config_id = $1`;
values.push(configId);
}
const jobs = await prisma.data_collection_jobs.findMany({
where: whereCondition,
orderBy: { started_at: "desc" },
include: {
config: {
select: {
config_name: true,
collection_type: true,
},
},
},
});
sql += ` ORDER BY j.started_at DESC`;
const jobs = await query<any>(sql, values);
return jobs as CollectionJob[];
}
@@ -227,11 +268,13 @@ export class CollectionService {
static async getCollectionHistory(
configId: number
): Promise<CollectionHistory[]> {
const history = await prisma.data_collection_jobs.findMany({
where: { config_id: configId },
orderBy: { started_at: "desc" },
take: 50, // 최근 50개 이력
});
const history = await query<any>(
`SELECT * FROM data_collection_jobs
WHERE config_id = $1
ORDER BY started_at DESC
LIMIT 50`,
[configId]
);
return history.map((item: any) => ({
id: item.id,