refactor: more consistent field names, also in STYLE.md

This commit is contained in:
sam 2024-09-09 14:50:00 +02:00
parent 344a0071e5
commit c77ee660ca
Signed by: sam
GPG key ID: B4EF20DDE721CAA1
14 changed files with 86 additions and 71 deletions

View file

@ -7,7 +7,7 @@ using NodaTime;
namespace Foxnouns.Backend.Controllers.Authentication; namespace Foxnouns.Backend.Controllers.Authentication;
[Route("/api/v2/auth")] [Route("/api/v2/auth")]
public class AuthController(Config config, KeyCacheService keyCacheSvc, ILogger logger) : ApiControllerBase public class AuthController(Config config, KeyCacheService keyCache, ILogger logger) : ApiControllerBase
{ {
private readonly ILogger _logger = logger.ForContext<AuthController>(); private readonly ILogger _logger = logger.ForContext<AuthController>();
@ -19,7 +19,7 @@ public class AuthController(Config config, KeyCacheService keyCacheSvc, ILogger
config.DiscordAuth.Enabled, config.DiscordAuth.Enabled,
config.GoogleAuth.Enabled, config.GoogleAuth.Enabled,
config.TumblrAuth.Enabled); config.TumblrAuth.Enabled);
var state = HttpUtility.UrlEncode(await keyCacheSvc.GenerateAuthStateAsync(ct)); var state = HttpUtility.UrlEncode(await keyCache.GenerateAuthStateAsync(ct));
string? discord = null; string? discord = null;
if (config.DiscordAuth is { ClientId: not null, ClientSecret: not null }) if (config.DiscordAuth is { ClientId: not null, ClientSecret: not null })
discord = discord =

View file

@ -15,10 +15,10 @@ public class DiscordAuthController(
ILogger logger, ILogger logger,
IClock clock, IClock clock,
DatabaseContext db, DatabaseContext db,
KeyCacheService keyCacheSvc, KeyCacheService keyCacheService,
AuthService authSvc, AuthService authService,
RemoteAuthService remoteAuthSvc, RemoteAuthService remoteAuthService,
UserRendererService userRendererSvc) : ApiControllerBase UserRendererService userRenderer) : ApiControllerBase
{ {
private readonly ILogger _logger = logger.ForContext<DiscordAuthController>(); private readonly ILogger _logger = logger.ForContext<DiscordAuthController>();
@ -30,17 +30,17 @@ public class DiscordAuthController(
public async Task<IActionResult> CallbackAsync([FromBody] AuthController.CallbackRequest req, CancellationToken ct = default) public async Task<IActionResult> CallbackAsync([FromBody] AuthController.CallbackRequest req, CancellationToken ct = default)
{ {
CheckRequirements(); CheckRequirements();
await keyCacheSvc.ValidateAuthStateAsync(req.State, ct); await keyCacheService.ValidateAuthStateAsync(req.State, ct);
var remoteUser = await remoteAuthSvc.RequestDiscordTokenAsync(req.Code, req.State, ct); var remoteUser = await remoteAuthService.RequestDiscordTokenAsync(req.Code, req.State, ct);
var user = await authSvc.AuthenticateUserAsync(AuthType.Discord, remoteUser.Id, ct: ct); var user = await authService.AuthenticateUserAsync(AuthType.Discord, remoteUser.Id, ct: ct);
if (user != null) return Ok(await GenerateUserTokenAsync(user,ct)); if (user != null) return Ok(await GenerateUserTokenAsync(user,ct));
_logger.Debug("Discord user {Username} ({Id}) authenticated with no local account", remoteUser.Username, _logger.Debug("Discord user {Username} ({Id}) authenticated with no local account", remoteUser.Username,
remoteUser.Id); remoteUser.Id);
var ticket = AuthUtils.RandomToken(); var ticket = AuthUtils.RandomToken();
await keyCacheSvc.SetKeyAsync($"discord:{ticket}", remoteUser, Duration.FromMinutes(20), ct); await keyCacheService.SetKeyAsync($"discord:{ticket}", remoteUser, Duration.FromMinutes(20), ct);
return Ok(new AuthController.CallbackResponse(false, ticket, remoteUser.Username)); return Ok(new AuthController.CallbackResponse(false, ticket, remoteUser.Username));
} }
@ -49,7 +49,7 @@ public class DiscordAuthController(
[ProducesResponseType<AuthController.AuthResponse>(StatusCodes.Status200OK)] [ProducesResponseType<AuthController.AuthResponse>(StatusCodes.Status200OK)]
public async Task<IActionResult> RegisterAsync([FromBody] AuthController.OauthRegisterRequest req, CancellationToken ct = default) public async Task<IActionResult> RegisterAsync([FromBody] AuthController.OauthRegisterRequest req, CancellationToken ct = default)
{ {
var remoteUser = await keyCacheSvc.GetKeyAsync<RemoteAuthService.RemoteUser>($"discord:{req.Ticket}",ct:ct); var remoteUser = await keyCacheService.GetKeyAsync<RemoteAuthService.RemoteUser>($"discord:{req.Ticket}",ct:ct);
if (remoteUser == null) throw new ApiError.BadRequest("Invalid ticket", "ticket", req.Ticket); if (remoteUser == null) throw new ApiError.BadRequest("Invalid ticket", "ticket", req.Ticket);
if (await db.AuthMethods.AnyAsync(a => a.AuthType == AuthType.Discord && a.RemoteId == remoteUser.Id, ct)) if (await db.AuthMethods.AnyAsync(a => a.AuthType == AuthType.Discord && a.RemoteId == remoteUser.Id, ct))
{ {
@ -58,7 +58,7 @@ public class DiscordAuthController(
throw new FoxnounsError("Discord ticket was issued for user with existing link"); throw new FoxnounsError("Discord ticket was issued for user with existing link");
} }
var user = await authSvc.CreateUserWithRemoteAuthAsync(req.Username, AuthType.Discord, remoteUser.Id, var user = await authService.CreateUserWithRemoteAuthAsync(req.Username, AuthType.Discord, remoteUser.Id,
remoteUser.Username, ct: ct); remoteUser.Username, ct: ct);
return Ok(await GenerateUserTokenAsync(user, ct)); return Ok(await GenerateUserTokenAsync(user, ct));
@ -70,7 +70,7 @@ public class DiscordAuthController(
_logger.Debug("Logging user {Id} in with Discord", user.Id); _logger.Debug("Logging user {Id} in with Discord", user.Id);
var (tokenStr, token) = var (tokenStr, token) =
authSvc.GenerateToken(user, frontendApp, ["*"], clock.GetCurrentInstant() + Duration.FromDays(365)); authService.GenerateToken(user, frontendApp, ["*"], clock.GetCurrentInstant() + Duration.FromDays(365));
db.Add(token); db.Add(token);
_logger.Debug("Generated token {TokenId} for {UserId}", user.Id, token.Id); _logger.Debug("Generated token {TokenId} for {UserId}", user.Id, token.Id);
@ -78,7 +78,7 @@ public class DiscordAuthController(
await db.SaveChangesAsync(ct); await db.SaveChangesAsync(ct);
return new AuthController.AuthResponse( return new AuthController.AuthResponse(
await userRendererSvc.RenderUserAsync(user, selfUser: user, renderMembers: false, ct: ct), await userRenderer.RenderUserAsync(user, selfUser: user, renderMembers: false, ct: ct),
tokenStr, tokenStr,
token.ExpiresAt token.ExpiresAt
); );

View file

@ -12,10 +12,10 @@ namespace Foxnouns.Backend.Controllers.Authentication;
[Route("/api/v2/auth/email")] [Route("/api/v2/auth/email")]
public class EmailAuthController( public class EmailAuthController(
DatabaseContext db, DatabaseContext db,
AuthService authSvc, AuthService authService,
MailService mailSvc, MailService mailService,
KeyCacheService keyCacheSvc, KeyCacheService keyCacheService,
UserRendererService userRendererSvc, UserRendererService userRenderer,
IClock clock, IClock clock,
ILogger logger) : ApiControllerBase ILogger logger) : ApiControllerBase
{ {
@ -26,30 +26,30 @@ public class EmailAuthController(
{ {
if (!req.Email.Contains('@')) throw new ApiError.BadRequest("Email is invalid", "email", req.Email); if (!req.Email.Contains('@')) throw new ApiError.BadRequest("Email is invalid", "email", req.Email);
var state = await keyCacheSvc.GenerateRegisterEmailStateAsync(req.Email, userId: null, ct); var state = await keyCacheService.GenerateRegisterEmailStateAsync(req.Email, userId: null, ct);
if (await db.AuthMethods.AnyAsync(a => a.AuthType == AuthType.Email && a.RemoteId == req.Email, ct)) if (await db.AuthMethods.AnyAsync(a => a.AuthType == AuthType.Email && a.RemoteId == req.Email, ct))
return NoContent(); return NoContent();
mailSvc.QueueAccountCreationEmail(req.Email, state); mailService.QueueAccountCreationEmail(req.Email, state);
return NoContent(); return NoContent();
} }
[HttpPost("callback")] [HttpPost("callback")]
public async Task<IActionResult> CallbackAsync([FromBody] CallbackRequest req, CancellationToken ct = default) public async Task<IActionResult> CallbackAsync([FromBody] CallbackRequest req, CancellationToken ct = default)
{ {
var state = await keyCacheSvc.GetRegisterEmailStateAsync(req.State, ct); var state = await keyCacheService.GetRegisterEmailStateAsync(req.State, ct);
if (state == null) throw new ApiError.BadRequest("Invalid state", "state", req.State); if (state == null) throw new ApiError.BadRequest("Invalid state", "state", req.State);
if (state.ExistingUserId != null) if (state.ExistingUserId != null)
{ {
var authMethod = var authMethod =
await authSvc.AddAuthMethodAsync(state.ExistingUserId.Value, AuthType.Email, state.Email, ct: ct); await authService.AddAuthMethodAsync(state.ExistingUserId.Value, AuthType.Email, state.Email, ct: ct);
_logger.Debug("Added email auth {AuthId} for user {UserId}", authMethod.Id, state.ExistingUserId); _logger.Debug("Added email auth {AuthId} for user {UserId}", authMethod.Id, state.ExistingUserId);
return NoContent(); return NoContent();
} }
var ticket = AuthUtils.RandomToken(); var ticket = AuthUtils.RandomToken();
await keyCacheSvc.SetKeyAsync($"email:{ticket}", state.Email, Duration.FromMinutes(20)); await keyCacheService.SetKeyAsync($"email:{ticket}", state.Email, Duration.FromMinutes(20));
return Ok(new AuthController.CallbackResponse(false, ticket, state.Email)); return Ok(new AuthController.CallbackResponse(false, ticket, state.Email));
} }
@ -58,7 +58,7 @@ public class EmailAuthController(
[ProducesResponseType<AuthController.AuthResponse>(StatusCodes.Status200OK)] [ProducesResponseType<AuthController.AuthResponse>(StatusCodes.Status200OK)]
public async Task<IActionResult> LoginAsync([FromBody] LoginRequest req, CancellationToken ct = default) public async Task<IActionResult> LoginAsync([FromBody] LoginRequest req, CancellationToken ct = default)
{ {
var (user, authenticationResult) = await authSvc.AuthenticateUserAsync(req.Email, req.Password, ct); var (user, authenticationResult) = await authService.AuthenticateUserAsync(req.Email, req.Password, ct);
if (authenticationResult == AuthService.EmailAuthenticationResult.MfaRequired) if (authenticationResult == AuthService.EmailAuthenticationResult.MfaRequired)
throw new NotImplementedException("MFA is not implemented yet"); throw new NotImplementedException("MFA is not implemented yet");
@ -67,7 +67,7 @@ public class EmailAuthController(
_logger.Debug("Logging user {Id} in with email and password", user.Id); _logger.Debug("Logging user {Id} in with email and password", user.Id);
var (tokenStr, token) = var (tokenStr, token) =
authSvc.GenerateToken(user, frontendApp, ["*"], clock.GetCurrentInstant() + Duration.FromDays(365)); authService.GenerateToken(user, frontendApp, ["*"], clock.GetCurrentInstant() + Duration.FromDays(365));
db.Add(token); db.Add(token);
_logger.Debug("Generated token {TokenId} for {UserId}", token.Id, user.Id); _logger.Debug("Generated token {TokenId} for {UserId}", token.Id, user.Id);
@ -75,7 +75,7 @@ public class EmailAuthController(
await db.SaveChangesAsync(ct); await db.SaveChangesAsync(ct);
return Ok(new AuthController.AuthResponse( return Ok(new AuthController.AuthResponse(
await userRendererSvc.RenderUserAsync(user, selfUser: user, renderMembers: false, ct: ct), await userRenderer.RenderUserAsync(user, selfUser: user, renderMembers: false, ct: ct),
tokenStr, tokenStr,
token.ExpiresAt token.ExpiresAt
)); ));

View file

@ -9,8 +9,8 @@ namespace Foxnouns.Backend.Controllers;
[Route("/api/v2/debug")] [Route("/api/v2/debug")]
public class DebugController( public class DebugController(
DatabaseContext db, DatabaseContext db,
AuthService authSvc, AuthService authService,
UserRendererService userRendererSvc, UserRendererService userRenderer,
IClock clock, IClock clock,
ILogger logger) : ApiControllerBase ILogger logger) : ApiControllerBase
{ {
@ -22,17 +22,17 @@ public class DebugController(
{ {
_logger.Debug("Creating user with username {Username} and email {Email}", req.Username, req.Email); _logger.Debug("Creating user with username {Username} and email {Email}", req.Username, req.Email);
var user = await authSvc.CreateUserWithPasswordAsync(req.Username, req.Email, req.Password); var user = await authService.CreateUserWithPasswordAsync(req.Username, req.Email, req.Password);
var frontendApp = await db.GetFrontendApplicationAsync(); var frontendApp = await db.GetFrontendApplicationAsync();
var (tokenStr, token) = var (tokenStr, token) =
authSvc.GenerateToken(user, frontendApp, ["*"], clock.GetCurrentInstant() + Duration.FromDays(365)); authService.GenerateToken(user, frontendApp, ["*"], clock.GetCurrentInstant() + Duration.FromDays(365));
db.Add(token); db.Add(token);
await db.SaveChangesAsync(); await db.SaveChangesAsync();
return Ok(new AuthController.AuthResponse( return Ok(new AuthController.AuthResponse(
await userRendererSvc.RenderUserAsync(user, selfUser: user, renderMembers: false), await userRenderer.RenderUserAsync(user, selfUser: user, renderMembers: false),
tokenStr, tokenStr,
token.ExpiresAt token.ExpiresAt
)); ));

View file

@ -16,9 +16,9 @@ namespace Foxnouns.Backend.Controllers;
public class MembersController( public class MembersController(
ILogger logger, ILogger logger,
DatabaseContext db, DatabaseContext db,
MemberRendererService memberRendererService, MemberRendererService memberRenderer,
ISnowflakeGenerator snowflakeGenerator, ISnowflakeGenerator snowflakeGenerator,
ObjectStorageService objectStorage, ObjectStorageService objectStorageService,
IQueue queue) : ApiControllerBase IQueue queue) : ApiControllerBase
{ {
private readonly ILogger _logger = logger.ForContext<MembersController>(); private readonly ILogger _logger = logger.ForContext<MembersController>();
@ -28,7 +28,7 @@ public class MembersController(
public async Task<IActionResult> GetMembersAsync(string userRef, CancellationToken ct = default) public async Task<IActionResult> GetMembersAsync(string userRef, CancellationToken ct = default)
{ {
var user = await db.ResolveUserAsync(userRef, CurrentToken, ct); var user = await db.ResolveUserAsync(userRef, CurrentToken, ct);
return Ok(await memberRendererService.RenderUserMembersAsync(user, CurrentToken)); return Ok(await memberRenderer.RenderUserMembersAsync(user, CurrentToken));
} }
[HttpGet("{memberRef}")] [HttpGet("{memberRef}")]
@ -36,7 +36,7 @@ public class MembersController(
public async Task<IActionResult> GetMemberAsync(string userRef, string memberRef, CancellationToken ct = default) public async Task<IActionResult> GetMemberAsync(string userRef, string memberRef, CancellationToken ct = default)
{ {
var member = await db.ResolveMemberAsync(userRef, memberRef, CurrentToken, ct); var member = await db.ResolveMemberAsync(userRef, memberRef, CurrentToken, ct);
return Ok(memberRendererService.RenderMember(member, CurrentToken)); return Ok(memberRenderer.RenderMember(member, CurrentToken));
} }
[HttpPost("/api/v2/users/@me/members")] [HttpPost("/api/v2/users/@me/members")]
@ -83,7 +83,7 @@ public class MembersController(
queue.QueueInvocableWithPayload<MemberAvatarUpdateInvocable, AvatarUpdatePayload>( queue.QueueInvocableWithPayload<MemberAvatarUpdateInvocable, AvatarUpdatePayload>(
new AvatarUpdatePayload(member.Id, req.Avatar)); new AvatarUpdatePayload(member.Id, req.Avatar));
return Ok(memberRendererService.RenderMember(member, CurrentToken)); return Ok(memberRenderer.RenderMember(member, CurrentToken));
} }
[HttpDelete("/api/v2/users/@me/members/{memberRef}")] [HttpDelete("/api/v2/users/@me/members/{memberRef}")]
@ -101,7 +101,7 @@ public class MembersController(
await db.SaveChangesAsync(ct); await db.SaveChangesAsync(ct);
if (member.Avatar != null) await objectStorage.DeleteMemberAvatarAsync(member.Id, member.Avatar); if (member.Avatar != null) await objectStorageService.DeleteMemberAvatarAsync(member.Id, member.Avatar);
return NoContent(); return NoContent();
} }

View file

@ -14,7 +14,7 @@ namespace Foxnouns.Backend.Controllers;
[Route("/api/v2/users")] [Route("/api/v2/users")]
public class UsersController( public class UsersController(
DatabaseContext db, DatabaseContext db,
UserRendererService userRendererService, UserRendererService userRenderer,
ISnowflakeGenerator snowflakeGenerator, ISnowflakeGenerator snowflakeGenerator,
IQueue queue) : ApiControllerBase IQueue queue) : ApiControllerBase
{ {
@ -23,7 +23,7 @@ public class UsersController(
public async Task<IActionResult> GetUserAsync(string userRef, CancellationToken ct = default) public async Task<IActionResult> GetUserAsync(string userRef, CancellationToken ct = default)
{ {
var user = await db.ResolveUserAsync(userRef, CurrentToken, ct); var user = await db.ResolveUserAsync(userRef, CurrentToken, ct);
return Ok(await userRendererService.RenderUserAsync( return Ok(await userRenderer.RenderUserAsync(
user, user,
selfUser: CurrentUser, selfUser: CurrentUser,
token: CurrentToken, token: CurrentToken,
@ -78,7 +78,7 @@ public class UsersController(
await db.SaveChangesAsync(ct); await db.SaveChangesAsync(ct);
await tx.CommitAsync(ct); await tx.CommitAsync(ct);
return Ok(await userRendererService.RenderUserAsync(user, CurrentUser, renderMembers: false, return Ok(await userRenderer.RenderUserAsync(user, CurrentUser, renderMembers: false,
renderAuthMethods: false, ct: ct)); renderAuthMethods: false, ct: ct));
} }

View file

@ -14,12 +14,12 @@ public static class AvatarObjectExtensions
private static readonly string[] ValidContentTypes = ["image/png", "image/webp", "image/jpeg"]; private static readonly string[] ValidContentTypes = ["image/png", "image/webp", "image/jpeg"];
public static async Task public static async Task
DeleteMemberAvatarAsync(this ObjectStorageService objectStorage, Snowflake id, string hash, CancellationToken ct = default) => DeleteMemberAvatarAsync(this ObjectStorageService objectStorageService, Snowflake id, string hash, CancellationToken ct = default) =>
await objectStorage.RemoveObjectAsync(MemberAvatarUpdateInvocable.Path(id, hash), ct); await objectStorageService.RemoveObjectAsync(MemberAvatarUpdateInvocable.Path(id, hash), ct);
public static async Task public static async Task
DeleteUserAvatarAsync(this ObjectStorageService objectStorage, Snowflake id, string hash, CancellationToken ct = default) => DeleteUserAvatarAsync(this ObjectStorageService objectStorageService, Snowflake id, string hash, CancellationToken ct = default) =>
await objectStorage.RemoveObjectAsync(UserAvatarUpdateInvocable.Path(id, hash), ct); await objectStorageService.RemoveObjectAsync(UserAvatarUpdateInvocable.Path(id, hash), ct);
public static async Task<Stream> ConvertBase64UriToAvatar(this string uri) public static async Task<Stream> ConvertBase64UriToAvatar(this string uri)
{ {

View file

@ -7,31 +7,33 @@ namespace Foxnouns.Backend.Extensions;
public static class KeyCacheExtensions public static class KeyCacheExtensions
{ {
public static async Task<string> GenerateAuthStateAsync(this KeyCacheService keyCacheSvc, CancellationToken ct = default) public static async Task<string> GenerateAuthStateAsync(this KeyCacheService keyCacheService,
CancellationToken ct = default)
{ {
var state = AuthUtils.RandomToken(); var state = AuthUtils.RandomToken();
await keyCacheSvc.SetKeyAsync($"oauth_state:{state}", "", Duration.FromMinutes(10), ct); await keyCacheService.SetKeyAsync($"oauth_state:{state}", "", Duration.FromMinutes(10), ct);
return state; return state;
} }
public static async Task ValidateAuthStateAsync(this KeyCacheService keyCacheSvc, string state, CancellationToken ct = default) public static async Task ValidateAuthStateAsync(this KeyCacheService keyCacheService, string state,
CancellationToken ct = default)
{ {
var val = await keyCacheSvc.GetKeyAsync($"oauth_state:{state}", delete: true, ct); var val = await keyCacheService.GetKeyAsync($"oauth_state:{state}", delete: true, ct);
if (val == null) throw new ApiError.BadRequest("Invalid OAuth state"); if (val == null) throw new ApiError.BadRequest("Invalid OAuth state");
} }
public static async Task<string> GenerateRegisterEmailStateAsync(this KeyCacheService keyCacheSvc, string email, public static async Task<string> GenerateRegisterEmailStateAsync(this KeyCacheService keyCacheService, string email,
Snowflake? userId = null, CancellationToken ct = default) Snowflake? userId = null, CancellationToken ct = default)
{ {
var state = AuthUtils.RandomToken(); var state = AuthUtils.RandomToken();
await keyCacheSvc.SetKeyAsync($"email_state:{state}", new RegisterEmailState(email, userId), await keyCacheService.SetKeyAsync($"email_state:{state}", new RegisterEmailState(email, userId),
Duration.FromDays(1), ct); Duration.FromDays(1), ct);
return state; return state;
} }
public static async Task<RegisterEmailState?> GetRegisterEmailStateAsync(this KeyCacheService keyCacheSvc, public static async Task<RegisterEmailState?> GetRegisterEmailStateAsync(this KeyCacheService keyCacheService,
string state, CancellationToken ct = default) => string state, CancellationToken ct = default) =>
await keyCacheSvc.GetKeyAsync<RegisterEmailState>($"email_state:{state}", delete: true, ct); await keyCacheService.GetKeyAsync<RegisterEmailState>($"email_state:{state}", delete: true, ct);
} }
public record RegisterEmailState(string Email, Snowflake? ExistingUserId); public record RegisterEmailState(string Email, Snowflake? ExistingUserId);

View file

@ -6,7 +6,7 @@ using Foxnouns.Backend.Services;
namespace Foxnouns.Backend.Jobs; namespace Foxnouns.Backend.Jobs;
public class MemberAvatarUpdateInvocable(DatabaseContext db, ObjectStorageService objectStorage, ILogger logger) public class MemberAvatarUpdateInvocable(DatabaseContext db, ObjectStorageService objectStorageService, ILogger logger)
: IInvocable, IInvocableWithPayload<AvatarUpdatePayload> : IInvocable, IInvocableWithPayload<AvatarUpdatePayload>
{ {
private readonly ILogger _logger = logger.ForContext<UserAvatarUpdateInvocable>(); private readonly ILogger _logger = logger.ForContext<UserAvatarUpdateInvocable>();
@ -36,13 +36,13 @@ public class MemberAvatarUpdateInvocable(DatabaseContext db, ObjectStorageServic
image.Seek(0, SeekOrigin.Begin); image.Seek(0, SeekOrigin.Begin);
var prevHash = member.Avatar; var prevHash = member.Avatar;
await objectStorage.PutObjectAsync(Path(id, hash), image, "image/webp"); await objectStorageService.PutObjectAsync(Path(id, hash), image, "image/webp");
member.Avatar = hash; member.Avatar = hash;
await db.SaveChangesAsync(); await db.SaveChangesAsync();
if (prevHash != null && prevHash != hash) if (prevHash != null && prevHash != hash)
await objectStorage.RemoveObjectAsync(Path(id, prevHash)); await objectStorageService.RemoveObjectAsync(Path(id, prevHash));
_logger.Information("Updated avatar for member {MemberId}", id); _logger.Information("Updated avatar for member {MemberId}", id);
} }
@ -69,7 +69,7 @@ public class MemberAvatarUpdateInvocable(DatabaseContext db, ObjectStorageServic
return; return;
} }
await objectStorage.RemoveObjectAsync(Path(member.Id, member.Avatar)); await objectStorageService.RemoveObjectAsync(Path(member.Id, member.Avatar));
member.Avatar = null; member.Avatar = null;
await db.SaveChangesAsync(); await db.SaveChangesAsync();

View file

@ -6,7 +6,7 @@ using Foxnouns.Backend.Services;
namespace Foxnouns.Backend.Jobs; namespace Foxnouns.Backend.Jobs;
public class UserAvatarUpdateInvocable(DatabaseContext db, ObjectStorageService objectStorage, ILogger logger) public class UserAvatarUpdateInvocable(DatabaseContext db, ObjectStorageService objectStorageService, ILogger logger)
: IInvocable, IInvocableWithPayload<AvatarUpdatePayload> : IInvocable, IInvocableWithPayload<AvatarUpdatePayload>
{ {
private readonly ILogger _logger = logger.ForContext<UserAvatarUpdateInvocable>(); private readonly ILogger _logger = logger.ForContext<UserAvatarUpdateInvocable>();
@ -36,13 +36,13 @@ public class UserAvatarUpdateInvocable(DatabaseContext db, ObjectStorageService
image.Seek(0, SeekOrigin.Begin); image.Seek(0, SeekOrigin.Begin);
var prevHash = user.Avatar; var prevHash = user.Avatar;
await objectStorage.PutObjectAsync(Path(id, hash), image, "image/webp"); await objectStorageService.PutObjectAsync(Path(id, hash), image, "image/webp");
user.Avatar = hash; user.Avatar = hash;
await db.SaveChangesAsync(); await db.SaveChangesAsync();
if (prevHash != null && prevHash != hash) if (prevHash != null && prevHash != hash)
await objectStorage.RemoveObjectAsync(Path(id, prevHash)); await objectStorageService.RemoveObjectAsync(Path(id, prevHash));
_logger.Information("Updated avatar for user {UserId}", id); _logger.Information("Updated avatar for user {UserId}", id);
} }
@ -69,7 +69,7 @@ public class UserAvatarUpdateInvocable(DatabaseContext db, ObjectStorageService
return; return;
} }
await objectStorage.RemoveObjectAsync(Path(user.Id, user.Avatar)); await objectStorageService.RemoveObjectAsync(Path(user.Id, user.Avatar));
user.Avatar = null; user.Avatar = null;
await db.SaveChangesAsync(); await db.SaveChangesAsync();

View file

@ -47,7 +47,7 @@ public class MetricsCollectionService(
} }
} }
public class BackgroundMetricsCollectionService(ILogger logger, MetricsCollectionService innerService) public class BackgroundMetricsCollectionService(ILogger logger, MetricsCollectionService metricsCollectionService)
: BackgroundService : BackgroundService
{ {
private readonly ILogger _logger = logger.ForContext<BackgroundMetricsCollectionService>(); private readonly ILogger _logger = logger.ForContext<BackgroundMetricsCollectionService>();
@ -60,7 +60,7 @@ public class BackgroundMetricsCollectionService(ILogger logger, MetricsCollectio
while (await timer.WaitForNextTickAsync(ct)) while (await timer.WaitForNextTickAsync(ct))
{ {
_logger.Debug("Collecting metrics"); _logger.Debug("Collecting metrics");
await innerService.CollectMetricsAsync(ct); await metricsCollectionService.CollectMetricsAsync(ct);
} }
} }
} }

View file

@ -4,7 +4,7 @@ using Minio.Exceptions;
namespace Foxnouns.Backend.Services; namespace Foxnouns.Backend.Services;
public class ObjectStorageService(ILogger logger, Config config, IMinioClient minio) public class ObjectStorageService(ILogger logger, Config config, IMinioClient minioClient)
{ {
private readonly ILogger _logger = logger.ForContext<ObjectStorageService>(); private readonly ILogger _logger = logger.ForContext<ObjectStorageService>();
@ -13,7 +13,8 @@ public class ObjectStorageService(ILogger logger, Config config, IMinioClient mi
_logger.Debug("Deleting object at path {Path}", path); _logger.Debug("Deleting object at path {Path}", path);
try try
{ {
await minio.RemoveObjectAsync(new RemoveObjectArgs().WithBucket(config.Storage.Bucket).WithObject(path), await minioClient.RemoveObjectAsync(
new RemoveObjectArgs().WithBucket(config.Storage.Bucket).WithObject(path),
ct); ct);
} }
catch (InvalidObjectNameException) catch (InvalidObjectNameException)
@ -27,7 +28,7 @@ public class ObjectStorageService(ILogger logger, Config config, IMinioClient mi
_logger.Debug("Putting object at path {Path} with length {Length} and content type {ContentType}", path, _logger.Debug("Putting object at path {Path} with length {Length} and content type {ContentType}", path,
data.Length, contentType); data.Length, contentType);
await minio.PutObjectAsync(new PutObjectArgs() await minioClient.PutObjectAsync(new PutObjectArgs()
.WithBucket(config.Storage.Bucket) .WithBucket(config.Storage.Bucket)
.WithObject(path) .WithObject(path)
.WithObjectSize(data.Length) .WithObjectSize(data.Length)

View file

@ -7,7 +7,7 @@ using NodaTime;
namespace Foxnouns.Backend.Services; namespace Foxnouns.Backend.Services;
public class UserRendererService(DatabaseContext db, MemberRendererService memberRendererService, Config config) public class UserRendererService(DatabaseContext db, MemberRendererService memberRenderer, Config config)
{ {
public async Task<UserResponse> RenderUserAsync(User user, User? selfUser = null, public async Task<UserResponse> RenderUserAsync(User user, User? selfUser = null,
Token? token = null, Token? token = null,
@ -39,7 +39,7 @@ public class UserRendererService(DatabaseContext db, MemberRendererService membe
return new UserResponse( return new UserResponse(
user.Id, user.Username, user.DisplayName, user.Bio, user.MemberTitle, AvatarUrlFor(user), user.Links, user.Id, user.Username, user.DisplayName, user.Bio, user.MemberTitle, AvatarUrlFor(user), user.Links,
user.Names, user.Pronouns, user.Fields, user.CustomPreferences, user.Names, user.Pronouns, user.Fields, user.CustomPreferences,
renderMembers ? members.Select(memberRendererService.RenderPartialMember) : null, renderMembers ? members.Select(memberRenderer.RenderPartialMember) : null,
renderAuthMethods renderAuthMethods
? authMethods.Select(a => new AuthenticationMethodResponse( ? authMethods.Select(a => new AuthenticationMethodResponse(
a.Id, a.AuthType, a.RemoteId, a.Id, a.AuthType, a.RemoteId,

View file

@ -2,10 +2,22 @@
## C# code style ## C# code style
Code should be formatted with `dotnet format` or Rider's built-in formatter. - Code should be formatted with `dotnet format` or Rider's built-in formatter.
Variables should *always* be declared using `var`, unless the correct type - Variables should *always* be declared using `var`,
can't be inferred from the declaration (i.e. if the variable needs to be an unless the correct type can't be inferred from the declaration (i.e. if the variable needs to be an `IEnumerable<T>`
`IEnumerable<T>` instead of a `List<T>`, or if a variable is initialized as `null`). instead of a `List<T>`, or if a variable is initialized as `null`).
### Naming
- Service values should be named the same as the type, but camel case, if the name of the service does *not*
in a verb (i.e. a variable of type `KeyCacheService` should be named `keyCacheService`).
If the name of the service *does* end in a verb, the final "service" should be omitted
(i.e. a variable of type `UserRendererService` should be named `userRenderer`).
- Interface values should be named the same as the type, but camel case, without the leading `I`
(i.e. a variable of type `ISnowflakeGenerator` should be named `snowflakeGenerator`).
- Values of type `DatabaseContext` should always be named `db`.
There are some exceptions to this. For example Sentry's `IHub` should be named `sentry` as the name `hub` isn't clear.
## TypeScript code style ## TypeScript code style