summaryrefslogtreecommitdiffstats
path: root/server/src/Data
diff options
context:
space:
mode:
authorivarlovlie <git@ivarlovlie.no>2022-06-01 22:10:32 +0200
committerivarlovlie <git@ivarlovlie.no>2022-06-01 22:10:32 +0200
commita640703f2da8815dc26ad1600a6f206be1624379 (patch)
treedbda195fb5783d16487e557e06471cf848b75427 /server/src/Data
downloadgreatoffice-a640703f2da8815dc26ad1600a6f206be1624379.tar.xz
greatoffice-a640703f2da8815dc26ad1600a6f206be1624379.zip
feat: Initial after clean slate
Diffstat (limited to 'server/src/Data')
-rw-r--r--server/src/Data/AppDbContext.cs58
-rw-r--r--server/src/Data/Database/ApiAccessToken.cs31
-rw-r--r--server/src/Data/Database/Base.cs14
-rw-r--r--server/src/Data/Database/BaseWithOwner.cs24
-rw-r--r--server/src/Data/Database/ForgotPasswordRequest.cs23
-rw-r--r--server/src/Data/Database/GithubUserMapping.cs9
-rw-r--r--server/src/Data/Database/Tenant.cs10
-rw-r--r--server/src/Data/Database/TimeCategory.cs31
-rw-r--r--server/src/Data/Database/TimeEntry.cs45
-rw-r--r--server/src/Data/Database/TimeLabel.cs31
-rw-r--r--server/src/Data/Database/User.cs29
-rw-r--r--server/src/Data/Dtos/TimeQueryDto.cs34
-rw-r--r--server/src/Data/Dtos/UserArchiveDto.cs131
-rw-r--r--server/src/Data/Enums/TimeEntryQueryDuration.cs37
-rw-r--r--server/src/Data/Exceptions/ForgotPasswordRequestNotFoundException.cs21
-rw-r--r--server/src/Data/Exceptions/UserNotFoundException.cs19
-rw-r--r--server/src/Data/Models/ApiSpecDocument.cs9
-rw-r--r--server/src/Data/Models/AppPath.cs23
-rw-r--r--server/src/Data/Models/LoggedInUserModel.cs8
-rw-r--r--server/src/Data/Results/ErrorResult.cs12
-rw-r--r--server/src/Data/Static/AppClaims.cs8
-rw-r--r--server/src/Data/Static/AppConstants.cs11
-rw-r--r--server/src/Data/Static/AppEnvironmentVariables.cs27
-rw-r--r--server/src/Data/Static/AppHeaders.cs6
-rw-r--r--server/src/Data/Static/AppPaths.cs17
-rw-r--r--server/src/Data/Static/JsonSettings.cs11
26 files changed, 679 insertions, 0 deletions
diff --git a/server/src/Data/AppDbContext.cs b/server/src/Data/AppDbContext.cs
new file mode 100644
index 0000000..3f949dd
--- /dev/null
+++ b/server/src/Data/AppDbContext.cs
@@ -0,0 +1,58 @@
+namespace IOL.GreatOffice.Api.Data;
+
+public class AppDbContext : DbContext
+{
+ 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<GithubUserMapping> GithubUserMappings { get; set; }
+ public DbSet<ApiAccessToken> AccessTokens { get; set; }
+ public DbSet<Tenant> Tenants { 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.HasOne(c => c.User);
+ e.ToTable("time_categories");
+ });
+
+ modelBuilder.Entity<TimeLabel>(e => {
+ e.HasOne(c => c.User);
+ e.ToTable("time_labels");
+ });
+
+ modelBuilder.Entity<TimeEntry>(e => {
+ e.HasOne(c => c.User);
+ e.HasOne(c => c.Category);
+ e.HasMany(c => c.Labels);
+ e.ToTable("time_entries");
+ });
+
+ modelBuilder.Entity<GithubUserMapping>(e => {
+ e.HasOne(c => c.User);
+ e.HasKey(c => c.GithubId);
+ e.ToTable("github_user_mappings");
+ });
+
+ modelBuilder.Entity<ApiAccessToken>(e => {
+ e.ToTable("api_access_tokens");
+ });
+
+ modelBuilder.Entity<Tenant>(e => {
+ e.ToTable("tenants");
+ });
+
+ base.OnModelCreating(modelBuilder);
+ }
+}
diff --git a/server/src/Data/Database/ApiAccessToken.cs b/server/src/Data/Database/ApiAccessToken.cs
new file mode 100644
index 0000000..3eff5f3
--- /dev/null
+++ b/server/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 < DateTime.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 < DateTime.UtcNow;
+ }
+}
diff --git a/server/src/Data/Database/Base.cs b/server/src/Data/Database/Base.cs
new file mode 100644
index 0000000..2439668
--- /dev/null
+++ b/server/src/Data/Database/Base.cs
@@ -0,0 +1,14 @@
+namespace IOL.GreatOffice.Api.Data.Database;
+
+public class Base
+{
+ protected Base() {
+ Id = Guid.NewGuid();
+ CreatedAt = DateTime.UtcNow;
+ }
+
+ public Guid Id { get; init; }
+ public DateTime CreatedAt { get; init; }
+ public DateTime? ModifiedAt { get; private set; }
+ public void Modified() => ModifiedAt = DateTime.UtcNow;
+}
diff --git a/server/src/Data/Database/BaseWithOwner.cs b/server/src/Data/Database/BaseWithOwner.cs
new file mode 100644
index 0000000..eb4438d
--- /dev/null
+++ b/server/src/Data/Database/BaseWithOwner.cs
@@ -0,0 +1,24 @@
+namespace IOL.GreatOffice.Api.Data.Database;
+
+/// <summary>
+/// Base class for all entities.
+/// </summary>
+public class BaseWithOwner : Base
+{
+ protected BaseWithOwner() { }
+
+ protected BaseWithOwner(Guid userId) {
+ UserId = userId;
+ }
+
+ public Guid UserId { get; init; }
+ public User User { get; init; }
+ public Guid TenantId { get; set; }
+ public Tenant Tenant { get; init; }
+ public Guid ModifiedById { get; init; }
+ public User ModifiedBy { get; set; }
+ public Guid CreatedById { get; init; }
+ public User CreatedBy { get; set; }
+ public Guid DeletedById { get; init; }
+ public User DeletedBy { get; set; }
+}
diff --git a/server/src/Data/Database/ForgotPasswordRequest.cs b/server/src/Data/Database/ForgotPasswordRequest.cs
new file mode 100644
index 0000000..164f09d
--- /dev/null
+++ b/server/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 = DateTime.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, DateTime.UtcNow) < 0;
+}
diff --git a/server/src/Data/Database/GithubUserMapping.cs b/server/src/Data/Database/GithubUserMapping.cs
new file mode 100644
index 0000000..dbdb2b7
--- /dev/null
+++ b/server/src/Data/Database/GithubUserMapping.cs
@@ -0,0 +1,9 @@
+namespace IOL.GreatOffice.Api.Data.Database;
+
+public class GithubUserMapping
+{
+ public User User { get; set; }
+ public string GithubId { get; set; }
+ public string Email { get; set; }
+ public string RefreshToken { get; set; }
+}
diff --git a/server/src/Data/Database/Tenant.cs b/server/src/Data/Database/Tenant.cs
new file mode 100644
index 0000000..3028d13
--- /dev/null
+++ b/server/src/Data/Database/Tenant.cs
@@ -0,0 +1,10 @@
+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; }
+}
diff --git a/server/src/Data/Database/TimeCategory.cs b/server/src/Data/Database/TimeCategory.cs
new file mode 100644
index 0000000..69c6957
--- /dev/null
+++ b/server/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/server/src/Data/Database/TimeEntry.cs b/server/src/Data/Database/TimeEntry.cs
new file mode 100644
index 0000000..46c62e1
--- /dev/null
+++ b/server/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<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/server/src/Data/Database/TimeLabel.cs b/server/src/Data/Database/TimeLabel.cs
new file mode 100644
index 0000000..55e20b0
--- /dev/null
+++ b/server/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/server/src/Data/Database/User.cs b/server/src/Data/Database/User.cs
new file mode 100644
index 0000000..c5063f6
--- /dev/null
+++ b/server/src/Data/Database/User.cs
@@ -0,0 +1,29 @@
+namespace IOL.GreatOffice.Api.Data.Database;
+
+public class User : Base
+{
+ public User() { }
+
+ public User(string username) {
+ Username = username;
+ }
+
+ public string Username { get; set; }
+ public string Password { get; set; }
+
+
+ public void HashAndSetPassword(string password) {
+ Password = PasswordHelper.HashPassword(password);
+ }
+
+ public bool VerifyPassword(string password) {
+ return PasswordHelper.Verify(password, Password);
+ }
+
+ public IEnumerable<Claim> DefaultClaims() {
+ return new Claim[] {
+ new(AppClaims.USER_ID, Id.ToString()),
+ new(AppClaims.NAME, Username),
+ };
+ }
+}
diff --git a/server/src/Data/Dtos/TimeQueryDto.cs b/server/src/Data/Dtos/TimeQueryDto.cs
new file mode 100644
index 0000000..f734cb1
--- /dev/null
+++ b/server/src/Data/Dtos/TimeQueryDto.cs
@@ -0,0 +1,34 @@
+
+namespace IOL.GreatOffice.Api.Data.Dtos;
+
+public class TimeQueryDto
+{
+ public TimeQueryDto() {
+ Results = new List<TimeEntry.TimeEntryDto>();
+ }
+
+ /// <summary>
+ /// List of entries.
+ /// </summary>
+ public List<TimeEntry.TimeEntryDto> Results { get; set; }
+
+ /// <summary>
+ /// Curren page.
+ /// </summary>
+ public int Page { get; set; }
+
+ /// <summary>
+ /// Maximum count of entries in a page.
+ /// </summary>
+ public int PageSize { get; set; }
+
+ /// <summary>
+ /// Total count of entries.
+ /// </summary>
+ public int TotalSize { get; set; }
+
+ /// <summary>
+ /// Total count of pages.
+ /// </summary>
+ public int TotalPageCount { get; set; }
+}
diff --git a/server/src/Data/Dtos/UserArchiveDto.cs b/server/src/Data/Dtos/UserArchiveDto.cs
new file mode 100644
index 0000000..63b1470
--- /dev/null
+++ b/server/src/Data/Dtos/UserArchiveDto.cs
@@ -0,0 +1,131 @@
+
+namespace IOL.GreatOffice.Api.Data.Dtos;
+
+/// <summary>
+/// Represents a user archive as it is provided to users.
+/// </summary>
+public class UserArchiveDto
+{
+ /// <inheritdoc cref="UserArchiveDto"/>
+ public UserArchiveDto(User user) {
+ Meta = new MetaDto {
+ GeneratedAt = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ")
+ };
+ User = new UserDto(user);
+ Entries = new List<EntryDto>();
+ }
+
+ /// <summary>
+ /// Metadata for the user archive.
+ /// </summary>
+ public MetaDto Meta { get; }
+
+ /// <summary>
+ /// Relevant user data for the archive.
+ /// </summary>
+ public UserDto User { get; }
+
+ /// <summary>
+ /// List of entries that the user has created.
+ /// </summary>
+ public List<EntryDto> Entries { get; }
+
+ public void CountEntries() {
+ Meta.EntryCount = Entries.Count;
+ }
+
+ /// <summary>
+ /// Represents a time entry in the data archive.
+ /// </summary>
+ public class EntryDto
+ {
+ public string CreatedAt { get; init; }
+
+ [JsonIgnore]
+ public DateTime StartDateTime { get; init; }
+
+ /// <summary>
+ /// ISO 8601 string of the UTC date the time entry started.
+ /// </summary>
+ public string Start => StartDateTime.ToString("yyyy-MM-ddTHH:mm:ssZ");
+
+ [JsonIgnore]
+ public DateTime StopDateTime { get; init; }
+
+ /// <summary>
+ /// ISO 8601 string of the UTC date the time entry stopped.
+ /// </summary>
+ public string Stop => StopDateTime.ToString("yyyy-MM-ddTHH:mm:ssZ");
+
+ /// <summary>
+ /// Total amount of minutes elapsed from start to stop on this time entry.
+ /// </summary>
+ public double Minutes => StopDateTime.Subtract(StartDateTime).TotalMinutes;
+
+ public string Description { get; init; }
+
+ /// <summary>
+ /// Archive spesific category for this time entry.
+ /// </summary>
+ public CategoryDto Category { get; init; }
+
+ /// <summary>
+ /// Archive spesific list of labels for this time entry.
+ /// </summary>
+ public List<LabelDto> Labels { get; init; }
+ }
+
+ /// <summary>
+ /// Time entry category as it is written to the user archive.
+ /// </summary>
+ public class CategoryDto
+ {
+ public string Name { get; init; }
+ public string Color { get; init; }
+ }
+
+ /// <summary>
+ /// Time entry label as it is written to the user archive.
+ /// </summary>
+ public class LabelDto
+ {
+ public string Name { get; init; }
+ public string Color { get; init; }
+ }
+
+
+ /// <summary>
+ /// Represents the user who this archive's data is based on.
+ /// </summary>
+ public class UserDto
+ {
+ /// <inheritdoc cref="UserDto"/>
+ public UserDto(User user) {
+ Username = user.Username;
+ CreatedAt = user.CreatedAt;
+ }
+
+ /// <summary>
+ /// UTC date this user was created.
+ /// </summary>
+ public DateTime CreatedAt { get; }
+
+ public string Username { get; }
+ }
+
+ /// <summary>
+ /// Represents the meta object which contains metdata for this archive.
+ /// </summary>
+ public class MetaDto
+ {
+ /// <summary>
+ /// ISO 8601 UTC date string for when this archive was created.
+ /// </summary>
+ public string GeneratedAt { get; init; }
+
+ /// <summary>
+ /// Amount of entries in the archive.
+ /// </summary>
+ public int EntryCount { get; set; }
+ }
+}
diff --git a/server/src/Data/Enums/TimeEntryQueryDuration.cs b/server/src/Data/Enums/TimeEntryQueryDuration.cs
new file mode 100644
index 0000000..af70ca6
--- /dev/null
+++ b/server/src/Data/Enums/TimeEntryQueryDuration.cs
@@ -0,0 +1,37 @@
+namespace IOL.GreatOffice.Api.Data.Enums;
+
+/// <summary>
+/// Specify a duration filter for time entry queries.
+/// </summary>
+public enum TimeEntryQueryDuration
+{
+ /// <summary>
+ /// Only query entries created today.
+ /// </summary>
+ TODAY = 0,
+
+ /// <summary>
+ /// Only query entries created this week.
+ /// </summary>
+ THIS_WEEK = 1,
+
+ /// <summary>
+ /// Only query entries created this month.
+ /// </summary>
+ THIS_MONTH = 2,
+
+ /// <summary>
+ /// Only query entries created this year.
+ /// </summary>
+ THIS_YEAR = 3,
+
+ /// <summary>
+ /// Only query entries created at a spesific date.
+ /// </summary>
+ SPECIFIC_DATE = 4,
+
+ /// <summary>
+ /// Only query entries created between two dates.
+ /// </summary>
+ DATE_RANGE = 5,
+}
diff --git a/server/src/Data/Exceptions/ForgotPasswordRequestNotFoundException.cs b/server/src/Data/Exceptions/ForgotPasswordRequestNotFoundException.cs
new file mode 100644
index 0000000..02474b4
--- /dev/null
+++ b/server/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/server/src/Data/Exceptions/UserNotFoundException.cs b/server/src/Data/Exceptions/UserNotFoundException.cs
new file mode 100644
index 0000000..06b57a9
--- /dev/null
+++ b/server/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/server/src/Data/Models/ApiSpecDocument.cs b/server/src/Data/Models/ApiSpecDocument.cs
new file mode 100644
index 0000000..1c7d936
--- /dev/null
+++ b/server/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/server/src/Data/Models/AppPath.cs b/server/src/Data/Models/AppPath.cs
new file mode 100644
index 0000000..e47e48c
--- /dev/null
+++ b/server/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/server/src/Data/Models/LoggedInUserModel.cs b/server/src/Data/Models/LoggedInUserModel.cs
new file mode 100644
index 0000000..4a5bef9
--- /dev/null
+++ b/server/src/Data/Models/LoggedInUserModel.cs
@@ -0,0 +1,8 @@
+namespace IOL.GreatOffice.Api.Data.Models;
+
+public class LoggedInUserModel
+{
+ public LoggedInUserModel() { }
+ public Guid Id { get; set; }
+ public string Username { get; set; }
+}
diff --git a/server/src/Data/Results/ErrorResult.cs b/server/src/Data/Results/ErrorResult.cs
new file mode 100644
index 0000000..fd2fd6a
--- /dev/null
+++ b/server/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/server/src/Data/Static/AppClaims.cs b/server/src/Data/Static/AppClaims.cs
new file mode 100644
index 0000000..8b6d3a8
--- /dev/null
+++ b/server/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/server/src/Data/Static/AppConstants.cs b/server/src/Data/Static/AppConstants.cs
new file mode 100644
index 0000000..61e5cd5
--- /dev/null
+++ b/server/src/Data/Static/AppConstants.cs
@@ -0,0 +1,11 @@
+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";
+}
diff --git a/server/src/Data/Static/AppEnvironmentVariables.cs b/server/src/Data/Static/AppEnvironmentVariables.cs
new file mode 100644
index 0000000..a734146
--- /dev/null
+++ b/server/src/Data/Static/AppEnvironmentVariables.cs
@@ -0,0 +1,27 @@
+namespace IOL.GreatOffice.Api.Data.Static;
+
+public static class AppEnvironmentVariables
+{
+ public const string DB_HOST = "DB_HOST";
+ public const string DB_PORT = "DB_PORT";
+ public const string DB_USER = "DB_USER";
+ public const string DB_PASSWORD = "DB_PASSWORD";
+ public const string DB_NAME = "DB_NAME";
+ public const string QUARTZ_DB_HOST = "QUARTZ_DB_HOST";
+ public const string QUARTZ_DB_PORT = "QUARTZ_DB_PORT";
+ public const string QUARTZ_DB_USER = "QUARTZ_DB_USER";
+ public const string QUARTZ_DB_PASSWORD = "QUARTZ_DB_PASSWORD";
+ public const string QUARTZ_DB_NAME = "QUARTZ_DB_NAME";
+ public const string SEQ_API_KEY = "SEQ_API_KEY";
+ public const string SEQ_API_URL = "SEQ_API_URL";
+ public const string SMTP_HOST = "SMTP_HOST";
+ public const string SMTP_PORT = "SMTP_PORT";
+ public const string SMTP_USER = "SMTP_USER";
+ public const string SMTP_PASSWORD = "SMTP_PASSWORD";
+ public const string EMAIL_FROM_ADDRESS = "EMAIL_FROM_ADDRESS";
+ public const string EMAIL_FROM_DISPLAY_NAME = "EMAIL_FROM_DISPLAY_NAME";
+ public const string ACCOUNTS_URL = "ACCOUNTS_URL";
+ public const string GITHUB_CLIENT_ID = "GH_CLIENT_ID";
+ public const string GITHUB_CLIENT_SECRET = "GH_CLIENT_SECRET";
+ public const string APP_AES_KEY = "APP_AES_KEY";
+}
diff --git a/server/src/Data/Static/AppHeaders.cs b/server/src/Data/Static/AppHeaders.cs
new file mode 100644
index 0000000..41a3085
--- /dev/null
+++ b/server/src/Data/Static/AppHeaders.cs
@@ -0,0 +1,6 @@
+namespace IOL.GreatOffice.Api.Data.Static;
+
+public static class AppHeaders
+{
+ public const string BROWSER_TIME_ZONE = "X-TimeZone";
+}
diff --git a/server/src/Data/Static/AppPaths.cs b/server/src/Data/Static/AppPaths.cs
new file mode 100644
index 0000000..a24f5af
--- /dev/null
+++ b/server/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/server/src/Data/Static/JsonSettings.cs b/server/src/Data/Static/JsonSettings.cs
new file mode 100644
index 0000000..a163c11
--- /dev/null
+++ b/server/src/Data/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;
+ };
+}