feat(sprint-004): admin features + API auth guard

Sprint 003 non-blocking 처리:
- API Key Guard (passport-http-bearer) + AuthModule
- Helmet 적용 (main.ts)
- SisterNamePipe - name 파라미터 Controller 검증
- lib/sisters.ts - formatLastSeen/sisterEmojis/sisterDisplayNames 공통 추출

Sprint 004 본문:
- TASK-012: POST /api/admin/sisters/:name/{restart,reset} (ApiKeyGuard 적용)
- TASK-013: GET/PUT /api/admin/harness/:name/:file (허용 파일 allowlist)
- TASK-015: GET /api/admin/logs/:name
- 관리자 전 API @UseGuards(ApiKeyGuard)
- TASK-014: /admin 자매 관리 (재시작/리셋 + ConfirmModal)
- TASK-014: /admin/harness 하네스 편집 (CodeEditor + 파일트리)
- TASK-015: /admin/logs 로그 뷰어 (LogTerminal + 검색/에러필터)
- /admin/repos 저장소 목록
- AdminLayout (API Key 로컬스토리지 저장)
- 테스트 22/22 pass, FE 11 routes build 성공
This commit is contained in:
2026-04-04 12:11:06 +09:00
parent 4e5a077d40
commit 033c142c0e
24 changed files with 1634 additions and 8 deletions

View File

@@ -1,9 +1,8 @@
import { Controller, Get, Param } from '@nestjs/common';
import { SistersService } from './sisters.service';
import { SisterDetailService } from './sister-detail.service';
const VALID_NAMES = ['harang', 'narang', 'darang', 'erang'] as const;
type SisterName = (typeof VALID_NAMES)[number];
import { SisterNamePipe } from '../common/sister-name.pipe';
import type { SisterName } from '../common/sister-name.pipe';
@Controller('api/sisters')
export class SistersController {
@@ -18,22 +17,22 @@ export class SistersController {
}
@Get(':name/config')
async getSisterConfig(@Param('name') name: string) {
async getSisterConfig(@Param('name', SisterNamePipe) name: SisterName) {
return this.sisterDetail.getSisterConfig(name as SisterName);
}
@Get(':name/sessions')
async getSisterSessions(@Param('name') name: string) {
async getSisterSessions(@Param('name', SisterNamePipe) name: SisterName) {
return this.sisterDetail.getSisterSessions(name as SisterName);
}
@Get(':name/subagents')
async getSisterSubagents(@Param('name') name: string) {
async getSisterSubagents(@Param('name', SisterNamePipe) name: SisterName) {
return this.sisterDetail.getSisterSubagents(name as SisterName);
}
@Get(':name/activity')
async getSisterActivity(@Param('name') name: string) {
async getSisterActivity(@Param('name', SisterNamePipe) name: SisterName) {
return this.sisterDetail.getSisterActivityLog(name as SisterName);
}
}