using Data.SeaHavenIndustries; using Microsoft.AspNetCore.Identity; using SeaHaven.DataServices.Interfaces; using SeaHaven.Services.DTOs; using SeaHaven.Services.Helpers; using SeaHaven.Services.Interfaces; using System.Security.Claims; using System.Security.Cryptography; namespace SeaHaven.Services.Implementation { public class UserService : IUserService { private readonly UserManager _userManager; private readonly IUserDataService _userDataService; private readonly IAccountDataService _accountDataService; private readonly IEmailSender _emailSender; public UserService( UserManager userManager, IUserDataService userDataService, IAccountDataService accountDataService, IEmailSender emailSender) { _userManager = userManager; _userDataService = userDataService; _accountDataService = accountDataService; _emailSender = emailSender; } public async Task> GetUsersAsync(CancellationToken cancellationToken) { var users = await _userDataService.GetAllForListAsync(cancellationToken); return users.Select(s => new UserListRowDTO { RoleName = s.RoleName, Email = s.Email, Name = s.Name, Date = s.CreatedDate != null ? s.CreatedDate.Value.Date.ToString("MMM dd yyyy") : "", Status = s.Status, Id = s.Id, Phone = s.Phone, Color = s.Color, PendingRegistration = s.PendingRegistration, ServiceAreas = s.ServiceAreas }); } public async Task AddUserAsync( AddUserRequestDTO dto, ClaimsPrincipal caller, CancellationToken cancellationToken) { var authFailure = EnsureAdminCaller(caller); if (authFailure != null) return authFailure; var accountFailure = await EnsureAccountValidAsync(dto.AccountId); if (accountFailure != null) return accountFailure; var model = new ApplicationUser { UserName = dto.Email, FirstName = dto.Name, Email = dto.Email, AccountId = dto.AccountId }; var exist = await _userDataService.GetByIdAsync(model.Id); if (exist == null) { model.EmailConfirmed = true; model.UserName = model.Email; model.CreatedDate = DateTime.UtcNow; model.UniqueName = "Active"; model.PhoneNumber = dto.Role; var password = GenerateRandomPassword(); var result = await _userManager.CreateAsync(model, password); if (result.Succeeded) { await _userManager.AddToRoleAsync(model, dto.Role ?? ""); } else { return new AddUserOutcomeDTO { Success = false, Error = result.Errors.FirstOrDefault()?.Description ?? "error" }; } var domain = "http://shoc-ui-app.s3-website-us-east-1.amazonaws.com/#"; string subject = "Login Information"; string greeting = $"Dear {model.FirstName},\n\n"; string loginLink = $"Click here to login:Login\n"; string passwordInfo = $"Your password is: {password}\n\n"; string body = $"{greeting}\n\n{loginLink}\n\n{passwordInfo}"; await _emailSender.SendEmailAsync(model?.Email ?? "", subject, body); return new AddUserOutcomeDTO { Success = true }; } else { var exist1 = await _userDataService.GetForEditAsync(model.Id, cancellationToken); if (exist1 == null) return new AddUserOutcomeDTO { Success = false, Error = UserMutationErrors.UserNotFound }; var existingRole = await _userManager.GetRolesAsync(exist1); if (existingRole != null && existingRole.Any() && existingRole.FirstOrDefault() != exist1.PhoneNumber) { await _userManager.RemoveFromRolesAsync(exist1, existingRole); } exist1.FirstName = model.FirstName; exist1.LastName = model.LastName; exist1.Contact = model.Contact; exist1.PhoneNumber = model.PhoneNumber; exist1.AccountId = dto.AccountId; await _userDataService.UpdateUserAsync(exist1, cancellationToken); await _userManager.AddToRoleAsync(exist1, dto.Role ?? ""); await _userManager.UpdateAsync(exist1); return new AddUserOutcomeDTO { Success = true }; } } public async Task EditUserAsync( EditUserRequestDTO dto, ClaimsPrincipal caller, CancellationToken cancellationToken) { var authFailure = EnsureAdminCaller(caller); if (authFailure != null) return authFailure; var accountFailure = await EnsureAccountValidAsync(dto.AccountId); if (accountFailure != null) return accountFailure; var exist = await _userDataService.GetForEditAsync(dto.Id ?? "", cancellationToken); if (exist == null) return new AddUserOutcomeDTO { Success = false, Error = UserMutationErrors.UserNotFound }; if (await _userDataService.IsAccountOwnerAsync(exist.Id, cancellationToken)) return new AddUserOutcomeDTO { Success = false, Error = UserMutationErrors.Forbidden }; exist.FirstName = dto.Name; if (string.IsNullOrWhiteSpace(dto.Email)) throw new ArgumentException("Email is required.", nameof(dto)); exist.EmailConfirmed = true; exist.UserName = dto.Email; exist.Email = dto.Email; exist.NormalizedEmail = dto.Email.ToUpperInvariant(); exist.NormalizedUserName = dto.Email.ToUpperInvariant(); exist.CreatedDate = DateTime.UtcNow; exist.UniqueName = "Active"; exist.PhoneNumber = dto.Role; exist.AccountId = dto.AccountId; var existingRole = await _userManager.GetRolesAsync(exist); if (existingRole != null && existingRole.Any()) { await _userManager.RemoveFromRolesAsync(exist, existingRole); } await _userManager.AddToRoleAsync(exist, dto.Role ?? ""); await _userDataService.UpdateUserAsync(exist, cancellationToken); return new AddUserOutcomeDTO { Success = true }; } public async Task DeleteUserAsync( string id, ClaimsPrincipal caller, CancellationToken cancellationToken) { var authFailure = EnsureAdminCaller(caller); if (authFailure != null) return authFailure; var data = await _userDataService.GetForEditAsync(id, cancellationToken); if (data == null) return new AddUserOutcomeDTO { Success = false, Error = UserMutationErrors.UserNotFound }; if (await _userDataService.IsAccountOwnerAsync(data.Id, cancellationToken)) return new AddUserOutcomeDTO { Success = false, Error = UserMutationErrors.Forbidden }; await _userDataService.DeleteUserWithCascadeAsync(data, cancellationToken); return new AddUserOutcomeDTO { Success = true }; } public async Task DeleteCurrentUserAsync(string userId, CancellationToken cancellationToken) { var exist = await _userDataService.GetForEditAsync(userId, cancellationToken); if (exist != null && !await _userDataService.IsAccountOwnerAsync(exist.Id, cancellationToken)) { exist.IsDeleted = true; await _userDataService.UpdateUserAsync(exist, cancellationToken); } } public async Task> GetUserProfileAsync(string userId, CancellationToken cancellationToken) { var user = await _userDataService.GetProfileAsync(userId, cancellationToken); if (user == null) return Array.Empty(); return new List { new UserProfileRowDTO { Id = user.Id, Name = user.FirstName, AddedDate = user.CreatedDate, Email = user.Email, Contact = user.Contact } }; } private static AddUserOutcomeDTO? EnsureAdminCaller(ClaimsPrincipal caller) { if (caller is null || !caller.IsInRole("Admin")) { return new AddUserOutcomeDTO { Success = false, Error = UserMutationErrors.Forbidden }; } return null; } private async Task EnsureAccountValidAsync(int? accountId) { if (!accountId.HasValue) return null; if (accountId.Value <= 0 || !await _accountDataService.ExistsAsync(accountId.Value)) { return new AddUserOutcomeDTO { Success = false, Error = UserMutationErrors.AccountNotFound }; } return null; } private static string GenerateRandomPassword(int length = 8) { const string validChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@#$%^&*()_-+=<>?"; const string capitalLetters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; const string digits = "1234567890"; const string symbols = "!@#$%^&*()_-+=<>?"; var passwordChars = new char[length]; passwordChars[0] = capitalLetters[RandomNumberGenerator.GetInt32(capitalLetters.Length)]; passwordChars[1] = digits[RandomNumberGenerator.GetInt32(digits.Length)]; passwordChars[2] = symbols[RandomNumberGenerator.GetInt32(symbols.Length)]; passwordChars[3] = "abcdefghijklmnopqrstuvwxyz"[RandomNumberGenerator.GetInt32(26)]; for (int i = 4; i < length; i++) { passwordChars[i] = validChars[RandomNumberGenerator.GetInt32(validChars.Length)]; } for (int i = passwordChars.Length - 1; i > 0; i--) { int j = RandomNumberGenerator.GetInt32(i + 1); (passwordChars[i], passwordChars[j]) = (passwordChars[j], passwordChars[i]); } return new string(passwordChars); } } }