summaryrefslogtreecommitdiff
path: root/app/api
diff options
context:
space:
mode:
Diffstat (limited to 'app/api')
-rw-r--r--app/api/attachment-delete/route.ts48
-rw-r--r--app/api/revision-attachment/route.ts15
-rw-r--r--app/api/revision-upload-ship/route.ts4
-rw-r--r--app/api/revision-upload/route.ts25
-rw-r--r--app/api/revisions/max-serial-no/route.ts49
-rw-r--r--app/api/sync/batches/route.ts2
-rw-r--r--app/api/sync/status/route.ts105
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 }
)
}