many fixes and improvements - rework for modules/ and common/
feat(emailer): add Postmark and Resend providers
This commit is contained in:
110
backend/Modules/Creators/Features/ReserveSlug.cs
Normal file
110
backend/Modules/Creators/Features/ReserveSlug.cs
Normal file
@@ -0,0 +1,110 @@
|
||||
using System.Net;
|
||||
using FluentValidation.Results;
|
||||
using Hutopy.Infrastructure.Security;
|
||||
using Hutopy.Modules.Creators.Configuration;
|
||||
using Hutopy.Modules.Creators.Data;
|
||||
using Hutopy.Modules.Creators.Services;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Npgsql;
|
||||
|
||||
namespace Hutopy.Modules.Creators.Features;
|
||||
|
||||
[PublicAPI]
|
||||
public record ReserveSlugRequest
|
||||
{
|
||||
public required Guid ReservationId { get; set; }
|
||||
public string Slug { get; set; } = null!;
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public sealed class ReserveSlugRequestValidator : Validator<ReserveSlugRequest>
|
||||
{
|
||||
public ReserveSlugRequestValidator()
|
||||
{
|
||||
RuleFor(r => r.Slug)
|
||||
.NotEmpty()
|
||||
.NotNull()
|
||||
.WithMessage("You should specify a valid Slug");
|
||||
}
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public sealed class ReserveSlug(
|
||||
CreatorsDbContext context,
|
||||
IOptions<CreatorOptions> opts,
|
||||
SlugPurger slugPurger)
|
||||
: Endpoint<ReserveSlugRequest>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
Post("/api/creators/@{Slug}/reserve");
|
||||
Options(o => o.WithTags("Creators"));
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(
|
||||
ReserveSlugRequest req,
|
||||
CancellationToken ct)
|
||||
{
|
||||
await using var transaction = await context.Database.BeginTransactionAsync(ct);
|
||||
|
||||
try
|
||||
{
|
||||
// First, purge any expired slugs
|
||||
await slugPurger.PurgeExpiredSlugsAsync(ct);
|
||||
|
||||
var reservation = await context.Slugs.FirstOrDefaultAsync(
|
||||
s => s.Id == req.ReservationId && s.CreatedBy == User.GetUserId(),
|
||||
cancellationToken: ct);
|
||||
|
||||
if (reservation == null)
|
||||
{
|
||||
reservation = new Slugs
|
||||
{
|
||||
Id = req.ReservationId,
|
||||
CreatedBy = User.GetUserId(),
|
||||
CreatedAt = DateTimeOffset.UtcNow,
|
||||
};
|
||||
|
||||
context.Slugs.Attach(reservation);
|
||||
context.Entry(reservation).State = EntityState.Added;
|
||||
}
|
||||
|
||||
reservation.Name = req.Slug;
|
||||
reservation.ReservedUntil = DateTimeOffset.UtcNow + opts.Value.SlugReservationDuration;
|
||||
|
||||
await context.SaveChangesAsync(ct);
|
||||
|
||||
await transaction.CommitAsync(ct);
|
||||
|
||||
await SendOkAsync(new { Message = "Slug reserved." }, ct);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await transaction.RollbackAsync(ct);
|
||||
|
||||
Logger.LogError("Transaction failed: {Message}", e.Message);
|
||||
|
||||
if (e.InnerException is PostgresException innerException)
|
||||
{
|
||||
if (innerException.ConstraintName == "IX_Slugs_NormalizedName")
|
||||
{
|
||||
await SendResultAsync(new ProblemDetails(
|
||||
[
|
||||
new ValidationFailure(nameof(Slugs.Name),
|
||||
"The name is already taken.")
|
||||
],
|
||||
(int)HttpStatusCode.Conflict));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await SendResultAsync(new ProblemDetails(
|
||||
[
|
||||
new ValidationFailure(nameof(Slugs.Name),
|
||||
e.Message)
|
||||
],
|
||||
(int)HttpStatusCode.Conflict));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user