refactor(backend): use explicit types instead of var by default
This commit is contained in:
parent
bc7fd6d804
commit
649988db25
52 changed files with 506 additions and 420 deletions
|
@ -33,14 +33,16 @@ public class AuthController(
|
|||
config.GoogleAuth.Enabled,
|
||||
config.TumblrAuth.Enabled
|
||||
);
|
||||
var state = HttpUtility.UrlEncode(await keyCacheService.GenerateAuthStateAsync(ct));
|
||||
string state = HttpUtility.UrlEncode(await keyCacheService.GenerateAuthStateAsync(ct));
|
||||
string? discord = null;
|
||||
if (config.DiscordAuth is { ClientId: not null, ClientSecret: not null })
|
||||
{
|
||||
discord =
|
||||
$"https://discord.com/oauth2/authorize?response_type=code"
|
||||
+ $"&client_id={config.DiscordAuth.ClientId}&scope=identify"
|
||||
+ $"&prompt=none&state={state}"
|
||||
+ $"&redirect_uri={HttpUtility.UrlEncode($"{config.BaseUrl}/auth/callback/discord")}";
|
||||
}
|
||||
|
||||
return Ok(new UrlsResponse(config.EmailAuth.Enabled, discord, null, null));
|
||||
}
|
||||
|
@ -86,7 +88,7 @@ public class AuthController(
|
|||
)]
|
||||
public async Task<IActionResult> GetAuthMethodAsync(Snowflake id)
|
||||
{
|
||||
var authMethod = await db
|
||||
AuthMethod? authMethod = await db
|
||||
.AuthMethods.Include(a => a.FediverseApplication)
|
||||
.FirstOrDefaultAsync(a => a.UserId == CurrentUser!.Id && a.Id == id);
|
||||
if (authMethod == null)
|
||||
|
@ -99,17 +101,19 @@ public class AuthController(
|
|||
[Authorize("*")]
|
||||
public async Task<IActionResult> DeleteAuthMethodAsync(Snowflake id)
|
||||
{
|
||||
var authMethods = await db
|
||||
List<AuthMethod> authMethods = await db
|
||||
.AuthMethods.Where(a => a.UserId == CurrentUser!.Id)
|
||||
.ToListAsync();
|
||||
if (authMethods.Count < 2)
|
||||
{
|
||||
throw new ApiError(
|
||||
"You cannot remove your last authentication method.",
|
||||
HttpStatusCode.BadRequest,
|
||||
ErrorCode.LastAuthMethod
|
||||
);
|
||||
}
|
||||
|
||||
var authMethod = authMethods.FirstOrDefault(a => a.Id == id);
|
||||
AuthMethod? authMethod = authMethods.FirstOrDefault(a => a.Id == id);
|
||||
if (authMethod == null)
|
||||
throw new ApiError.NotFound("No authentication method with that ID found.");
|
||||
|
||||
|
|
|
@ -34,8 +34,10 @@ public class DiscordAuthController(
|
|||
CheckRequirements();
|
||||
await keyCacheService.ValidateAuthStateAsync(req.State);
|
||||
|
||||
var remoteUser = await remoteAuthService.RequestDiscordTokenAsync(req.Code);
|
||||
var user = await authService.AuthenticateUserAsync(AuthType.Discord, remoteUser.Id);
|
||||
RemoteAuthService.RemoteUser remoteUser = await remoteAuthService.RequestDiscordTokenAsync(
|
||||
req.Code
|
||||
);
|
||||
User? user = await authService.AuthenticateUserAsync(AuthType.Discord, remoteUser.Id);
|
||||
if (user != null)
|
||||
return Ok(await authService.GenerateUserTokenAsync(user));
|
||||
|
||||
|
@ -45,23 +47,14 @@ public class DiscordAuthController(
|
|||
remoteUser.Id
|
||||
);
|
||||
|
||||
var ticket = AuthUtils.RandomToken();
|
||||
string ticket = AuthUtils.RandomToken();
|
||||
await keyCacheService.SetKeyAsync(
|
||||
$"discord:{ticket}",
|
||||
remoteUser,
|
||||
Duration.FromMinutes(20)
|
||||
);
|
||||
|
||||
return Ok(
|
||||
new CallbackResponse(
|
||||
HasAccount: false,
|
||||
Ticket: ticket,
|
||||
RemoteUsername: remoteUser.Username,
|
||||
User: null,
|
||||
Token: null,
|
||||
ExpiresAt: null
|
||||
)
|
||||
);
|
||||
return Ok(new CallbackResponse(false, ticket, remoteUser.Username, null, null, null));
|
||||
}
|
||||
|
||||
[HttpPost("register")]
|
||||
|
@ -70,9 +63,10 @@ public class DiscordAuthController(
|
|||
[FromBody] AuthController.OauthRegisterRequest req
|
||||
)
|
||||
{
|
||||
var remoteUser = await keyCacheService.GetKeyAsync<RemoteAuthService.RemoteUser>(
|
||||
$"discord:{req.Ticket}"
|
||||
);
|
||||
RemoteAuthService.RemoteUser? remoteUser =
|
||||
await keyCacheService.GetKeyAsync<RemoteAuthService.RemoteUser>(
|
||||
$"discord:{req.Ticket}"
|
||||
);
|
||||
if (remoteUser == null)
|
||||
throw new ApiError.BadRequest("Invalid ticket", "ticket", req.Ticket);
|
||||
if (
|
||||
|
@ -88,7 +82,7 @@ public class DiscordAuthController(
|
|||
throw new ApiError.BadRequest("Invalid ticket", "ticket", req.Ticket);
|
||||
}
|
||||
|
||||
var user = await authService.CreateUserWithRemoteAuthAsync(
|
||||
User user = await authService.CreateUserWithRemoteAuthAsync(
|
||||
req.Username,
|
||||
AuthType.Discord,
|
||||
remoteUser.Id,
|
||||
|
@ -104,12 +98,12 @@ public class DiscordAuthController(
|
|||
{
|
||||
CheckRequirements();
|
||||
|
||||
var state = await remoteAuthService.ValidateAddAccountRequestAsync(
|
||||
string state = await remoteAuthService.ValidateAddAccountRequestAsync(
|
||||
CurrentUser!.Id,
|
||||
AuthType.Discord
|
||||
);
|
||||
|
||||
var url =
|
||||
string url =
|
||||
$"https://discord.com/oauth2/authorize?response_type=code"
|
||||
+ $"&client_id={config.DiscordAuth.ClientId}&scope=identify"
|
||||
+ $"&prompt=none&state={state}"
|
||||
|
@ -132,10 +126,12 @@ public class DiscordAuthController(
|
|||
AuthType.Discord
|
||||
);
|
||||
|
||||
var remoteUser = await remoteAuthService.RequestDiscordTokenAsync(req.Code);
|
||||
RemoteAuthService.RemoteUser remoteUser = await remoteAuthService.RequestDiscordTokenAsync(
|
||||
req.Code
|
||||
);
|
||||
try
|
||||
{
|
||||
var authMethod = await authService.AddAuthMethodAsync(
|
||||
AuthMethod authMethod = await authService.AddAuthMethodAsync(
|
||||
CurrentUser.Id,
|
||||
AuthType.Discord,
|
||||
remoteUser.Id,
|
||||
|
@ -169,8 +165,10 @@ public class DiscordAuthController(
|
|||
private void CheckRequirements()
|
||||
{
|
||||
if (!config.DiscordAuth.Enabled)
|
||||
{
|
||||
throw new ApiError.BadRequest(
|
||||
"Discord authentication is not enabled on this instance."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,11 +39,7 @@ public class EmailAuthController(
|
|||
if (!req.Email.Contains('@'))
|
||||
throw new ApiError.BadRequest("Email is invalid", "email", req.Email);
|
||||
|
||||
var state = await keyCacheService.GenerateRegisterEmailStateAsync(
|
||||
req.Email,
|
||||
userId: null,
|
||||
ct
|
||||
);
|
||||
string state = await keyCacheService.GenerateRegisterEmailStateAsync(req.Email, null, ct);
|
||||
|
||||
// If there's already a user with that email address, pretend we sent an email but actually ignore it
|
||||
if (
|
||||
|
@ -63,23 +59,14 @@ public class EmailAuthController(
|
|||
{
|
||||
CheckRequirements();
|
||||
|
||||
var state = await keyCacheService.GetRegisterEmailStateAsync(req.State);
|
||||
RegisterEmailState? state = await keyCacheService.GetRegisterEmailStateAsync(req.State);
|
||||
if (state is not { ExistingUserId: null })
|
||||
throw new ApiError.BadRequest("Invalid state", "state", req.State);
|
||||
|
||||
var ticket = AuthUtils.RandomToken();
|
||||
string ticket = AuthUtils.RandomToken();
|
||||
await keyCacheService.SetKeyAsync($"email:{ticket}", state.Email, Duration.FromMinutes(20));
|
||||
|
||||
return Ok(
|
||||
new CallbackResponse(
|
||||
HasAccount: false,
|
||||
Ticket: ticket,
|
||||
RemoteUsername: state.Email,
|
||||
User: null,
|
||||
Token: null,
|
||||
ExpiresAt: null
|
||||
)
|
||||
);
|
||||
return Ok(new CallbackResponse(false, ticket, state.Email, null, null, null));
|
||||
}
|
||||
|
||||
[HttpPost("register")]
|
||||
|
@ -89,14 +76,18 @@ public class EmailAuthController(
|
|||
{
|
||||
CheckRequirements();
|
||||
|
||||
var email = await keyCacheService.GetKeyAsync($"email:{req.Ticket}");
|
||||
string? email = await keyCacheService.GetKeyAsync($"email:{req.Ticket}");
|
||||
if (email == null)
|
||||
throw new ApiError.BadRequest("Unknown ticket", "ticket", req.Ticket);
|
||||
|
||||
var user = await authService.CreateUserWithPasswordAsync(req.Username, email, req.Password);
|
||||
var frontendApp = await db.GetFrontendApplicationAsync();
|
||||
User user = await authService.CreateUserWithPasswordAsync(
|
||||
req.Username,
|
||||
email,
|
||||
req.Password
|
||||
);
|
||||
Application frontendApp = await db.GetFrontendApplicationAsync();
|
||||
|
||||
var (tokenStr, token) = authService.GenerateToken(
|
||||
(string? tokenStr, Token? token) = authService.GenerateToken(
|
||||
user,
|
||||
frontendApp,
|
||||
["*"],
|
||||
|
@ -110,7 +101,7 @@ public class EmailAuthController(
|
|||
|
||||
return Ok(
|
||||
new AuthController.AuthResponse(
|
||||
await userRenderer.RenderUserAsync(user, selfUser: user, renderMembers: false),
|
||||
await userRenderer.RenderUserAsync(user, user, renderMembers: false),
|
||||
tokenStr,
|
||||
token.ExpiresAt
|
||||
)
|
||||
|
@ -126,19 +117,16 @@ public class EmailAuthController(
|
|||
{
|
||||
CheckRequirements();
|
||||
|
||||
var (user, authenticationResult) = await authService.AuthenticateUserAsync(
|
||||
req.Email,
|
||||
req.Password,
|
||||
ct
|
||||
);
|
||||
(User? user, AuthService.EmailAuthenticationResult authenticationResult) =
|
||||
await authService.AuthenticateUserAsync(req.Email, req.Password, ct);
|
||||
if (authenticationResult == AuthService.EmailAuthenticationResult.MfaRequired)
|
||||
throw new NotImplementedException("MFA is not implemented yet");
|
||||
|
||||
var frontendApp = await db.GetFrontendApplicationAsync(ct);
|
||||
Application frontendApp = await db.GetFrontendApplicationAsync(ct);
|
||||
|
||||
_logger.Debug("Logging user {Id} in with email and password", user.Id);
|
||||
|
||||
var (tokenStr, token) = authService.GenerateToken(
|
||||
(string? tokenStr, Token? token) = authService.GenerateToken(
|
||||
user,
|
||||
frontendApp,
|
||||
["*"],
|
||||
|
@ -152,12 +140,7 @@ public class EmailAuthController(
|
|||
|
||||
return Ok(
|
||||
new AuthController.AuthResponse(
|
||||
await userRenderer.RenderUserAsync(
|
||||
user,
|
||||
selfUser: user,
|
||||
renderMembers: false,
|
||||
ct: ct
|
||||
),
|
||||
await userRenderer.RenderUserAsync(user, user, renderMembers: false, ct: ct),
|
||||
tokenStr,
|
||||
token.ExpiresAt
|
||||
)
|
||||
|
@ -184,7 +167,7 @@ public class EmailAuthController(
|
|||
{
|
||||
CheckRequirements();
|
||||
|
||||
var emails = await db
|
||||
List<AuthMethod> emails = await db
|
||||
.AuthMethods.Where(m => m.UserId == CurrentUser!.Id && m.AuthType == AuthType.Email)
|
||||
.ToListAsync();
|
||||
if (emails.Count > AuthUtils.MaxAuthMethodsPerType)
|
||||
|
@ -207,12 +190,12 @@ public class EmailAuthController(
|
|||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
var state = await keyCacheService.GenerateRegisterEmailStateAsync(
|
||||
string state = await keyCacheService.GenerateRegisterEmailStateAsync(
|
||||
req.Email,
|
||||
userId: CurrentUser!.Id
|
||||
CurrentUser!.Id
|
||||
);
|
||||
|
||||
var emailExists = await db
|
||||
bool emailExists = await db
|
||||
.AuthMethods.Where(m => m.AuthType == AuthType.Email && m.RemoteId == req.Email)
|
||||
.AnyAsync();
|
||||
if (emailExists)
|
||||
|
@ -230,13 +213,13 @@ public class EmailAuthController(
|
|||
{
|
||||
CheckRequirements();
|
||||
|
||||
var state = await keyCacheService.GetRegisterEmailStateAsync(req.State);
|
||||
RegisterEmailState? state = await keyCacheService.GetRegisterEmailStateAsync(req.State);
|
||||
if (state?.ExistingUserId != CurrentUser!.Id)
|
||||
throw new ApiError.BadRequest("Invalid state", "state", req.State);
|
||||
|
||||
try
|
||||
{
|
||||
var authMethod = await authService.AddAuthMethodAsync(
|
||||
AuthMethod authMethod = await authService.AddAuthMethodAsync(
|
||||
CurrentUser.Id,
|
||||
AuthType.Email,
|
||||
state.Email
|
||||
|
@ -252,7 +235,7 @@ public class EmailAuthController(
|
|||
authMethod.Id,
|
||||
AuthType.Email,
|
||||
authMethod.RemoteId,
|
||||
RemoteUsername: null
|
||||
null
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ public class FediverseAuthController(
|
|||
if (instance.Any(c => c is '@' or ':' or '/') || !instance.Contains('.'))
|
||||
throw new ApiError.BadRequest("Not a valid domain.", "instance", instance);
|
||||
|
||||
var url = await fediverseAuthService.GenerateAuthUrlAsync(instance, forceRefresh);
|
||||
string url = await fediverseAuthService.GenerateAuthUrlAsync(instance, forceRefresh);
|
||||
return Ok(new AuthController.SingleUrlResponse(url));
|
||||
}
|
||||
|
||||
|
@ -42,22 +42,19 @@ public class FediverseAuthController(
|
|||
[ProducesResponseType<CallbackResponse>(statusCode: StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> FediverseCallbackAsync([FromBody] CallbackRequest req)
|
||||
{
|
||||
var app = await fediverseAuthService.GetApplicationAsync(req.Instance);
|
||||
var remoteUser = await fediverseAuthService.GetRemoteFediverseUserAsync(
|
||||
app,
|
||||
req.Code,
|
||||
req.State
|
||||
);
|
||||
FediverseApplication app = await fediverseAuthService.GetApplicationAsync(req.Instance);
|
||||
FediverseAuthService.FediverseUser remoteUser =
|
||||
await fediverseAuthService.GetRemoteFediverseUserAsync(app, req.Code, req.State);
|
||||
|
||||
var user = await authService.AuthenticateUserAsync(
|
||||
User? user = await authService.AuthenticateUserAsync(
|
||||
AuthType.Fediverse,
|
||||
remoteUser.Id,
|
||||
instance: app
|
||||
app
|
||||
);
|
||||
if (user != null)
|
||||
return Ok(await authService.GenerateUserTokenAsync(user));
|
||||
|
||||
var ticket = AuthUtils.RandomToken();
|
||||
string ticket = AuthUtils.RandomToken();
|
||||
await keyCacheService.SetKeyAsync(
|
||||
$"fediverse:{ticket}",
|
||||
new FediverseTicketData(app.Id, remoteUser),
|
||||
|
@ -66,12 +63,12 @@ public class FediverseAuthController(
|
|||
|
||||
return Ok(
|
||||
new CallbackResponse(
|
||||
HasAccount: false,
|
||||
Ticket: ticket,
|
||||
RemoteUsername: $"@{remoteUser.Username}@{app.Domain}",
|
||||
User: null,
|
||||
Token: null,
|
||||
ExpiresAt: null
|
||||
false,
|
||||
ticket,
|
||||
$"@{remoteUser.Username}@{app.Domain}",
|
||||
null,
|
||||
null,
|
||||
null
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -82,14 +79,16 @@ public class FediverseAuthController(
|
|||
[FromBody] AuthController.OauthRegisterRequest req
|
||||
)
|
||||
{
|
||||
var ticketData = await keyCacheService.GetKeyAsync<FediverseTicketData>(
|
||||
FediverseTicketData? ticketData = await keyCacheService.GetKeyAsync<FediverseTicketData>(
|
||||
$"fediverse:{req.Ticket}",
|
||||
delete: true
|
||||
true
|
||||
);
|
||||
if (ticketData == null)
|
||||
throw new ApiError.BadRequest("Invalid ticket", "ticket", req.Ticket);
|
||||
|
||||
var app = await db.FediverseApplications.FindAsync(ticketData.ApplicationId);
|
||||
FediverseApplication? app = await db.FediverseApplications.FindAsync(
|
||||
ticketData.ApplicationId
|
||||
);
|
||||
if (app == null)
|
||||
throw new FoxnounsError("Null application found for ticket");
|
||||
|
||||
|
@ -111,12 +110,12 @@ public class FediverseAuthController(
|
|||
throw new ApiError.BadRequest("Invalid ticket", "ticket", req.Ticket);
|
||||
}
|
||||
|
||||
var user = await authService.CreateUserWithRemoteAuthAsync(
|
||||
User user = await authService.CreateUserWithRemoteAuthAsync(
|
||||
req.Username,
|
||||
AuthType.Fediverse,
|
||||
ticketData.User.Id,
|
||||
ticketData.User.Username,
|
||||
instance: app
|
||||
app
|
||||
);
|
||||
|
||||
return Ok(await authService.GenerateUserTokenAsync(user));
|
||||
|
@ -132,13 +131,13 @@ public class FediverseAuthController(
|
|||
if (instance.Any(c => c is '@' or ':' or '/') || !instance.Contains('.'))
|
||||
throw new ApiError.BadRequest("Not a valid domain.", "instance", instance);
|
||||
|
||||
var state = await remoteAuthService.ValidateAddAccountRequestAsync(
|
||||
string state = await remoteAuthService.ValidateAddAccountRequestAsync(
|
||||
CurrentUser!.Id,
|
||||
AuthType.Fediverse,
|
||||
instance
|
||||
);
|
||||
|
||||
var url = await fediverseAuthService.GenerateAuthUrlAsync(instance, forceRefresh, state);
|
||||
string url = await fediverseAuthService.GenerateAuthUrlAsync(instance, forceRefresh, state);
|
||||
return Ok(new AuthController.SingleUrlResponse(url));
|
||||
}
|
||||
|
||||
|
@ -153,11 +152,12 @@ public class FediverseAuthController(
|
|||
req.Instance
|
||||
);
|
||||
|
||||
var app = await fediverseAuthService.GetApplicationAsync(req.Instance);
|
||||
var remoteUser = await fediverseAuthService.GetRemoteFediverseUserAsync(app, req.Code);
|
||||
FediverseApplication app = await fediverseAuthService.GetApplicationAsync(req.Instance);
|
||||
FediverseAuthService.FediverseUser remoteUser =
|
||||
await fediverseAuthService.GetRemoteFediverseUserAsync(app, req.Code);
|
||||
try
|
||||
{
|
||||
var authMethod = await authService.AddAuthMethodAsync(
|
||||
AuthMethod authMethod = await authService.AddAuthMethodAsync(
|
||||
CurrentUser.Id,
|
||||
AuthType.Fediverse,
|
||||
remoteUser.Id,
|
||||
|
|
|
@ -25,7 +25,7 @@ public class ExportsController(
|
|||
[HttpGet]
|
||||
public async Task<IActionResult> GetDataExportsAsync()
|
||||
{
|
||||
var export = await db
|
||||
DataExport? export = await db
|
||||
.DataExports.Where(d => d.UserId == CurrentUser!.Id)
|
||||
.OrderByDescending(d => d.Id)
|
||||
.FirstOrDefaultAsync();
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using Coravel.Queuing.Interfaces;
|
||||
using Foxnouns.Backend.Database;
|
||||
using Foxnouns.Backend.Database.Models;
|
||||
using Foxnouns.Backend.Extensions;
|
||||
using Foxnouns.Backend.Jobs;
|
||||
using Foxnouns.Backend.Middleware;
|
||||
|
@ -7,6 +8,7 @@ using Foxnouns.Backend.Services;
|
|||
using Foxnouns.Backend.Utils;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Storage;
|
||||
|
||||
namespace Foxnouns.Backend.Controllers;
|
||||
|
||||
|
@ -29,7 +31,9 @@ public class FlagsController(
|
|||
)]
|
||||
public async Task<IActionResult> GetFlagsAsync(CancellationToken ct = default)
|
||||
{
|
||||
var flags = await db.PrideFlags.Where(f => f.UserId == CurrentUser!.Id).ToListAsync(ct);
|
||||
List<PrideFlag> flags = await db
|
||||
.PrideFlags.Where(f => f.UserId == CurrentUser!.Id)
|
||||
.ToListAsync(ct);
|
||||
|
||||
return Ok(flags.Select(userRenderer.RenderPrideFlag));
|
||||
}
|
||||
|
@ -43,7 +47,7 @@ public class FlagsController(
|
|||
{
|
||||
ValidationUtils.Validate(ValidateFlag(req.Name, req.Description, req.Image));
|
||||
|
||||
var id = snowflakeGenerator.GenerateSnowflake();
|
||||
Snowflake id = snowflakeGenerator.GenerateSnowflake();
|
||||
|
||||
queue.QueueInvocableWithPayload<CreateFlagInvocable, CreateFlagPayload>(
|
||||
new CreateFlagPayload(id, CurrentUser!.Id, req.Name, req.Image, req.Description)
|
||||
|
@ -62,7 +66,7 @@ public class FlagsController(
|
|||
{
|
||||
ValidationUtils.Validate(ValidateFlag(req.Name, req.Description, null));
|
||||
|
||||
var flag = await db.PrideFlags.FirstOrDefaultAsync(f =>
|
||||
PrideFlag? flag = await db.PrideFlags.FirstOrDefaultAsync(f =>
|
||||
f.Id == id && f.UserId == CurrentUser!.Id
|
||||
);
|
||||
if (flag == null)
|
||||
|
@ -90,20 +94,20 @@ public class FlagsController(
|
|||
[Authorize("user.update")]
|
||||
public async Task<IActionResult> DeleteFlagAsync(Snowflake id)
|
||||
{
|
||||
await using var tx = await db.Database.BeginTransactionAsync();
|
||||
await using IDbContextTransaction tx = await db.Database.BeginTransactionAsync();
|
||||
|
||||
var flag = await db.PrideFlags.FirstOrDefaultAsync(f =>
|
||||
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.");
|
||||
|
||||
var hash = flag.Hash;
|
||||
string hash = flag.Hash;
|
||||
|
||||
db.PrideFlags.Remove(flag);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
var flagCount = await db.PrideFlags.CountAsync(f => f.Hash == flag.Hash);
|
||||
int flagCount = await db.PrideFlags.CountAsync(f => f.Hash == flag.Hash);
|
||||
if (flagCount == 0)
|
||||
{
|
||||
try
|
||||
|
|
|
@ -44,21 +44,22 @@ public partial class InternalController(DatabaseContext db) : ControllerBase
|
|||
[HttpPost("request-data")]
|
||||
public async Task<IActionResult> GetRequestDataAsync([FromBody] RequestDataRequest req)
|
||||
{
|
||||
var endpoint = GetEndpoint(HttpContext, req.Path, req.Method);
|
||||
RouteEndpoint? endpoint = GetEndpoint(HttpContext, req.Path, req.Method);
|
||||
if (endpoint == null)
|
||||
throw new ApiError.BadRequest("Path/method combination is invalid");
|
||||
|
||||
var actionDescriptor = endpoint.Metadata.GetMetadata<ControllerActionDescriptor>();
|
||||
var template = actionDescriptor?.AttributeRouteInfo?.Template;
|
||||
ControllerActionDescriptor? actionDescriptor =
|
||||
endpoint.Metadata.GetMetadata<ControllerActionDescriptor>();
|
||||
string? template = actionDescriptor?.AttributeRouteInfo?.Template;
|
||||
if (template == null)
|
||||
throw new FoxnounsError("Template value was null on valid endpoint");
|
||||
template = GetCleanedTemplate(template);
|
||||
|
||||
// If no token was supplied, or it isn't valid base 64, return a null user ID (limiting by IP)
|
||||
if (!AuthUtils.TryParseToken(req.Token, out var rawToken))
|
||||
if (!AuthUtils.TryParseToken(req.Token, out byte[]? rawToken))
|
||||
return Ok(new RequestDataResponse(null, template));
|
||||
|
||||
var userId = await db.GetTokenUserId(rawToken);
|
||||
Snowflake? userId = await db.GetTokenUserId(rawToken);
|
||||
return Ok(new RequestDataResponse(userId, template));
|
||||
}
|
||||
|
||||
|
@ -72,12 +73,13 @@ public partial class InternalController(DatabaseContext db) : ControllerBase
|
|||
string requestMethod
|
||||
)
|
||||
{
|
||||
var endpointDataSource = httpContext.RequestServices.GetService<EndpointDataSource>();
|
||||
EndpointDataSource? endpointDataSource =
|
||||
httpContext.RequestServices.GetService<EndpointDataSource>();
|
||||
if (endpointDataSource == null)
|
||||
return null;
|
||||
var endpoints = endpointDataSource.Endpoints.OfType<RouteEndpoint>();
|
||||
IEnumerable<RouteEndpoint> endpoints = endpointDataSource.Endpoints.OfType<RouteEndpoint>();
|
||||
|
||||
foreach (var endpoint in endpoints)
|
||||
foreach (RouteEndpoint? endpoint in endpoints)
|
||||
{
|
||||
if (endpoint.RoutePattern.RawText == null)
|
||||
continue;
|
||||
|
@ -86,9 +88,10 @@ public partial class InternalController(DatabaseContext db) : ControllerBase
|
|||
TemplateParser.Parse(endpoint.RoutePattern.RawText),
|
||||
new RouteValueDictionary()
|
||||
);
|
||||
if (!templateMatcher.TryMatch(url, new()))
|
||||
if (!templateMatcher.TryMatch(url, new RouteValueDictionary()))
|
||||
continue;
|
||||
var httpMethodAttribute = endpoint.Metadata.GetMetadata<HttpMethodAttribute>();
|
||||
HttpMethodAttribute? httpMethodAttribute =
|
||||
endpoint.Metadata.GetMetadata<HttpMethodAttribute>();
|
||||
if (
|
||||
httpMethodAttribute != null
|
||||
&& !httpMethodAttribute.HttpMethods.Any(x =>
|
||||
|
|
|
@ -9,6 +9,7 @@ using Foxnouns.Backend.Services;
|
|||
using Foxnouns.Backend.Utils;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Storage;
|
||||
using NodaTime;
|
||||
|
||||
namespace Foxnouns.Backend.Controllers;
|
||||
|
@ -32,7 +33,7 @@ public class MembersController(
|
|||
)]
|
||||
public async Task<IActionResult> GetMembersAsync(string userRef, CancellationToken ct = default)
|
||||
{
|
||||
var user = await db.ResolveUserAsync(userRef, CurrentToken, ct);
|
||||
User user = await db.ResolveUserAsync(userRef, CurrentToken, ct);
|
||||
return Ok(await memberRenderer.RenderUserMembersAsync(user, CurrentToken));
|
||||
}
|
||||
|
||||
|
@ -44,7 +45,7 @@ public class MembersController(
|
|||
CancellationToken ct = default
|
||||
)
|
||||
{
|
||||
var member = await db.ResolveMemberAsync(userRef, memberRef, CurrentToken, ct);
|
||||
Member member = await db.ResolveMemberAsync(userRef, memberRef, CurrentToken, ct);
|
||||
return Ok(memberRenderer.RenderMember(member, CurrentToken));
|
||||
}
|
||||
|
||||
|
@ -78,7 +79,7 @@ public class MembersController(
|
|||
]
|
||||
);
|
||||
|
||||
var memberCount = await db.Members.CountAsync(m => m.UserId == CurrentUser.Id, ct);
|
||||
int memberCount = await db.Members.CountAsync(m => m.UserId == CurrentUser.Id, ct);
|
||||
if (memberCount >= MaxMemberCount)
|
||||
throw new ApiError.BadRequest("Maximum number of members reached");
|
||||
|
||||
|
@ -120,9 +121,11 @@ public class MembersController(
|
|||
}
|
||||
|
||||
if (req.Avatar != null)
|
||||
{
|
||||
queue.QueueInvocableWithPayload<MemberAvatarUpdateInvocable, AvatarUpdatePayload>(
|
||||
new AvatarUpdatePayload(member.Id, req.Avatar)
|
||||
);
|
||||
}
|
||||
|
||||
return Ok(memberRenderer.RenderMember(member, CurrentToken));
|
||||
}
|
||||
|
@ -134,8 +137,8 @@ public class MembersController(
|
|||
[FromBody] UpdateMemberRequest req
|
||||
)
|
||||
{
|
||||
await using var tx = await db.Database.BeginTransactionAsync();
|
||||
var member = await db.ResolveMemberAsync(CurrentUser!.Id, memberRef);
|
||||
await using IDbContextTransaction tx = await db.Database.BeginTransactionAsync();
|
||||
Member member = await db.ResolveMemberAsync(CurrentUser!.Id, memberRef);
|
||||
var errors = new List<(string, ValidationError?)>();
|
||||
|
||||
// We might add extra validations for names later down the line.
|
||||
|
@ -197,7 +200,11 @@ public class MembersController(
|
|||
|
||||
if (req.Flags != null)
|
||||
{
|
||||
var flagError = await db.SetMemberFlagsAsync(CurrentUser!.Id, member.Id, req.Flags);
|
||||
ValidationError? flagError = await db.SetMemberFlagsAsync(
|
||||
CurrentUser!.Id,
|
||||
member.Id,
|
||||
req.Flags
|
||||
);
|
||||
if (flagError != null)
|
||||
errors.Add(("flags", flagError));
|
||||
}
|
||||
|
@ -210,9 +217,12 @@ public class MembersController(
|
|||
// (atomic operations are hard when combined with background jobs)
|
||||
// so it's in a separate block to the validation above.
|
||||
if (req.HasProperty(nameof(req.Avatar)))
|
||||
{
|
||||
queue.QueueInvocableWithPayload<MemberAvatarUpdateInvocable, AvatarUpdatePayload>(
|
||||
new AvatarUpdatePayload(member.Id, req.Avatar)
|
||||
);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await db.SaveChangesAsync();
|
||||
|
@ -254,8 +264,8 @@ public class MembersController(
|
|||
[Authorize("member.update")]
|
||||
public async Task<IActionResult> DeleteMemberAsync(string memberRef)
|
||||
{
|
||||
var member = await db.ResolveMemberAsync(CurrentUser!.Id, memberRef);
|
||||
var deleteCount = await db
|
||||
Member member = await db.ResolveMemberAsync(CurrentUser!.Id, memberRef);
|
||||
int deleteCount = await db
|
||||
.Members.Where(m => m.UserId == CurrentUser!.Id && m.Id == member.Id)
|
||||
.ExecuteDeleteAsync();
|
||||
if (deleteCount == 0)
|
||||
|
@ -289,9 +299,9 @@ public class MembersController(
|
|||
[ProducesResponseType<UserRendererService.UserResponse>(statusCode: StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> RerollSidAsync(string memberRef)
|
||||
{
|
||||
var member = await db.ResolveMemberAsync(CurrentUser!.Id, memberRef);
|
||||
Member member = await db.ResolveMemberAsync(CurrentUser!.Id, memberRef);
|
||||
|
||||
var minTimeAgo = clock.GetCurrentInstant() - Duration.FromHours(1);
|
||||
Instant minTimeAgo = clock.GetCurrentInstant() - Duration.FromHours(1);
|
||||
if (CurrentUser!.LastSidReroll > minTimeAgo)
|
||||
throw new ApiError.BadRequest("Cannot reroll short ID yet");
|
||||
|
||||
|
@ -308,7 +318,10 @@ public class MembersController(
|
|||
);
|
||||
|
||||
// Fetch the new sid then pass that to RenderMember
|
||||
var newSid = await db.Members.Where(m => m.Id == member.Id).Select(m => m.Sid).FirstAsync();
|
||||
string newSid = await db
|
||||
.Members.Where(m => m.Id == member.Id)
|
||||
.Select(m => m.Sid)
|
||||
.FirstAsync();
|
||||
return Ok(memberRenderer.RenderMember(member, CurrentToken, newSid));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,9 +10,8 @@ public class MetaController : ApiControllerBase
|
|||
|
||||
[HttpGet]
|
||||
[ProducesResponseType<MetaResponse>(StatusCodes.Status200OK)]
|
||||
public IActionResult GetMeta()
|
||||
{
|
||||
return Ok(
|
||||
public IActionResult GetMeta() =>
|
||||
Ok(
|
||||
new MetaResponse(
|
||||
Repository,
|
||||
BuildInfo.Version,
|
||||
|
@ -25,14 +24,13 @@ public class MetaController : ApiControllerBase
|
|||
(int)FoxnounsMetrics.UsersActiveDayCount.Value
|
||||
),
|
||||
new Limits(
|
||||
MemberCount: MembersController.MaxMemberCount,
|
||||
BioLength: ValidationUtils.MaxBioLength,
|
||||
CustomPreferences: ValidationUtils.MaxCustomPreferences,
|
||||
MaxAuthMethods: AuthUtils.MaxAuthMethodsPerType
|
||||
MembersController.MaxMemberCount,
|
||||
ValidationUtils.MaxBioLength,
|
||||
ValidationUtils.MaxCustomPreferences,
|
||||
AuthUtils.MaxAuthMethodsPerType
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
[HttpGet("/api/v2/coffee")]
|
||||
public IActionResult BrewCoffee() =>
|
||||
|
|
|
@ -24,7 +24,7 @@ public class SidController(Config config, DatabaseContext db) : ApiControllerBas
|
|||
|
||||
private async Task<IActionResult> ResolveUserSidAsync(string id, CancellationToken ct = default)
|
||||
{
|
||||
var username = await db
|
||||
string? username = await db
|
||||
.Users.Where(u => u.Sid == id.ToLowerInvariant() && !u.Deleted)
|
||||
.Select(u => u.Username)
|
||||
.FirstOrDefaultAsync(ct);
|
||||
|
|
|
@ -9,6 +9,7 @@ using Foxnouns.Backend.Services;
|
|||
using Foxnouns.Backend.Utils;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Storage;
|
||||
using NodaTime;
|
||||
|
||||
namespace Foxnouns.Backend.Controllers;
|
||||
|
@ -29,16 +30,9 @@ public class UsersController(
|
|||
[ProducesResponseType<UserRendererService.UserResponse>(statusCode: StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> GetUserAsync(string userRef, CancellationToken ct = default)
|
||||
{
|
||||
var user = await db.ResolveUserAsync(userRef, CurrentToken, ct);
|
||||
User user = await db.ResolveUserAsync(userRef, CurrentToken, ct);
|
||||
return Ok(
|
||||
await userRenderer.RenderUserAsync(
|
||||
user,
|
||||
selfUser: CurrentUser,
|
||||
token: CurrentToken,
|
||||
renderMembers: true,
|
||||
renderAuthMethods: true,
|
||||
ct: ct
|
||||
)
|
||||
await userRenderer.RenderUserAsync(user, CurrentUser, CurrentToken, true, true, ct: ct)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -50,8 +44,8 @@ public class UsersController(
|
|||
CancellationToken ct = default
|
||||
)
|
||||
{
|
||||
await using var tx = await db.Database.BeginTransactionAsync(ct);
|
||||
var user = await db.Users.FirstAsync(u => u.Id == CurrentUser!.Id, ct);
|
||||
await using IDbContextTransaction tx = await db.Database.BeginTransactionAsync(ct);
|
||||
User user = await db.Users.FirstAsync(u => u.Id == CurrentUser!.Id, ct);
|
||||
var errors = new List<(string, ValidationError?)>();
|
||||
|
||||
if (req.Username != null && req.Username != user.Username)
|
||||
|
@ -108,7 +102,7 @@ public class UsersController(
|
|||
|
||||
if (req.Flags != null)
|
||||
{
|
||||
var flagError = await db.SetUserFlagsAsync(CurrentUser!.Id, req.Flags);
|
||||
ValidationError? flagError = await db.SetUserFlagsAsync(CurrentUser!.Id, req.Flags);
|
||||
if (flagError != null)
|
||||
errors.Add(("flags", flagError));
|
||||
}
|
||||
|
@ -143,12 +137,14 @@ public class UsersController(
|
|||
if (TimeZoneInfo.TryFindSystemTimeZoneById(req.Timezone, out _))
|
||||
user.Timezone = req.Timezone;
|
||||
else
|
||||
{
|
||||
errors.Add(
|
||||
(
|
||||
"timezone",
|
||||
ValidationError.GenericValidationError("Invalid timezone", req.Timezone)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -157,9 +153,11 @@ public class UsersController(
|
|||
// (atomic operations are hard when combined with background jobs)
|
||||
// so it's in a separate block to the validation above.
|
||||
if (req.HasProperty(nameof(req.Avatar)))
|
||||
{
|
||||
queue.QueueInvocableWithPayload<UserAvatarUpdateInvocable, AvatarUpdatePayload>(
|
||||
new AvatarUpdatePayload(CurrentUser!.Id, req.Avatar)
|
||||
);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
|
@ -202,12 +200,12 @@ public class UsersController(
|
|||
{
|
||||
ValidationUtils.Validate(ValidationUtils.ValidateCustomPreferences(req));
|
||||
|
||||
var user = await db.ResolveUserAsync(CurrentUser!.Id, ct);
|
||||
User user = await db.ResolveUserAsync(CurrentUser!.Id, ct);
|
||||
var preferences = user
|
||||
.CustomPreferences.Where(x => req.Any(r => r.Id == x.Key))
|
||||
.ToDictionary();
|
||||
|
||||
foreach (var r in req)
|
||||
foreach (CustomPreferenceUpdate? r in req)
|
||||
{
|
||||
if (r.Id != null && preferences.ContainsKey(r.Id.Value))
|
||||
{
|
||||
|
@ -271,7 +269,7 @@ public class UsersController(
|
|||
[ProducesResponseType<UserSettings>(statusCode: StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> GetUserSettingsAsync(CancellationToken ct = default)
|
||||
{
|
||||
var user = await db.Users.FirstAsync(u => u.Id == CurrentUser!.Id, ct);
|
||||
User user = await db.Users.FirstAsync(u => u.Id == CurrentUser!.Id, ct);
|
||||
return Ok(user.Settings);
|
||||
}
|
||||
|
||||
|
@ -283,7 +281,7 @@ public class UsersController(
|
|||
CancellationToken ct = default
|
||||
)
|
||||
{
|
||||
var user = await db.Users.FirstAsync(u => u.Id == CurrentUser!.Id, ct);
|
||||
User user = await db.Users.FirstAsync(u => u.Id == CurrentUser!.Id, ct);
|
||||
|
||||
if (req.HasProperty(nameof(req.DarkMode)))
|
||||
user.Settings.DarkMode = req.DarkMode;
|
||||
|
@ -304,7 +302,7 @@ public class UsersController(
|
|||
[ProducesResponseType<UserRendererService.UserResponse>(statusCode: StatusCodes.Status200OK)]
|
||||
public async Task<IActionResult> RerollSidAsync()
|
||||
{
|
||||
var minTimeAgo = clock.GetCurrentInstant() - Duration.FromHours(1);
|
||||
Instant minTimeAgo = clock.GetCurrentInstant() - Duration.FromHours(1);
|
||||
if (CurrentUser!.LastSidReroll > minTimeAgo)
|
||||
throw new ApiError.BadRequest("Cannot reroll short ID yet");
|
||||
|
||||
|
@ -318,18 +316,18 @@ public class UsersController(
|
|||
);
|
||||
|
||||
// Get the user's new sid
|
||||
var newSid = await db
|
||||
string newSid = await db
|
||||
.Users.Where(u => u.Id == CurrentUser.Id)
|
||||
.Select(u => u.Sid)
|
||||
.FirstAsync();
|
||||
|
||||
var user = await db.ResolveUserAsync(CurrentUser.Id);
|
||||
User user = await db.ResolveUserAsync(CurrentUser.Id);
|
||||
return Ok(
|
||||
await userRenderer.RenderUserAsync(
|
||||
CurrentUser,
|
||||
user,
|
||||
CurrentUser,
|
||||
CurrentToken,
|
||||
renderMembers: false,
|
||||
false,
|
||||
overrideSid: newSid
|
||||
)
|
||||
);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue