feat: 실시간 알림(인앱+SSE, Redis pub/sub) + Redis 캐싱 (8단계)
알림(8-2): - Notification 모듈 + SSE 스트림(@Sse, @SkipTransform 으로 표준 래퍼 우회). - 12종 알림 적재(업무 지시/해제/승인요청/승인/수정요청/시작/마감변경/삭제/댓글·답글, 멤버 초대/역할변경/제거). 행위자 제외 + 중복 dedup. AppShell 종(미읽음 뱃지·드롭다운). - 실시간 fan-out: REDIS_HOST 시 ioredis pub/sub(다중 인스턴스), 미설정 시 인메모리 폴백. Redis 캐싱(8-3): - CacheModule → @keyv/redis(미설정 시 인메모리 폴백). 저장소 목록을 버전 네임스페이스로 캐시, 생성/수정/삭제·동기화·멤버 초대/제거 시 무효화(공유 버전 키라 다중 인스턴스 정합). - enableShutdownHooks 로 종료 시 Redis 연결 graceful close. 검증: 백엔드 build/lint 0·20 tests, 프론트 build/lint/type-check 0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
import { SetMetadata } from '@nestjs/common';
|
||||
|
||||
// 응답 표준 래핑({ success, data }) 제외 마커.
|
||||
// SSE(text/event-stream)처럼 스트림을 그대로 흘려보내야 하는 핸들러에 사용한다.
|
||||
export const SKIP_TRANSFORM = 'skipTransform';
|
||||
export const SkipTransform = () => SetMetadata(SKIP_TRANSFORM, true);
|
||||
@@ -4,8 +4,10 @@ import {
|
||||
ExecutionContext,
|
||||
CallHandler,
|
||||
} from '@nestjs/common';
|
||||
import { Reflector } from '@nestjs/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { SKIP_TRANSFORM } from '../decorators/skip-transform.decorator';
|
||||
|
||||
export interface StandardResponse<T> {
|
||||
success: boolean;
|
||||
@@ -17,10 +19,23 @@ export class TransformInterceptor<T> implements NestInterceptor<
|
||||
T,
|
||||
StandardResponse<T>
|
||||
> {
|
||||
// main.ts 에서 new 로 생성하므로 DI 대신 자체 Reflector 인스턴스 사용
|
||||
private readonly reflector = new Reflector();
|
||||
|
||||
intercept(
|
||||
_context: ExecutionContext,
|
||||
context: ExecutionContext,
|
||||
next: CallHandler<T>,
|
||||
): Observable<StandardResponse<T>> {
|
||||
// @SkipTransform() 핸들러(SSE 스트림 등)는 래핑하지 않고 그대로 흘려보낸다
|
||||
const skip = this.reflector.get<boolean>(
|
||||
SKIP_TRANSFORM,
|
||||
context.getHandler(),
|
||||
);
|
||||
if (skip) {
|
||||
// SSE MessageEvent 스트림 등은 래핑 대상이 아니므로 그대로 통과(타입만 인터페이스에 맞춤)
|
||||
return next.handle() as unknown as Observable<StandardResponse<T>>;
|
||||
}
|
||||
|
||||
// 이미 Http 응답객체인 경우 등 예외처리가 필요할 수 있으나 기본적으로 data 래핑
|
||||
return next.handle().pipe(
|
||||
map((data: T) => ({
|
||||
|
||||
Reference in New Issue
Block a user