using System.Text; using AutoMapper; using MediatR; using mws.backend.dotnet.application.Audit; using mws.backend.dotnet.application.Common; using mws.backend.dotnet.application.Permissions; using mws.backend.dotnet.domain.Documents; namespace mws.backend.dotnet.application.Documents; public record CreateDocumentCommand(Guid UserId, Guid ProjectId, CreateDocumentRequest Request) : IRequest, IAuditableRequest { public string Action => "Document.Create"; public string EntityType => "Document"; public string? EntityName => Request.Title; } public class CreateDocumentHandler(IUnitOfWork uow, IMapper mapper, IDocumentContentStore contentStore) : IRequestHandler { public async Task Handle(CreateDocumentCommand command, CancellationToken ct) { await DocumentAccess.EnsureDocumentPermissionAsync(uow, command.UserId, command.ProjectId, PermissionAction.Create, ct); var request = command.Request; if (string.IsNullOrWhiteSpace(request.Title)) { throw new BadRequestException("Title is required"); } if (request.ParentId is { } parentId) { var parent = await uow.Documents.GetInProjectAsync(parentId, command.ProjectId, ct) ?? throw new BadRequestException("Parent document not found"); if (parent.Type != DocumentType.Folder) { throw new BadRequestException("Parent must be a folder"); } } if (request.Type == DocumentType.Folder && !string.IsNullOrWhiteSpace(request.Content)) { throw new BadRequestException("Folders cannot have content"); } var now = DateTime.UtcNow; var doc = new Document { Id = Guid.NewGuid(), ProjectId = command.ProjectId, ParentId = request.ParentId, Title = request.Title.Trim(), Type = request.Type, CreatedBy = command.UserId, CreatedAt = now, UpdatedBy = command.UserId, UpdatedAt = now, }; uow.Documents.Add(doc); await uow.SaveChangesAsync(ct); if (request.Type == DocumentType.Document && !string.IsNullOrEmpty(request.Content)) { var projectName = (await uow.Projects.GetByIdAsync(doc.ProjectId, ct))?.Name ?? doc.ProjectId.ToString(); var bytes = Encoding.UTF8.GetBytes(request.Content); using (var ms = new MemoryStream(bytes)) { doc.StorageKey = await contentStore.UploadAsync(projectName, doc.Title, "text/html; charset=utf-8", ms, ct); } doc.ContentSize = bytes.Length; doc.UpdatedContentAt = now; await uow.SaveChangesAsync(ct); } return mapper.Map(doc); } }