aboutsummaryrefslogtreecommitdiffstats
path: root/code/api/src/Models
diff options
context:
space:
mode:
Diffstat (limited to 'code/api/src/Models')
-rw-r--r--code/api/src/Models/Database/Api/ApiAccessToken.cs12
-rw-r--r--code/api/src/Models/Database/Base.cs22
-rw-r--r--code/api/src/Models/Database/BaseWithOwner.cs40
-rw-r--r--code/api/src/Models/Database/Customer/Customer.cs29
-rw-r--r--code/api/src/Models/Database/Customer/CustomerContact.cs12
-rw-r--r--code/api/src/Models/Database/Customer/CustomerEvent.cs8
-rw-r--r--code/api/src/Models/Database/Customer/CustomerGroup.cs7
-rw-r--r--code/api/src/Models/Database/Customer/CustomerGroupMembership.cs7
-rw-r--r--code/api/src/Models/Database/Internal/PasswordResetRequest.cs23
-rw-r--r--code/api/src/Models/Database/Internal/Tenant.cs12
-rw-r--r--code/api/src/Models/Database/Internal/User.cs44
-rw-r--r--code/api/src/Models/Database/MainAppDatabase.cs107
-rw-r--r--code/api/src/Models/Database/Project/Project.cs15
-rw-r--r--code/api/src/Models/Database/Project/ProjectLabel.cs8
-rw-r--r--code/api/src/Models/Database/Project/ProjectMember.cs8
-rw-r--r--code/api/src/Models/Database/Queues/ValidationEmail.cs8
-rw-r--r--code/api/src/Models/Database/Todo/Todo.cs17
-rw-r--r--code/api/src/Models/Database/Todo/TodoCollection.cs11
-rw-r--r--code/api/src/Models/Database/Todo/TodoCollectionAccessControl.cs12
-rw-r--r--code/api/src/Models/Database/Todo/TodoComment.cs8
-rw-r--r--code/api/src/Models/Database/Todo/TodoLabel.cs8
-rw-r--r--code/api/src/Models/Enums/FulfillPasswordResetRequestResult.cs8
-rw-r--r--code/api/src/Models/Enums/PasswordResetRequestStatus.cs6
-rw-r--r--code/api/src/Models/Enums/ProjectRole.cs9
-rw-r--r--code/api/src/Models/Enums/TodoClosingStatement.cs13
-rw-r--r--code/api/src/Models/Enums/TodoVisibility.cs10
-rw-r--r--code/api/src/Models/Misc/ApiSpecDocument.cs9
-rw-r--r--code/api/src/Models/Misc/AppConfiguration.cs118
-rw-r--r--code/api/src/Models/Misc/AppPath.cs23
-rw-r--r--code/api/src/Models/Misc/KnownProblemModel.cs26
-rw-r--r--code/api/src/Models/Misc/LoggedInUserModel.cs8
-rw-r--r--code/api/src/Models/Misc/RequestTimeZoneInfo.cs8
-rw-r--r--code/api/src/Models/Static/AppClaims.cs7
-rw-r--r--code/api/src/Models/Static/AppConstants.cs12
-rw-r--r--code/api/src/Models/Static/AppCookies.cs7
-rw-r--r--code/api/src/Models/Static/AppDateTime.cs16
-rw-r--r--code/api/src/Models/Static/AppEnvironmentVariables.cs21
-rw-r--r--code/api/src/Models/Static/AppHeaders.cs7
-rw-r--r--code/api/src/Models/Static/AppPaths.cs17
-rw-r--r--code/api/src/Models/Static/JsonSettings.cs11
40 files changed, 754 insertions, 0 deletions
diff --git a/code/api/src/Models/Database/Api/ApiAccessToken.cs b/code/api/src/Models/Database/Api/ApiAccessToken.cs
new file mode 100644
index 0000000..9359fc4
--- /dev/null
+++ b/code/api/src/Models/Database/Api/ApiAccessToken.cs
@@ -0,0 +1,12 @@
+namespace IOL.GreatOffice.Api.Data.Database;
+
+public class ApiAccessToken : Base
+{
+ public User User { get; set; }
+ public DateTime ExpiryDate { get; set; }
+ public bool AllowRead { get; set; }
+ public bool AllowCreate { get; set; }
+ public bool AllowUpdate { get; set; }
+ public bool AllowDelete { get; set; }
+ public bool HasExpired => ExpiryDate < AppDateTime.UtcNow;
+} \ No newline at end of file
diff --git a/code/api/src/Models/Database/Base.cs b/code/api/src/Models/Database/Base.cs
new file mode 100644
index 0000000..0b16b12
--- /dev/null
+++ b/code/api/src/Models/Database/Base.cs
@@ -0,0 +1,22 @@
+namespace IOL.GreatOffice.Api.Data.Database;
+
+public abstract class Base
+{
+ protected Base() {
+ Id = Guid.NewGuid();
+ CreatedAt = AppDateTime.UtcNow;
+ }
+
+ public Guid Id { get; init; }
+ public DateTime CreatedAt { get; init; }
+ public DateTime? ModifiedAt { get; private set; }
+ 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/Models/Database/BaseWithOwner.cs b/code/api/src/Models/Database/BaseWithOwner.cs
new file mode 100644
index 0000000..7e9f6c1
--- /dev/null
+++ b/code/api/src/Models/Database/BaseWithOwner.cs
@@ -0,0 +1,40 @@
+namespace IOL.GreatOffice.Api.Data.Database;
+
+/// <summary>
+/// Base class for all entities with ownership.
+/// </summary>
+public abstract class BaseWithOwner : Base
+{
+ protected BaseWithOwner() { }
+
+ protected BaseWithOwner(Guid createdBy) {
+ CreatedBy = createdBy;
+ }
+
+ protected BaseWithOwner(LoggedInUserModel loggedInUser) {
+ CreatedBy = loggedInUser.Id;
+ }
+
+ 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 User OwningUser { get; set; }
+ public Tenant OwningTenant { get; set; }
+
+ public void SetDeleted(Guid userId) {
+ DeletedBy = userId;
+ base.SetDeleted();
+ }
+
+ public void SetModified(Guid userId) {
+ ModifiedBy = userId;
+ base.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/Models/Database/Customer/Customer.cs b/code/api/src/Models/Database/Customer/Customer.cs
new file mode 100644
index 0000000..8e153c6
--- /dev/null
+++ b/code/api/src/Models/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 Guid? OwnerId { 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/Models/Database/Customer/CustomerContact.cs b/code/api/src/Models/Database/Customer/CustomerContact.cs
new file mode 100644
index 0000000..f5a951d
--- /dev/null
+++ b/code/api/src/Models/Database/Customer/CustomerContact.cs
@@ -0,0 +1,12 @@
+namespace IOL.GreatOffice.Api.Data.Database;
+
+public class CustomerContact : BaseWithOwner
+{
+ public Customer Customer { get; set; }
+ public string FirstName { get; set; }
+ public string LastName { get; set; }
+ public string Email { get; set; }
+ public string Phone { get; set; }
+ public string WorkTitle { get; set; }
+ public string Note { get; set; }
+}
diff --git a/code/api/src/Models/Database/Customer/CustomerEvent.cs b/code/api/src/Models/Database/Customer/CustomerEvent.cs
new file mode 100644
index 0000000..a87da4c
--- /dev/null
+++ b/code/api/src/Models/Database/Customer/CustomerEvent.cs
@@ -0,0 +1,8 @@
+namespace IOL.GreatOffice.Api.Data.Database;
+
+public class CustomerEvent : BaseWithOwner
+{
+ public Customer Customer { get; set; }
+ public string Title { get; set; }
+ public string Note { get; set; }
+}
diff --git a/code/api/src/Models/Database/Customer/CustomerGroup.cs b/code/api/src/Models/Database/Customer/CustomerGroup.cs
new file mode 100644
index 0000000..9438f3c
--- /dev/null
+++ b/code/api/src/Models/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/Models/Database/Customer/CustomerGroupMembership.cs b/code/api/src/Models/Database/Customer/CustomerGroupMembership.cs
new file mode 100644
index 0000000..ec0d4af
--- /dev/null
+++ b/code/api/src/Models/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/Models/Database/Internal/PasswordResetRequest.cs b/code/api/src/Models/Database/Internal/PasswordResetRequest.cs
new file mode 100644
index 0000000..ee73fd2
--- /dev/null
+++ b/code/api/src/Models/Database/Internal/PasswordResetRequest.cs
@@ -0,0 +1,23 @@
+namespace IOL.GreatOffice.Api.Data.Database;
+
+public class PasswordResetRequest
+{
+ public PasswordResetRequest() { }
+
+ public PasswordResetRequest(User user) {
+ CreatedAt = AppDateTime.UtcNow;
+ Id = Guid.NewGuid();
+ User = user;
+ }
+
+ public Guid Id { get; set; }
+ public Guid UserId { get; set; }
+ public User User { get; set; }
+ public DateTime CreatedAt { get; set; }
+
+ [NotMapped]
+ public DateTime ExpirationDate => CreatedAt.AddMinutes(15);
+
+ [NotMapped]
+ public bool IsExpired => DateTime.Compare(ExpirationDate, AppDateTime.UtcNow) < 0;
+}
diff --git a/code/api/src/Models/Database/Internal/Tenant.cs b/code/api/src/Models/Database/Internal/Tenant.cs
new file mode 100644
index 0000000..471164d
--- /dev/null
+++ b/code/api/src/Models/Database/Internal/Tenant.cs
@@ -0,0 +1,12 @@
+namespace IOL.GreatOffice.Api.Data.Database;
+
+public class Tenant : BaseWithOwner
+{
+ public string Slug { get; set; }
+ public string Name { get; set; }
+ public string Description { get; set; }
+ public string ContactEmail { get; set; }
+ public Guid MasterUserId { get; set; }
+ public string MasterUserPassword { get; set; }
+ public ICollection<User> Users { get; set; }
+} \ No newline at end of file
diff --git a/code/api/src/Models/Database/Internal/User.cs b/code/api/src/Models/Database/Internal/User.cs
new file mode 100644
index 0000000..f4d08ff
--- /dev/null
+++ b/code/api/src/Models/Database/Internal/User.cs
@@ -0,0 +1,44 @@
+namespace IOL.GreatOffice.Api.Data.Database;
+
+public class User : Base
+{
+ public User() { }
+
+ public User(string username) {
+ Username = username;
+ }
+
+ public string FirstName { get; set; }
+ public string LastName { get; set; }
+ public string Email { get; set; }
+ public string Username { get; set; }
+ public string Password { get; set; }
+ public DateTime EmailLastValidated { get; set; }
+ public ICollection<Tenant> Tenants { get; set; }
+ public Guid? DeletedBy { get; set; }
+
+ public string DisplayName(bool isForGreeting = false) {
+ if (!isForGreeting && FirstName.HasValue() && LastName.HasValue()) return FirstName + " " + LastName;
+ return FirstName.HasValue() ? FirstName : Username ?? Email;
+ }
+
+ public void HashAndSetPassword(string password) {
+ Password = PasswordHelper.HashPassword(password);
+ }
+
+ public bool VerifyPassword(string password) {
+ return PasswordHelper.Verify(password, Password);
+ }
+
+ public void SetDeleted(Guid userId) {
+ base.SetDeleted();
+ DeletedBy = userId;
+ }
+
+ public IEnumerable<Claim> DefaultClaims() {
+ return new Claim[] {
+ new(AppClaims.USER_ID, Id.ToString()),
+ new(AppClaims.NAME, Username),
+ };
+ }
+} \ No newline at end of file
diff --git a/code/api/src/Models/Database/MainAppDatabase.cs b/code/api/src/Models/Database/MainAppDatabase.cs
new file mode 100644
index 0000000..2b42fe0
--- /dev/null
+++ b/code/api/src/Models/Database/MainAppDatabase.cs
@@ -0,0 +1,107 @@
+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<PasswordResetRequest> PasswordResetRequests { 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; }
+ public DbSet<TodoLabel> TodoLabels { get; set; }
+ public DbSet<TodoCollectionAccessControl> TodoProjectAccessControls { get; set; }
+ public DbSet<TodoCollection> TodoProjects { get; set; }
+ public DbSet<TodoComment> TodoComments { get; set; }
+ public DbSet<Todo> Todos { get; set; }
+ public DbSet<ValidationEmail> ValidationEmails { get; set; }
+
+ protected override void OnModelCreating(ModelBuilder modelBuilder) {
+ modelBuilder.Entity<User>(e => {
+ e.HasMany(n => n.Tenants);
+ e.ToTable("users");
+ });
+ modelBuilder.Entity<PasswordResetRequest>(e => {
+ e.HasOne(c => c.User);
+ e.ToTable("password_reset_requests");
+ });
+ 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");
+ });
+ modelBuilder.Entity<Todo>(e => {
+ e.HasOne(n => n.Collection);
+ e.HasOne(n => n.AssignedTo);
+ e.HasOne(n => n.ClosedBy);
+ e.HasMany(n => n.Labels);
+ e.HasMany(n => n.Comments);
+ e.ToTable("todos");
+ });
+ modelBuilder.Entity<TodoCollection>(e => {
+ e.HasOne(n => n.Project);
+ e.HasMany(n => n.AccessControls);
+ e.ToTable("todo_collections");
+ });
+ modelBuilder.Entity<TodoComment>(e => {
+ e.HasOne(n => n.Todo);
+ e.ToTable("todo_comments");
+ });
+ modelBuilder.Entity<TodoLabel>(e => {
+ e.HasOne(n => n.Todo);
+ e.ToTable("todo_labels");
+ });
+ modelBuilder.Entity<TodoCollectionAccessControl>(e => {
+ e.HasOne(n => n.User);
+ e.HasOne(n => n.Collection);
+ e.ToTable("todo_collection_access_controls");
+ });
+ modelBuilder.Entity<ValidationEmail>(e => { e.ToTable("validation_emails"); });
+
+ base.OnModelCreating(modelBuilder);
+ }
+} \ No newline at end of file
diff --git a/code/api/src/Models/Database/Project/Project.cs b/code/api/src/Models/Database/Project/Project.cs
new file mode 100644
index 0000000..de9e2cb
--- /dev/null
+++ b/code/api/src/Models/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/Models/Database/Project/ProjectLabel.cs b/code/api/src/Models/Database/Project/ProjectLabel.cs
new file mode 100644
index 0000000..0e1dc5d
--- /dev/null
+++ b/code/api/src/Models/Database/Project/ProjectLabel.cs
@@ -0,0 +1,8 @@
+namespace IOL.GreatOffice.Api.Data.Database;
+
+public class ProjectLabel : BaseWithOwner
+{
+ public string Value { get; set; }
+ public string Color { get; set; }
+ public Project Project { get; set; }
+}
diff --git a/code/api/src/Models/Database/Project/ProjectMember.cs b/code/api/src/Models/Database/Project/ProjectMember.cs
new file mode 100644
index 0000000..a5e0682
--- /dev/null
+++ b/code/api/src/Models/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/Models/Database/Queues/ValidationEmail.cs b/code/api/src/Models/Database/Queues/ValidationEmail.cs
new file mode 100644
index 0000000..0457768
--- /dev/null
+++ b/code/api/src/Models/Database/Queues/ValidationEmail.cs
@@ -0,0 +1,8 @@
+namespace IOL.GreatOffice.Api.Data.Database;
+
+public class ValidationEmail
+{
+ public Guid Id { get; set; }
+ public DateTime EmailSentAt { get; set; }
+ public Guid UserId { get; set; }
+} \ No newline at end of file
diff --git a/code/api/src/Models/Database/Todo/Todo.cs b/code/api/src/Models/Database/Todo/Todo.cs
new file mode 100644
index 0000000..2d7f109
--- /dev/null
+++ b/code/api/src/Models/Database/Todo/Todo.cs
@@ -0,0 +1,17 @@
+namespace IOL.GreatOffice.Api.Data.Database;
+
+public class Todo : BaseWithOwner
+{
+ public string PublicId { get; set; }
+ public Guid? AssignedToId { get; set; }
+ public Guid? ClosedById { get; set; }
+ public Guid CollectionId { get; set; }
+ public DateTime? ClosedAt { get; set; }
+ public string Title { get; set; }
+ public string Description { get; set; }
+ public ICollection<TodoLabel> Labels { get; set; }
+ public ICollection<TodoComment> Comments { get; set; }
+ public User AssignedTo { get; set; }
+ public User ClosedBy { get; set; }
+ public TodoCollection Collection { get; set; }
+} \ No newline at end of file
diff --git a/code/api/src/Models/Database/Todo/TodoCollection.cs b/code/api/src/Models/Database/Todo/TodoCollection.cs
new file mode 100644
index 0000000..470e5e7
--- /dev/null
+++ b/code/api/src/Models/Database/Todo/TodoCollection.cs
@@ -0,0 +1,11 @@
+namespace IOL.GreatOffice.Api.Data.Database;
+
+public class TodoCollection : BaseWithOwner
+{
+ public string Name { get; set; }
+ public string Description { get; set; }
+ public TodoVisibility Visibility { get; set; }
+ public Guid? ProjectId { get; set; }
+ public Project Project { get; set; }
+ public ICollection<TodoCollectionAccessControl> AccessControls { get; set; }
+} \ No newline at end of file
diff --git a/code/api/src/Models/Database/Todo/TodoCollectionAccessControl.cs b/code/api/src/Models/Database/Todo/TodoCollectionAccessControl.cs
new file mode 100644
index 0000000..1676c06
--- /dev/null
+++ b/code/api/src/Models/Database/Todo/TodoCollectionAccessControl.cs
@@ -0,0 +1,12 @@
+namespace IOL.GreatOffice.Api.Data.Database;
+
+public class TodoCollectionAccessControl : Base
+{
+ public TodoCollection Collection { get; set; }
+ public User User { get; set; }
+ public Guid? UserId { get; set; }
+ public bool CanBrowse { get; set; }
+ public bool CanSubmit { get; set; }
+ public bool CanComment { get; set; }
+ public bool CanEdit { get; set; }
+} \ No newline at end of file
diff --git a/code/api/src/Models/Database/Todo/TodoComment.cs b/code/api/src/Models/Database/Todo/TodoComment.cs
new file mode 100644
index 0000000..32ac3a3
--- /dev/null
+++ b/code/api/src/Models/Database/Todo/TodoComment.cs
@@ -0,0 +1,8 @@
+namespace IOL.GreatOffice.Api.Data.Database;
+
+public class TodoComment : BaseWithOwner
+{
+ public string Value { get; set; }
+ public Todo Todo { get; set; }
+ public TodoClosingStatement? ClosingStatement { get; set; }
+} \ No newline at end of file
diff --git a/code/api/src/Models/Database/Todo/TodoLabel.cs b/code/api/src/Models/Database/Todo/TodoLabel.cs
new file mode 100644
index 0000000..7753ade
--- /dev/null
+++ b/code/api/src/Models/Database/Todo/TodoLabel.cs
@@ -0,0 +1,8 @@
+namespace IOL.GreatOffice.Api.Data.Database;
+
+public class TodoLabel : BaseWithOwner
+{
+ public string Name { get; set; }
+ public string Color { get; set; }
+ public Todo Todo { get; set; }
+}
diff --git a/code/api/src/Models/Enums/FulfillPasswordResetRequestResult.cs b/code/api/src/Models/Enums/FulfillPasswordResetRequestResult.cs
new file mode 100644
index 0000000..2a84c48
--- /dev/null
+++ b/code/api/src/Models/Enums/FulfillPasswordResetRequestResult.cs
@@ -0,0 +1,8 @@
+namespace IOL.GreatOffice.Api.Data.Enums;
+
+public enum FulfillPasswordResetRequestResult
+{
+ REQUEST_NOT_FOUND,
+ USER_NOT_FOUND,
+ FULFILLED
+} \ No newline at end of file
diff --git a/code/api/src/Models/Enums/PasswordResetRequestStatus.cs b/code/api/src/Models/Enums/PasswordResetRequestStatus.cs
new file mode 100644
index 0000000..5629e6f
--- /dev/null
+++ b/code/api/src/Models/Enums/PasswordResetRequestStatus.cs
@@ -0,0 +1,6 @@
+namespace IOL.GreatOffice.Api.Data.Enums;
+
+public enum PasswordResetRequestStatus
+{
+
+} \ No newline at end of file
diff --git a/code/api/src/Models/Enums/ProjectRole.cs b/code/api/src/Models/Enums/ProjectRole.cs
new file mode 100644
index 0000000..c4a3f29
--- /dev/null
+++ b/code/api/src/Models/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/Models/Enums/TodoClosingStatement.cs b/code/api/src/Models/Enums/TodoClosingStatement.cs
new file mode 100644
index 0000000..d838031
--- /dev/null
+++ b/code/api/src/Models/Enums/TodoClosingStatement.cs
@@ -0,0 +1,13 @@
+namespace IOL.GreatOffice.Api.Data.Enums;
+
+public enum TodoClosingStatement
+{
+ REPORTED,
+ RESOLVED,
+ FIXED,
+ IMPLEMENTED,
+ WONT_FIX,
+ BY_DESIGN,
+ INVALID,
+ DUPLICATE
+} \ No newline at end of file
diff --git a/code/api/src/Models/Enums/TodoVisibility.cs b/code/api/src/Models/Enums/TodoVisibility.cs
new file mode 100644
index 0000000..2c8fa83
--- /dev/null
+++ b/code/api/src/Models/Enums/TodoVisibility.cs
@@ -0,0 +1,10 @@
+
+namespace IOL.GreatOffice.Api.Data.Enums;
+
+public enum TodoVisibility
+{
+ PRIVATE = 0,
+ UNLISTED = 1,
+ INTERNAL = 2,
+ PUBLIC = 3,
+} \ No newline at end of file
diff --git a/code/api/src/Models/Misc/ApiSpecDocument.cs b/code/api/src/Models/Misc/ApiSpecDocument.cs
new file mode 100644
index 0000000..1c7d936
--- /dev/null
+++ b/code/api/src/Models/Misc/ApiSpecDocument.cs
@@ -0,0 +1,9 @@
+namespace IOL.GreatOffice.Api.Data.Models;
+
+public class ApiSpecDocument
+{
+ public string VersionName { get; set; }
+ public string SwaggerPath { get; set; }
+ public ApiVersion Version { get; set; }
+ public OpenApiInfo OpenApiInfo { get; set; }
+}
diff --git a/code/api/src/Models/Misc/AppConfiguration.cs b/code/api/src/Models/Misc/AppConfiguration.cs
new file mode 100644
index 0000000..31e5726
--- /dev/null
+++ b/code/api/src/Models/Misc/AppConfiguration.cs
@@ -0,0 +1,118 @@
+using System.Security.Cryptography.X509Certificates;
+
+namespace IOL.GreatOffice.Api.Data.Models;
+
+public class AppConfiguration
+{
+ /// <summary>
+ /// An reachable ip address or url that points to a postgres database.
+ /// </summary>
+ public string DB_HOST { get; set; }
+
+ /// <summary>
+ /// The port number to use with DB_HOST to point to the postgres database.
+ /// </summary>
+ public string DB_PORT { get; set; }
+
+ /// <summary>
+ /// The database user to authenticate against postgres with.
+ /// </summary>
+ public string DB_USER { get; set; }
+
+ /// <summary>
+ /// The password for the database user to authenticate against postgres with.
+ /// </summary>
+ public string DB_PASSWORD { get; set; }
+
+ /// <summary>
+ /// The name of the main app database.
+ /// </summary>
+ public string DB_NAME { get; set; }
+
+ /// <summary>
+ /// An reachable ip address or url that points to a postgres(quartz) database.
+ /// </summary>
+ public string QUARTZ_DB_HOST { get; set; }
+
+ /// <summary>
+ /// The port number to use with QUARTZ_DB_HOST to point to the postgres(quartz) database.
+ /// </summary>
+ public string QUARTZ_DB_PORT { get; set; }
+
+ /// <summary>
+ /// The database user to authenticate against postgres(quartz) with.
+ /// </summary>
+ public string QUARTZ_DB_USER { get; set; }
+
+ /// <summary>
+ /// The password for the database user to authenticate against postgres(quartz) with.
+ /// </summary>
+ public string QUARTZ_DB_PASSWORD { get; set; }
+
+ /// <summary>
+ /// The name of the quartz database.
+ /// </summary>
+ public string QUARTZ_DB_NAME { get; set; }
+
+ /// <summary>
+ /// API key to use when pushing logs to SEQ
+ /// </summary>
+ public string SEQ_API_KEY { get; set; }
+
+ /// <summary>
+ /// Url pointing to the seq instance that processes server logs
+ /// </summary>
+ public string SEQ_API_URL { get; set; }
+
+ /// <summary>
+ /// A token used when sending email via Postmakr
+ /// </summary>
+ public string POSTMARK_TOKEN { get; set; }
+
+ /// <summary>
+ /// The address to send emails from, needs to be setup as a sender in postmark
+ /// </summary>
+ public string EMAIL_FROM_ADDRESS { get; set; }
+
+ /// <summary>
+ /// The absolute url to the frontend app
+ /// </summary>
+ public string CANONICAL_FRONTEND_URL { get; set; }
+
+ /// <summary>
+ /// The absolute url to the backend api
+ /// </summary>
+ public string CANONICAL_BACKEND_URL { get; set; }
+
+ /// <summary>
+ /// A random string used to encrypt/decrypt for general purposes
+ /// </summary>
+ public string APP_AES_KEY { get; set; }
+
+ /// <summary>
+ /// A base64 string containing a passwordless pfx cert
+ /// </summary>
+ public string APP_CERT { get; set; }
+
+ public X509Certificate2 CERT1() => new(Convert.FromBase64String(APP_CERT));
+
+ public object GetPublicVersion() {
+ return new {
+ DB_HOST,
+ DB_PORT,
+ DB_USER,
+ DB_PASSWORD = DB_PASSWORD.Obfuscate() ?? "",
+ QUARTZ_DB_HOST,
+ QUARTZ_DB_PORT,
+ QUARTZ_DB_USER,
+ QUARTZ_DB_PASSWORD = QUARTZ_DB_PASSWORD.Obfuscate() ?? "",
+ SEQ_API_KEY = SEQ_API_KEY.Obfuscate() ?? "",
+ SEQ_API_URL,
+ POSTMARK_TOKEN = POSTMARK_TOKEN.Obfuscate() ?? "",
+ EMAIL_FROM_ADDRESS,
+ APP_AES_KEY = APP_AES_KEY.Obfuscate() ?? "",
+ CERT1 = CERT1().PublicKey.Oid.FriendlyName,
+ CANONICAL_FRONTEND_URL
+ };
+ }
+} \ No newline at end of file
diff --git a/code/api/src/Models/Misc/AppPath.cs b/code/api/src/Models/Misc/AppPath.cs
new file mode 100644
index 0000000..e47e48c
--- /dev/null
+++ b/code/api/src/Models/Misc/AppPath.cs
@@ -0,0 +1,23 @@
+namespace IOL.GreatOffice.Api.Data.Models;
+
+public sealed record AppPath
+{
+ public string HostPath { get; init; }
+ public string WebPath { get; init; }
+
+ public string GetHostPathForFilename(string filename, string fallback = "") {
+ if (filename.IsNullOrWhiteSpace()) {
+ return fallback;
+ }
+
+ return Path.Combine(HostPath, filename);
+ }
+
+ public string GetWebPathForFilename(string filename, string fallback = "") {
+ if (filename.IsNullOrWhiteSpace()) {
+ return fallback;
+ }
+
+ return Path.Combine(WebPath, filename);
+ }
+}
diff --git a/code/api/src/Models/Misc/KnownProblemModel.cs b/code/api/src/Models/Misc/KnownProblemModel.cs
new file mode 100644
index 0000000..9acc85c
--- /dev/null
+++ b/code/api/src/Models/Misc/KnownProblemModel.cs
@@ -0,0 +1,26 @@
+namespace IOL.GreatOffice.Api.Data.Models;
+
+public class KnownProblemModel
+{
+ public KnownProblemModel(string title = default, string subtitle = default, Dictionary<string, string[]> errors = default) {
+ Title = title;
+ Subtitle = subtitle;
+ Errors = errors ?? new();
+ }
+
+ public string Title { get; set; }
+ public string Subtitle { get; set; }
+ public Dictionary<string, string[]> Errors { get; set; }
+ public string TraceId { get; set; }
+
+ public void AddError(string field, string errorText) {
+ if (!Errors.ContainsKey(field)) {
+ Errors.Add(field, new[] {errorText});
+ } else {
+ var currentErrors = Errors[field];
+ var newErrors = currentErrors.Concat(new[] {errorText});
+ Errors.Remove(field);
+ Errors.Add(field, newErrors.ToArray());
+ }
+ }
+} \ No newline at end of file
diff --git a/code/api/src/Models/Misc/LoggedInUserModel.cs b/code/api/src/Models/Misc/LoggedInUserModel.cs
new file mode 100644
index 0000000..541d4a5
--- /dev/null
+++ b/code/api/src/Models/Misc/LoggedInUserModel.cs
@@ -0,0 +1,8 @@
+namespace IOL.GreatOffice.Api.Data.Models;
+
+public class LoggedInUserModel
+{
+ 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/Models/Misc/RequestTimeZoneInfo.cs b/code/api/src/Models/Misc/RequestTimeZoneInfo.cs
new file mode 100644
index 0000000..4c5aa13
--- /dev/null
+++ b/code/api/src/Models/Misc/RequestTimeZoneInfo.cs
@@ -0,0 +1,8 @@
+namespace IOL.GreatOffice.Api.Data.Models;
+
+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/Models/Static/AppClaims.cs b/code/api/src/Models/Static/AppClaims.cs
new file mode 100644
index 0000000..6569700
--- /dev/null
+++ b/code/api/src/Models/Static/AppClaims.cs
@@ -0,0 +1,7 @@
+namespace IOL.GreatOffice.Api.Data.Static;
+
+public static class AppClaims
+{
+ public const string USER_ID = "user_id";
+ public const string NAME = "name";
+}
diff --git a/code/api/src/Models/Static/AppConstants.cs b/code/api/src/Models/Static/AppConstants.cs
new file mode 100644
index 0000000..d0a888b
--- /dev/null
+++ b/code/api/src/Models/Static/AppConstants.cs
@@ -0,0 +1,12 @@
+namespace IOL.GreatOffice.Api.Data.Static;
+
+public static class AppConstants
+{
+ public const string API_NAME = "Greatoffice API";
+ public const string BASIC_AUTH_SCHEME = "BasicAuthenticationScheme";
+ public const string TOKEN_ALLOW_READ = "TOKEN_ALLOW_READ";
+ public const string TOKEN_ALLOW_CREATE = "TOKEN_ALLOW_CREATE";
+ public const string TOKEN_ALLOW_UPDATE = "TOKEN_ALLOW_UPDATE";
+ public const string TOKEN_ALLOW_DELETE = "TOKEN_ALLOW_DELETE";
+ public const string VAULT_CACHE_KEY = "VAULT_CACHE_KEY";
+}
diff --git a/code/api/src/Models/Static/AppCookies.cs b/code/api/src/Models/Static/AppCookies.cs
new file mode 100644
index 0000000..e307b83
--- /dev/null
+++ b/code/api/src/Models/Static/AppCookies.cs
@@ -0,0 +1,7 @@
+namespace IOL.GreatOffice.Api.Data.Static;
+
+public static class AppCookies
+{
+ public const string Locale = "go_locale";
+ public const string Session = "go_session";
+} \ No newline at end of file
diff --git a/code/api/src/Models/Static/AppDateTime.cs b/code/api/src/Models/Static/AppDateTime.cs
new file mode 100644
index 0000000..880d2a8
--- /dev/null
+++ b/code/api/src/Models/Static/AppDateTime.cs
@@ -0,0 +1,16 @@
+namespace IOL.GreatOffice.Api.Data.Static;
+
+public static class AppDateTime
+{
+ private static DateTime? dateTime;
+
+ public static DateTime UtcNow => dateTime ?? DateTime.UtcNow;
+
+ public static void Set(DateTime setDateTime) {
+ dateTime = setDateTime;
+ }
+
+ public static void Reset() {
+ dateTime = null;
+ }
+}
diff --git a/code/api/src/Models/Static/AppEnvironmentVariables.cs b/code/api/src/Models/Static/AppEnvironmentVariables.cs
new file mode 100644
index 0000000..c3f821d
--- /dev/null
+++ b/code/api/src/Models/Static/AppEnvironmentVariables.cs
@@ -0,0 +1,21 @@
+namespace IOL.GreatOffice.Api.Data.Static;
+
+public static class AppEnvironmentVariables
+{
+ /// <summary>
+ /// An access token that can be used to access the Hashicorp Vault instance that is available at VAULT_URL
+ /// </summary>
+ public const string VAULT_TOKEN = "VAULT_TOKEN";
+ /// <summary>
+ /// An url pointing to the Hashicorp Vault instance the app should use
+ /// </summary>
+ public const string VAULT_URL = "VAULT_URL";
+ /// <summary>
+ /// The duration of which to keep a local cached version of the configuration
+ /// </summary>
+ public const string VAULT_CACHE_TTL = "VAULT_CACHE_TTL";
+ /// <summary>
+ /// The vault key name for the main configuration json object, described by <see cref="AppConfiguration"/>
+ /// </summary>
+ public const string MAIN_CONFIG_SHEET = "MAIN_CONFIG_SHEET";
+}
diff --git a/code/api/src/Models/Static/AppHeaders.cs b/code/api/src/Models/Static/AppHeaders.cs
new file mode 100644
index 0000000..d534aba
--- /dev/null
+++ b/code/api/src/Models/Static/AppHeaders.cs
@@ -0,0 +1,7 @@
+namespace IOL.GreatOffice.Api.Data.Static;
+
+public static class AppHeaders
+{
+ public const string BROWSER_TIME_ZONE = "X-TimeZone";
+ public const string IS_KNOWN_PROBLEM = "X-IsKnownProblem";
+}
diff --git a/code/api/src/Models/Static/AppPaths.cs b/code/api/src/Models/Static/AppPaths.cs
new file mode 100644
index 0000000..a24f5af
--- /dev/null
+++ b/code/api/src/Models/Static/AppPaths.cs
@@ -0,0 +1,17 @@
+
+namespace IOL.GreatOffice.Api.Data.Static;
+
+public static class AppPaths
+{
+ public static AppPath AppData => new() {
+ HostPath = Path.Combine(Directory.GetCurrentDirectory(), "AppData")
+ };
+
+ public static AppPath DataProtectionKeys => new() {
+ HostPath = Path.Combine(Directory.GetCurrentDirectory(), "AppData", "dp-keys")
+ };
+
+ public static AppPath Frontend => new() {
+ HostPath = Path.Combine(Directory.GetCurrentDirectory(), "Frontend")
+ };
+}
diff --git a/code/api/src/Models/Static/JsonSettings.cs b/code/api/src/Models/Static/JsonSettings.cs
new file mode 100644
index 0000000..a163c11
--- /dev/null
+++ b/code/api/src/Models/Static/JsonSettings.cs
@@ -0,0 +1,11 @@
+namespace IOL.GreatOffice.Api.Data.Static;
+
+public static class JsonSettings
+{
+ public static Action<JsonOptions> Default { get; } = options => {
+ options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles;
+ options.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
+ options.JsonSerializerOptions.NumberHandling = JsonNumberHandling.AllowReadingFromString;
+ options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
+ };
+}