mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-09-30 20:03:11 +00:00
DeleteUserWithCascadeAsync never removed UserPermissionOverrides rows, and the FK on UserId is Restrict. Once an override was saved for a member, the admin hard-delete (DeleteUserAsync -> DeleteUserWithCascadeAsync) failed the foreign key inside the transaction and the controller surfaced it as a 400, so the user was never deleted. Add an explicit ExecuteDelete on UserPermissionOverrides before the user is removed, matching how UserRoles is already cleared in the same method (no schema change). SetOverrideAsync was check-then-insert on the composite key with no DbUpdateException handling, so two concurrent PUTs for the same (UserId, PermissionKey) let the loser violate PK_UserPermissionOverrides and return 500. Catch the conflict, detach the pending insert, and converge by updating the persisted row to the caller's requested state.
181 lines
6.5 KiB
C#
181 lines
6.5 KiB
C#
using Data.SeaHavenIndustries;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using SeaHaven.DataServices.Dto;
|
|
using SeaHaven.DataServices.Interfaces;
|
|
|
|
namespace SeaHaven.DataServices.Implementation
|
|
{
|
|
public class UserDataService : IUserDataService
|
|
{
|
|
private readonly ApplicationDbContext _context;
|
|
|
|
public UserDataService(ApplicationDbContext context)
|
|
{
|
|
_context = context;
|
|
}
|
|
|
|
public async Task<ApplicationUser?> GetByIdAsync(string id)
|
|
{
|
|
return await _context.Users.FindAsync(id);
|
|
}
|
|
|
|
public async Task<IEnumerable<ApplicationUser>> GetAllAsync()
|
|
{
|
|
return await _context.Users.ToListAsync();
|
|
}
|
|
|
|
public async Task<ApplicationUser?> GetByEmailAsync(string email)
|
|
{
|
|
return await _context.Users
|
|
.FirstOrDefaultAsync(u => u.Email == email);
|
|
}
|
|
|
|
public async Task<bool> ExistsAsync(string id)
|
|
{
|
|
return await _context.Users.AnyAsync(u => u.Id == id);
|
|
}
|
|
|
|
public async Task<IReadOnlyList<UserListRowData>> GetAllForListAsync(CancellationToken cancellationToken)
|
|
{
|
|
return await _context.Users
|
|
.AsNoTracking()
|
|
.Select(user => new UserListRowData
|
|
{
|
|
Id = user.Id,
|
|
RoleName = user.PhoneNumber,
|
|
Email = user.Email,
|
|
Name = ((user.FirstName ?? "") + " " + (user.LastName ?? "")).Trim(),
|
|
CreatedDate = user.CreatedDate,
|
|
Status = user.UniqueName
|
|
})
|
|
.ToListAsync(cancellationToken);
|
|
}
|
|
|
|
public async Task<UserProfileData?> GetProfileAsync(string id, CancellationToken cancellationToken)
|
|
{
|
|
return await _context.Users
|
|
.AsNoTracking()
|
|
.Where(u => u.Id == id)
|
|
.Select(user => new UserProfileData
|
|
{
|
|
Id = user.Id,
|
|
FirstName = user.FirstName,
|
|
Email = user.Email,
|
|
Contact = user.Contact,
|
|
CreatedDate = user.CreatedDate
|
|
})
|
|
.FirstOrDefaultAsync(cancellationToken);
|
|
}
|
|
|
|
public async Task<bool> UpdateProfileAsync(
|
|
string id,
|
|
string? name,
|
|
string? email,
|
|
string? contact,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
var updated = await _context.Users
|
|
.Where(user => user.Id == id)
|
|
.ExecuteUpdateAsync(
|
|
setters => setters
|
|
.SetProperty(user => user.FirstName, name)
|
|
.SetProperty(user => user.Email, email)
|
|
.SetProperty(user => user.Contact, contact),
|
|
cancellationToken);
|
|
|
|
return updated == 1;
|
|
}
|
|
|
|
public async Task<ApplicationUser?> GetForEditAsync(string id, CancellationToken cancellationToken)
|
|
{
|
|
return await _context.Users
|
|
.Where(w => w.Id == id)
|
|
.FirstOrDefaultAsync(cancellationToken);
|
|
}
|
|
|
|
public async Task<ApplicationUser?> GetByEmailNormalizedAsync(string email, CancellationToken cancellationToken)
|
|
{
|
|
var normalizedEmail = email.Trim().ToUpperInvariant();
|
|
return await _context.Users
|
|
.AsNoTracking()
|
|
.Where(user => user.NormalizedEmail == normalizedEmail)
|
|
.FirstOrDefaultAsync(cancellationToken);
|
|
}
|
|
|
|
public async Task UpdateUserAsync(ApplicationUser user, CancellationToken cancellationToken)
|
|
{
|
|
_context.Users.Update(user);
|
|
await _context.SaveChangesAsync(cancellationToken);
|
|
}
|
|
|
|
public async Task DeleteUserWithCascadeAsync(ApplicationUser user, CancellationToken cancellationToken)
|
|
{
|
|
var id = user.Id;
|
|
await using var transaction = await _context.Database.BeginTransactionAsync(cancellationToken);
|
|
|
|
await _context.workOrders
|
|
.Where(workOrder => workOrder.AssignTo == id)
|
|
.ExecuteUpdateAsync(
|
|
setters => setters.SetProperty(workOrder => workOrder.AssignTo, (string?)null),
|
|
cancellationToken);
|
|
|
|
await _context.Comments
|
|
.Where(comment => comment.UserId == id)
|
|
.ExecuteUpdateAsync(
|
|
setters => setters.SetProperty(comment => comment.UserId, (string?)null),
|
|
cancellationToken);
|
|
|
|
await _context.Templates
|
|
.Where(template => template.AssignTo == id)
|
|
.ExecuteUpdateAsync(
|
|
setters => setters.SetProperty(template => template.AssignTo, (string?)null),
|
|
cancellationToken);
|
|
|
|
await _context.UserRoles
|
|
.Where(role => role.UserId == id)
|
|
.ExecuteDeleteAsync(cancellationToken);
|
|
|
|
await _context.UserPermissionOverrides
|
|
.Where(permissionOverride => permissionOverride.UserId == id)
|
|
.ExecuteDeleteAsync(cancellationToken);
|
|
|
|
await _context.Users
|
|
.Where(existingUser => existingUser.Id == id)
|
|
.ExecuteDeleteAsync(cancellationToken);
|
|
|
|
await transaction.CommitAsync(cancellationToken);
|
|
}
|
|
|
|
public async Task<string?> GetEmailByIdAsync(
|
|
string userId, CancellationToken cancellationToken)
|
|
{
|
|
return await _context.Users
|
|
.AsNoTracking()
|
|
.Where(user => user.Id == userId)
|
|
.Select(user => user.Email)
|
|
.FirstOrDefaultAsync(cancellationToken);
|
|
}
|
|
|
|
public async Task<IReadOnlyDictionary<string, string>> GetDisplayNamesByIdsAsync(IEnumerable<string> ids)
|
|
{
|
|
var idList = ids
|
|
.Where(id => !string.IsNullOrWhiteSpace(id))
|
|
.Distinct(StringComparer.Ordinal)
|
|
.ToList();
|
|
|
|
if (idList.Count == 0)
|
|
return new Dictionary<string, string>(StringComparer.Ordinal);
|
|
|
|
var users = await _context.Users
|
|
.AsNoTracking()
|
|
.Where(u => idList.Contains(u.Id))
|
|
.Select(u => new { u.Id, u.FirstName, u.LastName })
|
|
.ToListAsync();
|
|
|
|
return users.ToDictionary(
|
|
u => u.Id,
|
|
u => $"{u.FirstName ?? ""} {u.LastName ?? ""}".Trim(),
|
|
StringComparer.Ordinal);
|
|
}
|
|
}
|
|
}
|