Files
FictionArchive/FictionArchive.Service.UserService/Services/UserManagementService.cs
2025-12-29 11:20:23 -05:00

121 lines
4.0 KiB
C#

using FictionArchive.Service.UserService.Models.Database;
using FictionArchive.Service.UserService.Services.AuthenticationClient;
using Microsoft.EntityFrameworkCore;
namespace FictionArchive.Service.UserService.Services;
public class UserManagementService
{
private readonly ILogger<UserManagementService> _logger;
private readonly UserServiceDbContext _dbContext;
private readonly IAuthenticationServiceClient _authClient;
public UserManagementService(
UserServiceDbContext dbContext,
ILogger<UserManagementService> logger,
IAuthenticationServiceClient authClient)
{
_dbContext = dbContext;
_logger = logger;
_authClient = authClient;
}
/// <summary>
/// Invites a new user by creating them in Authentik, saving to the database, and sending a recovery email.
/// </summary>
/// <param name="inviter">The user sending the invite</param>
/// <param name="email">Email address of the invitee</param>
/// <param name="username">Username for the invitee</param>
/// <returns>The created user, or null if the invite failed</returns>
public async Task<User?> InviteUserAsync(User inviter, string email, string username)
{
// Check if inviter has available invites
if (inviter.AvailableInvites <= 0)
{
_logger.LogWarning("User {InviterId} has no available invites", inviter.Id);
return null;
}
// Check if email is already in use
var existingUser = await _dbContext.Users
.AsQueryable()
.FirstOrDefaultAsync(u => u.Email == email);
if (existingUser != null)
{
_logger.LogWarning("Email {Email} is already in use", email);
return null;
}
// Check if username is already in use
var existingUsername = await _dbContext.Users
.AsQueryable()
.FirstOrDefaultAsync(u => u.Username == username);
if (existingUsername != null)
{
_logger.LogWarning("Username {Username} is already in use", username);
return null;
}
// Create user in Authentik
var authentikUser = await _authClient.CreateUserAsync(username, email, username);
if (authentikUser == null)
{
_logger.LogError("Failed to create user {Username} in Authentik", username);
return null;
}
// Send recovery email via Authentik
var emailSent = await _authClient.SendRecoveryEmailAsync(authentikUser.Pk);
if (!emailSent)
{
_logger.LogWarning(
"User {Username} was created in Authentik but recovery email failed to send. Authentik pk: {Pk}",
username, authentikUser.Pk);
// Continue anyway - the user is created, admin can resend email manually
}
// Create user in local database
var newUser = new User
{
Username = username,
Email = email,
OAuthProviderId = authentikUser.Uid,
Disabled = false,
AvailableInvites = 0,
InviterId = inviter.Id
};
_dbContext.Users.Add(newUser);
// Decrement inviter's available invites
inviter.AvailableInvites--;
await _dbContext.SaveChangesAsync();
_logger.LogInformation(
"User {Username} was successfully invited by {InviterId}. New user id: {NewUserId}",
username, inviter.Id, newUser.Id);
return newUser;
}
/// <summary>
/// Gets a user by their OAuth provider ID (Authentik UID).
/// </summary>
public async Task<User?> GetUserByOAuthProviderIdAsync(string oAuthProviderId)
{
return await _dbContext.Users
.AsQueryable()
.FirstOrDefaultAsync(u => u.OAuthProviderId == oAuthProviderId);
}
/// <summary>
/// Gets all users as a queryable for GraphQL.
/// </summary>
public IQueryable<User> GetUsers()
{
return _dbContext.Users.AsQueryable();
}
}