using Application.Abstractions.Messaging; using Application.Abstractions.Data; using SharedKernel.Storage; using Microsoft.EntityFrameworkCore; namespace Application.Features.Admin.Forum.Post.Create; public sealed class Handler(IAppDbContext db, IFileStorage fileStorage) : ICommandHandler { private static readonly string[] AllowedImageExtensions = [".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp"]; private static readonly string[] AllowedFileExtensions = [".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp", ".pdf", ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx", ".txt", ".zip", ".rar", ".7z", ".hwp", ".hwpx", ".csv"]; public async Task Handle(Command request, CancellationToken ct) { if (!await db.Board.AnyAsync(x => x.ID == request.BoardID, ct)) { throw new KeyNotFoundException("게시판을 찾을 수 없습니다."); } var post = new Domain.Entities.Forum.Posts.Post { BoardID = request.BoardID, BoardPrefixID = request.BoardPrefixID, MemberID = null, Subject = request.Subject, Content = request.Content ?? string.Empty, IsNotice = request.IsNotice, IsSecret = request.IsSecret, IsAnonymous = request.IsAnonymous, Name = "관리자" }; await db.Post.AddAsync(post, ct); await db.SaveChangesAsync(ct); var uploadPath = new FileStoragePath(UploadTarget.Upload, UploadFolder.Post, post.ID); // 썸네일 처리 if (request.ThumbnailFile is not null) { var result = await fileStorage.SaveFileAsync(request.ThumbnailFile, uploadPath, AllowedImageExtensions, ct); post.Thumbnail = result?.Url; } // 파일 처리 if (request.Files is { Count: > 0 }) { byte fileCount = 0; foreach (var file in request.Files) { var result = await fileStorage.SaveFileAsync(file, uploadPath, AllowedFileExtensions, ct); if (result is not null) { var ext = Path.GetExtension(file.FileName).ToLowerInvariant(); await db.PostFile.AddAsync(new Domain.Entities.Forum.Posts.PostFile { BoardID = request.BoardID, PostID = post.ID, UUID = Guid.NewGuid(), FileName = file.FileName, HashedName = result.FileName, Path = uploadPath.ToRelativePath(), Url = result.Url, Extension = ext, ContentType = file.ContentType, Size = result.Size }, ct); fileCount++; } } post.Files = fileCount; } // 태그 처리 if (request.Tags is { Count: > 0 }) { byte tagCount = 0; foreach (var tagName in request.Tags) { if (string.IsNullOrWhiteSpace(tagName)) { continue; } var name = tagName.Trim(); var slug = name.ToLowerInvariant().Replace(' ', '-'); var tag = await db.Tag.FirstOrDefaultAsync(x => x.Name == name, ct); if (tag is null) { tag = new Domain.Entities.Forum.Posts.Tag { Name = name, Slug = slug }; await db.Tag.AddAsync(tag, ct); await db.SaveChangesAsync(ct); } tag.UsageCount++; tag.UpdatedAt = DateTime.UtcNow; await db.PostTag.AddAsync(new Domain.Entities.Forum.Posts.PostTag { BoardID = request.BoardID, PostID = post.ID, TagID = tag.ID }, ct); tagCount++; } post.Tags = tagCount; } await db.SaveChangesAsync(ct); // Board 게시글 카운트 증가 var board = await db.Board.FirstOrDefaultAsync(x => x.ID == request.BoardID, ct); if (board is not null) { board.Posts++; board.UpdatedAt = DateTime.UtcNow; // BoardGroup 게시글 카운트 증가 var boardGroup = await db.BoardGroup.FirstOrDefaultAsync(x => x.ID == board.BoardGroupID, ct); if (boardGroup is not null) { boardGroup.Posts++; boardGroup.UpdatedAt = DateTime.UtcNow; } await db.SaveChangesAsync(ct); } } }