diff --git a/Foxnouns.Backend/Controllers/MembersController.cs b/Foxnouns.Backend/Controllers/MembersController.cs index 46fe51e..59bec37 100644 --- a/Foxnouns.Backend/Controllers/MembersController.cs +++ b/Foxnouns.Backend/Controllers/MembersController.cs @@ -9,6 +9,7 @@ using Foxnouns.Backend.Services; using Foxnouns.Backend.Utils; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; +using NodaTime; namespace Foxnouns.Backend.Controllers; @@ -19,7 +20,8 @@ public class MembersController( MemberRendererService memberRenderer, ISnowflakeGenerator snowflakeGenerator, ObjectStorageService objectStorageService, - IQueue queue) : ApiControllerBase + IQueue queue, + IClock clock) : ApiControllerBase { private readonly ILogger _logger = logger.ForContext(); @@ -114,4 +116,25 @@ public class MembersController( List? Names, List? Pronouns, List? Fields); + + [HttpPost("/api/v2/users/@me/members/{memberRef}/reroll-sid")] + [Authorize("member.update")] + [ProducesResponseType(statusCode: StatusCodes.Status200OK)] + public async Task RerollSidAsync(string memberRef) + { + var member = await db.ResolveMemberAsync(CurrentUser!.Id, memberRef); + + var minTimeAgo = clock.GetCurrentInstant() - Duration.FromHours(1); + if (CurrentUser!.LastSidReroll > minTimeAgo) + throw new ApiError.BadRequest("Cannot reroll short ID yet"); + + await db.Members.Where(m => m.Id == member.Id) + .ExecuteUpdateAsync(s => s + .SetProperty(m => m.Sid, _ => db.FindFreeMemberSid())); + + // Re-fetch member so we can be sure the sid is correct + var updatedMember = await db.ResolveMemberAsync(CurrentUser!.Id, memberRef); + + return Ok(memberRenderer.RenderMember(updatedMember, CurrentToken)); + } } \ No newline at end of file diff --git a/Foxnouns.Backend/Controllers/UsersController.cs b/Foxnouns.Backend/Controllers/UsersController.cs index bb3417c..7d1177e 100644 --- a/Foxnouns.Backend/Controllers/UsersController.cs +++ b/Foxnouns.Backend/Controllers/UsersController.cs @@ -8,6 +8,7 @@ using Foxnouns.Backend.Services; using Foxnouns.Backend.Utils; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; +using NodaTime; namespace Foxnouns.Backend.Controllers; @@ -16,7 +17,8 @@ public class UsersController( DatabaseContext db, UserRendererService userRenderer, ISnowflakeGenerator snowflakeGenerator, - IQueue queue) : ApiControllerBase + IQueue queue, + IClock clock) : ApiControllerBase { [HttpGet("{userRef}")] [ProducesResponseType(statusCode: StatusCodes.Status200OK)] @@ -213,4 +215,22 @@ public class UsersController( { public bool? DarkMode { get; init; } } + + [HttpPost("@me/reroll-sid")] + [Authorize("user.update")] + [ProducesResponseType(statusCode: StatusCodes.Status200OK)] + public async Task RerollSidAsync() + { + var minTimeAgo = clock.GetCurrentInstant() - Duration.FromHours(1); + if (CurrentUser!.LastSidReroll > minTimeAgo) + throw new ApiError.BadRequest("Cannot reroll short ID yet"); + + await db.Users.Where(u => u.Id == CurrentUser.Id) + .ExecuteUpdateAsync(s => s + .SetProperty(u => u.Sid, _ => db.FindFreeUserSid()) + .SetProperty(u => u.LastSidReroll, _ => clock.GetCurrentInstant())); + + var user = await db.ResolveUserAsync(CurrentUser.Id); + return Ok(await userRenderer.RenderUserAsync(user, CurrentUser, CurrentToken, renderMembers: false)); + } } \ No newline at end of file diff --git a/Foxnouns.Backend/Database/DatabaseContext.cs b/Foxnouns.Backend/Database/DatabaseContext.cs index b46079b..e907abd 100644 --- a/Foxnouns.Backend/Database/DatabaseContext.cs +++ b/Foxnouns.Backend/Database/DatabaseContext.cs @@ -76,7 +76,23 @@ public class DatabaseContext : DbContext modelBuilder.Entity().Property(m => m.Fields).HasColumnType("jsonb"); modelBuilder.Entity().Property(m => m.Names).HasColumnType("jsonb"); modelBuilder.Entity().Property(m => m.Pronouns).HasColumnType("jsonb"); + + modelBuilder.HasDbFunction(typeof(DatabaseContext).GetMethod(nameof(FindFreeUserSid))!) + .HasName("find_free_user_sid"); + + modelBuilder.HasDbFunction(typeof(DatabaseContext).GetMethod(nameof(FindFreeMemberSid))!) + .HasName("find_free_member_sid"); } + + /// + /// Dummy method that calls find_free_user_sid() when used in an EF Core query. + /// + public string FindFreeUserSid() => throw new NotSupportedException(); + + /// + /// Dummy method that calls find_free_member_sid() when used in an EF Core query. + /// + public string FindFreeMemberSid() => throw new NotSupportedException(); } [SuppressMessage("ReSharper", "UnusedType.Global", Justification = "Used by EF Core's migration generator")]