1+ import { Pool } from 'pg' ;
2+ import logger from '@/configs/logger.config' ;
3+ import { DBError , NotFoundError } from '@/exception' ;
4+ import { getCurrentKSTDateString , getKSTDateStringWithOffset } from '@/utils/date.util' ;
5+
6+ export class SvgRepository {
7+ constructor ( private pool : Pool ) { }
8+
9+ async getUserBadgeData ( username : string , withRank : boolean , dateRange : number = 30 ) {
10+ try {
11+ const pastDateKST = getKSTDateStringWithOffset ( - dateRange * 24 * 60 ) ;
12+ const cteQuery = this . buildBadgeCteQuery ( dateRange , pastDateKST ) ;
13+
14+ const userStatsQuery = `
15+ ${ cteQuery }
16+ SELECT
17+ u.username,
18+ COALESCE(SUM(ts.today_view), 0) AS total_views,
19+ COALESCE(SUM(ts.today_like), 0) AS total_likes,
20+ COUNT(DISTINCT CASE WHEN p.is_active = true THEN p.id END) AS total_posts,
21+ SUM(COALESCE(ts.today_view, 0) - COALESCE(ss.start_view, 0)) AS view_diff,
22+ SUM(COALESCE(ts.today_like, 0) - COALESCE(ss.start_like, 0)) AS like_diff,
23+ COUNT(DISTINCT CASE WHEN p.released_at >= '${ pastDateKST } ' AND p.is_active = true THEN p.id END) AS post_diff
24+ FROM users_user u
25+ LEFT JOIN posts_post p ON p.user_id = u.id
26+ LEFT JOIN today_stats ts ON ts.post_id = p.id
27+ LEFT JOIN start_stats ss ON ss.post_id = p.id
28+ WHERE u.username = $1
29+ GROUP BY u.username
30+ ` ;
31+ const userStatsResult = await this . pool . query ( userStatsQuery , [ username , dateRange ] ) ;
32+
33+ if ( userStatsResult . rows . length === 0 ) {
34+ throw new NotFoundError ( `사용자를 찾을 수 없습니다: ${ username } ` ) ;
35+ }
36+
37+ const recentPostsQuery = `
38+ ${ cteQuery }
39+ SELECT
40+ p.title,
41+ p.released_at,
42+ COALESCE(ts.today_view, 0) AS today_view,
43+ COALESCE(ts.today_like, 0) AS today_like,
44+ COALESCE(ts.today_view, 0) - COALESCE(ss.start_view, 0) AS view_diff
45+ FROM posts_post p
46+ JOIN users_user u ON u.id = p.user_id
47+ LEFT JOIN today_stats ts ON ts.post_id = p.id
48+ LEFT JOIN start_stats ss ON ss.post_id = p.id
49+ WHERE u.username = $1
50+ AND p.is_active = true
51+ ORDER BY p.released_at DESC
52+ LIMIT 3
53+ ` ;
54+ const recentPostsResult = await this . pool . query ( recentPostsQuery , [ username ] ) ;
55+
56+ return {
57+ ...userStatsResult . rows [ 0 ] ,
58+ recent_posts : recentPostsResult . rows ,
59+ } ;
60+ } catch ( error ) {
61+ if ( error instanceof NotFoundError ) {
62+ throw error ;
63+ }
64+
65+ logger . error ( 'SvgRepository getUserBadgeData error: ' , error ) ;
66+ throw new DBError ( '배지 데이터 조회 중 문제가 발생했습니다.' ) ;
67+ }
68+ }
69+
70+ private buildBadgeCteQuery ( dateRange : number , pastDateKST ?: string ) {
71+ const nowDateKST =
72+ new Date ( ) . getUTCHours ( ) === 15
73+ ? getKSTDateStringWithOffset ( - 24 * 60 )
74+ : getCurrentKSTDateString ( ) ;
75+
76+ if ( ! pastDateKST ) {
77+ pastDateKST = getKSTDateStringWithOffset ( - dateRange * 24 * 60 ) ;
78+ }
79+
80+ return `
81+ WITH
82+ today_stats AS (
83+ SELECT DISTINCT ON (post_id)
84+ post_id,
85+ daily_view_count AS today_view,
86+ daily_like_count AS today_like
87+ FROM posts_postdailystatistics
88+ WHERE date = '${ nowDateKST } '
89+ ),
90+ start_stats AS (
91+ SELECT DISTINCT ON (post_id)
92+ post_id,
93+ daily_view_count AS start_view,
94+ daily_like_count AS start_like
95+ FROM posts_postdailystatistics
96+ WHERE date = '${ pastDateKST } '
97+ )
98+ ` ;
99+ }
100+ }
0 commit comments