Public Source Viewer

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

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

Redacted View
src/services/persona.service.ts
공개 가능
1 import fs from 'fs';
2 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
3 import { Persona } from '../types/models';
4
5 const MAX_PERSONA_SLOTS = 3;
6
7 function readPersonas(): Persona[] {
8 try {
9 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
10 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
11 } catch {
12 return [];
13 }
14 }
15
16 function writePersonas(personas: Persona[]): void {
17 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
18 }
19
20 function normalizeSlot(slot?: number | string): number {
21 const parsed = Number.parseInt(String(slot || 1), 10);
22 if (!Number.isFinite(parsed)) return 1;
23 return Math.min(Math.max(parsed, 1), MAX_PERSONA_SLOTS);
24 }
25
26 function getPersonaSlot(persona: Persona): number {
27 return normalizeSlot(persona.personaSlot);
28 }
29
30 export function getPersonaByDiscordId(discordUserId: string): Persona | null {
31 const personas = getPersonasByDiscordId(discordUserId);
32 return personas.find(p => p.isActive)
33 || getPersonaByDiscordIdAndSlot(discordUserId, 1)
34 || getPersonasByDiscordId(discordUserId)[0]
35 || null;
36 }
37
38 export function getPersonaByDiscordIdAndSlot(discordUserId: string, slot: number | string): Persona | null {
39 const selectedSlot = normalizeSlot(slot);
40 return readPersonas().find(p => p.discordUserId === discordUserId && getPersonaSlot(p) === selectedSlot) || null;
41 }
42
43 export function getPersonasByDiscordId(discordUserId: string): Persona[] {
44 return readPersonas()
45 .filter(p => p.discordUserId === discordUserId)
46 .sort((a, b) => getPersonaSlot(a) - getPersonaSlot(b));
47 }
48
49 export function getPersonaByUsername(username: string): Persona | null {
50 return readPersonas().find(p => p.hinanaUsername === username && getPersonaSlot(p) === 1)
51 || getPersonasByUsername(username)[0]
52 || null;
53 }
54
55 export function getPersonasByUsername(username: string): Persona[] {
56 return readPersonas()
57 .filter(p => p.hinanaUsername === username)
58 .sort((a, b) => getPersonaSlot(a) - getPersonaSlot(b));
59 }
60
61 export function savePersona(data: Omit<Persona, 'createdAt' | 'updatedAt'>): void {
62 const personas = readPersonas();
63 const personaSlot = normalizeSlot(data.personaSlot);
64 const idx = personas.findIndex(p => p.discordUserId === data.discordUserId && getPersonaSlot(p) === personaSlot);
65 const now = new Date().toISOString();
66 const nextData = { ...data, personaSlot, isActive: true };
67 personas.forEach((persona) => {
68 if (persona.discordUserId === data.discordUserId) {
69 persona.isActive = getPersonaSlot(persona) === personaSlot;
70 }
71 });
72 if (idx !== -1) {
73 personas[idx] = { ...nextData, createdAt: personas[idx].createdAt, updatedAt: now };
74 } else {
75 const usedSlots = new Set(personas.filter(p => p.discordUserId === data.discordUserId).map(getPersonaSlot));
76 if (usedSlots.size >= MAX_PERSONA_SLOTS && !usedSlots.has(personaSlot)) {
77 throw new Error('PERSONA_SLOT_LIMIT_EXCEEDED');
78 }
79 personas.push({ ...nextData, createdAt: now, updatedAt: now });
80 }
81 writePersonas(personas);
82 }
83
84 export function hasPersona(discordUserId: string): boolean {
85 return readPersonas().some(p => p.discordUserId === discordUserId);
86 }
87
88 export { MAX_PERSONA_SLOTS, normalizeSlot };
89