Public Source Viewer
비나래아카이브 개발자 포털
실제 서비스 구조를 살펴볼 수 있는 공개용 코드 뷰어입니다. 인증, 세션, 외부 연동, 토큰, 관리자 식별 등 보안상 민감한 구현은 파일 단위 또는 줄 단위로 검열됩니다.
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