diff options
| author | ivarlovlie <git@ivarlovlie.no> | 2022-11-14 05:04:36 +0100 |
|---|---|---|
| committer | ivarlovlie <git@ivarlovlie.no> | 2022-11-14 05:05:09 +0100 |
| commit | 04a2b73805fc1213df42ef9af4cdcf27ff7c86db (patch) | |
| tree | 818df6112e2b74ce3af6f2d9ce7535915967feed | |
| parent | 94da43fd4e2c625babcf7cdabc5e82a47bcab2a5 (diff) | |
| download | greatoffice-04a2b73805fc1213df42ef9af4cdcf27ff7c86db.tar.xz greatoffice-04a2b73805fc1213df42ef9af4cdcf27ff7c86db.zip | |
refactor: Password reset service
- Use FulfillPasswordResetRequestResult to indicate fulfillment result
- Rename db names forgot_password_requests > password_reset_requests
| -rw-r--r-- | code/api/src/Data/Database/Internal/PasswordResetRequest.cs (renamed from code/api/src/Data/Database/Internal/ForgotPasswordRequest.cs) | 6 | ||||
| -rw-r--r-- | code/api/src/Data/Database/MainAppDatabase.cs | 4 | ||||
| -rw-r--r-- | code/api/src/Data/Enums/FulfillPasswordResetRequestResult.cs | 8 | ||||
| -rw-r--r-- | code/api/src/Data/Exceptions/ForgotPasswordRequestNotFoundException.cs | 21 | ||||
| -rw-r--r-- | code/api/src/Data/Exceptions/UserNotFoundException.cs | 19 | ||||
| -rw-r--r-- | code/api/src/Endpoints/Internal/Account/DeleteAccountRoute.cs | 4 | ||||
| -rw-r--r-- | code/api/src/Endpoints/Internal/PasswordResetRequests/FulfillResetRequestRoute.cs | 22 | ||||
| -rw-r--r-- | code/api/src/Migrations/20221114035223_RenameForgotPasswordRequests.Designer.cs | 1589 | ||||
| -rw-r--r-- | code/api/src/Migrations/20221114035223_RenameForgotPasswordRequests.cs | 78 | ||||
| -rw-r--r-- | code/api/src/Migrations/AppDbContextModelSnapshot.cs | 289 | ||||
| -rw-r--r-- | code/api/src/Program.cs | 14 | ||||
| -rw-r--r-- | code/api/src/Services/PasswordResetService.cs | 36 |
12 files changed, 1712 insertions, 378 deletions
diff --git a/code/api/src/Data/Database/Internal/ForgotPasswordRequest.cs b/code/api/src/Data/Database/Internal/PasswordResetRequest.cs index 1510a35..ee73fd2 100644 --- a/code/api/src/Data/Database/Internal/ForgotPasswordRequest.cs +++ b/code/api/src/Data/Database/Internal/PasswordResetRequest.cs @@ -1,10 +1,10 @@ namespace IOL.GreatOffice.Api.Data.Database; -public class ForgotPasswordRequest +public class PasswordResetRequest { - public ForgotPasswordRequest() { } + public PasswordResetRequest() { } - public ForgotPasswordRequest(User user) { + public PasswordResetRequest(User user) { CreatedAt = AppDateTime.UtcNow; Id = Guid.NewGuid(); User = user; diff --git a/code/api/src/Data/Database/MainAppDatabase.cs b/code/api/src/Data/Database/MainAppDatabase.cs index 41d62a7..fe3ed58 100644 --- a/code/api/src/Data/Database/MainAppDatabase.cs +++ b/code/api/src/Data/Database/MainAppDatabase.cs @@ -6,7 +6,7 @@ public class MainAppDatabase : DbContext, IDataProtectionKeyContext { public MainAppDatabase(DbContextOptions<MainAppDatabase> options) : base(options) { } public DbSet<User> Users { get; set; } - public DbSet<ForgotPasswordRequest> ForgotPasswordRequests { get; set; } + public DbSet<PasswordResetRequest> PasswordResetRequests { get; set; } public DbSet<ApiAccessToken> AccessTokens { get; set; } public DbSet<Tenant> Tenants { get; set; } public DbSet<DataProtectionKey> DataProtectionKeys { get; set; } @@ -27,7 +27,7 @@ public class MainAppDatabase : DbContext, IDataProtectionKeyContext e.HasMany(n => n.Tenants); e.ToTable("users"); }); - modelBuilder.Entity<ForgotPasswordRequest>(e => { + modelBuilder.Entity<PasswordResetRequest>(e => { e.HasOne(c => c.User); e.ToTable("forgot_password_requests"); }); diff --git a/code/api/src/Data/Enums/FulfillPasswordResetRequestResult.cs b/code/api/src/Data/Enums/FulfillPasswordResetRequestResult.cs new file mode 100644 index 0000000..2a84c48 --- /dev/null +++ b/code/api/src/Data/Enums/FulfillPasswordResetRequestResult.cs @@ -0,0 +1,8 @@ +namespace IOL.GreatOffice.Api.Data.Enums; + +public enum FulfillPasswordResetRequestResult +{ + REQUEST_NOT_FOUND, + USER_NOT_FOUND, + FULFILLED +}
\ No newline at end of file diff --git a/code/api/src/Data/Exceptions/ForgotPasswordRequestNotFoundException.cs b/code/api/src/Data/Exceptions/ForgotPasswordRequestNotFoundException.cs deleted file mode 100644 index 02474b4..0000000 --- a/code/api/src/Data/Exceptions/ForgotPasswordRequestNotFoundException.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace IOL.GreatOffice.Api.Data.Exceptions; - -[Serializable] -public class ForgotPasswordRequestNotFoundException : Exception -{ - // - // For guidelines regarding the creation of new exception types, see - // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconerrorraisinghandlingguidelines.asp - // and - // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dncscol/html/csharp07192001.asp - // - - public ForgotPasswordRequestNotFoundException() { } - public ForgotPasswordRequestNotFoundException(string message) : base(message) { } - public ForgotPasswordRequestNotFoundException(string message, Exception inner) : base(message, inner) { } - - protected ForgotPasswordRequestNotFoundException( - SerializationInfo info, - StreamingContext context - ) : base(info, context) { } -} diff --git a/code/api/src/Data/Exceptions/UserNotFoundException.cs b/code/api/src/Data/Exceptions/UserNotFoundException.cs deleted file mode 100644 index 06b57a9..0000000 --- a/code/api/src/Data/Exceptions/UserNotFoundException.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace IOL.GreatOffice.Api.Data.Exceptions; - -[Serializable] -public class UserNotFoundException : Exception -{ - // For guidelines regarding the creation of new exception types, see - // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconerrorraisinghandlingguidelines.asp - // and - // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dncscol/html/csharp07192001.asp - - public UserNotFoundException() { } - public UserNotFoundException(string message) : base(message) { } - public UserNotFoundException(string message, Exception inner) : base(message, inner) { } - - protected UserNotFoundException( - SerializationInfo info, - StreamingContext context - ) : base(info, context) { } -} diff --git a/code/api/src/Endpoints/Internal/Account/DeleteAccountRoute.cs b/code/api/src/Endpoints/Internal/Account/DeleteAccountRoute.cs index e5bbb10..f0e8362 100644 --- a/code/api/src/Endpoints/Internal/Account/DeleteAccountRoute.cs +++ b/code/api/src/Endpoints/Internal/Account/DeleteAccountRoute.cs @@ -28,9 +28,9 @@ public class DeleteAccountRoute : RouteBaseAsync.WithoutRequest.WithActionResult return Ok(); } - var passwordResets = _database.ForgotPasswordRequests.Where(c => c.UserId == user.Id); + var passwordResets = _database.PasswordResetRequests.Where(c => c.UserId == user.Id); - _database.ForgotPasswordRequests.RemoveRange(passwordResets); + _database.PasswordResetRequests.RemoveRange(passwordResets); _database.Users.Remove(user); await _database.SaveChangesAsync(cancellationToken); diff --git a/code/api/src/Endpoints/Internal/PasswordResetRequests/FulfillResetRequestRoute.cs b/code/api/src/Endpoints/Internal/PasswordResetRequests/FulfillResetRequestRoute.cs index 6f71b2f..5749242 100644 --- a/code/api/src/Endpoints/Internal/PasswordResetRequests/FulfillResetRequestRoute.cs +++ b/code/api/src/Endpoints/Internal/PasswordResetRequests/FulfillResetRequestRoute.cs @@ -8,24 +8,14 @@ public class FulfillResetRequestRoute : RouteBaseAsync.WithRequest<FulfillResetR _passwordResetService = passwordResetService; } - /// <summary> - /// Fulfill a password reset request. - /// </summary> - /// <param name="request"></param> - /// <param name="cancellationToken"></param> - /// <returns></returns> [AllowAnonymous] [HttpPost("~/_/password-reset-request/fulfill")] public override async Task<ActionResult> HandleAsync(FulfillResetRequestPayload request, CancellationToken cancellationToken = default) { - try { - var fulfilled = await _passwordResetService.FullFillRequestAsync(request.Id, request.NewPassword, cancellationToken); - return Ok(fulfilled); - } catch (Exception e) { - if (e is ForgotPasswordRequestNotFoundException or UserNotFoundException) { - return NotFound(); - } - - throw; - } + return await _passwordResetService.FulfillRequestAsync(request.Id, request.NewPassword, cancellationToken) switch { + FulfillPasswordResetRequestResult.REQUEST_NOT_FOUND => NotFound(), + FulfillPasswordResetRequestResult.USER_NOT_FOUND => NotFound(), + FulfillPasswordResetRequestResult.FULFILLED => Ok(), + _ => StatusCode(500) + }; } }
\ No newline at end of file diff --git a/code/api/src/Migrations/20221114035223_RenameForgotPasswordRequests.Designer.cs b/code/api/src/Migrations/20221114035223_RenameForgotPasswordRequests.Designer.cs new file mode 100644 index 0000000..feb9805 --- /dev/null +++ b/code/api/src/Migrations/20221114035223_RenameForgotPasswordRequests.Designer.cs @@ -0,0 +1,1589 @@ +// <auto-generated /> +using System; +using IOL.GreatOffice.Api.Data.Database; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace IOL.GreatOffice.Api.Migrations +{ + [DbContext(typeof(MainAppDatabase))] + [Migration("20221114035223_RenameForgotPasswordRequests")] + partial class RenameForgotPasswordRequests + { + /// <inheritdoc /> + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("CustomerCustomerGroup", b => + { + b.Property<Guid>("CustomersId") + .HasColumnType("uuid") + .HasColumnName("customers_id"); + + b.Property<Guid>("GroupsId") + .HasColumnType("uuid") + .HasColumnName("groups_id"); + + b.HasKey("CustomersId", "GroupsId") + .HasName("pk_customer_customer_group"); + + b.HasIndex("GroupsId") + .HasDatabaseName("ix_customer_customer_group_groups_id"); + + b.ToTable("customer_customer_group", (string)null); + }); + + modelBuilder.Entity("CustomerProject", b => + { + b.Property<Guid>("CustomersId") + .HasColumnType("uuid") + .HasColumnName("customers_id"); + + b.Property<Guid>("ProjectsId") + .HasColumnType("uuid") + .HasColumnName("projects_id"); + + b.HasKey("CustomersId", "ProjectsId") + .HasName("pk_customer_project"); + + b.HasIndex("ProjectsId") + .HasDatabaseName("ix_customer_project_projects_id"); + + b.ToTable("customer_project", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ApiAccessToken", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property<bool>("AllowCreate") + .HasColumnType("boolean") + .HasColumnName("allow_create"); + + b.Property<bool>("AllowDelete") + .HasColumnType("boolean") + .HasColumnName("allow_delete"); + + b.Property<bool>("AllowRead") + .HasColumnType("boolean") + .HasColumnName("allow_read"); + + b.Property<bool>("AllowUpdate") + .HasColumnType("boolean") + .HasColumnName("allow_update"); + + b.Property<DateTime>("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property<bool>("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property<DateTime?>("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property<DateTime>("ExpiryDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("expiry_date"); + + b.Property<DateTime?>("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property<Guid?>("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_api_access_tokens"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_api_access_tokens_user_id"); + + b.ToTable("api_access_tokens", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Customer", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property<string>("Address1") + .HasColumnType("text") + .HasColumnName("address1"); + + b.Property<string>("Address2") + .HasColumnType("text") + .HasColumnName("address2"); + + b.Property<string>("Country") + .HasColumnType("text") + .HasColumnName("country"); + + b.Property<DateTime>("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property<Guid?>("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property<string>("Currency") + .HasColumnType("text") + .HasColumnName("currency"); + + b.Property<string>("CustomerNumber") + .HasColumnType("text") + .HasColumnName("customer_number"); + + b.Property<string>("DefaultReference") + .HasColumnType("text") + .HasColumnName("default_reference"); + + b.Property<bool>("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property<DateTime?>("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property<Guid?>("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property<string>("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property<string>("Email") + .HasColumnType("text") + .HasColumnName("email"); + + b.Property<DateTime?>("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property<Guid?>("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property<string>("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property<string>("ORGNumber") + .HasColumnType("text") + .HasColumnName("org_number"); + + b.Property<Guid?>("OwnerId") + .HasColumnType("uuid") + .HasColumnName("owner_id"); + + b.Property<string>("Phone") + .HasColumnType("text") + .HasColumnName("phone"); + + b.Property<string>("PostalCity") + .HasColumnType("text") + .HasColumnName("postal_city"); + + b.Property<string>("PostalCode") + .HasColumnType("text") + .HasColumnName("postal_code"); + + b.Property<Guid?>("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property<Guid?>("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property<string>("VATNumber") + .HasColumnType("text") + .HasColumnName("vat_number"); + + b.Property<string>("Website") + .HasColumnType("text") + .HasColumnName("website"); + + b.HasKey("Id") + .HasName("pk_customers"); + + b.HasIndex("OwnerId") + .HasDatabaseName("ix_customers_owner_id"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_customers_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_customers_user_id"); + + b.ToTable("customers", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerContact", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property<DateTime>("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property<Guid?>("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property<Guid?>("CustomerId") + .HasColumnType("uuid") + .HasColumnName("customer_id"); + + b.Property<bool>("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property<DateTime?>("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property<Guid?>("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property<string>("Email") + .HasColumnType("text") + .HasColumnName("email"); + + b.Property<string>("FirstName") + .HasColumnType("text") + .HasColumnName("first_name"); + + b.Property<string>("LastName") + .HasColumnType("text") + .HasColumnName("last_name"); + + b.Property<DateTime?>("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property<Guid?>("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property<string>("Note") + .HasColumnType("text") + .HasColumnName("note"); + + b.Property<string>("Phone") + .HasColumnType("text") + .HasColumnName("phone"); + + b.Property<Guid?>("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property<Guid?>("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property<string>("WorkTitle") + .HasColumnType("text") + .HasColumnName("work_title"); + + b.HasKey("Id") + .HasName("pk_customer_contacts"); + + b.HasIndex("CustomerId") + .HasDatabaseName("ix_customer_contacts_customer_id"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_customer_contacts_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_customer_contacts_user_id"); + + b.ToTable("customer_contacts", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerEvent", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property<DateTime>("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property<Guid?>("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property<Guid?>("CustomerId") + .HasColumnType("uuid") + .HasColumnName("customer_id"); + + b.Property<bool>("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property<DateTime?>("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property<Guid?>("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property<DateTime?>("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property<Guid?>("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property<string>("Note") + .HasColumnType("text") + .HasColumnName("note"); + + b.Property<Guid?>("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property<string>("Title") + .HasColumnType("text") + .HasColumnName("title"); + + b.Property<Guid?>("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_customer_events"); + + b.HasIndex("CustomerId") + .HasDatabaseName("ix_customer_events_customer_id"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_customer_events_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_customer_events_user_id"); + + b.ToTable("customer_events", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerGroup", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property<DateTime>("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property<Guid?>("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property<bool>("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property<DateTime?>("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property<Guid?>("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property<DateTime?>("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property<Guid?>("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property<string>("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property<Guid?>("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property<Guid?>("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_customer_groups"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_customer_groups_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_customer_groups_user_id"); + + b.ToTable("customer_groups", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.PasswordResetRequest", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property<DateTime>("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property<Guid>("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_password_reset_requests"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_password_reset_requests_user_id"); + + b.ToTable("password_reset_requests", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Project", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property<DateTime>("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property<Guid?>("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property<bool>("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property<DateTime?>("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property<Guid?>("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property<string>("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property<DateTime?>("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property<Guid?>("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property<string>("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property<DateTime?>("Start") + .HasColumnType("timestamp with time zone") + .HasColumnName("start"); + + b.Property<DateTime?>("Stop") + .HasColumnType("timestamp with time zone") + .HasColumnName("stop"); + + b.Property<Guid?>("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property<Guid?>("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_projects"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_projects_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_projects_user_id"); + + b.ToTable("projects", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ProjectLabel", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property<string>("Color") + .HasColumnType("text") + .HasColumnName("color"); + + b.Property<DateTime>("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property<Guid?>("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property<bool>("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property<DateTime?>("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property<Guid?>("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property<DateTime?>("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property<Guid?>("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property<Guid?>("ProjectId") + .HasColumnType("uuid") + .HasColumnName("project_id"); + + b.Property<Guid?>("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property<Guid?>("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property<string>("Value") + .HasColumnType("text") + .HasColumnName("value"); + + b.HasKey("Id") + .HasName("pk_project_labels"); + + b.HasIndex("ProjectId") + .HasDatabaseName("ix_project_labels_project_id"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_project_labels_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_project_labels_user_id"); + + b.ToTable("project_labels", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ProjectMember", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property<DateTime>("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property<bool>("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property<DateTime?>("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property<DateTime?>("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property<Guid?>("ProjectId") + .HasColumnType("uuid") + .HasColumnName("project_id"); + + b.Property<int>("Role") + .HasColumnType("integer") + .HasColumnName("role"); + + b.Property<Guid?>("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_project_members"); + + b.HasIndex("ProjectId") + .HasDatabaseName("ix_project_members_project_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_project_members_user_id"); + + b.ToTable("project_members", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Tenant", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property<string>("ContactEmail") + .HasColumnType("text") + .HasColumnName("contact_email"); + + b.Property<DateTime>("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property<Guid?>("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property<bool>("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property<DateTime?>("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property<Guid?>("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property<string>("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property<Guid>("MasterUserId") + .HasColumnType("uuid") + .HasColumnName("master_user_id"); + + b.Property<string>("MasterUserPassword") + .HasColumnType("text") + .HasColumnName("master_user_password"); + + b.Property<DateTime?>("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property<Guid?>("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property<string>("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property<Guid?>("OwningTenantId") + .HasColumnType("uuid") + .HasColumnName("owning_tenant_id"); + + b.Property<Guid?>("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property<Guid?>("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_tenants"); + + b.HasIndex("OwningTenantId") + .HasDatabaseName("ix_tenants_owning_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_tenants_user_id"); + + b.ToTable("tenants", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Todo", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property<Guid?>("AssignedToId") + .HasColumnType("uuid") + .HasColumnName("assigned_to_id"); + + b.Property<DateTime?>("ClosedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("closed_at"); + + b.Property<Guid?>("ClosedById") + .HasColumnType("uuid") + .HasColumnName("closed_by_id"); + + b.Property<Guid>("CollectionId") + .HasColumnType("uuid") + .HasColumnName("collection_id"); + + b.Property<DateTime>("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property<Guid?>("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property<bool>("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property<DateTime?>("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property<Guid?>("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property<string>("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property<DateTime?>("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property<Guid?>("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property<string>("PublicId") + .HasColumnType("text") + .HasColumnName("public_id"); + + b.Property<Guid?>("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property<string>("Title") + .HasColumnType("text") + .HasColumnName("title"); + + b.Property<Guid?>("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_todos"); + + b.HasIndex("AssignedToId") + .HasDatabaseName("ix_todos_assigned_to_id"); + + b.HasIndex("ClosedById") + .HasDatabaseName("ix_todos_closed_by_id"); + + b.HasIndex("CollectionId") + .HasDatabaseName("ix_todos_collection_id"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_todos_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_todos_user_id"); + + b.ToTable("todos", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoCollection", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property<DateTime>("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property<Guid?>("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property<bool>("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property<DateTime?>("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property<Guid?>("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property<string>("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property<DateTime?>("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property<Guid?>("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property<string>("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property<Guid?>("ProjectId") + .HasColumnType("uuid") + .HasColumnName("project_id"); + + b.Property<Guid?>("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property<Guid?>("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property<int>("Visibility") + .HasColumnType("integer") + .HasColumnName("visibility"); + + b.HasKey("Id") + .HasName("pk_todo_collections"); + + b.HasIndex("ProjectId") + .HasDatabaseName("ix_todo_collections_project_id"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_todo_collections_tenant_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_todo_collections_user_id"); + + b.ToTable("todo_collections", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoCollectionAccessControl", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property<bool>("CanBrowse") + .HasColumnType("boolean") + .HasColumnName("can_browse"); + + b.Property<bool>("CanComment") + .HasColumnType("boolean") + .HasColumnName("can_comment"); + + b.Property<bool>("CanEdit") + .HasColumnType("boolean") + .HasColumnName("can_edit"); + + b.Property<bool>("CanSubmit") + .HasColumnType("boolean") + .HasColumnName("can_submit"); + + b.Property<Guid?>("CollectionId") + .HasColumnType("uuid") + .HasColumnName("collection_id"); + + b.Property<DateTime>("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property<bool>("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property<DateTime?>("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property<DateTime?>("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property<Guid?>("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_todo_collection_access_controls"); + + b.HasIndex("CollectionId") + .HasDatabaseName("ix_todo_collection_access_controls_collection_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_todo_collection_access_controls_user_id"); + + b.ToTable("todo_collection_access_controls", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoComment", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property<int?>("ClosingStatement") + .HasColumnType("integer") + .HasColumnName("closing_statement"); + + b.Property<DateTime>("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property<Guid?>("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property<bool>("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property<DateTime?>("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property<Guid?>("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property<DateTime?>("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property<Guid?>("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property<Guid?>("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property<Guid?>("TodoId") + .HasColumnType("uuid") + .HasColumnName("todo_id"); + + b.Property<Guid?>("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.Property<string>("Value") + .HasColumnType("text") + .HasColumnName("value"); + + b.HasKey("Id") + .HasName("pk_todo_comments"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_todo_comments_tenant_id"); + + b.HasIndex("TodoId") + .HasDatabaseName("ix_todo_comments_todo_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_todo_comments_user_id"); + + b.ToTable("todo_comments", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoLabel", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property<string>("Color") + .HasColumnType("text") + .HasColumnName("color"); + + b.Property<DateTime>("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property<Guid?>("CreatedBy") + .HasColumnType("uuid") + .HasColumnName("created_by"); + + b.Property<bool>("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property<DateTime?>("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property<Guid?>("DeletedBy") + .HasColumnType("uuid") + .HasColumnName("deleted_by"); + + b.Property<DateTime?>("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property<Guid?>("ModifiedBy") + .HasColumnType("uuid") + .HasColumnName("modified_by"); + + b.Property<string>("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property<Guid?>("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property<Guid?>("TodoId") + .HasColumnType("uuid") + .HasColumnName("todo_id"); + + b.Property<Guid?>("UserId") + .HasColumnType("uuid") + .HasColumnName("user_id"); + + b.HasKey("Id") + .HasName("pk_todo_labels"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_todo_labels_tenant_id"); + + b.HasIndex("TodoId") + .HasDatabaseName("ix_todo_labels_todo_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_todo_labels_user_id"); + + b.ToTable("todo_labels", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.User", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property<DateTime>("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("created_at"); + + b.Property<bool>("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property<DateTime?>("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property<string>("Email") + .HasColumnType("text") + .HasColumnName("email"); + + b.Property<string>("FirstName") + .HasColumnType("text") + .HasColumnName("first_name"); + + b.Property<string>("LastName") + .HasColumnType("text") + .HasColumnName("last_name"); + + b.Property<DateTime?>("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property<string>("Password") + .HasColumnType("text") + .HasColumnName("password"); + + b.Property<Guid?>("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property<string>("Username") + .HasColumnType("text") + .HasColumnName("username"); + + b.HasKey("Id") + .HasName("pk_users"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_users_tenant_id"); + + b.ToTable("users", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasColumnName("id"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id")); + + b.Property<string>("FriendlyName") + .HasColumnType("text") + .HasColumnName("friendly_name"); + + b.Property<string>("Xml") + .HasColumnType("text") + .HasColumnName("xml"); + + b.HasKey("Id") + .HasName("pk_data_protection_keys"); + + b.ToTable("data_protection_keys", (string)null); + }); + + modelBuilder.Entity("CustomerCustomerGroup", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Customer", null) + .WithMany() + .HasForeignKey("CustomersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_customer_customer_group_customers_customers_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.CustomerGroup", null) + .WithMany() + .HasForeignKey("GroupsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_customer_customer_group_customer_groups_groups_id"); + }); + + modelBuilder.Entity("CustomerProject", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Customer", null) + .WithMany() + .HasForeignKey("CustomersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_customer_project_customers_customers_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Project", null) + .WithMany() + .HasForeignKey("ProjectsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_customer_project_projects_projects_id"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ApiAccessToken", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_api_access_tokens_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Customer", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .HasConstraintName("fk_customers_users_owner_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_customers_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_customers_users_user_id"); + + b.Navigation("Owner"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerContact", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Customer", "Customer") + .WithMany("Contacts") + .HasForeignKey("CustomerId") + .HasConstraintName("fk_customer_contacts_customers_customer_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_customer_contacts_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_customer_contacts_users_user_id"); + + b.Navigation("Customer"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerEvent", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Customer", "Customer") + .WithMany("Events") + .HasForeignKey("CustomerId") + .HasConstraintName("fk_customer_events_customers_customer_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_customer_events_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_customer_events_users_user_id"); + + b.Navigation("Customer"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.CustomerGroup", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_customer_groups_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_customer_groups_users_user_id"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.PasswordResetRequest", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_password_reset_requests_users_user_id"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Project", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_projects_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_projects_users_user_id"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ProjectLabel", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Project", "Project") + .WithMany("Labels") + .HasForeignKey("ProjectId") + .HasConstraintName("fk_project_labels_projects_project_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_project_labels_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_project_labels_users_user_id"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + + b.Navigation("Project"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ProjectMember", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Project", "Project") + .WithMany("Members") + .HasForeignKey("ProjectId") + .HasConstraintName("fk_project_members_projects_project_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_project_members_users_user_id"); + + b.Navigation("Project"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Tenant", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("OwningTenantId") + .HasConstraintName("fk_tenants_tenants_owning_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany("Tenants") + .HasForeignKey("UserId") + .HasConstraintName("fk_tenants_users_user_id"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Todo", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "AssignedTo") + .WithMany() + .HasForeignKey("AssignedToId") + .HasConstraintName("fk_todos_users_assigned_to_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "ClosedBy") + .WithMany() + .HasForeignKey("ClosedById") + .HasConstraintName("fk_todos_users_closed_by_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.TodoCollection", "Collection") + .WithMany() + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_todos_todo_projects_collection_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_todos_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_todos_users_user_id"); + + b.Navigation("AssignedTo"); + + b.Navigation("ClosedBy"); + + b.Navigation("Collection"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoCollection", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Project", "Project") + .WithMany() + .HasForeignKey("ProjectId") + .HasConstraintName("fk_todo_collections_projects_project_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_todo_collections_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_todo_collections_users_user_id"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + + b.Navigation("Project"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoCollectionAccessControl", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.TodoCollection", "Collection") + .WithMany("AccessControls") + .HasForeignKey("CollectionId") + .HasConstraintName("fk_todo_collection_access_controls_todo_collections_collection"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_todo_collection_access_controls_users_user_id"); + + b.Navigation("Collection"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoComment", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_todo_comments_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Todo", "Todo") + .WithMany("Comments") + .HasForeignKey("TodoId") + .HasConstraintName("fk_todo_comments_todos_todo_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_todo_comments_users_user_id"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + + b.Navigation("Todo"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoLabel", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") + .WithMany() + .HasForeignKey("TenantId") + .HasConstraintName("fk_todo_labels_tenants_tenant_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.Todo", "Todo") + .WithMany("Labels") + .HasForeignKey("TodoId") + .HasConstraintName("fk_todo_labels_todos_todo_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_todo_labels_users_user_id"); + + b.Navigation("OwningTenant"); + + b.Navigation("OwningUser"); + + b.Navigation("Todo"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.User", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", null) + .WithMany("Users") + .HasForeignKey("TenantId") + .HasConstraintName("fk_users_tenants_tenant_id"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Customer", b => + { + b.Navigation("Contacts"); + + b.Navigation("Events"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Project", b => + { + b.Navigation("Labels"); + + b.Navigation("Members"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Tenant", b => + { + b.Navigation("Users"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Todo", b => + { + b.Navigation("Comments"); + + b.Navigation("Labels"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TodoCollection", b => + { + b.Navigation("AccessControls"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.User", b => + { + b.Navigation("Tenants"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/code/api/src/Migrations/20221114035223_RenameForgotPasswordRequests.cs b/code/api/src/Migrations/20221114035223_RenameForgotPasswordRequests.cs new file mode 100644 index 0000000..8b256e7 --- /dev/null +++ b/code/api/src/Migrations/20221114035223_RenameForgotPasswordRequests.cs @@ -0,0 +1,78 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace IOL.GreatOffice.Api.Migrations +{ + /// <inheritdoc /> + public partial class RenameForgotPasswordRequests : Migration + { + /// <inheritdoc /> + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "fk_forgot_password_requests_users_user_id", + table: "forgot_password_requests"); + + migrationBuilder.DropPrimaryKey( + name: "pk_forgot_password_requests", + table: "forgot_password_requests"); + + migrationBuilder.RenameTable( + name: "forgot_password_requests", + newName: "password_reset_requests"); + + migrationBuilder.RenameIndex( + name: "ix_forgot_password_requests_user_id", + table: "password_reset_requests", + newName: "ix_password_reset_requests_user_id"); + + migrationBuilder.AddPrimaryKey( + name: "pk_password_reset_requests", + table: "password_reset_requests", + column: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_password_reset_requests_users_user_id", + table: "password_reset_requests", + column: "user_id", + principalTable: "users", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + } + + /// <inheritdoc /> + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "fk_password_reset_requests_users_user_id", + table: "password_reset_requests"); + + migrationBuilder.DropPrimaryKey( + name: "pk_password_reset_requests", + table: "password_reset_requests"); + + migrationBuilder.RenameTable( + name: "password_reset_requests", + newName: "forgot_password_requests"); + + migrationBuilder.RenameIndex( + name: "ix_password_reset_requests_user_id", + table: "forgot_password_requests", + newName: "ix_forgot_password_requests_user_id"); + + migrationBuilder.AddPrimaryKey( + name: "pk_forgot_password_requests", + table: "forgot_password_requests", + column: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_forgot_password_requests_users_user_id", + table: "forgot_password_requests", + column: "user_id", + principalTable: "users", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + } + } +} diff --git a/code/api/src/Migrations/AppDbContextModelSnapshot.cs b/code/api/src/Migrations/AppDbContextModelSnapshot.cs index f2031c1..a266339 100644 --- a/code/api/src/Migrations/AppDbContextModelSnapshot.cs +++ b/code/api/src/Migrations/AppDbContextModelSnapshot.cs @@ -17,7 +17,7 @@ namespace IOL.GreatOffice.Api.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "6.0.10") + .HasAnnotation("ProductVersion", "7.0.0") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); @@ -453,7 +453,7 @@ namespace IOL.GreatOffice.Api.Migrations b.ToTable("customer_groups", (string)null); }); - modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ForgotPasswordRequest", b => + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.PasswordResetRequest", b => { b.Property<Guid>("Id") .ValueGeneratedOnAdd() @@ -469,12 +469,12 @@ namespace IOL.GreatOffice.Api.Migrations .HasColumnName("user_id"); b.HasKey("Id") - .HasName("pk_forgot_password_requests"); + .HasName("pk_password_reset_requests"); b.HasIndex("UserId") - .HasDatabaseName("ix_forgot_password_requests_user_id"); + .HasDatabaseName("ix_password_reset_requests_user_id"); - b.ToTable("forgot_password_requests", (string)null); + b.ToTable("password_reset_requests", (string)null); }); modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Project", b => @@ -744,213 +744,6 @@ namespace IOL.GreatOffice.Api.Migrations b.ToTable("tenants", (string)null); }); - modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeCategory", b => - { - b.Property<Guid>("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property<string>("Color") - .HasColumnType("text") - .HasColumnName("color"); - - b.Property<DateTime>("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property<Guid?>("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property<bool>("Deleted") - .HasColumnType("boolean") - .HasColumnName("deleted"); - - b.Property<DateTime?>("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property<Guid?>("DeletedBy") - .HasColumnType("uuid") - .HasColumnName("deleted_by"); - - b.Property<DateTime?>("ModifiedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("modified_at"); - - b.Property<Guid?>("ModifiedBy") - .HasColumnType("uuid") - .HasColumnName("modified_by"); - - b.Property<string>("Name") - .HasColumnType("text") - .HasColumnName("name"); - - b.Property<Guid?>("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property<Guid?>("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_time_categories"); - - b.HasIndex("TenantId") - .HasDatabaseName("ix_time_categories_tenant_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_time_categories_user_id"); - - b.ToTable("time_categories", (string)null); - }); - - modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeEntry", b => - { - b.Property<Guid>("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property<Guid?>("CategoryId") - .HasColumnType("uuid") - .HasColumnName("category_id"); - - b.Property<DateTime>("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property<Guid?>("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property<bool>("Deleted") - .HasColumnType("boolean") - .HasColumnName("deleted"); - - b.Property<DateTime?>("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property<Guid?>("DeletedBy") - .HasColumnType("uuid") - .HasColumnName("deleted_by"); - - b.Property<string>("Description") - .HasColumnType("text") - .HasColumnName("description"); - - b.Property<DateTime?>("ModifiedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("modified_at"); - - b.Property<Guid?>("ModifiedBy") - .HasColumnType("uuid") - .HasColumnName("modified_by"); - - b.Property<DateTime>("Start") - .HasColumnType("timestamp with time zone") - .HasColumnName("start"); - - b.Property<DateTime>("Stop") - .HasColumnType("timestamp with time zone") - .HasColumnName("stop"); - - b.Property<Guid?>("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property<Guid?>("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_time_entries"); - - b.HasIndex("CategoryId") - .HasDatabaseName("ix_time_entries_category_id"); - - b.HasIndex("TenantId") - .HasDatabaseName("ix_time_entries_tenant_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_time_entries_user_id"); - - b.ToTable("time_entries", (string)null); - }); - - modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeLabel", b => - { - b.Property<Guid>("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uuid") - .HasColumnName("id"); - - b.Property<string>("Color") - .HasColumnType("text") - .HasColumnName("color"); - - b.Property<DateTime>("CreatedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("created_at"); - - b.Property<Guid?>("CreatedBy") - .HasColumnType("uuid") - .HasColumnName("created_by"); - - b.Property<bool>("Deleted") - .HasColumnType("boolean") - .HasColumnName("deleted"); - - b.Property<DateTime?>("DeletedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("deleted_at"); - - b.Property<Guid?>("DeletedBy") - .HasColumnType("uuid") - .HasColumnName("deleted_by"); - - b.Property<DateTime?>("ModifiedAt") - .HasColumnType("timestamp with time zone") - .HasColumnName("modified_at"); - - b.Property<Guid?>("ModifiedBy") - .HasColumnType("uuid") - .HasColumnName("modified_by"); - - b.Property<string>("Name") - .HasColumnType("text") - .HasColumnName("name"); - - b.Property<Guid?>("TenantId") - .HasColumnType("uuid") - .HasColumnName("tenant_id"); - - b.Property<Guid?>("TimeEntryId") - .HasColumnType("uuid") - .HasColumnName("time_entry_id"); - - b.Property<Guid?>("UserId") - .HasColumnType("uuid") - .HasColumnName("user_id"); - - b.HasKey("Id") - .HasName("pk_time_labels"); - - b.HasIndex("TenantId") - .HasDatabaseName("ix_time_labels_tenant_id"); - - b.HasIndex("TimeEntryId") - .HasDatabaseName("ix_time_labels_time_entry_id"); - - b.HasIndex("UserId") - .HasDatabaseName("ix_time_labels_user_id"); - - b.ToTable("time_labels", (string)null); - }); - modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Todo", b => { b.Property<Guid>("Id") @@ -1528,14 +1321,14 @@ namespace IOL.GreatOffice.Api.Migrations b.Navigation("OwningUser"); }); - modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ForgotPasswordRequest", b => + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.PasswordResetRequest", b => { b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") .WithMany() .HasForeignKey("UserId") .OnDelete(DeleteBehavior.Cascade) .IsRequired() - .HasConstraintName("fk_forgot_password_requests_users_user_id"); + .HasConstraintName("fk_password_reset_requests_users_user_id"); b.Navigation("User"); }); @@ -1615,69 +1408,6 @@ namespace IOL.GreatOffice.Api.Migrations b.Navigation("OwningUser"); }); - modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeCategory", b => - { - b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") - .WithMany() - .HasForeignKey("TenantId") - .HasConstraintName("fk_time_categories_tenants_tenant_id"); - - b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") - .WithMany() - .HasForeignKey("UserId") - .HasConstraintName("fk_time_categories_users_user_id"); - - b.Navigation("OwningTenant"); - - b.Navigation("OwningUser"); - }); - - modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeEntry", b => - { - b.HasOne("IOL.GreatOffice.Api.Data.Database.TimeCategory", "Category") - .WithMany() - .HasForeignKey("CategoryId") - .HasConstraintName("fk_time_entries_time_categories_category_id"); - - b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") - .WithMany() - .HasForeignKey("TenantId") - .HasConstraintName("fk_time_entries_tenants_tenant_id"); - - b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") - .WithMany() - .HasForeignKey("UserId") - .HasConstraintName("fk_time_entries_users_user_id"); - - b.Navigation("Category"); - - b.Navigation("OwningTenant"); - - b.Navigation("OwningUser"); - }); - - modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeLabel", b => - { - b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", "OwningTenant") - .WithMany() - .HasForeignKey("TenantId") - .HasConstraintName("fk_time_labels_tenants_tenant_id"); - - b.HasOne("IOL.GreatOffice.Api.Data.Database.TimeEntry", null) - .WithMany("Labels") - .HasForeignKey("TimeEntryId") - .HasConstraintName("fk_time_labels_time_entries_time_entry_id"); - - b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "OwningUser") - .WithMany() - .HasForeignKey("UserId") - .HasConstraintName("fk_time_labels_users_user_id"); - - b.Navigation("OwningTenant"); - - b.Navigation("OwningUser"); - }); - modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Todo", b => { b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "AssignedTo") @@ -1834,11 +1564,6 @@ namespace IOL.GreatOffice.Api.Migrations b.Navigation("Users"); }); - modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeEntry", b => - { - b.Navigation("Labels"); - }); - modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Todo", b => { b.Navigation("Comments"); diff --git a/code/api/src/Program.cs b/code/api/src/Program.cs index e56c123..f27f048 100644 --- a/code/api/src/Program.cs +++ b/code/api/src/Program.cs @@ -6,15 +6,16 @@ global using System.Net; global using System.Threading; global using System.Threading.Tasks; global using System.Collections.Generic; -global using System.Runtime.Serialization; global using System.ComponentModel.DataAnnotations.Schema; global using System.Security.Claims; global using System.Text.Json; global using System.Text.Json.Serialization; global using IOL.GreatOffice.Api.Data.Database; -global using IOL.GreatOffice.Api.Data.Exceptions; global using IOL.GreatOffice.Api.Data.Enums; global using IOL.GreatOffice.Api.Data.Models; +global using IOL.GreatOffice.Api.Data.Static; +global using IOL.GreatOffice.Api.Services; +global using IOL.GreatOffice.Api.Utilities; global using IOL.Helpers; global using Microsoft.OpenApi.Models; global using Microsoft.AspNetCore.Authentication.Cookies; @@ -31,10 +32,6 @@ global using Microsoft.Extensions.DependencyInjection; global using Microsoft.Extensions.Hosting; global using Microsoft.Extensions.Logging; global using Serilog; -global using IOL.GreatOffice.Api.Data; -global using IOL.GreatOffice.Api.Data.Static; -global using IOL.GreatOffice.Api.Services; -global using IOL.GreatOffice.Api.Utilities; using IOL.GreatOffice.Api.Endpoints.V1; using IOL.GreatOffice.Api.Jobs; using Microsoft.AspNetCore.HttpOverrides; @@ -228,11 +225,6 @@ public static class Program CreateWebApplication(CreateAppBuilder(args)).Run(); return 0; } catch (Exception ex) { - // This is subject to change in future .net versions, see https://github.com/dotnet/runtime/issues/60600. - if (ex.GetType().Name.Equals("StopTheHostException", StringComparison.Ordinal)) { - throw; - } - Log.Fatal(ex, "Unhandled exception"); return 1; } diff --git a/code/api/src/Services/PasswordResetService.cs b/code/api/src/Services/PasswordResetService.cs index 76eb2fe..d4889bc 100644 --- a/code/api/src/Services/PasswordResetService.cs +++ b/code/api/src/Services/PasswordResetService.cs @@ -19,8 +19,8 @@ public class PasswordResetService _mailService = mailService; } - public async Task<ForgotPasswordRequest> GetRequestAsync(Guid id, CancellationToken cancellationToken = default) { - var request = await _database.ForgotPasswordRequests + public async Task<PasswordResetRequest> GetRequestAsync(Guid id, CancellationToken cancellationToken = default) { + var request = await _database.PasswordResetRequests .Include(c => c.User) .SingleOrDefaultAsync(c => c.Id == id, cancellationToken); if (request == default) { @@ -30,31 +30,24 @@ public class PasswordResetService _logger.LogInformation($"Found password reset request for user: {request.User.Username}, expires at {request.ExpirationDate} (in {request.ExpirationDate.Subtract(AppDateTime.UtcNow).Minutes} minutes)."); return request; } - - public async Task<bool> FullFillRequestAsync(Guid id, string newPassword, CancellationToken cancellationToken = default) { - var request = await GetRequestAsync(id, cancellationToken); - if (request == default) { - throw new ForgotPasswordRequestNotFoundException("Request with id: " + id + " was not found"); - } - - var user = _database.Users.SingleOrDefault(c => c.Id == request.User.Id); - if (user == default) { - throw new UserNotFoundException("User with id: " + request.User.Id + " was not found"); - } + public async Task<FulfillPasswordResetRequestResult> FulfillRequestAsync(Guid id, string newPassword, CancellationToken cancellationToken = default) { + var request = await GetRequestAsync(id, cancellationToken); + if (request == default) return FulfillPasswordResetRequestResult.REQUEST_NOT_FOUND; + var user = _database.Users.FirstOrDefault(c => c.Id == request.User.Id); + if (user == default) return FulfillPasswordResetRequestResult.USER_NOT_FOUND; user.HashAndSetPassword(newPassword); _database.Users.Update(user); await _database.SaveChangesAsync(cancellationToken); _logger.LogInformation($"Fullfilled password reset request for user: {request.User.Username}"); await DeleteRequestsForUserAsync(user.Id, cancellationToken); - return true; + return FulfillPasswordResetRequestResult.FULFILLED; } - public async Task AddRequestAsync(User user, TimeZoneInfo requestTz, CancellationToken cancellationToken = default) { await DeleteRequestsForUserAsync(user.Id, cancellationToken); - var request = new ForgotPasswordRequest(user); - _database.ForgotPasswordRequests.Add(request); + var request = new PasswordResetRequest(user); + _database.PasswordResetRequests.Add(request); await _database.SaveChangesAsync(cancellationToken); var portalUrl = _configuration.PORTAL_URL; var emailFromAddress = _configuration.EMAIL_FROM_ADDRESS; @@ -88,22 +81,21 @@ If you did not request a password reset, no action is required. } public async Task DeleteRequestsForUserAsync(Guid userId, CancellationToken cancellationToken = default) { - var requestsToRemove = _database.ForgotPasswordRequests.Where(c => c.UserId == userId).ToList(); + var requestsToRemove = _database.PasswordResetRequests.Where(c => c.UserId == userId).ToList(); if (!requestsToRemove.Any()) return; - _database.ForgotPasswordRequests.RemoveRange(requestsToRemove); + _database.PasswordResetRequests.RemoveRange(requestsToRemove); await _database.SaveChangesAsync(cancellationToken); _logger.LogInformation($"Deleted {requestsToRemove.Count} password reset requests for user: {userId}."); } - public async Task DeleteStaleRequestsAsync(CancellationToken cancellationToken = default) { var deleteCount = 0; - foreach (var request in _database.ForgotPasswordRequests.Where(c => c.IsExpired)) { + foreach (var request in _database.PasswordResetRequests.Where(c => c.IsExpired)) { if (!request.IsExpired) { continue; } - _database.ForgotPasswordRequests.Remove(request); + _database.PasswordResetRequests.Remove(request); deleteCount++; _logger.LogInformation($"Marking password reset request with id: {request.Id} for deletion, expiration date was {request.ExpirationDate}."); } |
