using FastEndpoints; using Microsoft.EntityFrameworkCore; using Socialize.Api.Data; using Socialize.Api.Infrastructure.Security; using Socialize.Api.Modules.Channels.Data; namespace Socialize.Api.Modules.Channels.Handlers; public record CreateChannelRequest( Guid WorkspaceId, string Name, string Network, string? Handle, string? ExternalUrl); public class CreateChannelRequestValidator : Validator { private static readonly string[] AllowedNetworks = [ "Instagram", "TikTok", "Facebook", "LinkedIn", "YouTube", "X", "Reddit", "Website", ]; public CreateChannelRequestValidator() { RuleFor(x => x.WorkspaceId).NotEmpty(); RuleFor(x => x.Name).NotEmpty().MaximumLength(256); RuleFor(x => x.Network).NotEmpty().Must(network => AllowedNetworks.Contains(network)) .WithMessage("Selected network is invalid."); RuleFor(x => x.Handle).MaximumLength(256); RuleFor(x => x.ExternalUrl).MaximumLength(2048); } } public class CreateChannelHandler( AppDbContext dbContext, AccessScopeService accessScopeService) : Endpoint { public override void Configure() { Post("/api/channels"); Options(o => o.WithTags("Channels")); } public override async Task HandleAsync(CreateChannelRequest request, CancellationToken ct) { if (!await accessScopeService.CanManageWorkspaceAsync(User, request.WorkspaceId, ct)) { await SendForbiddenAsync(ct); return; } bool workspaceExists = await dbContext.Workspaces .AnyAsync(workspace => workspace.Id == request.WorkspaceId, ct); if (!workspaceExists) { AddError(request => request.WorkspaceId, "The selected workspace does not exist."); await SendErrorsAsync(StatusCodes.Status400BadRequest, ct); return; } string normalizedName = request.Name.Trim(); string normalizedNetwork = request.Network.Trim(); string? normalizedHandle = request.Handle?.Trim(); string? normalizedExternalUrl = request.ExternalUrl?.Trim(); bool duplicateChannel = await dbContext.Channels .AnyAsync( channel => channel.WorkspaceId == request.WorkspaceId && channel.Network == normalizedNetwork && channel.Name == normalizedName, ct); if (duplicateChannel) { AddError(request => request.Name, "A channel with this name already exists for the selected network."); await SendErrorsAsync(StatusCodes.Status409Conflict, ct); return; } Channel channel = new() { Id = Guid.NewGuid(), WorkspaceId = request.WorkspaceId, Name = normalizedName, Network = normalizedNetwork, Handle = string.IsNullOrWhiteSpace(normalizedHandle) ? null : normalizedHandle, ExternalUrl = string.IsNullOrWhiteSpace(normalizedExternalUrl) ? null : normalizedExternalUrl, CreatedAt = DateTimeOffset.UtcNow, }; dbContext.Channels.Add(channel); await dbContext.SaveChangesAsync(ct); ChannelDto dto = new( channel.Id, channel.WorkspaceId, channel.Name, channel.Network, channel.Handle, channel.ExternalUrl, channel.CreatedAt); await SendAsync(dto, StatusCodes.Status201Created, ct); } }