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