SPMS_API/SPMS.API/Middlewares/ServiceCodeMiddleware.cs
SEAN 1ca4980293 improvement: 태그 관리 API 프론트엔드 연동 수정 (#267)
- TagSummaryDto에 tag_index 필드 추가 (서비스별 Id 순서 1-based 동적 계산)
- ServiceSummaryDto/ServiceResponseDto에 service_id 필드 추가
- ServiceCodeMiddleware OPTIONAL_FOR_ADMIN에 /v1/in/tag 경로 추가

Closes #267
2026-03-02 13:43:14 +09:00

104 lines
4.0 KiB
C#

using SPMS.Domain.Common;
using SPMS.Domain.Enums;
using SPMS.Domain.Interfaces;
namespace SPMS.API.Middlewares;
public class ServiceCodeMiddleware
{
private readonly RequestDelegate _next;
public ServiceCodeMiddleware(RequestDelegate next) => _next = next;
public async Task InvokeAsync(HttpContext context, IServiceRepository serviceRepository)
{
var path = context.Request.Path;
// === SKIP: X-Service-Code 불필요 ===
if (path == "/" ||
!path.StartsWithSegments("/v1") ||
path.StartsWithSegments("/v1/out") ||
path.StartsWithSegments("/v1/in/auth") ||
path.StartsWithSegments("/v1/in/account") ||
path.StartsWithSegments("/v1/in/public") ||
path.StartsWithSegments("/v1/in/service") ||
(path.StartsWithSegments("/v1/in/device") &&
!path.StartsWithSegments("/v1/in/device/list")) ||
path.StartsWithSegments("/swagger") ||
path.StartsWithSegments("/health"))
{
await _next(context);
return;
}
// === OPTIONAL_FOR_ADMIN: 관리자는 X-Service-Code 선택 ===
if (path.StartsWithSegments("/v1/in/stats") ||
path.StartsWithSegments("/v1/in/device/list") ||
path.StartsWithSegments("/v1/in/message/list") ||
path.StartsWithSegments("/v1/in/tag"))
{
if (context.Request.Headers.TryGetValue("X-Service-Code", out var optionalCode) &&
!string.IsNullOrWhiteSpace(optionalCode))
{
// 헤더가 있으면 기존 검증 수행
await ValidateAndSetService(context, serviceRepository, optionalCode!);
return;
}
// 헤더 없음 — 인증된 사용자만 전체 서비스 모드 허용
if (context.User.Identity?.IsAuthenticated == true)
{
// ServiceId 미설정 = 전체 서비스 모드
await _next(context);
return;
}
// 비인증 요청 → 에러
context.Response.StatusCode = 400;
context.Response.ContentType = "application/json";
await context.Response.WriteAsJsonAsync(
ApiResponse.Fail(ErrorCodes.ServiceScopeRequired, "X-Service-Code 헤더가 필요합니다."));
return;
}
// === REQUIRED: X-Service-Code 필수 ===
if (!context.Request.Headers.TryGetValue("X-Service-Code", out var serviceCode) ||
string.IsNullOrWhiteSpace(serviceCode))
{
context.Response.StatusCode = 400;
context.Response.ContentType = "application/json";
await context.Response.WriteAsJsonAsync(
ApiResponse.Fail(ErrorCodes.BadRequest, "X-Service-Code 헤더가 필요합니다."));
return;
}
await ValidateAndSetService(context, serviceRepository, serviceCode!);
}
private async Task ValidateAndSetService(HttpContext context, IServiceRepository serviceRepository, string serviceCode)
{
var service = await serviceRepository.GetByServiceCodeAsync(serviceCode);
if (service == null)
{
context.Response.StatusCode = 404;
context.Response.ContentType = "application/json";
await context.Response.WriteAsJsonAsync(
ApiResponse.Fail(ErrorCodes.NotFound, "존재하지 않는 서비스입니다."));
return;
}
if (service.Status != ServiceStatus.Active)
{
context.Response.StatusCode = 503;
context.Response.ContentType = "application/json";
await context.Response.WriteAsJsonAsync(
ApiResponse.Fail(ErrorCodes.Unauthorized, "비활성 상태의 서비스입니다."));
return;
}
context.Items["Service"] = service;
context.Items["ServiceId"] = service.Id;
await _next(context);
}
}