Public Source Viewer

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

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

Redacted View
src/services/archive-search.service.ts
공개 가능
1 import fs from 'fs';
2 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
3 import { loadPosts } from './blog.service';
4 import { listJapaneseLoungeEntries } from './japanese-lounge.service';
5 import { getUserAiNotes } from './ai-note.service';
6 import { getUserPersonalNotes } from './personal-note.service';
7 import { listVoicepeakJobs } from './voicepeak.service';
8
9 export type ArchiveSearchItem = { id: string; type: string; typeLabel: string; title: string; excerpt: string; date: string; url: string; restricted: boolean };
10
11 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
12 function plain(value: unknown): string { return String(value || '').replace(/<[^>]*>/g, ' ').replace(/\s+/g, ' ').trim(); }
13 function excerpt(value: unknown): string { const text = plain(value); return text.length > 220 ? `${text.slice(0, 220)}...` : text; }
14 function matches(item: ArchiveSearchItem, query: string): boolean { const haystack = `${item.title}\n${item.excerpt}`.toLocaleLowerCase(); return !query || haystack.includes(query); }
15
16 export function searchArchive(username: string | null, rawQuery: string, rawType: string): ArchiveSearchItem[] {
17 const query = rawQuery.trim().toLocaleLowerCase().slice(0, 120);
18 const type = rawType.trim();
19 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
20 const items: ArchiveSearchItem[] = [];
21
22 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
23 [...(Array.isArray(mainPosts) ? mainPosts : []), ...loadPosts()].forEach((post: any) => {
24 const owner = post.username || post.author;
25 if (post.isPrivate && owner !== username && !isAdmin) return;
26 const id = String(post.id);
27 const isLibraryPost = Boolean(post.createdAt && post.author);
28 items.push({ id: `blog-${id}`, type: 'blog', typeLabel: '블로그', title: plain(post.title) || '제목 없는 기록', excerpt: excerpt(post.content), date: post.createdAt || post.timestamp || '', url: isLibraryPost ? `/hinana/post/${encodeURIComponent(id)}` : `/hinana/index?selectedId=${encodeURIComponent(id)}`, restricted: Boolean(post.isPrivate) });
29 });
30
31 listJapaneseLoungeEntries().forEach((entry) => items.push({ id: `jp-${entry.id}`, type: 'japanese', typeLabel: '일본어 라운지', title: entry.title, excerpt: excerpt(`${entry.japaneseText}\n${entry.koreanText}`), date: entry.updatedAt || entry.createdAt, url: `/hinana/japanese-lounge?entry=${encodeURIComponent(entry.id)}`, restricted: false }));
32
33 if (username) getUserAiNotes(username).forEach((note) => items.push({ id: `ai-${note.id}`, type: 'ai-note', typeLabel: 'AI 노트', title: note.title || note.question, excerpt: excerpt(`${note.question}\n${note.excerpt}`), date: note.updatedAt || note.createdAt, url: '/hinana/ai/notes', restricted: true }));
34 if (isAdmin) getUserPersonalNotes(username!).forEach((note) => items.push({ id: `personal-${note.id}`, type: 'personal-note', typeLabel: '개인 노트', title: note.title, excerpt: excerpt(note.content), date: note.updatedAt || note.createdAt, url: '/hinana/personal-notes', restricted: true }));
35 if (isAdmin) listVoicepeakJobs().forEach((job: any) => items.push({ id: `voice-${job.id}`, type: 'voicepeak', typeLabel: 'Voicepeak', title: job.title, excerpt: excerpt((job.sentences || []).map((sentence: any) => sentence.text).join(' ')), date: new Date(job.updatedAt || job.createdAt).toISOString(), url: '/hinana/voicepeak', restricted: true }));
36
37 [SECURITY REDACTED] 민감한 설정/인증/토큰 관련 코드입니다.
38 Object.entries(views || {}).forEach(([id, view]: [string, any]) => items.push({ id: `view-${id}`, type: 'ai-view', typeLabel: '공개 AI 원문', title: plain(view.question) || 'AI 대화', excerpt: excerpt(view.answer), date: view.createdAt || '', url: `/hinana/ai/view/${encodeURIComponent(id)}`, restricted: false }));
39
40 return items.filter((item) => (!type || item.type === type) && matches(item, query)).sort((a, b) => Date.parse(b.date || '1970-01-01') - Date.parse(a.date || '1970-01-01')).slice(0, query ? 200 : 30);
41 }
42