diff options
Diffstat (limited to 'code')
115 files changed, 7785 insertions, 1405 deletions
diff --git a/code/api/src/Data/AppDbContext.cs b/code/api/src/Data/AppDbContext.cs deleted file mode 100644 index c970429..0000000 --- a/code/api/src/Data/AppDbContext.cs +++ /dev/null @@ -1,51 +0,0 @@ -using Microsoft.AspNetCore.DataProtection.EntityFrameworkCore; - -namespace IOL.GreatOffice.Api.Data; - -public class AppDbContext : DbContext, IDataProtectionKeyContext -{ - public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { } - public DbSet<User> Users { get; set; } - public DbSet<ForgotPasswordRequest> ForgotPasswordRequests { get; set; } - public DbSet<TimeLabel> TimeLabels { get; set; } - public DbSet<TimeEntry> TimeEntries { get; set; } - public DbSet<TimeCategory> TimeCategories { get; set; } - public DbSet<ApiAccessToken> AccessTokens { get; set; } - public DbSet<Tenant> Tenants { get; set; } - public DbSet<DataProtectionKey> DataProtectionKeys { get; set; } - - protected override void OnModelCreating(ModelBuilder modelBuilder) { - modelBuilder.Entity<User>(e => { - e.ToTable("users"); - }); - - modelBuilder.Entity<ForgotPasswordRequest>(e => { - e.HasOne(c => c.User); - e.ToTable("forgot_password_requests"); - }); - - modelBuilder.Entity<TimeCategory>(e => { - e.ToTable("time_categories"); - }); - - modelBuilder.Entity<TimeLabel>(e => { - e.ToTable("time_labels"); - }); - - modelBuilder.Entity<TimeEntry>(e => { - e.HasOne(c => c.Category); - e.HasMany(c => c.Labels); - e.ToTable("time_entries"); - }); - - modelBuilder.Entity<ApiAccessToken>(e => { - e.ToTable("api_access_tokens"); - }); - - modelBuilder.Entity<Tenant>(e => { - e.ToTable("tenants"); - }); - - base.OnModelCreating(modelBuilder); - } -} diff --git a/code/api/src/Data/Database/ApiAccessToken.cs b/code/api/src/Data/Database/Api/ApiAccessToken.cs index 9582869..9582869 100644 --- a/code/api/src/Data/Database/ApiAccessToken.cs +++ b/code/api/src/Data/Database/Api/ApiAccessToken.cs diff --git a/code/api/src/Data/Database/Base.cs b/code/api/src/Data/Database/Base.cs index ae9efa2..900b923 100644 --- a/code/api/src/Data/Database/Base.cs +++ b/code/api/src/Data/Database/Base.cs @@ -10,6 +10,13 @@ public class Base public Guid Id { get; init; } public DateTime CreatedAt { get; init; } public DateTime? ModifiedAt { get; private set; } - public bool Deleted { get; set; } - public void Modified() => ModifiedAt = AppDateTime.UtcNow; + public DateTime? DeletedAt { get; private set; } + public bool Deleted { get; private set; } + + public void SetModified() => ModifiedAt = AppDateTime.UtcNow; + + public void SetDeleted() { + Deleted = true; + DeletedAt = AppDateTime.UtcNow; + } }
\ No newline at end of file diff --git a/code/api/src/Data/Database/BaseWithOwner.cs b/code/api/src/Data/Database/BaseWithOwner.cs index 1eb99f4..84b5bfe 100644 --- a/code/api/src/Data/Database/BaseWithOwner.cs +++ b/code/api/src/Data/Database/BaseWithOwner.cs @@ -1,19 +1,34 @@ namespace IOL.GreatOffice.Api.Data.Database; /// <summary> -/// Base class for all entities. +/// Base class for all entities with ownership. /// </summary> public class BaseWithOwner : Base { protected BaseWithOwner() { } - protected BaseWithOwner(Guid userId) { - UserId = userId; + protected BaseWithOwner(LoggedInUserModel loggedInUser) { + CreatedBy = loggedInUser.Id; } - public Guid? UserId { get; set; } - public Guid? TenantId { get; init; } - public Guid? ModifiedById { get; init; } - public Guid? CreatedById { get; init; } - public Guid? DeletedById { get; init; } + public Guid? UserId { get; private set; } + public Guid? TenantId { get; private set; } + public Guid? ModifiedBy { get; private set; } + public Guid? CreatedBy { get; private set; } + public Guid? DeletedBy { get; private set; } + + public void SetDeleted(Guid userId) { + DeletedBy = userId; + SetDeleted(); + } + + public void SetModified(Guid userId) { + ModifiedBy = userId; + SetModified(); + } + + public void SetOwnerIds(Guid userId = default, Guid tenantId = default) { + if (tenantId != default) TenantId = tenantId; + if (userId != default) UserId = userId; + } }
\ No newline at end of file diff --git a/code/api/src/Data/Database/Customer.cs b/code/api/src/Data/Database/Customer.cs deleted file mode 100644 index c6b06a4..0000000 --- a/code/api/src/Data/Database/Customer.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace IOL.GreatOffice.Api.Data.Database; - -public class Customer : BaseWithOwner -{ - public string Name { get; set; } -}
\ No newline at end of file diff --git a/code/api/src/Data/Database/Customer/Customer.cs b/code/api/src/Data/Database/Customer/Customer.cs new file mode 100644 index 0000000..f9953da --- /dev/null +++ b/code/api/src/Data/Database/Customer/Customer.cs @@ -0,0 +1,29 @@ +namespace IOL.GreatOffice.Api.Data.Database; + +public class Customer : BaseWithOwner +{ + public Customer() { } + public Customer(LoggedInUserModel loggedInUserModel) : base(loggedInUserModel) { } + + public string CustomerNumber { get; set; } + public string Name { get; set; } + public string Description { get; set; } + public string Address1 { get; set; } + public string Address2 { get; set; } + public string PostalCode { get; set; } + public string PostalCity { get; set; } + public string Country { get; set; } + public string Phone { get; set; } + public string Email { get; set; } + public string VATNumber { get; set; } + public string ORGNumber { get; set; } + public string DefaultReference { get; set; } + public string Website { get; set; } + public string Currency { get; set; } + + public User Owner { get; set; } + public ICollection<CustomerGroup> Groups { get; set; } + public ICollection<CustomerContact> Contacts { get; set; } + public ICollection<CustomerEvent> Events { get; set; } + public ICollection<Project> Projects { get; set; } +}
\ No newline at end of file diff --git a/code/api/src/Data/Database/CustomerContact.cs b/code/api/src/Data/Database/Customer/CustomerContact.cs index f5a951d..f5a951d 100644 --- a/code/api/src/Data/Database/CustomerContact.cs +++ b/code/api/src/Data/Database/Customer/CustomerContact.cs diff --git a/code/api/src/Data/Database/CustomerEvent.cs b/code/api/src/Data/Database/Customer/CustomerEvent.cs index da3e3ed..a87da4c 100644 --- a/code/api/src/Data/Database/CustomerEvent.cs +++ b/code/api/src/Data/Database/Customer/CustomerEvent.cs @@ -3,5 +3,6 @@ namespace IOL.GreatOffice.Api.Data.Database; public class CustomerEvent : BaseWithOwner { public Customer Customer { get; set; } - public string Name { get; set; } + public string Title { get; set; } + public string Note { get; set; } } diff --git a/code/api/src/Data/Database/Customer/CustomerGroup.cs b/code/api/src/Data/Database/Customer/CustomerGroup.cs new file mode 100644 index 0000000..9438f3c --- /dev/null +++ b/code/api/src/Data/Database/Customer/CustomerGroup.cs @@ -0,0 +1,7 @@ +namespace IOL.GreatOffice.Api.Data.Database; + +public class CustomerGroup : BaseWithOwner +{ + public string Name { get; set; } + public ICollection<Customer> Customers { get; set; } +}
\ No newline at end of file diff --git a/code/api/src/Data/Database/Customer/CustomerGroupMembership.cs b/code/api/src/Data/Database/Customer/CustomerGroupMembership.cs new file mode 100644 index 0000000..ec0d4af --- /dev/null +++ b/code/api/src/Data/Database/Customer/CustomerGroupMembership.cs @@ -0,0 +1,7 @@ +namespace IOL.GreatOffice.Api.Data.Database; + +public class CustomerGroupMembership : Base +{ + public Customer Customer { get; set; } + public CustomerGroup Group { get; set; } +}
\ No newline at end of file diff --git a/code/api/src/Data/Database/ForgotPasswordRequest.cs b/code/api/src/Data/Database/Internal/ForgotPasswordRequest.cs index 1510a35..1510a35 100644 --- a/code/api/src/Data/Database/ForgotPasswordRequest.cs +++ b/code/api/src/Data/Database/Internal/ForgotPasswordRequest.cs diff --git a/code/api/src/Data/Database/Tenant.cs b/code/api/src/Data/Database/Internal/Tenant.cs index b185c7a..b185c7a 100644 --- a/code/api/src/Data/Database/Tenant.cs +++ b/code/api/src/Data/Database/Internal/Tenant.cs diff --git a/code/api/src/Data/Database/User.cs b/code/api/src/Data/Database/Internal/User.cs index 9db5d35..9db5d35 100644 --- a/code/api/src/Data/Database/User.cs +++ b/code/api/src/Data/Database/Internal/User.cs diff --git a/code/api/src/Data/Database/MainAppDatabase.cs b/code/api/src/Data/Database/MainAppDatabase.cs new file mode 100644 index 0000000..b529791 --- /dev/null +++ b/code/api/src/Data/Database/MainAppDatabase.cs @@ -0,0 +1,85 @@ +using IOL.GreatOffice.Api.Endpoints.V1.Projects; +using Microsoft.AspNetCore.DataProtection.EntityFrameworkCore; + +namespace IOL.GreatOffice.Api.Data.Database; + +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<TimeLabel> TimeLabels { get; set; } + public DbSet<TimeEntry> TimeEntries { get; set; } + public DbSet<TimeCategory> TimeCategories { get; set; } + public DbSet<ApiAccessToken> AccessTokens { get; set; } + public DbSet<Tenant> Tenants { get; set; } + public DbSet<DataProtectionKey> DataProtectionKeys { get; set; } + public DbSet<Project> Projects { get; set; } + public DbSet<ProjectLabel> ProjectLabels { get; set; } + public DbSet<Customer> Customers { get; set; } + public DbSet<CustomerContact> CustomersContacts { get; set; } + public DbSet<CustomerEvent> CustomerEvents { get; set; } + public DbSet<CustomerGroup> CustomerGroups { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) { + modelBuilder.Entity<User>(e => { + e.HasMany(n => n.Tenants); + e.ToTable("users"); + }); + modelBuilder.Entity<ForgotPasswordRequest>(e => { + e.HasOne(c => c.User); + e.ToTable("forgot_password_requests"); + }); + modelBuilder.Entity<TimeCategory>(e => { e.ToTable("time_categories"); }); + modelBuilder.Entity<TimeLabel>(e => { e.ToTable("time_labels"); }); + modelBuilder.Entity<TimeEntry>(e => { + e.HasOne(c => c.Category); + e.HasMany(c => c.Labels); + e.ToTable("time_entries"); + }); + modelBuilder.Entity<ApiAccessToken>(e => { + e.HasOne(n => n.User); + e.ToTable("api_access_tokens"); + }); + modelBuilder.Entity<Tenant>(e => { + e.HasMany(n => n.Users); + e.ToTable("tenants"); + }); + modelBuilder.Entity<Project>(e => { + e.HasMany(n => n.Members); + e.HasMany(n => n.Customers); + e.ToTable("projects"); + }); + modelBuilder.Entity<ProjectMember>(e => { + e.HasOne(n => n.Project); + e.HasOne(n => n.User); + e.ToTable("project_members"); + }); + modelBuilder.Entity<ProjectLabel>(e => { + e.HasOne(n => n.Project); + e.ToTable("project_labels"); + }); + modelBuilder.Entity<Customer>(e => { + e.HasOne(n => n.Owner); + e.HasMany(n => n.Events); + e.HasMany(n => n.Contacts); + e.HasMany(n => n.Groups); + e.HasMany(n => n.Projects); + e.ToTable("customers"); + }); + modelBuilder.Entity<CustomerContact>(e => { + e.HasOne(n => n.Customer); + e.ToTable("customer_contacts"); + }); + modelBuilder.Entity<CustomerEvent>(e => { + e.HasOne(n => n.Customer); + e.ToTable("customer_events"); + }); + modelBuilder.Entity<CustomerGroup>(e => { + e.HasMany(n => n.Customers); + e.ToTable("customer_groups"); + }); + + base.OnModelCreating(modelBuilder); + } +}
\ No newline at end of file diff --git a/code/api/src/Data/Database/Project.cs b/code/api/src/Data/Database/Project.cs deleted file mode 100644 index 99c6e7f..0000000 --- a/code/api/src/Data/Database/Project.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace IOL.GreatOffice.Api.Data.Database; - -public class Project : BaseWithOwner -{ - public string Name { get; set; } - public string Description { get; set; } - public DateTime? Start { get; set; } - public DateTime? Stop { get; set; } - public List<Customer> Customers { get; set; } - public List<User> Owners { get; set; } - public List<ProjectLabel> Labels { get; set; } -} diff --git a/code/api/src/Data/Database/Project/Project.cs b/code/api/src/Data/Database/Project/Project.cs new file mode 100644 index 0000000..de9e2cb --- /dev/null +++ b/code/api/src/Data/Database/Project/Project.cs @@ -0,0 +1,15 @@ +namespace IOL.GreatOffice.Api.Data.Database; + +public class Project : BaseWithOwner +{ + public Project() { } + + public Project(LoggedInUserModel loggedInUserModel) : base(loggedInUserModel) { } + public string Name { get; set; } + public string Description { get; set; } + public DateTime? Start { get; set; } + public DateTime? Stop { get; set; } + public ICollection<Customer> Customers { get; set; } + public ICollection<ProjectMember> Members { get; set; } + public ICollection<ProjectLabel> Labels { get; set; } +}
\ No newline at end of file diff --git a/code/api/src/Data/Database/ProjectLabel.cs b/code/api/src/Data/Database/Project/ProjectLabel.cs index 8fb18c2..0e1dc5d 100644 --- a/code/api/src/Data/Database/ProjectLabel.cs +++ b/code/api/src/Data/Database/Project/ProjectLabel.cs @@ -2,7 +2,7 @@ namespace IOL.GreatOffice.Api.Data.Database; public class ProjectLabel : BaseWithOwner { - public string Name { get; set; } + public string Value { get; set; } public string Color { get; set; } - public Project Todo { get; set; } + public Project Project { get; set; } } diff --git a/code/api/src/Data/Database/Project/ProjectMember.cs b/code/api/src/Data/Database/Project/ProjectMember.cs new file mode 100644 index 0000000..a5e0682 --- /dev/null +++ b/code/api/src/Data/Database/Project/ProjectMember.cs @@ -0,0 +1,8 @@ +namespace IOL.GreatOffice.Api.Data.Database; + +public class ProjectMember : Base +{ + public Project Project { get; set; } + public User User { get; set; } + public ProjectRole Role { get; set; } +}
\ No newline at end of file diff --git a/code/api/src/Data/Database/Time/TimeCategory.cs b/code/api/src/Data/Database/Time/TimeCategory.cs new file mode 100644 index 0000000..c3f013d --- /dev/null +++ b/code/api/src/Data/Database/Time/TimeCategory.cs @@ -0,0 +1,32 @@ +namespace IOL.GreatOffice.Api.Data.Database; + +public class TimeCategory : BaseWithOwner +{ + public TimeCategory() { } + + public TimeCategory(LoggedInUserModel loggedInUserModel) : base(loggedInUserModel) { } + public string Name { get; set; } + public string Color { get; set; } + public TimeCategoryDto AsDto => new(this); + + public class TimeCategoryDto + { + public TimeCategoryDto() { } + + public TimeCategoryDto(TimeCategory sourceEntry = default) { + if (sourceEntry == default) { + return; + } + + Id = sourceEntry.Id; + ModifiedAt = sourceEntry.ModifiedAt; + Name = sourceEntry.Name; + Color = sourceEntry.Color; + } + + public Guid Id { get; set; } + public DateTime? ModifiedAt { get; set; } + public string Name { get; set; } + public string Color { get; set; } + } +}
\ No newline at end of file diff --git a/code/api/src/Data/Database/Time/TimeEntry.cs b/code/api/src/Data/Database/Time/TimeEntry.cs new file mode 100644 index 0000000..0405df2 --- /dev/null +++ b/code/api/src/Data/Database/Time/TimeEntry.cs @@ -0,0 +1,46 @@ +namespace IOL.GreatOffice.Api.Data.Database; + +public class TimeEntry : BaseWithOwner +{ + public TimeEntry() { } + + public TimeEntry(LoggedInUserModel loggedInUserModel) : base(loggedInUserModel) { } + public DateTime Start { get; set; } + public DateTime Stop { get; set; } + public string Description { get; set; } + public ICollection<TimeLabel> Labels { get; set; } + public TimeCategory Category { get; set; } + public TimeEntryDto AsDto => new(this); + + public class TimeEntryDto + { + public TimeEntryDto() { } + + public TimeEntryDto(TimeEntry sourceEntry = default) { + if (sourceEntry == default) { + return; + } + + Id = sourceEntry.Id; + ModifiedAt = sourceEntry.ModifiedAt; + Stop = sourceEntry.Stop; + Start = sourceEntry.Start; + Description = sourceEntry.Description; + if (sourceEntry.Labels != default) { + Labels = sourceEntry.Labels + .Select(t => t.AsDto) + .ToList(); + } + + Category = sourceEntry.Category.AsDto; + } + + public Guid? Id { get; set; } + public DateTime? ModifiedAt { get; set; } + public DateTime Start { get; set; } + public DateTime Stop { get; set; } + public string Description { get; set; } + public List<TimeLabel.TimeLabelDto> Labels { get; set; } + public TimeCategory.TimeCategoryDto Category { get; set; } + } +}
\ No newline at end of file diff --git a/code/api/src/Data/Database/Time/TimeLabel.cs b/code/api/src/Data/Database/Time/TimeLabel.cs new file mode 100644 index 0000000..365161c --- /dev/null +++ b/code/api/src/Data/Database/Time/TimeLabel.cs @@ -0,0 +1,32 @@ +namespace IOL.GreatOffice.Api.Data.Database; + +public class TimeLabel : BaseWithOwner +{ + public TimeLabel() { } + + public TimeLabel(LoggedInUserModel loggedInUserModel) : base(loggedInUserModel) { } + public string Name { get; set; } + public string Color { get; set; } + + [NotMapped] + public TimeLabelDto AsDto => new(this); + + public class TimeLabelDto + { + public TimeLabelDto() { } + + public TimeLabelDto(TimeLabel sourceEntry) { + Id = sourceEntry.Id; + CreatedAt = sourceEntry.CreatedAt; + ModifiedAt = sourceEntry.ModifiedAt; + Name = sourceEntry.Name; + Color = sourceEntry.Color; + } + + public Guid Id { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime? ModifiedAt { get; set; } + public string Name { get; set; } + public string Color { get; set; } + } +}
\ No newline at end of file diff --git a/code/api/src/Data/Database/TimeCategory.cs b/code/api/src/Data/Database/TimeCategory.cs deleted file mode 100644 index 69c6957..0000000 --- a/code/api/src/Data/Database/TimeCategory.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace IOL.GreatOffice.Api.Data.Database; - -public class TimeCategory : BaseWithOwner -{ - public TimeCategory() { } - public TimeCategory(Guid userId) : base(userId) { } - public string Name { get; set; } - public string Color { get; set; } - public TimeCategoryDto AsDto => new(this); - - public class TimeCategoryDto - { - public TimeCategoryDto() { } - - public TimeCategoryDto(TimeCategory sourceEntry = default) { - if (sourceEntry == default) { - return; - } - - Id = sourceEntry.Id; - ModifiedAt = sourceEntry.ModifiedAt; - Name = sourceEntry.Name; - Color = sourceEntry.Color; - } - - public Guid Id { get; set; } - public DateTime? ModifiedAt { get; set; } - public string Name { get; set; } - public string Color { get; set; } - } -} diff --git a/code/api/src/Data/Database/TimeEntry.cs b/code/api/src/Data/Database/TimeEntry.cs deleted file mode 100644 index 46c62e1..0000000 --- a/code/api/src/Data/Database/TimeEntry.cs +++ /dev/null @@ -1,45 +0,0 @@ -namespace IOL.GreatOffice.Api.Data.Database; - -public class TimeEntry : BaseWithOwner -{ - public TimeEntry() { } - public TimeEntry(Guid userId) : base(userId) { } - public DateTime Start { get; set; } - public DateTime Stop { get; set; } - public string Description { get; set; } - public ICollection<TimeLabel> Labels { get; set; } - public TimeCategory Category { get; set; } - public TimeEntryDto AsDto => new(this); - - public class TimeEntryDto - { - public TimeEntryDto() { } - - public TimeEntryDto(TimeEntry sourceEntry = default) { - if (sourceEntry == default) { - return; - } - - Id = sourceEntry.Id; - ModifiedAt = sourceEntry.ModifiedAt; - Stop = sourceEntry.Stop; - Start = sourceEntry.Start; - Description = sourceEntry.Description; - if (sourceEntry.Labels != default) { - Labels = sourceEntry.Labels - .Select(t => t.AsDto) - .ToList(); - } - - Category = sourceEntry.Category.AsDto; - } - - public Guid? Id { get; set; } - public DateTime? ModifiedAt { get; set; } - public DateTime Start { get; set; } - public DateTime Stop { get; set; } - public string Description { get; set; } - public List<TimeLabel.TimeLabelDto> Labels { get; set; } - public TimeCategory.TimeCategoryDto Category { get; set; } - } -} diff --git a/code/api/src/Data/Database/TimeLabel.cs b/code/api/src/Data/Database/TimeLabel.cs deleted file mode 100644 index 55e20b0..0000000 --- a/code/api/src/Data/Database/TimeLabel.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace IOL.GreatOffice.Api.Data.Database; - -public class TimeLabel : BaseWithOwner -{ - public TimeLabel() { } - public TimeLabel(Guid userId) : base(userId) { } - public string Name { get; set; } - public string Color { get; set; } - - [NotMapped] - public TimeLabelDto AsDto => new(this); - - public class TimeLabelDto - { - public TimeLabelDto() { } - - public TimeLabelDto(TimeLabel sourceEntry) { - Id = sourceEntry.Id; - CreatedAt = sourceEntry.CreatedAt; - ModifiedAt = sourceEntry.ModifiedAt; - Name = sourceEntry.Name; - Color = sourceEntry.Color; - } - - public Guid Id { get; set; } - public DateTime CreatedAt { get; set; } - public DateTime? ModifiedAt { get; set; } - public string Name { get; set; } - public string Color { get; set; } - } -} diff --git a/code/api/src/Data/Database/Todo.cs b/code/api/src/Data/Database/Todo/Todo.cs index 5fe3c9a..5fe3c9a 100644 --- a/code/api/src/Data/Database/Todo.cs +++ b/code/api/src/Data/Database/Todo/Todo.cs diff --git a/code/api/src/Data/Database/TodoComment.cs b/code/api/src/Data/Database/Todo/TodoComment.cs index 44dcbed..44dcbed 100644 --- a/code/api/src/Data/Database/TodoComment.cs +++ b/code/api/src/Data/Database/Todo/TodoComment.cs diff --git a/code/api/src/Data/Database/TodoLabel.cs b/code/api/src/Data/Database/Todo/TodoLabel.cs index 7753ade..7753ade 100644 --- a/code/api/src/Data/Database/TodoLabel.cs +++ b/code/api/src/Data/Database/Todo/TodoLabel.cs diff --git a/code/api/src/Data/Database/TodoProject.cs b/code/api/src/Data/Database/Todo/TodoProject.cs index 5e22bbe..5e22bbe 100644 --- a/code/api/src/Data/Database/TodoProject.cs +++ b/code/api/src/Data/Database/Todo/TodoProject.cs diff --git a/code/api/src/Data/Database/TodoProjectAccessControl.cs b/code/api/src/Data/Database/Todo/TodoProjectAccessControl.cs index 964f831..964f831 100644 --- a/code/api/src/Data/Database/TodoProjectAccessControl.cs +++ b/code/api/src/Data/Database/Todo/TodoProjectAccessControl.cs diff --git a/code/api/src/Data/Database/TodoStatus.cs b/code/api/src/Data/Database/Todo/TodoStatus.cs index 416212d..9ace7d0 100644 --- a/code/api/src/Data/Database/TodoStatus.cs +++ b/code/api/src/Data/Database/Todo/TodoStatus.cs @@ -10,35 +10,27 @@ public class TodoStatus : BaseWithOwner return new List<TodoStatus>() { new() { Name = "Reported", - TenantId = tenantId }, new() { Name = "Resolved", - TenantId = tenantId }, new() { Name = "Fixed", - TenantId = tenantId }, new() { Name = "Implemented", - TenantId = tenantId }, new() { Name = "Won't fix", - TenantId = tenantId }, new() { Name = "By design", - TenantId = tenantId }, new() { Name = "Invalid", - TenantId = tenantId }, new() { Name = "Duplicate", - TenantId = tenantId } }; } diff --git a/code/api/src/Data/Enums/ProjectRole.cs b/code/api/src/Data/Enums/ProjectRole.cs new file mode 100644 index 0000000..c4a3f29 --- /dev/null +++ b/code/api/src/Data/Enums/ProjectRole.cs @@ -0,0 +1,9 @@ +namespace IOL.GreatOffice.Api.Data.Enums; + +public enum ProjectRole +{ + EXTERNAL, + RESOURCE, + LEADER, + OWNER +}
\ No newline at end of file diff --git a/code/api/src/Data/Enums/StringsLang.cs b/code/api/src/Data/Enums/StringsLang.cs deleted file mode 100644 index e4e2066..0000000 --- a/code/api/src/Data/Enums/StringsLang.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace IOL.GreatOffice.Api.Data.Enums; - -public enum StringsLang -{ - ENGLISH_GB = 0, - NORWEGIAN_NB = 1 -}
\ No newline at end of file diff --git a/code/api/src/Data/Static/AppConfiguration.cs b/code/api/src/Data/Models/AppConfiguration.cs index 4ee7a8e..f4346bb 100644 --- a/code/api/src/Data/Static/AppConfiguration.cs +++ b/code/api/src/Data/Models/AppConfiguration.cs @@ -1,6 +1,6 @@ using System.Security.Cryptography.X509Certificates; -namespace IOL.GreatOffice.Api.Data.Static; +namespace IOL.GreatOffice.Api.Data.Models; public class AppConfiguration { diff --git a/code/api/src/Data/Models/LoggedInUserModel.cs b/code/api/src/Data/Models/LoggedInUserModel.cs index d802b77..541d4a5 100644 --- a/code/api/src/Data/Models/LoggedInUserModel.cs +++ b/code/api/src/Data/Models/LoggedInUserModel.cs @@ -2,6 +2,7 @@ namespace IOL.GreatOffice.Api.Data.Models; public class LoggedInUserModel { - public Guid Id { get; set; } - public string Username { get; set; } -} + public Guid Id { get; set; } + public string Username { get; set; } + public Guid TenantId { get; set; } +}
\ No newline at end of file diff --git a/code/api/src/Endpoints/EndpointBase.cs b/code/api/src/Endpoints/EndpointBase.cs index de5d967..a5b0931 100644 --- a/code/api/src/Endpoints/EndpointBase.cs +++ b/code/api/src/Endpoints/EndpointBase.cs @@ -1,3 +1,5 @@ +using ILogger = Microsoft.Extensions.Logging.ILogger; + namespace IOL.GreatOffice.Api.Endpoints; [ApiController] @@ -20,4 +22,32 @@ public class EndpointBase : ControllerBase TraceId = HttpContext.TraceIdentifier }); } + + [NonAction] + protected RequestTimeZoneInfo GetRequestTimeZone(ILogger logger = default) { + Request.Headers.TryGetValue(AppHeaders.BROWSER_TIME_ZONE, out var timeZoneHeader); + var tz = TimeZoneInfo.FindSystemTimeZoneById(timeZoneHeader.ToString().HasValue() ? timeZoneHeader.ToString() : "UTC"); + var offset = tz.BaseUtcOffset.Hours; + + // This is fine as long as the client is not connecting from Australia: Lord Howe Island, + // according to https://en.wikipedia.org/wiki/Daylight_saving_time_by_country + if (tz.IsDaylightSavingTime(AppDateTime.UtcNow)) { + offset++; + } + + logger?.LogInformation("Request time zone (" + tz.Id + ") offset is: " + offset + " hours"); + + return new RequestTimeZoneInfo() { + TimeZoneInfo = tz, + Offset = offset, + LocalDateTime = TimeZoneInfo.ConvertTimeFromUtc(AppDateTime.UtcNow, tz) + }; + } + + public class RequestTimeZoneInfo + { + public TimeZoneInfo TimeZoneInfo { get; set; } + public int Offset { get; set; } + public DateTime LocalDateTime { get; set; } + } }
\ No newline at end of file diff --git a/code/api/src/Endpoints/Internal/Account/CreateAccountPayload.cs b/code/api/src/Endpoints/Internal/Account/CreateAccountPayload.cs index dc73e68..1161af3 100644 --- a/code/api/src/Endpoints/Internal/Account/CreateAccountPayload.cs +++ b/code/api/src/Endpoints/Internal/Account/CreateAccountPayload.cs @@ -5,13 +5,13 @@ namespace IOL.GreatOffice.Api.Endpoints.Internal.Account; /// </summary> public class CreateAccountPayload { - /// <summary> - /// Username for the new account. - /// </summary> - public string Username { get; set; } + /// <summary> + /// Username for the new account. + /// </summary> + public string Username { get; set; } - /// <summary> - /// Password for the new account. - /// </summary> - public string Password { get; set; } -} + /// <summary> + /// Password for the new account. + /// </summary> + public string Password { get; set; } +}
\ No newline at end of file diff --git a/code/api/src/Endpoints/Internal/Account/CreateAccountRoute.cs b/code/api/src/Endpoints/Internal/Account/CreateAccountRoute.cs index 0f4a383..f34056d 100644 --- a/code/api/src/Endpoints/Internal/Account/CreateAccountRoute.cs +++ b/code/api/src/Endpoints/Internal/Account/CreateAccountRoute.cs @@ -1,44 +1,42 @@ namespace IOL.GreatOffice.Api.Endpoints.Internal.Account; -/// <inheritdoc /> public class CreateAccountRoute : RouteBaseAsync.WithRequest<CreateAccountPayload>.WithActionResult { - private readonly AppDbContext _context; - private readonly UserService _userService; + private readonly MainAppDatabase _database; + private readonly UserService _userService; - /// <inheritdoc /> - public CreateAccountRoute(UserService userService, AppDbContext context) { - _userService = userService; - _context = context; - } + public CreateAccountRoute(UserService userService, MainAppDatabase database) { + _userService = userService; + _database = database; + } - /// <summary> - /// Create a new user account. - /// </summary> - /// <param name="request"></param> - /// <param name="cancellationToken"></param> - /// <returns></returns> - [AllowAnonymous] - [HttpPost("~/_/account/create")] - public override async Task<ActionResult> HandleAsync(CreateAccountPayload request, CancellationToken cancellationToken = default) { - if (request.Username.IsValidEmailAddress() == false) { - return BadRequest(new KnownProblemModel("Invalid form", request.Username + " does not look like a valid email")); - } + /// <summary> + /// Create a new user account. + /// </summary> + /// <param name="request"></param> + /// <param name="cancellationToken"></param> + /// <returns></returns> + [AllowAnonymous] + [HttpPost("~/_/account/create")] + public override async Task<ActionResult> HandleAsync(CreateAccountPayload request, CancellationToken cancellationToken = default) { + if (request.Username.IsValidEmailAddress() == false) { + return BadRequest(new KnownProblemModel("Invalid form", request.Username + " does not look like a valid email")); + } - if (request.Password.Length < 6) { - return BadRequest(new KnownProblemModel("Invalid form", "The password requires 6 or more characters.")); - } + if (request.Password.Length < 6) { + return BadRequest(new KnownProblemModel("Invalid form", "The password requires 6 or more characters.")); + } - var username = request.Username.Trim(); - if (_context.Users.Any(c => c.Username == username)) { - return BadRequest(new KnownProblemModel("Username is not available", "There is already a user registered with email: " + username)); - } + var username = request.Username.Trim(); + if (_database.Users.Any(c => c.Username == username)) { + return BadRequest(new KnownProblemModel("Username is not available", "There is already a user registered with email: " + username)); + } - var user = new User(username); - user.HashAndSetPassword(request.Password); - _context.Users.Add(user); - await _context.SaveChangesAsync(cancellationToken); - await _userService.LogInUser(HttpContext, user); - return Ok(); - } -} + var user = new User(username); + user.HashAndSetPassword(request.Password); + _database.Users.Add(user); + await _database.SaveChangesAsync(cancellationToken); + await _userService.LogInUser(HttpContext, user); + return Ok(); + } +}
\ No newline at end of file diff --git a/code/api/src/Endpoints/Internal/Account/CreateInitialAccountRoute.cs b/code/api/src/Endpoints/Internal/Account/CreateInitialAccountRoute.cs index 13fbdf4..56ff9c6 100644 --- a/code/api/src/Endpoints/Internal/Account/CreateInitialAccountRoute.cs +++ b/code/api/src/Endpoints/Internal/Account/CreateInitialAccountRoute.cs @@ -1,34 +1,32 @@ namespace IOL.GreatOffice.Api.Endpoints.Internal.Account; -/// <inheritdoc /> public class CreateInitialAccountRoute : RouteBaseAsync.WithoutRequest.WithActionResult { - private readonly AppDbContext _context; - private readonly UserService _userService; + private readonly MainAppDatabase _database; + private readonly UserService _userService; - /// <inheritdoc /> - public CreateInitialAccountRoute(AppDbContext context, UserService userService) { - _context = context; - _userService = userService; - } + public CreateInitialAccountRoute(MainAppDatabase database, UserService userService) { + _database = database; + _userService = userService; + } - /// <summary> - /// Create an initial user account. - /// </summary> - /// <param name="cancellationToken"></param> - /// <returns></returns> - [AllowAnonymous] - [HttpGet("~/_/account/create-initial")] - public override async Task<ActionResult> HandleAsync(CancellationToken cancellationToken = default) { - if (_context.Users.Any()) { - return NotFound(); - } + /// <summary> + /// Create an initial user account. + /// </summary> + /// <param name="cancellationToken"></param> + /// <returns></returns> + [AllowAnonymous] + [HttpGet("~/_/account/create-initial")] + public override async Task<ActionResult> HandleAsync(CancellationToken cancellationToken = default) { + if (_database.Users.Any()) { + return NotFound(); + } - var user = new User("admin@ivarlovlie.no"); - user.HashAndSetPassword("ivar123"); - _context.Users.Add(user); - await _context.SaveChangesAsync(cancellationToken); - await _userService.LogInUser(HttpContext, user); - return Redirect("/"); - } -} + var user = new User("admin@ivarlovlie.no"); + user.HashAndSetPassword("ivar123"); + _database.Users.Add(user); + await _database.SaveChangesAsync(cancellationToken); + await _userService.LogInUser(HttpContext, user); + return Redirect("/"); + } +}
\ No newline at end of file diff --git a/code/api/src/Endpoints/Internal/Account/DeleteAccountRoute.cs b/code/api/src/Endpoints/Internal/Account/DeleteAccountRoute.cs index 2149e15..5df1fb6 100644 --- a/code/api/src/Endpoints/Internal/Account/DeleteAccountRoute.cs +++ b/code/api/src/Endpoints/Internal/Account/DeleteAccountRoute.cs @@ -2,48 +2,47 @@ namespace IOL.GreatOffice.Api.Endpoints.Internal.Account; public class DeleteAccountRoute : RouteBaseAsync.WithoutRequest.WithActionResult { - private readonly AppDbContext _context; - private readonly UserService _userService; + private readonly MainAppDatabase _database; + private readonly UserService _userService; - /// <inheritdoc /> - public DeleteAccountRoute(AppDbContext context, UserService userService) { - _context = context; - _userService = userService; - } + public DeleteAccountRoute(MainAppDatabase database, UserService userService) { + _database = database; + _userService = userService; + } - /// <summary> - /// Delete the logged on user's account. - /// </summary> - /// <param name="cancellationToken"></param> - /// <returns></returns> - [HttpDelete("~/_/account/delete")] - public override async Task<ActionResult> HandleAsync(CancellationToken cancellationToken = default) { - var user = _context.Users.SingleOrDefault(c => c.Id == LoggedInUser.Id); - if (user == default) { - await _userService.LogOutUser(HttpContext); - return Unauthorized(); - } + /// <summary> + /// Delete the logged on user's account. + /// </summary> + /// <param name="cancellationToken"></param> + /// <returns></returns> + [HttpDelete("~/_/account/delete")] + public override async Task<ActionResult> HandleAsync(CancellationToken cancellationToken = default) { + var user = _database.Users.SingleOrDefault(c => c.Id == LoggedInUser.Id); + if (user == default) { + await _userService.LogOutUser(HttpContext); + return Unauthorized(); + } - if (user.Username == "demo@demo.demo") { - await _userService.LogOutUser(HttpContext); - return Ok(); - } + if (user.Username == "demo@demo.demo") { + await _userService.LogOutUser(HttpContext); + return Ok(); + } - var githubMappings = _context.TimeCategories.Where(c => c.UserId == user.Id); - var passwordResets = _context.ForgotPasswordRequests.Where(c => c.UserId == user.Id); - var entries = _context.TimeEntries.Where(c => c.UserId == user.Id); - var labels = _context.TimeLabels.Where(c => c.UserId == user.Id); - var categories = _context.TimeCategories.Where(c => c.UserId == user.Id); + var githubMappings = _database.TimeCategories.Where(c => c.UserId == user.Id); + var passwordResets = _database.ForgotPasswordRequests.Where(c => c.UserId == user.Id); + var entries = _database.TimeEntries.Where(c => c.UserId == user.Id); + var labels = _database.TimeLabels.Where(c => c.UserId == user.Id); + var categories = _database.TimeCategories.Where(c => c.UserId == user.Id); - _context.TimeCategories.RemoveRange(githubMappings); - _context.ForgotPasswordRequests.RemoveRange(passwordResets); - _context.TimeEntries.RemoveRange(entries); - _context.TimeLabels.RemoveRange(labels); - _context.TimeCategories.RemoveRange(categories); - _context.Users.Remove(user); + _database.TimeCategories.RemoveRange(githubMappings); + _database.ForgotPasswordRequests.RemoveRange(passwordResets); + _database.TimeEntries.RemoveRange(entries); + _database.TimeLabels.RemoveRange(labels); + _database.TimeCategories.RemoveRange(categories); + _database.Users.Remove(user); - await _context.SaveChangesAsync(cancellationToken); - await _userService.LogOutUser(HttpContext); - return Ok(); - } -} + await _database.SaveChangesAsync(cancellationToken); + await _userService.LogOutUser(HttpContext); + return Ok(); + } +}
\ No newline at end of file diff --git a/code/api/src/Endpoints/Internal/Account/GetArchiveRoute.cs b/code/api/src/Endpoints/Internal/Account/GetArchiveRoute.cs index f1b70f3..0d9f817 100644 --- a/code/api/src/Endpoints/Internal/Account/GetArchiveRoute.cs +++ b/code/api/src/Endpoints/Internal/Account/GetArchiveRoute.cs @@ -2,61 +2,60 @@ namespace IOL.GreatOffice.Api.Endpoints.Internal.Account; public class GetAccountArchiveRoute : RouteBaseAsync.WithoutRequest.WithActionResult<UserArchiveDto> { - private readonly AppDbContext _context; + private readonly MainAppDatabase _database; - /// <inheritdoc /> - public GetAccountArchiveRoute(AppDbContext context) { - _context = context; - } + public GetAccountArchiveRoute(MainAppDatabase database) { + _database = database; + } - /// <summary> - /// Get a data archive with the currently logged on user's data. - /// </summary> - /// <param name="cancellationToken"></param> - /// <returns></returns> - [HttpGet("~/_/account/archive")] - public override async Task<ActionResult<UserArchiveDto>> HandleAsync(CancellationToken cancellationToken = default) { - var user = _context.Users.SingleOrDefault(c => c.Id == LoggedInUser.Id); - if (user == default) { - await HttpContext.SignOutAsync(); - return Unauthorized(); - } + /// <summary> + /// Get a data archive with the currently logged on user's data. + /// </summary> + /// <param name="cancellationToken"></param> + /// <returns></returns> + [HttpGet("~/_/account/archive")] + public override async Task<ActionResult<UserArchiveDto>> HandleAsync(CancellationToken cancellationToken = default) { + var user = _database.Users.SingleOrDefault(c => c.Id == LoggedInUser.Id); + if (user == default) { + await HttpContext.SignOutAsync(); + return Unauthorized(); + } - var entries = _context.TimeEntries - .AsNoTracking() - .Include(c => c.Labels) - .Include(c => c.Category) - .Where(c => c.UserId == user.Id) - .ToList(); + var entries = _database.TimeEntries + .AsNoTracking() + .Include(c => c.Labels) + .Include(c => c.Category) + .Where(c => c.UserId == user.Id) + .ToList(); - var jsonOptions = new JsonSerializerOptions { - WriteIndented = true - }; + var jsonOptions = new JsonSerializerOptions { + WriteIndented = true + }; - var dto = new UserArchiveDto(user); - dto.Entries.AddRange(entries.Select(entry => new UserArchiveDto.EntryDto { - CreatedAt = entry.CreatedAt.ToString("yyyy-MM-ddTHH:mm:ssZ"), - StartDateTime = entry.Start, - StopDateTime = entry.Stop, - Description = entry.Description, - Labels = entry.Labels - .Select(c => new UserArchiveDto.LabelDto { - Name = c.Name, - Color = c.Color - }) - .ToList(), - Category = new UserArchiveDto.CategoryDto { - Name = entry.Category.Name, - Color = entry.Category.Color - }, - })); + var dto = new UserArchiveDto(user); + dto.Entries.AddRange(entries.Select(entry => new UserArchiveDto.EntryDto { + CreatedAt = entry.CreatedAt.ToString("yyyy-MM-ddTHH:mm:ssZ"), + StartDateTime = entry.Start, + StopDateTime = entry.Stop, + Description = entry.Description, + Labels = entry.Labels + .Select(c => new UserArchiveDto.LabelDto { + Name = c.Name, + Color = c.Color + }) + .ToList(), + Category = new UserArchiveDto.CategoryDto { + Name = entry.Category.Name, + Color = entry.Category.Color + }, + })); - dto.CountEntries(); + dto.CountEntries(); - var entriesSerialized = JsonSerializer.SerializeToUtf8Bytes(dto, jsonOptions); + var entriesSerialized = JsonSerializer.SerializeToUtf8Bytes(dto, jsonOptions); - return File(entriesSerialized, - "application/json", - user.Username + "-time-tracker-archive-" + AppDateTime.UtcNow.ToString("yyyyMMddTHHmmss") + ".json"); - } -} + return File(entriesSerialized, + "application/json", + user.Username + "-time-tracker-archive-" + AppDateTime.UtcNow.ToString("yyyyMMddTHHmmss") + ".json"); + } +}
\ No newline at end of file diff --git a/code/api/src/Endpoints/Internal/Account/GetRoute.cs b/code/api/src/Endpoints/Internal/Account/GetRoute.cs index 1aa7ecb..8d6c50f 100644 --- a/code/api/src/Endpoints/Internal/Account/GetRoute.cs +++ b/code/api/src/Endpoints/Internal/Account/GetRoute.cs @@ -2,10 +2,10 @@ namespace IOL.GreatOffice.Api.Endpoints.Internal.Account; public class GetAccountRoute : RouteBaseAsync.WithoutRequest.WithActionResult<LoggedInUserModel> { - private readonly AppDbContext _context; + private readonly MainAppDatabase _database; - public GetAccountRoute(AppDbContext context) { - _context = context; + public GetAccountRoute(MainAppDatabase database) { + _database = database; } /// <summary> @@ -15,7 +15,7 @@ public class GetAccountRoute : RouteBaseAsync.WithoutRequest.WithActionResult<Lo /// <returns></returns> [HttpGet("~/_/account")] public override async Task<ActionResult<LoggedInUserModel>> HandleAsync(CancellationToken cancellationToken = default) { - var user = _context.Users + var user = _database.Users .Select(x => new {x.Username, x.Id}) .SingleOrDefault(c => c.Id == LoggedInUser.Id); if (user != default) { diff --git a/code/api/src/Endpoints/Internal/Account/LoginRoute.cs b/code/api/src/Endpoints/Internal/Account/LoginRoute.cs index e4ef54c..696c3c2 100644 --- a/code/api/src/Endpoints/Internal/Account/LoginRoute.cs +++ b/code/api/src/Endpoints/Internal/Account/LoginRoute.cs @@ -1,37 +1,34 @@ namespace IOL.GreatOffice.Api.Endpoints.Internal.Account; -public class LoginRoute : RouteBaseAsync - .WithRequest<LoginPayload> - .WithActionResult +public class LoginRoute : RouteBaseAsync.WithRequest<LoginPayload>.WithActionResult { - private readonly AppDbContext _context; - private readonly UserService _userService; + private readonly MainAppDatabase _database; + private readonly UserService _userService; - /// <inheritdoc /> - public LoginRoute(AppDbContext context, UserService userService) { - _context = context; - _userService = userService; - } + public LoginRoute(MainAppDatabase database, UserService userService) { + _database = database; + _userService = userService; + } - /// <summary> - /// Login a user. - /// </summary> - /// <param name="request"></param> - /// <param name="cancellationToken"></param> - /// <returns></returns> - [AllowAnonymous] - [HttpPost("~/_/account/login")] - public override async Task<ActionResult> HandleAsync(LoginPayload request, CancellationToken cancellationToken = default) { - if (!ModelState.IsValid) { - return BadRequest(ModelState); - } + /// <summary> + /// Login a user. + /// </summary> + /// <param name="request"></param> + /// <param name="cancellationToken"></param> + /// <returns></returns> + [AllowAnonymous] + [HttpPost("~/_/account/login")] + public override async Task<ActionResult> HandleAsync(LoginPayload request, CancellationToken cancellationToken = default) { + if (!ModelState.IsValid) { + return BadRequest(ModelState); + } - var user = _context.Users.SingleOrDefault(u => u.Username == request.Username); - if (user == default || !user.VerifyPassword(request.Password)) { - return BadRequest(new KnownProblemModel("Invalid username or password")); - } + var user = _database.Users.SingleOrDefault(u => u.Username == request.Username); + if (user == default || !user.VerifyPassword(request.Password)) { + return BadRequest(new KnownProblemModel("Invalid username or password")); + } - await _userService.LogInUser(HttpContext, user, request.Persist); - return Ok(); - } -} + await _userService.LogInUser(HttpContext, user, request.Persist); + return Ok(); + } +}
\ No newline at end of file diff --git a/code/api/src/Endpoints/Internal/Account/LogoutRoute.cs b/code/api/src/Endpoints/Internal/Account/LogoutRoute.cs index 4a06f4a..042d729 100644 --- a/code/api/src/Endpoints/Internal/Account/LogoutRoute.cs +++ b/code/api/src/Endpoints/Internal/Account/LogoutRoute.cs @@ -2,21 +2,21 @@ namespace IOL.GreatOffice.Api.Endpoints.Internal.Account; public class LogoutRoute : RouteBaseAsync.WithoutRequest.WithActionResult { - private readonly UserService _userService; + private readonly UserService _userService; - public LogoutRoute(UserService userService) { - _userService = userService; - } + public LogoutRoute(UserService userService) { + _userService = userService; + } - /// <summary> - /// Logout a user. - /// </summary> - /// <param name="cancellationToken"></param> - /// <returns></returns> - [AllowAnonymous] - [HttpGet("~/_/account/logout")] - public override async Task<ActionResult> HandleAsync(CancellationToken cancellationToken = default) { - await _userService.LogOutUser(HttpContext); - return Ok(); - } -} + /// <summary> + /// Logout a user. + /// </summary> + /// <param name="cancellationToken"></param> + /// <returns></returns> + [AllowAnonymous] + [HttpGet("~/_/account/logout")] + public override async Task<ActionResult> HandleAsync(CancellationToken cancellationToken = default) { + await _userService.LogOutUser(HttpContext); + return Ok(); + } +}
\ No newline at end of file diff --git a/code/api/src/Endpoints/Internal/Account/UpdateAccountRoute.cs b/code/api/src/Endpoints/Internal/Account/UpdateAccountRoute.cs index 31ff10b..02dc3f1 100644 --- a/code/api/src/Endpoints/Internal/Account/UpdateAccountRoute.cs +++ b/code/api/src/Endpoints/Internal/Account/UpdateAccountRoute.cs @@ -2,50 +2,49 @@ namespace IOL.GreatOffice.Api.Endpoints.Internal.Account; public class UpdateAccountRoute : RouteBaseAsync.WithRequest<UpdatePayload>.WithActionResult { - private readonly AppDbContext _context; + private readonly MainAppDatabase _database; - /// <inheritdoc /> - public UpdateAccountRoute(AppDbContext context) { - _context = context; - } + public UpdateAccountRoute(MainAppDatabase database) { + _database = database; + } - /// <summary> - /// Update the logged on user's data. - /// </summary> - /// <param name="request"></param> - /// <param name="cancellationToken"></param> - /// <returns></returns> - [HttpPost("~/_/account/update")] - public override async Task<ActionResult> HandleAsync(UpdatePayload request, CancellationToken cancellationToken = default) { - var user = _context.Users.SingleOrDefault(c => c.Id == LoggedInUser.Id); - if (user == default) { - await HttpContext.SignOutAsync(); - return Unauthorized(); - } + /// <summary> + /// Update the logged on user's data. + /// </summary> + /// <param name="request"></param> + /// <param name="cancellationToken"></param> + /// <returns></returns> + [HttpPost("~/_/account/update")] + public override async Task<ActionResult> HandleAsync(UpdatePayload request, CancellationToken cancellationToken = default) { + var user = _database.Users.SingleOrDefault(c => c.Id == LoggedInUser.Id); + if (user == default) { + await HttpContext.SignOutAsync(); + return Unauthorized(); + } - if (request.Password.IsNullOrWhiteSpace() && request.Username.IsNullOrWhiteSpace()) { - return BadRequest(new KnownProblemModel("Invalid request", "No data was submitted")); - } + if (request.Password.IsNullOrWhiteSpace() && request.Username.IsNullOrWhiteSpace()) { + return BadRequest(new KnownProblemModel("Invalid request", "No data was submitted")); + } - if (request.Password.HasValue() && request.Password.Length < 6) { - return BadRequest(new KnownProblemModel("Invalid request", - "The new password must contain at least 6 characters")); - } + if (request.Password.HasValue() && request.Password.Length < 6) { + return BadRequest(new KnownProblemModel("Invalid request", + "The new password must contain at least 6 characters")); + } - if (request.Password.HasValue()) { - user.HashAndSetPassword(request.Password); - } + if (request.Password.HasValue()) { + user.HashAndSetPassword(request.Password); + } - if (request.Username.HasValue() && !request.Username.IsValidEmailAddress()) { - return BadRequest(new KnownProblemModel("Invalid request", - "The new username does not look like a valid email address")); - } + if (request.Username.HasValue() && !request.Username.IsValidEmailAddress()) { + return BadRequest(new KnownProblemModel("Invalid request", + "The new username does not look like a valid email address")); + } - if (request.Username.HasValue()) { - user.Username = request.Username.Trim(); - } + if (request.Username.HasValue()) { + user.Username = request.Username.Trim(); + } - await _context.SaveChangesAsync(cancellationToken); - return Ok(); - } -} + await _database.SaveChangesAsync(cancellationToken); + return Ok(); + } +}
\ No newline at end of file diff --git a/code/api/src/Endpoints/Internal/PasswordResetRequests/Create/RequestModel.cs b/code/api/src/Endpoints/Internal/PasswordResetRequests/CreateResetRequestPayload.cs index 236c650..1adb344 100644 --- a/code/api/src/Endpoints/Internal/PasswordResetRequests/Create/RequestModel.cs +++ b/code/api/src/Endpoints/Internal/PasswordResetRequests/CreateResetRequestPayload.cs @@ -1,6 +1,6 @@ -namespace IOL.GreatOffice.Api.Endpoints.Internal.PasswordResetRequests.Create; +namespace IOL.GreatOffice.Api.Endpoints.Internal.PasswordResetRequests; -public class RequestModel +public class CreateResetRequestPayload { public string Username { get; set; } }
\ No newline at end of file diff --git a/code/api/src/Endpoints/Internal/PasswordResetRequests/Create/Route.cs b/code/api/src/Endpoints/Internal/PasswordResetRequests/CreateResetRequestRoute.cs index f837fc0..bb72d38 100644 --- a/code/api/src/Endpoints/Internal/PasswordResetRequests/Create/Route.cs +++ b/code/api/src/Endpoints/Internal/PasswordResetRequests/CreateResetRequestRoute.cs @@ -1,15 +1,15 @@ -namespace IOL.GreatOffice.Api.Endpoints.Internal.PasswordResetRequests.Create; +namespace IOL.GreatOffice.Api.Endpoints.Internal.PasswordResetRequests; -public class Route : RouteBaseAsync.WithRequest<RequestModel>.WithActionResult +public class Route : RouteBaseAsync.WithRequest<CreateResetRequestPayload>.WithActionResult { private readonly ILogger<Route> _logger; private readonly PasswordResetService _passwordResetService; - private readonly AppDbContext _context; - - public Route(ILogger<Route> logger, PasswordResetService passwordResetService, AppDbContext context) { + private readonly MainAppDatabase _database; + + public Route(ILogger<Route> logger, PasswordResetService passwordResetService, MainAppDatabase database) { _logger = logger; _passwordResetService = passwordResetService; - _context = context; + _database = database; } /// <summary> @@ -20,30 +20,19 @@ public class Route : RouteBaseAsync.WithRequest<RequestModel>.WithActionResult /// <returns></returns> [AllowAnonymous] [HttpPost("~/_/password-reset-request/create")] - public override async Task<ActionResult> HandleAsync(RequestModel request, CancellationToken cancellationToken = default) { + public override async Task<ActionResult> HandleAsync(CreateResetRequestPayload request, CancellationToken cancellationToken = default) { if (!request.Username.IsValidEmailAddress()) { _logger.LogInformation("Username is invalid, not doing request for password change"); return KnownProblem("Invalid email address", request.Username + " looks like an invalid email address"); } - Request.Headers.TryGetValue(AppHeaders.BROWSER_TIME_ZONE, out var timeZoneHeader); - var tz = TimeZoneInfo.FindSystemTimeZoneById(timeZoneHeader.ToString().HasValue() ? timeZoneHeader.ToString() : "UTC"); - var offset = tz.BaseUtcOffset.Hours; - - // this is fine as long as the client is not connecting from Australia: Lord Howe Island - // according to https://en.wikipedia.org/wiki/Daylight_saving_time_by_country - if (tz.IsDaylightSavingTime(AppDateTime.UtcNow)) { - offset++; - } - - _logger.LogInformation("Request time zone (" + tz.Id + ") offset is: " + offset + " hours"); - var requestDateTime = TimeZoneInfo.ConvertTimeFromUtc(AppDateTime.UtcNow, tz); - _logger.LogInformation("Creating forgot password request with date time: " + requestDateTime.ToString("u")); + var tz = GetRequestTimeZone(_logger); + _logger.LogInformation("Creating forgot password request with local date time: " + tz.LocalDateTime.ToString("u")); try { - var user = _context.Users.SingleOrDefault(c => c.Username.Equals(request.Username)); + var user = _database.Users.SingleOrDefault(c => c.Username.Equals(request.Username)); if (user != default) { - await _passwordResetService.AddRequestAsync(user, tz, cancellationToken); + await _passwordResetService.AddRequestAsync(user, tz.TimeZoneInfo, cancellationToken); return Ok(); } diff --git a/code/api/src/Endpoints/Internal/PasswordResetRequests/FulfillResetRequestRoute.cs b/code/api/src/Endpoints/Internal/PasswordResetRequests/FulfillResetRequestRoute.cs index a0ad4d0..6f71b2f 100644 --- a/code/api/src/Endpoints/Internal/PasswordResetRequests/FulfillResetRequestRoute.cs +++ b/code/api/src/Endpoints/Internal/PasswordResetRequests/FulfillResetRequestRoute.cs @@ -1,34 +1,31 @@ - namespace IOL.GreatOffice.Api.Endpoints.Internal.PasswordResetRequests; -/// <inheritdoc /> public class FulfillResetRequestRoute : RouteBaseAsync.WithRequest<FulfillResetRequestPayload>.WithActionResult { - private readonly PasswordResetService _passwordResetService; + private readonly PasswordResetService _passwordResetService; - /// <inheritdoc /> - public FulfillResetRequestRoute(PasswordResetService passwordResetService) { - _passwordResetService = passwordResetService; - } + public FulfillResetRequestRoute(PasswordResetService passwordResetService) { + _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(); - } + /// <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; - } - } -} + throw; + } + } +}
\ No newline at end of file diff --git a/code/api/src/Endpoints/Internal/PasswordResetRequests/IsResetRequestValidRoute.cs b/code/api/src/Endpoints/Internal/PasswordResetRequests/IsResetRequestValidRoute.cs index 917c4f0..687cef6 100644 --- a/code/api/src/Endpoints/Internal/PasswordResetRequests/IsResetRequestValidRoute.cs +++ b/code/api/src/Endpoints/Internal/PasswordResetRequests/IsResetRequestValidRoute.cs @@ -1,29 +1,27 @@ namespace IOL.GreatOffice.Api.Endpoints.Internal.PasswordResetRequests; -/// <inheritdoc /> public class IsResetRequestValidRoute : RouteBaseAsync.WithRequest<Guid>.WithActionResult { - private readonly PasswordResetService _passwordResetService; + private readonly PasswordResetService _passwordResetService; - /// <inheritdoc /> - public IsResetRequestValidRoute(PasswordResetService passwordResetService) { - _passwordResetService = passwordResetService; - } + public IsResetRequestValidRoute(PasswordResetService passwordResetService) { + _passwordResetService = passwordResetService; + } - /// <summary> - /// Check if a given password reset request is still valid. - /// </summary> - /// <param name="id"></param> - /// <param name="cancellationToken"></param> - /// <returns></returns> - [AllowAnonymous] - [HttpGet("~/_/password-reset-request/is-valid")] - public override async Task<ActionResult> HandleAsync(Guid id, CancellationToken cancellationToken = default) { - var request = await _passwordResetService.GetRequestAsync(id, cancellationToken); - if (request == default) { - return NotFound(); - } + /// <summary> + /// Check if a given password reset request is still valid. + /// </summary> + /// <param name="id"></param> + /// <param name="cancellationToken"></param> + /// <returns></returns> + [AllowAnonymous] + [HttpGet("~/_/password-reset-request/is-valid")] + public override async Task<ActionResult> HandleAsync(Guid id, CancellationToken cancellationToken = default) { + var request = await _passwordResetService.GetRequestAsync(id, cancellationToken); + if (request == default) { + return NotFound(); + } - return Ok(request.IsExpired == false); - } -} + return Ok(request.IsExpired == false); + } +}
\ No newline at end of file diff --git a/code/api/src/Endpoints/Internal/Root/GetApplicationVersionRoute.cs b/code/api/src/Endpoints/Internal/Root/GetApplicationVersionRoute.cs index 5fb8213..34180d1 100644 --- a/code/api/src/Endpoints/Internal/Root/GetApplicationVersionRoute.cs +++ b/code/api/src/Endpoints/Internal/Root/GetApplicationVersionRoute.cs @@ -2,20 +2,19 @@ namespace IOL.GreatOffice.Api.Endpoints.Internal.Root; public class GetApplicationVersionRoute : RouteBaseSync.WithoutRequest.WithActionResult<string> { - private readonly IWebHostEnvironment _environment; + private readonly IWebHostEnvironment _environment; - /// <inheritdoc /> - public GetApplicationVersionRoute(IWebHostEnvironment environment) { - _environment = environment; - } + public GetApplicationVersionRoute(IWebHostEnvironment environment) { + _environment = environment; + } - /// <summary> - /// Get the running api version number. - /// </summary> - /// <returns></returns> - [HttpGet("~/_/version")] - public override ActionResult<string> Handle() { - var versionFilePath = Path.Combine(_environment.WebRootPath, "version.txt"); - return Ok(System.IO.File.ReadAllText(versionFilePath)); - } -} + /// <summary> + /// Get the running api version number. + /// </summary> + /// <returns></returns> + [HttpGet("~/_/version")] + public override ActionResult<string> Handle() { + var versionFilePath = Path.Combine(_environment.WebRootPath, "version.txt"); + return Ok(System.IO.File.ReadAllText(versionFilePath)); + } +}
\ No newline at end of file diff --git a/code/api/src/Endpoints/Internal/Root/LogRoute.cs b/code/api/src/Endpoints/Internal/Root/LogRoute.cs index 48b497a..2c69e94 100644 --- a/code/api/src/Endpoints/Internal/Root/LogRoute.cs +++ b/code/api/src/Endpoints/Internal/Root/LogRoute.cs @@ -2,15 +2,15 @@ namespace IOL.GreatOffice.Api.Endpoints.Internal.Root; public class LogRoute : RouteBaseSync.WithRequest<string>.WithoutResult { - private readonly ILogger<LogRoute> _logger; + private readonly ILogger<LogRoute> _logger; - public LogRoute(ILogger<LogRoute> logger) { - _logger = logger; - } + public LogRoute(ILogger<LogRoute> logger) { + _logger = logger; + } - [AllowAnonymous] - [HttpPost("~/_/log")] - public override void Handle([FromBody] string request) { - _logger.LogInformation(request); - } -} + [AllowAnonymous] + [HttpPost("~/_/log")] + public override void Handle([FromBody] string request) { + _logger.LogInformation(request); + } +}
\ No newline at end of file diff --git a/code/api/src/Endpoints/Internal/Root/ReadConfigurationRoute.cs b/code/api/src/Endpoints/Internal/Root/ReadConfigurationRoute.cs index e0dcca3..7270fd8 100644 --- a/code/api/src/Endpoints/Internal/Root/ReadConfigurationRoute.cs +++ b/code/api/src/Endpoints/Internal/Root/ReadConfigurationRoute.cs @@ -2,16 +2,16 @@ namespace IOL.GreatOffice.Api.Endpoints.Internal.Root; public class ReadConfigurationRoute : RouteBaseSync.WithoutRequest.WithActionResult { - private readonly VaultService _vaultService; + private readonly VaultService _vaultService; - public ReadConfigurationRoute(VaultService vaultService) { - _vaultService = vaultService; - } + public ReadConfigurationRoute(VaultService vaultService) { + _vaultService = vaultService; + } - [AllowAnonymous] - [HttpGet("~/_/configuration")] - public override ActionResult Handle() { - var config = _vaultService.GetCurrentAppConfiguration(); - return Content(JsonSerializer.Serialize(config.GetPublicVersion()), "application/json"); - } -} + [AllowAnonymous] + [HttpGet("~/_/configuration")] + public override ActionResult Handle() { + var config = _vaultService.GetCurrentAppConfiguration(); + return Content(JsonSerializer.Serialize(config.GetPublicVersion()), "application/json"); + } +}
\ No newline at end of file diff --git a/code/api/src/Endpoints/Internal/Root/RefreshConfigurationRoute.cs b/code/api/src/Endpoints/Internal/Root/RefreshConfigurationRoute.cs index 4b1beec..a616c00 100644 --- a/code/api/src/Endpoints/Internal/Root/RefreshConfigurationRoute.cs +++ b/code/api/src/Endpoints/Internal/Root/RefreshConfigurationRoute.cs @@ -2,14 +2,14 @@ namespace IOL.GreatOffice.Api.Endpoints.Internal.Root; public class RefreshConfigurationRoute : RouteBaseSync.WithoutRequest.WithoutResult { - private readonly VaultService _vaultService; + private readonly VaultService _vaultService; - public RefreshConfigurationRoute(VaultService vaultService) { - _vaultService = vaultService; - } + public RefreshConfigurationRoute(VaultService vaultService) { + _vaultService = vaultService; + } - [HttpGet("~/_/refresh-configuration")] - public override void Handle() { - _vaultService.RefreshCurrentAppConfiguration(); - } -} + [HttpGet("~/_/refresh-configuration")] + public override void Handle() { + _vaultService.RefreshCurrentAppConfiguration(); + } +}
\ No newline at end of file diff --git a/code/api/src/Endpoints/V1/ApiTokens/CreateTokenRoute.cs b/code/api/src/Endpoints/V1/ApiTokens/CreateTokenRoute.cs index 60b00ff..6bc2fdc 100644 --- a/code/api/src/Endpoints/V1/ApiTokens/CreateTokenRoute.cs +++ b/code/api/src/Endpoints/V1/ApiTokens/CreateTokenRoute.cs @@ -4,13 +4,12 @@ namespace IOL.GreatOffice.Api.Endpoints.V1.ApiTokens; public class CreateTokenRoute : RouteBaseSync.WithRequest<ApiAccessToken.ApiAccessTokenDto>.WithActionResult { - private readonly AppDbContext _context; + private readonly MainAppDatabase _database; private readonly AppConfiguration _configuration; private readonly ILogger<CreateTokenRoute> _logger; - public CreateTokenRoute(AppDbContext context, VaultService vaultService, ILogger<CreateTokenRoute> logger) - { - _context = context; + public CreateTokenRoute(MainAppDatabase database, VaultService vaultService, ILogger<CreateTokenRoute> logger) { + _database = database; _configuration = vaultService.GetCurrentAppConfiguration(); _logger = logger; } @@ -24,24 +23,19 @@ public class CreateTokenRoute : RouteBaseSync.WithRequest<ApiAccessToken.ApiAcce [HttpPost("~/v{version:apiVersion}/api-tokens/create")] [ProducesResponseType(200, Type = typeof(string))] [ProducesResponseType(404, Type = typeof(KnownProblemModel))] - public override ActionResult Handle(ApiAccessToken.ApiAccessTokenDto request) - { - var user = _context.Users.SingleOrDefault(c => c.Id == LoggedInUser.Id); - if (user == default) - { + public override ActionResult Handle(ApiAccessToken.ApiAccessTokenDto request) { + var user = _database.Users.SingleOrDefault(c => c.Id == LoggedInUser.Id); + if (user == default) { return NotFound(new KnownProblemModel("User does not exist")); } var token_entropy = _configuration.APP_AES_KEY; - if (token_entropy.IsNullOrWhiteSpace()) - { + if (token_entropy.IsNullOrWhiteSpace()) { _logger.LogWarning("No token entropy is available, Basic auth is disabled"); return NotFound(); } - var access_token = new ApiAccessToken() - { - Id = Guid.NewGuid(), + var accessToken = new ApiAccessToken() { User = user, ExpiryDate = request.ExpiryDate.ToUniversalTime(), AllowCreate = request.AllowCreate, @@ -50,8 +44,8 @@ public class CreateTokenRoute : RouteBaseSync.WithRequest<ApiAccessToken.ApiAcce AllowUpdate = request.AllowUpdate }; - _context.AccessTokens.Add(access_token); - _context.SaveChanges(); - return Ok(Convert.ToBase64String(Encoding.UTF8.GetBytes(access_token.Id.ToString().EncryptWithAes(token_entropy)))); + _database.AccessTokens.Add(accessToken); + _database.SaveChanges(); + return Ok(Convert.ToBase64String(Encoding.UTF8.GetBytes(accessToken.Id.ToString().EncryptWithAes(token_entropy)))); } -} +}
\ No newline at end of file diff --git a/code/api/src/Endpoints/V1/ApiTokens/DeleteTokenRoute.cs b/code/api/src/Endpoints/V1/ApiTokens/DeleteTokenRoute.cs index a90b4c0..116a814 100644 --- a/code/api/src/Endpoints/V1/ApiTokens/DeleteTokenRoute.cs +++ b/code/api/src/Endpoints/V1/ApiTokens/DeleteTokenRoute.cs @@ -2,32 +2,32 @@ namespace IOL.GreatOffice.Api.Endpoints.V1.ApiTokens; public class DeleteTokenRoute : RouteBaseSync.WithRequest<Guid>.WithActionResult { - private readonly AppDbContext _context; - private readonly ILogger<DeleteTokenRoute> _logger; + private readonly MainAppDatabase _database; + private readonly ILogger<DeleteTokenRoute> _logger; - public DeleteTokenRoute(AppDbContext context, ILogger<DeleteTokenRoute> logger) { - _context = context; - _logger = logger; - } + public DeleteTokenRoute(MainAppDatabase database, ILogger<DeleteTokenRoute> logger) { + _database = database; + _logger = logger; + } - /// <summary> - /// Delete an api token, rendering it unusable - /// </summary> - /// <param name="id">Id of the token to delete</param> - /// <returns>Nothing</returns> - [ApiVersion(ApiSpecV1.VERSION_STRING)] - [HttpDelete("~/v{version:apiVersion}/api-tokens/delete")] - [ProducesResponseType(200)] - [ProducesResponseType(404)] - public override ActionResult Handle(Guid id) { - var token = _context.AccessTokens.SingleOrDefault(c => c.Id == id); - if (token == default) { - _logger.LogWarning("A deletion request of an already deleted (maybe) api token was received."); - return NotFound(); - } + /// <summary> + /// Delete an api token, rendering it unusable + /// </summary> + /// <param name="id">Id of the token to delete</param> + /// <returns>Nothing</returns> + [ApiVersion(ApiSpecV1.VERSION_STRING)] + [HttpDelete("~/v{version:apiVersion}/api-tokens/delete")] + [ProducesResponseType(200)] + [ProducesResponseType(404)] + public override ActionResult Handle(Guid id) { + var token = _database.AccessTokens.SingleOrDefault(c => c.Id == id); + if (token == default) { + _logger.LogWarning("A deletion request of an already deleted (maybe) api token was received."); + return NotFound(); + } - _context.AccessTokens.Remove(token); - _context.SaveChanges(); - return Ok(); - } -} + _database.AccessTokens.Remove(token); + _database.SaveChanges(); + return Ok(); + } +}
\ No newline at end of file diff --git a/code/api/src/Endpoints/V1/ApiTokens/GetTokensRoute.cs b/code/api/src/Endpoints/V1/ApiTokens/GetTokensRoute.cs index 59fd077..19790e4 100644 --- a/code/api/src/Endpoints/V1/ApiTokens/GetTokensRoute.cs +++ b/code/api/src/Endpoints/V1/ApiTokens/GetTokensRoute.cs @@ -2,21 +2,21 @@ namespace IOL.GreatOffice.Api.Endpoints.V1.ApiTokens; public class GetTokensRoute : RouteBaseSync.WithoutRequest.WithResult<ActionResult<List<ApiAccessToken.ApiAccessTokenDto>>> { - private readonly AppDbContext _context; + private readonly MainAppDatabase _database; - public GetTokensRoute(AppDbContext context) { - _context = context; - } + public GetTokensRoute(MainAppDatabase database) { + _database = database; + } - /// <summary> - /// Get all tokens, both active and inactive. - /// </summary> - /// <returns>A list of tokens</returns> - [ApiVersion(ApiSpecV1.VERSION_STRING)] - [HttpGet("~/v{version:apiVersion}/api-tokens")] - [ProducesResponseType(200, Type = typeof(List<ApiAccessToken.ApiAccessTokenDto>))] - [ProducesResponseType(204)] - public override ActionResult<List<ApiAccessToken.ApiAccessTokenDto>> Handle() { - return Ok(_context.AccessTokens.Where(c => c.User.Id == LoggedInUser.Id).Select(c => c.AsDto)); - } -} + /// <summary> + /// Get all tokens, both active and inactive. + /// </summary> + /// <returns>A list of tokens</returns> + [ApiVersion(ApiSpecV1.VERSION_STRING)] + [HttpGet("~/v{version:apiVersion}/api-tokens")] + [ProducesResponseType(200, Type = typeof(List<ApiAccessToken.ApiAccessTokenDto>))] + [ProducesResponseType(204)] + public override ActionResult<List<ApiAccessToken.ApiAccessTokenDto>> Handle() { + return Ok(_database.AccessTokens.Where(c => c.User.Id == LoggedInUser.Id).Select(c => c.AsDto)); + } +}
\ No newline at end of file diff --git a/code/api/src/Endpoints/V1/Categories/CreateCategoryRoute.cs b/code/api/src/Endpoints/V1/Categories/CreateCategoryRoute.cs index fac2b5e..0471637 100644 --- a/code/api/src/Endpoints/V1/Categories/CreateCategoryRoute.cs +++ b/code/api/src/Endpoints/V1/Categories/CreateCategoryRoute.cs @@ -2,42 +2,43 @@ namespace IOL.GreatOffice.Api.Endpoints.V1.Categories; public class CreateCategoryRoute : RouteBaseSync.WithRequest<TimeCategory.TimeCategoryDto>.WithActionResult<TimeCategory.TimeCategoryDto> { - private readonly AppDbContext _context; + private readonly MainAppDatabase _database; - public CreateCategoryRoute(AppDbContext context) { - _context = context; - } + public CreateCategoryRoute(MainAppDatabase database) { + _database = database; + } - /// <summary> - /// Create a new time entry category. - /// </summary> - /// <param name="categoryTimeCategoryDto"></param> - /// <returns></returns> - [ApiVersion(ApiSpecV1.VERSION_STRING)] - [BasicAuthentication(AppConstants.TOKEN_ALLOW_CREATE)] - [HttpPost("~/v{version:apiVersion}/categories/create")] - [ProducesResponseType(200, Type = typeof(TimeCategory.TimeCategoryDto))] - public override ActionResult<TimeCategory.TimeCategoryDto> Handle(TimeCategory.TimeCategoryDto categoryTimeCategoryDto) { - var duplicate = _context.TimeCategories - .Where(c => c.UserId == LoggedInUser.Id) - .Any(c => c.Name.Trim() == categoryTimeCategoryDto.Name.Trim()); - if (duplicate) { - var category = _context.TimeCategories - .Where(c => c.UserId == LoggedInUser.Id) - .SingleOrDefault(c => c.Name.Trim() == categoryTimeCategoryDto.Name.Trim()); - if (category != default) { - return Ok(category.AsDto); - } - } + /// <summary> + /// Create a new time entry category. + /// </summary> + /// <param name="categoryTimeCategoryDto"></param> + /// <returns></returns> + [ApiVersion(ApiSpecV1.VERSION_STRING)] + [BasicAuthentication(AppConstants.TOKEN_ALLOW_CREATE)] + [HttpPost("~/v{version:apiVersion}/categories/create")] + [ProducesResponseType(200, Type = typeof(TimeCategory.TimeCategoryDto))] + public override ActionResult<TimeCategory.TimeCategoryDto> Handle(TimeCategory.TimeCategoryDto categoryTimeCategoryDto) { + var duplicate = _database.TimeCategories + .Where(c => c.UserId == LoggedInUser.Id) + .Any(c => c.Name.Trim() == categoryTimeCategoryDto.Name.Trim()); + if (duplicate) { + var category = _database.TimeCategories + .Where(c => c.UserId == LoggedInUser.Id) + .SingleOrDefault(c => c.Name.Trim() == categoryTimeCategoryDto.Name.Trim()); + if (category != default) { + return Ok(category.AsDto); + } + } - var newCategory = new TimeCategory(LoggedInUser.Id) { - Name = categoryTimeCategoryDto.Name.Trim(), - Color = categoryTimeCategoryDto.Color - }; + var newCategory = new TimeCategory(LoggedInUser) { + Name = categoryTimeCategoryDto.Name.Trim(), + Color = categoryTimeCategoryDto.Color + }; + newCategory.SetOwnerIds(LoggedInUser.Id); - _context.TimeCategories.Add(newCategory); - _context.SaveChanges(); - categoryTimeCategoryDto.Id = newCategory.Id; - return Ok(categoryTimeCategoryDto); - } -} + _database.TimeCategories.Add(newCategory); + _database.SaveChanges(); + categoryTimeCategoryDto.Id = newCategory.Id; + return Ok(categoryTimeCategoryDto); + } +}
\ No newline at end of file diff --git a/code/api/src/Endpoints/V1/Categories/DeleteCategoryRoute.cs b/code/api/src/Endpoints/V1/Categories/DeleteCategoryRoute.cs index 3d438a0..582ec7d 100644 --- a/code/api/src/Endpoints/V1/Categories/DeleteCategoryRoute.cs +++ b/code/api/src/Endpoints/V1/Categories/DeleteCategoryRoute.cs @@ -2,37 +2,37 @@ namespace IOL.GreatOffice.Api.Endpoints.V1.Categories; public class DeleteCategoryRoute : RouteBaseSync.WithRequest<Guid>.WithActionResult { - private readonly AppDbContext _context; + private readonly MainAppDatabase _database; - public DeleteCategoryRoute(AppDbContext context) { - _context = context; - } + public DeleteCategoryRoute(MainAppDatabase database) { + _database = database; + } - /// <summary> - /// Delete a time entry category. - /// </summary> - /// <param name="id"></param> - /// <returns></returns> - [ApiVersion(ApiSpecV1.VERSION_STRING)] - [BasicAuthentication(AppConstants.TOKEN_ALLOW_DELETE)] - [HttpDelete("~/v{version:apiVersion}/categories/{id:guid}/delete")] - [ProducesResponseType(200)] - [ProducesResponseType(404)] - public override ActionResult Handle(Guid id) { - var category = _context.TimeCategories - .Where(c => c.UserId == LoggedInUser.Id) - .SingleOrDefault(c => c.Id == id); + /// <summary> + /// Delete a time entry category. + /// </summary> + /// <param name="id"></param> + /// <returns></returns> + [ApiVersion(ApiSpecV1.VERSION_STRING)] + [BasicAuthentication(AppConstants.TOKEN_ALLOW_DELETE)] + [HttpDelete("~/v{version:apiVersion}/categories/{id:guid}/delete")] + [ProducesResponseType(200)] + [ProducesResponseType(404)] + public override ActionResult Handle(Guid id) { + var category = _database.TimeCategories + .Where(c => c.UserId == LoggedInUser.Id) + .SingleOrDefault(c => c.Id == id); - if (category == default) { - return NotFound(); - } + if (category == default) { + return NotFound(); + } - var entries = _context.TimeEntries - .Include(c => c.Category) - .Where(c => c.Category.Id == category.Id); - _context.TimeEntries.RemoveRange(entries); - _context.TimeCategories.Remove(category); - _context.SaveChanges(); - return Ok(); - } -} + var entries = _database.TimeEntries + .Include(c => c.Category) + .Where(c => c.Category.Id == category.Id); + _database.TimeEntries.RemoveRange(entries); + _database.TimeCategories.Remove(category); + _database.SaveChanges(); + return Ok(); + } +}
\ No newline at end of file diff --git a/code/api/src/Endpoints/V1/Categories/GetCategoriesRoute.cs b/code/api/src/Endpoints/V1/Categories/GetCategoriesRoute.cs index a40a832..937f8e3 100644 --- a/code/api/src/Endpoints/V1/Categories/GetCategoriesRoute.cs +++ b/code/api/src/Endpoints/V1/Categories/GetCategoriesRoute.cs @@ -1,35 +1,33 @@ namespace IOL.GreatOffice.Api.Endpoints.V1.Categories; -/// <inheritdoc /> public class GetCategoriesRoute : RouteBaseSync.WithoutRequest.WithActionResult<List<TimeCategory.TimeCategoryDto>> { - private readonly AppDbContext _context; + private readonly MainAppDatabase _database; - /// <inheritdoc /> - public GetCategoriesRoute(AppDbContext context) { - _context = context; - } + public GetCategoriesRoute(MainAppDatabase database) { + _database = database; + } - /// <summary> - /// Get a minimal list of time entry categories. - /// </summary> - /// <returns></returns> - [ApiVersion(ApiSpecV1.VERSION_STRING)] - [ProducesResponseType(200, Type = typeof(List<TimeCategory.TimeCategoryDto>))] - [ProducesResponseType(204)] - [BasicAuthentication(AppConstants.TOKEN_ALLOW_READ)] - [HttpGet("~/v{version:apiVersion}/categories")] - public override ActionResult<List<TimeCategory.TimeCategoryDto>> Handle() { - var categories = _context.TimeCategories - .Where(c => c.UserId == LoggedInUser.Id) - .OrderByDescending(c => c.CreatedAt) - .Select(c => c.AsDto) - .ToList(); + /// <summary> + /// Get a minimal list of time entry categories. + /// </summary> + /// <returns></returns> + [ApiVersion(ApiSpecV1.VERSION_STRING)] + [ProducesResponseType(200, Type = typeof(List<TimeCategory.TimeCategoryDto>))] + [ProducesResponseType(204)] + [BasicAuthentication(AppConstants.TOKEN_ALLOW_READ)] + [HttpGet("~/v{version:apiVersion}/categories")] + public override ActionResult<List<TimeCategory.TimeCategoryDto>> Handle() { + var categories = _database.TimeCategories + .Where(c => c.UserId == LoggedInUser.Id) + .OrderByDescending(c => c.CreatedAt) + .Select(c => c.AsDto) + .ToList(); - if (categories.Count == 0) { - return NoContent(); - } + if (categories.Count == 0) { + return NoContent(); + } - return Ok(categories); - } -} + return Ok(categories); + } +}
\ No newline at end of file diff --git a/code/api/src/Endpoints/V1/Categories/UpdateCategoryRoute.cs b/code/api/src/Endpoints/V1/Categories/UpdateCategoryRoute.cs index ca7dfdf..096132d 100644 --- a/code/api/src/Endpoints/V1/Categories/UpdateCategoryRoute.cs +++ b/code/api/src/Endpoints/V1/Categories/UpdateCategoryRoute.cs @@ -2,38 +2,38 @@ namespace IOL.GreatOffice.Api.Endpoints.V1.Categories; public class UpdateCategoryRoute : RouteBaseSync.WithRequest<TimeCategory.TimeCategoryDto>.WithActionResult { - private readonly AppDbContext _context; + private readonly MainAppDatabase _database; - public UpdateCategoryRoute(AppDbContext context) { - _context = context; - } + public UpdateCategoryRoute(MainAppDatabase database) { + _database = database; + } - /// <summary> - /// Update a time entry category. - /// </summary> - /// <param name="categoryTimeCategoryDto"></param> - /// <returns></returns> - [ApiVersion(ApiSpecV1.VERSION_STRING)] - [BasicAuthentication(AppConstants.TOKEN_ALLOW_UPDATE)] - [HttpPost("~/v{version:apiVersion}/categories/update")] - [ProducesResponseType(200)] - [ProducesResponseType(404)] - [ProducesResponseType(403)] - public override ActionResult Handle(TimeCategory.TimeCategoryDto categoryTimeCategoryDto) { - var category = _context.TimeCategories - .Where(c => c.UserId == LoggedInUser.Id) - .SingleOrDefault(c => c.Id == categoryTimeCategoryDto.Id); - if (category == default) { - return NotFound(); - } + /// <summary> + /// Update a time entry category. + /// </summary> + /// <param name="categoryTimeCategoryDto"></param> + /// <returns></returns> + [ApiVersion(ApiSpecV1.VERSION_STRING)] + [BasicAuthentication(AppConstants.TOKEN_ALLOW_UPDATE)] + [HttpPost("~/v{version:apiVersion}/categories/update")] + [ProducesResponseType(200)] + [ProducesResponseType(404)] + [ProducesResponseType(403)] + public override ActionResult Handle(TimeCategory.TimeCategoryDto categoryTimeCategoryDto) { + var category = _database.TimeCategories + .Where(c => c.UserId == LoggedInUser.Id) + .SingleOrDefault(c => c.Id == categoryTimeCategoryDto.Id); + if (category == default) { + return NotFound(); + } - if (LoggedInUser.Id != category.UserId) { - return Forbid(); - } + if (LoggedInUser.Id != category.UserId) { + return Forbid(); + } - category.Name = categoryTimeCategoryDto.Name; - category.Color = categoryTimeCategoryDto.Color; - _context.SaveChanges(); - return Ok(); - } -} + category.Name = categoryTimeCategoryDto.Name; + category.Color = categoryTimeCategoryDto.Color; + _database.SaveChanges(); + return Ok(); + } +}
\ No newline at end of file diff --git a/code/api/src/Endpoints/V1/Customers/CreateCustomerRoute.cs b/code/api/src/Endpoints/V1/Customers/CreateCustomerRoute.cs new file mode 100644 index 0000000..eb69f7f --- /dev/null +++ b/code/api/src/Endpoints/V1/Customers/CreateCustomerRoute.cs @@ -0,0 +1,63 @@ +using Microsoft.Extensions.Localization; + +namespace IOL.GreatOffice.Api.Endpoints.V1.Customers; + +public class CreateCustomerRoute : RouteBaseAsync.WithRequest<CreateCustomerPayload>.WithActionResult +{ + private readonly MainAppDatabase _database; + private readonly ILogger<CreateCustomerRoute> _logger; + private readonly IStringLocalizer<SharedResources> _localizer; + + public CreateCustomerRoute(MainAppDatabase database, ILogger<CreateCustomerRoute> logger, IStringLocalizer<SharedResources> localizer) { + _database = database; + _logger = logger; + _localizer = localizer; + } + + [HttpPost("~/v{version:apiVersion}/customers/create")] + public override async Task<ActionResult> HandleAsync(CreateCustomerPayload request, CancellationToken cancellationToken = default) { + var errors = new Dictionary<string, string>(); + if (request.Name.Trim().IsNullOrEmpty()) errors.Add("name", _localizer["Name is a required field"]); + if (errors.Any()) return KnownProblem(_localizer["Invalid form"], _localizer["One or more fields is invalid"], errors); + var customer = new Customer(LoggedInUser) { + CustomerNumber = request.CustomerNumber, + Name = request.Name, + Description = request.Description, + Address1 = request.Address1, + Address2 = request.Address2, + Country = request.Country, + Currency = request.Currency, + Email = request.Email, + Phone = request.Phone, + PostalCity = request.PostalCity, + PostalCode = request.PostalCode, + VATNumber = request.VATNumber, + ORGNumber = request.ORGNumber, + DefaultReference = request.DefaultReference, + Website = request.Website + }; + customer.SetOwnerIds(default, LoggedInUser.TenantId); + _database.Customers.Add(customer); + await _database.SaveChangesAsync(cancellationToken); + return Ok(); + } +} + +public class CreateCustomerPayload +{ + public string CustomerNumber { get; set; } + public string Name { get; set; } + public string Description { get; set; } + public string Address1 { get; set; } + public string Address2 { get; set; } + public string PostalCode { get; set; } + public string PostalCity { get; set; } + public string Country { get; set; } + public string Phone { get; set; } + public string Email { get; set; } + public string VATNumber { get; set; } + public string ORGNumber { get; set; } + public string DefaultReference { get; set; } + public string Website { get; set; } + public string Currency { get; set; } +}
\ No newline at end of file diff --git a/code/api/src/Endpoints/V1/Entries/CreateEntryRoute.cs b/code/api/src/Endpoints/V1/Entries/CreateEntryRoute.cs index 854ff59..45d8f32 100644 --- a/code/api/src/Endpoints/V1/Entries/CreateEntryRoute.cs +++ b/code/api/src/Endpoints/V1/Entries/CreateEntryRoute.cs @@ -2,65 +2,66 @@ namespace IOL.GreatOffice.Api.Endpoints.V1.Entries; public class CreateEntryRoute : RouteBaseSync.WithRequest<TimeEntry.TimeEntryDto>.WithActionResult<TimeEntry.TimeEntryDto> { - private readonly AppDbContext _context; + private readonly MainAppDatabase _database; - public CreateEntryRoute(AppDbContext context) { - _context = context; - } + public CreateEntryRoute(MainAppDatabase database) { + _database = database; + } - /// <summary> - /// Create a time entry. - /// </summary> - /// <param name="timeEntryTimeEntryDto"></param> - /// <returns></returns> - [ApiVersion(ApiSpecV1.VERSION_STRING)] - [BasicAuthentication(AppConstants.TOKEN_ALLOW_CREATE)] - [ProducesResponseType(200)] - [ProducesResponseType(400, Type = typeof(KnownProblemModel))] - [ProducesResponseType(404, Type = typeof(KnownProblemModel))] - [HttpPost("~/v{version:apiVersion}/entries/create")] - public override ActionResult<TimeEntry.TimeEntryDto> Handle(TimeEntry.TimeEntryDto timeEntryTimeEntryDto) { - if (timeEntryTimeEntryDto.Stop == default) { - return BadRequest(new KnownProblemModel("Invalid form", "A stop date is required")); - } + /// <summary> + /// Create a time entry. + /// </summary> + /// <param name="timeEntryTimeEntryDto"></param> + /// <returns></returns> + [ApiVersion(ApiSpecV1.VERSION_STRING)] + [BasicAuthentication(AppConstants.TOKEN_ALLOW_CREATE)] + [ProducesResponseType(200)] + [ProducesResponseType(400, Type = typeof(KnownProblemModel))] + [ProducesResponseType(404, Type = typeof(KnownProblemModel))] + [HttpPost("~/v{version:apiVersion}/entries/create")] + public override ActionResult<TimeEntry.TimeEntryDto> Handle(TimeEntry.TimeEntryDto timeEntryTimeEntryDto) { + if (timeEntryTimeEntryDto.Stop == default) { + return BadRequest(new KnownProblemModel("Invalid form", "A stop date is required")); + } - if (timeEntryTimeEntryDto.Start == default) { - return BadRequest(new KnownProblemModel("Invalid form", "A start date is required")); - } + if (timeEntryTimeEntryDto.Start == default) { + return BadRequest(new KnownProblemModel("Invalid form", "A start date is required")); + } - if (timeEntryTimeEntryDto.Category == default) { - return BadRequest(new KnownProblemModel("Invalid form", "A category is required")); - } + if (timeEntryTimeEntryDto.Category == default) { + return BadRequest(new KnownProblemModel("Invalid form", "A category is required")); + } - var category = _context.TimeCategories - .Where(c => c.UserId == LoggedInUser.Id) - .SingleOrDefault(c => c.Id == timeEntryTimeEntryDto.Category.Id); - - if (category == default) { - return NotFound(new KnownProblemModel("Not found", $"Could not find category {timeEntryTimeEntryDto.Category.Name}")); - } + var category = _database.TimeCategories + .Where(c => c.UserId == LoggedInUser.Id) + .SingleOrDefault(c => c.Id == timeEntryTimeEntryDto.Category.Id); - var entry = new TimeEntry(LoggedInUser.Id) { - Category = category, - Start = timeEntryTimeEntryDto.Start.ToUniversalTime(), - Stop = timeEntryTimeEntryDto.Stop.ToUniversalTime(), - Description = timeEntryTimeEntryDto.Description, - }; + if (category == default) { + return NotFound(new KnownProblemModel("Not found", $"Could not find category {timeEntryTimeEntryDto.Category.Name}")); + } - if (timeEntryTimeEntryDto.Labels?.Count > 0) { - var labels = _context.TimeLabels - .Where(c => c.UserId == LoggedInUser.Id) - .Where(c => timeEntryTimeEntryDto.Labels.Select(p => p.Id).Contains(c.Id)) - .ToList(); - if (labels.Count != timeEntryTimeEntryDto.Labels.Count) { - return NotFound(new KnownProblemModel("Not found", "Could not find all of the specified labels")); - } + var entry = new TimeEntry(LoggedInUser) { + Category = category, + Start = timeEntryTimeEntryDto.Start.ToUniversalTime(), + Stop = timeEntryTimeEntryDto.Stop.ToUniversalTime(), + Description = timeEntryTimeEntryDto.Description, + }; + entry.SetOwnerIds(LoggedInUser.Id); - entry.Labels = labels; - } + if (timeEntryTimeEntryDto.Labels?.Count > 0) { + var labels = _database.TimeLabels + .Where(c => c.UserId == LoggedInUser.Id) + .Where(c => timeEntryTimeEntryDto.Labels.Select(p => p.Id).Contains(c.Id)) + .ToList(); + if (labels.Count != timeEntryTimeEntryDto.Labels.Count) { + return NotFound(new KnownProblemModel("Not found", "Could not find all of the specified labels")); + } - _context.TimeEntries.Add(entry); - _context.SaveChanges(); - return Ok(entry.AsDto); - } -} + entry.Labels = labels; + } + + _database.TimeEntries.Add(entry); + _database.SaveChanges(); + return Ok(entry.AsDto); + } +}
\ No newline at end of file diff --git a/code/api/src/Endpoints/V1/Entries/DeleteEntryRoute.cs b/code/api/src/Endpoints/V1/Entries/DeleteEntryRoute.cs index 0850af0..c488ed5 100644 --- a/code/api/src/Endpoints/V1/Entries/DeleteEntryRoute.cs +++ b/code/api/src/Endpoints/V1/Entries/DeleteEntryRoute.cs @@ -1,35 +1,33 @@ namespace IOL.GreatOffice.Api.Endpoints.V1.Entries; -/// <inheritdoc /> public class DeleteEntryRoute : RouteBaseSync.WithRequest<Guid>.WithActionResult { - private readonly AppDbContext _context; + private readonly MainAppDatabase _database; - /// <inheritdoc /> - public DeleteEntryRoute(AppDbContext context) { - _context = context; - } + public DeleteEntryRoute(MainAppDatabase database) { + _database = database; + } - /// <summary> - /// Delete a time entry. - /// </summary> - /// <param name="id"></param> - /// <returns></returns> - [ApiVersion(ApiSpecV1.VERSION_STRING)] - [BasicAuthentication(AppConstants.TOKEN_ALLOW_DELETE)] - [HttpDelete("~/v{version:apiVersion}/entries/{id:guid}/delete")] - [ProducesResponseType(404)] - [ProducesResponseType(200)] - public override ActionResult Handle(Guid id) { - var entry = _context.TimeEntries - .Where(c => c.UserId == LoggedInUser.Id) - .SingleOrDefault(c => c.Id == id); - if (entry == default) { - return NotFound(); - } + /// <summary> + /// Delete a time entry. + /// </summary> + /// <param name="id"></param> + /// <returns></returns> + [ApiVersion(ApiSpecV1.VERSION_STRING)] + [BasicAuthentication(AppConstants.TOKEN_ALLOW_DELETE)] + [HttpDelete("~/v{version:apiVersion}/entries/{id:guid}/delete")] + [ProducesResponseType(404)] + [ProducesResponseType(200)] + public override ActionResult Handle(Guid id) { + var entry = _database.TimeEntries + .Where(c => c.UserId == LoggedInUser.Id) + .SingleOrDefault(c => c.Id == id); + if (entry == default) { + return NotFound(); + } - _context.TimeEntries.Remove(entry); - _context.SaveChanges(); - return Ok(); - } -} + _database.TimeEntries.Remove(entry); + _database.SaveChanges(); + return Ok(); + } +}
\ No newline at end of file diff --git a/code/api/src/Endpoints/V1/Entries/EntryQueryRoute.cs b/code/api/src/Endpoints/V1/Entries/EntryQueryRoute.cs index ec6003c..03a64d2 100644 --- a/code/api/src/Endpoints/V1/Entries/EntryQueryRoute.cs +++ b/code/api/src/Endpoints/V1/Entries/EntryQueryRoute.cs @@ -2,185 +2,185 @@ namespace IOL.GreatOffice.Api.Endpoints.V1.Entries; public class EntryQueryRoute : RouteBaseSync.WithRequest<EntryQueryPayload>.WithActionResult<EntryQueryResponse> { - private readonly ILogger<EntryQueryRoute> _logger; - private readonly AppDbContext _context; + private readonly ILogger<EntryQueryRoute> _logger; + private readonly MainAppDatabase _database; - public EntryQueryRoute(ILogger<EntryQueryRoute> logger, AppDbContext context) { - _logger = logger; - _context = context; - } + public EntryQueryRoute(ILogger<EntryQueryRoute> logger, MainAppDatabase database) { + _logger = logger; + _database = database; + } - /// <summary> - /// Get a list of entries based on a given query. - /// </summary> - /// <param name="entryQuery"></param> - /// <returns></returns> - /// <exception cref="ArgumentOutOfRangeException"></exception> - [ApiVersion(ApiSpecV1.VERSION_STRING)] - [BasicAuthentication(AppConstants.TOKEN_ALLOW_READ)] - [HttpPost("~/v{version:apiVersion}/entries/query")] - [ProducesResponseType(204)] - [ProducesResponseType(400, Type = typeof(KnownProblemModel))] - [ProducesResponseType(200, Type = typeof(EntryQueryResponse))] - public override ActionResult<EntryQueryResponse> Handle(EntryQueryPayload entryQuery) { - var result = new TimeQueryDto(); + /// <summary> + /// Get a list of entries based on a given query. + /// </summary> + /// <param name="entryQuery"></param> + /// <returns></returns> + /// <exception cref="ArgumentOutOfRangeException"></exception> + [ApiVersion(ApiSpecV1.VERSION_STRING)] + [BasicAuthentication(AppConstants.TOKEN_ALLOW_READ)] + [HttpPost("~/v{version:apiVersion}/entries/query")] + [ProducesResponseType(204)] + [ProducesResponseType(400, Type = typeof(KnownProblemModel))] + [ProducesResponseType(200, Type = typeof(EntryQueryResponse))] + public override ActionResult<EntryQueryResponse> Handle(EntryQueryPayload entryQuery) { + var result = new TimeQueryDto(); - Request.Headers.TryGetValue(AppHeaders.BROWSER_TIME_ZONE, out var timeZoneHeader); - var tz = TimeZoneInfo.FindSystemTimeZoneById(timeZoneHeader.ToString().HasValue() ? timeZoneHeader.ToString() : "UTC"); - var offsetInHours = tz.BaseUtcOffset.Hours; + Request.Headers.TryGetValue(AppHeaders.BROWSER_TIME_ZONE, out var timeZoneHeader); + var tz = TimeZoneInfo.FindSystemTimeZoneById(timeZoneHeader.ToString().HasValue() ? timeZoneHeader.ToString() : "UTC"); + var offsetInHours = tz.BaseUtcOffset.Hours; - // this is fine as long as the client is not connecting from Australia: Lord Howe Island - // according to https://en.wikipedia.org/wiki/Daylight_saving_time_by_country - if (tz.IsDaylightSavingTime(AppDateTime.UtcNow)) { - offsetInHours++; - } + // this is fine as long as the client is not connecting from Australia: Lord Howe Island + // according to https://en.wikipedia.org/wiki/Daylight_saving_time_by_country + if (tz.IsDaylightSavingTime(AppDateTime.UtcNow)) { + offsetInHours++; + } - _logger.LogInformation("Request time zone (" + tz.Id + ") offset is: " + offsetInHours + " hours"); - var requestDateTime = TimeZoneInfo.ConvertTimeFromUtc(AppDateTime.UtcNow, tz); - _logger.LogInformation("Querying data with date time: " + requestDateTime.ToString("u")); + _logger.LogInformation("Request time zone (" + tz.Id + ") offset is: " + offsetInHours + " hours"); + var requestDateTime = TimeZoneInfo.ConvertTimeFromUtc(AppDateTime.UtcNow, tz); + _logger.LogInformation("Querying data with date time: " + requestDateTime.ToString("u")); - var skipCount = 0; - if (entryQuery.Page > 1) { - skipCount = entryQuery.PageSize * entryQuery.Page; - } + var skipCount = 0; + if (entryQuery.Page > 1) { + skipCount = entryQuery.PageSize * entryQuery.Page; + } - result.Page = entryQuery.Page; - result.PageSize = entryQuery.PageSize; + result.Page = entryQuery.Page; + result.PageSize = entryQuery.PageSize; - var baseQuery = _context.TimeEntries - .AsNoTracking() - .Include(c => c.Category) - .Include(c => c.Labels) - .Where(c => c.UserId == LoggedInUser.Id) - .ConditionalWhere(entryQuery.Categories?.Any() ?? false, c => entryQuery.Categories.Any(p => p.Id == c.Category.Id)) - .ConditionalWhere(entryQuery.Labels?.Any() ?? false, c => c.Labels.Any(l => entryQuery.Labels.Any(p => p.Id == l.Id))) - .OrderByDescending(c => c.Start); + var baseQuery = _database.TimeEntries + .AsNoTracking() + .Include(c => c.Category) + .Include(c => c.Labels) + .Where(c => c.UserId == LoggedInUser.Id) + .ConditionalWhere(entryQuery.Categories?.Any() ?? false, c => entryQuery.Categories.Any(p => p.Id == c.Category.Id)) + .ConditionalWhere(entryQuery.Labels?.Any() ?? false, c => c.Labels.Any(l => entryQuery.Labels.Any(p => p.Id == l.Id))) + .OrderByDescending(c => c.Start); - switch (entryQuery.Duration) { - case TimeEntryQueryDuration.TODAY: - var baseTodaysEntries = baseQuery - .Where(c => DateTime.Compare(c.Start.AddHours(offsetInHours).Date, AppDateTime.UtcNow.Date) == 0); - var baseTodaysEntriesCount = baseTodaysEntries.Count(); + switch (entryQuery.Duration) { + case TimeEntryQueryDuration.TODAY: + var baseTodaysEntries = baseQuery + .Where(c => DateTime.Compare(c.Start.AddHours(offsetInHours).Date, AppDateTime.UtcNow.Date) == 0); + var baseTodaysEntriesCount = baseTodaysEntries.Count(); - if (baseTodaysEntriesCount == 0) { - return NoContent(); - } + if (baseTodaysEntriesCount == 0) { + return NoContent(); + } - result.TotalSize = baseTodaysEntriesCount; - result.TotalPageCount = Convert.ToInt32(Math.Round((double)baseTodaysEntriesCount / entryQuery.PageSize)); + result.TotalSize = baseTodaysEntriesCount; + result.TotalPageCount = Convert.ToInt32(Math.Round((double) baseTodaysEntriesCount / entryQuery.PageSize)); - var pagedTodaysEntries = baseTodaysEntries.Skip(skipCount).Take(entryQuery.PageSize); + var pagedTodaysEntries = baseTodaysEntries.Skip(skipCount).Take(entryQuery.PageSize); - result.Results.AddRange(pagedTodaysEntries.Select(c => c.AsDto)); - break; - case TimeEntryQueryDuration.THIS_WEEK: - var lastMonday = AppDateTime.UtcNow.StartOfWeek(DayOfWeek.Monday); + result.Results.AddRange(pagedTodaysEntries.Select(c => c.AsDto)); + break; + case TimeEntryQueryDuration.THIS_WEEK: + var lastMonday = AppDateTime.UtcNow.StartOfWeek(DayOfWeek.Monday); - var baseEntriesThisWeek = baseQuery - .Where(c => c.Start.AddHours(offsetInHours).Date >= lastMonday.Date && c.Start.AddHours(offsetInHours).Date <= AppDateTime.UtcNow.Date); + var baseEntriesThisWeek = baseQuery + .Where(c => c.Start.AddHours(offsetInHours).Date >= lastMonday.Date && c.Start.AddHours(offsetInHours).Date <= AppDateTime.UtcNow.Date); - var baseEntriesThisWeekCount = baseEntriesThisWeek.Count(); + var baseEntriesThisWeekCount = baseEntriesThisWeek.Count(); - if (baseEntriesThisWeekCount == 0) { - return NoContent(); - } + if (baseEntriesThisWeekCount == 0) { + return NoContent(); + } - result.TotalSize = baseEntriesThisWeekCount; - result.TotalPageCount = Convert.ToInt32(Math.Round((double)baseEntriesThisWeekCount / entryQuery.PageSize)); + result.TotalSize = baseEntriesThisWeekCount; + result.TotalPageCount = Convert.ToInt32(Math.Round((double) baseEntriesThisWeekCount / entryQuery.PageSize)); - var pagedEntriesThisWeek = baseEntriesThisWeek.Skip(skipCount).Take(entryQuery.PageSize); + var pagedEntriesThisWeek = baseEntriesThisWeek.Skip(skipCount).Take(entryQuery.PageSize); - result.Results.AddRange(pagedEntriesThisWeek.Select(c => c.AsDto)); - break; - case TimeEntryQueryDuration.THIS_MONTH: - var baseEntriesThisMonth = baseQuery - .Where(c => c.Start.AddHours(offsetInHours).Month == AppDateTime.UtcNow.Month - && c.Start.AddHours(offsetInHours).Year == AppDateTime.UtcNow.Year); - var baseEntriesThisMonthCount = baseEntriesThisMonth.Count(); - if (baseEntriesThisMonthCount == 0) { - return NoContent(); - } + result.Results.AddRange(pagedEntriesThisWeek.Select(c => c.AsDto)); + break; + case TimeEntryQueryDuration.THIS_MONTH: + var baseEntriesThisMonth = baseQuery + .Where(c => c.Start.AddHours(offsetInHours).Month == AppDateTime.UtcNow.Month + && c.Start.AddHours(offsetInHours).Year == AppDateTime.UtcNow.Year); + var baseEntriesThisMonthCount = baseEntriesThisMonth.Count(); + if (baseEntriesThisMonthCount == 0) { + return NoContent(); + } - result.TotalSize = baseEntriesThisMonthCount; - result.TotalPageCount = Convert.ToInt32(Math.Round((double)baseEntriesThisMonthCount / entryQuery.PageSize)); + result.TotalSize = baseEntriesThisMonthCount; + result.TotalPageCount = Convert.ToInt32(Math.Round((double) baseEntriesThisMonthCount / entryQuery.PageSize)); - var pagedEntriesThisMonth = baseEntriesThisMonth.Skip(skipCount).Take(entryQuery.PageSize); + var pagedEntriesThisMonth = baseEntriesThisMonth.Skip(skipCount).Take(entryQuery.PageSize); - result.Results.AddRange(pagedEntriesThisMonth.Select(c => c.AsDto)); - break; - case TimeEntryQueryDuration.THIS_YEAR: - var baseEntriesThisYear = baseQuery - .Where(c => c.Start.AddHours(offsetInHours).Year == AppDateTime.UtcNow.Year); + result.Results.AddRange(pagedEntriesThisMonth.Select(c => c.AsDto)); + break; + case TimeEntryQueryDuration.THIS_YEAR: + var baseEntriesThisYear = baseQuery + .Where(c => c.Start.AddHours(offsetInHours).Year == AppDateTime.UtcNow.Year); - var baseEntriesThisYearCount = baseEntriesThisYear.Count(); - if (baseEntriesThisYearCount == 0) { - return NoContent(); - } + var baseEntriesThisYearCount = baseEntriesThisYear.Count(); + if (baseEntriesThisYearCount == 0) { + return NoContent(); + } - result.TotalSize = baseEntriesThisYearCount; - result.TotalPageCount = Convert.ToInt32(Math.Round((double)baseEntriesThisYearCount / entryQuery.PageSize)); + result.TotalSize = baseEntriesThisYearCount; + result.TotalPageCount = Convert.ToInt32(Math.Round((double) baseEntriesThisYearCount / entryQuery.PageSize)); - var pagedEntriesThisYear = baseEntriesThisYear.Skip(skipCount).Take(entryQuery.PageSize); + var pagedEntriesThisYear = baseEntriesThisYear.Skip(skipCount).Take(entryQuery.PageSize); - result.Results.AddRange(pagedEntriesThisYear.Select(c => c.AsDto)); - break; - case TimeEntryQueryDuration.SPECIFIC_DATE: - var date = DateTime.SpecifyKind(entryQuery.SpecificDate, DateTimeKind.Utc); - var baseEntriesOnThisDate = baseQuery.Where(c => c.Start.AddHours(offsetInHours).Date == date.Date); - var baseEntriesOnThisDateCount = baseEntriesOnThisDate.Count(); + result.Results.AddRange(pagedEntriesThisYear.Select(c => c.AsDto)); + break; + case TimeEntryQueryDuration.SPECIFIC_DATE: + var date = DateTime.SpecifyKind(entryQuery.SpecificDate, DateTimeKind.Utc); + var baseEntriesOnThisDate = baseQuery.Where(c => c.Start.AddHours(offsetInHours).Date == date.Date); + var baseEntriesOnThisDateCount = baseEntriesOnThisDate.Count(); - if (baseEntriesOnThisDateCount == 0) { - return NoContent(); - } + if (baseEntriesOnThisDateCount == 0) { + return NoContent(); + } - result.TotalSize = baseEntriesOnThisDateCount; - result.TotalPageCount = Convert.ToInt32(Math.Round((double)baseEntriesOnThisDateCount / entryQuery.PageSize)); + result.TotalSize = baseEntriesOnThisDateCount; + result.TotalPageCount = Convert.ToInt32(Math.Round((double) baseEntriesOnThisDateCount / entryQuery.PageSize)); - var pagedEntriesOnThisDate = baseEntriesOnThisDate.Skip(skipCount).Take(entryQuery.PageSize); + var pagedEntriesOnThisDate = baseEntriesOnThisDate.Skip(skipCount).Take(entryQuery.PageSize); - result.Results.AddRange(pagedEntriesOnThisDate.Select(c => c.AsDto)); - break; - case TimeEntryQueryDuration.DATE_RANGE: - if (entryQuery.DateRange.From == default) { - return BadRequest(new KnownProblemModel("Invalid query", "From date cannot be empty")); - } + result.Results.AddRange(pagedEntriesOnThisDate.Select(c => c.AsDto)); + break; + case TimeEntryQueryDuration.DATE_RANGE: + if (entryQuery.DateRange.From == default) { + return BadRequest(new KnownProblemModel("Invalid query", "From date cannot be empty")); + } - var fromDate = DateTime.SpecifyKind(entryQuery.DateRange.From, DateTimeKind.Utc); + var fromDate = DateTime.SpecifyKind(entryQuery.DateRange.From, DateTimeKind.Utc); - if (entryQuery.DateRange.To == default) { - return BadRequest(new KnownProblemModel("Invalid query", "To date cannot be empty")); - } + if (entryQuery.DateRange.To == default) { + return BadRequest(new KnownProblemModel("Invalid query", "To date cannot be empty")); + } - var toDate = DateTime.SpecifyKind(entryQuery.DateRange.To, DateTimeKind.Utc); + var toDate = DateTime.SpecifyKind(entryQuery.DateRange.To, DateTimeKind.Utc); - if (DateTime.Compare(fromDate, toDate) > 0) { - return BadRequest(new KnownProblemModel("Invalid query", "To date cannot be less than From date")); - } + if (DateTime.Compare(fromDate, toDate) > 0) { + return BadRequest(new KnownProblemModel("Invalid query", "To date cannot be less than From date")); + } - var baseDateRangeEntries = baseQuery - .Where(c => c.Start.AddHours(offsetInHours).Date > fromDate && c.Start.AddHours(offsetInHours).Date <= toDate); + var baseDateRangeEntries = baseQuery + .Where(c => c.Start.AddHours(offsetInHours).Date > fromDate && c.Start.AddHours(offsetInHours).Date <= toDate); - var baseDateRangeEntriesCount = baseDateRangeEntries.Count(); - if (baseDateRangeEntriesCount == 0) { - return NoContent(); - } + var baseDateRangeEntriesCount = baseDateRangeEntries.Count(); + if (baseDateRangeEntriesCount == 0) { + return NoContent(); + } - result.TotalSize = baseDateRangeEntriesCount; - result.TotalPageCount = Convert.ToInt32(Math.Round((double)baseDateRangeEntriesCount / entryQuery.PageSize)); + result.TotalSize = baseDateRangeEntriesCount; + result.TotalPageCount = Convert.ToInt32(Math.Round((double) baseDateRangeEntriesCount / entryQuery.PageSize)); - var pagedDateRangeEntries = baseDateRangeEntries.Skip(skipCount).Take(entryQuery.PageSize); + var pagedDateRangeEntries = baseDateRangeEntries.Skip(skipCount).Take(entryQuery.PageSize); - result.Results.AddRange(pagedDateRangeEntries.Select(c => c.AsDto)); - break; - default: - throw new ArgumentOutOfRangeException(nameof(entryQuery), "Unknown duration for query"); - } + result.Results.AddRange(pagedDateRangeEntries.Select(c => c.AsDto)); + break; + default: + throw new ArgumentOutOfRangeException(nameof(entryQuery), "Unknown duration for query"); + } - if (result.Results.Any() && result.Page == 0) { - result.Page = 1; - result.TotalPageCount = 1; - } + if (result.Results.Any() && result.Page == 0) { + result.Page = 1; + result.TotalPageCount = 1; + } - return Ok(result); - } -} + return Ok(result); + } +}
\ No newline at end of file diff --git a/code/api/src/Endpoints/V1/Entries/GetEntryRoute.cs b/code/api/src/Endpoints/V1/Entries/GetEntryRoute.cs index 87038db..6e064c7 100644 --- a/code/api/src/Endpoints/V1/Entries/GetEntryRoute.cs +++ b/code/api/src/Endpoints/V1/Entries/GetEntryRoute.cs @@ -2,33 +2,33 @@ namespace IOL.GreatOffice.Api.Endpoints.V1.Entries; public class GetEntryRoute : RouteBaseSync.WithRequest<Guid>.WithActionResult<TimeEntry.TimeEntryDto> { - private readonly AppDbContext _context; + private readonly MainAppDatabase _database; - public GetEntryRoute(AppDbContext context) { - _context = context; - } + public GetEntryRoute(MainAppDatabase database) { + _database = database; + } - /// <summary> - /// Get a spesific time entry. - /// </summary> - /// <param name="id"></param> - /// <returns></returns> - [ApiVersion(ApiSpecV1.VERSION_STRING)] - [BasicAuthentication(AppConstants.TOKEN_ALLOW_READ)] - [HttpGet("~/v{version:apiVersion}/entries/{id:guid}")] - [ProducesResponseType(404)] - [ProducesResponseType(200, Type = typeof(TimeEntry.TimeEntryDto))] - public override ActionResult<TimeEntry.TimeEntryDto> Handle(Guid id) { - var entry = _context.TimeEntries - .Where(c => c.UserId == LoggedInUser.Id) - .Include(c => c.Category) - .Include(c => c.Labels) - .SingleOrDefault(c => c.Id == id); + /// <summary> + /// Get a spesific time entry. + /// </summary> + /// <param name="id"></param> + /// <returns></returns> + [ApiVersion(ApiSpecV1.VERSION_STRING)] + [BasicAuthentication(AppConstants.TOKEN_ALLOW_READ)] + [HttpGet("~/v{version:apiVersion}/entries/{id:guid}")] + [ProducesResponseType(404)] + [ProducesResponseType(200, Type = typeof(TimeEntry.TimeEntryDto))] + public override ActionResult<TimeEntry.TimeEntryDto> Handle(Guid id) { + var entry = _database.TimeEntries + .Where(c => c.UserId == LoggedInUser.Id) + .Include(c => c.Category) + .Include(c => c.Labels) + .SingleOrDefault(c => c.Id == id); - if (entry == default) { - return NotFound(); - } + if (entry == default) { + return NotFound(); + } - return Ok(entry); - } -} + return Ok(entry); + } +}
\ No newline at end of file diff --git a/code/api/src/Endpoints/V1/Entries/UpdateEntryRoute.cs b/code/api/src/Endpoints/V1/Entries/UpdateEntryRoute.cs index 09e3b9c..2254214 100644 --- a/code/api/src/Endpoints/V1/Entries/UpdateEntryRoute.cs +++ b/code/api/src/Endpoints/V1/Entries/UpdateEntryRoute.cs @@ -2,65 +2,65 @@ namespace IOL.GreatOffice.Api.Endpoints.V1.Entries; public class UpdateEntryRoute : RouteBaseSync.WithRequest<TimeEntry.TimeEntryDto>.WithActionResult<TimeEntry.TimeEntryDto> { - private readonly AppDbContext _context; + private readonly MainAppDatabase _database; - public UpdateEntryRoute(AppDbContext context) { - _context = context; - } + public UpdateEntryRoute(MainAppDatabase database) { + _database = database; + } - /// <summary> - /// Update a time entry. - /// </summary> - /// <param name="timeEntryTimeEntryDto"></param> - /// <returns></returns> - [ApiVersion(ApiSpecV1.VERSION_STRING)] - [BasicAuthentication(AppConstants.TOKEN_ALLOW_UPDATE)] - [HttpPost("~/v{version:apiVersion}/entries/update")] - [ProducesResponseType(404, Type = typeof(KnownProblemModel))] - [ProducesResponseType(200, Type = typeof(TimeEntry.TimeEntryDto))] - public override ActionResult<TimeEntry.TimeEntryDto> Handle(TimeEntry.TimeEntryDto timeEntryTimeEntryDto) { - var entry = _context.TimeEntries - .Where(c => c.UserId == LoggedInUser.Id) - .Include(c => c.Labels) - .SingleOrDefault(c => c.Id == timeEntryTimeEntryDto.Id); + /// <summary> + /// Update a time entry. + /// </summary> + /// <param name="timeEntryTimeEntryDto"></param> + /// <returns></returns> + [ApiVersion(ApiSpecV1.VERSION_STRING)] + [BasicAuthentication(AppConstants.TOKEN_ALLOW_UPDATE)] + [HttpPost("~/v{version:apiVersion}/entries/update")] + [ProducesResponseType(404, Type = typeof(KnownProblemModel))] + [ProducesResponseType(200, Type = typeof(TimeEntry.TimeEntryDto))] + public override ActionResult<TimeEntry.TimeEntryDto> Handle(TimeEntry.TimeEntryDto timeEntryTimeEntryDto) { + var entry = _database.TimeEntries + .Where(c => c.UserId == LoggedInUser.Id) + .Include(c => c.Labels) + .SingleOrDefault(c => c.Id == timeEntryTimeEntryDto.Id); - if (entry == default) { - return NotFound(); - } + if (entry == default) { + return NotFound(); + } - var category = _context.TimeCategories - .Where(c => c.UserId == LoggedInUser.Id) - .SingleOrDefault(c => c.Id == timeEntryTimeEntryDto.Category.Id); - if (category == default) { - return NotFound(new KnownProblemModel("Not found", $"Could not find category {timeEntryTimeEntryDto.Category.Name}")); - } + var category = _database.TimeCategories + .Where(c => c.UserId == LoggedInUser.Id) + .SingleOrDefault(c => c.Id == timeEntryTimeEntryDto.Category.Id); + if (category == default) { + return NotFound(new KnownProblemModel("Not found", $"Could not find category {timeEntryTimeEntryDto.Category.Name}")); + } - entry.Start = timeEntryTimeEntryDto.Start.ToUniversalTime(); - entry.Stop = timeEntryTimeEntryDto.Stop.ToUniversalTime(); - entry.Description = timeEntryTimeEntryDto.Description; - entry.Category = category; + entry.Start = timeEntryTimeEntryDto.Start.ToUniversalTime(); + entry.Stop = timeEntryTimeEntryDto.Stop.ToUniversalTime(); + entry.Description = timeEntryTimeEntryDto.Description; + entry.Category = category; - if (timeEntryTimeEntryDto.Labels?.Count > 0) { - var labels = new List<TimeLabel>(); + if (timeEntryTimeEntryDto.Labels?.Count > 0) { + var labels = new List<TimeLabel>(); - foreach (var labelDto in timeEntryTimeEntryDto.Labels) { - var label = _context.TimeLabels - .Where(c => c.UserId == LoggedInUser.Id) - .SingleOrDefault(c => c.Id == labelDto.Id); + foreach (var labelDto in timeEntryTimeEntryDto.Labels) { + var label = _database.TimeLabels + .Where(c => c.UserId == LoggedInUser.Id) + .SingleOrDefault(c => c.Id == labelDto.Id); - if (label == default) { - continue; - } + if (label == default) { + continue; + } - labels.Add(label); - } + labels.Add(label); + } - entry.Labels = labels; - } else { - entry.Labels = default; - } + entry.Labels = labels; + } else { + entry.Labels = default; + } - _context.SaveChanges(); - return Ok(entry.AsDto); - } -} + _database.SaveChanges(); + return Ok(entry.AsDto); + } +}
\ No newline at end of file diff --git a/code/api/src/Endpoints/V1/Labels/CreateLabelRoute.cs b/code/api/src/Endpoints/V1/Labels/CreateLabelRoute.cs index 4fe418b..d6106aa 100644 --- a/code/api/src/Endpoints/V1/Labels/CreateLabelRoute.cs +++ b/code/api/src/Endpoints/V1/Labels/CreateLabelRoute.cs @@ -1,44 +1,44 @@ - namespace IOL.GreatOffice.Api.Endpoints.V1.Labels; public class CreateLabelRoute : RouteBaseSync.WithRequest<TimeLabel.TimeLabelDto>.WithActionResult<TimeLabel.TimeLabelDto> { - private readonly AppDbContext _context; + private readonly MainAppDatabase _database; - public CreateLabelRoute(AppDbContext context) { - _context = context; - } + public CreateLabelRoute(MainAppDatabase database) { + _database = database; + } - /// <summary> - /// Create a time entry label. - /// </summary> - /// <param name="labelTimeLabelDto"></param> - /// <returns></returns> - [ApiVersion(ApiSpecV1.VERSION_STRING)] - [BasicAuthentication(AppConstants.TOKEN_ALLOW_CREATE)] - [HttpPost("~/v{version:apiVersion}/labels/create")] - public override ActionResult<TimeLabel.TimeLabelDto> Handle(TimeLabel.TimeLabelDto labelTimeLabelDto) { - var duplicate = _context.TimeLabels - .Where(c => c.UserId == LoggedInUser.Id) - .Any(c => c.Name.Trim() == labelTimeLabelDto.Name.Trim()); - if (duplicate) { - var label = _context.TimeLabels - .Where(c => c.UserId == LoggedInUser.Id) - .SingleOrDefault(c => c.Name.Trim() == labelTimeLabelDto.Name.Trim()); + /// <summary> + /// Create a time entry label. + /// </summary> + /// <param name="labelTimeLabelDto"></param> + /// <returns></returns> + [ApiVersion(ApiSpecV1.VERSION_STRING)] + [BasicAuthentication(AppConstants.TOKEN_ALLOW_CREATE)] + [HttpPost("~/v{version:apiVersion}/labels/create")] + public override ActionResult<TimeLabel.TimeLabelDto> Handle(TimeLabel.TimeLabelDto labelTimeLabelDto) { + var duplicate = _database.TimeLabels + .Where(c => c.UserId == LoggedInUser.Id) + .Any(c => c.Name.Trim() == labelTimeLabelDto.Name.Trim()); + if (duplicate) { + var label = _database.TimeLabels + .Where(c => c.UserId == LoggedInUser.Id) + .SingleOrDefault(c => c.Name.Trim() == labelTimeLabelDto.Name.Trim()); - if (label != default) { - return Ok(label.AsDto); - } - } + if (label != default) { + return Ok(label.AsDto); + } + } - var newLabel = new TimeLabel(LoggedInUser.Id) { - Name = labelTimeLabelDto.Name.Trim(), - Color = labelTimeLabelDto.Color - }; + var newLabel = new TimeLabel(LoggedInUser) { + Name = labelTimeLabelDto.Name.Trim(), + Color = labelTimeLabelDto.Color + }; + newLabel.SetOwnerIds(LoggedInUser.Id); - _context.TimeLabels.Add(newLabel); - _context.SaveChanges(); - labelTimeLabelDto.Id = newLabel.Id; - return Ok(labelTimeLabelDto); - } -} + _database.TimeLabels.Add(newLabel); + _database.SaveChanges(); + labelTimeLabelDto.Id = newLabel.Id; + return Ok(labelTimeLabelDto); + } +}
\ No newline at end of file diff --git a/code/api/src/Endpoints/V1/Labels/DeleteLabelRoute.cs b/code/api/src/Endpoints/V1/Labels/DeleteLabelRoute.cs index d845a6f..1baf4ef 100644 --- a/code/api/src/Endpoints/V1/Labels/DeleteLabelRoute.cs +++ b/code/api/src/Endpoints/V1/Labels/DeleteLabelRoute.cs @@ -1,35 +1,32 @@ - namespace IOL.GreatOffice.Api.Endpoints.V1.Labels; -/// <inheritdoc /> public class DeleteLabelEndpoint : RouteBaseSync.WithRequest<Guid>.WithActionResult { - private readonly AppDbContext _context; + private readonly MainAppDatabase _database; - /// <inheritdoc /> - public DeleteLabelEndpoint(AppDbContext context) { - _context = context; - } + public DeleteLabelEndpoint(MainAppDatabase database) { + _database = database; + } - /// <summary> - /// Delete a time entry label. - /// </summary> - /// <param name="id"></param> - /// <returns></returns> - [ApiVersion(ApiSpecV1.VERSION_STRING)] - [BasicAuthentication(AppConstants.TOKEN_ALLOW_DELETE)] - [HttpDelete("~/v{version:apiVersion}/labels/{id:guid}/delete")] - public override ActionResult Handle(Guid id) { - var label = _context.TimeLabels - .Where(c => c.UserId == LoggedInUser.Id) - .SingleOrDefault(c => c.Id == id); + /// <summary> + /// Delete a time entry label. + /// </summary> + /// <param name="id"></param> + /// <returns></returns> + [ApiVersion(ApiSpecV1.VERSION_STRING)] + [BasicAuthentication(AppConstants.TOKEN_ALLOW_DELETE)] + [HttpDelete("~/v{version:apiVersion}/labels/{id:guid}/delete")] + public override ActionResult Handle(Guid id) { + var label = _database.TimeLabels + .Where(c => c.UserId == LoggedInUser.Id) + .SingleOrDefault(c => c.Id == id); - if (label == default) { - return NotFound(); - } + if (label == default) { + return NotFound(); + } - _context.TimeLabels.Remove(label); - _context.SaveChanges(); - return Ok(); - } -} + _database.TimeLabels.Remove(label); + _database.SaveChanges(); + return Ok(); + } +}
\ No newline at end of file diff --git a/code/api/src/Endpoints/V1/Labels/GetLabelRoute.cs b/code/api/src/Endpoints/V1/Labels/GetLabelRoute.cs index c9ccef3..09d453b 100644 --- a/code/api/src/Endpoints/V1/Labels/GetLabelRoute.cs +++ b/code/api/src/Endpoints/V1/Labels/GetLabelRoute.cs @@ -1,34 +1,31 @@ - namespace IOL.GreatOffice.Api.Endpoints.V1.Labels; -/// <inheritdoc /> public class GetEndpoint : RouteBaseSync.WithoutRequest.WithActionResult<List<TimeLabel.TimeLabelDto>> { - private readonly AppDbContext _context; + private readonly MainAppDatabase _database; - /// <inheritdoc /> - public GetEndpoint(AppDbContext context) { - _context = context; - } + public GetEndpoint(MainAppDatabase database) { + _database = database; + } - /// <summary> - /// Get a minimal list of time entry labels. - /// </summary> - /// <returns></returns> - [ApiVersion(ApiSpecV1.VERSION_STRING)] - [BasicAuthentication(AppConstants.TOKEN_ALLOW_READ)] - [HttpGet("~/v{version:apiVersion}/labels")] - public override ActionResult<List<TimeLabel.TimeLabelDto>> Handle() { - var labels = _context.TimeLabels - .Where(c => c.UserId == LoggedInUser.Id) - .OrderByDescending(c => c.CreatedAt) - .Select(c => c.AsDto) - .ToList(); + /// <summary> + /// Get a minimal list of time entry labels. + /// </summary> + /// <returns></returns> + [ApiVersion(ApiSpecV1.VERSION_STRING)] + [BasicAuthentication(AppConstants.TOKEN_ALLOW_READ)] + [HttpGet("~/v{version:apiVersion}/labels")] + public override ActionResult<List<TimeLabel.TimeLabelDto>> Handle() { + var labels = _database.TimeLabels + .Where(c => c.UserId == LoggedInUser.Id) + .OrderByDescending(c => c.CreatedAt) + .Select(c => c.AsDto) + .ToList(); - if (labels.Count == 0) { - return NoContent(); - } + if (labels.Count == 0) { + return NoContent(); + } - return Ok(labels); - } -} + return Ok(labels); + } +}
\ No newline at end of file diff --git a/code/api/src/Endpoints/V1/Labels/UpdateLabelRoute.cs b/code/api/src/Endpoints/V1/Labels/UpdateLabelRoute.cs index 30d72ec..9857b7d 100644 --- a/code/api/src/Endpoints/V1/Labels/UpdateLabelRoute.cs +++ b/code/api/src/Endpoints/V1/Labels/UpdateLabelRoute.cs @@ -1,38 +1,36 @@ namespace IOL.GreatOffice.Api.Endpoints.V1.Labels; -/// <inheritdoc /> public class UpdateLabelEndpoint : RouteBaseSync.WithRequest<TimeLabel.TimeLabelDto>.WithActionResult { - private readonly AppDbContext _context; + private readonly MainAppDatabase _database; - /// <inheritdoc /> - public UpdateLabelEndpoint(AppDbContext context) { - _context = context; - } + public UpdateLabelEndpoint(MainAppDatabase database) { + _database = database; + } - /// <summary> - /// Update a time entry label. - /// </summary> - /// <param name="labelTimeLabelDto"></param> - /// <returns></returns> - [ApiVersion(ApiSpecV1.VERSION_STRING)] - [BasicAuthentication(AppConstants.TOKEN_ALLOW_UPDATE)] - [HttpPost("~/v{version:apiVersion}/labels/update")] - public override ActionResult Handle(TimeLabel.TimeLabelDto labelTimeLabelDto) { - var label = _context.TimeLabels - .Where(c => c.UserId == LoggedInUser.Id) - .SingleOrDefault(c => c.Id == labelTimeLabelDto.Id); - if (label == default) { - return NotFound(); - } + /// <summary> + /// Update a time entry label. + /// </summary> + /// <param name="labelTimeLabelDto"></param> + /// <returns></returns> + [ApiVersion(ApiSpecV1.VERSION_STRING)] + [BasicAuthentication(AppConstants.TOKEN_ALLOW_UPDATE)] + [HttpPost("~/v{version:apiVersion}/labels/update")] + public override ActionResult Handle(TimeLabel.TimeLabelDto labelTimeLabelDto) { + var label = _database.TimeLabels + .Where(c => c.UserId == LoggedInUser.Id) + .SingleOrDefault(c => c.Id == labelTimeLabelDto.Id); + if (label == default) { + return NotFound(); + } - if (LoggedInUser.Id != label.UserId) { - return Forbid(); - } + if (LoggedInUser.Id != label.UserId) { + return Forbid(); + } - label.Name = labelTimeLabelDto.Name; - label.Color = labelTimeLabelDto.Color; - _context.SaveChanges(); - return Ok(); - } -} + label.Name = labelTimeLabelDto.Name; + label.Color = labelTimeLabelDto.Color; + _database.SaveChanges(); + return Ok(); + } +}
\ No newline at end of file diff --git a/code/api/src/Endpoints/V1/Projects/CreateProjectRoute.cs b/code/api/src/Endpoints/V1/Projects/CreateProjectRoute.cs new file mode 100644 index 0000000..5c78e27 --- /dev/null +++ b/code/api/src/Endpoints/V1/Projects/CreateProjectRoute.cs @@ -0,0 +1,80 @@ +using Microsoft.Extensions.Localization; + +namespace IOL.GreatOffice.Api.Endpoints.V1.Projects; + +public class CreateProjectRoute : RouteBaseAsync.WithRequest<CreateProjectPayload>.WithActionResult +{ + private readonly MainAppDatabase _database; + private readonly IStringLocalizer<SharedResources> _localizer; + + public CreateProjectRoute(MainAppDatabase database, IStringLocalizer<SharedResources> localizer) { + _database = database; + _localizer = localizer; + } + + [HttpPost("~/v{version:apiVersion}/projects/create")] + public override async Task<ActionResult> HandleAsync(CreateProjectPayload request, CancellationToken cancellationToken = default) { + var errors = new Dictionary<string, string>(); + + if (request.Name.IsNullOrEmpty()) { + errors.Add("name", _localizer["Name is a required field"]); + } + + var project = new Project(LoggedInUser) { + Name = request.Name, + Description = request.Description, + Start = request.Start, + Stop = request.Stop, + }; + project.SetOwnerIds(default, LoggedInUser.TenantId); + + foreach (var customerId in request.CustomerIds) { + var customer = _database.Customers.FirstOrDefault(c => c.Id == customerId); + if (customer == default) { + errors.Add("customer_" + customerId, _localizer["Customer not found"]); + continue; + } + + project.Customers.Add(customer); + } + + foreach (var member in request.Members) { + var user = _database.Users.FirstOrDefault(c => c.Id == member.UserId); + if (user == default) { + errors.Add("members_" + member.UserId, _localizer["User not found"]); + continue; + } + + project.Members.Add(new ProjectMember() { + Project = project, + User = user, + Role = member.Role + }); + } + + if (errors.Any()) return KnownProblem(_localizer["Invalid form"], _localizer["One or more fields is invalid"], errors); + + _database.Projects.Add(project); + await _database.SaveChangesAsync(cancellationToken); + return Ok(); + } +} + +public class CreateProjectResponse +{ } + +public class CreateProjectPayload +{ + public string Name { get; set; } + public string Description { get; set; } + public DateTime Start { get; set; } + public DateTime Stop { get; set; } + public List<Guid> CustomerIds { get; set; } + public List<ProjectMember> Members { get; set; } + + public class ProjectMember + { + public Guid UserId { get; set; } + public ProjectRole Role { get; set; } + } +}
\ No newline at end of file diff --git a/code/api/src/Endpoints/V1/Projects/GetProjectsRoute.cs b/code/api/src/Endpoints/V1/Projects/GetProjectsRoute.cs new file mode 100644 index 0000000..d60f974 --- /dev/null +++ b/code/api/src/Endpoints/V1/Projects/GetProjectsRoute.cs @@ -0,0 +1,43 @@ +using MR.AspNetCore.Pagination; + +namespace IOL.GreatOffice.Api.Endpoints.V1.Projects; + +public class GetProjectsRoute : RouteBaseAsync.WithRequest<GetProjectsQueryParameters>.WithActionResult<KeysetPaginationResult<GetProjectsResponseDto>> +{ + private readonly MainAppDatabase _database; + private readonly PaginationService _pagination; + + public GetProjectsRoute(MainAppDatabase database, PaginationService pagination) { + _database = database; + _pagination = pagination; + } + + [HttpGet("~/v{version:apiVersion}/projects")] + public override async Task<ActionResult<KeysetPaginationResult<GetProjectsResponseDto>>> HandleAsync(GetProjectsQueryParameters request, CancellationToken cancellationToken = default) { + var result = await _pagination.KeysetPaginateAsync( + _database.Projects.ForTenant(LoggedInUser), + b => b.Descending(x => x.CreatedAt), + async id => await _database.Projects.FindAsync(id), + query => query.Select(p => new GetProjectsResponseDto() { + Id = p.Id, + Name = p.Name + }) + ); + return Ok(result); + } +} + +public class GetProjectsResponseDto +{ + public Guid Id { get; set; } + public string Name { get; set; } +} + +public class GetProjectsQueryParameters +{ + [FromQuery] + public int Page { get; set; } + + [FromQuery] + public int Size { get; set; } +}
\ No newline at end of file diff --git a/code/api/src/Endpoints/V1/Projects/_calls.http b/code/api/src/Endpoints/V1/Projects/_calls.http new file mode 100644 index 0000000..af0eba6 --- /dev/null +++ b/code/api/src/Endpoints/V1/Projects/_calls.http @@ -0,0 +1,12 @@ +### Create Project +GET https://localhost:5001/v1/projects/create +Accept: application/json +Content-Type: application/json +Cookie: "" + +{ + "": "" +} + +### Get Projects +POST http://localhost diff --git a/code/api/src/Endpoints/V1/RouteBaseAsync.cs b/code/api/src/Endpoints/V1/RouteBaseAsync.cs index a75e9da..33b6f5f 100644 --- a/code/api/src/Endpoints/V1/RouteBaseAsync.cs +++ b/code/api/src/Endpoints/V1/RouteBaseAsync.cs @@ -3,9 +3,9 @@ namespace IOL.GreatOffice.Api.Endpoints.V1; /// <summary> /// A base class for an endpoint that accepts parameters. /// </summary> -public static class RouteBaseAsync +public class RouteBaseAsync { - public static class WithRequest<TRequest> + public class WithRequest<TRequest> { public abstract class WithResult<TResponse> : V1_EndpointBase { diff --git a/code/api/src/IOL.GreatOffice.Api.csproj b/code/api/src/IOL.GreatOffice.Api.csproj index 1a3c97e..a5154db 100644 --- a/code/api/src/IOL.GreatOffice.Api.csproj +++ b/code/api/src/IOL.GreatOffice.Api.csproj @@ -21,6 +21,8 @@ <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference> + <PackageReference Include="MR.AspNetCore.Pagination" Version="1.0.1" /> + <PackageReference Include="MR.AspNetCore.Pagination.Swashbuckle" Version="1.0.1" /> <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.7" /> <PackageReference Include="Quartz.Extensions.Hosting" Version="3.5.0" /> <PackageReference Include="Serilog.AspNetCore" Version="6.0.1" /> @@ -38,19 +40,33 @@ <Content Include="..\build_and_push.sh"> <Link>build_and_push.sh</Link> </Content> - <Content Include="..\CHANGELOG.md"> - <Link>CHANGELOG.md</Link> - </Content> - <Content Include="..\cliff.toml"> - <Link>cliff.toml</Link> - </Content> <Content Include="..\Dockerfile"> <Link>Dockerfile</Link> </Content> </ItemGroup> <ItemGroup> - <Folder Include="Resources" /> + <EmbeddedResource Update="Resources\SharedResources.en.resx"> + <Generator>ResXFileCodeGenerator</Generator> + <LastGenOutput>SharedResources.en.Designer.cs</LastGenOutput> + </EmbeddedResource> + <EmbeddedResource Update="Resources\SharedResources.nb.resx"> + <Generator>ResXFileCodeGenerator</Generator> + <LastGenOutput>SharedResources.nb.Designer.cs</LastGenOutput> + </EmbeddedResource> + </ItemGroup> + + <ItemGroup> + <Compile Update="Resources\SharedResources.en.Designer.cs"> + <DesignTime>True</DesignTime> + <AutoGen>True</AutoGen> + <DependentUpon>SharedResource.en.resx</DependentUpon> + </Compile> + <Compile Update="Resources\SharedResources.nb.Designer.cs"> + <DesignTime>True</DesignTime> + <AutoGen>True</AutoGen> + <DependentUpon>SharedResources.nb.resx</DependentUpon> + </Compile> </ItemGroup> </Project> diff --git a/code/api/src/Jobs/TokenCleanupJob.cs b/code/api/src/Jobs/TokenCleanupJob.cs index fce40c9..04ccf23 100644 --- a/code/api/src/Jobs/TokenCleanupJob.cs +++ b/code/api/src/Jobs/TokenCleanupJob.cs @@ -5,9 +5,9 @@ namespace IOL.GreatOffice.Api.Jobs; public class TokenCleanupJob : IJob { private readonly ILogger<TokenCleanupJob> _logger; - private readonly AppDbContext _context; + private readonly MainAppDatabase _context; - public TokenCleanupJob(ILogger<TokenCleanupJob> logger, AppDbContext context) { + public TokenCleanupJob(ILogger<TokenCleanupJob> logger, MainAppDatabase context) { _logger = logger; _context = context; } diff --git a/code/api/src/Migrations/20210517202115_InitialMigration.Designer.cs b/code/api/src/Migrations/20210517202115_InitialMigration.Designer.cs index b6a01ff..da3c3ec 100644 --- a/code/api/src/Migrations/20210517202115_InitialMigration.Designer.cs +++ b/code/api/src/Migrations/20210517202115_InitialMigration.Designer.cs @@ -9,7 +9,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace IOL.GreatOffice.Api.Migrations { - [DbContext(typeof(AppDbContext))] + [DbContext(typeof(MainAppDatabase))] [Migration("20210517202115_InitialMigration")] partial class InitialMigration { diff --git a/code/api/src/Migrations/20210522165932_RenameNoteToDescription.Designer.cs b/code/api/src/Migrations/20210522165932_RenameNoteToDescription.Designer.cs index 368e6b3..28408c7 100644 --- a/code/api/src/Migrations/20210522165932_RenameNoteToDescription.Designer.cs +++ b/code/api/src/Migrations/20210522165932_RenameNoteToDescription.Designer.cs @@ -9,7 +9,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace IOL.GreatOffice.Api.Migrations { - [DbContext(typeof(AppDbContext))] + [DbContext(typeof(MainAppDatabase))] [Migration("20210522165932_RenameNoteToDescription")] partial class RenameNoteToDescription { diff --git a/code/api/src/Migrations/20211002113037_V6Migration.Designer.cs b/code/api/src/Migrations/20211002113037_V6Migration.Designer.cs index 59e6112..50f461e 100644 --- a/code/api/src/Migrations/20211002113037_V6Migration.Designer.cs +++ b/code/api/src/Migrations/20211002113037_V6Migration.Designer.cs @@ -12,7 +12,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace IOL.GreatOffice.Api.Migrations { - [DbContext(typeof(AppDbContext))] + [DbContext(typeof(MainAppDatabase))] [Migration("20211002113037_V6Migration")] partial class V6Migration { diff --git a/code/api/src/Migrations/20220225143559_GithubUserMappings.Designer.cs b/code/api/src/Migrations/20220225143559_GithubUserMappings.Designer.cs index 2b95f9d..6c59262 100644 --- a/code/api/src/Migrations/20220225143559_GithubUserMappings.Designer.cs +++ b/code/api/src/Migrations/20220225143559_GithubUserMappings.Designer.cs @@ -12,7 +12,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace IOL.GreatOffice.Api.Migrations { - [DbContext(typeof(AppDbContext))] + [DbContext(typeof(MainAppDatabase))] [Migration("20220225143559_GithubUserMappings")] partial class GithubUserMappings { diff --git a/code/api/src/Migrations/20220319135910_RenameCreated.Designer.cs b/code/api/src/Migrations/20220319135910_RenameCreated.Designer.cs index 3d57f1a..2f96cdc 100644 --- a/code/api/src/Migrations/20220319135910_RenameCreated.Designer.cs +++ b/code/api/src/Migrations/20220319135910_RenameCreated.Designer.cs @@ -12,7 +12,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace IOL.GreatOffice.Api.Migrations { - [DbContext(typeof(AppDbContext))] + [DbContext(typeof(MainAppDatabase))] [Migration("20220319135910_RenameCreated")] partial class RenameCreated { diff --git a/code/api/src/Migrations/20220319144958_ModifiedAt.Designer.cs b/code/api/src/Migrations/20220319144958_ModifiedAt.Designer.cs index f75400e..349c4ad 100644 --- a/code/api/src/Migrations/20220319144958_ModifiedAt.Designer.cs +++ b/code/api/src/Migrations/20220319144958_ModifiedAt.Designer.cs @@ -12,7 +12,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace IOL.GreatOffice.Api.Migrations { - [DbContext(typeof(AppDbContext))] + [DbContext(typeof(MainAppDatabase))] [Migration("20220319144958_ModifiedAt")] partial class ModifiedAt { diff --git a/code/api/src/Migrations/20220319203018_UserBase.Designer.cs b/code/api/src/Migrations/20220319203018_UserBase.Designer.cs index 6c7a76f..c918d3d 100644 --- a/code/api/src/Migrations/20220319203018_UserBase.Designer.cs +++ b/code/api/src/Migrations/20220319203018_UserBase.Designer.cs @@ -12,7 +12,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace IOL.GreatOffice.Api.Migrations { - [DbContext(typeof(AppDbContext))] + [DbContext(typeof(MainAppDatabase))] [Migration("20220319203018_UserBase")] partial class UserBase { diff --git a/code/api/src/Migrations/20220320115601_Update1.Designer.cs b/code/api/src/Migrations/20220320115601_Update1.Designer.cs index c7463fb..3b6a63a 100644 --- a/code/api/src/Migrations/20220320115601_Update1.Designer.cs +++ b/code/api/src/Migrations/20220320115601_Update1.Designer.cs @@ -12,7 +12,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace IOL.GreatOffice.Api.Migrations { - [DbContext(typeof(AppDbContext))] + [DbContext(typeof(MainAppDatabase))] [Migration("20220320115601_Update1")] partial class Update1 { diff --git a/code/api/src/Migrations/20220320132220_UpdatedForgotPasswordRequests.Designer.cs b/code/api/src/Migrations/20220320132220_UpdatedForgotPasswordRequests.Designer.cs index 3a18463..8dae7c8 100644 --- a/code/api/src/Migrations/20220320132220_UpdatedForgotPasswordRequests.Designer.cs +++ b/code/api/src/Migrations/20220320132220_UpdatedForgotPasswordRequests.Designer.cs @@ -12,7 +12,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace IOL.GreatOffice.Api.Migrations { - [DbContext(typeof(AppDbContext))] + [DbContext(typeof(MainAppDatabase))] [Migration("20220320132220_UpdatedForgotPasswordRequests")] partial class UpdatedForgotPasswordRequests { diff --git a/code/api/src/Migrations/20220529190359_ApiAccessTokens.Designer.cs b/code/api/src/Migrations/20220529190359_ApiAccessTokens.Designer.cs index 74f9b40..3dc01a7 100644 --- a/code/api/src/Migrations/20220529190359_ApiAccessTokens.Designer.cs +++ b/code/api/src/Migrations/20220529190359_ApiAccessTokens.Designer.cs @@ -11,7 +11,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace IOL.GreatOffice.Api.Migrations { - [DbContext(typeof(AppDbContext))] + [DbContext(typeof(MainAppDatabase))] [Migration("20220529190359_ApiAccessTokens")] partial class ApiAccessTokens { diff --git a/code/api/src/Migrations/20220530174741_Tenants.Designer.cs b/code/api/src/Migrations/20220530174741_Tenants.Designer.cs index 678c52d..aadbec9 100644 --- a/code/api/src/Migrations/20220530174741_Tenants.Designer.cs +++ b/code/api/src/Migrations/20220530174741_Tenants.Designer.cs @@ -11,7 +11,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace IOL.GreatOffice.Api.Migrations { - [DbContext(typeof(AppDbContext))] + [DbContext(typeof(MainAppDatabase))] [Migration("20220530174741_Tenants")] partial class Tenants { diff --git a/code/api/src/Migrations/20220530175322_RemoveUnusedNavs.Designer.cs b/code/api/src/Migrations/20220530175322_RemoveUnusedNavs.Designer.cs index 8fd6b40..f87309f 100644 --- a/code/api/src/Migrations/20220530175322_RemoveUnusedNavs.Designer.cs +++ b/code/api/src/Migrations/20220530175322_RemoveUnusedNavs.Designer.cs @@ -11,7 +11,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace IOL.GreatOffice.Api.Migrations { - [DbContext(typeof(AppDbContext))] + [DbContext(typeof(MainAppDatabase))] [Migration("20220530175322_RemoveUnusedNavs")] partial class RemoveUnusedNavs { diff --git a/code/api/src/Migrations/20220602214238_NullableOptionalBaseFields.Designer.cs b/code/api/src/Migrations/20220602214238_NullableOptionalBaseFields.Designer.cs index a05b0e4..ddbe9d2 100644 --- a/code/api/src/Migrations/20220602214238_NullableOptionalBaseFields.Designer.cs +++ b/code/api/src/Migrations/20220602214238_NullableOptionalBaseFields.Designer.cs @@ -11,7 +11,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace IOL.GreatOffice.Api.Migrations { - [DbContext(typeof(AppDbContext))] + [DbContext(typeof(MainAppDatabase))] [Migration("20220602214238_NullableOptionalBaseFields")] partial class NullableOptionalBaseFields { diff --git a/code/api/src/Migrations/20220606232346_FleshOutNewModules.Designer.cs b/code/api/src/Migrations/20220606232346_FleshOutNewModules.Designer.cs index 69d4f7e..5c4d3a3 100644 --- a/code/api/src/Migrations/20220606232346_FleshOutNewModules.Designer.cs +++ b/code/api/src/Migrations/20220606232346_FleshOutNewModules.Designer.cs @@ -11,7 +11,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace IOL.GreatOffice.Api.Migrations { - [DbContext(typeof(AppDbContext))] + [DbContext(typeof(MainAppDatabase))] [Migration("20220606232346_FleshOutNewModules")] partial class FleshOutNewModules { diff --git a/code/api/src/Migrations/20220616170311_DataProtectionKeys.Designer.cs b/code/api/src/Migrations/20220616170311_DataProtectionKeys.Designer.cs index b333f23..b89e68d 100644 --- a/code/api/src/Migrations/20220616170311_DataProtectionKeys.Designer.cs +++ b/code/api/src/Migrations/20220616170311_DataProtectionKeys.Designer.cs @@ -11,7 +11,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace IOL.GreatOffice.Api.Migrations { - [DbContext(typeof(AppDbContext))] + [DbContext(typeof(MainAppDatabase))] [Migration("20220616170311_DataProtectionKeys")] partial class DataProtectionKeys { diff --git a/code/api/src/Migrations/20220819203816_RemoveGithubUsers.Designer.cs b/code/api/src/Migrations/20220819203816_RemoveGithubUsers.Designer.cs index 33b5cfd..0ba742c 100644 --- a/code/api/src/Migrations/20220819203816_RemoveGithubUsers.Designer.cs +++ b/code/api/src/Migrations/20220819203816_RemoveGithubUsers.Designer.cs @@ -11,7 +11,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace IOL.GreatOffice.Api.Migrations { - [DbContext(typeof(AppDbContext))] + [DbContext(typeof(MainAppDatabase))] [Migration("20220819203816_RemoveGithubUsers")] partial class RemoveGithubUsers { diff --git a/code/api/src/Migrations/20221030080515_InitialProjectAndCustomer.Designer.cs b/code/api/src/Migrations/20221030080515_InitialProjectAndCustomer.Designer.cs new file mode 100644 index 0000000..284ea89 --- /dev/null +++ b/code/api/src/Migrations/20221030080515_InitialProjectAndCustomer.Designer.cs @@ -0,0 +1,1074 @@ +// <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("20221030080515_InitialProjectAndCustomer")] + partial class InitialProjectAndCustomer + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.10") + .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("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>("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?>("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + 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<Guid?>("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + 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?>("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property<string>("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property<string>("ORGNumber") + .HasColumnType("text") + .HasColumnName("org_number"); + + 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?>("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>("VATNumber") + .HasColumnType("text") + .HasColumnName("vat_number"); + + b.Property<string>("Website") + .HasColumnType("text") + .HasColumnName("website"); + + b.HasKey("Id") + .HasName("pk_customers"); + + b.HasIndex("ProjectId") + .HasDatabaseName("ix_customers_project_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?>("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property<Guid?>("CustomerId") + .HasColumnType("uuid") + .HasColumnName("customer_id"); + + b.Property<bool>("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property<Guid?>("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + 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?>("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + 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.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?>("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property<Guid?>("CustomerId") + .HasColumnType("uuid") + .HasColumnName("customer_id"); + + b.Property<bool>("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property<Guid?>("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property<DateTime?>("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property<Guid?>("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + 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.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?>("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property<bool>("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property<Guid?>("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property<DateTime?>("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property<Guid?>("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + 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.ToTable("customer_groups", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ForgotPasswordRequest", 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_forgot_password_requests"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_forgot_password_requests_user_id"); + + b.ToTable("forgot_password_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?>("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property<bool>("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property<Guid?>("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property<string>("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property<DateTime?>("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property<Guid?>("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + 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.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?>("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property<bool>("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property<Guid?>("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property<DateTime?>("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property<Guid?>("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + 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.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?>("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_member"); + + b.HasIndex("ProjectId") + .HasDatabaseName("ix_project_member_project_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_project_member_user_id"); + + b.ToTable("project_member", (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?>("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property<bool>("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property<Guid?>("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + 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?>("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + 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_tenants"); + + 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?>("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property<bool>("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property<Guid?>("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property<DateTime?>("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property<Guid?>("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + 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.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?>("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property<bool>("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property<Guid?>("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property<string>("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property<DateTime?>("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property<Guid?>("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + 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.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?>("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property<bool>("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property<Guid?>("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property<DateTime?>("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property<Guid?>("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + 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("TimeEntryId") + .HasDatabaseName("ix_time_labels_time_entry_id"); + + b.ToTable("time_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<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<string>("Username") + .HasColumnType("text") + .HasColumnName("username"); + + b.HasKey("Id") + .HasName("pk_users"); + + 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("TenantUser", b => + { + b.Property<Guid>("TenantsId") + .HasColumnType("uuid") + .HasColumnName("tenants_id"); + + b.Property<Guid>("UsersId") + .HasColumnType("uuid") + .HasColumnName("users_id"); + + b.HasKey("TenantsId", "UsersId") + .HasName("pk_tenant_user"); + + b.HasIndex("UsersId") + .HasDatabaseName("ix_tenant_user_users_id"); + + b.ToTable("tenant_user", (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("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.Project", null) + .WithMany("Customers") + .HasForeignKey("ProjectId") + .HasConstraintName("fk_customers_projects_project_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "Owner") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_customers_users_user_id"); + + b.Navigation("Owner"); + }); + + 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.Navigation("Customer"); + }); + + 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.Navigation("Customer"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ForgotPasswordRequest", 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"); + + b.Navigation("User"); + }); + + 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.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_member_projects_project_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_project_member_users_user_id"); + + b.Navigation("Project"); + + b.Navigation("User"); + }); + + 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.Navigation("Category"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeLabel", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.TimeEntry", null) + .WithMany("Labels") + .HasForeignKey("TimeEntryId") + .HasConstraintName("fk_time_labels_time_entries_time_entry_id"); + }); + + modelBuilder.Entity("TenantUser", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", null) + .WithMany() + .HasForeignKey("TenantsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_tenant_user_tenants_tenants_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", null) + .WithMany() + .HasForeignKey("UsersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_tenant_user_users_users_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("Customers"); + + b.Navigation("Labels"); + + b.Navigation("Members"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeEntry", b => + { + b.Navigation("Labels"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/code/api/src/Migrations/20221030080515_InitialProjectAndCustomer.cs b/code/api/src/Migrations/20221030080515_InitialProjectAndCustomer.cs new file mode 100644 index 0000000..8a856cb --- /dev/null +++ b/code/api/src/Migrations/20221030080515_InitialProjectAndCustomer.cs @@ -0,0 +1,304 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace IOL.GreatOffice.Api.Migrations +{ + public partial class InitialProjectAndCustomer : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "customer_groups", + columns: table => new + { + id = table.Column<Guid>(type: "uuid", nullable: false), + name = table.Column<string>(type: "text", nullable: true), + created_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: false), + modified_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: true), + deleted = table.Column<bool>(type: "boolean", nullable: false), + user_id = table.Column<Guid>(type: "uuid", nullable: true), + tenant_id = table.Column<Guid>(type: "uuid", nullable: true), + modified_by_id = table.Column<Guid>(type: "uuid", nullable: true), + created_by_id = table.Column<Guid>(type: "uuid", nullable: true), + deleted_by_id = table.Column<Guid>(type: "uuid", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_customer_groups", x => x.id); + }); + + migrationBuilder.CreateTable( + name: "projects", + columns: table => new + { + id = table.Column<Guid>(type: "uuid", nullable: false), + name = table.Column<string>(type: "text", nullable: true), + description = table.Column<string>(type: "text", nullable: true), + start = table.Column<DateTime>(type: "timestamp with time zone", nullable: true), + stop = table.Column<DateTime>(type: "timestamp with time zone", nullable: true), + created_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: false), + modified_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: true), + deleted = table.Column<bool>(type: "boolean", nullable: false), + user_id = table.Column<Guid>(type: "uuid", nullable: true), + tenant_id = table.Column<Guid>(type: "uuid", nullable: true), + modified_by_id = table.Column<Guid>(type: "uuid", nullable: true), + created_by_id = table.Column<Guid>(type: "uuid", nullable: true), + deleted_by_id = table.Column<Guid>(type: "uuid", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_projects", x => x.id); + }); + + migrationBuilder.CreateTable( + name: "customers", + columns: table => new + { + id = table.Column<Guid>(type: "uuid", nullable: false), + customer_number = table.Column<string>(type: "text", nullable: true), + name = table.Column<string>(type: "text", nullable: true), + description = table.Column<string>(type: "text", nullable: true), + address1 = table.Column<string>(type: "text", nullable: true), + address2 = table.Column<string>(type: "text", nullable: true), + postal_code = table.Column<string>(type: "text", nullable: true), + postal_city = table.Column<string>(type: "text", nullable: true), + country = table.Column<string>(type: "text", nullable: true), + phone = table.Column<string>(type: "text", nullable: true), + email = table.Column<string>(type: "text", nullable: true), + vat_number = table.Column<string>(type: "text", nullable: true), + org_number = table.Column<string>(type: "text", nullable: true), + default_reference = table.Column<string>(type: "text", nullable: true), + website = table.Column<string>(type: "text", nullable: true), + currency = table.Column<string>(type: "text", nullable: true), + project_id = table.Column<Guid>(type: "uuid", nullable: true), + created_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: false), + modified_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: true), + deleted = table.Column<bool>(type: "boolean", nullable: false), + user_id = table.Column<Guid>(type: "uuid", nullable: true), + tenant_id = table.Column<Guid>(type: "uuid", nullable: true), + modified_by_id = table.Column<Guid>(type: "uuid", nullable: true), + created_by_id = table.Column<Guid>(type: "uuid", nullable: true), + deleted_by_id = table.Column<Guid>(type: "uuid", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_customers", x => x.id); + table.ForeignKey( + name: "fk_customers_projects_project_id", + column: x => x.project_id, + principalTable: "projects", + principalColumn: "id"); + table.ForeignKey( + name: "fk_customers_users_user_id", + column: x => x.user_id, + principalTable: "users", + principalColumn: "id"); + }); + + migrationBuilder.CreateTable( + name: "project_labels", + columns: table => new + { + id = table.Column<Guid>(type: "uuid", nullable: false), + value = table.Column<string>(type: "text", nullable: true), + color = table.Column<string>(type: "text", nullable: true), + project_id = table.Column<Guid>(type: "uuid", nullable: true), + created_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: false), + modified_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: true), + deleted = table.Column<bool>(type: "boolean", nullable: false), + user_id = table.Column<Guid>(type: "uuid", nullable: true), + tenant_id = table.Column<Guid>(type: "uuid", nullable: true), + modified_by_id = table.Column<Guid>(type: "uuid", nullable: true), + created_by_id = table.Column<Guid>(type: "uuid", nullable: true), + deleted_by_id = table.Column<Guid>(type: "uuid", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_project_labels", x => x.id); + table.ForeignKey( + name: "fk_project_labels_projects_project_id", + column: x => x.project_id, + principalTable: "projects", + principalColumn: "id"); + }); + + migrationBuilder.CreateTable( + name: "project_member", + columns: table => new + { + id = table.Column<Guid>(type: "uuid", nullable: false), + project_id = table.Column<Guid>(type: "uuid", nullable: true), + user_id = table.Column<Guid>(type: "uuid", nullable: true), + role = table.Column<int>(type: "integer", nullable: false), + created_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: false), + modified_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: true), + deleted = table.Column<bool>(type: "boolean", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_project_member", x => x.id); + table.ForeignKey( + name: "fk_project_member_projects_project_id", + column: x => x.project_id, + principalTable: "projects", + principalColumn: "id"); + table.ForeignKey( + name: "fk_project_member_users_user_id", + column: x => x.user_id, + principalTable: "users", + principalColumn: "id"); + }); + + migrationBuilder.CreateTable( + name: "customer_contacts", + columns: table => new + { + id = table.Column<Guid>(type: "uuid", nullable: false), + customer_id = table.Column<Guid>(type: "uuid", nullable: true), + first_name = table.Column<string>(type: "text", nullable: true), + last_name = table.Column<string>(type: "text", nullable: true), + email = table.Column<string>(type: "text", nullable: true), + phone = table.Column<string>(type: "text", nullable: true), + work_title = table.Column<string>(type: "text", nullable: true), + note = table.Column<string>(type: "text", nullable: true), + created_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: false), + modified_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: true), + deleted = table.Column<bool>(type: "boolean", nullable: false), + user_id = table.Column<Guid>(type: "uuid", nullable: true), + tenant_id = table.Column<Guid>(type: "uuid", nullable: true), + modified_by_id = table.Column<Guid>(type: "uuid", nullable: true), + created_by_id = table.Column<Guid>(type: "uuid", nullable: true), + deleted_by_id = table.Column<Guid>(type: "uuid", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_customer_contacts", x => x.id); + table.ForeignKey( + name: "fk_customer_contacts_customers_customer_id", + column: x => x.customer_id, + principalTable: "customers", + principalColumn: "id"); + }); + + migrationBuilder.CreateTable( + name: "customer_customer_group", + columns: table => new + { + customers_id = table.Column<Guid>(type: "uuid", nullable: false), + groups_id = table.Column<Guid>(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_customer_customer_group", x => new { x.customers_id, x.groups_id }); + table.ForeignKey( + name: "fk_customer_customer_group_customer_groups_groups_id", + column: x => x.groups_id, + principalTable: "customer_groups", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_customer_customer_group_customers_customers_id", + column: x => x.customers_id, + principalTable: "customers", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "customer_events", + columns: table => new + { + id = table.Column<Guid>(type: "uuid", nullable: false), + customer_id = table.Column<Guid>(type: "uuid", nullable: true), + title = table.Column<string>(type: "text", nullable: true), + note = table.Column<string>(type: "text", nullable: true), + created_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: false), + modified_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: true), + deleted = table.Column<bool>(type: "boolean", nullable: false), + user_id = table.Column<Guid>(type: "uuid", nullable: true), + tenant_id = table.Column<Guid>(type: "uuid", nullable: true), + modified_by_id = table.Column<Guid>(type: "uuid", nullable: true), + created_by_id = table.Column<Guid>(type: "uuid", nullable: true), + deleted_by_id = table.Column<Guid>(type: "uuid", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("pk_customer_events", x => x.id); + table.ForeignKey( + name: "fk_customer_events_customers_customer_id", + column: x => x.customer_id, + principalTable: "customers", + principalColumn: "id"); + }); + + migrationBuilder.CreateIndex( + name: "ix_customer_contacts_customer_id", + table: "customer_contacts", + column: "customer_id"); + + migrationBuilder.CreateIndex( + name: "ix_customer_customer_group_groups_id", + table: "customer_customer_group", + column: "groups_id"); + + migrationBuilder.CreateIndex( + name: "ix_customer_events_customer_id", + table: "customer_events", + column: "customer_id"); + + migrationBuilder.CreateIndex( + name: "ix_customers_project_id", + table: "customers", + column: "project_id"); + + migrationBuilder.CreateIndex( + name: "ix_customers_user_id", + table: "customers", + column: "user_id"); + + migrationBuilder.CreateIndex( + name: "ix_project_labels_project_id", + table: "project_labels", + column: "project_id"); + + migrationBuilder.CreateIndex( + name: "ix_project_member_project_id", + table: "project_member", + column: "project_id"); + + migrationBuilder.CreateIndex( + name: "ix_project_member_user_id", + table: "project_member", + column: "user_id"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "customer_contacts"); + + migrationBuilder.DropTable( + name: "customer_customer_group"); + + migrationBuilder.DropTable( + name: "customer_events"); + + migrationBuilder.DropTable( + name: "project_labels"); + + migrationBuilder.DropTable( + name: "project_member"); + + migrationBuilder.DropTable( + name: "customer_groups"); + + migrationBuilder.DropTable( + name: "customers"); + + migrationBuilder.DropTable( + name: "projects"); + } + } +} diff --git a/code/api/src/Migrations/20221030081459_DeletedAt.Designer.cs b/code/api/src/Migrations/20221030081459_DeletedAt.Designer.cs new file mode 100644 index 0000000..78f9454 --- /dev/null +++ b/code/api/src/Migrations/20221030081459_DeletedAt.Designer.cs @@ -0,0 +1,1126 @@ +// <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("20221030081459_DeletedAt")] + partial class DeletedAt + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.10") + .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("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?>("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + 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?>("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + 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?>("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property<string>("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property<string>("ORGNumber") + .HasColumnType("text") + .HasColumnName("org_number"); + + 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?>("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>("VATNumber") + .HasColumnType("text") + .HasColumnName("vat_number"); + + b.Property<string>("Website") + .HasColumnType("text") + .HasColumnName("website"); + + b.HasKey("Id") + .HasName("pk_customers"); + + b.HasIndex("ProjectId") + .HasDatabaseName("ix_customers_project_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?>("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + 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?>("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + 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?>("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + 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.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?>("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + 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?>("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property<DateTime?>("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property<Guid?>("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + 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.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?>("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property<bool>("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property<DateTime?>("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property<Guid?>("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property<DateTime?>("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property<Guid?>("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + 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.ToTable("customer_groups", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ForgotPasswordRequest", 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_forgot_password_requests"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_forgot_password_requests_user_id"); + + b.ToTable("forgot_password_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?>("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property<bool>("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property<DateTime?>("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property<Guid?>("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property<string>("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property<DateTime?>("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property<Guid?>("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + 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.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?>("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property<bool>("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property<DateTime?>("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property<Guid?>("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property<DateTime?>("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property<Guid?>("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + 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.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_member"); + + b.HasIndex("ProjectId") + .HasDatabaseName("ix_project_member_project_id"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_project_member_user_id"); + + b.ToTable("project_member", (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?>("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property<bool>("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property<DateTime?>("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property<Guid?>("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + 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?>("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + 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_tenants"); + + 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?>("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property<bool>("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property<DateTime?>("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property<Guid?>("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property<DateTime?>("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property<Guid?>("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + 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.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?>("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property<bool>("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property<DateTime?>("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property<Guid?>("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property<string>("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property<DateTime?>("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property<Guid?>("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + 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.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?>("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property<bool>("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property<DateTime?>("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property<Guid?>("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property<DateTime?>("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property<Guid?>("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + 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("TimeEntryId") + .HasDatabaseName("ix_time_labels_time_entry_id"); + + b.ToTable("time_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<string>("Username") + .HasColumnType("text") + .HasColumnName("username"); + + b.HasKey("Id") + .HasName("pk_users"); + + 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("TenantUser", b => + { + b.Property<Guid>("TenantsId") + .HasColumnType("uuid") + .HasColumnName("tenants_id"); + + b.Property<Guid>("UsersId") + .HasColumnType("uuid") + .HasColumnName("users_id"); + + b.HasKey("TenantsId", "UsersId") + .HasName("pk_tenant_user"); + + b.HasIndex("UsersId") + .HasDatabaseName("ix_tenant_user_users_id"); + + b.ToTable("tenant_user", (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("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.Project", null) + .WithMany("Customers") + .HasForeignKey("ProjectId") + .HasConstraintName("fk_customers_projects_project_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "Owner") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_customers_users_user_id"); + + b.Navigation("Owner"); + }); + + 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.Navigation("Customer"); + }); + + 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.Navigation("Customer"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ForgotPasswordRequest", 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"); + + b.Navigation("User"); + }); + + 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.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_member_projects_project_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_project_member_users_user_id"); + + b.Navigation("Project"); + + b.Navigation("User"); + }); + + 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.Navigation("Category"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeLabel", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.TimeEntry", null) + .WithMany("Labels") + .HasForeignKey("TimeEntryId") + .HasConstraintName("fk_time_labels_time_entries_time_entry_id"); + }); + + modelBuilder.Entity("TenantUser", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", null) + .WithMany() + .HasForeignKey("TenantsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_tenant_user_tenants_tenants_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", null) + .WithMany() + .HasForeignKey("UsersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_tenant_user_users_users_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("Customers"); + + b.Navigation("Labels"); + + b.Navigation("Members"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeEntry", b => + { + b.Navigation("Labels"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/code/api/src/Migrations/20221030081459_DeletedAt.cs b/code/api/src/Migrations/20221030081459_DeletedAt.cs new file mode 100644 index 0000000..8132a6b --- /dev/null +++ b/code/api/src/Migrations/20221030081459_DeletedAt.cs @@ -0,0 +1,146 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace IOL.GreatOffice.Api.Migrations +{ + public partial class DeletedAt : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn<DateTime>( + name: "deleted_at", + table: "users", + type: "timestamp with time zone", + nullable: true); + + migrationBuilder.AddColumn<DateTime>( + name: "deleted_at", + table: "time_labels", + type: "timestamp with time zone", + nullable: true); + + migrationBuilder.AddColumn<DateTime>( + name: "deleted_at", + table: "time_entries", + type: "timestamp with time zone", + nullable: true); + + migrationBuilder.AddColumn<DateTime>( + name: "deleted_at", + table: "time_categories", + type: "timestamp with time zone", + nullable: true); + + migrationBuilder.AddColumn<DateTime>( + name: "deleted_at", + table: "tenants", + type: "timestamp with time zone", + nullable: true); + + migrationBuilder.AddColumn<DateTime>( + name: "deleted_at", + table: "projects", + type: "timestamp with time zone", + nullable: true); + + migrationBuilder.AddColumn<DateTime>( + name: "deleted_at", + table: "project_member", + type: "timestamp with time zone", + nullable: true); + + migrationBuilder.AddColumn<DateTime>( + name: "deleted_at", + table: "project_labels", + type: "timestamp with time zone", + nullable: true); + + migrationBuilder.AddColumn<DateTime>( + name: "deleted_at", + table: "customers", + type: "timestamp with time zone", + nullable: true); + + migrationBuilder.AddColumn<DateTime>( + name: "deleted_at", + table: "customer_groups", + type: "timestamp with time zone", + nullable: true); + + migrationBuilder.AddColumn<DateTime>( + name: "deleted_at", + table: "customer_events", + type: "timestamp with time zone", + nullable: true); + + migrationBuilder.AddColumn<DateTime>( + name: "deleted_at", + table: "customer_contacts", + type: "timestamp with time zone", + nullable: true); + + migrationBuilder.AddColumn<DateTime>( + name: "deleted_at", + table: "api_access_tokens", + type: "timestamp with time zone", + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "deleted_at", + table: "users"); + + migrationBuilder.DropColumn( + name: "deleted_at", + table: "time_labels"); + + migrationBuilder.DropColumn( + name: "deleted_at", + table: "time_entries"); + + migrationBuilder.DropColumn( + name: "deleted_at", + table: "time_categories"); + + migrationBuilder.DropColumn( + name: "deleted_at", + table: "tenants"); + + migrationBuilder.DropColumn( + name: "deleted_at", + table: "projects"); + + migrationBuilder.DropColumn( + name: "deleted_at", + table: "project_member"); + + migrationBuilder.DropColumn( + name: "deleted_at", + table: "project_labels"); + + migrationBuilder.DropColumn( + name: "deleted_at", + table: "customers"); + + migrationBuilder.DropColumn( + name: "deleted_at", + table: "customer_groups"); + + migrationBuilder.DropColumn( + name: "deleted_at", + table: "customer_events"); + + migrationBuilder.DropColumn( + name: "deleted_at", + table: "customer_contacts"); + + migrationBuilder.DropColumn( + name: "deleted_at", + table: "api_access_tokens"); + } + } +} diff --git a/code/api/src/Migrations/20221030084716_MinorChanges.Designer.cs b/code/api/src/Migrations/20221030084716_MinorChanges.Designer.cs new file mode 100644 index 0000000..1527d91 --- /dev/null +++ b/code/api/src/Migrations/20221030084716_MinorChanges.Designer.cs @@ -0,0 +1,1126 @@ +// <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("20221030084716_MinorChanges")] + partial class MinorChanges + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.10") + .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("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?>("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + 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?>("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + 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?>("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property<string>("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property<string>("ORGNumber") + .HasColumnType("text") + .HasColumnName("org_number"); + + 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?>("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>("VATNumber") + .HasColumnType("text") + .HasColumnName("vat_number"); + + b.Property<string>("Website") + .HasColumnType("text") + .HasColumnName("website"); + + b.HasKey("Id") + .HasName("pk_customers"); + + b.HasIndex("ProjectId") + .HasDatabaseName("ix_customers_project_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?>("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + 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?>("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + 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?>("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + 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.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?>("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + 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?>("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property<DateTime?>("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property<Guid?>("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + 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.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?>("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property<bool>("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property<DateTime?>("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property<Guid?>("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property<DateTime?>("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property<Guid?>("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + 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.ToTable("customer_groups", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ForgotPasswordRequest", 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_forgot_password_requests"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_forgot_password_requests_user_id"); + + b.ToTable("forgot_password_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?>("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property<bool>("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property<DateTime?>("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property<Guid?>("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property<string>("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property<DateTime?>("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property<Guid?>("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + 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.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?>("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property<bool>("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property<DateTime?>("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property<Guid?>("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property<DateTime?>("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property<Guid?>("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + 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.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?>("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property<bool>("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property<DateTime?>("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property<Guid?>("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + 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?>("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + 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_tenants"); + + 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?>("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property<bool>("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property<DateTime?>("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property<Guid?>("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property<DateTime?>("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property<Guid?>("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + 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.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?>("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property<bool>("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property<DateTime?>("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property<Guid?>("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property<string>("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property<DateTime?>("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property<Guid?>("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + 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.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?>("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property<bool>("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property<DateTime?>("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property<Guid?>("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property<DateTime?>("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property<Guid?>("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + 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("TimeEntryId") + .HasDatabaseName("ix_time_labels_time_entry_id"); + + b.ToTable("time_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<string>("Username") + .HasColumnType("text") + .HasColumnName("username"); + + b.HasKey("Id") + .HasName("pk_users"); + + 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("TenantUser", b => + { + b.Property<Guid>("TenantsId") + .HasColumnType("uuid") + .HasColumnName("tenants_id"); + + b.Property<Guid>("UsersId") + .HasColumnType("uuid") + .HasColumnName("users_id"); + + b.HasKey("TenantsId", "UsersId") + .HasName("pk_tenant_user"); + + b.HasIndex("UsersId") + .HasDatabaseName("ix_tenant_user_users_id"); + + b.ToTable("tenant_user", (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("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.Project", null) + .WithMany("Customers") + .HasForeignKey("ProjectId") + .HasConstraintName("fk_customers_projects_project_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "Owner") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_customers_users_user_id"); + + b.Navigation("Owner"); + }); + + 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.Navigation("Customer"); + }); + + 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.Navigation("Customer"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ForgotPasswordRequest", 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"); + + b.Navigation("User"); + }); + + 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.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.TimeEntry", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.TimeCategory", "Category") + .WithMany() + .HasForeignKey("CategoryId") + .HasConstraintName("fk_time_entries_time_categories_category_id"); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeLabel", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.TimeEntry", null) + .WithMany("Labels") + .HasForeignKey("TimeEntryId") + .HasConstraintName("fk_time_labels_time_entries_time_entry_id"); + }); + + modelBuilder.Entity("TenantUser", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", null) + .WithMany() + .HasForeignKey("TenantsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_tenant_user_tenants_tenants_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", null) + .WithMany() + .HasForeignKey("UsersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_tenant_user_users_users_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("Customers"); + + b.Navigation("Labels"); + + b.Navigation("Members"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeEntry", b => + { + b.Navigation("Labels"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/code/api/src/Migrations/20221030084716_MinorChanges.cs b/code/api/src/Migrations/20221030084716_MinorChanges.cs new file mode 100644 index 0000000..e708caf --- /dev/null +++ b/code/api/src/Migrations/20221030084716_MinorChanges.cs @@ -0,0 +1,105 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace IOL.GreatOffice.Api.Migrations +{ + public partial class MinorChanges : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "fk_project_member_projects_project_id", + table: "project_member"); + + migrationBuilder.DropForeignKey( + name: "fk_project_member_users_user_id", + table: "project_member"); + + migrationBuilder.DropPrimaryKey( + name: "pk_project_member", + table: "project_member"); + + migrationBuilder.RenameTable( + name: "project_member", + newName: "project_members"); + + migrationBuilder.RenameIndex( + name: "ix_project_member_user_id", + table: "project_members", + newName: "ix_project_members_user_id"); + + migrationBuilder.RenameIndex( + name: "ix_project_member_project_id", + table: "project_members", + newName: "ix_project_members_project_id"); + + migrationBuilder.AddPrimaryKey( + name: "pk_project_members", + table: "project_members", + column: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_project_members_projects_project_id", + table: "project_members", + column: "project_id", + principalTable: "projects", + principalColumn: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_project_members_users_user_id", + table: "project_members", + column: "user_id", + principalTable: "users", + principalColumn: "id"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "fk_project_members_projects_project_id", + table: "project_members"); + + migrationBuilder.DropForeignKey( + name: "fk_project_members_users_user_id", + table: "project_members"); + + migrationBuilder.DropPrimaryKey( + name: "pk_project_members", + table: "project_members"); + + migrationBuilder.RenameTable( + name: "project_members", + newName: "project_member"); + + migrationBuilder.RenameIndex( + name: "ix_project_members_user_id", + table: "project_member", + newName: "ix_project_member_user_id"); + + migrationBuilder.RenameIndex( + name: "ix_project_members_project_id", + table: "project_member", + newName: "ix_project_member_project_id"); + + migrationBuilder.AddPrimaryKey( + name: "pk_project_member", + table: "project_member", + column: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_project_member_projects_project_id", + table: "project_member", + column: "project_id", + principalTable: "projects", + principalColumn: "id"); + + migrationBuilder.AddForeignKey( + name: "fk_project_member_users_user_id", + table: "project_member", + column: "user_id", + principalTable: "users", + principalColumn: "id"); + } + } +} diff --git a/code/api/src/Migrations/20221030090557_MoreMinorChanges.Designer.cs b/code/api/src/Migrations/20221030090557_MoreMinorChanges.Designer.cs new file mode 100644 index 0000000..ff1dfb2 --- /dev/null +++ b/code/api/src/Migrations/20221030090557_MoreMinorChanges.Designer.cs @@ -0,0 +1,1148 @@ +// <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("20221030090557_MoreMinorChanges")] + partial class MoreMinorChanges + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "6.0.10") + .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?>("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + 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?>("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + 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?>("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property<string>("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property<string>("ORGNumber") + .HasColumnType("text") + .HasColumnName("org_number"); + + 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("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?>("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + 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?>("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + 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?>("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + 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.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?>("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + 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?>("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property<DateTime?>("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property<Guid?>("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + 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.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?>("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property<bool>("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property<DateTime?>("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property<Guid?>("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property<DateTime?>("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property<Guid?>("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + 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.ToTable("customer_groups", (string)null); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ForgotPasswordRequest", 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_forgot_password_requests"); + + b.HasIndex("UserId") + .HasDatabaseName("ix_forgot_password_requests_user_id"); + + b.ToTable("forgot_password_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?>("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property<bool>("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property<DateTime?>("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property<Guid?>("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property<string>("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property<DateTime?>("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property<Guid?>("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + 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.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?>("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property<bool>("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property<DateTime?>("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property<Guid?>("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property<DateTime?>("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property<Guid?>("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + 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.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?>("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property<bool>("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property<DateTime?>("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property<Guid?>("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + 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?>("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + 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_tenants"); + + 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?>("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property<bool>("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property<DateTime?>("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property<Guid?>("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property<DateTime?>("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property<Guid?>("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + 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.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?>("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property<bool>("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property<DateTime?>("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property<Guid?>("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property<string>("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property<DateTime?>("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property<Guid?>("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + 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.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?>("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property<bool>("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property<DateTime?>("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property<Guid?>("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property<DateTime?>("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property<Guid?>("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + 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("TimeEntryId") + .HasDatabaseName("ix_time_labels_time_entry_id"); + + b.ToTable("time_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<string>("Username") + .HasColumnType("text") + .HasColumnName("username"); + + b.HasKey("Id") + .HasName("pk_users"); + + 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("TenantUser", b => + { + b.Property<Guid>("TenantsId") + .HasColumnType("uuid") + .HasColumnName("tenants_id"); + + b.Property<Guid>("UsersId") + .HasColumnType("uuid") + .HasColumnName("users_id"); + + b.HasKey("TenantsId", "UsersId") + .HasName("pk_tenant_user"); + + b.HasIndex("UsersId") + .HasDatabaseName("ix_tenant_user_users_id"); + + b.ToTable("tenant_user", (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("UserId") + .HasConstraintName("fk_customers_users_user_id"); + + b.Navigation("Owner"); + }); + + 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.Navigation("Customer"); + }); + + 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.Navigation("Customer"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ForgotPasswordRequest", 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"); + + b.Navigation("User"); + }); + + 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.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.TimeEntry", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.TimeCategory", "Category") + .WithMany() + .HasForeignKey("CategoryId") + .HasConstraintName("fk_time_entries_time_categories_category_id"); + + b.Navigation("Category"); + }); + + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.TimeLabel", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.TimeEntry", null) + .WithMany("Labels") + .HasForeignKey("TimeEntryId") + .HasConstraintName("fk_time_labels_time_entries_time_entry_id"); + }); + + modelBuilder.Entity("TenantUser", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.Tenant", null) + .WithMany() + .HasForeignKey("TenantsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_tenant_user_tenants_tenants_id"); + + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", null) + .WithMany() + .HasForeignKey("UsersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_tenant_user_users_users_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.TimeEntry", b => + { + b.Navigation("Labels"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/code/api/src/Migrations/20221030090557_MoreMinorChanges.cs b/code/api/src/Migrations/20221030090557_MoreMinorChanges.cs new file mode 100644 index 0000000..5ebd664 --- /dev/null +++ b/code/api/src/Migrations/20221030090557_MoreMinorChanges.cs @@ -0,0 +1,78 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace IOL.GreatOffice.Api.Migrations +{ + public partial class MoreMinorChanges : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "fk_customers_projects_project_id", + table: "customers"); + + migrationBuilder.DropIndex( + name: "ix_customers_project_id", + table: "customers"); + + migrationBuilder.DropColumn( + name: "project_id", + table: "customers"); + + migrationBuilder.CreateTable( + name: "customer_project", + columns: table => new + { + customers_id = table.Column<Guid>(type: "uuid", nullable: false), + projects_id = table.Column<Guid>(type: "uuid", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("pk_customer_project", x => new { x.customers_id, x.projects_id }); + table.ForeignKey( + name: "fk_customer_project_customers_customers_id", + column: x => x.customers_id, + principalTable: "customers", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "fk_customer_project_projects_projects_id", + column: x => x.projects_id, + principalTable: "projects", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "ix_customer_project_projects_id", + table: "customer_project", + column: "projects_id"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "customer_project"); + + migrationBuilder.AddColumn<Guid>( + name: "project_id", + table: "customers", + type: "uuid", + nullable: true); + + migrationBuilder.CreateIndex( + name: "ix_customers_project_id", + table: "customers", + column: "project_id"); + + migrationBuilder.AddForeignKey( + name: "fk_customers_projects_project_id", + table: "customers", + column: "project_id", + principalTable: "projects", + principalColumn: "id"); + } + } +} diff --git a/code/api/src/Migrations/AppDbContextModelSnapshot.cs b/code/api/src/Migrations/AppDbContextModelSnapshot.cs index cc4bf72..7a534ad 100644 --- a/code/api/src/Migrations/AppDbContextModelSnapshot.cs +++ b/code/api/src/Migrations/AppDbContextModelSnapshot.cs @@ -1,6 +1,6 @@ // <auto-generated /> using System; -using IOL.GreatOffice.Api.Data; +using IOL.GreatOffice.Api.Data.Database; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; @@ -10,18 +10,56 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; namespace IOL.GreatOffice.Api.Migrations { - [DbContext(typeof(AppDbContext))] + [DbContext(typeof(MainAppDatabase))] partial class AppDbContextModelSnapshot : ModelSnapshot { protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "6.0.7") + .HasAnnotation("ProductVersion", "6.0.10") .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") @@ -53,6 +91,10 @@ namespace IOL.GreatOffice.Api.Migrations .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"); @@ -74,6 +116,315 @@ namespace IOL.GreatOffice.Api.Migrations 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?>("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + 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?>("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + 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?>("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + b.Property<string>("Name") + .HasColumnType("text") + .HasColumnName("name"); + + b.Property<string>("ORGNumber") + .HasColumnType("text") + .HasColumnName("org_number"); + + 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("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?>("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + 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?>("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + 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?>("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + 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.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?>("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + 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?>("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property<DateTime?>("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property<Guid?>("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + 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.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?>("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property<bool>("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property<DateTime?>("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property<Guid?>("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property<DateTime?>("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property<Guid?>("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + 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.ToTable("customer_groups", (string)null); + }); + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ForgotPasswordRequest", b => { b.Property<Guid>("Id") @@ -98,6 +449,182 @@ namespace IOL.GreatOffice.Api.Migrations b.ToTable("forgot_password_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?>("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property<bool>("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property<DateTime?>("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property<Guid?>("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property<string>("Description") + .HasColumnType("text") + .HasColumnName("description"); + + b.Property<DateTime?>("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property<Guid?>("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + 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.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?>("CreatedById") + .HasColumnType("uuid") + .HasColumnName("created_by_id"); + + b.Property<bool>("Deleted") + .HasColumnType("boolean") + .HasColumnName("deleted"); + + b.Property<DateTime?>("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + + b.Property<Guid?>("DeletedById") + .HasColumnType("uuid") + .HasColumnName("deleted_by_id"); + + b.Property<DateTime?>("ModifiedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("modified_at"); + + b.Property<Guid?>("ModifiedById") + .HasColumnType("uuid") + .HasColumnName("modified_by_id"); + + 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.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") @@ -121,6 +648,10 @@ namespace IOL.GreatOffice.Api.Migrations .HasColumnType("boolean") .HasColumnName("deleted"); + b.Property<DateTime?>("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + b.Property<Guid?>("DeletedById") .HasColumnType("uuid") .HasColumnName("deleted_by_id"); @@ -186,6 +717,10 @@ namespace IOL.GreatOffice.Api.Migrations .HasColumnType("boolean") .HasColumnName("deleted"); + b.Property<DateTime?>("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + b.Property<Guid?>("DeletedById") .HasColumnType("uuid") .HasColumnName("deleted_by_id"); @@ -239,6 +774,10 @@ namespace IOL.GreatOffice.Api.Migrations .HasColumnType("boolean") .HasColumnName("deleted"); + b.Property<DateTime?>("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + b.Property<Guid?>("DeletedById") .HasColumnType("uuid") .HasColumnName("deleted_by_id"); @@ -303,6 +842,10 @@ namespace IOL.GreatOffice.Api.Migrations .HasColumnType("boolean") .HasColumnName("deleted"); + b.Property<DateTime?>("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + b.Property<Guid?>("DeletedById") .HasColumnType("uuid") .HasColumnName("deleted_by_id"); @@ -355,6 +898,10 @@ namespace IOL.GreatOffice.Api.Migrations .HasColumnType("boolean") .HasColumnName("deleted"); + b.Property<DateTime?>("DeletedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("deleted_at"); + b.Property<string>("Email") .HasColumnType("text") .HasColumnName("email"); @@ -427,6 +974,40 @@ namespace IOL.GreatOffice.Api.Migrations b.ToTable("tenant_user", (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") @@ -437,6 +1018,36 @@ namespace IOL.GreatOffice.Api.Migrations b.Navigation("User"); }); + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.Customer", b => + { + b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "Owner") + .WithMany() + .HasForeignKey("UserId") + .HasConstraintName("fk_customers_users_user_id"); + + b.Navigation("Owner"); + }); + + 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.Navigation("Customer"); + }); + + 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.Navigation("Customer"); + }); + modelBuilder.Entity("IOL.GreatOffice.Api.Data.Database.ForgotPasswordRequest", b => { b.HasOne("IOL.GreatOffice.Api.Data.Database.User", "User") @@ -449,6 +1060,33 @@ namespace IOL.GreatOffice.Api.Migrations b.Navigation("User"); }); + 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.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.TimeEntry", b => { b.HasOne("IOL.GreatOffice.Api.Data.Database.TimeCategory", "Category") @@ -484,6 +1122,20 @@ namespace IOL.GreatOffice.Api.Migrations .HasConstraintName("fk_tenant_user_users_users_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.TimeEntry", b => { b.Navigation("Labels"); diff --git a/code/api/src/Program.cs b/code/api/src/Program.cs index ebd686f..21c0a7d 100644 --- a/code/api/src/Program.cs +++ b/code/api/src/Program.cs @@ -98,7 +98,7 @@ public static class Program builder.Services .AddDataProtection() .ProtectKeysWithCertificate(configuration.CERT1()) - .PersistKeysToDbContext<AppDbContext>(); + .PersistKeysToDbContext<MainAppDatabase>(); builder.Services.Configure(JsonSettings.Default); builder.Services.AddQuartz(options => { @@ -129,7 +129,7 @@ public static class Program }) .AddScheme<AuthenticationSchemeOptions, BasicAuthenticationHandler>(AppConstants.BASIC_AUTH_SCHEME, default); - builder.Services.AddDbContext<AppDbContext>(options => { + builder.Services.AddDbContext<MainAppDatabase>(options => { options.UseNpgsql(builder.Configuration.GetAppDatabaseConnectionString(vaultService.GetCurrentAppConfiguration), npgsqlDbContextOptionsBuilder => { npgsqlDbContextOptionsBuilder.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery); @@ -150,6 +150,7 @@ public static class Program builder.Services.AddSwaggerGen(options => { options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, "IOL.GreatOffice.Api.xml")); options.UseApiEndpoints(); + options.ConfigurePagination(); options.OperationFilter<SwaggerDefaultValues>(); options.SwaggerDoc(ApiSpecV1.Document.VersionName, ApiSpecV1.Document.OpenApiInfo); options.AddSecurityDefinition("Basic", @@ -175,7 +176,11 @@ public static class Program } }); }); - + builder.Services.AddPagination(options => { + options.DefaultSize = 50; + options.MaxSize = 100; + options.CanChangeSizeFromQuery = true; + }); builder.Services .AddControllers() .AddDataAnnotationsLocalization() diff --git a/code/api/src/Resources/SharedResources.cs b/code/api/src/Resources/SharedResources.cs new file mode 100644 index 0000000..4b8ec2f --- /dev/null +++ b/code/api/src/Resources/SharedResources.cs @@ -0,0 +1,4 @@ +namespace IOL.GreatOffice.Api; + +public class SharedResources +{ }
\ No newline at end of file diff --git a/code/api/src/Resources/SharedResources.en.Designer.cs b/code/api/src/Resources/SharedResources.en.Designer.cs new file mode 100644 index 0000000..4eb786f --- /dev/null +++ b/code/api/src/Resources/SharedResources.en.Designer.cs @@ -0,0 +1,78 @@ +//------------------------------------------------------------------------------ +// <auto-generated> +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// </auto-generated> +//------------------------------------------------------------------------------ + +namespace IOL.GreatOffice.Api.Resources { + using System; + + + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class SharedResources_en { + + private static System.Resources.ResourceManager resourceMan; + + private static System.Globalization.CultureInfo resourceCulture; + + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal SharedResources_en() { + } + + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] + internal static System.Resources.ResourceManager ResourceManager { + get { + if (object.Equals(null, resourceMan)) { + System.Resources.ResourceManager temp = new System.Resources.ResourceManager("IOL.GreatOffice.Api.Resources.SharedResources_en", typeof(SharedResources_en).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] + internal static System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + internal static string Name_is_a_required_field { + get { + return ResourceManager.GetString("Name is a required field", resourceCulture); + } + } + + internal static string Start_is_a_required_field { + get { + return ResourceManager.GetString("Start is a required field", resourceCulture); + } + } + + internal static string Invalid_form { + get { + return ResourceManager.GetString("Invalid form", resourceCulture); + } + } + + internal static string One_or_more_validation_errors_occured { + get { + return ResourceManager.GetString("One or more validation errors occured", resourceCulture); + } + } + + internal static string One_or_more_fields_is_invalid { + get { + return ResourceManager.GetString("One or more fields is invalid", resourceCulture); + } + } + } +} diff --git a/code/api/src/Resources/SharedResources.en.resx b/code/api/src/Resources/SharedResources.en.resx new file mode 100644 index 0000000..5987e8d --- /dev/null +++ b/code/api/src/Resources/SharedResources.en.resx @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="utf-8"?> + +<root> + <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> + <xsd:element name="root" msdata:IsDataSet="true"> + + </xsd:element> + </xsd:schema> + <resheader name="resmimetype"> + <value>text/microsoft-resx</value> + </resheader> + <resheader name="version"> + <value>1.3</value> + </resheader> + <resheader name="reader"> + <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <resheader name="writer"> + <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <data name="Name is a required field" xml:space="preserve"> + <value>Name is a required field</value> + <comment>Name is a required field</comment> + </data> + <data name="Start is a required field" xml:space="preserve"> + <value>Start is a required field</value> + <comment>Start is a required field</comment> + </data> + <data name="Invalid form" xml:space="preserve"> + <value>Invalid form</value> + <comment>Invalid form</comment> + </data> + <data name="One or more validation errors occured" xml:space="preserve"> + <value>One or more validation errors occured</value> + <comment>One or more validation errors occured</comment> + </data> + <data name="One or more fields is invalid" xml:space="preserve"> + <value>One or more fields is invalid</value> + <comment>One or more fields is invalid</comment> + </data> +</root>
\ No newline at end of file diff --git a/code/api/src/Resources/SharedResources.nb.Designer.cs b/code/api/src/Resources/SharedResources.nb.Designer.cs new file mode 100644 index 0000000..114c86f --- /dev/null +++ b/code/api/src/Resources/SharedResources.nb.Designer.cs @@ -0,0 +1,72 @@ +//------------------------------------------------------------------------------ +// <auto-generated> +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// </auto-generated> +//------------------------------------------------------------------------------ + +namespace IOL.GreatOffice.Api.Resources { + using System; + + + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class SharedResources_nb { + + private static System.Resources.ResourceManager resourceMan; + + private static System.Globalization.CultureInfo resourceCulture; + + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal SharedResources_nb() { + } + + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] + internal static System.Resources.ResourceManager ResourceManager { + get { + if (object.Equals(null, resourceMan)) { + System.Resources.ResourceManager temp = new System.Resources.ResourceManager("IOL.GreatOffice.Api.Resources.SharedResources_nb", typeof(SharedResources_nb).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] + internal static System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + internal static string Name_is_a_required_field { + get { + return ResourceManager.GetString("Name is a required field", resourceCulture); + } + } + + internal static string Start_is_a_required_field { + get { + return ResourceManager.GetString("Start is a required field", resourceCulture); + } + } + + internal static string Invalid_form { + get { + return ResourceManager.GetString("Invalid form", resourceCulture); + } + } + + internal static string One_or_more_fields_is_invalid { + get { + return ResourceManager.GetString("One or more fields is invalid", resourceCulture); + } + } + } +} diff --git a/code/api/src/Resources/SharedResources.nb.resx b/code/api/src/Resources/SharedResources.nb.resx new file mode 100644 index 0000000..6c8937b --- /dev/null +++ b/code/api/src/Resources/SharedResources.nb.resx @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="utf-8"?> + +<root> + <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" + xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> + <xsd:element name="root" msdata:IsDataSet="true"> + + </xsd:element> + </xsd:schema> + <resheader name="resmimetype"> + <value>text/microsoft-resx</value> + </resheader> + <resheader name="version"> + <value>1.3</value> + </resheader> + <resheader name="reader"> + <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + </value> + </resheader> + <resheader name="writer"> + <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + </value> + </resheader> + <data name="Name is a required field" xml:space="preserve"> + <value>Navn er et påkrevd felt</value> + <comment>Name is a required field</comment> + </data> + <data name="Start is a required field" xml:space="preserve"> + <value>Start er et påkrevd felt</value> + <comment>Start is a required field</comment> + </data> + <data name="Invalid form" xml:space="preserve"> + <value>Ugyldig skjema</value> + <comment>Invalid form</comment> + </data> + <data name="One or more fields is invalid" xml:space="preserve"> + <comment>One or more fields is invalid</comment> + <value>En eller flere felt er ugyldige</value> + </data> +</root>
\ No newline at end of file diff --git a/code/api/src/Resources/SharedResources.resx b/code/api/src/Resources/SharedResources.resx new file mode 100644 index 0000000..377994b --- /dev/null +++ b/code/api/src/Resources/SharedResources.resx @@ -0,0 +1,30 @@ +<root> + <resheader name="resmimetype"> + <value>text/microsoft-resx</value> + </resheader> + <resheader name="version"> + <value>1.3</value> + </resheader> + <resheader name="reader"> + <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <resheader name="writer"> + <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> + </resheader> + <data name="Name is a required field" xml:space="preserve"> + <value>Name is a required field</value> + <comment>Name is a required field</comment> + </data> + <data name="Start is a required field" xml:space="preserve"> + <value>Start is a required field</value> + <comment>Start is a required field</comment> + </data> + <data name="Invalid form" xml:space="preserve"> + <value>Invalid form</value> + <comment>Invalid form</comment> + </data> + <data name="One or more fields is invalid" xml:space="preserve"> + <value>One or more fields is invalid</value> + <comment>One or more fields is invalid</comment> + </data> +</root>
\ No newline at end of file diff --git a/code/api/src/Services/MailService.cs b/code/api/src/Services/MailService.cs index c08cb84..b55b48f 100644 --- a/code/api/src/Services/MailService.cs +++ b/code/api/src/Services/MailService.cs @@ -41,7 +41,7 @@ public class MailService new JsonSerializerOptions { WriteIndented = true }); - + _logger.LogDebug("SmtpClient was instansiated with the following configuration\n" + configurationString); smtpClient.Send(message); diff --git a/code/api/src/Services/PasswordResetService.cs b/code/api/src/Services/PasswordResetService.cs index 1b4f147..76eb2fe 100644 --- a/code/api/src/Services/PasswordResetService.cs +++ b/code/api/src/Services/PasswordResetService.cs @@ -2,26 +2,25 @@ namespace IOL.GreatOffice.Api.Services; public class PasswordResetService { - private readonly AppDbContext _context; + private readonly MainAppDatabase _database; private readonly MailService _mailService; private readonly AppConfiguration _configuration; private readonly ILogger<PasswordResetService> _logger; - public PasswordResetService( - AppDbContext context, + MainAppDatabase database, VaultService vaultService, ILogger<PasswordResetService> logger, MailService mailService ) { - _context = context; + _database = database; _configuration = vaultService.GetCurrentAppConfiguration(); _logger = logger; _mailService = mailService; } public async Task<ForgotPasswordRequest> GetRequestAsync(Guid id, CancellationToken cancellationToken = default) { - var request = await _context.ForgotPasswordRequests + var request = await _database.ForgotPasswordRequests .Include(c => c.User) .SingleOrDefaultAsync(c => c.Id == id, cancellationToken); if (request == default) { @@ -31,21 +30,21 @@ 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 = _context.Users.SingleOrDefault(c => c.Id == request.User.Id); + 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"); } user.HashAndSetPassword(newPassword); - _context.Users.Update(user); - await _context.SaveChangesAsync(cancellationToken); + _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; @@ -55,8 +54,8 @@ public class PasswordResetService public async Task AddRequestAsync(User user, TimeZoneInfo requestTz, CancellationToken cancellationToken = default) { await DeleteRequestsForUserAsync(user.Id, cancellationToken); var request = new ForgotPasswordRequest(user); - _context.ForgotPasswordRequests.Add(request); - await _context.SaveChangesAsync(cancellationToken); + _database.ForgotPasswordRequests.Add(request); + await _database.SaveChangesAsync(cancellationToken); var portalUrl = _configuration.PORTAL_URL; var emailFromAddress = _configuration.EMAIL_FROM_ADDRESS; var emailFromDisplayName = _configuration.EMAIL_FROM_DISPLAY_NAME; @@ -89,27 +88,27 @@ If you did not request a password reset, no action is required. } public async Task DeleteRequestsForUserAsync(Guid userId, CancellationToken cancellationToken = default) { - var requestsToRemove = _context.ForgotPasswordRequests.Where(c => c.UserId == userId).ToList(); + var requestsToRemove = _database.ForgotPasswordRequests.Where(c => c.UserId == userId).ToList(); if (!requestsToRemove.Any()) return; - _context.ForgotPasswordRequests.RemoveRange(requestsToRemove); - await _context.SaveChangesAsync(cancellationToken); + _database.ForgotPasswordRequests.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 _context.ForgotPasswordRequests.Where(c => c.IsExpired)) { + foreach (var request in _database.ForgotPasswordRequests.Where(c => c.IsExpired)) { if (!request.IsExpired) { continue; } - _context.ForgotPasswordRequests.Remove(request); + _database.ForgotPasswordRequests.Remove(request); deleteCount++; _logger.LogInformation($"Marking password reset request with id: {request.Id} for deletion, expiration date was {request.ExpirationDate}."); } - await _context.SaveChangesAsync(cancellationToken); + await _database.SaveChangesAsync(cancellationToken); _logger.LogInformation($"Deleted {deleteCount} stale password reset requests."); } }
\ No newline at end of file diff --git a/code/api/src/Services/UserService.cs b/code/api/src/Services/UserService.cs index 6db663a..30231e8 100644 --- a/code/api/src/Services/UserService.cs +++ b/code/api/src/Services/UserService.cs @@ -2,49 +2,49 @@ namespace IOL.GreatOffice.Api.Services; public class UserService { - private readonly PasswordResetService _passwordResetService; + private readonly PasswordResetService _passwordResetService; - /// <summary> - /// Provides methods to perform common operations on user data. - /// </summary> - /// <param name="passwordResetService"></param> - public UserService(PasswordResetService passwordResetService) { - _passwordResetService = passwordResetService; - } + /// <summary> + /// Provides methods to perform common operations on user data. + /// </summary> + /// <param name="passwordResetService"></param> + public UserService(PasswordResetService passwordResetService) { + _passwordResetService = passwordResetService; + } - /// <summary> - /// Log in a user. - /// </summary> - /// <param name="httpContext"></param> - /// <param name="user"></param> - /// <param name="persist"></param> - public async Task LogInUser(HttpContext httpContext, User user, bool persist = false) { - var claims = new List<Claim> { - new(AppClaims.USER_ID, user.Id.ToString()), - new(AppClaims.NAME, user.Username), - }; + /// <summary> + /// Log in a user. + /// </summary> + /// <param name="httpContext"></param> + /// <param name="user"></param> + /// <param name="persist"></param> + public async Task LogInUser(HttpContext httpContext, User user, bool persist = false) { + var claims = new List<Claim> { + new(AppClaims.USER_ID, user.Id.ToString()), + new(AppClaims.NAME, user.Username), + }; - var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme); - var principal = new ClaimsPrincipal(identity); - var authenticationProperties = new AuthenticationProperties { - AllowRefresh = true, - IssuedUtc = DateTimeOffset.UtcNow, - }; + var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme); + var principal = new ClaimsPrincipal(identity); + var authenticationProperties = new AuthenticationProperties { + AllowRefresh = true, + IssuedUtc = DateTimeOffset.UtcNow, + }; - if (persist) { - authenticationProperties.ExpiresUtc = DateTimeOffset.UtcNow.AddMonths(6); - authenticationProperties.IsPersistent = true; - } + if (persist) { + authenticationProperties.ExpiresUtc = DateTimeOffset.UtcNow.AddMonths(6); + authenticationProperties.IsPersistent = true; + } - await httpContext.SignInAsync(principal, authenticationProperties); - await _passwordResetService.DeleteRequestsForUserAsync(user.Id); - } + await httpContext.SignInAsync(principal, authenticationProperties); + await _passwordResetService.DeleteRequestsForUserAsync(user.Id); + } - /// <summary> - /// Log out a user. - /// </summary> - /// <param name="httpContext"></param> - public async Task LogOutUser(HttpContext httpContext) { - await httpContext.SignOutAsync(); - } -} + /// <summary> + /// Log out a user. + /// </summary> + /// <param name="httpContext"></param> + public async Task LogOutUser(HttpContext httpContext) { + await httpContext.SignOutAsync(); + } +}
\ No newline at end of file diff --git a/code/api/src/Services/VaultService.cs b/code/api/src/Services/VaultService.cs index 2f8d46e..3d58608 100644 --- a/code/api/src/Services/VaultService.cs +++ b/code/api/src/Services/VaultService.cs @@ -4,155 +4,169 @@ namespace IOL.GreatOffice.Api.Services; public class VaultService { - private readonly HttpClient _client; - private readonly IMemoryCache _cache; - private readonly IConfiguration _configuration; - private readonly ILogger<VaultService> _logger; - private int CACHE_TTL { get; set; } + private readonly HttpClient _client; + private readonly IMemoryCache _cache; + private readonly IConfiguration _configuration; + private readonly ILogger<VaultService> _logger; + private int CACHE_TTL { get; set; } - public VaultService(HttpClient client, IConfiguration configuration, IMemoryCache cache, ILogger<VaultService> logger) { - var token = configuration.GetValue<string>(AppEnvironmentVariables.VAULT_TOKEN); - var vaultUrl = configuration.GetValue<string>(AppEnvironmentVariables.VAULT_URL); - CACHE_TTL = configuration.GetValue(AppEnvironmentVariables.VAULT_CACHE_TTL, 60 * 60 * 12); - if (token.IsNullOrWhiteSpace()) throw new ApplicationException("VAULT_TOKEN is empty"); - if (vaultUrl.IsNullOrWhiteSpace()) throw new ApplicationException("VAULT_URL is empty"); - client.DefaultRequestHeaders.Add("X-Vault-Token", token); - client.BaseAddress = new Uri(vaultUrl); - _client = client; - _cache = cache; - _configuration = configuration; - _logger = logger; - } + public VaultService(HttpClient client, IConfiguration configuration, IMemoryCache cache, ILogger<VaultService> logger) { + var token = configuration.GetValue<string>(AppEnvironmentVariables.VAULT_TOKEN); + var vaultUrl = configuration.GetValue<string>(AppEnvironmentVariables.VAULT_URL); + CACHE_TTL = configuration.GetValue(AppEnvironmentVariables.VAULT_CACHE_TTL, 60 * 60 * 12); + if (token.IsNullOrWhiteSpace()) throw new ApplicationException("VAULT_TOKEN is empty"); + if (vaultUrl.IsNullOrWhiteSpace()) throw new ApplicationException("VAULT_URL is empty"); + client.DefaultRequestHeaders.Add("X-Vault-Token", token); + client.BaseAddress = new Uri(vaultUrl); + _client = client; + _cache = cache; + _configuration = configuration; + _logger = logger; + } - public static object Data { get; set; } + public T Get<T>(string path) { + var result = _cache.GetOrCreate(AppConstants.VAULT_CACHE_KEY, + cacheEntry => { + cacheEntry.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(CACHE_TTL); + var getSecretResponse = _client.GetFromJsonAsync<GetSecretResponse<T>>("/v1/kv/data/" + path).Result; + if (getSecretResponse == null) { + return default; + } - public T Get<T>(string path) { - var result = _cache.GetOrCreate(AppConstants.VAULT_CACHE_KEY, - cacheEntry => { - cacheEntry.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(CACHE_TTL); - var getSecretResponse = _client.GetFromJsonAsync<GetSecretResponse<T>>("/v1/kv/data/" + path).Result; - - if (getSecretResponse == null) { - return default; - } + Log.Debug("Setting new vault cache, " + new { + PATH = path, + CACHE_TTL, + Data = JsonSerializer.Serialize(getSecretResponse.Data.Data) + }); + return getSecretResponse.Data.Data ?? default; + }); + return result; + } - Log.Debug("Setting new Vault cache, " - + new { - PATH = path, - CACHE_TTL, - Data = JsonSerializer.Serialize(getSecretResponse.Data.Data) - }); - return getSecretResponse.Data.Data ?? default; - }); - return result; - } + public T Refresh<T>(string path) { + _cache.Remove(AppConstants.VAULT_CACHE_KEY); + CACHE_TTL = _configuration.GetValue(AppEnvironmentVariables.VAULT_CACHE_TTL, 60 * 60 * 12); + return Get<T>(path); + } - public T Refresh<T>(string path) { - _cache.Remove(AppConstants.VAULT_CACHE_KEY); - CACHE_TTL = _configuration.GetValue(AppEnvironmentVariables.VAULT_CACHE_TTL, 60 * 60 * 12); - return Get<T>(path); - } + public async Task<RenewTokenResponse> RenewTokenAsync<T>(string token) { + var response = await _client.PostAsJsonAsync("v1/auth/token/renew", + new { + Token = token + }); + if (response.IsSuccessStatusCode) { + return await response.Content.ReadFromJsonAsync<RenewTokenResponse>(); + } - public async Task<RenewTokenResponse> RenewTokenAsync<T>(string token) { - var response = await _client.PostAsJsonAsync("v1/auth/token/renew", - new { - Token = token - }); - if (response.IsSuccessStatusCode) { - return await response.Content.ReadFromJsonAsync<RenewTokenResponse>(); - } + return default; + } - return default; - } + public AppConfiguration GetCurrentAppConfiguration() { + var isInFlightMode = true; + if (isInFlightMode) { + return new AppConfiguration() { + DB_HOST = "localhost", + DB_PORT = "5432", + DB_NAME = "greatoffice_ivar_dev", + DB_PASSWORD = "ivar123", + DB_USER = "postgres", + QUARTZ_DB_HOST = "localhost", + QUARTZ_DB_PORT = "5432", + QUARTZ_DB_NAME = "greatoffice_quartz_ivar_dev", + QUARTZ_DB_PASSWORD = "ivar123", + QUARTZ_DB_USER = "postgres", + APP_CERT = "MIII2QIBAzCCCJ8GCSqGSIb3DQEHAaCCCJAEggiMMIIIiDCCAz8GCSqGSIb3DQEHBqCCAzAwggMsAgEAMIIDJQYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQI1JebRQOOJekCAggAgIIC+FTMxILwgOWxknEWvucjaHTtf/KUcSPwc/zWg0RoFzu03o1vZkStztz92L2J+nnNrQti7WEx0C/h5ug67qCQAdzkjNHZE9wn1pI2EqaCwKYwZnOTmyJDLV86xQlH9QYTs4/L1F2qTwpKdBoB2lNyTswYgZ8WNYY65fbFKUUVIaHkReGnma/ERm8F38Ymp7cudqQ4G6sh6E+JFSo2IfcTFb260fWO/iMDU3GryNsaUl4amT4aQfsSrNtf6ODy8Ivh7tJeLbR6bqzMtsKPzavT5ZbA6AP2GYAQSVejNP79lqmtoOs8+dz7HaJdxORBESYi02z+j2rb4ssI+ZPx+YtGvgpr79gVf3qM1/VX4ROzLDUUJGWS2RnRDBBY/d0uMqftndUHSPMFuhfvgBUXdzLqhoEqYNMTP3BpOyBQ7f26soLKrc3zup2WIn8WSnQFLi2D8cWPVPS9iAb0EgQ5cBjuaLz2aX1WVMCSzya7a6Z93rLxUf9s3+PEy75ibhoh/cJvAlCMTfiVAhJOaIroR1K4MKkO23ylyLHv49/2GYIaZ8n0WRO57fM5jDUhOfti+ZzPM6hrSJkRSla+pr8DFlpqOqObksGwxGGTqq6ZvWon19kXesFl5n640uJBu7Viq8IdxGAbX/aRkZNlvja7sOgfiNz3Hxomz7DWwgWLKaNKlFSqFMzsTUye+mUByC1AfEn14/SYyyxRTB99PmItxWFyjo9nOsxH5riz7tPTPxUXzhVb4eDt7PjY+ZsEKTC3a/LFqf3k5MWk+qc4p0Kx1sGaGEAPCCE04mZ7NOdqk6dhoP46FNUEh8CmxDDVaMSdThrvyzv9KrclwQnRMJz7BJWVXUemyQl3aModepXIhvLv90nH1qzYlFDQ0H6rxzCB4f1l//GoWPyYFBxGh6UrkunXWx2fopR87zi2OF3azxqscF/qLVU4FHKzhMrec7eE0/dk3d+0If/AxQ4p7Cso92i/5n0Bsg5DWc4EIWBuldodsjVxxq7dKxinKJkwggVBBgkqhkiG9w0BBwGgggUyBIIFLjCCBSowggUmBgsqhkiG9w0BDAoBAqCCBO4wggTqMBwGCiqGSIb3DQEMAQMwDgQIb6GEBS5DxrkCAggABIIEyHcCXqJMUG8t0PhvDwf0dHZo6SiA2WsLz1hM+KgNBrE8YwuXEZTGYzfHy85cEWNB2WLV5kxgu/vtifCnnlS1bvc2kMKT3dIFER/7hOqRh8pNvzMoeNc4zNkiEB1ZXxlctUKDsQozbLUhnRATwNyeaMkt3B0KQuRaMxGuA9riRISnmGd1K5GTm3VZ0I7e6vDqXCllLzfOQ+aoz8WIOFJ1aoN2E5+bDTtcr18xYJMd+kNOMjMcbg5f9kmNZAk1MBRuiEWtUjMhRySYWk1Km/y5WHRNRShHTae/E4ifmpLuUKsfOjX7T/4RDWg8rYCnxUpLfCln+omQ9t0gFSN+Et7Dw+cyW48Kkrw6StnRz/AeLxo3SU/PAXVazmAa6ZkuNe+uasvTniYM+enw4hgcXPzTu90R40fTGHO1Sp8EV3IbvrtwFu9kjCxtgleLQ139HTtpWXjVZ0o1ikmn2uN4f73gxKIKxmSX4xZZN6IDOze3OOY1aalUIzkbwFAYCV74zEL05dJzo3GOOJfdQsp2GNJPfkcAcuMPMvi+mieBk6XjKDCj95b41hSWDqfuMUgPh3xm3/felVD1HRNO9NF0RscosP02NLi44TcNz4LX9j/E9PHpNFF+W4ba1w7v7h4P5/leQFRP7+H90fPHA2M8UOHZ4AwmwdA5sHYXBoXkVS3snbVzhzkvW5GblFn0l1AFj8AO0HLCwGSumZ1uUEvEA021hmluHbs62iIiOYJbacbcT/TUpO5/2tFMPKr042LmpQFDIuEfrurLTC3r1iXuS6fkWbf2IxdjTrtL61AtPqtFagKSGsyHViO7nPu6yhbhTbmQJ4G0t++b6h18RPS+3muwrnSxgAz6OmbBWybNKOlAyTjd4JO3hfCaQ+K/mO2Q9TnSUOTgeobXXZsOEdltPXFJExQ7+cqkr4gKdPeoTZEcv8jRoS+NHasZIvMPGrwYnvOuSJ09qAwtIcvhGaPkEmTZ6b3wQl0mnTMPCQHXGTXztucB0O33kbn8sClfs6P6dg0GdR6ZnNFacwIpe8T8PmLg5q8bu5FL1eNo4+ijzC64lrZkKeRKKT1vBtZfcGQTvE8TTdQQS5MkKcptfL/3HVE9VopNZlzryJGYj89GMeQ1PfABi1Ovs5gjfro+0xBbtVuAWbP8dM5ugozO1//vjTMZYwXml4nIFkHuGe7R4ZpKRIVjVy7RScelCuQ0yNMGIzx/5Dz3FQXWq1Jii669Oxs/R7iupwo+f6O9XmCJAGXIw5a11Yw9cULptVNc9rPHrauAOeNpE77aSQRKEOJZADvdLB8cXjpXFf2mvzFib69Cuks8QxktAK7Yk3fke1CJpoIb75d8iHkY21epOtqavTppezEd+0uq5RJThH+/nMyZVhRI3tSJ0kVDc1HVX2bTquWcXtniuZNOWYklLxKPfQNho8n0pHRk22UmB8DOxMjnAyt3s7xBNpujU+I7D30lK9N3PH4U+Oyc9pIWc2T7pFILvvToxoE3l2flg6eHnBd6a7ENDVbz1ELwwmt36QQAVQytEngTBYkorbJcQC6e2r/RqoqpP2N4dB7+2ZDMVw97VBraMl7ELaYdf9SOdzuis2engAojSiUUO/gdKGaJGnnldOSi5rvnxs+iMjElMCMGCSqGSIb3DQEJFTEWBBRSLC58imQohokANg6rVjq9KE/MxjAxMCEwCQYFKw4DAhoFAAQUILUGtKvqxRY/ywrrlxKrsuLiNLwECCWv9bVh/bZZAgIIAA==" + }; + } - public AppConfiguration GetCurrentAppConfiguration() { - var path = _configuration.GetValue<string>(AppEnvironmentVariables.MAIN_CONFIG_SHEET); - var result = Get<AppConfiguration>(path); - var overwrites = new { - DB_HOST = _configuration.GetValue("OVERWRITE_DB_HOST", string.Empty), - DB_PORT = _configuration.GetValue("OVERWRITE_DB_PORT", string.Empty), - DB_USER = _configuration.GetValue("OVERWRITE_DB_USER", string.Empty), - DB_PASSWORD = _configuration.GetValue("OVERWRITE_DB_PASSWORD", string.Empty), - DB_NAME = _configuration.GetValue("OVERWRITE_DB_NAME", string.Empty), - }; - if (overwrites.DB_HOST.HasValue()) { - _logger.LogInformation("OVERWRITE_DB_HOST is specified, using it's value: " + overwrites.DB_HOST); - result.DB_HOST = overwrites.DB_HOST; - } + var path = _configuration.GetValue<string>(AppEnvironmentVariables.MAIN_CONFIG_SHEET); + var result = Get<AppConfiguration>(path); + var overwrites = new { + DB_HOST = _configuration.GetValue("OVERWRITE_DB_HOST", string.Empty), + DB_PORT = _configuration.GetValue("OVERWRITE_DB_PORT", string.Empty), + DB_USER = _configuration.GetValue("OVERWRITE_DB_USER", string.Empty), + DB_PASSWORD = _configuration.GetValue("OVERWRITE_DB_PASSWORD", string.Empty), + DB_NAME = _configuration.GetValue("OVERWRITE_DB_NAME", string.Empty), + }; - if (overwrites.DB_PORT.HasValue()) { - _logger.LogInformation("OVERWRITE_DB_PORT is specified, using it's value: " + overwrites.DB_PORT); - result.DB_PORT = overwrites.DB_PORT; - } - - if (overwrites.DB_USER.HasValue()) { - _logger.LogInformation("OVERWRITE_DB_USER is specified, using it's value: " + overwrites.DB_USER); - result.DB_USER = overwrites.DB_USER; - } - - if (overwrites.DB_PASSWORD.HasValue()) { - _logger.LogInformation("OVERWRITE_DB_PASSWORD is specified, using it's value: " + "(redacted)"); - result.DB_PASSWORD = overwrites.DB_PASSWORD; - } - - if (overwrites.DB_NAME.HasValue()) { - _logger.LogInformation("OVERWRITE_DB_NAME is specified, using it's value: " + overwrites.DB_NAME); - result.DB_NAME = overwrites.DB_NAME; - } - - return result; - } + if (overwrites.DB_HOST.HasValue()) { + _logger.LogInformation("OVERWRITE_DB_HOST is specified, using it's value: {DB_HOST}", overwrites.DB_HOST); + result.DB_HOST = overwrites.DB_HOST; + } - public AppConfiguration RefreshCurrentAppConfiguration() { - var path = _configuration.GetValue<string>(AppEnvironmentVariables.MAIN_CONFIG_SHEET); - return Refresh<AppConfiguration>(path); - } + if (overwrites.DB_PORT.HasValue()) { + _logger.LogInformation("OVERWRITE_DB_PORT is specified, using it's value: {DB_PORT}", overwrites.DB_PORT); + result.DB_PORT = overwrites.DB_PORT; + } - public class RenewTokenResponse - { - public Guid RequestId { get; set; } - public string LeaseId { get; set; } - public bool Renewable { get; set; } - public long LeaseDuration { get; set; } - public object Data { get; set; } - public object WrapInfo { get; set; } - public List<string> Warnings { get; set; } - public Auth Auth { get; set; } - } + if (overwrites.DB_USER.HasValue()) { + _logger.LogInformation("OVERWRITE_DB_USER is specified, using it's value: {DB_USER}", overwrites.DB_USER); + result.DB_USER = overwrites.DB_USER; + } - public class Auth - { - public string ClientToken { get; set; } - public string Accessor { get; set; } - public List<string> Policies { get; set; } - public List<string> TokenPolicies { get; set; } - public object Metadata { get; set; } - public long LeaseDuration { get; set; } - public bool Renewable { get; set; } - public string EntityId { get; set; } - public string TokenType { get; set; } - public bool Orphan { get; set; } - public object MfaRequirement { get; set; } - public long NumUses { get; set; } - } + if (overwrites.DB_PASSWORD.HasValue()) { + _logger.LogInformation("OVERWRITE_DB_PASSWORD is specified, using it's value: (redacted)"); + result.DB_PASSWORD = overwrites.DB_PASSWORD; + } - public class GetSecretResponse<T> - { - public VaultSecret<T> Data { get; set; } - } + if (overwrites.DB_NAME.HasValue()) { + _logger.LogInformation("OVERWRITE_DB_NAME is specified, using it's value: {DB_NAME}", overwrites.DB_NAME); + result.DB_NAME = overwrites.DB_NAME; + } - public class VaultSecret<T> - { - public T Data { get; set; } - public VaultSecretMetadata Metadata { get; set; } - } + return result; + } - public class VaultSecretMetadata - { - public DateTimeOffset CreatedTime { get; set; } - public object CustomMetadata { get; set; } - public string DeletionTime { get; set; } - public bool Destroyed { get; set; } - public long Version { get; set; } - } -} + public AppConfiguration RefreshCurrentAppConfiguration() { + var path = _configuration.GetValue<string>(AppEnvironmentVariables.MAIN_CONFIG_SHEET); + return Refresh<AppConfiguration>(path); + } + + public class RenewTokenResponse + { + public Guid RequestId { get; set; } + public string LeaseId { get; set; } + public bool Renewable { get; set; } + public long LeaseDuration { get; set; } + public object Data { get; set; } + public object WrapInfo { get; set; } + public List<string> Warnings { get; set; } + public Auth Auth { get; set; } + } + + public class Auth + { + public string ClientToken { get; set; } + public string Accessor { get; set; } + public List<string> Policies { get; set; } + public List<string> TokenPolicies { get; set; } + public object Metadata { get; set; } + public long LeaseDuration { get; set; } + public bool Renewable { get; set; } + public string EntityId { get; set; } + public string TokenType { get; set; } + public bool Orphan { get; set; } + public object MfaRequirement { get; set; } + public long NumUses { get; set; } + } + + public class GetSecretResponse<T> + { + public VaultSecret<T> Data { get; set; } + } + + public class VaultSecret<T> + { + public T Data { get; set; } + public VaultSecretMetadata Metadata { get; set; } + } + + public class VaultSecretMetadata + { + public DateTimeOffset CreatedTime { get; set; } + public object CustomMetadata { get; set; } + public string DeletionTime { get; set; } + public bool Destroyed { get; set; } + public long Version { get; set; } + } +}
\ No newline at end of file diff --git a/code/api/src/Utilities/BasicAuthenticationHandler.cs b/code/api/src/Utilities/BasicAuthenticationHandler.cs index 6138193..b0a2d1a 100644 --- a/code/api/src/Utilities/BasicAuthenticationHandler.cs +++ b/code/api/src/Utilities/BasicAuthenticationHandler.cs @@ -7,7 +7,7 @@ namespace IOL.GreatOffice.Api.Utilities; public class BasicAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions> { - private readonly AppDbContext _context; + private readonly MainAppDatabase _context; private readonly AppConfiguration _configuration; private readonly ILogger<BasicAuthenticationHandler> _logger; @@ -16,7 +16,7 @@ public class BasicAuthenticationHandler : AuthenticationHandler<AuthenticationSc ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, - AppDbContext context, + MainAppDatabase context, VaultService vaultService ) : base(options, logger, encoder, clock) { diff --git a/code/api/src/Utilities/DateTimeExtensions.cs b/code/api/src/Utilities/DateTimeExtensions.cs new file mode 100644 index 0000000..d25e9a8 --- /dev/null +++ b/code/api/src/Utilities/DateTimeExtensions.cs @@ -0,0 +1,8 @@ +namespace IOL.GreatOffice.Api.Utilities; + +public static class DateTimeExtensions +{ + public static bool IsNullOrEmpty(this DateTime dateTime) { + return (dateTime == default); + } +}
\ No newline at end of file diff --git a/code/api/src/Utilities/QueryableExtensions.cs b/code/api/src/Utilities/QueryableExtensions.cs new file mode 100644 index 0000000..bf2bf3b --- /dev/null +++ b/code/api/src/Utilities/QueryableExtensions.cs @@ -0,0 +1,12 @@ +namespace IOL.GreatOffice.Api.Utilities; + +public static class QueryableExtensions +{ + public static IQueryable<T> ForTenant<T>(this IQueryable<T> queryable, LoggedInUserModel loggedInUserModel) where T : BaseWithOwner { + return queryable.Where(c => c.TenantId == loggedInUserModel.TenantId); + } + + public static IQueryable<T> ForUser<T>(this IQueryable<T> queryable, LoggedInUserModel loggedInUserModel) where T : BaseWithOwner { + return queryable.Where(c => c.UserId == loggedInUserModel.Id); + } +}
\ No newline at end of file diff --git a/code/api/src/Utilities/Validators.cs b/code/api/src/Utilities/Validators.cs new file mode 100644 index 0000000..fa3a144 --- /dev/null +++ b/code/api/src/Utilities/Validators.cs @@ -0,0 +1,12 @@ +using System.Text.RegularExpressions; + +namespace IOL.GreatOffice.Api.Utilities; + +public static class Validators +{ + private static readonly Regex EMAIL_REGEX = new(""); + + public static bool IsValidEmail(this string email) { + return EMAIL_REGEX.IsMatch(email); + } +}
\ No newline at end of file |
