본문으로 건너뛰기
Public Source Viewer

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

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

Redacted View
src/services/hinana-rail.service.ts
공개 가능
1 import fs from 'fs';
2 import path from 'path';
3 import { createHash, randomUUID } from 'crypto';
4 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
5
6 export const RAIL_COLLECTION_TYPES = ['KTX-I', 'KTX-산천', 'SRT', 'KTX-이음', 'KTX-청룡', 'ITX-청춘'];
7 export const RAIL_TYPES = [...RAIL_COLLECTION_TYPES, 'KTX (미확인)', 'ITX-새마을', 'ITX-마음', '무궁화호', '기타'];
8 export interface RailTrip {
9 id: string; date: string; departure: string; arrival: string; departureTime: string; arrivalTime: string;
10 trainType: string; trainNumber: string; seat: string; seatClass: string; fare: number | null;
11 distance: number | null; line: string; note: string; photos: string[]; createdAt: string;
12 }
13 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
14 export function railUserDir(username: string): string {
15 return path.join(root, createHash('sha256').update(username).digest('hex'));
16 }
17 export function readRailTrips(username: string): RailTrip[] {
18 try {
19 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
20 if (!Array.isArray(rows)) throw new Error('Invalid rail data');
21 return rows;
22 } catch (err: any) { if (err.code === 'ENOENT') return []; throw err; }
23 }
24 function writeTrips(username: string, trips: RailTrip[]): void {
25 const dir = railUserDir(username);
26 fs.mkdirSync(dir, { recursive: true });
27 const temp = path.join(dir, 'trips.tmp');
28 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
29 fs.renameSync(temp, path.join(dir, 'trips.json'));
30 }
31 export function validateRailTrip(input: any): Omit<RailTrip, 'id' | 'photos' | 'createdAt'> {
32 const text = (key: string, max = 80) => typeof input[key] === 'string' ? input[key].trim().slice(0, max) : '';
33 const date = text('date', 10);
34 if (!/^\d{4}-\d{2}-\d{2}$/.test(date) || !Number.isFinite(Date.parse(date)) || new Date(date).toISOString().slice(0, 10) !== date) throw new Error('이용 날짜를 확인해 주세요.');
35 const departure = text('departure'), arrival = text('arrival');
36 if (!departure || !arrival || departure === arrival) throw new Error('출발역과 도착역을 확인해 주세요.');
37 const departureTime = text('departureTime'), arrivalTime = text('arrivalTime');
38 for (const time of [departureTime, arrivalTime]) if (time && !/^([01]\d|2[0-3]):[0-5]\d$/.test(time)) throw new Error('시간을 확인해 주세요.');
39 const number = (key: string, max: number) => {
40 if (input[key] === '' || input[key] == null) return null;
41 const value = Number(input[key]);
42 if (!Number.isFinite(value) || value < 0 || value > max) throw new Error('금액 또는 거리를 확인해 주세요.');
43 return value;
44 };
45 const trainType = text('trainType');
46 if (!RAIL_TYPES.includes(trainType)) throw new Error('차종을 선택해 주세요.');
47 return { date, departure, arrival, departureTime, arrivalTime, trainType, trainNumber: text('trainNumber', 20), seat: text('seat', 40), seatClass: text('seatClass', 30), fare: number('fare', 10000000), distance: number('distance', 10000), line: text('line'), note: text('note', 2000) };
48 }
49 export function saveRailTrip(username: string, input: any, photos: string[], id?: string): RailTrip {
50 const fields = validateRailTrip(input);
51 const trips = readRailTrips(username);
52 const index = id ? trips.findIndex(t => t.id === id) : -1;
53 if (id && index < 0) throw new Error('기록을 찾을 수 없습니다.');
54 const previous = index >= 0 ? trips[index] : null;
55 const trip = { ...fields, id: previous?.id || randomUUID(), createdAt: previous?.createdAt || new Date().toISOString(), photos: [...(previous?.photos || []), ...photos] };
56 if (trip.photos.length > 6) throw new Error('사진은 기록당 6장까지 가능합니다.');
57 if (index >= 0) trips[index] = trip; else trips.unshift(trip);
58 writeTrips(username, trips);
59 return trip;
60 }
61 export function deleteRailTrip(username: string, id: string): boolean {
62 const trips = readRailTrips(username), trip = trips.find(t => t.id === id);
63 if (!trip) return false;
64 writeTrips(username, trips.filter(t => t.id !== id));
65 for (const photo of trip.photos) fs.rmSync(path.join(railUserDir(username), photo), { force: true });
66 return true;
67 }
68