Skip to content

Commit 7e53136

Browse files
author
CIS Guru
committed
simple start refactor
1 parent f47bc65 commit 7e53136

12 files changed

Lines changed: 108 additions & 63 deletions

File tree

0-Aquiis.Core/Constants/ApplicationSettings.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ public class ApplicationSettings
99
public string Repository { get; set; } = string.Empty;
1010
public bool SoftDeleteEnabled { get; set; }
1111
public string SchemaVersion { get; set; } = "1.0.0";
12+
public int MaxOrganizationUsers { get; set; } = 0; // 0 = unlimited (Professional), 3 = SimpleStart limit
1213
}
1314

1415
// Property & Tenant Lifecycle Enums

2-Aquiis.Application/Services/OrganizationService.cs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,24 @@
22
using Aquiis.Core.Constants;
33
using Aquiis.Core.Interfaces.Services;
44
using Microsoft.EntityFrameworkCore;
5+
using Microsoft.Extensions.Options;
56

67
namespace Aquiis.Application.Services
78
{
89
public class OrganizationService
910
{
1011
private readonly ApplicationDbContext _dbContext;
1112
private readonly IUserContextService _userContext;
13+
private readonly ApplicationSettings _settings;
1214

13-
public OrganizationService(ApplicationDbContext dbContext, IUserContextService userContextService)
15+
public OrganizationService(
16+
ApplicationDbContext dbContext,
17+
IUserContextService userContextService,
18+
IOptions<ApplicationSettings> settings)
1419
{
1520
_dbContext = dbContext;
1621
_userContext = userContextService;
22+
_settings = settings.Value;
1723
}
1824

1925
#region CRUD Operations
@@ -289,6 +295,24 @@ public async Task<bool> GrantOrganizationAccessAsync(string userId, Guid organiz
289295
if (organization == null || organization.IsDeleted)
290296
return false;
291297

298+
// Check user limit for SimpleStart (MaxOrganizationUsers > 0)
299+
if (_settings.MaxOrganizationUsers > 0)
300+
{
301+
var currentUserCount = await _dbContext.OrganizationUsers
302+
.Where(ou => ou.OrganizationId == organizationId
303+
&& ou.UserId != ApplicationConstants.SystemUser.Id
304+
&& ou.IsActive
305+
&& !ou.IsDeleted)
306+
.CountAsync();
307+
308+
if (currentUserCount >= _settings.MaxOrganizationUsers)
309+
{
310+
throw new InvalidOperationException(
311+
$"User limit reached. SimpleStart allows maximum {_settings.MaxOrganizationUsers} user accounts (including system account). " +
312+
"Upgrade to Aquiis Professional for unlimited users.");
313+
}
314+
}
315+
292316
// Check if user already has access
293317
var existing = await _dbContext.OrganizationUsers
294318
.FirstOrDefaultAsync(uo => uo.UserId == userId && uo.OrganizationId == organizationId);

3-Aquiis.UI.Shared/Components/Entities/Properties/PropertyListView.razor

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,11 @@
2929
@if (ShowMetrics)
3030
{
3131
<PropertyMetricsCard
32-
ShowAvailable="true"
33-
ShowPending="false"
34-
ShowOccupied="true"
35-
ShowMonthlyRent="true"
32+
ShowAvailableProperties="true"
33+
ShowLeasePendingProperties="false"
34+
ShowOccupiedProperties="true"
35+
ShowTotalMonthlyRent="true"
36+
TotalPropertiesCount="@TotalCount"
3637
AvailableCount="@AvailableCount"
3738
PendingCount="@PendingCount"
3839
OccupiedCount="@OccupiedCount"
@@ -219,6 +220,12 @@ else
219220
/// </summary>
220221
[Parameter]
221222
public bool SortAscending { get; set; } = true;
223+
224+
/// <summary>
225+
/// Total properties count (for metrics)
226+
/// </summary>
227+
[Parameter]
228+
public int TotalCount { get; set; }
222229

223230
/// <summary>
224231
/// Available properties count (for metrics)

3-Aquiis.UI.Shared/Features/PropertyManagement/Properties/PropertyListForm.razor

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ else
5959
StatusFilter="@selectedPropertyStatus"
6060
SortColumn="@sortColumn"
6161
SortAscending="@sortAscending"
62+
TotalCount="@totalCount"
6263
AvailableCount="@availableCount"
6364
PendingCount="@pendingCount"
6465
OccupiedCount="@occupiedCount"
@@ -131,6 +132,8 @@ else
131132
private List<Property> pagedProperties = new();
132133
private string searchTerm = string.Empty;
133134
private string selectedPropertyStatus = string.Empty;
135+
136+
private int totalCount = 0;
134137
private int availableCount = 0;
135138
private int pendingCount = 0;
136139
private int occupiedCount = 0;
@@ -214,6 +217,7 @@ else
214217
private void CalculateMetrics(){
215218
if (filteredProperties != null)
216219
{
220+
totalCount = filteredProperties.Count;
217221
availableCount = filteredProperties.Count(p => p.Status == ApplicationConstants.PropertyStatuses.Available);
218222
pendingCount = filteredProperties.Count(p => p.Status == ApplicationConstants.PropertyStatuses.ApplicationPending || p.Status == ApplicationConstants.PropertyStatuses.LeasePending);
219223
occupiedCount = filteredProperties.Count(p => p.Status == ApplicationConstants.PropertyStatuses.Occupied);

4-Aquiis.SimpleStart/Features/Administration/Organizations/Pages/ManageUsers.razor

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,19 @@
5858
<div class="card">
5959
<div class="card-header d-flex justify-content-between align-items-center">
6060
<h5 class="mb-0">Users with Access</h5>
61-
<button class="btn btn-sm btn-primary" @onclick="ShowAddUserModal">
61+
<button class="btn btn-sm btn-primary" @onclick="ShowAddUserModal" disabled="@(!CanAddMoreUsers)">
6262
<i class="bi bi-person-plus"></i> Add User
6363
</button>
6464
</div>
65+
@if (!CanAddMoreUsers)
66+
{
67+
<div class="alert alert-warning m-3 mb-0">
68+
<i class="bi bi-exclamation-triangle"></i>
69+
<strong>User Limit Reached</strong><br />
70+
SimpleStart allows 3 user accounts (1 system + 2 login users).<br />
71+
<small>Upgrade to <strong>Aquiis Professional</strong> for unlimited users and advanced features.</small>
72+
</div>
73+
}
6574
<div class="card-body p-0">
6675
@if (!organizationUsers.Any())
6776
{
@@ -229,6 +238,10 @@
229238
private string selectedRole = ApplicationConstants.OrganizationRoles.User;
230239
private string addUserError = string.Empty;
231240

241+
// SimpleStart: 3-user limit (excluding system account)
242+
private bool CanAddMoreUsers => organizationUsers
243+
.Count(ou => ou.UserId != ApplicationConstants.SystemUser.Id) < 3;
244+
232245
protected override async Task OnInitializedAsync()
233246
{
234247
await LoadOrganization();

4-Aquiis.SimpleStart/Shared/Components/Account/Pages/Register.razor

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
@page "/Account/Register"
22

33
@using System.ComponentModel.DataAnnotations
4-
@using System.Text
5-
@using System.Text.Encodings.Web
64
@using Microsoft.AspNetCore.Identity
7-
@using Microsoft.AspNetCore.WebUtilities
85
@using Aquiis.Infrastructure.Data
96
@using Aquiis.Application.Services
107
@using Aquiis.Core.Entities
@@ -324,21 +321,21 @@ else
324321
}
325322
}
326323

324+
// Auto-confirm email (no email integration yet)
327325
var code = await UserManager.GenerateEmailConfirmationTokenAsync(user);
328-
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
329-
var callbackUrl = NavigationManager.GetUriWithQueryParameters(
330-
NavigationManager.ToAbsoluteUri("Account/ConfirmEmail").AbsoluteUri,
331-
new Dictionary<string, object?> { ["userId"] = userId, ["code"] = code, ["returnUrl"] = ReturnUrl });
332-
333-
await EmailSender.SendConfirmationLinkAsync(user, Input.Email, HtmlEncoder.Default.Encode(callbackUrl));
334-
335-
if (UserManager.Options.SignIn.RequireConfirmedAccount)
326+
var confirmResult = await UserManager.ConfirmEmailAsync(user, code);
327+
328+
if (!confirmResult.Succeeded)
329+
{
330+
Logger.LogWarning("Failed to auto-confirm email for user {UserId}: {Errors}",
331+
userId, string.Join(", ", confirmResult.Errors.Select(e => e.Description)));
332+
}
333+
else
336334
{
337-
RedirectManager.RedirectTo(
338-
"Account/RegisterConfirmation",
339-
new() { ["email"] = Input.Email, ["returnUrl"] = ReturnUrl });
335+
Logger.LogInformation("Email auto-confirmed for user {UserId}", userId);
340336
}
341337

338+
// Sign in the user directly
342339
await SignInManager.SignInAsync(user, isPersistent: false);
343340
RedirectManager.RedirectTo(ReturnUrl);
344341
}

4-Aquiis.SimpleStart/appsettings.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@
2020
"SoftDeleteEnabled": true,
2121
"DatabaseFileName": "app_v0.3.0.db",
2222
"PreviousDatabaseFileName": "app_v0.0.0.db",
23-
"SchemaVersion": "0.3.0"
23+
"SchemaVersion": "0.3.0",
24+
"MaxOrganizationUsers": 3
2425
},
2526
"SessionTimeout": {
2627
"InactivityTimeoutMinutes": 18,

5-Aquiis.Professional/Shared/Components/Account/Pages/Register.razor

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
@page "/Account/Register"
22

33
@using System.ComponentModel.DataAnnotations
4-
@using System.Text
5-
@using System.Text.Encodings.Web
64
@using Microsoft.AspNetCore.Identity
7-
@using Microsoft.AspNetCore.WebUtilities
85
@using Aquiis.Infrastructure.Data
96
@using Aquiis.Application.Services
107
@using Aquiis.Core.Entities
@@ -324,21 +321,21 @@ else
324321
}
325322
}
326323

324+
// Auto-confirm email (no email integration yet)
327325
var code = await UserManager.GenerateEmailConfirmationTokenAsync(user);
328-
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
329-
var callbackUrl = NavigationManager.GetUriWithQueryParameters(
330-
NavigationManager.ToAbsoluteUri("Account/ConfirmEmail").AbsoluteUri,
331-
new Dictionary<string, object?> { ["userId"] = userId, ["code"] = code, ["returnUrl"] = ReturnUrl });
332-
333-
await EmailSender.SendConfirmationLinkAsync(user, Input.Email, HtmlEncoder.Default.Encode(callbackUrl));
334-
335-
if (UserManager.Options.SignIn.RequireConfirmedAccount)
326+
var confirmResult = await UserManager.ConfirmEmailAsync(user, code);
327+
328+
if (!confirmResult.Succeeded)
329+
{
330+
Logger.LogWarning("Failed to auto-confirm email for user {UserId}: {Errors}",
331+
userId, string.Join(", ", confirmResult.Errors.Select(e => e.Description)));
332+
}
333+
else
336334
{
337-
RedirectManager.RedirectTo(
338-
"Account/RegisterConfirmation",
339-
new() { ["email"] = Input.Email, ["returnUrl"] = ReturnUrl });
335+
Logger.LogInformation("Email auto-confirmed for user {UserId}", userId);
340336
}
341337

338+
// Sign in the user directly
342339
await SignInManager.SignInAsync(user, isPersistent: false);
343340
RedirectManager.RedirectTo(ReturnUrl);
344341
}

5-Aquiis.Professional/appsettings.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@
2020
"SoftDeleteEnabled": true,
2121
"DatabaseFileName": "app_v0.3.0.db",
2222
"PreviousDatabaseFileName": "app_v0.0.0.db",
23-
"SchemaVersion": "0.3.0"
23+
"SchemaVersion": "0.3.0",
24+
"MaxOrganizationUsers": 0
2425
},
2526
"SessionTimeout": {
2627
"InactivityTimeoutMinutes": 18,

6-Tests/Aquiis.UI.Professional.Tests/NewSetupUITests.cs

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public async Task CreateNewAccount()
3131
await Page.GetByRole(AriaRole.Textbox, new() { Name = "Organization Name" }).ClickAsync();
3232
await Page.GetByRole(AriaRole.Textbox, new() { Name = "Organization Name" }).FillAsync("Aquiis");
3333
await Page.GetByRole(AriaRole.Textbox, new() { Name = "Organization Name" }).PressAsync("Tab");
34-
await Page.Locator("select[id='Input.State']").SelectOptionAsync(new[] { "LA" });
34+
await Page.Locator("select[id='Input.State']").SelectOptionAsync(new[] { "TX" });
3535
await Page.Locator("select[id='Input.State']").PressAsync("Tab");
3636
await Page.GetByRole(AriaRole.Textbox, new() { Name = "Email" }).FillAsync("owner1@aquiis.com");
3737
await Page.GetByRole(AriaRole.Textbox, new() { Name = "Email" }).PressAsync("Tab");
@@ -44,25 +44,25 @@ public async Task CreateNewAccount()
4444
await Page.GetByRole(AriaRole.Textbox, new() { Name = "Confirm Password" }).FillAsync("Today123");
4545
await Page.GetByRole(AriaRole.Button, new() { Name = "Register" }).ClickAsync();
4646

47-
await Page.WaitForSelectorAsync("h1:has-text('Register confirmation')");
47+
// await Page.WaitForSelectorAsync("h1:has-text('Register confirmation')");
4848

49-
// await Page.GetByRole(AriaRole.Heading, new() { Name = "Register confirmation" }).ClickAsync();
50-
await Page.GetByRole(AriaRole.Link, new() { Name = "Click here to confirm your account" }).ClickAsync();
49+
// // await Page.GetByRole(AriaRole.Heading, new() { Name = "Register confirmation" }).ClickAsync();
50+
// await Page.GetByRole(AriaRole.Link, new() { Name = "Click here to confirm your account" }).ClickAsync();
5151

52-
await Page.WaitForSelectorAsync("h1:has-text('Confirm email')");
52+
// await Page.WaitForSelectorAsync("h1:has-text('Confirm email')");
5353

54-
// await Page.GetByText("Thank you for confirming your").ClickAsync();
55-
await Page.GetByRole(AriaRole.Link, new() { Name = "Home" }).ClickAsync();
56-
await Page.GetByRole(AriaRole.Button, new() { Name = "Sign In" }).ClickAsync();
54+
// // await Page.GetByText("Thank you for confirming your").ClickAsync();
55+
// await Page.GetByRole(AriaRole.Link, new() { Name = "Home" }).ClickAsync();
56+
// await Page.GetByRole(AriaRole.Button, new() { Name = "Sign In" }).ClickAsync();
5757

58-
await Page.WaitForSelectorAsync("h1:has-text('Log in')");
58+
// await Page.WaitForSelectorAsync("h1:has-text('Log in')");
5959

60-
// await Page.GetByRole(AriaRole.Heading, new() { Name = "Log in", Exact = true }).ClickAsync();
61-
await Page.GetByRole(AriaRole.Textbox, new() { Name = "Email" }).ClickAsync();
62-
await Page.GetByRole(AriaRole.Textbox, new() { Name = "Email" }).FillAsync("owner1@aquiis.com");
63-
await Page.GetByRole(AriaRole.Textbox, new() { Name = "Email" }).PressAsync("Tab");
64-
await Page.GetByRole(AriaRole.Textbox, new() { Name = "Password" }).FillAsync("Today123");
65-
await Page.GetByRole(AriaRole.Button, new() { Name = "Log in" }).ClickAsync();
60+
// // await Page.GetByRole(AriaRole.Heading, new() { Name = "Log in", Exact = true }).ClickAsync();
61+
// await Page.GetByRole(AriaRole.Textbox, new() { Name = "Email" }).ClickAsync();
62+
// await Page.GetByRole(AriaRole.Textbox, new() { Name = "Email" }).FillAsync("owner1@aquiis.com");
63+
// await Page.GetByRole(AriaRole.Textbox, new() { Name = "Email" }).PressAsync("Tab");
64+
// await Page.GetByRole(AriaRole.Textbox, new() { Name = "Password" }).FillAsync("Today123");
65+
// await Page.GetByRole(AriaRole.Button, new() { Name = "Log in" }).ClickAsync();
6666

6767
await Page.WaitForSelectorAsync("text=Dashboard");
6868

0 commit comments

Comments
 (0)