From 887f6f255a38e43b16e8e34a28064710c5841df8 Mon Sep 17 00:00:00 2001 From: Jonathan Bourdon Date: Wed, 16 Apr 2025 15:26:29 -0400 Subject: [PATCH] Adds edition of slug. --- .../Features/Contents/Handlers/ChangeName.cs | 49 ++++++++++ .../Features/Contents/Handlers/ChangeSlug.cs | 97 +++++++++++++++++++ frontend/src/views/creators/NameTitle.vue | 70 ++----------- frontend/src/views/profile/ProfilePage.vue | 12 ++- .../profile/creators/ChangeNameDialog.vue | 69 +++++++++++++ .../profile/creators/ChangeSlugDialog.vue | 97 +++++++++++++++++++ 6 files changed, 328 insertions(+), 66 deletions(-) create mode 100644 backend/src/Web/Features/Contents/Handlers/ChangeName.cs create mode 100644 backend/src/Web/Features/Contents/Handlers/ChangeSlug.cs create mode 100644 frontend/src/views/profile/creators/ChangeNameDialog.vue create mode 100644 frontend/src/views/profile/creators/ChangeSlugDialog.vue diff --git a/backend/src/Web/Features/Contents/Handlers/ChangeName.cs b/backend/src/Web/Features/Contents/Handlers/ChangeName.cs new file mode 100644 index 0000000..77fff48 --- /dev/null +++ b/backend/src/Web/Features/Contents/Handlers/ChangeName.cs @@ -0,0 +1,49 @@ +using Hutopy.Web.Features.Contents.Data; + +namespace Hutopy.Web.Features.Contents.Handlers; + +[PublicAPI] +public record ChangeNameRequest( + Guid CreatorId, + string Name); + +[PublicAPI] +internal sealed class ChangeNameRequestValidator + : Validator +{ + public ChangeNameRequestValidator() + { + RuleFor(r => r.Name) + .NotNull().WithMessage("You should specify the Name") + .NotEmpty().WithMessage("You should specify a valid/not empty Name"); + } +} + +[PublicAPI] +public class ChangeNameHandler( + ContentDbContext context) + : Endpoint +{ + public override void Configure() + { + Post("/api/creators/{CreatorId}/name"); + Options(o => o.WithTags("Creators")); + } + + public override async Task HandleAsync( + ChangeNameRequest request, + CancellationToken ct) + { + var creator = await context + .Creators + .SingleAsync( + c => c.Id == request.CreatorId, + cancellationToken: ct); + + creator.Name = request.Name; + + await context.SaveChangesAsync(ct); + + await SendOkAsync(ct); + } +} diff --git a/backend/src/Web/Features/Contents/Handlers/ChangeSlug.cs b/backend/src/Web/Features/Contents/Handlers/ChangeSlug.cs new file mode 100644 index 0000000..78ef9ad --- /dev/null +++ b/backend/src/Web/Features/Contents/Handlers/ChangeSlug.cs @@ -0,0 +1,97 @@ +using Hutopy.Web.Common.Security; +using Hutopy.Web.Features.Contents.Data; + +namespace Hutopy.Web.Features.Contents.Handlers; + +[PublicAPI] +public record ChangeSlugRequest( + Guid CreatorId, + Guid SlugReservationId); + +[PublicAPI] +internal sealed class ChangeSlugRequestValidator + : Validator +{ + public ChangeSlugRequestValidator() + { + RuleFor(r => r.CreatorId) + .NotNull().WithMessage("You should specify the CreatorId") + .NotEmpty().WithMessage("You should specify a valid/not empty CreatorId"); + + RuleFor(r => r.SlugReservationId) + .NotNull().WithMessage("You should specify the SlugReservationId") + .NotEmpty().WithMessage("You should specify a valid/not empty SlugReservationId"); + } +} + +[PublicAPI] +public class ChangeSlugHandler( + ContentDbContext context) + : Endpoint +{ + public override void Configure() + { + Put("/api/creators/{CreatorId}/slug"); + Options(o => o.WithTags("Creators")); + } + + public override async Task HandleAsync( + ChangeSlugRequest request, + CancellationToken ct) + { + await using var transaction = await context.Database.BeginTransactionAsync(ct); + + try + { + var creator = await context + .Creators + .SingleAsync( + c => c.Id == request.CreatorId, + cancellationToken: ct); + + if (creator.CreatedBy != User.GetUserId()) + { + await SendUnauthorizedAsync(ct); + return; + } + + var reservation = await context + .Slugs + .FirstOrDefaultAsync( + s => s.Id == request.SlugReservationId, + ct); + + if (reservation is null) + { + await SendNotFoundAsync(ct); + return; + } + + var previousReservation = await context + .Slugs + .FirstOrDefaultAsync( + s => s.UsedBy == request.CreatorId, + ct); + + if (previousReservation is null) + { + await SendErrorsAsync(cancellation: ct); + return; + } + + context.Remove(previousReservation); + reservation.UsedBy = creator.Id; + creator.Slug = reservation.NormalizedName; + + await context.SaveChangesAsync(ct); + + await transaction.CommitAsync(ct); + + await SendOkAsync(ct); + } + catch + { + await transaction.RollbackAsync(ct); + } + } +} diff --git a/frontend/src/views/creators/NameTitle.vue b/frontend/src/views/creators/NameTitle.vue index 917650d..ca37720 100644 --- a/frontend/src/views/creators/NameTitle.vue +++ b/frontend/src/views/creators/NameTitle.vue @@ -1,78 +1,24 @@  \ No newline at end of file diff --git a/frontend/src/views/profile/ProfilePage.vue b/frontend/src/views/profile/ProfilePage.vue index 2259420..36f8a26 100644 --- a/frontend/src/views/profile/ProfilePage.vue +++ b/frontend/src/views/profile/ProfilePage.vue @@ -7,6 +7,8 @@ import AliasDialog from "@/views/profile/account/AliasDialog.vue"; import FullnameDialog from "@/views/profile/account/FullnameDialog.vue"; import EmailDialog from "@/views/profile/account/EmailDialog.vue"; import ChangeStripeIdDialog from '@/views/profile/creators/ChangeStripeIdDialog.vue'; +import ChangeNameDialog from '@/views/profile/creators/ChangeNameDialog.vue'; +import ChangeSlugDialog from '@/views/profile/creators/ChangeSlugDialog.vue'; import ChangeTitleDialog from '@/views/profile/creators/ChangeTitleDialog.vue'; import Youtube from "@/views/svg/Youtube.vue"; import Web from "@/views/svg/Web.vue"; @@ -75,6 +77,8 @@ const currentComponent = ref(''); const componentsMap = { EmailDialog, SocialsDialog, + ChangeSlugDialog, + ChangeNameDialog, ChangeTitleDialog, ChangeStripeIdDialog, }; @@ -174,15 +178,15 @@ const closeDialog = () => {
- - @@ -332,7 +336,7 @@ const closeDialog = () => { } .value { - @apply flex-auto text-left pr-6 capitalize; + @apply flex-auto text-left pr-6; @apply break-words overflow-auto; } diff --git a/frontend/src/views/profile/creators/ChangeNameDialog.vue b/frontend/src/views/profile/creators/ChangeNameDialog.vue new file mode 100644 index 0000000..c912454 --- /dev/null +++ b/frontend/src/views/profile/creators/ChangeNameDialog.vue @@ -0,0 +1,69 @@ + + + + + diff --git a/frontend/src/views/profile/creators/ChangeSlugDialog.vue b/frontend/src/views/profile/creators/ChangeSlugDialog.vue new file mode 100644 index 0000000..fcf4fb6 --- /dev/null +++ b/frontend/src/views/profile/creators/ChangeSlugDialog.vue @@ -0,0 +1,97 @@ + + + + + \ No newline at end of file