Auth integration
This commit is contained in:
@@ -6,5 +6,15 @@
|
||||
<MudNavLink Href="feiertage" Icon="@Icons.Material.Filled.Celebration">Feiertage</MudNavLink>
|
||||
<MudNavLink Href="urlaub-maximizer" Icon="@Icons.Material.Filled.AutoAwesome">Urlaubs-Maximizer</MudNavLink>
|
||||
<MudNavLink Href="settings" Icon="@Icons.Material.Filled.Settings">Einstellungen</MudNavLink>
|
||||
<MudDivider Class="mt-2 mb-2" />
|
||||
<AuthorizeView>
|
||||
<Authorized>
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Class="px-4 py-1" Spacing="1">
|
||||
<MudIcon Icon="@Icons.Material.Filled.AccountCircle" Color="Color.Primary" Size="Size.Small" />
|
||||
<MudText Typo="Typo.body2" Style="font-weight:600">@context.User.Identity?.Name</MudText>
|
||||
</MudStack>
|
||||
<MudNavLink Href="/auth/logout" Icon="@Icons.Material.Filled.Logout">Abmelden</MudNavLink>
|
||||
</Authorized>
|
||||
</AuthorizeView>
|
||||
</MudNavMenu>
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
@page "/feiertage"
|
||||
@rendermode InteractiveServer
|
||||
@attribute [Authorize]
|
||||
@inject HolidayService HolidayService
|
||||
|
||||
<PageTitle>Feiertage – Timetracker</PageTitle>
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
@page "/"
|
||||
@rendermode InteractiveServer
|
||||
@attribute [Authorize]
|
||||
@inject TimetrackerService TrackerService
|
||||
@inject HolidayService HolidayService
|
||||
@inject ISnackbar Snackbar
|
||||
@inject AuthenticationStateProvider AuthStateProvider
|
||||
|
||||
<PageTitle>KW @_kw – Wochenübersicht – Timetracker</PageTitle>
|
||||
|
||||
@@ -332,6 +334,7 @@ else
|
||||
private static readonly System.Globalization.CultureInfo _deCulture = new("de-DE");
|
||||
|
||||
private bool _loading = true;
|
||||
private int _userId;
|
||||
private DateOnly _monday;
|
||||
private List<DayVm> _days = [];
|
||||
private AppSettings _settings = new();
|
||||
@@ -349,10 +352,14 @@ else
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
var authState = await AuthStateProvider.GetAuthenticationStateAsync();
|
||||
var claim = authState.User.FindFirst(ClaimTypes.NameIdentifier);
|
||||
if (claim == null) return; // Prerender-Pass – Circuit noch nicht authentifiziert
|
||||
_userId = int.Parse(claim.Value);
|
||||
_monday = GetMonday(DateOnly.FromDateTime(DateTime.Today));
|
||||
_settings = await TrackerService.GetSettingsAsync();
|
||||
_settings = await TrackerService.GetSettingsAsync(_userId);
|
||||
await LoadWeek();
|
||||
_totalOvertime = await TrackerService.GetTotalOvertimeAsync(_settings);
|
||||
_totalOvertime = await TrackerService.GetTotalOvertimeAsync(_userId, _settings);
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
@@ -364,11 +371,11 @@ else
|
||||
_holidays = list.ToDictionary(h => h.Date, h => h.Name);
|
||||
_holidayYear = _monday.Year;
|
||||
}
|
||||
var dbDays = await TrackerService.GetWeekAsync(_monday);
|
||||
var dbDays = await TrackerService.GetWeekAsync(_userId, _monday);
|
||||
_days = Enumerable.Range(0, 7).Select(i =>
|
||||
{
|
||||
var date = _monday.AddDays(i);
|
||||
return DayVm.From(dbDays.FirstOrDefault(d => d.Date == date), date);
|
||||
return DayVm.From(dbDays.FirstOrDefault(d => d.Date == date), date, _userId);
|
||||
}).ToList();
|
||||
BuildWeekLabels();
|
||||
}
|
||||
@@ -421,7 +428,7 @@ else
|
||||
private async Task SaveDay(DayVm day)
|
||||
{
|
||||
await TrackerService.UpsertWorkDayAsync(day.ToWorkDay());
|
||||
_totalOvertime = await TrackerService.GetTotalOvertimeAsync(_settings);
|
||||
_totalOvertime = await TrackerService.GetTotalOvertimeAsync(_userId, _settings);
|
||||
BuildWeekLabels();
|
||||
}
|
||||
|
||||
@@ -486,6 +493,7 @@ else
|
||||
private sealed class DayVm
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int UserId { get; set; }
|
||||
public DateOnly Date { get; set; }
|
||||
public TimeSpan? Start { get; set; }
|
||||
public TimeSpan? End { get; set; }
|
||||
@@ -500,9 +508,10 @@ else
|
||||
|
||||
public TimeSpan? NetWork => GrossWork.HasValue ? GrossWork.Value - TotalBreakTime : null;
|
||||
|
||||
public static DayVm From(WorkDay? wd, DateOnly date) => new()
|
||||
public static DayVm From(WorkDay? wd, DateOnly date, int userId) => new()
|
||||
{
|
||||
Id = wd?.Id ?? 0,
|
||||
UserId = wd?.UserId ?? userId,
|
||||
Date = date,
|
||||
Start = wd?.StartTime?.ToTimeSpan(),
|
||||
End = wd?.EndTime?.ToTimeSpan(),
|
||||
@@ -517,6 +526,7 @@ else
|
||||
public WorkDay ToWorkDay() => new()
|
||||
{
|
||||
Id = Id,
|
||||
UserId = UserId,
|
||||
Date = Date,
|
||||
StartTime = Start.HasValue ? TimeOnly.FromTimeSpan(Start.Value) : null,
|
||||
EndTime = End.HasValue ? TimeOnly.FromTimeSpan(End.Value) : null,
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
@page "/login"
|
||||
@rendermode InteractiveServer
|
||||
@attribute [AllowAnonymous]
|
||||
@inject NavigationManager Nav
|
||||
@inject ISnackbar Snackbar
|
||||
|
||||
<PageTitle>Anmelden – Timetracker</PageTitle>
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.Small" Class="mt-16">
|
||||
<MudStack AlignItems="AlignItems.Center" Spacing="4">
|
||||
|
||||
@* ── Logo / Header ── *@
|
||||
<MudStack AlignItems="AlignItems.Center" Spacing="1">
|
||||
<MudIcon Icon="@Icons.Material.Filled.AccessTime"
|
||||
Style="font-size:4rem; color:#1565C0" />
|
||||
<MudText Typo="Typo.h4" Style="font-weight:700; color:#1565C0">Timetracker</MudText>
|
||||
</MudStack>
|
||||
|
||||
<MudPaper Elevation="4" Class="pa-6 rounded-xl" Style="width:100%">
|
||||
<MudTabs @bind-ActivePanelIndex="_activeTab" Rounded="true" Centered="true" Color="Color.Primary">
|
||||
|
||||
@* ── Login ── *@
|
||||
<MudTabPanel Text="Anmelden">
|
||||
<MudStack Spacing="3" Class="mt-4">
|
||||
@if (_error != null && _activeTab == 0)
|
||||
{
|
||||
<MudAlert Severity="Severity.Error" Dense="true">@_error</MudAlert>
|
||||
}
|
||||
<form action="/auth/login" method="post">
|
||||
<MudStack Spacing="3">
|
||||
<MudTextField @bind-Value="_loginUsername"
|
||||
Label="Benutzername"
|
||||
Variant="Variant.Outlined"
|
||||
Adornment="Adornment.Start"
|
||||
AdornmentIcon="@Icons.Material.Filled.Person"
|
||||
name="username"
|
||||
AutoFocus="true" />
|
||||
<MudTextField @bind-Value="_loginPassword"
|
||||
Label="Passwort"
|
||||
Variant="Variant.Outlined"
|
||||
Adornment="Adornment.Start"
|
||||
AdornmentIcon="@Icons.Material.Filled.Lock"
|
||||
InputType="@(_showLoginPw ? InputType.Text : InputType.Password)"
|
||||
AdornmentAriaLabel="Passwort anzeigen"
|
||||
name="password"
|
||||
OnAdornmentClick="@(() => _showLoginPw = !_showLoginPw)" />
|
||||
<MudButton ButtonType="ButtonType.Submit"
|
||||
Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
FullWidth="true"
|
||||
Size="Size.Large"
|
||||
StartIcon="@Icons.Material.Filled.Login">
|
||||
Anmelden
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
</form>
|
||||
</MudStack>
|
||||
</MudTabPanel>
|
||||
|
||||
@* ── Registrieren ── *@
|
||||
<MudTabPanel Text="Registrieren">
|
||||
<MudStack Spacing="3" Class="mt-4">
|
||||
@if (_error != null && _activeTab == 1)
|
||||
{
|
||||
<MudAlert Severity="Severity.Error" Dense="true">@_error</MudAlert>
|
||||
}
|
||||
<form action="/auth/register" method="post">
|
||||
<MudStack Spacing="3">
|
||||
<MudTextField @bind-Value="_regUsername"
|
||||
Label="Benutzername"
|
||||
Variant="Variant.Outlined"
|
||||
Adornment="Adornment.Start"
|
||||
AdornmentIcon="@Icons.Material.Filled.Person"
|
||||
name="username"
|
||||
HelperText="Mindestens 3 Zeichen" />
|
||||
<MudTextField @bind-Value="_regPassword"
|
||||
Label="Passwort"
|
||||
Variant="Variant.Outlined"
|
||||
Adornment="Adornment.Start"
|
||||
AdornmentIcon="@Icons.Material.Filled.Lock"
|
||||
InputType="@(_showRegPw ? InputType.Text : InputType.Password)"
|
||||
AdornmentAriaLabel="Passwort anzeigen"
|
||||
name="password"
|
||||
HelperText="Mindestens 6 Zeichen"
|
||||
OnAdornmentClick="@(() => _showRegPw = !_showRegPw)" />
|
||||
<MudButton ButtonType="ButtonType.Submit"
|
||||
Variant="Variant.Filled"
|
||||
Color="Color.Secondary"
|
||||
FullWidth="true"
|
||||
Size="Size.Large"
|
||||
StartIcon="@Icons.Material.Filled.PersonAdd">
|
||||
Konto erstellen
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
</form>
|
||||
</MudStack>
|
||||
</MudTabPanel>
|
||||
|
||||
</MudTabs>
|
||||
</MudPaper>
|
||||
</MudStack>
|
||||
</MudContainer>
|
||||
|
||||
@code {
|
||||
private int _activeTab = 0;
|
||||
private string? _error;
|
||||
private string _loginUsername = "";
|
||||
private string _loginPassword = "";
|
||||
private string _regUsername = "";
|
||||
private string _regPassword = "";
|
||||
private bool _showLoginPw;
|
||||
private bool _showRegPw;
|
||||
|
||||
[SupplyParameterFromQuery(Name = "error")]
|
||||
public string? ErrorParam { get; set; }
|
||||
|
||||
[SupplyParameterFromQuery(Name = "tab")]
|
||||
public string? TabParam { get; set; }
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
_error = ErrorParam switch
|
||||
{
|
||||
"invalid" => "Benutzername oder Passwort falsch.",
|
||||
not null => Uri.UnescapeDataString(ErrorParam),
|
||||
_ => null
|
||||
};
|
||||
_activeTab = TabParam == "register" ? 1 : 0;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
@page "/month"
|
||||
@rendermode InteractiveServer
|
||||
@attribute [Authorize]
|
||||
@inject TimetrackerService TrackerService
|
||||
@inject HolidayService HolidayService
|
||||
@inject AuthenticationStateProvider AuthStateProvider
|
||||
|
||||
<PageTitle>@_deCulture.DateTimeFormat.GetMonthName(_month) @_year – Monatsübersicht – Timetracker</PageTitle>
|
||||
|
||||
@@ -150,6 +152,7 @@ else
|
||||
private static readonly System.Globalization.CultureInfo _deCulture = new("de-DE");
|
||||
|
||||
private bool _loading = true;
|
||||
private int _userId;
|
||||
private int _year = DateTime.Today.Year;
|
||||
private int _month = DateTime.Today.Month;
|
||||
private List<MonthDayVm> _days = [];
|
||||
@@ -167,16 +170,20 @@ else
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
_settings = await TrackerService.GetSettingsAsync();
|
||||
var authState = await AuthStateProvider.GetAuthenticationStateAsync();
|
||||
var claim = authState.User.FindFirst(ClaimTypes.NameIdentifier);
|
||||
if (claim == null) return;
|
||||
_userId = int.Parse(claim.Value);
|
||||
_settings = await TrackerService.GetSettingsAsync(_userId);
|
||||
await LoadMonth();
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
private async Task LoadMonth()
|
||||
{
|
||||
var workDays = await TrackerService.GetMonthAsync(_year, _month);
|
||||
var workDays = await TrackerService.GetMonthAsync(_userId, _year, _month);
|
||||
var holidays = await HolidayService.GetHolidaysAsync(_year);
|
||||
var vacations = await TrackerService.GetVacationDaysAsync(_year);
|
||||
var vacations = await TrackerService.GetVacationDaysAsync(_userId, _year);
|
||||
|
||||
var holidayMap = holidays.ToDictionary(h => h.Date, h => h.Name);
|
||||
var vacationSet = vacations.Select(v => v.Date).ToHashSet();
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
@page "/settings"
|
||||
@rendermode InteractiveServer
|
||||
@attribute [Authorize]
|
||||
@inject TimetrackerService TrackerService
|
||||
@inject HolidayService HolidayService
|
||||
@inject ISnackbar Snackbar
|
||||
@inject AuthenticationStateProvider AuthStateProvider
|
||||
|
||||
<PageTitle>Einstellungen – Timetracker</PageTitle>
|
||||
|
||||
@@ -375,6 +377,7 @@ else
|
||||
private static readonly System.Globalization.CultureInfo _deCulture = new("de-DE");
|
||||
|
||||
private AppSettings? _settings;
|
||||
private int _userId;
|
||||
private int _vacYear = DateTime.Today.Year;
|
||||
private List<VacationDay> _vacationDays = [];
|
||||
private DateTime? _newVacDateFrom;
|
||||
@@ -402,14 +405,18 @@ else
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
_settings = await TrackerService.GetSettingsAsync();
|
||||
var authState = await AuthStateProvider.GetAuthenticationStateAsync();
|
||||
var claim = authState.User.FindFirst(ClaimTypes.NameIdentifier);
|
||||
if (claim == null) return;
|
||||
_userId = int.Parse(claim.Value);
|
||||
_settings = await TrackerService.GetSettingsAsync(_userId);
|
||||
await LoadVacations();
|
||||
_holHolidays = await HolidayService.GetHolidaysAsync(_holYear);
|
||||
}
|
||||
|
||||
private async Task LoadVacations()
|
||||
{
|
||||
_vacationDays = await TrackerService.GetVacationDaysAsync(_vacYear);
|
||||
_vacationDays = await TrackerService.GetVacationDaysAsync(_userId, _vacYear);
|
||||
}
|
||||
|
||||
private async Task ChangeYear(int delta)
|
||||
@@ -438,7 +445,7 @@ else
|
||||
{
|
||||
if (_settings!.IsWorkDay(current.DayOfWeek))
|
||||
{
|
||||
await TrackerService.AddVacationDayAsync(new VacationDay { Date = current, Note = note });
|
||||
await TrackerService.AddVacationDayAsync(new VacationDay { UserId = _userId, Date = current, Note = note });
|
||||
added++;
|
||||
}
|
||||
current = current.AddDays(1);
|
||||
@@ -452,7 +459,7 @@ else
|
||||
|
||||
private async Task RemoveVacation(int id)
|
||||
{
|
||||
await TrackerService.RemoveVacationDayAsync(id);
|
||||
await TrackerService.RemoveVacationDayAsync(_userId, id);
|
||||
await LoadVacations();
|
||||
Snackbar.Add("Urlaubstag entfernt", Severity.Info);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
@page "/urlaub-maximizer"
|
||||
@rendermode InteractiveServer
|
||||
@attribute [Authorize]
|
||||
@inject TimetrackerService TrackerService
|
||||
@inject HolidayService HolidayService
|
||||
@inject ISnackbar Snackbar
|
||||
@inject AuthenticationStateProvider AuthStateProvider
|
||||
|
||||
<PageTitle>Urlaubs-Maximizer – Timetracker</PageTitle>
|
||||
|
||||
@@ -232,10 +234,15 @@ else
|
||||
private int _remainingDays;
|
||||
private List<Suggestion> _suggestions = [];
|
||||
private string _subLabel = "";
|
||||
private int _userId;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
_settings = await TrackerService.GetSettingsAsync();
|
||||
var authState = await AuthStateProvider.GetAuthenticationStateAsync();
|
||||
var claim = authState.User.FindFirst(ClaimTypes.NameIdentifier);
|
||||
if (claim == null) return;
|
||||
_userId = int.Parse(claim.Value);
|
||||
_settings = await TrackerService.GetSettingsAsync(_userId);
|
||||
await LoadYear();
|
||||
_loading = false;
|
||||
}
|
||||
@@ -243,7 +250,7 @@ else
|
||||
private async Task LoadYear()
|
||||
{
|
||||
var holidays = await HolidayService.GetHolidaysAsync(_year);
|
||||
var vacations = await TrackerService.GetVacationDaysAsync(_year);
|
||||
var vacations = await TrackerService.GetVacationDaysAsync(_userId, _year);
|
||||
_holidays = holidays.ToDictionary(h => h.Date, h => h.Name);
|
||||
_vacationSet = vacations.Select(v => v.Date).ToHashSet();
|
||||
_remainingDays = Math.Max(0, _settings.VacationDaysPerYear - vacations.Count);
|
||||
@@ -262,7 +269,7 @@ else
|
||||
private async Task TakeSuggestion(Suggestion s)
|
||||
{
|
||||
foreach (var d in s.VacationDaysToTake.Where(d => !_vacationSet.Contains(d)))
|
||||
await TrackerService.AddVacationDayAsync(new VacationDay { Date = d, Note = "Urlaubs-Maximizer" });
|
||||
await TrackerService.AddVacationDayAsync(new VacationDay { UserId = _userId, Date = d, Note = "Urlaubs-Maximizer" });
|
||||
await LoadYear();
|
||||
var word = s.VacationDaysNeeded == 1 ? "Urlaubstag" : "Urlaubstage";
|
||||
Snackbar.Add($"{s.VacationDaysNeeded} {word} eingetragen – {s.TotalFreeDays} Tage frei!", Severity.Success);
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
@inject NavigationManager Nav
|
||||
|
||||
@code {
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
var returnUrl = Uri.EscapeDataString(Nav.Uri);
|
||||
Nav.NavigateTo($"/login?returnUrl={returnUrl}", forceLoad: true);
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,11 @@
|
||||
|
||||
<Router AppAssembly="typeof(Program).Assembly" NotFoundPage="typeof(Pages.NotFound)">
|
||||
<Found Context="routeData">
|
||||
<RouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)" />
|
||||
<AuthorizeRouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)">
|
||||
<NotAuthorized>
|
||||
<RedirectToLogin />
|
||||
</NotAuthorized>
|
||||
</AuthorizeRouteView>
|
||||
<FocusOnNavigate RouteData="routeData" Selector="h1" />
|
||||
</Found>
|
||||
</Router>
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
@using System.Net.Http
|
||||
@using System.Net.Http.Json
|
||||
@using System.Security.Claims
|
||||
@using Microsoft.AspNetCore.Authorization
|
||||
@using Microsoft.AspNetCore.Components.Authorization
|
||||
@using Microsoft.AspNetCore.Components.Forms
|
||||
@using Microsoft.AspNetCore.Components.Routing
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
|
||||
@@ -3,6 +3,7 @@ namespace timetracker.Data;
|
||||
public class AppSettings
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int UserId { get; set; }
|
||||
public double DailyTargetHours { get; set; } = 7.5;
|
||||
public int MinimumBreakMinutes { get; set; } = 30;
|
||||
public int VacationDaysPerYear { get; set; } = 30;
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace timetracker.Data;
|
||||
|
||||
public class AuthService(IDbContextFactory<TimetrackerDbContext> factory)
|
||||
{
|
||||
public async Task<User?> LoginAsync(string username, string password)
|
||||
{
|
||||
await using var db = await factory.CreateDbContextAsync();
|
||||
var user = await db.Users
|
||||
.FirstOrDefaultAsync(u => u.Username == username);
|
||||
if (user == null) return null;
|
||||
return VerifyPassword(password, user.PasswordHash, user.PasswordSalt) ? user : null;
|
||||
}
|
||||
|
||||
public async Task<(User? User, string? Error)> RegisterAsync(string username, string password)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(username) || username.Length < 3)
|
||||
return (null, "Benutzername muss mindestens 3 Zeichen lang sein.");
|
||||
if (string.IsNullOrWhiteSpace(password) || password.Length < 6)
|
||||
return (null, "Passwort muss mindestens 6 Zeichen lang sein.");
|
||||
|
||||
await using var db = await factory.CreateDbContextAsync();
|
||||
if (await db.Users.AnyAsync(u => u.Username == username))
|
||||
return (null, "Benutzername bereits vergeben.");
|
||||
|
||||
var (hash, salt) = HashPassword(password);
|
||||
var user = new User { Username = username, PasswordHash = hash, PasswordSalt = salt };
|
||||
db.Users.Add(user);
|
||||
await db.SaveChangesAsync();
|
||||
return (user, null);
|
||||
}
|
||||
|
||||
private static (string hash, string salt) HashPassword(string password)
|
||||
{
|
||||
var saltBytes = RandomNumberGenerator.GetBytes(32);
|
||||
var salt = Convert.ToBase64String(saltBytes);
|
||||
var hash = ComputeHash(password, salt);
|
||||
return (hash, salt);
|
||||
}
|
||||
|
||||
private static bool VerifyPassword(string password, string hash, string salt)
|
||||
=> ComputeHash(password, salt) == hash;
|
||||
|
||||
private static string ComputeHash(string password, string salt)
|
||||
{
|
||||
var hash = Rfc2898DeriveBytes.Pbkdf2(
|
||||
Encoding.UTF8.GetBytes(password),
|
||||
Convert.FromBase64String(salt),
|
||||
iterations: 200_000,
|
||||
HashAlgorithmName.SHA256,
|
||||
outputLength: 32);
|
||||
return Convert.ToBase64String(hash);
|
||||
}
|
||||
}
|
||||
+191
@@ -0,0 +1,191 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using timetracker.Data;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace timetracker.Data.Migrations
|
||||
{
|
||||
[DbContext(typeof(TimetrackerDbContext))]
|
||||
[Migration("20260522081459_AddMultiUser")]
|
||||
partial class AddMultiUser
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "10.0.8");
|
||||
|
||||
modelBuilder.Entity("timetracker.Data.AppSettings", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<double>("DailyTargetHours")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<int>("MinimumBreakMinutes")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("VacationDaysPerYear")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("WorkFriday")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("WorkMonday")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("WorkSaturday")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("WorkSunday")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("WorkThursday")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("WorkTuesday")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("WorkWednesday")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("AppSettings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("timetracker.Data.BreakEntry", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<TimeOnly?>("EndTime")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<TimeOnly?>("StartTime")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("WorkDayId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("WorkDayId");
|
||||
|
||||
b.ToTable("BreakEntries");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("timetracker.Data.PublicHoliday", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateOnly>("Date")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("PublicHolidays");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("timetracker.Data.User", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PasswordSalt")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Username")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("timetracker.Data.VacationDay", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateOnly>("Date")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Note")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("VacationDays");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("timetracker.Data.WorkDay", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateOnly>("Date")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<TimeOnly?>("EndTime")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<TimeOnly?>("StartTime")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("WorkDays");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("timetracker.Data.BreakEntry", b =>
|
||||
{
|
||||
b.HasOne("timetracker.Data.WorkDay", "WorkDay")
|
||||
.WithMany("Breaks")
|
||||
.HasForeignKey("WorkDayId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("WorkDay");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("timetracker.Data.WorkDay", b =>
|
||||
{
|
||||
b.Navigation("Breaks");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace timetracker.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddMultiUser : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "UserId",
|
||||
table: "WorkDays",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "UserId",
|
||||
table: "VacationDays",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "UserId",
|
||||
table: "AppSettings",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Users",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
Username = table.Column<string>(type: "TEXT", nullable: false),
|
||||
PasswordHash = table.Column<string>(type: "TEXT", nullable: false),
|
||||
PasswordSalt = table.Column<string>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Users", x => x.Id);
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "Users");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "UserId",
|
||||
table: "WorkDays");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "UserId",
|
||||
table: "VacationDays");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "UserId",
|
||||
table: "AppSettings");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,9 @@ namespace timetracker.Data.Migrations
|
||||
b.Property<int>("MinimumBreakMinutes")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("VacationDaysPerYear")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
@@ -58,24 +61,6 @@ namespace timetracker.Data.Migrations
|
||||
b.ToTable("AppSettings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("timetracker.Data.PublicHoliday", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateOnly>("Date")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("PublicHolidays");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("timetracker.Data.BreakEntry", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -98,6 +83,47 @@ namespace timetracker.Data.Migrations
|
||||
b.ToTable("BreakEntries");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("timetracker.Data.PublicHoliday", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateOnly>("Date")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("PublicHolidays");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("timetracker.Data.User", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PasswordSalt")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Username")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("timetracker.Data.VacationDay", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -110,6 +136,9 @@ namespace timetracker.Data.Migrations
|
||||
b.Property<string>("Note")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("VacationDays");
|
||||
@@ -130,6 +159,9 @@ namespace timetracker.Data.Migrations
|
||||
b.Property<TimeOnly?>("StartTime")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("WorkDays");
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace timetracker.Data;
|
||||
|
||||
public class TimetrackerDbContext(DbContextOptions<TimetrackerDbContext> options) : DbContext(options)
|
||||
{
|
||||
public DbSet<User> Users => Set<User>();
|
||||
public DbSet<WorkDay> WorkDays => Set<WorkDay>();
|
||||
public DbSet<BreakEntry> BreakEntries => Set<BreakEntry>();
|
||||
public DbSet<AppSettings> AppSettings => Set<AppSettings>();
|
||||
|
||||
+16
-16
@@ -4,12 +4,12 @@ namespace timetracker.Data;
|
||||
|
||||
public class TimetrackerService(IDbContextFactory<TimetrackerDbContext> factory)
|
||||
{
|
||||
public async Task<List<WorkDay>> GetWeekAsync(DateOnly monday)
|
||||
public async Task<List<WorkDay>> GetWeekAsync(int userId, DateOnly monday)
|
||||
{
|
||||
await using var db = await factory.CreateDbContextAsync();
|
||||
return await db.WorkDays
|
||||
.Include(w => w.Breaks)
|
||||
.Where(w => w.Date >= monday && w.Date < monday.AddDays(7))
|
||||
.Where(w => w.UserId == userId && w.Date >= monday && w.Date < monday.AddDays(7))
|
||||
.OrderBy(w => w.Date)
|
||||
.ToListAsync();
|
||||
}
|
||||
@@ -19,7 +19,7 @@ public class TimetrackerService(IDbContextFactory<TimetrackerDbContext> factory)
|
||||
await using var db = await factory.CreateDbContextAsync();
|
||||
var existing = await db.WorkDays
|
||||
.Include(w => w.Breaks)
|
||||
.FirstOrDefaultAsync(w => w.Date == workDay.Date);
|
||||
.FirstOrDefaultAsync(w => w.UserId == workDay.UserId && w.Date == workDay.Date);
|
||||
|
||||
if (existing == null)
|
||||
{
|
||||
@@ -45,17 +45,17 @@ public class TimetrackerService(IDbContextFactory<TimetrackerDbContext> factory)
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task<AppSettings> GetSettingsAsync()
|
||||
public async Task<AppSettings> GetSettingsAsync(int userId)
|
||||
{
|
||||
await using var db = await factory.CreateDbContextAsync();
|
||||
return await db.AppSettings.FindAsync(1) ?? new AppSettings { Id = 1 };
|
||||
return await db.AppSettings.FirstOrDefaultAsync(s => s.UserId == userId)
|
||||
?? new AppSettings { UserId = userId };
|
||||
}
|
||||
|
||||
public async Task SaveSettingsAsync(AppSettings settings)
|
||||
{
|
||||
await using var db = await factory.CreateDbContextAsync();
|
||||
settings.Id = 1;
|
||||
var existing = await db.AppSettings.FindAsync(1);
|
||||
var existing = await db.AppSettings.FirstOrDefaultAsync(s => s.UserId == settings.UserId);
|
||||
if (existing == null)
|
||||
db.AppSettings.Add(settings);
|
||||
else
|
||||
@@ -75,11 +75,11 @@ public class TimetrackerService(IDbContextFactory<TimetrackerDbContext> factory)
|
||||
}
|
||||
|
||||
// ── Urlaub ────────────────────────────────────────────────────────────
|
||||
public async Task<List<VacationDay>> GetVacationDaysAsync(int year)
|
||||
public async Task<List<VacationDay>> GetVacationDaysAsync(int userId, int year)
|
||||
{
|
||||
await using var db = await factory.CreateDbContextAsync();
|
||||
return await db.VacationDays
|
||||
.Where(v => v.Date.Year == year)
|
||||
.Where(v => v.UserId == userId && v.Date.Year == year)
|
||||
.OrderBy(v => v.Date)
|
||||
.ToListAsync();
|
||||
}
|
||||
@@ -87,7 +87,7 @@ public class TimetrackerService(IDbContextFactory<TimetrackerDbContext> factory)
|
||||
public async Task AddVacationDayAsync(VacationDay vacationDay)
|
||||
{
|
||||
await using var db = await factory.CreateDbContextAsync();
|
||||
var exists = await db.VacationDays.AnyAsync(v => v.Date == vacationDay.Date);
|
||||
var exists = await db.VacationDays.AnyAsync(v => v.UserId == vacationDay.UserId && v.Date == vacationDay.Date);
|
||||
if (!exists)
|
||||
{
|
||||
vacationDay.Id = 0;
|
||||
@@ -96,10 +96,10 @@ public class TimetrackerService(IDbContextFactory<TimetrackerDbContext> factory)
|
||||
}
|
||||
}
|
||||
|
||||
public async Task RemoveVacationDayAsync(int id)
|
||||
public async Task RemoveVacationDayAsync(int userId, int id)
|
||||
{
|
||||
await using var db = await factory.CreateDbContextAsync();
|
||||
var v = await db.VacationDays.FindAsync(id);
|
||||
var v = await db.VacationDays.FirstOrDefaultAsync(v => v.Id == id && v.UserId == userId);
|
||||
if (v != null)
|
||||
{
|
||||
db.VacationDays.Remove(v);
|
||||
@@ -108,12 +108,12 @@ public class TimetrackerService(IDbContextFactory<TimetrackerDbContext> factory)
|
||||
}
|
||||
|
||||
// ── Gleitzeitkonto ───────────────────────────────────────────────────
|
||||
public async Task<TimeSpan> GetTotalOvertimeAsync(AppSettings settings)
|
||||
public async Task<TimeSpan> GetTotalOvertimeAsync(int userId, AppSettings settings)
|
||||
{
|
||||
await using var db = await factory.CreateDbContextAsync();
|
||||
var allDays = await db.WorkDays
|
||||
.Include(w => w.Breaks)
|
||||
.Where(w => w.StartTime != null && w.EndTime != null)
|
||||
.Where(w => w.UserId == userId && w.StartTime != null && w.EndTime != null)
|
||||
.ToListAsync();
|
||||
|
||||
var total = TimeSpan.Zero;
|
||||
@@ -132,14 +132,14 @@ public class TimetrackerService(IDbContextFactory<TimetrackerDbContext> factory)
|
||||
}
|
||||
|
||||
// ── Monatsübersicht ───────────────────────────────────────────────────
|
||||
public async Task<List<WorkDay>> GetMonthAsync(int year, int month)
|
||||
public async Task<List<WorkDay>> GetMonthAsync(int userId, int year, int month)
|
||||
{
|
||||
await using var db = await factory.CreateDbContextAsync();
|
||||
var from = new DateOnly(year, month, 1);
|
||||
var to = from.AddMonths(1);
|
||||
return await db.WorkDays
|
||||
.Include(w => w.Breaks)
|
||||
.Where(w => w.Date >= from && w.Date < to)
|
||||
.Where(w => w.UserId == userId && w.Date >= from && w.Date < to)
|
||||
.OrderBy(w => w.Date)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace timetracker.Data;
|
||||
|
||||
public class User
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Username { get; set; } = "";
|
||||
public string PasswordHash { get; set; } = "";
|
||||
public string PasswordSalt { get; set; } = "";
|
||||
}
|
||||
@@ -3,6 +3,7 @@ namespace timetracker.Data;
|
||||
public class VacationDay
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int UserId { get; set; }
|
||||
public DateOnly Date { get; set; }
|
||||
public string? Note { get; set; }
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ namespace timetracker.Data;
|
||||
public class WorkDay
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int UserId { get; set; }
|
||||
public DateOnly Date { get; set; }
|
||||
public TimeOnly? StartTime { get; set; }
|
||||
public TimeOnly? EndTime { get; set; }
|
||||
|
||||
+66
@@ -1,3 +1,6 @@
|
||||
using System.Security.Claims;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using MudBlazor.Services;
|
||||
using timetracker.Components;
|
||||
@@ -5,6 +8,19 @@ using timetracker.Data;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
|
||||
.AddCookie(options =>
|
||||
{
|
||||
options.LoginPath = "/login";
|
||||
options.LogoutPath = "/auth/logout";
|
||||
options.ExpireTimeSpan = TimeSpan.FromDays(30);
|
||||
options.SlidingExpiration = true;
|
||||
});
|
||||
builder.Services.AddAuthorization();
|
||||
builder.Services.AddCascadingAuthenticationState();
|
||||
builder.Services.AddHttpContextAccessor();
|
||||
builder.Services.AddScoped<AuthService>();
|
||||
|
||||
// Add services to the container.
|
||||
builder.Services.AddRazorComponents()
|
||||
.AddInteractiveServerComponents();
|
||||
@@ -39,10 +55,60 @@ if (app.Configuration.GetValue("EnableHttpsRedirect", !app.Environment.IsDevelop
|
||||
app.UseHttpsRedirection();
|
||||
}
|
||||
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
app.UseAntiforgery();
|
||||
|
||||
app.MapStaticAssets();
|
||||
app.MapRazorComponents<App>()
|
||||
.AddInteractiveServerRenderMode();
|
||||
|
||||
// ── Auth-Endpoints ────────────────────────────────────────────────────────────
|
||||
app.MapPost("/auth/login", async (HttpContext ctx, AuthService authService) =>
|
||||
{
|
||||
var form = await ctx.Request.ReadFormAsync();
|
||||
var username = form["username"].ToString();
|
||||
var password = form["password"].ToString();
|
||||
var user = await authService.LoginAsync(username, password);
|
||||
if (user == null)
|
||||
return Results.Redirect("/login?error=invalid");
|
||||
|
||||
var claims = new[] {
|
||||
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
|
||||
new Claim(ClaimTypes.Name, user.Username)
|
||||
};
|
||||
var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
await ctx.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,
|
||||
new ClaimsPrincipal(identity),
|
||||
new AuthenticationProperties { IsPersistent = true });
|
||||
return Results.Redirect("/");
|
||||
}).DisableAntiforgery();
|
||||
|
||||
app.MapPost("/auth/register", async (HttpContext ctx, AuthService authService) =>
|
||||
{
|
||||
var form = await ctx.Request.ReadFormAsync();
|
||||
var username = form["username"].ToString();
|
||||
var password = form["password"].ToString();
|
||||
var (user, error) = await authService.RegisterAsync(username, password);
|
||||
if (user == null)
|
||||
return Results.Redirect($"/login?tab=register&error={Uri.EscapeDataString(error!)}");
|
||||
|
||||
var claims = new[] {
|
||||
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
|
||||
new Claim(ClaimTypes.Name, user.Username)
|
||||
};
|
||||
var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
await ctx.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,
|
||||
new ClaimsPrincipal(identity),
|
||||
new AuthenticationProperties { IsPersistent = true });
|
||||
return Results.Redirect("/");
|
||||
}).DisableAntiforgery();
|
||||
|
||||
app.MapGet("/auth/logout", async (HttpContext ctx) =>
|
||||
{
|
||||
await ctx.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
return Results.Redirect("/login");
|
||||
});
|
||||
|
||||
app.Run();
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1 +1 @@
|
||||
{"GlobalPropertiesHash":"N2len2uEF6pGj8Qv3c4lMAeSFUjWl3rJ1rxPPY4/gB8=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["ovSerYHPMi4ExhJn2xU4WmJOZFaSXPk63P4ISGtQoag=","t8/S/iqD\u002BWOqoqxZIUWy\u002B0CcyTxhFe\u002B2vqS6syOPh/g=","mVmHEtzmBWHAiYW83bThvmsAmJe7oQFzJayYA2a6PXk=","DJbjU5RSeu8ssKk6\u002BU9qcxdWKLA7PaURGpcwMtU1cds=","6VuM8RXxuOppytZc57Uu0vfEpQ9I3GXlnrIiYTN3XfE=","qujjEy2OAvAD8tqujNixipvOC/CDKEPXWqs9\u002BY4LFaE=","7WuNDk23eyfNe62PHykuRmy3161tRcARFgNWu/aCgOU=","jurLJARZjB7Fe9JZEUjWenuLIUJ/sw\u002Bx/o88BnaFTQM=","AgpupSW\u002BrcNbe52yu92bURql10h5\u002BRXymMC1zzF8wBM=","n2t0OoSd\u002B9NIbEadHYn7A7hwpWm6gJFg2djhVViRYng=","Sd/GZUkwZ4Q8w0TCBY95I\u002BxxWZm7Xp3xfksG3IkBDbA=","2IBypkfqBOLTlleDMyYeWTHw1dZ5UaSN8Vi9nmT2J4Q=","Lnw840ru88dnnZSKFinR4LTzOZNDJZ89SuvDs3LG5dY=","xLaUDp7TmLTsx9D\u002BuLLhgqu\u002BJtD5qDLs78ujVLoNhD8=","SgpHkk6QnWRH2tChObAeejM8HLdc51H8B4SCozq1l9g=","cWbFJtEIrIyk935jQQFMHU54PZ9RdwmE1BHwt0UobPU=","R0BAVdym78gDoKsSMzBsb6x2W951BlKFN0FNFYCsCX0=","E2ywdGQj0a0y7VMb8A2SagnNbmvdgNrFzjMdvqqV\u002BwY=","eKWaGHod6XeCFKA2syO4b5bjGd52KeRMe2lxebLpbr0=","6Gt\u002BhW0YD4AIfi3\u002B0JWOcj5ZoRHBh\u002Bd/73EHzLb9ZFo=","E/hX5PlBL0xni6OyXeL\u002BzEtGzj0DfWxsFDaHAUwGu0g=","I4pM727TacYD1RDXN0wzY1zglsMaABlr19WVkApraeA=","q4hWoO1ud\u002BwqFwmCG1nzekXs8nQ7QQMO9l3OKCx3XQo=","M6YvTp/SYon85swfQmM8j\u002BpvvhMDCpXhh\u002BS\u002B62d/V8A=","xmR8fiTLL43ux/I8xI3x9qDZOQjmbEpSVczpZKX/o\u002BU=","rPWN7AcG4RCNgoc7gqqe5bzu2fZeRHANU4G5mRmf4X4=","4YEeh/h12kH0FUGdt8/sHDJmg4zYunaXhfW6POiKYQg=","I/B2gPx/d6O7VA7kVp3syVfL8hMvYHhkflGDSPirAUY=","zJOxsbpIjQOhSWamGE9OKGXBGWjYSnpjnXmsdC56bzo=","6MXGnBspymDhNbACXBV1es99mw15UCxkoq0pSEcWwck=","vwqCCdlnynihoratP5HTjoE6JShLlWi4O5KC5cFdw2s=","ScQoTm373N8A9VaM33H8osIAb0hMiQB3cU1gvaxCLMg=","5Wb9KEGOvI68lY1zg533rWI/3u1c2pizlvhvFBW\u002BT\u002BU=","crQzyJw61YMAO/5cQfffHdBTSXO4POIRR3UPwYueZfY=","WA6BQ4K/xJ4kSdHf0dPt16vUqlYSHp\u002BquO1y\u002Br4idlU=","X\u002ByfyT2MuAWgf9UmpGLy/DJi6r3xLJ7R0fjn\u002BO3\u002BR\u002Bo=","n\u002BDKXe9Y3zHWEn4UB/eNHQTMuhDctkV5BDSwuliCKYo=","tUzryyDyISAnyr1e7ujmEglKl80GaEbNkw0uWsRegIM=","T1MNLl33nCZGSd4XbUBoRmZ1fyK\u002BBWsY0O4NHWiadQ0=","1GJdKBhQwxAongm2xWk7yLdbPyYKlYs\u002BByo/FSwa2Og=","QaD0ihDfiZcRw8Fa5XSqStAEkcFjb0xglTXrbeJN4a8=","ajlLSJ\u002BIWKj8EKV35W1hH4reyjN9/r1Ch8e1dBNjBSw=","DcApRy4nhjlP662diHmukEKqYuyoe0KHyOxGVdr9PyM=","zl\u002BzcRoxnXPakxxXJN6l9VLMwGk\u002By/SQT8B0WSnZMvM=","DiVsG2cevQdVuEwxHL\u002BgPGxh9fRtM3xn1Ix89OU01l4=","UKin6fGxIhO8Gs7LkLmN4kuS0Kl9uZWqgc6x7t2/xf0=","ejq560JF7/oAnu6I4VFt5f2wcSlzaEHpYTqsRh06o9A=","GDvNJZr3M2dgiymsy98U2bIMNajpHpIqUgn3VJu89NQ=","niE7G3oChy6PoSAzXj1Gj8yXNzqzO2SehGWXSGOs3ws=","TWCaa80sCF0UE7aeFJzSDp8vXtYi5Kf\u002Bv1v39TnyKs0=","SOIv4cDkxXKCivZ9A4sABqxsRRoomBeZ1nfsqaqq3aA=","GU6fHRhkxloPnx/L5QeYxDIprNNXbhjKzwM35bIsSdo=","2ioxqlu0rAxIbeWDbnlN9oLjD\u002BeRmektYla3y9AGc88=","ZWzbAgAFew9xp1iyo7knZbDaqWIv4Hm\u002BwCk9vPDUBSE=","pk1NXMdmexfgnN111i\u002B2VCt\u002Bh5FN6lNCwX\u002Bg5BR1Sgs=","SQ6Z3ikpNt1Ct\u002BC2yaguToCnzonqjZYbVGooOo7AY7Q=","gqFfW6e8\u002B0CzyL\u002BpttrsEfJhuLIlj/roGVa/T1vbFPo=","4C0vqrvHlqVp8iL/TwMg3WbaKZg8CIdmSCdnkCW3QT8=","hAhu8xNV6mIq4BPbje7mtlaB6rB9cnfpIkWsJTknocg=","EMmFm2Rvgw9UN/m5beCrxZ/0gK4sFx1rwrh4gdgnDSU=","Tp7HhdwydIvOa6w2bZfuPkEodLiDolooQOMj/RIqWD0=","BFYbEU1ne8O3890ApBmMSlCeMI8nMAZQEuo2W9qZIkw=","cehF2zRSp8AwpZ6pXjkx2ZvmkJBSnSNmX9eeZ8\u002B6O6w=","ak2/1eDOLIr8FN405ijsTWdZezAB4g53eQWw0BRWqpE=","iW599fFesdQ0y47pwj/FPUlHI/BG3klZVY4mfV97P7E=","XmS3sUENiF0muPKWCBQatKsMlhE11bSuzQjyQAwIqKY=","S84gXGiw17\u002BKUQqQ8WJGAEK7hmpmAFWuyw442W9m9vg=","Q0c94I84UXhvgssxmoOEeZZVYCQJZjts/ihDLFaCiss=","NH/A6t7u0uFF9sN6qoIt8K8nBlLJTutXBZ9u64pFEkc=","k37rU8U3X6kHrF9mPvy\u002B6vGHN\u002BmRwd1Rsu9i5oc6XJ4="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
||||
{"GlobalPropertiesHash":"N2len2uEF6pGj8Qv3c4lMAeSFUjWl3rJ1rxPPY4/gB8=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["ovSerYHPMi4ExhJn2xU4WmJOZFaSXPk63P4ISGtQoag=","t8/S/iqD\u002BWOqoqxZIUWy\u002B0CcyTxhFe\u002B2vqS6syOPh/g=","mVmHEtzmBWHAiYW83bThvmsAmJe7oQFzJayYA2a6PXk=","DJbjU5RSeu8ssKk6\u002BU9qcxdWKLA7PaURGpcwMtU1cds=","6VuM8RXxuOppytZc57Uu0vfEpQ9I3GXlnrIiYTN3XfE=","qujjEy2OAvAD8tqujNixipvOC/CDKEPXWqs9\u002BY4LFaE=","7WuNDk23eyfNe62PHykuRmy3161tRcARFgNWu/aCgOU=","jurLJARZjB7Fe9JZEUjWenuLIUJ/sw\u002Bx/o88BnaFTQM=","AgpupSW\u002BrcNbe52yu92bURql10h5\u002BRXymMC1zzF8wBM=","n2t0OoSd\u002B9NIbEadHYn7A7hwpWm6gJFg2djhVViRYng=","Sd/GZUkwZ4Q8w0TCBY95I\u002BxxWZm7Xp3xfksG3IkBDbA=","2IBypkfqBOLTlleDMyYeWTHw1dZ5UaSN8Vi9nmT2J4Q=","Lnw840ru88dnnZSKFinR4LTzOZNDJZ89SuvDs3LG5dY=","xLaUDp7TmLTsx9D\u002BuLLhgqu\u002BJtD5qDLs78ujVLoNhD8=","SgpHkk6QnWRH2tChObAeejM8HLdc51H8B4SCozq1l9g=","cWbFJtEIrIyk935jQQFMHU54PZ9RdwmE1BHwt0UobPU=","R0BAVdym78gDoKsSMzBsb6x2W951BlKFN0FNFYCsCX0=","E2ywdGQj0a0y7VMb8A2SagnNbmvdgNrFzjMdvqqV\u002BwY=","eKWaGHod6XeCFKA2syO4b5bjGd52KeRMe2lxebLpbr0=","6Gt\u002BhW0YD4AIfi3\u002B0JWOcj5ZoRHBh\u002Bd/73EHzLb9ZFo=","E/hX5PlBL0xni6OyXeL\u002BzEtGzj0DfWxsFDaHAUwGu0g=","I4pM727TacYD1RDXN0wzY1zglsMaABlr19WVkApraeA=","q4hWoO1ud\u002BwqFwmCG1nzekXs8nQ7QQMO9l3OKCx3XQo=","M6YvTp/SYon85swfQmM8j\u002BpvvhMDCpXhh\u002BS\u002B62d/V8A=","xmR8fiTLL43ux/I8xI3x9qDZOQjmbEpSVczpZKX/o\u002BU=","rPWN7AcG4RCNgoc7gqqe5bzu2fZeRHANU4G5mRmf4X4=","4YEeh/h12kH0FUGdt8/sHDJmg4zYunaXhfW6POiKYQg=","I/B2gPx/d6O7VA7kVp3syVfL8hMvYHhkflGDSPirAUY=","zJOxsbpIjQOhSWamGE9OKGXBGWjYSnpjnXmsdC56bzo=","6MXGnBspymDhNbACXBV1es99mw15UCxkoq0pSEcWwck=","vwqCCdlnynihoratP5HTjoE6JShLlWi4O5KC5cFdw2s=","ScQoTm373N8A9VaM33H8osIAb0hMiQB3cU1gvaxCLMg=","5Wb9KEGOvI68lY1zg533rWI/3u1c2pizlvhvFBW\u002BT\u002BU=","crQzyJw61YMAO/5cQfffHdBTSXO4POIRR3UPwYueZfY=","WA6BQ4K/xJ4kSdHf0dPt16vUqlYSHp\u002BquO1y\u002Br4idlU=","X\u002ByfyT2MuAWgf9UmpGLy/DJi6r3xLJ7R0fjn\u002BO3\u002BR\u002Bo=","n\u002BDKXe9Y3zHWEn4UB/eNHQTMuhDctkV5BDSwuliCKYo=","tUzryyDyISAnyr1e7ujmEglKl80GaEbNkw0uWsRegIM=","T1MNLl33nCZGSd4XbUBoRmZ1fyK\u002BBWsY0O4NHWiadQ0=","1GJdKBhQwxAongm2xWk7yLdbPyYKlYs\u002BByo/FSwa2Og=","QaD0ihDfiZcRw8Fa5XSqStAEkcFjb0xglTXrbeJN4a8=","ajlLSJ\u002BIWKj8EKV35W1hH4reyjN9/r1Ch8e1dBNjBSw=","DcApRy4nhjlP662diHmukEKqYuyoe0KHyOxGVdr9PyM=","zl\u002BzcRoxnXPakxxXJN6l9VLMwGk\u002By/SQT8B0WSnZMvM=","DiVsG2cevQdVuEwxHL\u002BgPGxh9fRtM3xn1Ix89OU01l4=","UKin6fGxIhO8Gs7LkLmN4kuS0Kl9uZWqgc6x7t2/xf0=","ejq560JF7/oAnu6I4VFt5f2wcSlzaEHpYTqsRh06o9A=","GDvNJZr3M2dgiymsy98U2bIMNajpHpIqUgn3VJu89NQ=","niE7G3oChy6PoSAzXj1Gj8yXNzqzO2SehGWXSGOs3ws=","TWCaa80sCF0UE7aeFJzSDp8vXtYi5Kf\u002Bv1v39TnyKs0=","SOIv4cDkxXKCivZ9A4sABqxsRRoomBeZ1nfsqaqq3aA=","ai1CABE7/c47ypIkjaDit5lzyUBLWjvvNxLvrggAohE=","2ioxqlu0rAxIbeWDbnlN9oLjD\u002BeRmektYla3y9AGc88=","ZWzbAgAFew9xp1iyo7knZbDaqWIv4Hm\u002BwCk9vPDUBSE=","HgxqJP3ldiIafkTvBBwB/28T3diIAJ7oN3hqfgQ9Kxc=","7Z/Bc6UO\u002BnTwEF1Bhi8coKTRrhvPdjnS5KhFbJHZyuI=","6xvIcY53zw\u002BouYBppzZhf9j631lueCP4AepKDpPFZLc=","EaULMAdq/h9gJo6D9j18s/QsIn1pfuMmdztg65lz1Ic=","4C0vqrvHlqVp8iL/TwMg3WbaKZg8CIdmSCdnkCW3QT8=","5l59oONcKS8dt7eCb1Jl1KQROo0JXt5brXo7D3UUbv8=","lH\u002BcYGTeSlITi8crNUIH4\u002BRxQRiRY4e88TocOvh0vng=","Px5vA8TGJcLr\u002B5y3yqjtCpTe0UN6tqKi1yvSSXfLG6I=","goCVUGyC8XonApSLTimnDrBuNidRio0jhkIWxhk4Xlg=","jwwQYOC\u002BEmbB37rtLRETqMKE04p34z68esNM1UGKNQ4=","cehF2zRSp8AwpZ6pXjkx2ZvmkJBSnSNmX9eeZ8\u002B6O6w=","ak2/1eDOLIr8FN405ijsTWdZezAB4g53eQWw0BRWqpE=","iW599fFesdQ0y47pwj/FPUlHI/BG3klZVY4mfV97P7E=","Xwav5bMzoofa3AJS3UwHS/0N9gwOFGAK98BOj3jfn80=","XmS3sUENiF0muPKWCBQatKsMlhE11bSuzQjyQAwIqKY=","InRyq0ks64otvGQeyZQtrNoJRijr\u002BqZrEZL1\u002BpkYfhw=","K1H3JdbP4eHxUIKs1pVHYY0V7FP9LHPqx7K7y49dPXQ=","Ta1VuT\u002BOsmwHcct1mkKNQFmnN9jyEmAro7m0MKhdrgA=","oYS18mLC1AdNbT1PoZ\u002BP0XsiXBf25aLmQ6kPJTnwMwg=","SSPxff1jRa3LUTShzcYBoRc9exs3wQiF68G/b6uxivE=","k37rU8U3X6kHrF9mPvy\u002B6vGHN\u002BmRwd1Rsu9i5oc6XJ4="],"CachedAssets":{},"CachedCopyCandidates":{}}
|
||||
@@ -1 +1 @@
|
||||
{"GlobalPropertiesHash":"byWqKuSx1f2vxGHl3jLmGrDGSjG9+QUusOxZF5wXRRM=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["ovSerYHPMi4ExhJn2xU4WmJOZFaSXPk63P4ISGtQoag=","t8/S/iqD\u002BWOqoqxZIUWy\u002B0CcyTxhFe\u002B2vqS6syOPh/g=","mVmHEtzmBWHAiYW83bThvmsAmJe7oQFzJayYA2a6PXk=","DJbjU5RSeu8ssKk6\u002BU9qcxdWKLA7PaURGpcwMtU1cds=","6VuM8RXxuOppytZc57Uu0vfEpQ9I3GXlnrIiYTN3XfE=","qujjEy2OAvAD8tqujNixipvOC/CDKEPXWqs9\u002BY4LFaE=","7WuNDk23eyfNe62PHykuRmy3161tRcARFgNWu/aCgOU=","jurLJARZjB7Fe9JZEUjWenuLIUJ/sw\u002Bx/o88BnaFTQM=","AgpupSW\u002BrcNbe52yu92bURql10h5\u002BRXymMC1zzF8wBM=","n2t0OoSd\u002B9NIbEadHYn7A7hwpWm6gJFg2djhVViRYng=","Sd/GZUkwZ4Q8w0TCBY95I\u002BxxWZm7Xp3xfksG3IkBDbA=","2IBypkfqBOLTlleDMyYeWTHw1dZ5UaSN8Vi9nmT2J4Q=","Lnw840ru88dnnZSKFinR4LTzOZNDJZ89SuvDs3LG5dY=","xLaUDp7TmLTsx9D\u002BuLLhgqu\u002BJtD5qDLs78ujVLoNhD8=","SgpHkk6QnWRH2tChObAeejM8HLdc51H8B4SCozq1l9g=","cWbFJtEIrIyk935jQQFMHU54PZ9RdwmE1BHwt0UobPU=","R0BAVdym78gDoKsSMzBsb6x2W951BlKFN0FNFYCsCX0=","E2ywdGQj0a0y7VMb8A2SagnNbmvdgNrFzjMdvqqV\u002BwY=","eKWaGHod6XeCFKA2syO4b5bjGd52KeRMe2lxebLpbr0=","6Gt\u002BhW0YD4AIfi3\u002B0JWOcj5ZoRHBh\u002Bd/73EHzLb9ZFo=","E/hX5PlBL0xni6OyXeL\u002BzEtGzj0DfWxsFDaHAUwGu0g=","I4pM727TacYD1RDXN0wzY1zglsMaABlr19WVkApraeA=","q4hWoO1ud\u002BwqFwmCG1nzekXs8nQ7QQMO9l3OKCx3XQo=","M6YvTp/SYon85swfQmM8j\u002BpvvhMDCpXhh\u002BS\u002B62d/V8A=","xmR8fiTLL43ux/I8xI3x9qDZOQjmbEpSVczpZKX/o\u002BU=","rPWN7AcG4RCNgoc7gqqe5bzu2fZeRHANU4G5mRmf4X4=","4YEeh/h12kH0FUGdt8/sHDJmg4zYunaXhfW6POiKYQg=","I/B2gPx/d6O7VA7kVp3syVfL8hMvYHhkflGDSPirAUY=","zJOxsbpIjQOhSWamGE9OKGXBGWjYSnpjnXmsdC56bzo=","6MXGnBspymDhNbACXBV1es99mw15UCxkoq0pSEcWwck=","vwqCCdlnynihoratP5HTjoE6JShLlWi4O5KC5cFdw2s=","ScQoTm373N8A9VaM33H8osIAb0hMiQB3cU1gvaxCLMg=","5Wb9KEGOvI68lY1zg533rWI/3u1c2pizlvhvFBW\u002BT\u002BU=","crQzyJw61YMAO/5cQfffHdBTSXO4POIRR3UPwYueZfY=","WA6BQ4K/xJ4kSdHf0dPt16vUqlYSHp\u002BquO1y\u002Br4idlU=","X\u002ByfyT2MuAWgf9UmpGLy/DJi6r3xLJ7R0fjn\u002BO3\u002BR\u002Bo=","n\u002BDKXe9Y3zHWEn4UB/eNHQTMuhDctkV5BDSwuliCKYo=","tUzryyDyISAnyr1e7ujmEglKl80GaEbNkw0uWsRegIM=","T1MNLl33nCZGSd4XbUBoRmZ1fyK\u002BBWsY0O4NHWiadQ0=","1GJdKBhQwxAongm2xWk7yLdbPyYKlYs\u002BByo/FSwa2Og=","QaD0ihDfiZcRw8Fa5XSqStAEkcFjb0xglTXrbeJN4a8=","ajlLSJ\u002BIWKj8EKV35W1hH4reyjN9/r1Ch8e1dBNjBSw=","DcApRy4nhjlP662diHmukEKqYuyoe0KHyOxGVdr9PyM=","zl\u002BzcRoxnXPakxxXJN6l9VLMwGk\u002By/SQT8B0WSnZMvM=","DiVsG2cevQdVuEwxHL\u002BgPGxh9fRtM3xn1Ix89OU01l4=","UKin6fGxIhO8Gs7LkLmN4kuS0Kl9uZWqgc6x7t2/xf0=","ejq560JF7/oAnu6I4VFt5f2wcSlzaEHpYTqsRh06o9A=","GDvNJZr3M2dgiymsy98U2bIMNajpHpIqUgn3VJu89NQ=","niE7G3oChy6PoSAzXj1Gj8yXNzqzO2SehGWXSGOs3ws=","TWCaa80sCF0UE7aeFJzSDp8vXtYi5Kf\u002Bv1v39TnyKs0=","SOIv4cDkxXKCivZ9A4sABqxsRRoomBeZ1nfsqaqq3aA=","GU6fHRhkxloPnx/L5QeYxDIprNNXbhjKzwM35bIsSdo=","2ioxqlu0rAxIbeWDbnlN9oLjD\u002BeRmektYla3y9AGc88=","ZWzbAgAFew9xp1iyo7knZbDaqWIv4Hm\u002BwCk9vPDUBSE=","pk1NXMdmexfgnN111i\u002B2VCt\u002Bh5FN6lNCwX\u002Bg5BR1Sgs=","SQ6Z3ikpNt1Ct\u002BC2yaguToCnzonqjZYbVGooOo7AY7Q=","gqFfW6e8\u002B0CzyL\u002BpttrsEfJhuLIlj/roGVa/T1vbFPo=","4C0vqrvHlqVp8iL/TwMg3WbaKZg8CIdmSCdnkCW3QT8=","hAhu8xNV6mIq4BPbje7mtlaB6rB9cnfpIkWsJTknocg=","EMmFm2Rvgw9UN/m5beCrxZ/0gK4sFx1rwrh4gdgnDSU=","Tp7HhdwydIvOa6w2bZfuPkEodLiDolooQOMj/RIqWD0=","BFYbEU1ne8O3890ApBmMSlCeMI8nMAZQEuo2W9qZIkw=","cehF2zRSp8AwpZ6pXjkx2ZvmkJBSnSNmX9eeZ8\u002B6O6w=","ak2/1eDOLIr8FN405ijsTWdZezAB4g53eQWw0BRWqpE=","iW599fFesdQ0y47pwj/FPUlHI/BG3klZVY4mfV97P7E=","XmS3sUENiF0muPKWCBQatKsMlhE11bSuzQjyQAwIqKY=","S84gXGiw17\u002BKUQqQ8WJGAEK7hmpmAFWuyw442W9m9vg=","Q0c94I84UXhvgssxmoOEeZZVYCQJZjts/ihDLFaCiss=","NH/A6t7u0uFF9sN6qoIt8K8nBlLJTutXBZ9u64pFEkc=","k37rU8U3X6kHrF9mPvy\u002B6vGHN\u002BmRwd1Rsu9i5oc6XJ4="],"CachedAssets":{"XmS3sUENiF0muPKWCBQatKsMlhE11bSuzQjyQAwIqKY=":{"Identity":"C:\\DEVQPDC_MW\\Uni\\Timetracker\\timetracker\\Components\\Layout\\ReconnectModal.razor.js","SourceId":"timetracker","SourceType":"Discovered","ContentRoot":"C:\\DEVQPDC_MW\\Uni\\Timetracker\\timetracker\\","BasePath":"/","RelativePath":"Components/Layout/ReconnectModal#[.{fingerprint}]?.razor.js","AssetKind":"All","AssetMode":"All","AssetRole":"Primary","AssetMergeBehavior":null,"AssetMergeSource":"","RelatedAsset":null,"AssetTraitName":null,"AssetTraitValue":null,"Fingerprint":"abdmv1u4y3","Integrity":"5u\u002Bv90fOttEFzE2qHMGMOXmy93Ik/BaGqLrrnu6mdGU=","CopyToOutputDirectory":"Never","CopyToPublishDirectory":"PreserveNewest","OriginalItemSpec":"Components\\Layout\\ReconnectModal.razor.js","FileLength":2448,"LastWriteTime":"2026-05-20T13:00:29.0736022+00:00"}},"CachedCopyCandidates":{}}
|
||||
{"GlobalPropertiesHash":"byWqKuSx1f2vxGHl3jLmGrDGSjG9+QUusOxZF5wXRRM=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["ovSerYHPMi4ExhJn2xU4WmJOZFaSXPk63P4ISGtQoag=","t8/S/iqD\u002BWOqoqxZIUWy\u002B0CcyTxhFe\u002B2vqS6syOPh/g=","mVmHEtzmBWHAiYW83bThvmsAmJe7oQFzJayYA2a6PXk=","DJbjU5RSeu8ssKk6\u002BU9qcxdWKLA7PaURGpcwMtU1cds=","6VuM8RXxuOppytZc57Uu0vfEpQ9I3GXlnrIiYTN3XfE=","qujjEy2OAvAD8tqujNixipvOC/CDKEPXWqs9\u002BY4LFaE=","7WuNDk23eyfNe62PHykuRmy3161tRcARFgNWu/aCgOU=","jurLJARZjB7Fe9JZEUjWenuLIUJ/sw\u002Bx/o88BnaFTQM=","AgpupSW\u002BrcNbe52yu92bURql10h5\u002BRXymMC1zzF8wBM=","n2t0OoSd\u002B9NIbEadHYn7A7hwpWm6gJFg2djhVViRYng=","Sd/GZUkwZ4Q8w0TCBY95I\u002BxxWZm7Xp3xfksG3IkBDbA=","2IBypkfqBOLTlleDMyYeWTHw1dZ5UaSN8Vi9nmT2J4Q=","Lnw840ru88dnnZSKFinR4LTzOZNDJZ89SuvDs3LG5dY=","xLaUDp7TmLTsx9D\u002BuLLhgqu\u002BJtD5qDLs78ujVLoNhD8=","SgpHkk6QnWRH2tChObAeejM8HLdc51H8B4SCozq1l9g=","cWbFJtEIrIyk935jQQFMHU54PZ9RdwmE1BHwt0UobPU=","R0BAVdym78gDoKsSMzBsb6x2W951BlKFN0FNFYCsCX0=","E2ywdGQj0a0y7VMb8A2SagnNbmvdgNrFzjMdvqqV\u002BwY=","eKWaGHod6XeCFKA2syO4b5bjGd52KeRMe2lxebLpbr0=","6Gt\u002BhW0YD4AIfi3\u002B0JWOcj5ZoRHBh\u002Bd/73EHzLb9ZFo=","E/hX5PlBL0xni6OyXeL\u002BzEtGzj0DfWxsFDaHAUwGu0g=","I4pM727TacYD1RDXN0wzY1zglsMaABlr19WVkApraeA=","q4hWoO1ud\u002BwqFwmCG1nzekXs8nQ7QQMO9l3OKCx3XQo=","M6YvTp/SYon85swfQmM8j\u002BpvvhMDCpXhh\u002BS\u002B62d/V8A=","xmR8fiTLL43ux/I8xI3x9qDZOQjmbEpSVczpZKX/o\u002BU=","rPWN7AcG4RCNgoc7gqqe5bzu2fZeRHANU4G5mRmf4X4=","4YEeh/h12kH0FUGdt8/sHDJmg4zYunaXhfW6POiKYQg=","I/B2gPx/d6O7VA7kVp3syVfL8hMvYHhkflGDSPirAUY=","zJOxsbpIjQOhSWamGE9OKGXBGWjYSnpjnXmsdC56bzo=","6MXGnBspymDhNbACXBV1es99mw15UCxkoq0pSEcWwck=","vwqCCdlnynihoratP5HTjoE6JShLlWi4O5KC5cFdw2s=","ScQoTm373N8A9VaM33H8osIAb0hMiQB3cU1gvaxCLMg=","5Wb9KEGOvI68lY1zg533rWI/3u1c2pizlvhvFBW\u002BT\u002BU=","crQzyJw61YMAO/5cQfffHdBTSXO4POIRR3UPwYueZfY=","WA6BQ4K/xJ4kSdHf0dPt16vUqlYSHp\u002BquO1y\u002Br4idlU=","X\u002ByfyT2MuAWgf9UmpGLy/DJi6r3xLJ7R0fjn\u002BO3\u002BR\u002Bo=","n\u002BDKXe9Y3zHWEn4UB/eNHQTMuhDctkV5BDSwuliCKYo=","tUzryyDyISAnyr1e7ujmEglKl80GaEbNkw0uWsRegIM=","T1MNLl33nCZGSd4XbUBoRmZ1fyK\u002BBWsY0O4NHWiadQ0=","1GJdKBhQwxAongm2xWk7yLdbPyYKlYs\u002BByo/FSwa2Og=","QaD0ihDfiZcRw8Fa5XSqStAEkcFjb0xglTXrbeJN4a8=","ajlLSJ\u002BIWKj8EKV35W1hH4reyjN9/r1Ch8e1dBNjBSw=","DcApRy4nhjlP662diHmukEKqYuyoe0KHyOxGVdr9PyM=","zl\u002BzcRoxnXPakxxXJN6l9VLMwGk\u002By/SQT8B0WSnZMvM=","DiVsG2cevQdVuEwxHL\u002BgPGxh9fRtM3xn1Ix89OU01l4=","UKin6fGxIhO8Gs7LkLmN4kuS0Kl9uZWqgc6x7t2/xf0=","ejq560JF7/oAnu6I4VFt5f2wcSlzaEHpYTqsRh06o9A=","GDvNJZr3M2dgiymsy98U2bIMNajpHpIqUgn3VJu89NQ=","niE7G3oChy6PoSAzXj1Gj8yXNzqzO2SehGWXSGOs3ws=","TWCaa80sCF0UE7aeFJzSDp8vXtYi5Kf\u002Bv1v39TnyKs0=","SOIv4cDkxXKCivZ9A4sABqxsRRoomBeZ1nfsqaqq3aA=","ai1CABE7/c47ypIkjaDit5lzyUBLWjvvNxLvrggAohE=","2ioxqlu0rAxIbeWDbnlN9oLjD\u002BeRmektYla3y9AGc88=","ZWzbAgAFew9xp1iyo7knZbDaqWIv4Hm\u002BwCk9vPDUBSE=","HgxqJP3ldiIafkTvBBwB/28T3diIAJ7oN3hqfgQ9Kxc=","7Z/Bc6UO\u002BnTwEF1Bhi8coKTRrhvPdjnS5KhFbJHZyuI=","6xvIcY53zw\u002BouYBppzZhf9j631lueCP4AepKDpPFZLc=","EaULMAdq/h9gJo6D9j18s/QsIn1pfuMmdztg65lz1Ic=","4C0vqrvHlqVp8iL/TwMg3WbaKZg8CIdmSCdnkCW3QT8=","5l59oONcKS8dt7eCb1Jl1KQROo0JXt5brXo7D3UUbv8=","lH\u002BcYGTeSlITi8crNUIH4\u002BRxQRiRY4e88TocOvh0vng=","Px5vA8TGJcLr\u002B5y3yqjtCpTe0UN6tqKi1yvSSXfLG6I=","goCVUGyC8XonApSLTimnDrBuNidRio0jhkIWxhk4Xlg=","jwwQYOC\u002BEmbB37rtLRETqMKE04p34z68esNM1UGKNQ4=","cehF2zRSp8AwpZ6pXjkx2ZvmkJBSnSNmX9eeZ8\u002B6O6w=","ak2/1eDOLIr8FN405ijsTWdZezAB4g53eQWw0BRWqpE=","iW599fFesdQ0y47pwj/FPUlHI/BG3klZVY4mfV97P7E=","Xwav5bMzoofa3AJS3UwHS/0N9gwOFGAK98BOj3jfn80=","XmS3sUENiF0muPKWCBQatKsMlhE11bSuzQjyQAwIqKY=","InRyq0ks64otvGQeyZQtrNoJRijr\u002BqZrEZL1\u002BpkYfhw=","K1H3JdbP4eHxUIKs1pVHYY0V7FP9LHPqx7K7y49dPXQ=","Ta1VuT\u002BOsmwHcct1mkKNQFmnN9jyEmAro7m0MKhdrgA=","oYS18mLC1AdNbT1PoZ\u002BP0XsiXBf25aLmQ6kPJTnwMwg=","SSPxff1jRa3LUTShzcYBoRc9exs3wQiF68G/b6uxivE=","k37rU8U3X6kHrF9mPvy\u002B6vGHN\u002BmRwd1Rsu9i5oc6XJ4="],"CachedAssets":{"XmS3sUENiF0muPKWCBQatKsMlhE11bSuzQjyQAwIqKY=":{"Identity":"C:\\DEVQPDC_MW\\Uni\\Timetracker\\timetracker\\Components\\Layout\\ReconnectModal.razor.js","SourceId":"timetracker","SourceType":"Discovered","ContentRoot":"C:\\DEVQPDC_MW\\Uni\\Timetracker\\timetracker\\","BasePath":"/","RelativePath":"Components/Layout/ReconnectModal#[.{fingerprint}]?.razor.js","AssetKind":"All","AssetMode":"All","AssetRole":"Primary","AssetMergeBehavior":null,"AssetMergeSource":"","RelatedAsset":null,"AssetTraitName":null,"AssetTraitValue":null,"Fingerprint":"abdmv1u4y3","Integrity":"5u\u002Bv90fOttEFzE2qHMGMOXmy93Ik/BaGqLrrnu6mdGU=","CopyToOutputDirectory":"Never","CopyToPublishDirectory":"PreserveNewest","OriginalItemSpec":"Components\\Layout\\ReconnectModal.razor.js","FileLength":2448,"LastWriteTime":"2026-05-20T13:00:29.0736022+00:00"}},"CachedCopyCandidates":{}}
|
||||
File diff suppressed because one or more lines are too long
@@ -13,7 +13,7 @@ using System.Reflection;
|
||||
[assembly: System.Reflection.AssemblyCompanyAttribute("timetracker")]
|
||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+0467f450368a3b05b63a5d2a2cc8779571042711")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+64c5f6aa2c77c868467dca487ae6b581bca6862d")]
|
||||
[assembly: System.Reflection.AssemblyProductAttribute("timetracker")]
|
||||
[assembly: System.Reflection.AssemblyTitleAttribute("timetracker")]
|
||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||
|
||||
@@ -1 +1 @@
|
||||
d82bd1af2676da228608191aea963cac3166fa85ba461e9bcfa8554e5f0bdffb
|
||||
452eedcfc45da63be9bf8c68bebf3387bcd4a96ecf3b483a73c4affbb5b93b4e
|
||||
|
||||
@@ -50,6 +50,10 @@ build_metadata.AdditionalFiles.CssScope =
|
||||
build_metadata.AdditionalFiles.TargetPath = Q29tcG9uZW50c1xQYWdlc1xIb21lLnJhem9y
|
||||
build_metadata.AdditionalFiles.CssScope =
|
||||
|
||||
[C:/DEVQPDC_MW/Uni/Timetracker/timetracker/Components/Pages/Login.razor]
|
||||
build_metadata.AdditionalFiles.TargetPath = Q29tcG9uZW50c1xQYWdlc1xMb2dpbi5yYXpvcg==
|
||||
build_metadata.AdditionalFiles.CssScope =
|
||||
|
||||
[C:/DEVQPDC_MW/Uni/Timetracker/timetracker/Components/Pages/Month.razor]
|
||||
build_metadata.AdditionalFiles.TargetPath = Q29tcG9uZW50c1xQYWdlc1xNb250aC5yYXpvcg==
|
||||
build_metadata.AdditionalFiles.CssScope =
|
||||
@@ -66,6 +70,10 @@ build_metadata.AdditionalFiles.CssScope =
|
||||
build_metadata.AdditionalFiles.TargetPath = Q29tcG9uZW50c1xQYWdlc1xVcmxhdWJzTWF4aW1pemVyLnJhem9y
|
||||
build_metadata.AdditionalFiles.CssScope =
|
||||
|
||||
[C:/DEVQPDC_MW/Uni/Timetracker/timetracker/Components/RedirectToLogin.razor]
|
||||
build_metadata.AdditionalFiles.TargetPath = Q29tcG9uZW50c1xSZWRpcmVjdFRvTG9naW4ucmF6b3I=
|
||||
build_metadata.AdditionalFiles.CssScope =
|
||||
|
||||
[C:/DEVQPDC_MW/Uni/Timetracker/timetracker/Components/Routes.razor]
|
||||
build_metadata.AdditionalFiles.TargetPath = Q29tcG9uZW50c1xSb3V0ZXMucmF6b3I=
|
||||
build_metadata.AdditionalFiles.CssScope =
|
||||
|
||||
@@ -1 +1 @@
|
||||
ec4c252fd2023b232a1f6088ccec5cbdd1025e488be8a7c43e01c6aa2c479e89
|
||||
73be10256eedd125d5f8f4d2e9dd0d1388a3fccf5758d7c2e52426f3812e17fd
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user