Public Source Viewer

비나래아카이브 개발자 포털

실제 서비스 구조를 살펴볼 수 있는 공개용 코드 뷰어입니다. 인증, 세션, 외부 연동, 토큰, 관리자 식별 등 보안상 민감한 구현은 파일 단위 또는 줄 단위로 검열됩니다.

Redacted View
src/routes/game.routes.ts
공개 가능
1 import { Router, Request, Response } from 'express';
2 import fs from 'fs';
3 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
4 import { calculateHinanaScore, getLeaderboard } from '../services/tetris.service';
5 import { readSettings } from '../services/settings.service';
6 import { awardTetrisBookmarks, canClaimTetrisToday } from '../services/bookmark.service';
7
8 const router = Router();
9
10 router.get('/hinana/tetris', (req: Request, res: Response) => {
11 const settings = readSettings();
12 let allData = getLeaderboard();
13
14 if (!Array.isArray(allData)) allData = [];
15
16 const hinanaRank = [...allData]
17 .sort((a, b) => b.hinanaScore - a.hinanaScore)
18 .slice(0, 10);
19
20 const pureRank = [...allData]
21 .sort((a, b) => b.rawScore - a.rawScore)
22 .slice(0, 10);
23
24 res.render('hinana/tetris', {
25 theme: req.session.theme || req.cookies.theme || 'light',
26 username: req.session.username || null,
27 leaderboard: hinanaRank,
28 pureLeaderboard: pureRank,
29 isGptEnabled: settings.isGptEnabled,
30 isSignupEnabled: settings.isSignupEnabled,
31 isAnonymousPostingEnabled: settings.isAnonymousPostingEnabled,
32 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
33 canClaimTetris: req.session.username ? canClaimTetrisToday(req.session.username) : false
34 });
35 });
36
37 router.post('/hinana/tetris/score', (req: Request, res: Response) => {
38 const { score, time, lines, lines4, tspinLines = 0 } = req.body;
39 const username = req.session.username || 'Guest';
40
41 // Guest는 랭킹 등록 불가
42 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
43 return res.json({ success: false, message: "로그인 후 이용 가능합니다." });
44 }
45
46 // 플레이 타임 최소 90초
47 if (time < 90) {
48 return res.json({
49 success: false,
50 message: "플레이 타임이 90초 미만이라 랭킹에 등록되지 않았어요! (T >= 90s)"
51 });
52 }
53
54 // Sanity check: 논리 검증
55 if (typeof score !== 'number' || typeof time !== 'number' || typeof lines !== 'number' || typeof lines4 !== 'number' || typeof tspinLines !== 'number') {
56 return res.json({ success: false, message: "잘못된 데이터입니다." });
57 }
58 if (lines4 > lines || lines < 0 || lines4 < 0 || score < 0 || tspinLines < 0 || tspinLines > lines) {
59 return res.json({ success: false, message: "비정상적인 데이터입니다." });
60 }
61
62 const finalScore = calculateHinanaScore(score, time, lines, lines4, tspinLines);
63
64 const leaderboard = getLeaderboard();
65 leaderboard.push({
66 username: username,
67 hinanaScore: finalScore,
68 rawScore: score,
69 time: time,
70 lines: lines,
71 lines4: lines4,
72 tspinLines: tspinLines,
73 date: new Date().toISOString().split('T')[0]
74 });
75
76 leaderboard.sort((a, b) => b.hinanaScore - a.hinanaScore);
77 if (leaderboard.length > 100) leaderboard.length = 100;
78
79 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
80
81 res.json({ success: true, hinanaScore: finalScore });
82 });
83
84 router.post('/hinana/tetris/claim-bookmarks', (req: Request, res: Response) => {
85 const username = req.session.username;
86 if (!username) {
87 return res.json({ success: false, message: "로그인이 필요합니다." });
88 }
89
90 const { hinanaScore } = req.body;
91 if (typeof hinanaScore !== 'number' || hinanaScore < 0) {
92 return res.json({ success: false, message: "잘못된 데이터입니다." });
93 }
94
95 const result = awardTetrisBookmarks(username, hinanaScore);
96
97 if (result.alreadyClaimed) {
98 return res.json({ success: false, message: "오늘은 이미 책갈피를 전환했습니다." });
99 }
100
101 return res.json({ success: true, awarded: result.awarded, total: result.total });
102 });
103
104 export default router;
105