| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748 |
- using Application.Abstractions.Messaging;
- using Application.Abstractions.Data;
- using SharedKernel.Results;
- using Domain.Entities.Common;
- namespace Application.Features.Admin.NavMenu.Create;
- public sealed class Handler(IAppDbContext db) : ICommandHandler<Command, Result>
- {
- public async Task<Result> Handle(Command request, CancellationToken ct)
- {
- if (string.IsNullOrWhiteSpace(request.Label))
- {
- return Result.Failure(Error.Problem("NavMenu.LabelRequired", "메뉴 명은 필수입니다."));
- }
- if (string.IsNullOrWhiteSpace(request.Href))
- {
- return Result.Failure(Error.Problem("NavMenu.HrefRequired", "링크 주소는 필수입니다."));
- }
- var label = request.Label.Trim();
- var href = request.Href.Trim();
- if (label.Length > 50)
- {
- return Result.Failure(Error.Problem("NavMenu.LabelTooLong", "메뉴 명은 50자 이하여야 합니다."));
- }
- if (href.Length > 200 || !IsSafeHref(href))
- {
- return Result.Failure(Error.Problem("NavMenu.HrefInvalid", "링크는 200자 이하이며 / 또는 http(s):// 로 시작해야 합니다."));
- }
- var item = NavMenuItem.Create(label, href, request.SortOrder, request.IsVisible);
- await db.NavMenuItem.AddAsync(item, ct);
- await db.SaveChangesAsync(ct);
- return Result.Success();
- }
- // 링크 스킴 허용목록 — 익명 렌더 XSS(javascript: 등) 차단. 내부 경로(/) 또는 http(s) 만 허용.
- private static bool IsSafeHref(string href)
- {
- return href.StartsWith('/') || href.StartsWith("http://", StringComparison.OrdinalIgnoreCase) || href.StartsWith("https://", StringComparison.OrdinalIgnoreCase);
- }
- }
|