Files
social-media/backend/src/Socialize.Api/Modules/Channels/Handlers/UpdateChannel.cs
Jonathan Bourdon afcdd1ace1
All checks were successful
deploy-socialize / image (push) Successful in 1m20s
deploy-socialize / deploy (push) Successful in 20s
feat: add editable channel images
2026-05-09 13:14:11 -04:00

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);
}
}