From b7e39b59fd0fc7b5610ebff29035bf622079e0d8 Mon Sep 17 00:00:00 2001 From: ivarlovlie Date: Wed, 5 Oct 2022 20:45:21 +0800 Subject: refactor: Change file structure --- code/api/src/Data/AppDbContext.cs | 51 ++++++++ code/api/src/Data/Database/ApiAccessToken.cs | 31 +++++ code/api/src/Data/Database/Base.cs | 15 +++ code/api/src/Data/Database/BaseWithOwner.cs | 19 +++ code/api/src/Data/Database/Customer.cs | 6 + code/api/src/Data/Database/CustomerContact.cs | 12 ++ code/api/src/Data/Database/CustomerEvent.cs | 7 ++ .../api/src/Data/Database/ForgotPasswordRequest.cs | 23 ++++ code/api/src/Data/Database/Project.cs | 7 ++ code/api/src/Data/Database/Tenant.cs | 11 ++ code/api/src/Data/Database/TimeCategory.cs | 31 +++++ code/api/src/Data/Database/TimeEntry.cs | 45 +++++++ code/api/src/Data/Database/TimeLabel.cs | 31 +++++ code/api/src/Data/Database/Todo.cs | 13 ++ code/api/src/Data/Database/TodoComment.cs | 7 ++ code/api/src/Data/Database/TodoLabel.cs | 8 ++ code/api/src/Data/Database/TodoProject.cs | 16 +++ .../src/Data/Database/TodoProjectAccessControl.cs | 11 ++ code/api/src/Data/Database/TodoStatus.cs | 45 +++++++ code/api/src/Data/Database/User.cs | 37 ++++++ code/api/src/Data/Dtos/TimeQueryDto.cs | 34 ++++++ code/api/src/Data/Dtos/UserArchiveDto.cs | 131 +++++++++++++++++++++ code/api/src/Data/Enums/TimeEntryQueryDuration.cs | 37 ++++++ .../ForgotPasswordRequestNotFoundException.cs | 21 ++++ .../src/Data/Exceptions/UserNotFoundException.cs | 19 +++ code/api/src/Data/Models/ApiSpecDocument.cs | 9 ++ code/api/src/Data/Models/AppPath.cs | 23 ++++ code/api/src/Data/Models/LoggedInUserModel.cs | 7 ++ code/api/src/Data/Results/ErrorResult.cs | 12 ++ code/api/src/Data/Static/AppClaims.cs | 8 ++ code/api/src/Data/Static/AppConfiguration.cs | 58 +++++++++ code/api/src/Data/Static/AppConstants.cs | 12 ++ code/api/src/Data/Static/AppDateTime.cs | 16 +++ .../api/src/Data/Static/AppEnvironmentVariables.cs | 21 ++++ code/api/src/Data/Static/AppHeaders.cs | 7 ++ code/api/src/Data/Static/AppPaths.cs | 17 +++ code/api/src/Data/Static/JsonSettings.cs | 11 ++ 37 files changed, 869 insertions(+) create mode 100644 code/api/src/Data/AppDbContext.cs create mode 100644 code/api/src/Data/Database/ApiAccessToken.cs create mode 100644 code/api/src/Data/Database/Base.cs create mode 100644 code/api/src/Data/Database/BaseWithOwner.cs create mode 100644 code/api/src/Data/Database/Customer.cs create mode 100644 code/api/src/Data/Database/CustomerContact.cs create mode 100644 code/api/src/Data/Database/CustomerEvent.cs create mode 100644 code/api/src/Data/Database/ForgotPasswordRequest.cs create mode 100644 code/api/src/Data/Database/Project.cs create mode 100644 code/api/src/Data/Database/Tenant.cs create mode 100644 code/api/src/Data/Database/TimeCategory.cs create mode 100644 code/api/src/Data/Database/TimeEntry.cs create mode 100644 code/api/src/Data/Database/TimeLabel.cs create mode 100644 code/api/src/Data/Database/Todo.cs create mode 100644 code/api/src/Data/Database/TodoComment.cs create mode 100644 code/api/src/Data/Database/TodoLabel.cs create mode 100644 code/api/src/Data/Database/TodoProject.cs create mode 100644 code/api/src/Data/Database/TodoProjectAccessControl.cs create mode 100644 code/api/src/Data/Database/TodoStatus.cs create mode 100644 code/api/src/Data/Database/User.cs create mode 100644 code/api/src/Data/Dtos/TimeQueryDto.cs create mode 100644 code/api/src/Data/Dtos/UserArchiveDto.cs create mode 100644 code/api/src/Data/Enums/TimeEntryQueryDuration.cs create mode 100644 code/api/src/Data/Exceptions/ForgotPasswordRequestNotFoundException.cs create mode 100644 code/api/src/Data/Exceptions/UserNotFoundException.cs create mode 100644 code/api/src/Data/Models/ApiSpecDocument.cs create mode 100644 code/api/src/Data/Models/AppPath.cs create mode 100644 code/api/src/Data/Models/LoggedInUserModel.cs create mode 100644 code/api/src/Data/Results/ErrorResult.cs create mode 100644 code/api/src/Data/Static/AppClaims.cs create mode 100644 code/api/src/Data/Static/AppConfiguration.cs create mode 100644 code/api/src/Data/Static/AppConstants.cs create mode 100644 code/api/src/Data/Static/AppDateTime.cs create mode 100644 code/api/src/Data/Static/AppEnvironmentVariables.cs create mode 100644 code/api/src/Data/Static/AppHeaders.cs create mode 100644 code/api/src/Data/Static/AppPaths.cs create mode 100644 code/api/src/Data/Static/JsonSettings.cs (limited to 'code/api/src/Data') diff --git a/code/api/src/Data/AppDbContext.cs b/code/api/src/Data/AppDbContext.cs new file mode 100644 index 0000000..c970429 --- /dev/null +++ b/code/api/src/Data/AppDbContext.cs @@ -0,0 +1,51 @@ +using Microsoft.AspNetCore.DataProtection.EntityFrameworkCore; + +namespace IOL.GreatOffice.Api.Data; + +public class AppDbContext : DbContext, IDataProtectionKeyContext +{ + public AppDbContext(DbContextOptions options) : base(options) { } + public DbSet Users { get; set; } + public DbSet ForgotPasswordRequests { get; set; } + public DbSet TimeLabels { get; set; } + public DbSet TimeEntries { get; set; } + public DbSet TimeCategories { get; set; } + public DbSet AccessTokens { get; set; } + public DbSet Tenants { get; set; } + public DbSet DataProtectionKeys { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) { + modelBuilder.Entity(e => { + e.ToTable("users"); + }); + + modelBuilder.Entity(e => { + e.HasOne(c => c.User); + e.ToTable("forgot_password_requests"); + }); + + modelBuilder.Entity(e => { + e.ToTable("time_categories"); + }); + + modelBuilder.Entity(e => { + e.ToTable("time_labels"); + }); + + modelBuilder.Entity(e => { + e.HasOne(c => c.Category); + e.HasMany(c => c.Labels); + e.ToTable("time_entries"); + }); + + modelBuilder.Entity(e => { + e.ToTable("api_access_tokens"); + }); + + modelBuilder.Entity(e => { + e.ToTable("tenants"); + }); + + base.OnModelCreating(modelBuilder); + } +} diff --git a/code/api/src/Data/Database/ApiAccessToken.cs b/code/api/src/Data/Database/ApiAccessToken.cs new file mode 100644 index 0000000..9582869 --- /dev/null +++ b/code/api/src/Data/Database/ApiAccessToken.cs @@ -0,0 +1,31 @@ +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; + public ApiAccessTokenDto AsDto => new(this); + + public class ApiAccessTokenDto + { + public ApiAccessTokenDto(ApiAccessToken source) { + ExpiryDate = source.ExpiryDate; + AllowRead = source.AllowRead; + AllowCreate = source.AllowCreate; + AllowUpdate = source.AllowUpdate; + AllowDelete = source.AllowDelete; + } + + 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/Data/Database/Base.cs b/code/api/src/Data/Database/Base.cs new file mode 100644 index 0000000..ae9efa2 --- /dev/null +++ b/code/api/src/Data/Database/Base.cs @@ -0,0 +1,15 @@ +namespace IOL.GreatOffice.Api.Data.Database; + +public 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 bool Deleted { get; set; } + public void Modified() => ModifiedAt = 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 new file mode 100644 index 0000000..1eb99f4 --- /dev/null +++ b/code/api/src/Data/Database/BaseWithOwner.cs @@ -0,0 +1,19 @@ +namespace IOL.GreatOffice.Api.Data.Database; + +/// +/// Base class for all entities. +/// +public class BaseWithOwner : Base +{ + protected BaseWithOwner() { } + + protected BaseWithOwner(Guid userId) { + UserId = userId; + } + + 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; } +} \ No newline at end of file diff --git a/code/api/src/Data/Database/Customer.cs b/code/api/src/Data/Database/Customer.cs new file mode 100644 index 0000000..c6b06a4 --- /dev/null +++ b/code/api/src/Data/Database/Customer.cs @@ -0,0 +1,6 @@ +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/CustomerContact.cs b/code/api/src/Data/Database/CustomerContact.cs new file mode 100644 index 0000000..f5a951d --- /dev/null +++ b/code/api/src/Data/Database/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/Data/Database/CustomerEvent.cs b/code/api/src/Data/Database/CustomerEvent.cs new file mode 100644 index 0000000..da3e3ed --- /dev/null +++ b/code/api/src/Data/Database/CustomerEvent.cs @@ -0,0 +1,7 @@ +namespace IOL.GreatOffice.Api.Data.Database; + +public class CustomerEvent : BaseWithOwner +{ + public Customer Customer { get; set; } + public string Name { get; set; } +} diff --git a/code/api/src/Data/Database/ForgotPasswordRequest.cs b/code/api/src/Data/Database/ForgotPasswordRequest.cs new file mode 100644 index 0000000..1510a35 --- /dev/null +++ b/code/api/src/Data/Database/ForgotPasswordRequest.cs @@ -0,0 +1,23 @@ +namespace IOL.GreatOffice.Api.Data.Database; + +public class ForgotPasswordRequest +{ + public ForgotPasswordRequest() { } + + public ForgotPasswordRequest(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/Data/Database/Project.cs b/code/api/src/Data/Database/Project.cs new file mode 100644 index 0000000..7e694ee --- /dev/null +++ b/code/api/src/Data/Database/Project.cs @@ -0,0 +1,7 @@ +namespace IOL.GreatOffice.Api.Data.Database; + +public class Project : BaseWithOwner +{ + public string Name { get; set; } + public Guid? CustomerId { get; set; } +} diff --git a/code/api/src/Data/Database/Tenant.cs b/code/api/src/Data/Database/Tenant.cs new file mode 100644 index 0000000..b185c7a --- /dev/null +++ b/code/api/src/Data/Database/Tenant.cs @@ -0,0 +1,11 @@ +namespace IOL.GreatOffice.Api.Data.Database; + +public class Tenant : BaseWithOwner +{ + 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 Users { get; set; } +} diff --git a/code/api/src/Data/Database/TimeCategory.cs b/code/api/src/Data/Database/TimeCategory.cs new file mode 100644 index 0000000..69c6957 --- /dev/null +++ b/code/api/src/Data/Database/TimeCategory.cs @@ -0,0 +1,31 @@ +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 new file mode 100644 index 0000000..46c62e1 --- /dev/null +++ b/code/api/src/Data/Database/TimeEntry.cs @@ -0,0 +1,45 @@ +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 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 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 new file mode 100644 index 0000000..55e20b0 --- /dev/null +++ b/code/api/src/Data/Database/TimeLabel.cs @@ -0,0 +1,31 @@ +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.cs new file mode 100644 index 0000000..5fe3c9a --- /dev/null +++ b/code/api/src/Data/Database/Todo.cs @@ -0,0 +1,13 @@ +namespace IOL.GreatOffice.Api.Data.Database; + +public class Todo : BaseWithOwner +{ + public int PublicId { get; set; } + public TodoStatus Status { get; set; } + public TodoProject Project { get; set; } + public Guid? AssignedUserId { get; set; } + public string Title { get; set; } + public string Description { get; set; } + public ICollection Labels { get; set; } + public ICollection Comments { get; set; } +} diff --git a/code/api/src/Data/Database/TodoComment.cs b/code/api/src/Data/Database/TodoComment.cs new file mode 100644 index 0000000..44dcbed --- /dev/null +++ b/code/api/src/Data/Database/TodoComment.cs @@ -0,0 +1,7 @@ +namespace IOL.GreatOffice.Api.Data.Database; + +public class TodoComment : BaseWithOwner +{ + public string Value { get; set; } + public Todo Todo { get; set; } +} diff --git a/code/api/src/Data/Database/TodoLabel.cs b/code/api/src/Data/Database/TodoLabel.cs new file mode 100644 index 0000000..7753ade --- /dev/null +++ b/code/api/src/Data/Database/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/Data/Database/TodoProject.cs b/code/api/src/Data/Database/TodoProject.cs new file mode 100644 index 0000000..0a4a7be --- /dev/null +++ b/code/api/src/Data/Database/TodoProject.cs @@ -0,0 +1,16 @@ +namespace IOL.GreatOffice.Api.Data.Database; + +public class TodoProject : BaseWithOwner +{ + public string Name { get; set; } + public TodoVisibility Visibility { get; set; } + public Guid? ProjectId { get; set; } +} + +public enum TodoVisibility +{ + PRIVATE = 0, + UNLISTED = 1, + TENANT_WIDE = 2, + PUBLIC = 3, +} diff --git a/code/api/src/Data/Database/TodoProjectAccessControl.cs b/code/api/src/Data/Database/TodoProjectAccessControl.cs new file mode 100644 index 0000000..964f831 --- /dev/null +++ b/code/api/src/Data/Database/TodoProjectAccessControl.cs @@ -0,0 +1,11 @@ +namespace IOL.GreatOffice.Api.Data.Database; + +public class TodoProjectAccessControl +{ + public TodoProject Project { get; set; } + public Guid? UserId { get; set; } + public bool Browse { get; set; } + public bool Submit { get; set; } + public bool Comment { get; set; } + public bool Edit { get; set; } +} diff --git a/code/api/src/Data/Database/TodoStatus.cs b/code/api/src/Data/Database/TodoStatus.cs new file mode 100644 index 0000000..416212d --- /dev/null +++ b/code/api/src/Data/Database/TodoStatus.cs @@ -0,0 +1,45 @@ +namespace IOL.GreatOffice.Api.Data.Database; + +public class TodoStatus : BaseWithOwner +{ + public string Name { get; set; } + public string Color { get; set; } + public Todo Todo { get; set; } + + public static List GetDefaultStatusSetForTenant(Guid tenantId) { + return new List() { + 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/Database/User.cs b/code/api/src/Data/Database/User.cs new file mode 100644 index 0000000..9db5d35 --- /dev/null +++ b/code/api/src/Data/Database/User.cs @@ -0,0 +1,37 @@ +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 ICollection Tenants { get; set; } + + public string DisplayName() { + if (FirstName.HasValue() && LastName.HasValue()) return FirstName + " " + LastName; + return FirstName.HasValue() ? FirstName : Email; + } + + public void HashAndSetPassword(string password) { + Password = PasswordHelper.HashPassword(password); + } + + public bool VerifyPassword(string password) { + return PasswordHelper.Verify(password, Password); + } + + public IEnumerable 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/Data/Dtos/TimeQueryDto.cs b/code/api/src/Data/Dtos/TimeQueryDto.cs new file mode 100644 index 0000000..f734cb1 --- /dev/null +++ b/code/api/src/Data/Dtos/TimeQueryDto.cs @@ -0,0 +1,34 @@ + +namespace IOL.GreatOffice.Api.Data.Dtos; + +public class TimeQueryDto +{ + public TimeQueryDto() { + Results = new List(); + } + + /// + /// List of entries. + /// + public List Results { get; set; } + + /// + /// Curren page. + /// + public int Page { get; set; } + + /// + /// Maximum count of entries in a page. + /// + public int PageSize { get; set; } + + /// + /// Total count of entries. + /// + public int TotalSize { get; set; } + + /// + /// Total count of pages. + /// + public int TotalPageCount { get; set; } +} diff --git a/code/api/src/Data/Dtos/UserArchiveDto.cs b/code/api/src/Data/Dtos/UserArchiveDto.cs new file mode 100644 index 0000000..42e0600 --- /dev/null +++ b/code/api/src/Data/Dtos/UserArchiveDto.cs @@ -0,0 +1,131 @@ + +namespace IOL.GreatOffice.Api.Data.Dtos; + +/// +/// Represents a user archive as it is provided to users. +/// +public class UserArchiveDto +{ + /// + public UserArchiveDto(User user) { + Meta = new MetaDto { + GeneratedAt = AppDateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ") + }; + User = new UserDto(user); + Entries = new List(); + } + + /// + /// Metadata for the user archive. + /// + public MetaDto Meta { get; } + + /// + /// Relevant user data for the archive. + /// + public UserDto User { get; } + + /// + /// List of entries that the user has created. + /// + public List Entries { get; } + + public void CountEntries() { + Meta.EntryCount = Entries.Count; + } + + /// + /// Represents a time entry in the data archive. + /// + public class EntryDto + { + public string CreatedAt { get; init; } + + [JsonIgnore] + public DateTime StartDateTime { get; init; } + + /// + /// ISO 8601 string of the UTC date the time entry started. + /// + public string Start => StartDateTime.ToString("yyyy-MM-ddTHH:mm:ssZ"); + + [JsonIgnore] + public DateTime StopDateTime { get; init; } + + /// + /// ISO 8601 string of the UTC date the time entry stopped. + /// + public string Stop => StopDateTime.ToString("yyyy-MM-ddTHH:mm:ssZ"); + + /// + /// Total amount of minutes elapsed from start to stop on this time entry. + /// + public double Minutes => StopDateTime.Subtract(StartDateTime).TotalMinutes; + + public string Description { get; init; } + + /// + /// Archive spesific category for this time entry. + /// + public CategoryDto Category { get; init; } + + /// + /// Archive spesific list of labels for this time entry. + /// + public List Labels { get; init; } + } + + /// + /// Time entry category as it is written to the user archive. + /// + public class CategoryDto + { + public string Name { get; init; } + public string Color { get; init; } + } + + /// + /// Time entry label as it is written to the user archive. + /// + public class LabelDto + { + public string Name { get; init; } + public string Color { get; init; } + } + + + /// + /// Represents the user who this archive's data is based on. + /// + public class UserDto + { + /// + public UserDto(User user) { + Username = user.Username; + CreatedAt = user.CreatedAt; + } + + /// + /// UTC date this user was created. + /// + public DateTime CreatedAt { get; } + + public string Username { get; } + } + + /// + /// Represents the meta object which contains metdata for this archive. + /// + public class MetaDto + { + /// + /// ISO 8601 UTC date string for when this archive was created. + /// + public string GeneratedAt { get; init; } + + /// + /// Amount of entries in the archive. + /// + public int EntryCount { get; set; } + } +} diff --git a/code/api/src/Data/Enums/TimeEntryQueryDuration.cs b/code/api/src/Data/Enums/TimeEntryQueryDuration.cs new file mode 100644 index 0000000..af70ca6 --- /dev/null +++ b/code/api/src/Data/Enums/TimeEntryQueryDuration.cs @@ -0,0 +1,37 @@ +namespace IOL.GreatOffice.Api.Data.Enums; + +/// +/// Specify a duration filter for time entry queries. +/// +public enum TimeEntryQueryDuration +{ + /// + /// Only query entries created today. + /// + TODAY = 0, + + /// + /// Only query entries created this week. + /// + THIS_WEEK = 1, + + /// + /// Only query entries created this month. + /// + THIS_MONTH = 2, + + /// + /// Only query entries created this year. + /// + THIS_YEAR = 3, + + /// + /// Only query entries created at a spesific date. + /// + SPECIFIC_DATE = 4, + + /// + /// Only query entries created between two dates. + /// + DATE_RANGE = 5, +} diff --git a/code/api/src/Data/Exceptions/ForgotPasswordRequestNotFoundException.cs b/code/api/src/Data/Exceptions/ForgotPasswordRequestNotFoundException.cs new file mode 100644 index 0000000..02474b4 --- /dev/null +++ b/code/api/src/Data/Exceptions/ForgotPasswordRequestNotFoundException.cs @@ -0,0 +1,21 @@ +namespace IOL.GreatOffice.Api.Data.Exceptions; + +[Serializable] +public class ForgotPasswordRequestNotFoundException : Exception +{ + // + // For guidelines regarding the creation of new exception types, see + // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconerrorraisinghandlingguidelines.asp + // and + // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dncscol/html/csharp07192001.asp + // + + public ForgotPasswordRequestNotFoundException() { } + public ForgotPasswordRequestNotFoundException(string message) : base(message) { } + public ForgotPasswordRequestNotFoundException(string message, Exception inner) : base(message, inner) { } + + protected ForgotPasswordRequestNotFoundException( + SerializationInfo info, + StreamingContext context + ) : base(info, context) { } +} diff --git a/code/api/src/Data/Exceptions/UserNotFoundException.cs b/code/api/src/Data/Exceptions/UserNotFoundException.cs new file mode 100644 index 0000000..06b57a9 --- /dev/null +++ b/code/api/src/Data/Exceptions/UserNotFoundException.cs @@ -0,0 +1,19 @@ +namespace IOL.GreatOffice.Api.Data.Exceptions; + +[Serializable] +public class UserNotFoundException : Exception +{ + // For guidelines regarding the creation of new exception types, see + // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconerrorraisinghandlingguidelines.asp + // and + // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dncscol/html/csharp07192001.asp + + public UserNotFoundException() { } + public UserNotFoundException(string message) : base(message) { } + public UserNotFoundException(string message, Exception inner) : base(message, inner) { } + + protected UserNotFoundException( + SerializationInfo info, + StreamingContext context + ) : base(info, context) { } +} diff --git a/code/api/src/Data/Models/ApiSpecDocument.cs b/code/api/src/Data/Models/ApiSpecDocument.cs new file mode 100644 index 0000000..1c7d936 --- /dev/null +++ b/code/api/src/Data/Models/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/Data/Models/AppPath.cs b/code/api/src/Data/Models/AppPath.cs new file mode 100644 index 0000000..e47e48c --- /dev/null +++ b/code/api/src/Data/Models/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/Data/Models/LoggedInUserModel.cs b/code/api/src/Data/Models/LoggedInUserModel.cs new file mode 100644 index 0000000..d802b77 --- /dev/null +++ b/code/api/src/Data/Models/LoggedInUserModel.cs @@ -0,0 +1,7 @@ +namespace IOL.GreatOffice.Api.Data.Models; + +public class LoggedInUserModel +{ + public Guid Id { get; set; } + public string Username { get; set; } +} diff --git a/code/api/src/Data/Results/ErrorResult.cs b/code/api/src/Data/Results/ErrorResult.cs new file mode 100644 index 0000000..fd2fd6a --- /dev/null +++ b/code/api/src/Data/Results/ErrorResult.cs @@ -0,0 +1,12 @@ +namespace IOL.GreatOffice.Api.Data.Results; + +public class ErrorResult +{ + public ErrorResult(string title = default, string text = default) { + Title = title; + Text = text; + } + + public string Title { get; set; } + public string Text { get; set; } +} diff --git a/code/api/src/Data/Static/AppClaims.cs b/code/api/src/Data/Static/AppClaims.cs new file mode 100644 index 0000000..8b6d3a8 --- /dev/null +++ b/code/api/src/Data/Static/AppClaims.cs @@ -0,0 +1,8 @@ +namespace IOL.GreatOffice.Api.Data.Static; + +public static class AppClaims +{ + public const string USER_ID = "user_id"; + public const string NAME = "name"; + public const string GITHUB_ACCESS_TOKEN = ""; +} diff --git a/code/api/src/Data/Static/AppConfiguration.cs b/code/api/src/Data/Static/AppConfiguration.cs new file mode 100644 index 0000000..4ee7a8e --- /dev/null +++ b/code/api/src/Data/Static/AppConfiguration.cs @@ -0,0 +1,58 @@ +using System.Security.Cryptography.X509Certificates; + +namespace IOL.GreatOffice.Api.Data.Static; + +public class AppConfiguration +{ + public string DB_HOST { get; set; } + public string DB_PORT { get; set; } + public string DB_USER { get; set; } + public string DB_PASSWORD { get; set; } + public string DB_NAME { get; set; } + public string QUARTZ_DB_HOST { get; set; } + public string QUARTZ_DB_PORT { get; set; } + public string QUARTZ_DB_USER { get; set; } + public string QUARTZ_DB_PASSWORD { get; set; } + public string QUARTZ_DB_NAME { get; set; } + public string SEQ_API_KEY { get; set; } + public string SEQ_API_URL { get; set; } + public string SMTP_HOST { get; set; } + public string SMTP_PORT { get; set; } + public string SMTP_USER { get; set; } + public string SMTP_PASSWORD { get; set; } + public string EMAIL_FROM_ADDRESS { get; set; } + public string EMAIL_FROM_DISPLAY_NAME { get; set; } + public string PORTAL_URL { get; set; } + public string GITHUB_CLIENT_ID { get; set; } + public string GITHUB_CLIENT_SECRET { get; set; } + public string APP_AES_KEY { get; set; } + 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, + SMTP_HOST, + SMTP_PORT, + SMTP_USER = SMTP_USER.Obfuscate() ?? "", + SMTP_PASSWORD = SMTP_PASSWORD.Obfuscate() ?? "", + EMAIL_FROM_ADDRESS, + EMAIL_FROM_DISPLAY_NAME, + PORTAL_URL, + GITHUB_CLIENT_ID = GITHUB_CLIENT_ID.Obfuscate() ?? "", + GITHUB_CLIENT_SECRET = GITHUB_CLIENT_SECRET.Obfuscate() ?? "", + APP_AES_KEY = APP_AES_KEY.Obfuscate() ?? "", + CERT1 = CERT1().PublicKey.Oid.FriendlyName + }; + } +} diff --git a/code/api/src/Data/Static/AppConstants.cs b/code/api/src/Data/Static/AppConstants.cs new file mode 100644 index 0000000..461317b --- /dev/null +++ b/code/api/src/Data/Static/AppConstants.cs @@ -0,0 +1,12 @@ +namespace IOL.GreatOffice.Api.Data.Static; + +public static class AppConstants +{ + public const string API_NAME = "Great Office 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/Data/Static/AppDateTime.cs b/code/api/src/Data/Static/AppDateTime.cs new file mode 100644 index 0000000..880d2a8 --- /dev/null +++ b/code/api/src/Data/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/Data/Static/AppEnvironmentVariables.cs b/code/api/src/Data/Static/AppEnvironmentVariables.cs new file mode 100644 index 0000000..c3f821d --- /dev/null +++ b/code/api/src/Data/Static/AppEnvironmentVariables.cs @@ -0,0 +1,21 @@ +namespace IOL.GreatOffice.Api.Data.Static; + +public static class AppEnvironmentVariables +{ + /// + /// An access token that can be used to access the Hashicorp Vault instance that is available at VAULT_URL + /// + public const string VAULT_TOKEN = "VAULT_TOKEN"; + /// + /// An url pointing to the Hashicorp Vault instance the app should use + /// + public const string VAULT_URL = "VAULT_URL"; + /// + /// The duration of which to keep a local cached version of the configuration + /// + public const string VAULT_CACHE_TTL = "VAULT_CACHE_TTL"; + /// + /// The vault key name for the main configuration json object, described by + /// + public const string MAIN_CONFIG_SHEET = "MAIN_CONFIG_SHEET"; +} diff --git a/code/api/src/Data/Static/AppHeaders.cs b/code/api/src/Data/Static/AppHeaders.cs new file mode 100644 index 0000000..7912418 --- /dev/null +++ b/code/api/src/Data/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 VAULT_TOKEN = "X-Vault-Token"; +} diff --git a/code/api/src/Data/Static/AppPaths.cs b/code/api/src/Data/Static/AppPaths.cs new file mode 100644 index 0000000..a24f5af --- /dev/null +++ b/code/api/src/Data/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/Data/Static/JsonSettings.cs b/code/api/src/Data/Static/JsonSettings.cs new file mode 100644 index 0000000..a163c11 --- /dev/null +++ b/code/api/src/Data/Static/JsonSettings.cs @@ -0,0 +1,11 @@ +namespace IOL.GreatOffice.Api.Data.Static; + +public static class JsonSettings +{ + public static Action Default { get; } = options => { + options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles; + options.JsonSerializerOptions.PropertyNameCaseInsensitive = true; + options.JsonSerializerOptions.NumberHandling = JsonNumberHandling.AllowReadingFromString; + options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; + }; +} -- cgit v1.3