refactor(backend): move all request/response types to a new Dto namespace

This commit is contained in:
sam 2024-12-08 20:17:30 +01:00
parent f8e6032449
commit 8bd4449804
Signed by: sam
GPG key ID: B4EF20DDE721CAA1
21 changed files with 310 additions and 316 deletions

View file

@ -2,14 +2,12 @@ using System.Net;
using System.Web;
using Foxnouns.Backend.Database;
using Foxnouns.Backend.Database.Models;
using Foxnouns.Backend.Dto;
using Foxnouns.Backend.Extensions;
using Foxnouns.Backend.Middleware;
using Foxnouns.Backend.Services;
using Foxnouns.Backend.Utils;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json;
using NodaTime;
namespace Foxnouns.Backend.Controllers.Authentication;
@ -47,28 +45,6 @@ public class AuthController(
return Ok(new UrlsResponse(config.EmailAuth.Enabled, discord, null, null));
}
private record UrlsResponse(bool EmailEnabled, string? Discord, string? Google, string? Tumblr);
public record AuthResponse(
UserRendererService.UserResponse User,
string Token,
Instant ExpiresAt
);
public record SingleUrlResponse(string Url);
public record AddOauthAccountResponse(
Snowflake Id,
[property: JsonConverter(typeof(ScreamingSnakeCaseEnumConverter))] AuthType Type,
string RemoteId,
[property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
string? RemoteUsername
);
public record OauthRegisterRequest(string Ticket, string Username);
public record CallbackRequest(string Code, string State);
[HttpPost("force-log-out")]
[Authorize("identify")]
public async Task<IActionResult> ForceLogoutAsync()
@ -83,9 +59,7 @@ public class AuthController(
[HttpGet("methods/{id}")]
[Authorize("*")]
[ProducesResponseType<UserRendererService.AuthMethodResponse>(
statusCode: StatusCodes.Status200OK
)]
[ProducesResponseType<AuthMethodResponse>(statusCode: StatusCodes.Status200OK)]
public async Task<IActionResult> GetAuthMethodAsync(Snowflake id)
{
AuthMethod? authMethod = await db
@ -143,13 +117,3 @@ public class AuthController(
return NoContent();
}
}
public record CallbackResponse(
bool HasAccount,
[property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] string? Ticket,
[property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] string? RemoteUsername,
[property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
UserRendererService.UserResponse? User,
[property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] string? Token,
[property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] Instant? ExpiresAt
);

View file

@ -3,6 +3,7 @@ using System.Web;
using EntityFramework.Exceptions.Common;
using Foxnouns.Backend.Database;
using Foxnouns.Backend.Database.Models;
using Foxnouns.Backend.Dto;
using Foxnouns.Backend.Extensions;
using Foxnouns.Backend.Middleware;
using Foxnouns.Backend.Services;
@ -29,7 +30,7 @@ public class DiscordAuthController(
[HttpPost("callback")]
[ProducesResponseType<CallbackResponse>(StatusCodes.Status200OK)]
public async Task<IActionResult> CallbackAsync([FromBody] AuthController.CallbackRequest req)
public async Task<IActionResult> CallbackAsync([FromBody] CallbackRequest req)
{
CheckRequirements();
await keyCacheService.ValidateAuthStateAsync(req.State);
@ -58,10 +59,8 @@ public class DiscordAuthController(
}
[HttpPost("register")]
[ProducesResponseType<AuthController.AuthResponse>(StatusCodes.Status200OK)]
public async Task<IActionResult> RegisterAsync(
[FromBody] AuthController.OauthRegisterRequest req
)
[ProducesResponseType<AuthResponse>(StatusCodes.Status200OK)]
public async Task<IActionResult> RegisterAsync([FromBody] OauthRegisterRequest req)
{
RemoteAuthService.RemoteUser? remoteUser =
await keyCacheService.GetKeyAsync<RemoteAuthService.RemoteUser>(
@ -109,14 +108,12 @@ public class DiscordAuthController(
+ $"&prompt=none&state={state}"
+ $"&redirect_uri={HttpUtility.UrlEncode($"{config.BaseUrl}/auth/callback/discord")}";
return Ok(new AuthController.SingleUrlResponse(url));
return Ok(new SingleUrlResponse(url));
}
[HttpPost("add-account/callback")]
[Authorize("*")]
public async Task<IActionResult> AddAccountCallbackAsync(
[FromBody] AuthController.CallbackRequest req
)
public async Task<IActionResult> AddAccountCallbackAsync([FromBody] CallbackRequest req)
{
CheckRequirements();
@ -144,7 +141,7 @@ public class DiscordAuthController(
);
return Ok(
new AuthController.AddOauthAccountResponse(
new AddOauthAccountResponse(
authMethod.Id,
AuthType.Discord,
authMethod.RemoteId,

View file

@ -2,6 +2,7 @@ using System.Net;
using EntityFramework.Exceptions.Common;
using Foxnouns.Backend.Database;
using Foxnouns.Backend.Database.Models;
using Foxnouns.Backend.Dto;
using Foxnouns.Backend.Extensions;
using Foxnouns.Backend.Middleware;
using Foxnouns.Backend.Services;
@ -30,7 +31,7 @@ public class EmailAuthController(
[HttpPost("register/init")]
public async Task<IActionResult> RegisterInitAsync(
[FromBody] RegisterRequest req,
[FromBody] EmailRegisterRequest req,
CancellationToken ct = default
)
{
@ -73,7 +74,7 @@ public class EmailAuthController(
[HttpPost("register")]
public async Task<IActionResult> CompleteRegistrationAsync(
[FromBody] CompleteRegistrationRequest req
[FromBody] EmailCompleteRegistrationRequest req
)
{
CheckRequirements();
@ -102,7 +103,7 @@ public class EmailAuthController(
await keyCacheService.DeleteKeyAsync($"email:{req.Ticket}");
return Ok(
new AuthController.AuthResponse(
new AuthResponse(
await userRenderer.RenderUserAsync(user, user, renderMembers: false),
tokenStr,
token.ExpiresAt
@ -111,9 +112,9 @@ public class EmailAuthController(
}
[HttpPost("login")]
[ProducesResponseType<AuthController.AuthResponse>(StatusCodes.Status200OK)]
[ProducesResponseType<AuthResponse>(StatusCodes.Status200OK)]
public async Task<IActionResult> LoginAsync(
[FromBody] LoginRequest req,
[FromBody] EmailLoginRequest req,
CancellationToken ct = default
)
{
@ -141,7 +142,7 @@ public class EmailAuthController(
await db.SaveChangesAsync(ct);
return Ok(
new AuthController.AuthResponse(
new AuthResponse(
await userRenderer.RenderUserAsync(user, user, renderMembers: false, ct: ct),
tokenStr,
token.ExpiresAt
@ -151,7 +152,7 @@ public class EmailAuthController(
[HttpPost("change-password")]
[Authorize("*")]
public async Task<IActionResult> UpdatePasswordAsync([FromBody] ChangePasswordRequest req)
public async Task<IActionResult> UpdatePasswordAsync([FromBody] EmailChangePasswordRequest req)
{
if (!await authService.ValidatePasswordAsync(CurrentUser!, req.Current))
throw new ApiError.Forbidden("Invalid password");
@ -211,7 +212,7 @@ public class EmailAuthController(
[HttpPost("add-email/callback")]
[Authorize("*")]
public async Task<IActionResult> AddEmailCallbackAsync([FromBody] CallbackRequest req)
public async Task<IActionResult> AddEmailCallbackAsync([FromBody] EmailCallbackRequest req)
{
CheckRequirements();
@ -233,7 +234,7 @@ public class EmailAuthController(
);
return Ok(
new AuthController.AddOauthAccountResponse(
new AddOauthAccountResponse(
authMethod.Id,
AuthType.Email,
authMethod.RemoteId,
@ -258,14 +259,4 @@ public class EmailAuthController(
if (!config.EmailAuth.Enabled)
throw new ApiError.BadRequest("Email authentication is not enabled on this instance.");
}
public record LoginRequest(string Email, string Password);
public record RegisterRequest(string Email);
public record CompleteRegistrationRequest(string Ticket, string Username, string Password);
public record CallbackRequest(string State);
public record ChangePasswordRequest(string Current, string New);
}

View file

@ -2,6 +2,7 @@ using System.Net;
using EntityFramework.Exceptions.Common;
using Foxnouns.Backend.Database;
using Foxnouns.Backend.Database.Models;
using Foxnouns.Backend.Dto;
using Foxnouns.Backend.Middleware;
using Foxnouns.Backend.Services;
using Foxnouns.Backend.Services.Auth;
@ -25,7 +26,7 @@ public class FediverseAuthController(
private readonly ILogger _logger = logger.ForContext<FediverseAuthController>();
[HttpGet]
[ProducesResponseType<AuthController.SingleUrlResponse>(statusCode: StatusCodes.Status200OK)]
[ProducesResponseType<SingleUrlResponse>(statusCode: StatusCodes.Status200OK)]
public async Task<IActionResult> GetFediverseUrlAsync(
[FromQuery] string instance,
[FromQuery] bool forceRefresh = false
@ -35,12 +36,12 @@ public class FediverseAuthController(
throw new ApiError.BadRequest("Not a valid domain.", "instance", instance);
string url = await fediverseAuthService.GenerateAuthUrlAsync(instance, forceRefresh);
return Ok(new AuthController.SingleUrlResponse(url));
return Ok(new SingleUrlResponse(url));
}
[HttpPost("callback")]
[ProducesResponseType<CallbackResponse>(statusCode: StatusCodes.Status200OK)]
public async Task<IActionResult> FediverseCallbackAsync([FromBody] CallbackRequest req)
public async Task<IActionResult> FediverseCallbackAsync([FromBody] FediverseCallbackRequest req)
{
FediverseApplication app = await fediverseAuthService.GetApplicationAsync(req.Instance);
FediverseAuthService.FediverseUser remoteUser =
@ -74,10 +75,8 @@ public class FediverseAuthController(
}
[HttpPost("register")]
[ProducesResponseType<AuthController.AuthResponse>(statusCode: StatusCodes.Status200OK)]
public async Task<IActionResult> RegisterAsync(
[FromBody] AuthController.OauthRegisterRequest req
)
[ProducesResponseType<AuthResponse>(statusCode: StatusCodes.Status200OK)]
public async Task<IActionResult> RegisterAsync([FromBody] OauthRegisterRequest req)
{
FediverseTicketData? ticketData = await keyCacheService.GetKeyAsync<FediverseTicketData>(
$"fediverse:{req.Ticket}",
@ -138,12 +137,14 @@ public class FediverseAuthController(
);
string url = await fediverseAuthService.GenerateAuthUrlAsync(instance, forceRefresh, state);
return Ok(new AuthController.SingleUrlResponse(url));
return Ok(new SingleUrlResponse(url));
}
[HttpPost("add-account/callback")]
[Authorize("*")]
public async Task<IActionResult> AddAccountCallbackAsync([FromBody] CallbackRequest req)
public async Task<IActionResult> AddAccountCallbackAsync(
[FromBody] FediverseCallbackRequest req
)
{
await remoteAuthService.ValidateAddAccountStateAsync(
req.State,
@ -171,7 +172,7 @@ public class FediverseAuthController(
);
return Ok(
new AuthController.AddOauthAccountResponse(
new AddOauthAccountResponse(
authMethod.Id,
AuthType.Fediverse,
authMethod.RemoteId,
@ -189,8 +190,6 @@ public class FediverseAuthController(
}
}
public record CallbackRequest(string Instance, string Code, string State);
private record FediverseTicketData(
Snowflake ApplicationId,
FediverseAuthService.FediverseUser User

View file

@ -1,6 +1,7 @@
using Coravel.Queuing.Interfaces;
using Foxnouns.Backend.Database;
using Foxnouns.Backend.Database.Models;
using Foxnouns.Backend.Dto;
using Foxnouns.Backend.Jobs;
using Foxnouns.Backend.Middleware;
using Microsoft.AspNetCore.Mvc;
@ -43,8 +44,6 @@ public class ExportsController(
private string ExportUrl(Snowflake userId, string filename) =>
$"{config.MediaBaseUrl}/data-exports/{userId}/{filename}.zip";
private record DataExportResponse(string? Url, Instant? ExpiresAt);
[HttpPost]
public async Task<IActionResult> QueueDataExportAsync()
{

View file

@ -1,50 +1,49 @@
using Coravel.Queuing.Interfaces;
using Foxnouns.Backend.Database;
using Foxnouns.Backend.Database.Models;
using Foxnouns.Backend.Extensions;
using Foxnouns.Backend.Dto;
using Foxnouns.Backend.Jobs;
using Foxnouns.Backend.Middleware;
using Foxnouns.Backend.Services;
using Foxnouns.Backend.Utils;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;
namespace Foxnouns.Backend.Controllers;
[Route("/api/v2/users/@me/flags")]
public class FlagsController(
ILogger logger,
DatabaseContext db,
UserRendererService userRenderer,
ObjectStorageService objectStorageService,
ISnowflakeGenerator snowflakeGenerator,
IQueue queue
) : ApiControllerBase
{
private readonly ILogger _logger = logger.ForContext<FlagsController>();
[HttpGet]
[Authorize("identify")]
[ProducesResponseType<IEnumerable<UserRendererService.PrideFlagResponse>>(
statusCode: StatusCodes.Status200OK
)]
[ProducesResponseType<IEnumerable<PrideFlagResponse>>(statusCode: StatusCodes.Status200OK)]
public async Task<IActionResult> GetFlagsAsync(CancellationToken ct = default)
{
List<PrideFlag> flags = await db
.PrideFlags.Where(f => f.UserId == CurrentUser!.Id)
.OrderBy(f => f.Name)
.ThenBy(f => f.Id)
.ToListAsync(ct);
return Ok(flags.Select(userRenderer.RenderPrideFlag));
}
public const int MaxFlagCount = 500;
[HttpPost]
[Authorize("user.update")]
[ProducesResponseType<UserRendererService.PrideFlagResponse>(
statusCode: StatusCodes.Status202Accepted
)]
public IActionResult CreateFlag([FromBody] CreateFlagRequest req)
[ProducesResponseType<PrideFlagResponse>(statusCode: StatusCodes.Status202Accepted)]
public async Task<IActionResult> CreateFlagAsync([FromBody] CreateFlagRequest req)
{
int flagCount = await db.PrideFlags.Where(f => f.UserId == CurrentUser!.Id).CountAsync();
if (flagCount >= MaxFlagCount)
throw new ApiError.BadRequest("Maximum number of flags reached");
ValidationUtils.Validate(ValidateFlag(req.Name, req.Description, req.Image));
Snowflake id = snowflakeGenerator.GenerateSnowflake();
@ -56,10 +55,6 @@ public class FlagsController(
return Accepted(new CreateFlagResponse(id, req.Name, req.Description));
}
public record CreateFlagRequest(string Name, string Image, string? Description);
private record CreateFlagResponse(Snowflake Id, string Name, string? Description);
[HttpPatch("{id}")]
[Authorize("user.update")]
public async Task<IActionResult> UpdateFlagAsync(Snowflake id, [FromBody] UpdateFlagRequest req)
@ -84,52 +79,19 @@ public class FlagsController(
return Ok(userRenderer.RenderPrideFlag(flag));
}
public class UpdateFlagRequest : PatchRequest
{
public string? Name { get; init; }
public string? Description { get; init; }
}
[HttpDelete("{id}")]
[Authorize("user.update")]
public async Task<IActionResult> DeleteFlagAsync(Snowflake id)
{
await using IDbContextTransaction tx = await db.Database.BeginTransactionAsync();
PrideFlag? flag = await db.PrideFlags.FirstOrDefaultAsync(f =>
f.Id == id && f.UserId == CurrentUser!.Id
);
if (flag == null)
throw new ApiError.NotFound("Unknown flag ID, or it's not your flag.");
string hash = flag.Hash;
db.PrideFlags.Remove(flag);
await db.SaveChangesAsync();
int flagCount = await db.PrideFlags.CountAsync(f => f.Hash == flag.Hash);
if (flagCount == 0)
{
try
{
_logger.Information(
"Deleting flag file {Hash} as it is no longer used by any flags",
hash
);
await objectStorageService.DeleteFlagAsync(hash);
}
catch (Exception e)
{
_logger.Error(e, "Error deleting flag file {Hash}", hash);
}
}
else
{
_logger.Debug("Flag file {Hash} is used by other flags, not deleting", hash);
}
await tx.CommitAsync();
return NoContent();
}

View file

@ -1,5 +1,6 @@
using System.Text.RegularExpressions;
using Foxnouns.Backend.Database;
using Foxnouns.Backend.Dto;
using Foxnouns.Backend.Utils;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Controllers;
@ -63,10 +64,6 @@ public partial class InternalController(DatabaseContext db) : ControllerBase
return Ok(new RequestDataResponse(userId, template));
}
public record RequestDataRequest(string? Token, string Method, string Path);
public record RequestDataResponse(Snowflake? UserId, string Template);
private static RouteEndpoint? GetEndpoint(
HttpContext httpContext,
string url,

View file

@ -2,6 +2,7 @@ using Coravel.Queuing.Interfaces;
using EntityFramework.Exceptions.Common;
using Foxnouns.Backend.Database;
using Foxnouns.Backend.Database.Models;
using Foxnouns.Backend.Dto;
using Foxnouns.Backend.Extensions;
using Foxnouns.Backend.Jobs;
using Foxnouns.Backend.Middleware;
@ -28,9 +29,7 @@ public class MembersController(
private readonly ILogger _logger = logger.ForContext<MembersController>();
[HttpGet]
[ProducesResponseType<IEnumerable<MemberRendererService.PartialMember>>(
StatusCodes.Status200OK
)]
[ProducesResponseType<IEnumerable<PartialMember>>(StatusCodes.Status200OK)]
public async Task<IActionResult> GetMembersAsync(string userRef, CancellationToken ct = default)
{
User user = await db.ResolveUserAsync(userRef, CurrentToken, ct);
@ -38,7 +37,7 @@ public class MembersController(
}
[HttpGet("{memberRef}")]
[ProducesResponseType<MemberRendererService.MemberResponse>(StatusCodes.Status200OK)]
[ProducesResponseType<MemberResponse>(StatusCodes.Status200OK)]
public async Task<IActionResult> GetMemberAsync(
string userRef,
string memberRef,
@ -52,7 +51,7 @@ public class MembersController(
public const int MaxMemberCount = 500;
[HttpPost("/api/v2/users/@me/members")]
[ProducesResponseType<MemberRendererService.MemberResponse>(StatusCodes.Status200OK)]
[ProducesResponseType<MemberResponse>(StatusCodes.Status200OK)]
[Authorize("member.create")]
public async Task<IActionResult> CreateMemberAsync(
[FromBody] CreateMemberRequest req,
@ -246,20 +245,6 @@ public class MembersController(
return Ok(memberRenderer.RenderMember(member, CurrentToken));
}
public class UpdateMemberRequest : PatchRequest
{
public string? Name { get; init; }
public string? DisplayName { get; init; }
public string? Bio { get; init; }
public string? Avatar { get; init; }
public string[]? Links { get; init; }
public FieldEntry[]? Names { get; init; }
public Pronoun[]? Pronouns { get; init; }
public Field[]? Fields { get; init; }
public Snowflake[]? Flags { get; init; }
public bool? Unlisted { get; init; }
}
[HttpDelete("/api/v2/users/@me/members/{memberRef}")]
[Authorize("member.update")]
public async Task<IActionResult> DeleteMemberAsync(string memberRef)
@ -282,21 +267,9 @@ public class MembersController(
return NoContent();
}
public record CreateMemberRequest(
string Name,
string? DisplayName,
string? Bio,
string? Avatar,
bool? Unlisted,
string[]? Links,
List<FieldEntry>? Names,
List<Pronoun>? Pronouns,
List<Field>? Fields
);
[HttpPost("/api/v2/users/@me/members/{memberRef}/reroll-sid")]
[Authorize("member.update")]
[ProducesResponseType<UserRendererService.UserResponse>(statusCode: StatusCodes.Status200OK)]
[ProducesResponseType<MemberResponse>(statusCode: StatusCodes.Status200OK)]
public async Task<IActionResult> RerollSidAsync(string memberRef)
{
Member member = await db.ResolveMemberAsync(CurrentUser!.Id, memberRef);

View file

@ -1,3 +1,4 @@
using Foxnouns.Backend.Dto;
using Foxnouns.Backend.Utils;
using Microsoft.AspNetCore.Mvc;
@ -17,17 +18,18 @@ public class MetaController : ApiControllerBase
BuildInfo.Version,
BuildInfo.Hash,
(int)FoxnounsMetrics.MemberCount.Value,
new UserInfo(
new UserInfoResponse(
(int)FoxnounsMetrics.UsersCount.Value,
(int)FoxnounsMetrics.UsersActiveMonthCount.Value,
(int)FoxnounsMetrics.UsersActiveWeekCount.Value,
(int)FoxnounsMetrics.UsersActiveDayCount.Value
),
new Limits(
new LimitsResponse(
MembersController.MaxMemberCount,
ValidationUtils.MaxBioLength,
ValidationUtils.MaxCustomPreferences,
AuthUtils.MaxAuthMethodsPerType
AuthUtils.MaxAuthMethodsPerType,
FlagsController.MaxFlagCount
)
)
);
@ -35,23 +37,4 @@ public class MetaController : ApiControllerBase
[HttpGet("/api/v2/coffee")]
public IActionResult BrewCoffee() =>
Problem("Sorry, I'm a teapot!", statusCode: StatusCodes.Status418ImATeapot);
private record MetaResponse(
string Repository,
string Version,
string Hash,
int Members,
UserInfo Users,
Limits Limits
);
private record UserInfo(int Total, int ActiveMonth, int ActiveWeek, int ActiveDay);
// All limits that the frontend should know about (for UI purposes)
private record Limits(
int MemberCount,
int BioLength,
int CustomPreferences,
int MaxAuthMethods
);
}

View file

@ -1,8 +1,8 @@
using System.Diagnostics.CodeAnalysis;
using Coravel.Queuing.Interfaces;
using EntityFramework.Exceptions.Common;
using Foxnouns.Backend.Database;
using Foxnouns.Backend.Database.Models;
using Foxnouns.Backend.Dto;
using Foxnouns.Backend.Jobs;
using Foxnouns.Backend.Middleware;
using Foxnouns.Backend.Services;
@ -27,7 +27,7 @@ public class UsersController(
private readonly ILogger _logger = logger.ForContext<UsersController>();
[HttpGet("{userRef}")]
[ProducesResponseType<UserRendererService.UserResponse>(statusCode: StatusCodes.Status200OK)]
[ProducesResponseType<UserResponse>(statusCode: StatusCodes.Status200OK)]
public async Task<IActionResult> GetUserAsync(string userRef, CancellationToken ct = default)
{
User user = await db.ResolveUserAsync(userRef, CurrentToken, ct);
@ -38,7 +38,7 @@ public class UsersController(
[HttpPatch("@me")]
[Authorize("user.update")]
[ProducesResponseType<UserRendererService.UserResponse>(statusCode: StatusCodes.Status200OK)]
[ProducesResponseType<UserResponse>(statusCode: StatusCodes.Status200OK)]
public async Task<IActionResult> UpdateUserAsync(
[FromBody] UpdateUserRequest req,
CancellationToken ct = default
@ -196,7 +196,7 @@ public class UsersController(
[Authorize("user.update")]
[ProducesResponseType<Dictionary<Snowflake, User.CustomPreference>>(StatusCodes.Status200OK)]
public async Task<IActionResult> UpdateCustomPreferencesAsync(
[FromBody] List<CustomPreferenceUpdate> req,
[FromBody] List<CustomPreferenceUpdateRequest> req,
CancellationToken ct = default
)
{
@ -207,7 +207,7 @@ public class UsersController(
.CustomPreferences.Where(x => req.Any(r => r.Id == x.Key))
.ToDictionary();
foreach (CustomPreferenceUpdate? r in req)
foreach (CustomPreferenceUpdateRequest? r in req)
{
if (r.Id != null && preferences.ContainsKey(r.Id.Value))
{
@ -239,33 +239,6 @@ public class UsersController(
return Ok(user.CustomPreferences);
}
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
public class CustomPreferenceUpdate
{
public Snowflake? Id { get; init; }
public required string Icon { get; set; }
public required string Tooltip { get; set; }
public PreferenceSize Size { get; set; }
public bool Muted { get; set; }
public bool Favourite { get; set; }
}
public class UpdateUserRequest : PatchRequest
{
public string? Username { get; init; }
public string? DisplayName { get; init; }
public string? Bio { get; init; }
public string? Avatar { get; init; }
public string[]? Links { get; init; }
public FieldEntry[]? Names { get; init; }
public Pronoun[]? Pronouns { get; init; }
public Field[]? Fields { get; init; }
public Snowflake[]? Flags { get; init; }
public string? MemberTitle { get; init; }
public bool? MemberListHidden { get; init; }
public string? Timezone { get; init; }
}
[HttpGet("@me/settings")]
[Authorize("user.read_hidden")]
[ProducesResponseType<UserSettings>(statusCode: StatusCodes.Status200OK)]
@ -294,14 +267,9 @@ public class UsersController(
return Ok(user.Settings);
}
public class UpdateUserSettingsRequest : PatchRequest
{
public bool? DarkMode { get; init; }
}
[HttpPost("@me/reroll-sid")]
[Authorize("user.update")]
[ProducesResponseType<UserRendererService.UserResponse>(statusCode: StatusCodes.Status200OK)]
[ProducesResponseType<UserResponse>(statusCode: StatusCodes.Status200OK)]
public async Task<IActionResult> RerollSidAsync()
{
Instant minTimeAgo = clock.GetCurrentInstant() - Duration.FromHours(1);