diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-07-07 01:44:45 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-07-07 01:44:45 +0000 |
| commit | 90f79a7a691943a496f67f01c1e493256070e4de (patch) | |
| tree | 37275fde3ae08c2bca384fbbc8eb378de7e39230 /db/schema/history.ts | |
| parent | fbb3b7f05737f9571b04b0a8f4f15c0928de8545 (diff) | |
(대표님) 변경사항 20250707 10시 43분 - unstaged 변경사항 추가
Diffstat (limited to 'db/schema/history.ts')
| -rw-r--r-- | db/schema/history.ts | 113 |
1 files changed, 113 insertions, 0 deletions
diff --git a/db/schema/history.ts b/db/schema/history.ts new file mode 100644 index 00000000..13b00196 --- /dev/null +++ b/db/schema/history.ts @@ -0,0 +1,113 @@ +import { + pgTable, + uuid, + varchar, + timestamp, + text, + boolean, + integer, + inet +} from 'drizzle-orm/pg-core'; +import { relations } from 'drizzle-orm'; +import { users } from './users'; + +// 로그인 세션 테이블 (로그인/로그아웃 이력) +export const loginSessions = pgTable('login_sessions', { + id: uuid('id').primaryKey().defaultRandom(), + userId: integer('user_id').references(() => users.id, { onDelete: 'cascade' }).notNull(), + loginAt: timestamp('login_at').defaultNow().notNull(), + logoutAt: timestamp('logout_at'), // 로그아웃 시간 (nullable) + ipAddress: inet('ip_address').notNull(), + userAgent: text('user_agent'), + sessionToken: varchar('session_token', { length: 255 }).unique(), // NextAuth JWT token ID + nextAuthSessionId: varchar('nextauth_session_id', { length: 255 }).unique(), // NextAuth 세션 ID + authMethod: varchar('auth_method', { length: 50 }).notNull(), // 'otp', 'email', 'sgips', 'saml' + isActive: boolean('is_active').default(true).notNull(), + lastActivityAt: timestamp('last_activity_at').defaultNow().notNull(), // 마지막 활동 시간 + sessionExpiredAt: timestamp('session_expired_at'), // 세션 만료 시간 + createdAt: timestamp('created_at').defaultNow().notNull(), + updatedAt: timestamp('updated_at').defaultNow().notNull(), +}); + +// 페이지 방문 이력 테이블 (라우트별 접속 이력) +export const pageVisits = pgTable('page_visits', { + id: uuid('id').primaryKey().defaultRandom(), + userId: integer('user_id').references(() => users.id, { onDelete: 'cascade' }), // nullable (비로그인 사용자 추적 가능) + sessionId: uuid('session_id').references(() => loginSessions.id, { onDelete: 'set null' }), // nullable + route: varchar('route', { length: 500 }).notNull(), // 방문한 라우트 + pageTitle: varchar('page_title', { length: 200 }), // 페이지 제목 + referrer: text('referrer'), // 이전 페이지 URL + ipAddress: inet('ip_address').notNull(), + userAgent: text('user_agent'), + visitedAt: timestamp('visited_at').defaultNow().notNull(), + duration: integer('duration'), // 페이지 체류 시간 (초 단위, nullable) + // 추가 메타데이터 + queryParams: text('query_params'), // URL 쿼리 파라미터 + deviceType: varchar('device_type', { length: 50 }), // mobile, desktop, tablet + browserName: varchar('browser_name', { length: 50 }), + osName: varchar('os_name', { length: 50 }), +}); + +// 일별 접속 통계 테이블 (선택사항 - 성능 최적화용) +export const dailyAccessStats = pgTable('daily_access_stats', { + id: uuid('id').primaryKey().defaultRandom(), + date: timestamp('date').notNull(), + totalVisits: integer('total_visits').default(0).notNull(), + uniqueUsers: integer('unique_users').default(0).notNull(), + totalSessions: integer('total_sessions').default(0).notNull(), + avgSessionDuration: integer('avg_session_duration'), // 평균 세션 지속 시간 (초) + createdAt: timestamp('created_at').defaultNow().notNull(), +}); + +// Relations 정의 +export const usersRelationsLogin = relations(users, ({ many }) => ({ + loginSessions: many(loginSessions), + pageVisits: many(pageVisits), +})); + +export const loginSessionsRelations = relations(loginSessions, ({ one, many }) => ({ + user: one(users, { + fields: [loginSessions.userId], + references: [users.id], + }), + pageVisits: many(pageVisits), +})); + +export const pageVisitsRelations = relations(pageVisits, ({ one }) => ({ + user: one(users, { + fields: [pageVisits.userId], + references: [users.id], + }), + session: one(loginSessions, { + fields: [pageVisits.sessionId], + references: [loginSessions.id], + }), +})); + + +// NextAuth 연동을 위한 추가 필드 +export const tempAuthSessions = pgTable('temp_auth_sessions', { + id: uuid('id').primaryKey().defaultRandom(), + tempAuthKey: varchar('temp_auth_key', { length: 255 }).unique().notNull(), + userId: integer('user_id').references(() => users.id, { onDelete: 'cascade' }).notNull(), + email: varchar('email', { length: 255 }).notNull(), + authMethod: varchar('auth_method', { length: 50 }).notNull(), // 'otp', 'email', 'sgips', 'saml' + expiresAt: timestamp('expires_at').notNull(), + isUsed: boolean('is_used').default(false).notNull(), + createdAt: timestamp('created_at').defaultNow().notNull(), +}); + + + +export type TempAuthSession = typeof tempAuthSessions.$inferSelect; +export type NewTempAuthSession = typeof tempAuthSessions.$inferInsert; + + +export type LoginSession = typeof loginSessions.$inferSelect; +export type NewLoginSession = typeof loginSessions.$inferInsert; + +export type PageVisit = typeof pageVisits.$inferSelect; +export type NewPageVisit = typeof pageVisits.$inferInsert; + +export type DailyAccessStats = typeof dailyAccessStats.$inferSelect; +export type NewDailyAccessStats = typeof dailyAccessStats.$inferInsert;
\ No newline at end of file |
