Scaffolded project

This commit is contained in:
Marco 2026-02-18 17:11:13 +01:00
commit 17a561094a
123 changed files with 64313 additions and 0 deletions

View file

@ -0,0 +1,61 @@
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
using WorkTracker.Configuration;
using WorkTracker.Data;
namespace WorkTracker.Services.Auth;
public sealed class SingleUserSeedService : IHostedService
{
private readonly IServiceProvider serviceProvider;
private readonly IOptions<SingleUserOptions> options;
private readonly ILogger<SingleUserSeedService> logger;
public SingleUserSeedService(
IServiceProvider serviceProvider,
IOptions<SingleUserOptions> options,
ILogger<SingleUserSeedService> logger)
{
this.serviceProvider = serviceProvider;
this.options = options;
this.logger = logger;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
if (!options.Value.SeedOnStartup)
{
return;
}
using var scope = serviceProvider.CreateScope();
var userManager = scope.ServiceProvider.GetRequiredService<UserManager<ApplicationUser>>();
var configuredEmail = options.Value.Email;
var existingUser = await userManager.FindByEmailAsync(configuredEmail);
if (existingUser is not null)
{
return;
}
var user = new ApplicationUser
{
UserName = configuredEmail,
Email = configuredEmail,
EmailConfirmed = true
};
var result = await userManager.CreateAsync(user, options.Value.Password);
if (!result.Succeeded)
{
var errors = string.Join("; ", result.Errors.Select(x => x.Description));
logger.LogError("Unable to seed single user account {Email}. Errors: {Errors}", configuredEmail, errors);
return;
}
logger.LogInformation("Seeded single user account {Email}", configuredEmail);
}
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}

View file

@ -0,0 +1,6 @@
namespace WorkTracker.Services.Festivities;
public interface IItalianFestivitySource
{
IReadOnlyCollection<DateOnly> GetFestivities(int year);
}

View file

@ -0,0 +1,46 @@
namespace WorkTracker.Services.Festivities;
public sealed class ItalianFestivitySource : IItalianFestivitySource
{
public IReadOnlyCollection<DateOnly> GetFestivities(int year)
{
var easterSunday = GetEasterSunday(year);
var easterMonday = easterSunday.AddDays(1);
return new HashSet<DateOnly>
{
new(year, 1, 1),
new(year, 1, 6),
easterSunday,
easterMonday,
new(year, 4, 25),
new(year, 5, 1),
new(year, 6, 2),
new(year, 8, 15),
new(year, 11, 1),
new(year, 12, 8),
new(year, 12, 25),
new(year, 12, 26)
};
}
private static DateOnly GetEasterSunday(int year)
{
var a = year % 19;
var b = year / 100;
var c = year % 100;
var d = b / 4;
var e = b % 4;
var f = (b + 8) / 25;
var g = (b - f + 1) / 3;
var h = (19 * a + b - d - g + 15) % 30;
var i = c / 4;
var k = c % 4;
var l = (32 + 2 * e + 2 * i - h - k) % 7;
var m = (a + 11 * h + 22 * l) / 451;
var month = (h + l - 7 * m + 114) / 31;
var day = ((h + l - 7 * m + 114) % 31) + 1;
return new DateOnly(year, month, day);
}
}

View file

@ -0,0 +1,10 @@
using WorkTracker.Domain;
namespace WorkTracker.Services.Settings;
public interface IAppSettingsService
{
Task<AppSettingsDocument> GetAsync(CancellationToken cancellationToken = default);
Task<AppSettingsDocument> SaveAsync(AppSettingsDocument settings, CancellationToken cancellationToken = default);
}

View file

@ -0,0 +1,44 @@
using MongoDB.Driver;
using WorkTracker.Domain;
namespace WorkTracker.Services.Settings;
public sealed class MongoAppSettingsService : IAppSettingsService
{
private const string DefaultSettingsId = "global";
private readonly IMongoCollection<AppSettingsDocument> appSettingsCollection;
public MongoAppSettingsService(IMongoDatabase database)
{
appSettingsCollection = database.GetCollection<AppSettingsDocument>("app_settings");
}
public async Task<AppSettingsDocument> GetAsync(CancellationToken cancellationToken = default)
{
var filter = Builders<AppSettingsDocument>.Filter.Eq(x => x.Id, DefaultSettingsId);
var settings = await appSettingsCollection.Find(filter).FirstOrDefaultAsync(cancellationToken);
if (settings is not null)
{
return settings;
}
var defaults = new AppSettingsDocument();
await appSettingsCollection.InsertOneAsync(defaults, cancellationToken: cancellationToken);
return defaults;
}
public async Task<AppSettingsDocument> SaveAsync(AppSettingsDocument settings, CancellationToken cancellationToken = default)
{
var existing = await GetAsync(cancellationToken);
settings.Id = DefaultSettingsId;
settings.CreatedAtUtc = existing.CreatedAtUtc;
settings.UpdatedAtUtc = DateTimeOffset.UtcNow;
var filter = Builders<AppSettingsDocument>.Filter.Eq(x => x.Id, DefaultSettingsId);
await appSettingsCollection.ReplaceOneAsync(filter, settings, new ReplaceOptions { IsUpsert = true }, cancellationToken);
return settings;
}
}