Compare commits
2
Commits
main
..
3f0d23dab2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3f0d23dab2 | ||
|
|
b12e557e1a
|
@@ -1,17 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net10.0</TargetFramework>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<SupportedPlatform Include="browser"/>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="10.0.0"/>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
@page "/"
|
|
||||||
@using LoopFit.Shared.Services
|
|
||||||
@inject IFormFactor FormFactor
|
|
||||||
|
|
||||||
<PageTitle>Home</PageTitle>
|
|
||||||
|
|
||||||
<h1>Hello, world!</h1>
|
|
||||||
|
|
||||||
Welcome to your new app running on <em>@factor</em> using <em>@platform</em>.
|
|
||||||
|
|
||||||
@code {
|
|
||||||
private string factor => FormFactor.GetFormFactor();
|
|
||||||
private string platform => FormFactor.GetPlatform();
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
@page "/not-found"
|
|
||||||
@layout MainLayout
|
|
||||||
|
|
||||||
<h3>Not Found</h3>
|
|
||||||
<p>Sorry, the content you are looking for does not exist.</p>
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
namespace LoopFit.Shared.Services;
|
|
||||||
|
|
||||||
public interface IFormFactor
|
|
||||||
{
|
|
||||||
public string GetFormFactor();
|
|
||||||
public string GetPlatform();
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
@using System.Net.Http
|
|
||||||
@using System.Net.Http.Json
|
|
||||||
@using Microsoft.AspNetCore.Components.Forms
|
|
||||||
@using Microsoft.AspNetCore.Components.Routing
|
|
||||||
@using Microsoft.AspNetCore.Components.Web
|
|
||||||
@using static Microsoft.AspNetCore.Components.Web.RenderMode
|
|
||||||
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
|
||||||
@using Microsoft.JSInterop
|
|
||||||
@using LoopFit
|
|
||||||
@using LoopFit.Shared
|
|
||||||
@using LoopFit.Shared.Layout
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.1 KiB |
File diff suppressed because one or more lines are too long
@@ -6,11 +6,11 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
<base href="/"/>
|
<base href="/"/>
|
||||||
<ResourcePreloader/>
|
<ResourcePreloader/>
|
||||||
<link rel="stylesheet" href="_content/LoopFit.Shared/lib/bootstrap/dist/css/bootstrap.min.css"/>
|
<link rel="stylesheet" href="lib/bootstrap/dist/css/bootstrap.min.css"/>
|
||||||
<link rel="stylesheet" href="_content/LoopFit.Shared/app.css"/>
|
<link rel="stylesheet" href="app.css"/>
|
||||||
<link rel="stylesheet" href="LoopFit.Web.styles.css"/>
|
<link rel="stylesheet" href="LoopFit.Web.styles.css"/>
|
||||||
<ImportMap/>
|
<ImportMap/>
|
||||||
<link rel="icon" type="image/png" href="_content/LoopFit.Shared/favicon.png"/>
|
<link rel="icon" type="image/png" href="favicon.png"/>
|
||||||
<HeadOutlet @rendermode="InteractiveServer"/>
|
<HeadOutlet @rendermode="InteractiveServer"/>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
@inherits LayoutComponentBase
|
@inherits LayoutComponentBase
|
||||||
|
|
||||||
<div class="page">
|
<div class="page">
|
||||||
<div class="sidebar">
|
<div class="sidebar">
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<div class="top-row ps-3 navbar navbar-dark">
|
<div class="top-row ps-3 navbar navbar-dark">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<a class="navbar-brand" href="">LoopFit</a>
|
<a class="navbar-brand" href="">LoopFit</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -13,7 +13,5 @@
|
|||||||
<span class="bi bi-house-door-fill-nav-menu" aria-hidden="true"></span> Home
|
<span class="bi bi-house-door-fill-nav-menu" aria-hidden="true"></span> Home
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
-11
@@ -38,14 +38,6 @@
|
|||||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-house-door-fill' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z'/%3E%3C/svg%3E");
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-house-door-fill' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z'/%3E%3C/svg%3E");
|
||||||
}
|
}
|
||||||
|
|
||||||
.bi-plus-square-fill-nav-menu {
|
|
||||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-plus-square-fill' viewBox='0 0 16 16'%3E%3Cpath d='M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm6.5 4.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3a.5.5 0 0 1 1 0z'/%3E%3C/svg%3E");
|
|
||||||
}
|
|
||||||
|
|
||||||
.bi-list-nested-nav-menu {
|
|
||||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.5 11.5A.5.5 0 0 1 5 11h10a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 3 7h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 1 3h10a.5.5 0 0 1 0 1H1a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E");
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-item {
|
.nav-item {
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
padding-bottom: 0.5rem;
|
padding-bottom: 0.5rem;
|
||||||
@@ -95,10 +87,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.nav-scrollable {
|
.nav-scrollable {
|
||||||
/* Never collapse the sidebar for wide screens */
|
|
||||||
display: block;
|
display: block;
|
||||||
|
|
||||||
/* Allow sidebar to scroll for tall menus */
|
|
||||||
height: calc(100vh - 3.5rem);
|
height: calc(100vh - 3.5rem);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
@page "/"
|
||||||
|
|
||||||
|
<PageTitle>Home</PageTitle>
|
||||||
|
|
||||||
|
<h1>LoopFit</h1>
|
||||||
|
|
||||||
|
<p>Willkommen bei LoopFit - dein Fitness-Tracker.</p>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
@page "/not-found"
|
||||||
|
@layout Layout.MainLayout
|
||||||
|
|
||||||
|
<h3>Not Found</h3>
|
||||||
|
<p>Sorry, the content you are looking for does not exist.</p>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<Router AppAssembly="typeof(Layout.MainLayout).Assembly" NotFoundPage="typeof(Pages.NotFound)">
|
<Router AppAssembly="typeof(App).Assembly" NotFoundPage="typeof(Pages.NotFound)">
|
||||||
<Found Context="routeData">
|
<Found Context="routeData">
|
||||||
<RouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)"/>
|
<RouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)"/>
|
||||||
<FocusOnNavigate RouteData="routeData" Selector="h1"/>
|
<FocusOnNavigate RouteData="routeData" Selector="h1"/>
|
||||||
@@ -6,7 +6,5 @@
|
|||||||
@using static Microsoft.AspNetCore.Components.Web.RenderMode
|
@using static Microsoft.AspNetCore.Components.Web.RenderMode
|
||||||
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
||||||
@using Microsoft.JSInterop
|
@using Microsoft.JSInterop
|
||||||
@using LoopFit.Shared
|
|
||||||
@using LoopFit.Shared.Layout
|
|
||||||
@using LoopFit.Web.Components
|
@using LoopFit.Web.Components
|
||||||
@using LoopFit.Web.Components.Layout
|
@using LoopFit.Web.Components.Layout
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
using System.Security.Claims;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using LoopFit.Application.DTOs.Auth;
|
||||||
|
using LoopFit.Application.Interfaces;
|
||||||
|
|
||||||
|
namespace LoopFit.Web.Controllers;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/[controller]")]
|
||||||
|
public class AuthController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly IAuthService _authService;
|
||||||
|
|
||||||
|
public AuthController(IAuthService authService)
|
||||||
|
{
|
||||||
|
_authService = authService;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("register")]
|
||||||
|
public async Task<ActionResult<AuthResponse>> Register([FromBody] RegisterRequest request)
|
||||||
|
{
|
||||||
|
var result = await _authService.RegisterAsync(request);
|
||||||
|
|
||||||
|
if (result is null)
|
||||||
|
return BadRequest(new { message = "Registrierung fehlgeschlagen. Email existiert bereits oder Passwort ist nicht gültig." });
|
||||||
|
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("login")]
|
||||||
|
public async Task<ActionResult<AuthResponse>> Login([FromBody] LoginRequest request)
|
||||||
|
{
|
||||||
|
var result = await _authService.LoginAsync(request);
|
||||||
|
|
||||||
|
if (result is null)
|
||||||
|
return Unauthorized(new { message = "Email oder Passwort falsch." });
|
||||||
|
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Authorize]
|
||||||
|
[HttpGet("me")]
|
||||||
|
public async Task<ActionResult<AuthResponse>> GetCurrentUser()
|
||||||
|
{
|
||||||
|
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||||
|
|
||||||
|
if (userId is null)
|
||||||
|
return Unauthorized();
|
||||||
|
|
||||||
|
var result = await _authService.GetCurrentUserAsync(Guid.Parse(userId));
|
||||||
|
|
||||||
|
if (result is null)
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,7 +7,8 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\LoopFit.Shared\LoopFit.Shared.csproj"/>
|
<ProjectReference Include="..\src\LoopFit.Application\LoopFit.Application.csproj"/>
|
||||||
|
<ProjectReference Include="..\src\LoopFit.Infrastructure\LoopFit.Infrastructure.csproj"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
+34
-10
@@ -1,36 +1,60 @@
|
|||||||
|
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||||
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
using System.Text;
|
||||||
|
using LoopFit.Application;
|
||||||
|
using LoopFit.Infrastructure;
|
||||||
using LoopFit.Web.Components;
|
using LoopFit.Web.Components;
|
||||||
using LoopFit.Shared.Services;
|
|
||||||
using LoopFit.Web.Services;
|
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
// Add services to the container.
|
builder.Services.AddInfrastructure(builder.Configuration);
|
||||||
|
builder.Services.AddApplication();
|
||||||
|
|
||||||
|
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
||||||
|
.AddJwtBearer(options =>
|
||||||
|
{
|
||||||
|
var jwtSettings = builder.Configuration.GetSection("Jwt");
|
||||||
|
options.TokenValidationParameters = new TokenValidationParameters
|
||||||
|
{
|
||||||
|
ValidateIssuer = true,
|
||||||
|
ValidateAudience = true,
|
||||||
|
ValidateLifetime = true,
|
||||||
|
ValidateIssuerSigningKey = true,
|
||||||
|
ValidIssuer = jwtSettings["Issuer"],
|
||||||
|
ValidAudience = jwtSettings["Audience"],
|
||||||
|
IssuerSigningKey = new SymmetricSecurityKey(
|
||||||
|
Encoding.UTF8.GetBytes(jwtSettings["Key"]!))
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
builder.Services.AddAuthorization();
|
||||||
|
|
||||||
builder.Services.AddRazorComponents()
|
builder.Services.AddRazorComponents()
|
||||||
.AddInteractiveServerComponents();
|
.AddInteractiveServerComponents();
|
||||||
|
|
||||||
// Add device-specific services used by the LoopFit.Shared project
|
builder.Services.AddControllers();
|
||||||
builder.Services.AddSingleton<IFormFactor, FormFactor>();
|
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
// Configure the HTTP request pipeline.
|
|
||||||
if (!app.Environment.IsDevelopment())
|
if (!app.Environment.IsDevelopment())
|
||||||
{
|
{
|
||||||
app.UseExceptionHandler("/Error", createScopeForErrors: true);
|
app.UseExceptionHandler("/Error", createScopeForErrors: true);
|
||||||
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
|
|
||||||
app.UseHsts();
|
app.UseHsts();
|
||||||
}
|
}
|
||||||
|
|
||||||
app.UseStatusCodePagesWithReExecute("/not-found", createScopeForStatusCodePages: true);
|
app.UseStatusCodePagesWithReExecute("/not-found", createScopeForStatusCodePages: true);
|
||||||
app.UseHttpsRedirection();
|
app.UseHttpsRedirection();
|
||||||
|
|
||||||
|
app.UseStaticFiles();
|
||||||
app.UseAntiforgery();
|
app.UseAntiforgery();
|
||||||
|
|
||||||
app.MapStaticAssets();
|
app.UseAuthentication();
|
||||||
|
app.UseAuthorization();
|
||||||
|
|
||||||
|
app.MapControllers();
|
||||||
|
|
||||||
app.MapRazorComponents<App>()
|
app.MapRazorComponents<App>()
|
||||||
.AddInteractiveServerRenderMode()
|
.AddInteractiveServerRenderMode()
|
||||||
.AddAdditionalAssemblies(
|
.AddAdditionalAssemblies(typeof(App).Assembly);
|
||||||
typeof(LoopFit.Shared._Imports).Assembly);
|
|
||||||
|
|
||||||
app.Run();
|
app.Run();
|
||||||
@@ -5,5 +5,14 @@
|
|||||||
"Microsoft.AspNetCore": "Warning"
|
"Microsoft.AspNetCore": "Warning"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"AllowedHosts": "*"
|
"AllowedHosts": "*",
|
||||||
|
"ConnectionStrings": {
|
||||||
|
"DefaultConnection": "Host=localhost;Database=loopfit;Username=postgres;Password=postgres"
|
||||||
|
},
|
||||||
|
"Jwt": {
|
||||||
|
"Key": "LoopFitSuperSecretKeyThatIsAtLeast32CharactersLong!",
|
||||||
|
"Issuer": "LoopFit",
|
||||||
|
"Audience": "LoopFitApp",
|
||||||
|
"ExpirationMinutes": 60
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 228 B |
@@ -0,0 +1 @@
|
|||||||
|
404: Not Found
|
||||||
+72
-6
@@ -2,27 +2,93 @@
|
|||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LoopFit", "LoopFit\LoopFit.csproj", "{230BF2FE-83B3-443B-B561-E33ED9307B71}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LoopFit", "LoopFit\LoopFit.csproj", "{230BF2FE-83B3-443B-B561-E33ED9307B71}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LoopFit.Shared", "LoopFit.Shared\LoopFit.Shared.csproj", "{784878BE-3611-4C20-B91F-9724F150F78E}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LoopFit.Web", "LoopFit.Web\LoopFit.Web.csproj", "{0011D403-B7FB-4ED1-9885-B6A594872B91}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LoopFit.Web", "LoopFit.Web\LoopFit.Web.csproj", "{0011D403-B7FB-4ED1-9885-B6A594872B91}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LoopFit.Domain", "src\LoopFit.Domain\LoopFit.Domain.csproj", "{A7CC92FF-09E8-4331-94B2-24FC1B0D17A3}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LoopFit.Application", "src\LoopFit.Application\LoopFit.Application.csproj", "{CF4C1F14-B82D-4023-BED6-F750F97CE9C8}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LoopFit.Infrastructure", "src\LoopFit.Infrastructure\LoopFit.Infrastructure.csproj", "{C91B39DF-E06E-4EAF-AD2B-32641A15E558}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Debug|x64 = Debug|x64
|
||||||
|
Debug|x86 = Debug|x86
|
||||||
Release|Any CPU = Release|Any CPU
|
Release|Any CPU = Release|Any CPU
|
||||||
|
Release|x64 = Release|x64
|
||||||
|
Release|x86 = Release|x86
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
{230BF2FE-83B3-443B-B561-E33ED9307B71}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{230BF2FE-83B3-443B-B561-E33ED9307B71}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{230BF2FE-83B3-443B-B561-E33ED9307B71}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{230BF2FE-83B3-443B-B561-E33ED9307B71}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{230BF2FE-83B3-443B-B561-E33ED9307B71}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{230BF2FE-83B3-443B-B561-E33ED9307B71}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{230BF2FE-83B3-443B-B561-E33ED9307B71}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{230BF2FE-83B3-443B-B561-E33ED9307B71}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{230BF2FE-83B3-443B-B561-E33ED9307B71}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{230BF2FE-83B3-443B-B561-E33ED9307B71}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{230BF2FE-83B3-443B-B561-E33ED9307B71}.Release|Any CPU.Build.0 = Release|Any CPU
|
{230BF2FE-83B3-443B-B561-E33ED9307B71}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{784878BE-3611-4C20-B91F-9724F150F78E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{230BF2FE-83B3-443B-B561-E33ED9307B71}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
{784878BE-3611-4C20-B91F-9724F150F78E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{230BF2FE-83B3-443B-B561-E33ED9307B71}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{784878BE-3611-4C20-B91F-9724F150F78E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{230BF2FE-83B3-443B-B561-E33ED9307B71}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{784878BE-3611-4C20-B91F-9724F150F78E}.Release|Any CPU.Build.0 = Release|Any CPU
|
{230BF2FE-83B3-443B-B561-E33ED9307B71}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{0011D403-B7FB-4ED1-9885-B6A594872B91}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{0011D403-B7FB-4ED1-9885-B6A594872B91}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{0011D403-B7FB-4ED1-9885-B6A594872B91}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{0011D403-B7FB-4ED1-9885-B6A594872B91}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{0011D403-B7FB-4ED1-9885-B6A594872B91}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{0011D403-B7FB-4ED1-9885-B6A594872B91}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{0011D403-B7FB-4ED1-9885-B6A594872B91}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{0011D403-B7FB-4ED1-9885-B6A594872B91}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{0011D403-B7FB-4ED1-9885-B6A594872B91}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{0011D403-B7FB-4ED1-9885-B6A594872B91}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{0011D403-B7FB-4ED1-9885-B6A594872B91}.Release|Any CPU.Build.0 = Release|Any CPU
|
{0011D403-B7FB-4ED1-9885-B6A594872B91}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{0011D403-B7FB-4ED1-9885-B6A594872B91}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{0011D403-B7FB-4ED1-9885-B6A594872B91}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{0011D403-B7FB-4ED1-9885-B6A594872B91}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{0011D403-B7FB-4ED1-9885-B6A594872B91}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{A7CC92FF-09E8-4331-94B2-24FC1B0D17A3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{A7CC92FF-09E8-4331-94B2-24FC1B0D17A3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{A7CC92FF-09E8-4331-94B2-24FC1B0D17A3}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{A7CC92FF-09E8-4331-94B2-24FC1B0D17A3}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{A7CC92FF-09E8-4331-94B2-24FC1B0D17A3}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{A7CC92FF-09E8-4331-94B2-24FC1B0D17A3}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{A7CC92FF-09E8-4331-94B2-24FC1B0D17A3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{A7CC92FF-09E8-4331-94B2-24FC1B0D17A3}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{A7CC92FF-09E8-4331-94B2-24FC1B0D17A3}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{A7CC92FF-09E8-4331-94B2-24FC1B0D17A3}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{A7CC92FF-09E8-4331-94B2-24FC1B0D17A3}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{A7CC92FF-09E8-4331-94B2-24FC1B0D17A3}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{CF4C1F14-B82D-4023-BED6-F750F97CE9C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{CF4C1F14-B82D-4023-BED6-F750F97CE9C8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{CF4C1F14-B82D-4023-BED6-F750F97CE9C8}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{CF4C1F14-B82D-4023-BED6-F750F97CE9C8}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{CF4C1F14-B82D-4023-BED6-F750F97CE9C8}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{CF4C1F14-B82D-4023-BED6-F750F97CE9C8}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{CF4C1F14-B82D-4023-BED6-F750F97CE9C8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{CF4C1F14-B82D-4023-BED6-F750F97CE9C8}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{CF4C1F14-B82D-4023-BED6-F750F97CE9C8}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{CF4C1F14-B82D-4023-BED6-F750F97CE9C8}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{CF4C1F14-B82D-4023-BED6-F750F97CE9C8}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{CF4C1F14-B82D-4023-BED6-F750F97CE9C8}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{C91B39DF-E06E-4EAF-AD2B-32641A15E558}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{C91B39DF-E06E-4EAF-AD2B-32641A15E558}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{C91B39DF-E06E-4EAF-AD2B-32641A15E558}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{C91B39DF-E06E-4EAF-AD2B-32641A15E558}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{C91B39DF-E06E-4EAF-AD2B-32641A15E558}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{C91B39DF-E06E-4EAF-AD2B-32641A15E558}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{C91B39DF-E06E-4EAF-AD2B-32641A15E558}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{C91B39DF-E06E-4EAF-AD2B-32641A15E558}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{C91B39DF-E06E-4EAF-AD2B-32641A15E558}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{C91B39DF-E06E-4EAF-AD2B-32641A15E558}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{C91B39DF-E06E-4EAF-AD2B-32641A15E558}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{C91B39DF-E06E-4EAF-AD2B-32641A15E558}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(NestedProjects) = preSolution
|
||||||
|
{A7CC92FF-09E8-4331-94B2-24FC1B0D17A3} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
|
||||||
|
{CF4C1F14-B82D-4023-BED6-F750F97CE9C8} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
|
||||||
|
{C91B39DF-E06E-4EAF-AD2B-32641A15E558} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
namespace LoopFit;
|
namespace LoopFit;
|
||||||
|
|
||||||
public partial class App : Application
|
public partial class App : Microsoft.Maui.Controls.Application
|
||||||
{
|
{
|
||||||
public App()
|
public App()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
@inherits LayoutComponentBase
|
||||||
|
|
||||||
|
<div class="page">
|
||||||
|
<div class="sidebar">
|
||||||
|
<NavMenu/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<div class="top-row px-4">
|
||||||
|
<a href="" @onclick="Logout" @onclick:preventDefault>Abmelden</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<article class="content px-4">
|
||||||
|
@Body
|
||||||
|
</article>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="blazor-error-ui" data-nosnippet>
|
||||||
|
An unhandled error has occurred.
|
||||||
|
<a href="." class="reload">Reload</a>
|
||||||
|
<span class="dismiss">🗙</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Inject] private Services.AuthService AuthService { get; set; } = default!;
|
||||||
|
[Inject] private NavigationManager Navigation { get; set; } = default!;
|
||||||
|
|
||||||
|
private async Task Logout()
|
||||||
|
{
|
||||||
|
await AuthService.LogoutAsync();
|
||||||
|
Navigation.NavigateTo("/login", forceLoad: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
.page {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-row {
|
||||||
|
background-color: #f7f7f7;
|
||||||
|
border-bottom: 1px solid #d6d5d5;
|
||||||
|
justify-content: flex-end;
|
||||||
|
height: 3.5rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-row ::deep a, .top-row ::deep .btn-link {
|
||||||
|
white-space: nowrap;
|
||||||
|
margin-left: 1.5rem;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-row ::deep a:hover, .top-row ::deep .btn-link:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 640.98px) {
|
||||||
|
.top-row {
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-row ::deep a, .top-row ::deep .btn-link {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 641px) {
|
||||||
|
.page {
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
width: 250px;
|
||||||
|
height: 100vh;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-row {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-row, article {
|
||||||
|
padding-left: 2rem !important;
|
||||||
|
padding-right: 1.5rem !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#blazor-error-ui {
|
||||||
|
color-scheme: light only;
|
||||||
|
background: lightyellow;
|
||||||
|
bottom: 0;
|
||||||
|
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: none;
|
||||||
|
left: 0;
|
||||||
|
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
|
||||||
|
position: fixed;
|
||||||
|
width: 100%;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
#blazor-error-ui .dismiss {
|
||||||
|
cursor: pointer;
|
||||||
|
position: absolute;
|
||||||
|
right: 0.75rem;
|
||||||
|
top: 0.5rem;
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<div class="top-row ps-3 navbar navbar-dark">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<a class="navbar-brand" href="">LoopFit</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input type="checkbox" title="Navigation menu" class="navbar-toggler"/>
|
||||||
|
|
||||||
|
<div class="nav-scrollable" onclick="document.querySelector('.navbar-toggler').click()">
|
||||||
|
<nav class="nav flex-column">
|
||||||
|
<div class="nav-item px-3">
|
||||||
|
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
|
||||||
|
<span class="bi bi-house-door-fill-nav-menu" aria-hidden="true"></span> Home
|
||||||
|
</NavLink>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
.navbar-toggler {
|
||||||
|
appearance: none;
|
||||||
|
cursor: pointer;
|
||||||
|
width: 3.5rem;
|
||||||
|
height: 2.5rem;
|
||||||
|
color: white;
|
||||||
|
position: absolute;
|
||||||
|
top: 0.5rem;
|
||||||
|
right: 1rem;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e") no-repeat center/1.75rem rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-toggler:checked {
|
||||||
|
background-color: rgba(255, 255, 255, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-row {
|
||||||
|
min-height: 3.5rem;
|
||||||
|
background-color: rgba(0,0,0,0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-brand {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bi {
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
width: 1.25rem;
|
||||||
|
height: 1.25rem;
|
||||||
|
margin-right: 0.75rem;
|
||||||
|
top: -1px;
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bi-house-door-fill-nav-menu {
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-house-door-fill' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z'/%3E%3C/svg%3E");
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
padding-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item:first-of-type {
|
||||||
|
padding-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item:last-of-type {
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item ::deep .nav-link {
|
||||||
|
color: #d7d7d7;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
height: 3rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
line-height: 3rem;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item ::deep a.active {
|
||||||
|
background-color: rgba(255,255,255,0.37);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-item ::deep .nav-link:hover {
|
||||||
|
background-color: rgba(255,255,255,0.1);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-scrollable {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-toggler:checked ~ .nav-scrollable {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 641px) {
|
||||||
|
.navbar-toggler {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-scrollable {
|
||||||
|
display: block;
|
||||||
|
height: calc(100vh - 3.5rem);
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
@page "/"
|
||||||
|
@attribute [Microsoft.AspNetCore.Authorization.Authorize]
|
||||||
|
|
||||||
|
<PageTitle>Home</PageTitle>
|
||||||
|
|
||||||
|
<h1>LoopFit</h1>
|
||||||
|
|
||||||
|
<p>Willkommen bei LoopFit - dein Fitness-Tracker.</p>
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
@page "/login"
|
||||||
|
|
||||||
|
<PageTitle>Login</PageTitle>
|
||||||
|
|
||||||
|
<div class="container mt-5">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-md-6 col-lg-4">
|
||||||
|
<div class="card shadow">
|
||||||
|
<div class="card-body p-4">
|
||||||
|
<h2 class="card-title text-center mb-4">Anmelden</h2>
|
||||||
|
|
||||||
|
@if (!string.IsNullOrEmpty(_errorMessage))
|
||||||
|
{
|
||||||
|
<div class="alert alert-danger">@_errorMessage</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<EditForm Model="_loginRequest" OnValidSubmit="HandleLogin" FormName="loginForm">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Email</label>
|
||||||
|
<InputText class="form-control" @bind-Value="_loginRequest.Email" type="email" placeholder="name@beispiel.de"/>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Passwort</label>
|
||||||
|
<InputText class="form-control" @bind-Value="_loginRequest.Password" type="password" placeholder="Passwort"/>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary w-100" disabled="@_isLoading">
|
||||||
|
@if (_isLoading)
|
||||||
|
{
|
||||||
|
<span class="spinner-border spinner-border-sm me-2"></span>
|
||||||
|
}
|
||||||
|
Anmelden
|
||||||
|
</button>
|
||||||
|
</EditForm>
|
||||||
|
|
||||||
|
<div class="text-center mt-3">
|
||||||
|
<a href="/register">Noch kein Konto? Registrieren</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Inject] private Services.AuthService AuthService { get; set; } = default!;
|
||||||
|
[Inject] private NavigationManager Navigation { get; set; } = default!;
|
||||||
|
|
||||||
|
private LoopFit.Application.DTOs.Auth.LoginRequest _loginRequest = new();
|
||||||
|
private bool _isLoading;
|
||||||
|
private string? _errorMessage;
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
if (await AuthService.IsAuthenticatedAsync())
|
||||||
|
{
|
||||||
|
Navigation.NavigateTo("/", forceLoad: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task HandleLogin()
|
||||||
|
{
|
||||||
|
_isLoading = true;
|
||||||
|
_errorMessage = null;
|
||||||
|
|
||||||
|
var result = await AuthService.LoginAsync(_loginRequest);
|
||||||
|
|
||||||
|
if (result is not null)
|
||||||
|
{
|
||||||
|
Navigation.NavigateTo("/", forceLoad: true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_errorMessage = "Email oder Passwort falsch.";
|
||||||
|
}
|
||||||
|
|
||||||
|
_isLoading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
@page "/not-found"
|
||||||
|
@layout Layout.MainLayout
|
||||||
|
|
||||||
|
<h3>Not Found</h3>
|
||||||
|
<p>Sorry, the content you are looking for does not exist.</p>
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
@page "/register"
|
||||||
|
|
||||||
|
<PageTitle>Registrieren</PageTitle>
|
||||||
|
|
||||||
|
<div class="container mt-5">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-md-6 col-lg-4">
|
||||||
|
<div class="card shadow">
|
||||||
|
<div class="card-body p-4">
|
||||||
|
<h2 class="card-title text-center mb-4">Registrieren</h2>
|
||||||
|
|
||||||
|
@if (!string.IsNullOrEmpty(_errorMessage))
|
||||||
|
{
|
||||||
|
<div class="alert alert-danger">@_errorMessage</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<EditForm Model="_registerRequest" OnValidSubmit="HandleRegister" FormName="registerForm">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Vorname</label>
|
||||||
|
<InputText class="form-control" @bind-Value="_registerRequest.FirstName" placeholder="Vorname"/>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Nachname</label>
|
||||||
|
<InputText class="form-control" @bind-Value="_registerRequest.LastName" placeholder="Nachname"/>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Email</label>
|
||||||
|
<InputText class="form-control" @bind-Value="_registerRequest.Email" type="email" placeholder="name@beispiel.de"/>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Passwort</label>
|
||||||
|
<InputText class="form-control" @bind-Value="_registerRequest.Password" type="password" placeholder="Mind. 8 Zeichen"/>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary w-100" disabled="@_isLoading">
|
||||||
|
@if (_isLoading)
|
||||||
|
{
|
||||||
|
<span class="spinner-border spinner-border-sm me-2"></span>
|
||||||
|
}
|
||||||
|
Registrieren
|
||||||
|
</button>
|
||||||
|
</EditForm>
|
||||||
|
|
||||||
|
<div class="text-center mt-3">
|
||||||
|
<a href="/login">Bereits ein Konto? Anmelden</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Inject] private Services.AuthService AuthService { get; set; } = default!;
|
||||||
|
[Inject] private NavigationManager Navigation { get; set; } = default!;
|
||||||
|
|
||||||
|
private LoopFit.Application.DTOs.Auth.RegisterRequest _registerRequest = new();
|
||||||
|
private bool _isLoading;
|
||||||
|
private string? _errorMessage;
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
if (await AuthService.IsAuthenticatedAsync())
|
||||||
|
{
|
||||||
|
Navigation.NavigateTo("/", forceLoad: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task HandleRegister()
|
||||||
|
{
|
||||||
|
_isLoading = true;
|
||||||
|
_errorMessage = null;
|
||||||
|
|
||||||
|
var result = await AuthService.RegisterAsync(_registerRequest);
|
||||||
|
|
||||||
|
if (result is not null)
|
||||||
|
{
|
||||||
|
Navigation.NavigateTo("/", forceLoad: true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_errorMessage = "Registrierung fehlgeschlagen. Bitte Eingaben prüfen.";
|
||||||
|
}
|
||||||
|
|
||||||
|
_isLoading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<Router AppAssembly="typeof(App).Assembly" NotFoundPage="typeof(Pages.NotFound)">
|
||||||
|
<Found Context="routeData">
|
||||||
|
<RouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)"/>
|
||||||
|
<FocusOnNavigate RouteData="routeData" Selector="h1"/>
|
||||||
|
</Found>
|
||||||
|
</Router>
|
||||||
@@ -3,8 +3,11 @@
|
|||||||
@using Microsoft.AspNetCore.Components.Forms
|
@using Microsoft.AspNetCore.Components.Forms
|
||||||
@using Microsoft.AspNetCore.Components.Routing
|
@using Microsoft.AspNetCore.Components.Routing
|
||||||
@using Microsoft.AspNetCore.Components.Web
|
@using Microsoft.AspNetCore.Components.Web
|
||||||
|
@using static Microsoft.AspNetCore.Components.Web.RenderMode
|
||||||
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
||||||
@using Microsoft.JSInterop
|
@using Microsoft.JSInterop
|
||||||
@using LoopFit
|
@using LoopFit
|
||||||
@using LoopFit.Components
|
@using LoopFit.Components
|
||||||
@using LoopFit.Shared
|
@using LoopFit.Components.Layout
|
||||||
|
@using LoopFit.Application.DTOs.Auth
|
||||||
|
@using LoopFit.Application.Interfaces
|
||||||
|
|||||||
+5
-28
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>net10.0-android</TargetFrameworks>
|
<TargetFramework>net10.0-android</TargetFramework>
|
||||||
|
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<RootNamespace>LoopFit</RootNamespace>
|
<RootNamespace>LoopFit</RootNamespace>
|
||||||
@@ -10,57 +10,34 @@
|
|||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<EnableDefaultCssItems>false</EnableDefaultCssItems>
|
<EnableDefaultCssItems>false</EnableDefaultCssItems>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
|
|
||||||
<!-- Enable XAML source generation for faster build times and improved performance.
|
|
||||||
This generates C# code from XAML at compile time instead of runtime inflation.
|
|
||||||
To disable, remove this line.
|
|
||||||
For individual files, you can override by setting Inflator metadata:
|
|
||||||
<MauiXaml Update="MyPage.xaml" Inflator="Default" /> (reverts to defaults: Runtime for Debug, XamlC for Release)
|
|
||||||
<MauiXaml Update="MyPage.xaml" Inflator="Runtime" /> (force runtime inflation) -->
|
|
||||||
<MauiXamlInflator>SourceGen</MauiXamlInflator>
|
<MauiXamlInflator>SourceGen</MauiXamlInflator>
|
||||||
|
|
||||||
<!-- Display name -->
|
|
||||||
<ApplicationTitle>LoopFit</ApplicationTitle>
|
<ApplicationTitle>LoopFit</ApplicationTitle>
|
||||||
|
|
||||||
<!-- App Identifier -->
|
|
||||||
<ApplicationId>com.companyname.loopfit</ApplicationId>
|
<ApplicationId>com.companyname.loopfit</ApplicationId>
|
||||||
|
|
||||||
<!-- Versions -->
|
|
||||||
<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
|
<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
|
||||||
<ApplicationVersion>1</ApplicationVersion>
|
<ApplicationVersion>1</ApplicationVersion>
|
||||||
|
|
||||||
<!-- To develop, package, and publish an app to the Microsoft Store, see: https://aka.ms/MauiTemplateUnpackaged -->
|
|
||||||
<WindowsPackageType>None</WindowsPackageType>
|
<WindowsPackageType>None</WindowsPackageType>
|
||||||
|
|
||||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">24.0</SupportedOSPlatformVersion>
|
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">24.0</SupportedOSPlatformVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<!-- App Icon -->
|
|
||||||
<MauiIcon Include="Resources\AppIcon\appicon.svg" ForegroundFile="Resources\AppIcon\appiconfg.svg" Color="#512BD4"/>
|
<MauiIcon Include="Resources\AppIcon\appicon.svg" ForegroundFile="Resources\AppIcon\appiconfg.svg" Color="#512BD4"/>
|
||||||
|
|
||||||
<!-- Splash Screen -->
|
|
||||||
<MauiSplashScreen Include="Resources\Splash\splash.svg" Color="#512BD4" BaseSize="128,128"/>
|
<MauiSplashScreen Include="Resources\Splash\splash.svg" Color="#512BD4" BaseSize="128,128"/>
|
||||||
|
|
||||||
<!-- Images -->
|
|
||||||
<MauiImage Include="Resources\Images\*"/>
|
<MauiImage Include="Resources\Images\*"/>
|
||||||
<MauiImage Update="Resources\Images\dotnet_bot.svg" BaseSize="168,208"/>
|
<MauiImage Update="Resources\Images\dotnet_bot.svg" BaseSize="168,208"/>
|
||||||
|
|
||||||
<!-- Custom Fonts -->
|
|
||||||
<MauiFont Include="Resources\Fonts\*"/>
|
<MauiFont Include="Resources\Fonts\*"/>
|
||||||
|
|
||||||
<!-- Raw Assets (also remove the "Resources\Raw" prefix) -->
|
|
||||||
<MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)"/>
|
<MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.Maui.Controls" Version="$(MauiVersion)"/>
|
<PackageReference Include="Microsoft.Maui.Controls" Version="10.0.20"/>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebView.Maui" Version="$(MauiVersion)"/>
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebView.Maui" Version="10.0.20"/>
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.0"/>
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="10.0.0"/>
|
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="10.0.0"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\LoopFit.Shared\LoopFit.Shared.csproj"/>
|
<ProjectReference Include="..\src\LoopFit.Application\LoopFit.Application.csproj"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -2,13 +2,13 @@
|
|||||||
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||||
xmlns:local="clr-namespace:LoopFit"
|
xmlns:local="clr-namespace:LoopFit"
|
||||||
xmlns:shared="clr-namespace:LoopFit.Shared;assembly=LoopFit.Shared"
|
xmlns:components="clr-namespace:LoopFit.Components"
|
||||||
x:Class="LoopFit.MainPage"
|
x:Class="LoopFit.MainPage"
|
||||||
BackgroundColor="{DynamicResource PageBackgroundColor}">
|
BackgroundColor="{DynamicResource PageBackgroundColor}">
|
||||||
|
|
||||||
<BlazorWebView x:Name="blazorWebView" HostPage="wwwroot/index.html">
|
<BlazorWebView x:Name="blazorWebView" HostPage="wwwroot/index.html">
|
||||||
<BlazorWebView.RootComponents>
|
<BlazorWebView.RootComponents>
|
||||||
<RootComponent Selector="#app" ComponentType="{x:Type shared:Routes}" />
|
<RootComponent Selector="#app" ComponentType="{x:Type components:Routes}" />
|
||||||
</BlazorWebView.RootComponents>
|
</BlazorWebView.RootComponents>
|
||||||
</BlazorWebView>
|
</BlazorWebView>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using LoopFit.Shared.Services;
|
using LoopFit.Application.Interfaces;
|
||||||
using LoopFit.Services;
|
using LoopFit.Services;
|
||||||
|
|
||||||
namespace LoopFit;
|
namespace LoopFit;
|
||||||
@@ -13,10 +13,17 @@ public static class MauiProgram
|
|||||||
.UseMauiApp<App>()
|
.UseMauiApp<App>()
|
||||||
.ConfigureFonts(fonts => { fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"); });
|
.ConfigureFonts(fonts => { fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"); });
|
||||||
|
|
||||||
// Add device-specific services used by the LoopFit.Shared project
|
builder.Services.AddSingleton<TokenStorageService>();
|
||||||
|
builder.Services.AddScoped<AuthService>();
|
||||||
builder.Services.AddSingleton<IFormFactor, FormFactor>();
|
builder.Services.AddSingleton<IFormFactor, FormFactor>();
|
||||||
|
|
||||||
|
builder.Services.AddHttpClient<AuthService>(client =>
|
||||||
|
{
|
||||||
|
client.BaseAddress = new Uri("http://10.0.2.2:5000");
|
||||||
|
});
|
||||||
|
|
||||||
builder.Services.AddMauiBlazorWebView();
|
builder.Services.AddMauiBlazorWebView();
|
||||||
|
builder.Services.AddAuthorizationCore();
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
builder.Services.AddBlazorWebViewDeveloperTools();
|
builder.Services.AddBlazorWebViewDeveloperTools();
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
using System.Net.Http.Json;
|
||||||
|
using LoopFit.Application.DTOs.Auth;
|
||||||
|
using LoopFit.Application.Interfaces;
|
||||||
|
|
||||||
|
namespace LoopFit.Services;
|
||||||
|
|
||||||
|
public class AuthService : IAuthService
|
||||||
|
{
|
||||||
|
private readonly HttpClient _httpClient;
|
||||||
|
private readonly TokenStorageService _tokenStorage;
|
||||||
|
|
||||||
|
public AuthService(HttpClient httpClient, TokenStorageService tokenStorage)
|
||||||
|
{
|
||||||
|
_httpClient = httpClient;
|
||||||
|
_tokenStorage = tokenStorage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<AuthResponse?> RegisterAsync(RegisterRequest request)
|
||||||
|
{
|
||||||
|
var response = await _httpClient.PostAsJsonAsync("api/auth/register", request);
|
||||||
|
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var result = await response.Content.ReadFromJsonAsync<AuthResponse>();
|
||||||
|
|
||||||
|
if (result is not null)
|
||||||
|
await _tokenStorage.SaveTokenAsync(result.Token);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<AuthResponse?> LoginAsync(LoginRequest request)
|
||||||
|
{
|
||||||
|
var response = await _httpClient.PostAsJsonAsync("api/auth/login", request);
|
||||||
|
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var result = await response.Content.ReadFromJsonAsync<AuthResponse>();
|
||||||
|
|
||||||
|
if (result is not null)
|
||||||
|
await _tokenStorage.SaveTokenAsync(result.Token);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<AuthResponse?> GetCurrentUserAsync(Guid userId)
|
||||||
|
{
|
||||||
|
var token = await _tokenStorage.GetTokenAsync();
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(token))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
_httpClient.DefaultRequestHeaders.Authorization =
|
||||||
|
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
|
||||||
|
|
||||||
|
var response = await _httpClient.GetAsync("api/auth/me");
|
||||||
|
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return await response.Content.ReadFromJsonAsync<AuthResponse>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> IsAuthenticatedAsync()
|
||||||
|
{
|
||||||
|
var token = await _tokenStorage.GetTokenAsync();
|
||||||
|
return !string.IsNullOrEmpty(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task LogoutAsync()
|
||||||
|
{
|
||||||
|
await _tokenStorage.RemoveTokenAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
using LoopFit.Shared.Services;
|
using LoopFit.Application.Interfaces;
|
||||||
|
|
||||||
namespace LoopFit.Services;
|
namespace LoopFit.Services;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
namespace LoopFit.Services;
|
||||||
|
|
||||||
|
public class TokenStorageService
|
||||||
|
{
|
||||||
|
private const string TokenKey = "loopfit_jwt_token";
|
||||||
|
|
||||||
|
public async Task SaveTokenAsync(string token)
|
||||||
|
{
|
||||||
|
await SecureStorage.Default.SetAsync(TokenKey, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string?> GetTokenAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return await SecureStorage.Default.GetAsync(TokenKey);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task RemoveTokenAsync()
|
||||||
|
{
|
||||||
|
SecureStorage.Default.Remove(TokenKey);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,41 @@
|
|||||||
|
html, body {
|
||||||
|
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
a, .btn-link {
|
||||||
|
color: #006bb7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #1b6ec2;
|
||||||
|
border-color: #1861ac;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
|
||||||
|
box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
padding-top: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.valid.modified:not([type=checkbox]) {
|
||||||
|
outline: 1px solid #26b050;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invalid {
|
||||||
|
outline: 1px solid #e50000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.validation-message {
|
||||||
|
color: #e50000;
|
||||||
|
}
|
||||||
|
|
||||||
.status-bar-safe-area {
|
.status-bar-safe-area {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,7 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
|
||||||
<title>LoopFit</title>
|
<title>LoopFit</title>
|
||||||
<base href="/" />
|
<base href="/" />
|
||||||
<link rel="stylesheet" href="_content/LoopFit.Shared/lib/bootstrap/dist/css/bootstrap.min.css" />
|
<link rel="stylesheet" href="lib/bootstrap/dist/css/bootstrap.min.css" />
|
||||||
<link rel="stylesheet" href="_content/LoopFit.Shared/app.css" />
|
|
||||||
<link rel="stylesheet" href="app.css" />
|
<link rel="stylesheet" href="app.css" />
|
||||||
<link rel="stylesheet" href="LoopFit.styles.css" />
|
<link rel="stylesheet" href="LoopFit.styles.css" />
|
||||||
<link rel="icon" href="data:,">
|
<link rel="icon" href="data:,">
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,8 @@
|
|||||||
|
namespace LoopFit.Application.DTOs.Auth;
|
||||||
|
|
||||||
|
public class AuthResponse
|
||||||
|
{
|
||||||
|
public string Token { get; set; } = string.Empty;
|
||||||
|
public DateTime Expiration { get; set; }
|
||||||
|
public string UserName { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace LoopFit.Application.DTOs.Auth;
|
||||||
|
|
||||||
|
public class LoginRequest
|
||||||
|
{
|
||||||
|
public string Email { get; set; } = string.Empty;
|
||||||
|
public string Password { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
namespace LoopFit.Application.DTOs.Auth;
|
||||||
|
|
||||||
|
public class RegisterRequest
|
||||||
|
{
|
||||||
|
public string Email { get; set; } = string.Empty;
|
||||||
|
public string Password { get; set; } = string.Empty;
|
||||||
|
public string FirstName { get; set; } = string.Empty;
|
||||||
|
public string LastName { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using LoopFit.Application.Interfaces;
|
||||||
|
|
||||||
|
namespace LoopFit.Application;
|
||||||
|
|
||||||
|
public static class DependencyInjection
|
||||||
|
{
|
||||||
|
public static IServiceCollection AddApplication(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddScoped<IAuthService>(sp =>
|
||||||
|
throw new InvalidOperationException("IAuthService muss in Infrastructure registriert werden."));
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
using LoopFit.Application.DTOs.Auth;
|
||||||
|
|
||||||
|
namespace LoopFit.Application.Interfaces;
|
||||||
|
|
||||||
|
public interface IAuthService
|
||||||
|
{
|
||||||
|
Task<AuthResponse?> RegisterAsync(RegisterRequest request);
|
||||||
|
Task<AuthResponse?> LoginAsync(LoginRequest request);
|
||||||
|
Task<AuthResponse?> GetCurrentUserAsync(Guid userId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace LoopFit.Application.Interfaces;
|
||||||
|
|
||||||
|
public interface IFormFactor
|
||||||
|
{
|
||||||
|
string GetFormFactor();
|
||||||
|
string GetPlatform();
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.11"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\LoopFit.Domain\LoopFit.Domain.csproj"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
namespace LoopFit.Domain.Entities;
|
||||||
|
|
||||||
|
public class ApplicationUser
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
public string Email { get; set; } = string.Empty;
|
||||||
|
public string FirstName { get; set; } = string.Empty;
|
||||||
|
public string LastName { get; set; } = string.Empty;
|
||||||
|
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
namespace LoopFit.Domain.Entities;
|
||||||
|
|
||||||
|
public class CalorieEntry
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
public Guid UserId { get; set; }
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
public int Calories { get; set; }
|
||||||
|
public DateTime Date { get; set; } = DateTime.UtcNow;
|
||||||
|
public string? Notes { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
namespace LoopFit.Domain.Entities;
|
||||||
|
|
||||||
|
public class VitalData
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
public Guid UserId { get; set; }
|
||||||
|
public int? HeartRate { get; set; }
|
||||||
|
public int? Steps { get; set; }
|
||||||
|
public double? SleepHours { get; set; }
|
||||||
|
public int? CaloriesBurned { get; set; }
|
||||||
|
public DateTime Date { get; set; } = DateTime.UtcNow;
|
||||||
|
public string? Source { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
using LoopFit.Domain.Enums;
|
||||||
|
|
||||||
|
namespace LoopFit.Domain.Entities;
|
||||||
|
|
||||||
|
public class Workout
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
public Guid UserId { get; set; }
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
public WorkoutType Type { get; set; }
|
||||||
|
public int DurationMinutes { get; set; }
|
||||||
|
public int CaloriesBurned { get; set; }
|
||||||
|
public DateTime Date { get; set; } = DateTime.UtcNow;
|
||||||
|
public string? Notes { get; set; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
namespace LoopFit.Domain.Enums;
|
||||||
|
|
||||||
|
public enum WorkoutType
|
||||||
|
{
|
||||||
|
Strength,
|
||||||
|
Cardio,
|
||||||
|
Endurance,
|
||||||
|
Flexibility,
|
||||||
|
Other
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace LoopFit.Domain.Interfaces;
|
||||||
|
|
||||||
|
public interface IJwtTokenGenerator
|
||||||
|
{
|
||||||
|
string GenerateToken(Guid userId, string email);
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
using LoopFit.Domain.Entities;
|
||||||
|
|
||||||
|
namespace LoopFit.Domain.Interfaces;
|
||||||
|
|
||||||
|
public interface IUserRepository
|
||||||
|
{
|
||||||
|
Task<ApplicationUser?> GetByIdAsync(Guid id);
|
||||||
|
Task<ApplicationUser?> GetByEmailAsync(string email);
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
using LoopFit.Domain.Entities;
|
||||||
|
|
||||||
|
namespace LoopFit.Domain.Interfaces;
|
||||||
|
|
||||||
|
public interface IWorkoutRepository
|
||||||
|
{
|
||||||
|
Task<Workout?> GetByIdAsync(Guid id);
|
||||||
|
Task<IEnumerable<Workout>> GetByUserIdAsync(Guid userId);
|
||||||
|
Task AddAsync(Workout workout);
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using LoopFit.Domain.Entities;
|
||||||
|
|
||||||
|
namespace LoopFit.Infrastructure.Data;
|
||||||
|
|
||||||
|
public class AppDbContext : IdentityDbContext<IdentityUser>
|
||||||
|
{
|
||||||
|
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public DbSet<ApplicationUser> ApplicationUsers => Set<ApplicationUser>();
|
||||||
|
public DbSet<Workout> Workouts => Set<Workout>();
|
||||||
|
public DbSet<CalorieEntry> CalorieEntries => Set<CalorieEntry>();
|
||||||
|
public DbSet<VitalData> VitalData => Set<VitalData>();
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using LoopFit.Application.Interfaces;
|
||||||
|
using LoopFit.Domain.Interfaces;
|
||||||
|
using LoopFit.Infrastructure.Data;
|
||||||
|
using LoopFit.Infrastructure.Identity;
|
||||||
|
using LoopFit.Infrastructure.Repositories;
|
||||||
|
using LoopFit.Infrastructure.Services;
|
||||||
|
|
||||||
|
namespace LoopFit.Infrastructure;
|
||||||
|
|
||||||
|
public static class DependencyInjection
|
||||||
|
{
|
||||||
|
public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration configuration)
|
||||||
|
{
|
||||||
|
services.AddDbContext<AppDbContext>(options =>
|
||||||
|
options.UseNpgsql(configuration.GetConnectionString("DefaultConnection")));
|
||||||
|
|
||||||
|
services.AddIdentity<IdentityUser, IdentityRole>(options =>
|
||||||
|
{
|
||||||
|
options.Password.RequireDigit = true;
|
||||||
|
options.Password.RequiredLength = 8;
|
||||||
|
options.Password.RequireNonAlphanumeric = false;
|
||||||
|
options.Password.RequireUppercase = true;
|
||||||
|
options.Password.RequireLowercase = true;
|
||||||
|
options.User.RequireUniqueEmail = true;
|
||||||
|
})
|
||||||
|
.AddEntityFrameworkStores<AppDbContext>()
|
||||||
|
.AddDefaultTokenProviders();
|
||||||
|
|
||||||
|
services.AddScoped<IJwtTokenGenerator, JwtTokenGenerator>();
|
||||||
|
services.AddScoped<IAuthService, AuthService>();
|
||||||
|
services.AddScoped<IFormFactor, FormFactor>();
|
||||||
|
services.AddScoped<IUserRepository, UserRepository>();
|
||||||
|
services.AddScoped<IWorkoutRepository, WorkoutRepository>();
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using LoopFit.Application.DTOs.Auth;
|
||||||
|
using LoopFit.Application.Interfaces;
|
||||||
|
using LoopFit.Domain.Entities;
|
||||||
|
using LoopFit.Domain.Interfaces;
|
||||||
|
using LoopFit.Infrastructure.Data;
|
||||||
|
|
||||||
|
namespace LoopFit.Infrastructure.Identity;
|
||||||
|
|
||||||
|
public class AuthService : IAuthService
|
||||||
|
{
|
||||||
|
private readonly UserManager<IdentityUser> _userManager;
|
||||||
|
private readonly IJwtTokenGenerator _jwtTokenGenerator;
|
||||||
|
private readonly AppDbContext _dbContext;
|
||||||
|
|
||||||
|
public AuthService(
|
||||||
|
UserManager<IdentityUser> userManager,
|
||||||
|
IJwtTokenGenerator jwtTokenGenerator,
|
||||||
|
AppDbContext dbContext)
|
||||||
|
{
|
||||||
|
_userManager = userManager;
|
||||||
|
_jwtTokenGenerator = jwtTokenGenerator;
|
||||||
|
_dbContext = dbContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<AuthResponse?> RegisterAsync(RegisterRequest request)
|
||||||
|
{
|
||||||
|
var identityUser = new IdentityUser
|
||||||
|
{
|
||||||
|
UserName = request.Email,
|
||||||
|
Email = request.Email
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = await _userManager.CreateAsync(identityUser, request.Password);
|
||||||
|
|
||||||
|
if (!result.Succeeded)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var appUser = new ApplicationUser
|
||||||
|
{
|
||||||
|
Id = Guid.Parse(identityUser.Id),
|
||||||
|
Email = request.Email,
|
||||||
|
FirstName = request.FirstName,
|
||||||
|
LastName = request.LastName
|
||||||
|
};
|
||||||
|
|
||||||
|
_dbContext.ApplicationUsers.Add(appUser);
|
||||||
|
await _dbContext.SaveChangesAsync();
|
||||||
|
|
||||||
|
var token = _jwtTokenGenerator.GenerateToken(appUser.Id, appUser.Email);
|
||||||
|
|
||||||
|
return new AuthResponse
|
||||||
|
{
|
||||||
|
Token = token,
|
||||||
|
Expiration = DateTime.UtcNow.AddMinutes(60),
|
||||||
|
UserName = $"{appUser.FirstName} {appUser.LastName}"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<AuthResponse?> LoginAsync(LoginRequest request)
|
||||||
|
{
|
||||||
|
var identityUser = await _userManager.FindByEmailAsync(request.Email);
|
||||||
|
|
||||||
|
if (identityUser is null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var isValidPassword = await _userManager.CheckPasswordAsync(identityUser, request.Password);
|
||||||
|
|
||||||
|
if (!isValidPassword)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var appUser = await _dbContext.ApplicationUsers
|
||||||
|
.FirstOrDefaultAsync(u => u.Id == Guid.Parse(identityUser.Id));
|
||||||
|
|
||||||
|
var userId = Guid.Parse(identityUser.Id);
|
||||||
|
var token = _jwtTokenGenerator.GenerateToken(userId, identityUser.Email!);
|
||||||
|
|
||||||
|
return new AuthResponse
|
||||||
|
{
|
||||||
|
Token = token,
|
||||||
|
Expiration = DateTime.UtcNow.AddMinutes(60),
|
||||||
|
UserName = appUser != null ? $"{appUser.FirstName} {appUser.LastName}" : identityUser.Email!
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<AuthResponse?> GetCurrentUserAsync(Guid userId)
|
||||||
|
{
|
||||||
|
var appUser = await _dbContext.ApplicationUsers
|
||||||
|
.FirstOrDefaultAsync(u => u.Id == userId);
|
||||||
|
|
||||||
|
if (appUser is null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var token = _jwtTokenGenerator.GenerateToken(appUser.Id, appUser.Email);
|
||||||
|
|
||||||
|
return new AuthResponse
|
||||||
|
{
|
||||||
|
Token = token,
|
||||||
|
Expiration = DateTime.UtcNow.AddMinutes(60),
|
||||||
|
UserName = $"{appUser.FirstName} {appUser.LastName}"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
using System.IdentityModel.Tokens.Jwt;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using System.Text;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
using LoopFit.Domain.Interfaces;
|
||||||
|
|
||||||
|
namespace LoopFit.Infrastructure.Identity;
|
||||||
|
|
||||||
|
public class JwtTokenGenerator : IJwtTokenGenerator
|
||||||
|
{
|
||||||
|
private readonly IConfiguration _configuration;
|
||||||
|
|
||||||
|
public JwtTokenGenerator(IConfiguration configuration)
|
||||||
|
{
|
||||||
|
_configuration = configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GenerateToken(Guid userId, string email)
|
||||||
|
{
|
||||||
|
var jwtSettings = _configuration.GetSection("Jwt");
|
||||||
|
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings["Key"]!));
|
||||||
|
var expiration = DateTime.UtcNow.AddMinutes(Convert.ToDouble(jwtSettings["ExpirationMinutes"]));
|
||||||
|
|
||||||
|
var claims = new List<Claim>
|
||||||
|
{
|
||||||
|
new(ClaimTypes.NameIdentifier, userId.ToString()),
|
||||||
|
new(ClaimTypes.Email, email),
|
||||||
|
new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
|
||||||
|
};
|
||||||
|
|
||||||
|
var token = new JwtSecurityToken(
|
||||||
|
issuer: jwtSettings["Issuer"],
|
||||||
|
audience: jwtSettings["Audience"],
|
||||||
|
claims: claims,
|
||||||
|
expires: expiration,
|
||||||
|
signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256));
|
||||||
|
|
||||||
|
return new JwtSecurityTokenHandler().WriteToken(token);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="10.0.11"/>
|
||||||
|
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.3"/>
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.11">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.11"/>
|
||||||
|
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.22.0"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\LoopFit.Application\LoopFit.Application.csproj"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using LoopFit.Domain.Entities;
|
||||||
|
using LoopFit.Domain.Interfaces;
|
||||||
|
using LoopFit.Infrastructure.Data;
|
||||||
|
|
||||||
|
namespace LoopFit.Infrastructure.Repositories;
|
||||||
|
|
||||||
|
public class UserRepository : IUserRepository
|
||||||
|
{
|
||||||
|
private readonly AppDbContext _context;
|
||||||
|
|
||||||
|
public UserRepository(AppDbContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ApplicationUser?> GetByIdAsync(Guid id)
|
||||||
|
{
|
||||||
|
return await _context.ApplicationUsers.FindAsync(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ApplicationUser?> GetByEmailAsync(string email)
|
||||||
|
{
|
||||||
|
return await _context.ApplicationUsers
|
||||||
|
.FirstOrDefaultAsync(u => u.Email == email);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using LoopFit.Domain.Entities;
|
||||||
|
using LoopFit.Domain.Interfaces;
|
||||||
|
using LoopFit.Infrastructure.Data;
|
||||||
|
|
||||||
|
namespace LoopFit.Infrastructure.Repositories;
|
||||||
|
|
||||||
|
public class WorkoutRepository : IWorkoutRepository
|
||||||
|
{
|
||||||
|
private readonly AppDbContext _context;
|
||||||
|
|
||||||
|
public WorkoutRepository(AppDbContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Workout?> GetByIdAsync(Guid id)
|
||||||
|
{
|
||||||
|
return await _context.Workouts.FindAsync(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<Workout>> GetByUserIdAsync(Guid userId)
|
||||||
|
{
|
||||||
|
return await _context.Workouts
|
||||||
|
.Where(w => w.UserId == userId)
|
||||||
|
.OrderByDescending(w => w.Date)
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task AddAsync(Workout workout)
|
||||||
|
{
|
||||||
|
_context.Workouts.Add(workout);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
using LoopFit.Shared.Services;
|
using LoopFit.Application.Interfaces;
|
||||||
|
|
||||||
namespace LoopFit.Web.Services;
|
namespace LoopFit.Infrastructure.Services;
|
||||||
|
|
||||||
public class FormFactor : IFormFactor
|
public class FormFactor : IFormFactor
|
||||||
{
|
{
|
||||||
Reference in New Issue
Block a user