121 lines
4.0 KiB
C#
121 lines
4.0 KiB
C#
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;
|
|
|
|
internal record UpdateChannelRequest(
|
|
Guid Id,
|
|
Guid WorkspaceId,
|
|
string Name,
|
|
string Network,
|
|
string? Handle,
|
|
string? ExternalUrl,
|
|
string? PortraitUrl,
|
|
string? BannerUrl);
|
|
|
|
internal class UpdateChannelRequestValidator
|
|
: Validator<UpdateChannelRequest>
|
|
{
|
|
private static readonly string[] AllowedNetworks =
|
|
[
|
|
"Instagram",
|
|
"TikTok",
|
|
"Facebook",
|
|
"LinkedIn",
|
|
"YouTube",
|
|
"X",
|
|
"Reddit",
|
|
"Website",
|
|
];
|
|
|
|
public UpdateChannelRequestValidator()
|
|
{
|
|
RuleFor(x => x.Id).NotEmpty();
|
|
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);
|
|
RuleFor(x => x.PortraitUrl).MaximumLength(2048);
|
|
RuleFor(x => x.BannerUrl).MaximumLength(2048);
|
|
}
|
|
}
|
|
|
|
internal class UpdateChannelHandler(
|
|
AppDbContext dbContext,
|
|
AccessScopeService accessScopeService)
|
|
: Endpoint<UpdateChannelRequest, ChannelDto>
|
|
{
|
|
public override void Configure()
|
|
{
|
|
Put("/api/channels/{id}");
|
|
Options(o => o.WithTags("Channels"));
|
|
}
|
|
|
|
public override async Task HandleAsync(UpdateChannelRequest request, CancellationToken ct)
|
|
{
|
|
Channel? channel = await dbContext.Channels
|
|
.SingleOrDefaultAsync(candidate => candidate.Id == request.Id && candidate.WorkspaceId == request.WorkspaceId, ct);
|
|
|
|
if (channel is null)
|
|
{
|
|
await SendNotFoundAsync(ct);
|
|
return;
|
|
}
|
|
|
|
if (!await accessScopeService.CanManageWorkspaceAsync(User, channel.WorkspaceId, ct))
|
|
{
|
|
await SendForbiddenAsync(ct);
|
|
return;
|
|
}
|
|
|
|
string normalizedName = request.Name.Trim();
|
|
string normalizedNetwork = request.Network.Trim();
|
|
string? normalizedHandle = request.Handle?.Trim();
|
|
string? normalizedExternalUrl = request.ExternalUrl?.Trim();
|
|
string? normalizedPortraitUrl = request.PortraitUrl?.Trim();
|
|
string? normalizedBannerUrl = request.BannerUrl?.Trim();
|
|
|
|
bool duplicateChannel = await dbContext.Channels
|
|
.AnyAsync(
|
|
candidate => candidate.Id != channel.Id
|
|
&& candidate.WorkspaceId == channel.WorkspaceId
|
|
&& candidate.Network == normalizedNetwork
|
|
&& candidate.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.Name = normalizedName;
|
|
channel.Network = normalizedNetwork;
|
|
channel.Handle = string.IsNullOrWhiteSpace(normalizedHandle) ? null : normalizedHandle;
|
|
channel.ExternalUrl = string.IsNullOrWhiteSpace(normalizedExternalUrl) ? null : normalizedExternalUrl;
|
|
channel.PortraitUrl = string.IsNullOrWhiteSpace(normalizedPortraitUrl) ? null : normalizedPortraitUrl;
|
|
channel.BannerUrl = string.IsNullOrWhiteSpace(normalizedBannerUrl) ? null : normalizedBannerUrl;
|
|
|
|
await dbContext.SaveChangesAsync(ct);
|
|
|
|
ChannelDto dto = new(
|
|
channel.Id,
|
|
channel.WorkspaceId,
|
|
channel.Name,
|
|
channel.Network,
|
|
channel.Handle,
|
|
channel.ExternalUrl,
|
|
channel.PortraitUrl,
|
|
channel.BannerUrl,
|
|
channel.CreatedAt);
|
|
|
|
await SendOkAsync(dto, ct);
|
|
}
|
|
}
|