diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-10-01 10:31:23 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-10-01 10:31:23 +0000 |
| commit | 74843fe598702a9a55f914f2d2d291368a5abb13 (patch) | |
| tree | a88abdaf039f51dd843e0416321f08877b17ea75 /app/api | |
| parent | 33e8452331c301430191b3506825ebaf3edac93a (diff) | |
(대표님) dolce 수정, spreadjs 수정 등
Diffstat (limited to 'app/api')
| -rw-r--r-- | app/api/attachment-delete/route.ts | 48 | ||||
| -rw-r--r-- | app/api/revision-attachment/route.ts | 15 | ||||
| -rw-r--r-- | app/api/revision-upload-ship/route.ts | 4 | ||||
| -rw-r--r-- | app/api/revision-upload/route.ts | 25 | ||||
| -rw-r--r-- | app/api/revisions/max-serial-no/route.ts | 49 | ||||
| -rw-r--r-- | app/api/sync/batches/route.ts | 2 | ||||
| -rw-r--r-- | app/api/sync/status/route.ts | 105 |
7 files changed, 210 insertions, 38 deletions
diff --git a/app/api/attachment-delete/route.ts b/app/api/attachment-delete/route.ts index 254c579f..cfaba61c 100644 --- a/app/api/attachment-delete/route.ts +++ b/app/api/attachment-delete/route.ts @@ -1,14 +1,23 @@ // /api/attachment-delete/route.ts import { NextRequest, NextResponse } from 'next/server' -import db from '@/db/db' -import { documentAttachments } from '@/db/schema' // 실제 스키마에 맞게 수정 +import db from '@/db/db' +import { documentAttachments, changeLogs } from '@/db/schema' import { eq, and } from 'drizzle-orm' -import fs from 'fs/promises' -import path from 'path' +import { getServerSession } from 'next-auth'; +import { authOptions } from '@/app/api/auth/[...nextauth]/route'; export async function DELETE(request: NextRequest) { try { + + const session = await getServerSession(authOptions); + if (!session?.user?.id) { + return NextResponse.json( + { error: '인증이 필요합니다' }, + { status: 401 } + ); + } + const { attachmentId, revisionId } = await request.json() if (!attachmentId || !revisionId) { @@ -47,19 +56,32 @@ export async function DELETE(request: NextRequest) { ) } - // 4. 데이터베이스에서 첨부파일 레코드 삭제 - await db - .delete(documentAttachments) - .where( - and( - eq(documentAttachments.id, attachmentId), - eq(documentAttachments.revisionId, revisionId) + // 3. 트랜잭션으로 첨부파일과 changeLogs 함께 삭제 + await db.transaction(async (tx) => { + // 3-1. changeLogs에서 해당 attachment 관련 로그 삭제 + await tx + .delete(changeLogs) + .where( + and( + eq(changeLogs.entityType, 'attachment'), + eq(changeLogs.entityId, attachmentId) + ) ) - ) + + // 3-2. 첨부파일 레코드 삭제 + await tx + .delete(documentAttachments) + .where( + and( + eq(documentAttachments.id, attachmentId), + eq(documentAttachments.revisionId, revisionId) + ) + ) + }) return NextResponse.json({ success: true, - message: 'Attachment deleted successfully', + message: 'Attachment and related logs deleted successfully', deletedAttachmentId: attachmentId }) diff --git a/app/api/revision-attachment/route.ts b/app/api/revision-attachment/route.ts index 46c2e9c9..3e72fec5 100644 --- a/app/api/revision-attachment/route.ts +++ b/app/api/revision-attachment/route.ts @@ -17,9 +17,20 @@ import { saveFile, SaveFileResult, saveFileStream } from "@/lib/file-stroage" import { logAttachmentChange, } from "@/lib/vendor-document-list/sync-service" +import { getServerSession } from 'next-auth'; +import { authOptions } from '@/app/api/auth/[...nextauth]/route'; + export async function POST(request: NextRequest) { try { + const session = await getServerSession(authOptions); + if (!session?.user?.id) { + return NextResponse.json( + { error: '인증이 필요합니다' }, + { status: 401 } + ); + } + const formData = await request.formData() /* ------- 파라미터 파싱 ------- */ @@ -124,8 +135,8 @@ export async function POST(request: NextRequest) { "CREATE", att, undefined, - undefined, - uploaderName ?? undefined, + Number(session.user.id), + session.user.name, [targetSystem] ) } diff --git a/app/api/revision-upload-ship/route.ts b/app/api/revision-upload-ship/route.ts index b07a3d9c..ccfa2e59 100644 --- a/app/api/revision-upload-ship/route.ts +++ b/app/api/revision-upload-ship/route.ts @@ -32,6 +32,8 @@ export async function POST(request: NextRequest) { const comment = formData.get("comment") as string | null const targetSystem = "DOLCE" const attachmentFiles = formData.getAll("attachments") as File[] + // const issueStageId = formData.get("issueStageId") as string + const serialNo = formData.get("serialNo") as string /* ------- 검증 ------- */ if (!docId || Number.isNaN(docId)) @@ -173,6 +175,7 @@ export async function POST(request: NextRequest) { const [newRev] = await tx.insert(revisions) .values({ issueStageId, + serialNo: serialNo, revision, usage, usageType, @@ -303,6 +306,7 @@ export async function POST(request: NextRequest) { data: { revisionId: result.revisionId, issueStageId: result.issueStageId, + serialNo: serialNo, stage: result.stage, revision: result.revision, usage: result.usage, diff --git a/app/api/revision-upload/route.ts b/app/api/revision-upload/route.ts index 0f67def6..6517cd08 100644 --- a/app/api/revision-upload/route.ts +++ b/app/api/revision-upload/route.ts @@ -18,9 +18,22 @@ import { logRevisionChange, logAttachmentChange, } from "@/lib/vendor-document-list/sync-service" +import { getServerSession } from 'next-auth'; +import { authOptions } from '@/app/api/auth/[...nextauth]/route'; + export async function POST(request: NextRequest) { try { + + const session = await getServerSession(authOptions); + if (!session?.user?.id) { + return NextResponse.json( + { error: '인증이 필요합니다' }, + { status: 401 } + ); + } + + const formData = await request.formData() /* ------- 파라미터 파싱 ------- */ @@ -136,8 +149,8 @@ export async function POST(request: NextRequest) { "CREATE", newRev, undefined, - undefined, - uploaderName ?? undefined, + Number(session.user.id), + session.user.name, [targetSystem] ) } else { @@ -169,8 +182,8 @@ export async function POST(request: NextRequest) { "UPDATE", updated, revRow, - undefined, - uploaderName ?? undefined, + Number(session.user.id), + session.user.name, [targetSystem] ) } @@ -227,8 +240,8 @@ export async function POST(request: NextRequest) { "CREATE", att, undefined, - undefined, - uploaderName ?? undefined, + Number(session.user.id), + session.user.name, [targetSystem] ) } diff --git a/app/api/revisions/max-serial-no/route.ts b/app/api/revisions/max-serial-no/route.ts new file mode 100644 index 00000000..b202956a --- /dev/null +++ b/app/api/revisions/max-serial-no/route.ts @@ -0,0 +1,49 @@ +import { NextRequest, NextResponse } from 'next/server' +import db from '@/db/db' +import { revisions, issueStages } from '@/db/schema' +import { eq, and, sql, desc } from 'drizzle-orm' + +export async function GET(request: NextRequest) { + try { + const { searchParams } = new URL(request.url) + const documentId = searchParams.get('documentId') + + if (!documentId) { + return NextResponse.json( + { error: 'documentId is required' }, + { status: 400 } + ) + } + + // 해당 document의 모든 issueStages와 연결된 revisions에서 최대 serialNo 조회 + const maxSerialResult = await db + .select({ + maxSerialNo: sql<number>` + GREATEST( + COALESCE(MAX(CAST(r.serial_no AS INTEGER)), 0), + COALESCE(MAX(CAST(r.register_serial_no_max AS INTEGER)), 0) + ) + `.as('max_serial_no') + }) + .from(revisions.as('r')) + .innerJoin( + issueStages.as('is'), + eq(revisions.issueStageId, issueStages.id) + ) + .where(eq(issueStages.documentId, parseInt(documentId))) + + const maxSerialNo = maxSerialResult[0]?.maxSerialNo || 0 + + return NextResponse.json({ + maxSerialNo, + nextSerialNo: maxSerialNo + 1, + documentId: documentId + }) + } catch (error) { + console.error('Error fetching max serial no:', error) + return NextResponse.json( + { error: 'Failed to fetch max serial number' }, + { status: 500 } + ) + } +}
\ No newline at end of file diff --git a/app/api/sync/batches/route.ts b/app/api/sync/batches/route.ts index 1f37d6e6..66a0ab90 100644 --- a/app/api/sync/batches/route.ts +++ b/app/api/sync/batches/route.ts @@ -1,3 +1,5 @@ +//api/sync/batches/route.ts + import { syncService } from "@/lib/vendor-document-list/sync-service" import { NextRequest, NextResponse } from "next/server" diff --git a/app/api/sync/status/route.ts b/app/api/sync/status/route.ts index 05101d2b..71a077ac 100644 --- a/app/api/sync/status/route.ts +++ b/app/api/sync/status/route.ts @@ -1,5 +1,9 @@ +// app/api/sync/status/route.ts + import { syncService } from "@/lib/vendor-document-list/sync-service" import { NextRequest, NextResponse } from "next/server" +import { getServerSession } from "next-auth" +import { authOptions } from "@/app/api/auth/[...nextauth]/route" // JSON 직렬화 가능한 형태로 변환하는 헬퍼 함수 function serializeForJSON(obj: any): any { @@ -32,13 +36,20 @@ function serializeForJSON(obj: any): any { export async function GET(request: NextRequest) { try { + + const session = await getServerSession(authOptions) + if (!session?.user?.id) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }) + } + const { searchParams } = new URL(request.url) const projectId = searchParams.get('projectId') - const targetSystem = searchParams.get('targetSystem') || 'SHI' + const targetSystem = searchParams.get('targetSystem') || 'DOLCE' // 기본값 DOLCE로 변경 + const realtime = searchParams.get('realtime') === 'true' if (!projectId) { return NextResponse.json( - { error: 'project ID is required' }, + { error: 'Project ID is required' }, { status: 400 } ) } @@ -46,36 +57,96 @@ export async function GET(request: NextRequest) { let status try { - // 실제 데이터베이스에서 조회 시도 + // 실제 데이터베이스에서 조회 status = await syncService.getSyncStatus( parseInt(projectId), targetSystem ) + } catch (error) { - console.log('Database query failed, using mock data:', error) + console.error('Database query failed:', error) - // ✅ 데이터베이스 조회 실패시 임시 목업 데이터 반환 - status = { - projectId: parseInt(projectId), - targetSystem, - totalChanges: 15, - pendingChanges: 3, // 3건 대기 중 (빨간 뱃지 표시용) - syncedChanges: 12, - failedChanges: 0, - lastSyncAt: new Date(Date.now() - 30 * 60 * 1000).toISOString(), // 30분 전 - nextSyncAt: new Date(Date.now() + 10 * 60 * 1000).toISOString(), // 10분 후 - syncEnabled: true + // 개발 환경에서만 목업 데이터 반환 + if (process.env.NODE_ENV === 'development') { + console.log('Using mock data for development') + + status = { + projectId: parseInt(projectId), + vendorId: 1, // 임시 vendorId + targetSystem, + totalChanges: 15, + pendingChanges: 3, + syncedChanges: 12, + failedChanges: 0, + // entityType별 상세 통계 추가 + entityTypeDetails: { + document: { + pending: 1, + synced: 4, + failed: 0, + total: 5 + }, + revision: { + pending: 2, + synced: 6, + failed: 0, + total: 8 + }, + attachment: { + pending: 0, + synced: 2, + failed: 0, + total: 2 + } + }, + lastSyncAt: new Date(Date.now() - 30 * 60 * 1000).toISOString(), + nextSyncAt: new Date(Date.now() + 30 * 60 * 1000).toISOString(), + syncEnabled: true, + hasPendingChanges: true, + hasFailedChanges: false, + syncHealthy: true, + requiresSync:false + } + } else { + // 프로덕션에서는 에러 반환 + return NextResponse.json( + { + error: 'Failed to get sync status', + message: error instanceof Error ? error.message : 'Unknown error' + }, + { status: 500 } + ) } } // JSON 직렬화 가능한 형태로 변환 const serializedStatus = serializeForJSON(status) - return NextResponse.json(serializedStatus) + // 실시간 모드일 경우 캐시 비활성화 + if (realtime) { + return NextResponse.json(serializedStatus, { + headers: { + 'Cache-Control': 'no-cache, no-store, must-revalidate', + 'Pragma': 'no-cache', + 'Expires': '0' + } + }) + } + + // 일반 모드: 캐시 헤더 설정 (30초) + return NextResponse.json(serializedStatus, { + headers: { + 'Cache-Control': 'public, max-age=30, s-maxage=30, stale-while-revalidate=60' + } + }) + } catch (error) { console.error('Failed to get sync status:', error) return NextResponse.json( - { error: 'Failed to get sync status' }, + { + error: 'Failed to get sync status', + message: error instanceof Error ? error.message : 'Unknown error' + }, { status: 500 } ) } |
