using AutoMapper; using Marco.Pms.DataAccess.Data; using Marco.Pms.Model.Dtos.Tenant; using Marco.Pms.Model.Employees; using Marco.Pms.Model.Entitlements; using Marco.Pms.Model.Roles; using Marco.Pms.Model.Utilities; using Marco.Pms.Model.ViewModels.Activities; using Marco.Pms.Model.ViewModels.Tenant; using Marco.Pms.Services.Service; using MarcoBMS.Services.Helpers; using MarcoBMS.Services.Service; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using System.Net; using System.Text.RegularExpressions; // For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860 namespace Marco.Pms.Services.Controllers { [Route("api/[controller]")] [ApiController] [Authorize] public class TenantController : ControllerBase { private readonly IDbContextFactory _dbContextFactory; private readonly IServiceScopeFactory _serviceScopeFactory; private readonly ILoggingService _logger; private readonly UserManager _userManager; private readonly IMapper _mapper; private readonly static Guid activeStatus = Guid.Parse("62b05792-5115-4f99-8ff5-e8374859b191"); public TenantController(IDbContextFactory dbContextFactory, IServiceScopeFactory serviceScopeFactory, ILoggingService logger, UserManager userManager, IMapper mapper) { _dbContextFactory = dbContextFactory; _serviceScopeFactory = serviceScopeFactory; _logger = logger; _userManager = userManager; _mapper = mapper; } // GET: api/ [HttpGet] public IEnumerable Get() { return new string[] { "value1", "value2" }; } // GET api//5 [HttpGet("{id}")] public string Get(int id) { return "value"; } // POST api/ [HttpPost("create")] public async Task Post([FromBody] CreateTenantDto model) { using var scope = _serviceScopeFactory.CreateScope(); await using var _context = await _dbContextFactory.CreateDbContextAsync(); var _configuration = scope.ServiceProvider.GetRequiredService(); var _emailSender = scope.ServiceProvider.GetRequiredService(); var userHelper = scope.ServiceProvider.GetRequiredService(); var loggedInEmployee = await userHelper.GetCurrentEmployeeAsync(); var permissionService = scope.ServiceProvider.GetRequiredService(); var hasPermission = await permissionService.HasPermission(PermissionsMaster.ManageTenants, loggedInEmployee.Id); if (!hasPermission || !(loggedInEmployee.ApplicationUser?.IsRootUser ?? false)) { _logger.LogWarning("User {EmployeeId} attmpted to create new tenant but not have permissions", loggedInEmployee.Id); return StatusCode(403, ApiResponse.ErrorResponse("Access denied", "User don't have rights for this action", 403)); } var existingUser = await _userManager.FindByEmailAsync(model.Email); if (existingUser != null) { _logger.LogWarning("User {EmployeeId} attempted to create tenant with email {Email} but already exists in database", loggedInEmployee.Id, model.Email); return StatusCode(409, ApiResponse.ErrorResponse("Tenant can't be created", "User with same email already exists", 409)); } var isTenantExists = await _context.Tenants.AnyAsync(t => t.TaxId != null && t.TaxId == model.TaxId); if (isTenantExists) { _logger.LogWarning("User {EmployeeId} attempted to create tenant with duplicate taxId", loggedInEmployee.Id); return StatusCode(409, ApiResponse.ErrorResponse("Tenant can't be created", "User with same taxId already exists", 409)); } if (!string.IsNullOrWhiteSpace(model.logoImage) && !IsBase64String(model.logoImage)) { _logger.LogWarning("User {EmployeeId} attempted to create tenant with Invalid logoImage", loggedInEmployee.Id); return StatusCode(400, ApiResponse.ErrorResponse("Tenant can't be created", "User with same taxId already exists", 400)); } await using var transaction = await _context.Database.BeginTransactionAsync(); try { var tenant = new Tenant { Name = model.OragnizationName, ContactName = $"{model.FirstName} {model.LastName}", ContactNumber = model.ContactNumber, Email = model.Email, IndustryId = model.IndustryId, TenantStatusId = activeStatus, Description = model.Description, OnBoardingDate = model.OnBoardingDate, OragnizationSize = model.OragnizationSize, Reference = model.Reference, CreatedById = loggedInEmployee.Id, BillingAddress = model.BillingAddress, TaxId = model.TaxId, logoImage = model.logoImage, DomainName = model.DomainName, IsSuperTenant = false }; _context.Tenants.Add(tenant); await _context.SaveChangesAsync(); var designation = new JobRole { Name = "Admin", Description = "Root degination for tenant only", TenantId = tenant.Id }; var applicationUser = new ApplicationUser { Email = model.Email, UserName = model.Email, IsRootUser = true, EmailConfirmed = true, TenantId = tenant.Id }; _context.JobRoles.Add(designation); var result = await _userManager.CreateAsync(applicationUser, "User@123"); if (!result.Succeeded) return BadRequest(ApiResponse.ErrorResponse("Failed to create user", result.Errors, 400)); await _context.SaveChangesAsync(); var employeeUser = new Employee { FirstName = model.FirstName, LastName = model.LastName, Email = model.Email, PhoneNumber = model.ContactNumber, ApplicationUserId = applicationUser.Id, JobRole = designation, CurrentAddress = model.BillingAddress, TenantId = tenant.Id }; await _context.SaveChangesAsync(); var token = await _userManager.GeneratePasswordResetTokenAsync(applicationUser); var resetLink = $"{_configuration["AppSettings:WebFrontendUrl"]}/reset-password?token={WebUtility.UrlEncode(token)}"; if (employeeUser.FirstName != null) { await _emailSender.SendResetPasswordEmailOnRegister(applicationUser.Email, employeeUser.FirstName, resetLink); } var vm = _mapper.Map(tenant); vm.CreatedBy = _mapper.Map(loggedInEmployee); return Ok(tenant); } catch (DbUpdateException dbEx) { _logger.LogError(dbEx, "Database Exception occured while creating tenant"); return StatusCode(500, ApiResponse.ErrorResponse("Internal Error occured", ExceptionMapper(dbEx), 500)); } catch (Exception ex) { _logger.LogError(ex, "Exception occured while creating tenant"); return StatusCode(500, ApiResponse.ErrorResponse("Internal Error occured", ExceptionMapper(ex), 500)); } } // PUT api//5 [HttpPut("{id}")] public void Put(int id, [FromBody] string value) { } // DELETE api//5 [HttpDelete("{id}")] public void Delete(int id) { } private static object ExceptionMapper(Exception ex) { return new { Message = ex.Message, StackTrace = ex.StackTrace, Source = ex.Source, InnerException = new { Message = ex.InnerException?.Message, StackTrace = ex.InnerException?.StackTrace, Source = ex.InnerException?.Source, } }; } private bool IsBase64String(string? input) { if (string.IsNullOrWhiteSpace(input)) return false; // Normalize string input = input.Trim(); // Length must be multiple of 4 if (input.Length % 4 != 0) return false; // Valid Base64 characters with correct padding var base64Regex = new Regex(@"^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$"); if (!base64Regex.IsMatch(input)) return false; try { // Decode and re-encode to confirm validity var bytes = Convert.FromBase64String(input); var reEncoded = Convert.ToBase64String(bytes); return input == reEncoded; } catch { return false; } } } }