summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjoonhoekim <26rote@gmail.com>2025-11-02 15:57:12 +0900
committerjoonhoekim <26rote@gmail.com>2025-11-02 15:57:12 +0900
commitd72acdceebf6e6824f025279d036b0b6f1155ea9 (patch)
treeaa2af24c4ff8cf93c650f17b341abe444e584917
parent110847783bc1f099b2b1174d8a1f1609dab47b0e (diff)
(김준회) 미사용 코드베이스 스키마 제거(SWP), signout console 추가
-rw-r--r--db/schema/SWP/swp-documents.ts220
-rw-r--r--db/schema/index.ts3
-rw-r--r--lib/auth/custom-signout.ts78
3 files changed, 72 insertions, 229 deletions
diff --git a/db/schema/SWP/swp-documents.ts b/db/schema/SWP/swp-documents.ts
deleted file mode 100644
index c7661ff6..00000000
--- a/db/schema/SWP/swp-documents.ts
+++ /dev/null
@@ -1,220 +0,0 @@
-import {
- varchar,
- timestamp,
- serial,
- uniqueIndex,
- index,
- pgEnum,
- pgSchema,
-} from "drizzle-orm/pg-core";
-
-// ============================================================================
-// 스키마
-// ============================================================================
-
-export const swpSchema = pgSchema("swp");
-
-// ============================================================================
-// ENUMS
-// ============================================================================
-
-export const syncStatusEnum = pgEnum("swp_sync_status", [
- "synced",
- "pending",
- "error",
-]);
-
-// ============================================================================
-// 문서 마스터 (GetVDRDocumentList)
-// 컬럼명: API 필드명과 동일하게 유지 (UPPER_SNAKE_CASE)
-// ============================================================================
-
-export const swpDocuments = swpSchema.table(
- "swp_documents",
- {
- // Composite Primary Key: DOC_NO + PROJ_NO
- DOC_NO: varchar("DOC_NO", { length: 1000 }).notNull(),
- PROJ_NO: varchar("PROJ_NO", { length: 1000 }).notNull(),
-
- // 문서 기본 정보
- DOC_TITLE: varchar("DOC_TITLE", { length: 1000 }).notNull(),
- DOC_GB: varchar("DOC_GB", { length: 1000 }),
- DOC_TYPE: varchar("DOC_TYPE", { length: 1000 }),
- OWN_DOC_NO: varchar("OWN_DOC_NO", { length: 1000 }),
- SHI_DOC_NO: varchar("SHI_DOC_NO", { length: 1000 }),
-
- // 프로젝트 정보
- PROJ_NM: varchar("PROJ_NM", { length: 1000 }),
- PKG_NO: varchar("PKG_NO", { length: 1000 }),
-
- // 자재/기술 정보
- MAT_CD: varchar("MAT_CD", { length: 1000 }),
- MAT_NM: varchar("MAT_NM", { length: 1000 }),
- DISPLN: varchar("DISPLN", { length: 1000 }),
- CTGRY: varchar("CTGRY", { length: 1000 }),
-
- // 업체 정보
- VNDR_CD: varchar("VNDR_CD", { length: 1000 }),
- CPY_CD: varchar("CPY_CD", { length: 1000 }),
- CPY_NM: varchar("CPY_NM", { length: 1000 }),
-
- // 담당자 정보
- PIC_NM: varchar("PIC_NM", { length: 1000 }),
- PIC_DEPTCD: varchar("PIC_DEPTCD", { length: 1000 }),
- PIC_DEPTNM: varchar("PIC_DEPTNM", { length: 1000 }),
-
- // 최신 리비전 정보 (빠른 조회용)
- LTST_REV_NO: varchar("LTST_REV_NO", { length: 1000 }),
- LTST_REV_SEQ: varchar("LTST_REV_SEQ", { length: 1000 }),
- LTST_ACTV_STAT: varchar("LTST_ACTV_STAT", { length: 1000 }),
-
- // 기타
- STAGE: varchar("STAGE", { length: 1000 }),
- SKL_CD: varchar("SKL_CD", { length: 1000 }),
- MOD_TYPE: varchar("MOD_TYPE", { length: 1000 }),
- ACT_TYPE_NM: varchar("ACT_TYPE_NM", { length: 1000 }),
- USE_YN: varchar("USE_YN", { length: 1000 }),
-
- // 이력 정보 (SWP)
- CRTER: varchar("CRTER", { length: 1000 }),
- CRTE_DTM: varchar("CRTE_DTM", { length: 1000 }),
- CHGR: varchar("CHGR", { length: 1000 }),
- CHG_DTM: varchar("CHG_DTM", { length: 1000 }),
- REV_DTM: varchar("REV_DTM", { length: 1000 }),
-
- // 동기화 메타데이터
- sync_status: syncStatusEnum("sync_status").default("synced").notNull(),
- last_synced_at: timestamp("last_synced_at").defaultNow().notNull(),
- created_at: timestamp("created_at").defaultNow().notNull(),
- updated_at: timestamp("updated_at").defaultNow().notNull(),
- },
- (table) => ({
- // Composite Primary Key
- pk: uniqueIndex("swp_documents_pk").on(table.DOC_NO, table.PROJ_NO),
- // Indexes
- projNoIdx: index("swp_documents_proj_no_idx").on(table.PROJ_NO),
- vndrCdIdx: index("swp_documents_vndr_cd_idx").on(table.VNDR_CD),
- pkgNoIdx: index("swp_documents_pkg_no_idx").on(table.PKG_NO),
- syncStatusIdx: index("swp_documents_sync_status_idx").on(table.sync_status),
- })
-);
-
-// ============================================================================
-// 문서 리비전 (GetExternalInboxList에서 추출)
-// 컬럼명: API 필드명과 동일하게 유지 (UPPER_SNAKE_CASE)
-// ============================================================================
-
-export const swpDocumentRevisions = swpSchema.table(
- "swp_document_revisions",
- {
- // Primary Key
- id: serial("id").primaryKey(),
-
- // Document Reference (NO FK - 외래키 제거)
- DOC_NO: varchar("DOC_NO", { length: 1000 }).notNull(),
-
- // 리비전 정보
- REV_NO: varchar("REV_NO", { length: 1000 }).notNull(),
- STAGE: varchar("STAGE", { length: 1000 }).notNull(),
-
- // Activity 정보
- ACTV_NO: varchar("ACTV_NO", { length: 1000 }),
- ACTV_SEQ: varchar("ACTV_SEQ", { length: 1000 }),
- BOX_SEQ: varchar("BOX_SEQ", { length: 1000 }),
- OFDC_NO: varchar("OFDC_NO", { length: 1000 }),
-
- // 프로젝트/패키지 정보 (파일 API에서만 제공)
- PROJ_NO: varchar("PROJ_NO", { length: 1000 }),
- PKG_NO: varchar("PKG_NO", { length: 1000 }),
- VNDR_CD: varchar("VNDR_CD", { length: 1000 }),
- CPY_CD: varchar("CPY_CD", { length: 1000 }),
-
- // 동기화 메타데이터
- sync_status: syncStatusEnum("sync_status").default("synced").notNull(),
- last_synced_at: timestamp("last_synced_at").defaultNow().notNull(),
- created_at: timestamp("created_at").defaultNow().notNull(),
- updated_at: timestamp("updated_at").defaultNow().notNull(),
- },
- (table) => ({
- // Unique constraint: 문서당 리비전은 유일
- docRevUnique: uniqueIndex("swp_doc_rev_unique_idx").on(
- table.DOC_NO,
- table.REV_NO
- ),
- docNoIdx: index("swp_revisions_doc_no_idx").on(table.DOC_NO),
- revNoIdx: index("swp_revisions_rev_no_idx").on(table.REV_NO),
- stageIdx: index("swp_revisions_stage_idx").on(table.STAGE),
- })
-);
-
-// ============================================================================
-// 첨부파일 (GetExternalInboxList)
-// 컬럼명: API 필드명과 동일하게 유지 (UPPER_SNAKE_CASE)
-// ============================================================================
-
-export const swpDocumentFiles = swpSchema.table(
- "swp_document_files",
- {
- // Primary Key
- id: serial("id").primaryKey(),
-
- // Foreign Key
- revision_id: serial("revision_id")
- .notNull()
- .references(() => swpDocumentRevisions.id, { onDelete: "cascade" }),
-
- // 파일 정보
- FILE_NM: varchar("FILE_NM", { length: 1000 }).notNull(),
- FILE_SEQ: varchar("FILE_SEQ", { length: 1000 }).notNull(),
- FILE_SZ: varchar("FILE_SZ", { length: 1000 }),
- FLD_PATH: varchar("FLD_PATH", { length: 1000 }),
-
- // 문서 참조 (조회 편의용, 비정규화)
- DOC_NO: varchar("DOC_NO", { length: 1000 }).notNull(),
-
- // 상태 정보
- STAT: varchar("STAT", { length: 1000 }),
- STAT_NM: varchar("STAT_NM", { length: 1000 }),
- IDX: varchar("IDX", { length: 1000 }),
-
- // Activity 정보
- ACTV_NO: varchar("ACTV_NO", { length: 1000 }),
-
- // 이력 정보 (SWP)
- CRTER: varchar("CRTER", { length: 1000 }),
- CRTE_DTM: varchar("CRTE_DTM", { length: 1000 }),
- CHGR: varchar("CHGR", { length: 1000 }),
- CHG_DTM: varchar("CHG_DTM", { length: 1000 }),
-
- // 동기화 메타데이터
- sync_status: syncStatusEnum("sync_status").default("synced").notNull(),
- last_synced_at: timestamp("last_synced_at").defaultNow().notNull(),
- created_at: timestamp("created_at").defaultNow().notNull(),
- updated_at: timestamp("updated_at").defaultNow().notNull(),
- },
- (table) => ({
- // Unique constraint: 리비전당 파일 시퀀스는 유일
- revFileUnique: uniqueIndex("swp_rev_file_unique_idx").on(
- table.revision_id,
- table.FILE_SEQ
- ),
- revisionIdIdx: index("swp_files_revision_id_idx").on(table.revision_id),
- docNoIdx: index("swp_files_doc_no_idx").on(table.DOC_NO),
- fileNmIdx: index("swp_files_file_nm_idx").on(table.FILE_NM),
- })
-);
-
-// ============================================================================
-// TYPES
-// ============================================================================
-
-export type SwpDocument = typeof swpDocuments.$inferSelect;
-export type SwpDocumentInsert = typeof swpDocuments.$inferInsert;
-
-export type SwpDocumentRevision = typeof swpDocumentRevisions.$inferSelect;
-export type SwpDocumentRevisionInsert =
- typeof swpDocumentRevisions.$inferInsert;
-
-export type SwpDocumentFile = typeof swpDocumentFiles.$inferSelect;
-export type SwpDocumentFileInsert = typeof swpDocumentFiles.$inferInsert;
-
diff --git a/db/schema/index.ts b/db/schema/index.ts
index ea39ae8c..85258371 100644
--- a/db/schema/index.ts
+++ b/db/schema/index.ts
@@ -84,6 +84,3 @@ export * from './avl/vendor-pool';
// === Email Logs 스키마 ===
export * from './emailLogs';
export * from './emailWhitelist';
-
-// SWP 문서/첨부파일 테이블 및 뷰 스키마
-export * from './SWP/swp-documents'; \ No newline at end of file
diff --git a/lib/auth/custom-signout.ts b/lib/auth/custom-signout.ts
index d59bd81c..6f3a6b01 100644
--- a/lib/auth/custom-signout.ts
+++ b/lib/auth/custom-signout.ts
@@ -11,19 +11,28 @@ interface CustomSignOutOptions {
/**
* 커스텀 로그아웃 함수
*
- * @param options - callbackUrl: 로그아웃 후 이동할 URL (기본: 현재 origin + "/")
+ * @param options - callbackUrl: 로그아웃 후 이동할 URL (상대 경로 권장: "/ko/partners")
* @param options - redirect: 자동 리다이렉트 여부 (기본: true)
*/
export async function customSignOut(options?: CustomSignOutOptions): Promise<void> {
const { callbackUrl, redirect = true } = options || {};
+ console.log('[customSignOut] 시작:', {
+ currentOrigin: window.location.origin,
+ currentHref: window.location.href,
+ callbackUrl,
+ redirect,
+ });
+
try {
// 1. CSRF 토큰 가져오기
const csrfResponse = await fetch('/api/auth/csrf');
const { csrfToken } = await csrfResponse.json();
- // 2. 서버에 로그아웃 요청
- await fetch('/api/auth/signout', {
+ console.log('[customSignOut] CSRF 토큰 획득');
+
+ // 2. 서버에 로그아웃 요청 (리다이렉트 방지)
+ const signoutResponse = await fetch('/api/auth/signout', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
@@ -32,18 +41,75 @@ export async function customSignOut(options?: CustomSignOutOptions): Promise<voi
csrfToken,
json: 'true',
}),
+ redirect: 'manual', // ⭐ 서버의 리다이렉트를 자동으로 따라가지 않음
+ });
+
+ console.log('[customSignOut] 서버 응답:', {
+ status: signoutResponse.status,
+ statusText: signoutResponse.statusText,
+ redirected: signoutResponse.redirected,
+ url: signoutResponse.url,
});
- // 3. 리다이렉트
+ // 3. NextAuth 세션 쿠키 즉시 삭제 (middleware가 감지하도록)
+ document.cookie = 'next-auth.session-token=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;';
+ document.cookie = '__Secure-next-auth.session-token=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT; Secure;';
+
+ console.log('[customSignOut] 세션 쿠키 삭제 완료');
+
+ // 4. 리다이렉트
if (redirect) {
- const finalUrl = callbackUrl || window.location.origin;
+ // ⭐ URL 객체로 변환하여 상대 경로인지 확인
+ let finalUrl: string;
+
+ if (callbackUrl) {
+ try {
+ // callbackUrl이 절대 URL인 경우 (http:// 또는 https://로 시작)
+ if (callbackUrl.startsWith('http://') || callbackUrl.startsWith('https://')) {
+ const urlObj = new URL(callbackUrl);
+ // 같은 origin인 경우 pathname만 사용 (상대 경로로 변환)
+ if (urlObj.origin === window.location.origin) {
+ finalUrl = urlObj.pathname + urlObj.search + urlObj.hash;
+ } else {
+ finalUrl = callbackUrl;
+ }
+ } else {
+ // 이미 상대 경로인 경우 (/, /ko/partners 등)
+ finalUrl = callbackUrl;
+ }
+ } catch (error) {
+ console.error('[customSignOut] callbackUrl 파싱 오류:', error);
+ finalUrl = callbackUrl; // 오류 시 원본 사용
+ }
+ } else {
+ // callbackUrl이 없는 경우 루트 경로로
+ finalUrl = '/';
+ }
+
+ console.log('[customSignOut] 리다이렉트 실행:', finalUrl);
window.location.href = finalUrl;
}
} catch (error) {
console.error('Custom sign out error:', error);
// 에러 발생 시에도 리다이렉트 (세션이 이미 만료되었을 수 있음)
if (redirect) {
- const finalUrl = callbackUrl || window.location.origin;
+ let finalUrl = '/';
+ if (callbackUrl) {
+ try {
+ if (callbackUrl.startsWith('http://') || callbackUrl.startsWith('https://')) {
+ const urlObj = new URL(callbackUrl);
+ if (urlObj.origin === window.location.origin) {
+ finalUrl = urlObj.pathname + urlObj.search + urlObj.hash;
+ } else {
+ finalUrl = callbackUrl;
+ }
+ } else {
+ finalUrl = callbackUrl;
+ }
+ } catch {
+ finalUrl = callbackUrl;
+ }
+ }
window.location.href = finalUrl;
}
}