유저 관리
NestJS Admin API를 활용한 유저 관리 시스템
Portfolio-Server의 Admin API를 활용한 유저 관리 시스템입니다. NestJS와 TypeORM으로 구축된 백엔드 API와 연동합니다.
주요 기능
- • JWT 인증: JwtAuthGuard 기반 보안
- • 동적 헤더: selected-user-id 헤더로 유저 컨텍스트 전환
- • 고급 필터: FilterDto 기반 다양한 필터 타입 지원
- • 페이지네이션: PaginationInDto/OutDto 구조
Portfolio-Server API
- • User: 유저 기본 정보 조회
- • UserDetail: 게임 재화 및 설정
- • Character: 보유 캐릭터 목록
- • Mission: 미션 진행 상태
- • Arena: 최고 점수 및 히스토리
현재 상태
포트폴리오에서는 먼저 인증 및 필터/페이지네이션 표준화를 구축하고, 유저·상세·캐릭터·미션·점수 API를 단계적으로 연동했습니다. 프런트는 표준 필터 UI와 JWT 기반 접근 제어를 사용해 운영 편의성과 보안을 함께 확보했습니다.
로그인
유저 목록
인증 정보
NestJS Admin API에 접근하기 위해서는 먼저 로그인이 필요합니다.
- • 이메일: 아무 이메일 형식으로 넣어주세요
- • 패스워드: 소문자, 대문자, 숫자, 특수문자, 10글자 이상
- • 로그인 성공 시 Access Token이 자동으로 저장됩니다
로그인
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
import { plainToInstance } from 'class-transformer';
import { PaginationInDto } from '@libs/common/dto/pagination-in.dto';
// 쿼리 파라미터를 PaginationInDto로 자동 변환하는 데코레이터
// - page, size, order, filter를 자동으로 파싱
// - filter는 JSON 문자열을 객체로 변환
export const QueryFilter = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
const value = request.query;
// eslint-disable-next-line prefer-const
let { page, size, order, ...filters } = value;
// 기본값 설정
page = page || 1;
size = size || 20;
order = order || 'DESC';
// 필터 객체 파싱 (JSON 문자열 → 객체)
const filterObj: any = {};
for (const key of Object.keys(filters)) {
try {
filterObj[key] = JSON.parse(filters[key]);
} catch {
// JSON 파싱 실패 시 무시
}
}
// PaginationInDto 인스턴스로 변환
return plainToInstance(PaginationInDto, {
page,
size,
order,
filter: filterObj,
});
},
);로그 조회
포트폴리오 서버의 로그 조회
MongoDB
- • 애플리케이션 DB와 동일 스택 사용 가능: 운영 단순화
- • 단일/복합/TTL 인덱스, Aggregation Pipeline으로 유연한 쿼리
- • 컬렉션 파티셔닝(샤딩)으로 수평 확장 가능
- • 소~중규모 로그에 비용 대비 성능 우수
- • 운영 복잡도와 러닝 커브 낮음
- • 단점: 전문(Full‑text) 검색/분석은 한계, 스키마 관리 필요
Elasticsearch
- • 역색인 기반 실시간 전문 검색·집계(terms, range, full‑text)
- • Kibana 대시보드/Alerting으로 시각화·모니터링 용이
- • ILM(라이프사이클 관리)로 핫‑웜‑콜드 티어링 가능
- • 대용량 로그/고급 검색 시 탁월한 응답 성능
- • 단점: 별도 클러스터 운영·튜닝 비용, 스토리지 비용 부담
- • 최적: 검색/집계가 많은 실시간 운영 대시보드
BigQuery
- • 서버리스 컬럼형 데이터 웨어하우스: 관리 부담 최소화
- • 파티션/클러스터링으로 대용량 비용·성능 최적화
- • SQL 기반 배치 분석/리포팅에 강점, 장기 보관 용이
- • Looker Studio 등과 연계한 시각화·리포팅 수월
- • 단점: 실시간성 낮음(스트리밍 가능하나 비용·지연 고려 필요)
- • 최적: 월간/주간 통계, 코호트·추세 분석, 비용 예측
로그인
로그 조회
인증 정보
로그 조회 API에 접근하기 위해서는 먼저 로그인이 필요합니다.
- • 이메일: 아무 이메일 형식으로 넣어주세요
- • 패스워드: 소문자, 대문자, 숫자, 특수문자, 10글자 이상
- • 로그인 성공 시 Access Token이 자동으로 저장됩니다
로그인
@ApiTags('mongo-log')
@Controller('admin/mongo-log')
@ApiBearerAuth('jwt')
@UseGuards(JwtAuthGuard)
export class MongoLogController {
constructor(private readonly mongoLogService: MongoLogService) {}
@Get('')
@ApiResponseEntity({
summary: '몽고 로그 컬렉션 조회',
})
async getMongoLogCollection(): Promise<ResponseEntity<string[]>> {
const collections = this.mongoLogService.getMongoLogCollection();
return ResponseEntity.ok().body(collections);
}
@Post('search')
@ApiResponseEntity({
type: GetMongoLogsOutDto,
isPagination: true,
summary: '몽고 로그 조회',
})
async getMongoLogs(
@Body() getMongoLogsInDto: GetMongoLogsInDto,
): Promise<ResponseEntity<GetMongoLogsOutDto>> {
const getMongoLogsOutDto =
await this.mongoLogService.getMongoLog(getMongoLogsInDto);
return ResponseEntity.ok().body(getMongoLogsOutDto);
}
}Admin Tool (Ruby on Rails ActiveAdmin)
Ruby on Rails ActiveAdmin을 활용한 운영 관리 도구
장점
- • 빠른 개발: 모델만 정의하면 CRUD 인터페이스 자동 생성
- • 고급 필터: Ransack 기반 검색/정렬/필터링 자동 생성
- • 데이터 익스포트: CSV/JSON/XML 다운로드 기능 내장
- • 로컬라이징: I18n.t 사용으로 다국어 지원
- • DSL 기반: 간단한 코드로 복잡한 관리 기능 구현
- • Rails 통합: Devise(인증), Pundit(권한), ActiveJob(백그라운드) 자동 연동
- • 권한 관리: 역할 기반 세밀한 접근 제어
- • 일관된 UI: 통일된 관리 인터페이스
단점
- • DB 직접 연결: 트랜잭션 없이 직접 수정, 롤백 불가
- • 성능 제약: N+1 쿼리, 대용량 데이터 처리 어려움
- • 학습 곡선: ActiveAdmin만의 DSL 문법 학습 필요
- • 커스터마이징: 복잡한 프론트엔드 로직 구현 제한
- • 유지보수: Rails 업그레이드 시 호환성 문제
- • UI 제약: 기본 테마에서 벗어나기 어려움
개선점
- • Index 페이지 활용: 빠른 개발, 고급 필터, 데이터 익스포트 장점 극대화
- • DB 읽기 전용 권한: 운영툴 DB 연결은 SELECT만 가능, 직접 수정/삭제 차단
- • NestJS REST API: 모든 CUD(Create/Update/Delete) 작업은 REST API를 통해서만 수행
- • 보안 강화: API 레벨에서 검증 및 권한 제어, 트랜잭션 보장
- • 휴먼 에러 방지: 서비스 레이어에서 비즈니스 로직 검증, 롤백 가능
class ApplicationController < ActionController::Base
# CSRF 공격 방어 - 예외 발생 시 세션 리셋
protect_from_forgery with: :exception
# 모든 요청 전에 IP 화이트리스트 검증
before_action :verify_ip_address
# 모든 요청 전에 세션 만료 확인
before_action :check_session_expiry
private
# 세션 만료 시간 체크
# - Devise timeout 설정 기반으로 세션 유효성 검증
# - 마지막 활동 시간(last_seen)과 현재 시간 차이로 만료 여부 판단
def check_session_expiry
# 세션에서 마지막 활동 시간 조회 (없으면 현재 시간)
last_seen = session[:last_seen] ? Time.parse(session[:last_seen]) : Time.current
# Devise 설정의 타임아웃 시간 조회 (초 단위)
session_expiry_duration = Devise.timeout_in.to_i
# 마지막 활동 이후 경과 시간이 타임아웃보다 크면 세션 만료
if (Time.current - last_seen).to_i > session_expiry_duration
# 현재 관리자 사용자 로그아웃
sign_out current_admin_user
# 관리자 루트로 리다이렉트 + 경고 메시지
redirect_to admin_root_path, alert: "Your session has expired. Please log in again."
else
# 세션 유효 시 마지막 활동 시간 갱신
session[:last_seen] = Time.current.to_s
end
end
# IP 화이트리스트 검증
# - X-Forwarded-For 헤더에서 실제 클라이언트 IP 추출
# - 화이트리스트에 등록된 IP만 접근 허용
def verify_ip_address
# 프록시를 거친 경우 X-Forwarded-For 헤더에서 원본 IP 추출
forwarded_ips = request.env['HTTP_X_FORWARDED_FOR']
remote_ip = forwarded_ips ? forwarded_ips.split(',').first.strip : request.remote_ip
# 화이트리스트에 없고 + 프로덕션 환경 + health 체크 아니면 401 Unauthorized 반환
head :unauthorized if Whitelist.find_by(ip_address: remote_ip).nil? && !Rails.env.development? && request.path != '/health'
end
end