1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
|
// lib/middleware/page-tracking.ts - 페이지 방문 추적 미들웨어
import { NextRequest, NextResponse } from 'next/server'
import { getToken } from 'next-auth/jwt'
import { UAParser } from 'ua-parser-js';
import { SessionRepository } from '../session/repository'
export async function trackPageVisit(request: NextRequest) {
try {
const token = await getToken({ req: request })
const url = new URL(request.url)
// API 경로나 정적 파일은 추적하지 않음
if (url.pathname.startsWith('/api') ||
url.pathname.startsWith('/_next') ||
url.pathname.includes('.')) {
return
}
const userAgent = request.headers.get('user-agent') || ''
const parser = new UAParser(userAgent)
const result = parser.getResult()
// 활성 세션 조회
let sessionId = null
if (token?.id) {
const activeSession = await SessionRepository.getActiveSessionByUserId(token.id)
if (activeSession) {
sessionId = activeSession.id
// 세션 활동 시간 업데이트
await SessionRepository.updateLoginSession(activeSession.id, {
lastActivityAt: new Date()
})
}
}
// 페이지 방문 기록
await SessionRepository.recordPageVisit({
userId: token?.id || undefined,
sessionId,
route: url.pathname,
pageTitle: extractPageTitle(url.pathname), // 구현 필요
referrer: request.headers.get('referer') || undefined,
ipAddress: getClientIP(request),
userAgent,
queryParams: url.search ? url.search.substring(1) : undefined,
deviceType: getDeviceType(result.device.type),
browserName: result.browser.name,
osName: result.os.name,
})
} catch (error) {
console.error('Failed to track page visit:', error)
}
}
function getClientIP(request: NextRequest): string {
const forwarded = request.headers.get('x-forwarded-for');
const realIP = request.headers.get('x-real-ip');
const cfConnectingIP = request.headers.get('cf-connecting-ip'); // Cloudflare
if (cfConnectingIP) {
return cfConnectingIP;
}
if (forwarded) {
return forwarded.split(',')[0].trim();
}
if (realIP) {
return realIP;
}
// NextRequest에는 ip 프로퍼티가 없으므로 기본값 반환
return '127.0.0.1';
}
function getDeviceType(deviceType?: string): string {
if (!deviceType) return 'desktop'
if (deviceType === 'mobile') return 'mobile'
if (deviceType === 'tablet') return 'tablet'
return 'desktop'
}
function extractPageTitle(pathname: string): string {
// 라우트 기반 페이지 제목 매핑
const titleMap: Record<string, string> = {
'/': 'Home',
'/dashboard': 'Dashboard',
'/profile': 'Profile',
'/settings': 'Settings',
// 추가 필요
}
return titleMap[pathname] || pathname
}
|