Compare commits
33 Commits
main
...
identity/d
| Author | SHA1 | Date | |
|---|---|---|---|
| fddbc1e2ce | |||
| e21e6aa96d | |||
|
|
2ee1f68c3f | ||
|
|
5ff0df8685 | ||
| ce26a30693 | |||
| 91dd8d1603 | |||
| d1a72876d2 | |||
|
|
ba2a455a2b | ||
| 44b39b4393 | |||
| facc84157c | |||
| e4862f1878 | |||
| e0ecdad408 | |||
| 534ec5f36f | |||
| cec2b65c29 | |||
| 23505060c5 | |||
|
|
c63da03a3e | ||
|
|
94fcf83241 | ||
|
|
6cc45313f5 | ||
| aacd8b7d96 | |||
| 9128b199e9 | |||
| 3125d657dd | |||
| 35eee78efc | |||
| c2d84ad5e0 | |||
| 99af5fe13e | |||
| a118026b04 | |||
| 30eeaabc5a | |||
| 4ce2e05dc8 | |||
| 2b139495a3 | |||
| 7c227d45bb | |||
| e12795bacd | |||
| 75393940b4 | |||
| 74cfa283a1 | |||
| 9ccfd2e526 |
22
.gitignore
vendored
@@ -67,10 +67,30 @@ yarn-error.log*
|
|||||||
|
|
||||||
# Editor / IDE
|
# Editor / IDE
|
||||||
.vscode/
|
.vscode/
|
||||||
|
.vs/
|
||||||
*.suo
|
*.suo
|
||||||
*.user
|
*.user
|
||||||
*.userosscache
|
*.userosscache
|
||||||
*.sln.docstates
|
*.sln.docstates
|
||||||
|
|
||||||
# JetBrains IDEs
|
# JetBrains IDEs
|
||||||
.idea/
|
.idea/
|
||||||
|
|
||||||
|
# Claude Code
|
||||||
|
.claude
|
||||||
|
|
||||||
|
# Database (Sql Lite)
|
||||||
|
API/app.db-wal
|
||||||
|
API/app.db-shm
|
||||||
|
|
||||||
|
# Vite build output
|
||||||
|
GUI/dist/
|
||||||
|
wwwroot/
|
||||||
|
|
||||||
|
# SQLite Datenbank-Dateien
|
||||||
|
*.db
|
||||||
|
*.db-shm
|
||||||
|
*.db-wal
|
||||||
|
|
||||||
|
# AppSettings Development files
|
||||||
|
appsettings.Development.json
|
||||||
@@ -1,12 +1,49 @@
|
|||||||
{
|
{
|
||||||
"Version": 1,
|
"Version": 1,
|
||||||
"WorkspaceRootPath": "D:\\Programmieren\\CSharp\\JudoWeb\\",
|
"WorkspaceRootPath": "D:\\Programmieren\\CSharp\\JudoWeb\\",
|
||||||
"Documents": [],
|
"Documents": [
|
||||||
|
{
|
||||||
|
"AbsoluteMoniker": "D:0:0:{98166726-DC3A-4D5B-889A-8B4428E28656}|API\\API.csproj|d:\\programmieren\\csharp\\judoweb\\api\\program.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
|
||||||
|
"RelativeMoniker": "D:0:0:{98166726-DC3A-4D5B-889A-8B4428E28656}|API\\API.csproj|solutionrelative:api\\program.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
|
||||||
|
}
|
||||||
|
],
|
||||||
"DocumentGroupContainers": [
|
"DocumentGroupContainers": [
|
||||||
{
|
{
|
||||||
"Orientation": 0,
|
"Orientation": 0,
|
||||||
"VerticalTabListWidth": 256,
|
"VerticalTabListWidth": 256,
|
||||||
"DocumentGroups": []
|
"DocumentGroups": [
|
||||||
|
{
|
||||||
|
"DockedWidth": 200,
|
||||||
|
"SelectedChildIndex": 2,
|
||||||
|
"Children": [
|
||||||
|
{
|
||||||
|
"$type": "Bookmark",
|
||||||
|
"Name": "ST:0:0:{0174dea2-fdbe-4ef1-8f99-c0beae78880f}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$type": "Bookmark",
|
||||||
|
"Name": "ST:0:0:{aa2115a1-9712-457b-9047-dbb71ca2cdd2}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$type": "Document",
|
||||||
|
"DocumentIndex": 0,
|
||||||
|
"Title": "Program.cs",
|
||||||
|
"DocumentMoniker": "D:\\Programmieren\\CSharp\\JudoWeb\\API\\Program.cs",
|
||||||
|
"RelativeDocumentMoniker": "API\\Program.cs",
|
||||||
|
"ToolTip": "D:\\Programmieren\\CSharp\\JudoWeb\\API\\Program.cs",
|
||||||
|
"RelativeToolTip": "API\\Program.cs",
|
||||||
|
"ViewState": "AgIAAAAAAAAAAAAAAAAAABkAAAAAAAAAAAAAAA==",
|
||||||
|
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
|
||||||
|
"WhenOpened": "2025-10-05T16:26:39.39Z",
|
||||||
|
"EditorCaption": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$type": "Bookmark",
|
||||||
|
"Name": "ST:0:0:{cce594b6-0c39-4442-ba28-10c64ac7e89f}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -1,12 +1,49 @@
|
|||||||
{
|
{
|
||||||
"Version": 1,
|
"Version": 1,
|
||||||
"WorkspaceRootPath": "D:\\Programmieren\\CSharp\\JudoWeb\\",
|
"WorkspaceRootPath": "D:\\Programmieren\\CSharp\\JudoWeb\\",
|
||||||
"Documents": [],
|
"Documents": [
|
||||||
|
{
|
||||||
|
"AbsoluteMoniker": "D:0:0:{98166726-DC3A-4D5B-889A-8B4428E28656}|API\\API.csproj|d:\\programmieren\\csharp\\judoweb\\api\\program.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
|
||||||
|
"RelativeMoniker": "D:0:0:{98166726-DC3A-4D5B-889A-8B4428E28656}|API\\API.csproj|solutionrelative:api\\program.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
|
||||||
|
}
|
||||||
|
],
|
||||||
"DocumentGroupContainers": [
|
"DocumentGroupContainers": [
|
||||||
{
|
{
|
||||||
"Orientation": 0,
|
"Orientation": 0,
|
||||||
"VerticalTabListWidth": 256,
|
"VerticalTabListWidth": 256,
|
||||||
"DocumentGroups": []
|
"DocumentGroups": [
|
||||||
|
{
|
||||||
|
"DockedWidth": 200,
|
||||||
|
"SelectedChildIndex": 2,
|
||||||
|
"Children": [
|
||||||
|
{
|
||||||
|
"$type": "Bookmark",
|
||||||
|
"Name": "ST:0:0:{0174dea2-fdbe-4ef1-8f99-c0beae78880f}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$type": "Bookmark",
|
||||||
|
"Name": "ST:0:0:{aa2115a1-9712-457b-9047-dbb71ca2cdd2}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$type": "Document",
|
||||||
|
"DocumentIndex": 0,
|
||||||
|
"Title": "Program.cs",
|
||||||
|
"DocumentMoniker": "D:\\Programmieren\\CSharp\\JudoWeb\\API\\Program.cs",
|
||||||
|
"RelativeDocumentMoniker": "API\\Program.cs",
|
||||||
|
"ToolTip": "D:\\Programmieren\\CSharp\\JudoWeb\\API\\Program.cs",
|
||||||
|
"RelativeToolTip": "API\\Program.cs",
|
||||||
|
"ViewState": "AgIAAAAAAAAAAAAAAAAAABkAAAAAAAAAAAAAAA==",
|
||||||
|
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
|
||||||
|
"WhenOpened": "2025-10-05T16:26:39.39Z",
|
||||||
|
"EditorCaption": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$type": "Bookmark",
|
||||||
|
"Name": "ST:0:0:{cce594b6-0c39-4442-ba28-10c64ac7e89f}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -1,13 +1,33 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Compile Remove="Models\Ingoing\Altersgruppen\AltersGruppeIngoing\**" />
|
||||||
|
<Compile Remove="Services\Interfaces\**" />
|
||||||
|
<Content Remove="Models\Ingoing\Altersgruppen\AltersGruppeIngoing\**" />
|
||||||
|
<Content Remove="Services\Interfaces\**" />
|
||||||
|
<EmbeddedResource Remove="Models\Ingoing\Altersgruppen\AltersGruppeIngoing\**" />
|
||||||
|
<EmbeddedResource Remove="Services\Interfaces\**" />
|
||||||
|
<None Remove="Models\Ingoing\Altersgruppen\AltersGruppeIngoing\**" />
|
||||||
|
<None Remove="Services\Interfaces\**" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="9.0.11" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.11" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.11">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.11" />
|
||||||
|
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
|
||||||
|
<PackageReference Include="Ulid" Version="1.4.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
73
API/Controllers/AgeGroupController.cs
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
using API.Models.Internal.Altersgruppen;
|
||||||
|
using API.Repository.AgeGroupRepo;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace API.Controllers
|
||||||
|
{
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/ageGroups/")]
|
||||||
|
public class AgeGroupController : ControllerBase
|
||||||
|
{
|
||||||
|
private IAgeGroupService _ageGroupService;
|
||||||
|
|
||||||
|
public AgeGroupController(IAgeGroupService ageGroupService)
|
||||||
|
{
|
||||||
|
_ageGroupService = ageGroupService;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet()]
|
||||||
|
public async Task<IActionResult> GetAll()
|
||||||
|
{
|
||||||
|
var allAgeGroups = await _ageGroupService.GetAllAsync();
|
||||||
|
|
||||||
|
return Ok(allAgeGroups);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{id}")]
|
||||||
|
public async Task<IActionResult> GetOne([FromRoute] string id)
|
||||||
|
{
|
||||||
|
var group = await _ageGroupService.GetAsync(id);
|
||||||
|
|
||||||
|
if (group == null)
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(group);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost()]
|
||||||
|
public async Task<IActionResult> Create([FromBody] AltersGruppeIngoing groupDto)
|
||||||
|
{
|
||||||
|
var group = await _ageGroupService.CreateAsync(groupDto.ToInternalFromIngoing());
|
||||||
|
|
||||||
|
return CreatedAtAction(nameof(GetOne), new { Id = group.Id }, group);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPut("{id}")]
|
||||||
|
public async Task<IActionResult> Update([FromRoute] string id, [FromBody] AltersGruppeIngoing groupDto)
|
||||||
|
{
|
||||||
|
var group = await _ageGroupService.UpdateAsync(id, groupDto.ToInternalFromIngoing());
|
||||||
|
|
||||||
|
if(group == null)
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(group);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpDelete("{id}")]
|
||||||
|
public async Task<IActionResult> Delete([FromRoute] string Id)
|
||||||
|
{
|
||||||
|
var group = await _ageGroupService.DeleteAsync(Id);
|
||||||
|
|
||||||
|
if (group == null)
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
16
API/Controllers/AuthController.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
using API.Models.Internal.User;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
using Microsoft.AspNetCore.Identity.UI.Services;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace API.Controllers;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/account")]
|
||||||
|
public class AuthController(UserManager<User> userManager, SignInManager<User> signInManager, IEmailSender emailSender)
|
||||||
|
: ControllerBase
|
||||||
|
{
|
||||||
|
private readonly UserManager<User> _userManager = userManager;
|
||||||
|
private readonly SignInManager<User> _signInManager = signInManager;
|
||||||
|
private readonly IEmailSender _emailSender = emailSender;
|
||||||
|
}
|
||||||
21
API/Controllers/RegistrationKeyController.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
using API.Repository.RegistrationKeyRepo;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace API.Controllers;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/registrationKey")]
|
||||||
|
public class RegistrationKeyController : ControllerBase
|
||||||
|
{
|
||||||
|
private IRegistrationKeyService _keyService;
|
||||||
|
private readonly RoleManager<IdentityRole> _roleManager;
|
||||||
|
|
||||||
|
public RegistrationKeyController(IRegistrationKeyService keyService, RoleManager<IdentityRole> roleManager)
|
||||||
|
{
|
||||||
|
_keyService = keyService;
|
||||||
|
_roleManager = roleManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
|
|
||||||
namespace API.Controllers
|
|
||||||
{
|
|
||||||
[ApiController]
|
|
||||||
[Route("[controller]")]
|
|
||||||
public class WeatherForecastController : ControllerBase
|
|
||||||
{
|
|
||||||
private static readonly string[] Summaries = new[]
|
|
||||||
{
|
|
||||||
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
|
|
||||||
};
|
|
||||||
|
|
||||||
private readonly ILogger<WeatherForecastController> _logger;
|
|
||||||
|
|
||||||
public WeatherForecastController(ILogger<WeatherForecastController> logger)
|
|
||||||
{
|
|
||||||
_logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet(Name = "GetWeatherForecast")]
|
|
||||||
public IEnumerable<WeatherForecast> Get()
|
|
||||||
{
|
|
||||||
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
|
|
||||||
{
|
|
||||||
Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
|
|
||||||
TemperatureC = Random.Shared.Next(-20, 55),
|
|
||||||
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
|
|
||||||
})
|
|
||||||
.ToArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
24
API/Database/ApplicationDbContext.cs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
using API.Models.Internal.Altersgruppen;
|
||||||
|
using API.Models.Internal.User;
|
||||||
|
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace API.Database
|
||||||
|
{
|
||||||
|
public class ApplicationDbContext : IdentityDbContext<User>
|
||||||
|
{
|
||||||
|
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public DbSet<AltersGruppe> Altersgruppen { get; set; }
|
||||||
|
public DbSet<RegistrationKey> RegistrationKeys { get; set; }
|
||||||
|
|
||||||
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
base.OnModelCreating(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.ApplyConfigurationsFromAssembly(typeof(ApplicationDbContext).Assembly);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
34
API/Database/Configurations/AltersGruppeConfiguration.cs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
using API.Models.Internal.Altersgruppen;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
|
||||||
|
namespace API.Database.Configurations
|
||||||
|
{
|
||||||
|
public class AltersGruppeConfiguration : IEntityTypeConfiguration<AltersGruppe>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<AltersGruppe> entity)
|
||||||
|
{
|
||||||
|
// Primary Key
|
||||||
|
entity.HasKey(e => e.Id);
|
||||||
|
|
||||||
|
// Id-Konfiguration
|
||||||
|
entity.Property(e => e.Id)
|
||||||
|
.HasMaxLength(26)
|
||||||
|
.IsRequired()
|
||||||
|
.ValueGeneratedNever();
|
||||||
|
|
||||||
|
// Name
|
||||||
|
entity.Property(e => e.Name)
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
// StartingAge
|
||||||
|
entity.Property(e => e.StartingAge)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
// EndingAge
|
||||||
|
entity.Property(e => e.EndingAge)
|
||||||
|
.IsRequired();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
13
API/Database/Configurations/UserConfiguration.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
using API.Models.Internal.User;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
|
||||||
|
namespace API.Database.Configurations;
|
||||||
|
|
||||||
|
public class UserConfiguration : IEntityTypeConfiguration<User>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<User> entity)
|
||||||
|
{
|
||||||
|
entity.Property(e => e.Ininitals).HasMaxLength(5);
|
||||||
|
}
|
||||||
|
}
|
||||||
46
API/Migrations/20251207192702_Init.Designer.cs
generated
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using API.Database;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace API.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(ApplicationDbContext))]
|
||||||
|
[Migration("20251207192702_Init")]
|
||||||
|
partial class Init
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder.HasAnnotation("ProductVersion", "9.0.11");
|
||||||
|
|
||||||
|
modelBuilder.Entity("API.Models.Internal.Altersgruppen.AltersGruppe", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasMaxLength(26)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("EndingAge")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("StartingAge")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Altersgruppen");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
35
API/Migrations/20251207192702_Init.cs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace API.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class Init : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Altersgruppen",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<string>(type: "TEXT", maxLength: 26, nullable: false),
|
||||||
|
Name = table.Column<string>(type: "TEXT", maxLength: 100, nullable: false),
|
||||||
|
StartingAge = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
EndingAge = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Altersgruppen", x => x.Id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Altersgruppen");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
294
API/Migrations/20260123202637_InitialIdentity.Designer.cs
generated
Normal file
@@ -0,0 +1,294 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using API.Database;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace API.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(ApplicationDbContext))]
|
||||||
|
[Migration("20260123202637_InitialIdentity")]
|
||||||
|
partial class InitialIdentity
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder.HasAnnotation("ProductVersion", "9.0.11");
|
||||||
|
|
||||||
|
modelBuilder.Entity("API.Models.Internal.Altersgruppen.AltersGruppe", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasMaxLength(26)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("EndingAge")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("StartingAge")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Altersgruppen");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("API.Models.Internal.User.User", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("AccessFailedCount")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("ConcurrencyStamp")
|
||||||
|
.IsConcurrencyToken()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Email")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("EmailConfirmed")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Ininitals")
|
||||||
|
.HasMaxLength(5)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("LockoutEnabled")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset?>("LockoutEnd")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("NormalizedEmail")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("NormalizedUserName")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("PasswordHash")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("PhoneNumber")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("PhoneNumberConfirmed")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("SecurityStamp")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("TwoFactorEnabled")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("UserName")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("NormalizedEmail")
|
||||||
|
.HasDatabaseName("EmailIndex");
|
||||||
|
|
||||||
|
b.HasIndex("NormalizedUserName")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("UserNameIndex");
|
||||||
|
|
||||||
|
b.ToTable("AspNetUsers", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("ConcurrencyStamp")
|
||||||
|
.IsConcurrencyToken()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("NormalizedName")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("NormalizedName")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("RoleNameIndex");
|
||||||
|
|
||||||
|
b.ToTable("AspNetRoles", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("ClaimType")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("ClaimValue")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("RoleId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("RoleId");
|
||||||
|
|
||||||
|
b.ToTable("AspNetRoleClaims", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("ClaimType")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("ClaimValue")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("UserId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.ToTable("AspNetUserClaims", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("LoginProvider")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("ProviderKey")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("ProviderDisplayName")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("UserId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("LoginProvider", "ProviderKey");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.ToTable("AspNetUserLogins", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("UserId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("RoleId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("UserId", "RoleId");
|
||||||
|
|
||||||
|
b.HasIndex("RoleId");
|
||||||
|
|
||||||
|
b.ToTable("AspNetUserRoles", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("UserId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("LoginProvider")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Value")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("UserId", "LoginProvider", "Name");
|
||||||
|
|
||||||
|
b.ToTable("AspNetUserTokens", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("RoleId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("API.Models.Internal.User.User", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("API.Models.Internal.User.User", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("RoleId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("API.Models.Internal.User.User", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("API.Models.Internal.User.User", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
223
API/Migrations/20260123202637_InitialIdentity.cs
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace API.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class InitialIdentity : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "AspNetRoles",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<string>(type: "TEXT", nullable: false),
|
||||||
|
Name = table.Column<string>(type: "TEXT", maxLength: 256, nullable: true),
|
||||||
|
NormalizedName = table.Column<string>(type: "TEXT", maxLength: 256, nullable: true),
|
||||||
|
ConcurrencyStamp = table.Column<string>(type: "TEXT", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_AspNetRoles", x => x.Id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "AspNetUsers",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<string>(type: "TEXT", nullable: false),
|
||||||
|
Ininitals = table.Column<string>(type: "TEXT", maxLength: 5, nullable: true),
|
||||||
|
UserName = table.Column<string>(type: "TEXT", maxLength: 256, nullable: true),
|
||||||
|
NormalizedUserName = table.Column<string>(type: "TEXT", maxLength: 256, nullable: true),
|
||||||
|
Email = table.Column<string>(type: "TEXT", maxLength: 256, nullable: true),
|
||||||
|
NormalizedEmail = table.Column<string>(type: "TEXT", maxLength: 256, nullable: true),
|
||||||
|
EmailConfirmed = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||||
|
PasswordHash = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
|
SecurityStamp = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
|
ConcurrencyStamp = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
|
PhoneNumber = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
|
PhoneNumberConfirmed = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||||
|
TwoFactorEnabled = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||||
|
LockoutEnd = table.Column<DateTimeOffset>(type: "TEXT", nullable: true),
|
||||||
|
LockoutEnabled = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||||
|
AccessFailedCount = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_AspNetUsers", x => x.Id);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "AspNetRoleClaims",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
RoleId = table.Column<string>(type: "TEXT", nullable: false),
|
||||||
|
ClaimType = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
|
ClaimValue = table.Column<string>(type: "TEXT", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_AspNetRoleClaims_AspNetRoles_RoleId",
|
||||||
|
column: x => x.RoleId,
|
||||||
|
principalTable: "AspNetRoles",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "AspNetUserClaims",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
UserId = table.Column<string>(type: "TEXT", nullable: false),
|
||||||
|
ClaimType = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
|
ClaimValue = table.Column<string>(type: "TEXT", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_AspNetUserClaims", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_AspNetUserClaims_AspNetUsers_UserId",
|
||||||
|
column: x => x.UserId,
|
||||||
|
principalTable: "AspNetUsers",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "AspNetUserLogins",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
LoginProvider = table.Column<string>(type: "TEXT", nullable: false),
|
||||||
|
ProviderKey = table.Column<string>(type: "TEXT", nullable: false),
|
||||||
|
ProviderDisplayName = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
|
UserId = table.Column<string>(type: "TEXT", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey });
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_AspNetUserLogins_AspNetUsers_UserId",
|
||||||
|
column: x => x.UserId,
|
||||||
|
principalTable: "AspNetUsers",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "AspNetUserRoles",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
UserId = table.Column<string>(type: "TEXT", nullable: false),
|
||||||
|
RoleId = table.Column<string>(type: "TEXT", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId });
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_AspNetUserRoles_AspNetRoles_RoleId",
|
||||||
|
column: x => x.RoleId,
|
||||||
|
principalTable: "AspNetRoles",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_AspNetUserRoles_AspNetUsers_UserId",
|
||||||
|
column: x => x.UserId,
|
||||||
|
principalTable: "AspNetUsers",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "AspNetUserTokens",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
UserId = table.Column<string>(type: "TEXT", nullable: false),
|
||||||
|
LoginProvider = table.Column<string>(type: "TEXT", nullable: false),
|
||||||
|
Name = table.Column<string>(type: "TEXT", nullable: false),
|
||||||
|
Value = table.Column<string>(type: "TEXT", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name });
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_AspNetUserTokens_AspNetUsers_UserId",
|
||||||
|
column: x => x.UserId,
|
||||||
|
principalTable: "AspNetUsers",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_AspNetRoleClaims_RoleId",
|
||||||
|
table: "AspNetRoleClaims",
|
||||||
|
column: "RoleId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "RoleNameIndex",
|
||||||
|
table: "AspNetRoles",
|
||||||
|
column: "NormalizedName",
|
||||||
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_AspNetUserClaims_UserId",
|
||||||
|
table: "AspNetUserClaims",
|
||||||
|
column: "UserId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_AspNetUserLogins_UserId",
|
||||||
|
table: "AspNetUserLogins",
|
||||||
|
column: "UserId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_AspNetUserRoles_RoleId",
|
||||||
|
table: "AspNetUserRoles",
|
||||||
|
column: "RoleId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "EmailIndex",
|
||||||
|
table: "AspNetUsers",
|
||||||
|
column: "NormalizedEmail");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "UserNameIndex",
|
||||||
|
table: "AspNetUsers",
|
||||||
|
column: "NormalizedUserName",
|
||||||
|
unique: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "AspNetRoleClaims");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "AspNetUserClaims");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "AspNetUserLogins");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "AspNetUserRoles");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "AspNetUserTokens");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "AspNetRoles");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "AspNetUsers");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
291
API/Migrations/ApplicationDbContextModelSnapshot.cs
Normal file
@@ -0,0 +1,291 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using API.Database;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace API.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(ApplicationDbContext))]
|
||||||
|
partial class ApplicationDbContextModelSnapshot : ModelSnapshot
|
||||||
|
{
|
||||||
|
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder.HasAnnotation("ProductVersion", "9.0.11");
|
||||||
|
|
||||||
|
modelBuilder.Entity("API.Models.Internal.Altersgruppen.AltersGruppe", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasMaxLength(26)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("EndingAge")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(100)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("StartingAge")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Altersgruppen");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("API.Models.Internal.User.User", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("AccessFailedCount")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("ConcurrencyStamp")
|
||||||
|
.IsConcurrencyToken()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Email")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("EmailConfirmed")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Ininitals")
|
||||||
|
.HasMaxLength(5)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("LockoutEnabled")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset?>("LockoutEnd")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("NormalizedEmail")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("NormalizedUserName")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("PasswordHash")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("PhoneNumber")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("PhoneNumberConfirmed")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("SecurityStamp")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("TwoFactorEnabled")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("UserName")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("NormalizedEmail")
|
||||||
|
.HasDatabaseName("EmailIndex");
|
||||||
|
|
||||||
|
b.HasIndex("NormalizedUserName")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("UserNameIndex");
|
||||||
|
|
||||||
|
b.ToTable("AspNetUsers", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("ConcurrencyStamp")
|
||||||
|
.IsConcurrencyToken()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("NormalizedName")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("NormalizedName")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("RoleNameIndex");
|
||||||
|
|
||||||
|
b.ToTable("AspNetRoles", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("ClaimType")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("ClaimValue")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("RoleId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("RoleId");
|
||||||
|
|
||||||
|
b.ToTable("AspNetRoleClaims", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("ClaimType")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("ClaimValue")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("UserId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.ToTable("AspNetUserClaims", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("LoginProvider")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("ProviderKey")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("ProviderDisplayName")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("UserId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("LoginProvider", "ProviderKey");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.ToTable("AspNetUserLogins", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("UserId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("RoleId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("UserId", "RoleId");
|
||||||
|
|
||||||
|
b.HasIndex("RoleId");
|
||||||
|
|
||||||
|
b.ToTable("AspNetUserRoles", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("UserId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("LoginProvider")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Value")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("UserId", "LoginProvider", "Name");
|
||||||
|
|
||||||
|
b.ToTable("AspNetUserTokens", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("RoleId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("API.Models.Internal.User.User", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("API.Models.Internal.User.User", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("RoleId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("API.Models.Internal.User.User", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("API.Models.Internal.User.User", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
35
API/Models/Internal/Altersgruppen/AltersGruppe.cs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace API.Models.Internal.Altersgruppen
|
||||||
|
{
|
||||||
|
public class AltersGruppe
|
||||||
|
{
|
||||||
|
public string Id { get; set; } = Ulid.NewUlid().ToString();
|
||||||
|
public required string Name { get; set; }
|
||||||
|
public int StartingAge { get; set; }
|
||||||
|
public int EndingAge { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AltersGruppeIngoing
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
public string Name { get; set; }
|
||||||
|
[Required]
|
||||||
|
public int StartingAge { get; set; }
|
||||||
|
[Required]
|
||||||
|
public int EndingAge { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class AltersgruppeMapper
|
||||||
|
{
|
||||||
|
public static AltersGruppe ToInternalFromIngoing(this AltersGruppeIngoing group)
|
||||||
|
{
|
||||||
|
return new AltersGruppe
|
||||||
|
{
|
||||||
|
Name = group.Name,
|
||||||
|
StartingAge = group.StartingAge,
|
||||||
|
EndingAge = group.EndingAge,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
24
API/Models/Internal/User/RegistrationKey.cs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
namespace API.Models.Internal.User;
|
||||||
|
|
||||||
|
public class RegistrationKey
|
||||||
|
{
|
||||||
|
public string Id { get; set; } = Ulid.NewUlid().ToString();
|
||||||
|
public string? LinkedRole { get; set; }
|
||||||
|
public DateTime Created { get; set; } = DateTime.UtcNow;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RegistrationKeyIngoing
|
||||||
|
{
|
||||||
|
public string? LinkedRole { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class RegistrationKeyMapper
|
||||||
|
{
|
||||||
|
public static RegistrationKey ToInternalFromIngoing(this RegistrationKeyIngoing key)
|
||||||
|
{
|
||||||
|
return new RegistrationKey
|
||||||
|
{
|
||||||
|
LinkedRole = key.LinkedRole,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
8
API/Models/Internal/User/User.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
|
||||||
|
namespace API.Models.Internal.User;
|
||||||
|
|
||||||
|
public class User : IdentityUser
|
||||||
|
{
|
||||||
|
public string? Ininitals { get; set; }
|
||||||
|
}
|
||||||
9
API/Models/Outgoing/PlunkEmailDto.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
namespace API.Models.Outgoing;
|
||||||
|
|
||||||
|
public class PlunkEmailDto
|
||||||
|
{
|
||||||
|
public required string To { get; set; }
|
||||||
|
public required string Subject { get; set; }
|
||||||
|
public required string Body { get; set; }
|
||||||
|
public string? Name { get; set; }
|
||||||
|
}
|
||||||
@@ -1,12 +1,58 @@
|
|||||||
var builder = WebApplication.CreateBuilder(args);
|
using API.Database;
|
||||||
|
using API.Models.Internal.User;
|
||||||
|
using API.Repository.AgeGroupRepo;
|
||||||
|
using API.Services;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
using Microsoft.AspNetCore.Identity.UI.Services;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
// Add services to the container.
|
var webRoot = Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), "..", "wwwroot"));
|
||||||
|
|
||||||
|
var builder = WebApplication.CreateBuilder(new WebApplicationOptions
|
||||||
|
{
|
||||||
|
Args = args,
|
||||||
|
WebRootPath = webRoot
|
||||||
|
});
|
||||||
|
|
||||||
builder.Services.AddControllers();
|
builder.Services.AddControllers();
|
||||||
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
|
|
||||||
builder.Services.AddEndpointsApiExplorer();
|
builder.Services.AddEndpointsApiExplorer();
|
||||||
builder.Services.AddSwaggerGen();
|
builder.Services.AddSwaggerGen();
|
||||||
|
|
||||||
|
// Authentication
|
||||||
|
builder.Services.AddAuthorization();
|
||||||
|
builder.Services.AddAuthentication()
|
||||||
|
.AddCookie(IdentityConstants.ApplicationScheme);
|
||||||
|
|
||||||
|
builder.Services.AddIdentityCore<User>(options =>
|
||||||
|
{
|
||||||
|
options.SignIn.RequireConfirmedAccount = true;
|
||||||
|
options.User.RequireUniqueEmail = true;
|
||||||
|
}).AddEntityFrameworkStores<ApplicationDbContext>().AddRoles<IdentityRole>().AddDefaultTokenProviders();
|
||||||
|
|
||||||
|
|
||||||
|
// Database
|
||||||
|
var postgresConnection = builder.Configuration.GetConnectionString("PostgresConnection");
|
||||||
|
if (!string.IsNullOrEmpty(postgresConnection))
|
||||||
|
{
|
||||||
|
// Nutze PostgresSQL
|
||||||
|
builder.Services.AddDbContext<ApplicationDbContext>(options => options.UseNpgsql(postgresConnection));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
builder.Services.AddDbContext<ApplicationDbContext>(options => options.UseSqlite("Data Source=app.db"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adding Email Service (Plunk)
|
||||||
|
builder.Services.AddHttpClient<IEmailSender, PlunkEmailSender>();
|
||||||
|
|
||||||
|
// Adding Database Services
|
||||||
|
builder.Services.AddScoped<IAgeGroupService, AgeGroupService>();
|
||||||
|
|
||||||
|
// Adding S3 Services
|
||||||
|
|
||||||
|
// Adding (latler) Redis Services
|
||||||
|
|
||||||
|
// Add Database Services
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
// Configure the HTTP request pipeline.
|
// Configure the HTTP request pipeline.
|
||||||
@@ -16,10 +62,22 @@ if (app.Environment.IsDevelopment())
|
|||||||
app.UseSwaggerUI();
|
app.UseSwaggerUI();
|
||||||
}
|
}
|
||||||
|
|
||||||
app.UseHttpsRedirection();
|
using(var scope = app.Services.CreateScope())
|
||||||
|
{
|
||||||
|
var dbContext = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||||
|
dbContext.Database.Migrate();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deliver frontend
|
||||||
|
app.UseDefaultFiles();
|
||||||
|
app.UseStaticFiles();
|
||||||
|
|
||||||
|
app.UseHttpsRedirection();
|
||||||
|
app.UseAuthentication();
|
||||||
app.UseAuthorization();
|
app.UseAuthorization();
|
||||||
|
|
||||||
app.MapControllers();
|
app.MapControllers();
|
||||||
|
// Frontend Fallback
|
||||||
|
app.MapFallbackToFile("index.html");
|
||||||
|
|
||||||
app.Run();
|
app.Run();
|
||||||
|
|||||||
68
API/Repository/AgeGroupRepo/AgeGroupService.cs
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
using API.Database;
|
||||||
|
using API.Models.Internal.Altersgruppen;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace API.Repository.AgeGroupRepo
|
||||||
|
{
|
||||||
|
public class AgeGroupService : IAgeGroupService
|
||||||
|
{
|
||||||
|
private ApplicationDbContext _context;
|
||||||
|
|
||||||
|
public AgeGroupService(ApplicationDbContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<AltersGruppe> CreateAsync(AltersGruppe altersGruppe)
|
||||||
|
{
|
||||||
|
await _context.Altersgruppen.AddAsync(altersGruppe);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
return altersGruppe;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<AltersGruppe?> DeleteAsync(string id)
|
||||||
|
{
|
||||||
|
var group = await _context.Altersgruppen.FirstOrDefaultAsync(x => x.Id == id);
|
||||||
|
|
||||||
|
if (group == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_context.Altersgruppen.Remove(group);
|
||||||
|
_context.SaveChanges();
|
||||||
|
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<AltersGruppe>> GetAllAsync()
|
||||||
|
{
|
||||||
|
var allGroups = await _context.Altersgruppen.ToListAsync();
|
||||||
|
|
||||||
|
return allGroups;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<AltersGruppe?> GetAsync(string id)
|
||||||
|
{
|
||||||
|
return await _context.Altersgruppen.FindAsync(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<AltersGruppe?> UpdateAsync(string id, AltersGruppe altersGruppe)
|
||||||
|
{
|
||||||
|
var existingGroup = await _context.Altersgruppen.FirstOrDefaultAsync(x => x.Id == id);
|
||||||
|
|
||||||
|
if (existingGroup == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
existingGroup.Name = altersGruppe.Name;
|
||||||
|
existingGroup.StartingAge = altersGruppe.StartingAge;
|
||||||
|
existingGroup.EndingAge = altersGruppe.EndingAge;
|
||||||
|
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
return existingGroup;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
API/Repository/AgeGroupRepo/IAgeGroupService.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
using API.Models.Internal.Altersgruppen;
|
||||||
|
|
||||||
|
namespace API.Repository.AgeGroupRepo
|
||||||
|
{
|
||||||
|
public interface IAgeGroupService
|
||||||
|
{
|
||||||
|
public Task<List<AltersGruppe>> GetAllAsync();
|
||||||
|
|
||||||
|
public Task<AltersGruppe?> GetAsync(string id);
|
||||||
|
public Task<AltersGruppe> CreateAsync(AltersGruppe altersGruppe);
|
||||||
|
public Task<AltersGruppe?> DeleteAsync(string id);
|
||||||
|
public Task<AltersGruppe?> UpdateAsync(string id, AltersGruppe altersGruppe);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
using API.Models.Internal.User;
|
||||||
|
|
||||||
|
namespace API.Repository.RegistrationKeyRepo;
|
||||||
|
|
||||||
|
public interface IRegistrationKeyService
|
||||||
|
{
|
||||||
|
public Task<List<RegistrationKey>> GetAllAsync();
|
||||||
|
public Task<RegistrationKey?> GetAsync(string id);
|
||||||
|
public Task<RegistrationKey> CreateAsync(RegistrationKeyIngoing key);
|
||||||
|
public Task<RegistrationKey?> DeleteAsync(string id);
|
||||||
|
|
||||||
|
public Task<int> DeleteOldRegistrationKeysAsync(int x);
|
||||||
|
}
|
||||||
76
API/Repository/RegistrationKeyRepo/RegistrationKeyService.cs
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
using API.Database;
|
||||||
|
using API.Models.Internal.User;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace API.Repository.RegistrationKeyRepo;
|
||||||
|
|
||||||
|
public class RegistrationKeyService : IRegistrationKeyService
|
||||||
|
{
|
||||||
|
private ApplicationDbContext _context;
|
||||||
|
|
||||||
|
public RegistrationKeyService(ApplicationDbContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<RegistrationKey>> GetAllAsync()
|
||||||
|
{
|
||||||
|
var allKeys = await _context.RegistrationKeys.ToListAsync();
|
||||||
|
|
||||||
|
return allKeys;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<RegistrationKey?> GetAsync(string id)
|
||||||
|
{
|
||||||
|
return await _context.RegistrationKeys.FindAsync(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<RegistrationKey> CreateAsync(RegistrationKeyIngoing key)
|
||||||
|
{
|
||||||
|
var internalKey = key.ToInternalFromIngoing();
|
||||||
|
|
||||||
|
await _context.RegistrationKeys.AddAsync(internalKey);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
return internalKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<RegistrationKey?> DeleteAsync(string id)
|
||||||
|
{
|
||||||
|
var key = await _context.RegistrationKeys.FirstOrDefaultAsync(x => x.Id == id);
|
||||||
|
|
||||||
|
if (key == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_context.RegistrationKeys.Remove(key);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<int> DeleteOldRegistrationKeysAsync(int x)
|
||||||
|
{
|
||||||
|
if (x <= 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var cutoff = DateTime.UtcNow.AddDays(-x);
|
||||||
|
|
||||||
|
var oldKeys = await _context.RegistrationKeys
|
||||||
|
.Where(k => k.Created < cutoff)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
if (oldKeys.Count == 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
_context.RegistrationKeys.RemoveRange(oldKeys);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
return oldKeys.Count;
|
||||||
|
}
|
||||||
|
}
|
||||||
36
API/Services/PlunkEmailSender.cs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
using System.Net.Http.Headers;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
using API.Models.Outgoing;
|
||||||
|
using Microsoft.AspNetCore.Identity.UI.Services;
|
||||||
|
|
||||||
|
namespace API.Services;
|
||||||
|
|
||||||
|
public class PlunkEmailSender(HttpClient httpClient, string plunkSecretKey) : IEmailSender
|
||||||
|
{
|
||||||
|
public async Task SendEmailAsync(string email, string subject, string htmlMessage)
|
||||||
|
{
|
||||||
|
var requestBody = new PlunkEmailDto()
|
||||||
|
{
|
||||||
|
To = email,
|
||||||
|
Subject = subject,
|
||||||
|
Body = htmlMessage
|
||||||
|
};
|
||||||
|
|
||||||
|
var jsonContent = new StringContent(
|
||||||
|
JsonSerializer.Serialize(requestBody),
|
||||||
|
Encoding.UTF8,
|
||||||
|
"application/json"
|
||||||
|
);
|
||||||
|
|
||||||
|
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", plunkSecretKey);
|
||||||
|
|
||||||
|
var response = await httpClient.PostAsync("https://api.useplunk.com/v1/send", jsonContent);
|
||||||
|
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var error = await response.Content.ReadAsStringAsync();
|
||||||
|
throw new Exception($"Fehler beim Senden der E-Mail über Plunk: {error}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
namespace API
|
|
||||||
{
|
|
||||||
public class WeatherForecast
|
|
||||||
{
|
|
||||||
public DateOnly Date { get; set; }
|
|
||||||
|
|
||||||
public int TemperatureC { get; set; }
|
|
||||||
|
|
||||||
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
|
|
||||||
|
|
||||||
public string? Summary { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"Logging": {
|
|
||||||
"LogLevel": {
|
|
||||||
"Default": "Information",
|
|
||||||
"Microsoft.AspNetCore": "Warning"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/prettierrc",
|
"$schema": "https://json.schemastore.org/prettierrc",
|
||||||
"semi": false,
|
"semi": true,
|
||||||
"singleQuote": true,
|
"singleQuote": true,
|
||||||
"printWidth": 100
|
"printWidth": 100
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,48 +0,0 @@
|
|||||||
# ./
|
|
||||||
|
|
||||||
This template should help get you started developing with Vue 3 in Vite.
|
|
||||||
|
|
||||||
## Recommended IDE Setup
|
|
||||||
|
|
||||||
[VS Code](https://code.visualstudio.com/) + [Vue (Official)](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
|
|
||||||
|
|
||||||
## Recommended Browser Setup
|
|
||||||
|
|
||||||
- Chromium-based browsers (Chrome, Edge, Brave, etc.):
|
|
||||||
- [Vue.js devtools](https://chromewebstore.google.com/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd)
|
|
||||||
- [Turn on Custom Object Formatter in Chrome DevTools](http://bit.ly/object-formatters)
|
|
||||||
- Firefox:
|
|
||||||
- [Vue.js devtools](https://addons.mozilla.org/en-US/firefox/addon/vue-js-devtools/)
|
|
||||||
- [Turn on Custom Object Formatter in Firefox DevTools](https://fxdx.dev/firefox-devtools-custom-object-formatters/)
|
|
||||||
|
|
||||||
## Type Support for `.vue` Imports in TS
|
|
||||||
|
|
||||||
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.
|
|
||||||
|
|
||||||
## Customize configuration
|
|
||||||
|
|
||||||
See [Vite Configuration Reference](https://vite.dev/config/).
|
|
||||||
|
|
||||||
## Project Setup
|
|
||||||
|
|
||||||
```sh
|
|
||||||
npm install
|
|
||||||
```
|
|
||||||
|
|
||||||
### Compile and Hot-Reload for Development
|
|
||||||
|
|
||||||
```sh
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
### Type-Check, Compile and Minify for Production
|
|
||||||
|
|
||||||
```sh
|
|
||||||
npm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
### Run Unit Tests with [Vitest](https://vitest.dev/)
|
|
||||||
|
|
||||||
```sh
|
|
||||||
npm run test:unit
|
|
||||||
```
|
|
||||||
@@ -4,10 +4,10 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<link rel="icon" href="/favicon.ico">
|
<link rel="icon" href="/favicon.ico">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Vite App</title>
|
<title>Judoteam - Stadtlohn</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
<script type="module" src="/src/main.ts"></script>
|
<script type="module" src="/src/main.ts"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
BIN
GUI/public/static/images/Logo.png
Normal file
|
After Width: | Height: | Size: 265 KiB |
BIN
GUI/public/static/images/appBarIcon.png
Normal file
|
After Width: | Height: | Size: 1.9 MiB |
BIN
GUI/public/static/images/house/entrance.jpg
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
GUI/public/static/images/house/front.jpg
Normal file
|
After Width: | Height: | Size: 2.3 MiB |
BIN
GUI/public/static/images/house/frontSide.jpg
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
GUI/public/static/images/kinoabend/total.jpg
Normal file
|
After Width: | Height: | Size: 133 KiB |
BIN
GUI/public/static/images/nikolausturnier/total.png
Normal file
|
After Width: | Height: | Size: 9.8 MiB |
BIN
GUI/public/static/images/startPage.png
Normal file
|
After Width: | Height: | Size: 10 MiB |
BIN
GUI/public/static/images/wettkampf/total.jpg
Normal file
|
After Width: | Height: | Size: 6.8 MiB |
BIN
GUI/public/static/images/whatsJudo/hold.jpg
Normal file
|
After Width: | Height: | Size: 5.9 MiB |
BIN
GUI/public/static/images/whatsJudo/stabil.jpg
Normal file
|
After Width: | Height: | Size: 282 KiB |
BIN
GUI/public/static/images/whatsJudo/throw.jpg
Normal file
|
After Width: | Height: | Size: 5.0 MiB |
@@ -1,11 +0,0 @@
|
|||||||
<script setup lang="ts"></script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<h1>You did it!</h1>
|
|
||||||
<p>
|
|
||||||
Visit <a href="https://vuejs.org/" target="_blank" rel="noopener">vuejs.org</a> to read the
|
|
||||||
documentation
|
|
||||||
</p>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped></style>
|
|
||||||
131
GUI/src/Layout.vue
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { onMounted, ref, watch } from 'vue';
|
||||||
|
import { useTheme } from 'vuetify';
|
||||||
|
import { Visibility, routes } from './plugins/routesLayout';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
|
||||||
|
const theme = useTheme();
|
||||||
|
const route = useRoute();
|
||||||
|
const showDrawer = ref(true);
|
||||||
|
|
||||||
|
function changeTheme() {
|
||||||
|
theme.toggle();
|
||||||
|
localStorage.setItem('theme', theme.name.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleDrawer() {
|
||||||
|
showDrawer.value = !showDrawer.value;
|
||||||
|
localStorage.setItem('drawer', showDrawer.value ? 'Y' : 'N');
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
theme.change(
|
||||||
|
localStorage.getItem('theme') ||
|
||||||
|
(window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'),
|
||||||
|
);
|
||||||
|
showDrawer.value = localStorage.getItem('drawer')?.startsWith('Y') || false;
|
||||||
|
});
|
||||||
|
|
||||||
|
function changeWebsiteTitle(path: string) {
|
||||||
|
// Pfad als Parameter
|
||||||
|
let currentPageInfo = routes.find((x) => x.path === path);
|
||||||
|
if (currentPageInfo) {
|
||||||
|
document.title = 'Judoteam - Stadtlohn | ' + currentPageInfo.name;
|
||||||
|
} else {
|
||||||
|
document.title = 'Judoteam - Stadtlohn';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Beobachte die Route auf Änderungen
|
||||||
|
watch(
|
||||||
|
() => route.path,
|
||||||
|
(newPath) => {
|
||||||
|
changeWebsiteTitle(newPath);
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<v-app>
|
||||||
|
<v-app-bar image="/static/images/appBarIcon.png">
|
||||||
|
<template v-slot:image>
|
||||||
|
<v-img
|
||||||
|
:gradient="
|
||||||
|
theme.name.value === 'dark'
|
||||||
|
? 'rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5)'
|
||||||
|
: 'rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.5)'
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template v-slot:prepend>
|
||||||
|
<v-app-bar-nav-icon
|
||||||
|
v-tooltip="!showDrawer ? 'Menü öffnen' : 'Menü schließen'"
|
||||||
|
@click="toggleDrawer()"
|
||||||
|
></v-app-bar-nav-icon>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<v-app-bar-title class="title" @click="$router.push({ name: 'Home' })"
|
||||||
|
><span class="pointer">Judoteam - Stadtlohn</span></v-app-bar-title
|
||||||
|
>
|
||||||
|
|
||||||
|
<v-tooltip v-if="!$vuetify.display.mobile">
|
||||||
|
<template #activator="{ props }">
|
||||||
|
<v-btn icon v-bind="props" to="/login">
|
||||||
|
<v-icon>mdi-account</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
Account
|
||||||
|
</v-tooltip>
|
||||||
|
<v-tooltip>
|
||||||
|
<template #activator="{ props }">
|
||||||
|
<v-btn icon @click="changeTheme()" v-bind="props">
|
||||||
|
<v-icon>mdi-brightness-6</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
{{ theme.name.value === 'dark' ? 'Hellen Modus aktivieren' : 'Dunklen Modus aktivieren' }}
|
||||||
|
</v-tooltip>
|
||||||
|
</v-app-bar>
|
||||||
|
|
||||||
|
<v-navigation-drawer
|
||||||
|
v-model="showDrawer"
|
||||||
|
:location="$vuetify.display.mobile ? 'bottom' : undefined"
|
||||||
|
:permanent="!$vuetify.display.mobile"
|
||||||
|
>
|
||||||
|
<v-list>
|
||||||
|
<v-list-item
|
||||||
|
v-for="item in routes.filter((x) => x.visible === Visibility.Public)"
|
||||||
|
:key="item.path"
|
||||||
|
:to="item.path"
|
||||||
|
:active="route.path === item.path"
|
||||||
|
link
|
||||||
|
:prepend-icon="item.icon"
|
||||||
|
:title="item.name"
|
||||||
|
class="rounded-lg mr-1 ml-1"
|
||||||
|
>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-navigation-drawer>
|
||||||
|
|
||||||
|
<v-main>
|
||||||
|
<router-view></router-view>
|
||||||
|
<v-footer
|
||||||
|
class="d-flex align-center justify-center ga-2 flex-wrap flex-grow-1 py-3"
|
||||||
|
>
|
||||||
|
<v-btn
|
||||||
|
v-for="link in routes.filter((x) => x.visible === Visibility.Footer)"
|
||||||
|
:key="link.path"
|
||||||
|
:to="link.path"
|
||||||
|
:text="link.name"
|
||||||
|
variant="text"
|
||||||
|
rounded
|
||||||
|
></v-btn>
|
||||||
|
<div class="flex-1-0-100 text-center mt-2">
|
||||||
|
{{ new Date().getFullYear() }} — <strong>Judoteam - Stadtlohn</strong>
|
||||||
|
</div>
|
||||||
|
</v-footer>
|
||||||
|
</v-main>
|
||||||
|
</v-app>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
import { describe, it, expect } from 'vitest'
|
|
||||||
|
|
||||||
import { mount } from '@vue/test-utils'
|
|
||||||
import App from '../App.vue'
|
|
||||||
|
|
||||||
describe('App', () => {
|
|
||||||
it('mounts renders properly', () => {
|
|
||||||
const wrapper = mount(App)
|
|
||||||
expect(wrapper.text()).toContain('You did it!')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
29
GUI/src/components/CarouselItemWithTitle.vue
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
const props = defineProps({
|
||||||
|
image: String,
|
||||||
|
text: String,
|
||||||
|
noCover: Boolean,
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<v-carousel-item :src="props.image" :cover="!props.noCover">
|
||||||
|
<template #default>
|
||||||
|
<div class="text-center">
|
||||||
|
<p class="text-h CarouselWithTitleText">{{ text }}</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</v-carousel-item>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.CarouselWithTitleText{
|
||||||
|
background-color: rgb(0, 0, 0, 0.65);
|
||||||
|
color: white;
|
||||||
|
display: inline-block;
|
||||||
|
padding: 2px;
|
||||||
|
padding-right: 10px;
|
||||||
|
padding-left: 10px;
|
||||||
|
border-radius: 0px 0px var(--default-radius) var(--default-radius);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
132
GUI/src/components/HomeEntrie.vue
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
title: String,
|
||||||
|
noMarginTop: Boolean,
|
||||||
|
extraBesideText: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
extraWidthPercent: {
|
||||||
|
type: Number,
|
||||||
|
default: 30,
|
||||||
|
},
|
||||||
|
titleInSplitRight: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const normalizedExtraWidth = computed(() => {
|
||||||
|
const width = Number.isFinite(props.extraWidthPercent) ? props.extraWidthPercent : 30;
|
||||||
|
|
||||||
|
return Math.min(100, Math.max(0, width));
|
||||||
|
});
|
||||||
|
|
||||||
|
const textWidthPercent = computed(() => 100 - normalizedExtraWidth.value);
|
||||||
|
|
||||||
|
const extraWidthStyle = computed(() => ({
|
||||||
|
flexBasis: `${normalizedExtraWidth.value}%`,
|
||||||
|
maxWidth: `${normalizedExtraWidth.value}%`,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const textWidthStyle = computed(() => ({
|
||||||
|
flexBasis: `${textWidthPercent.value}%`,
|
||||||
|
maxWidth: `${textWidthPercent.value}%`,
|
||||||
|
}));
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div :class="{ 'mt-6': !noMarginTop }">
|
||||||
|
<template v-if="props.extraBesideText">
|
||||||
|
<h3 v-if="!props.titleInSplitRight" class="text-h4 font-weight-bold">
|
||||||
|
{{ props.title }}
|
||||||
|
</h3>
|
||||||
|
<div class="entry-content entry-content--split">
|
||||||
|
<h3
|
||||||
|
v-if="props.titleInSplitRight"
|
||||||
|
class="text-h4 font-weight-bold entry-content__title--mobile"
|
||||||
|
>
|
||||||
|
{{ props.title }}
|
||||||
|
</h3>
|
||||||
|
<div class="entry-content__extra" :style="extraWidthStyle">
|
||||||
|
<slot name="extra" />
|
||||||
|
</div>
|
||||||
|
<div class="entry-content__text" :style="textWidthStyle">
|
||||||
|
<h3
|
||||||
|
v-if="props.titleInSplitRight"
|
||||||
|
class="text-h4 font-weight-bold entry-content__title--desktop"
|
||||||
|
>
|
||||||
|
{{ props.title }}
|
||||||
|
</h3>
|
||||||
|
<p class="text-subtitle-1">
|
||||||
|
<slot name="text" />
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<h3 class="text-h4 font-weight-bold">{{ props.title }}</h3>
|
||||||
|
<p class="text-subtitle-1">
|
||||||
|
<slot name="text" />
|
||||||
|
</p>
|
||||||
|
<slot name="extra" />
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.entry-content {
|
||||||
|
display: flex;
|
||||||
|
gap: 1.5rem;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-content--split {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-content__extra,
|
||||||
|
.entry-content__text {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-content__extra {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-content__text {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Desktop/Mobile title visibility helpers */
|
||||||
|
.entry-content__title--mobile {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.entry-content__title--desktop {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.entry-content--split {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-content__extra,
|
||||||
|
.entry-content__text {
|
||||||
|
flex-basis: auto !important;
|
||||||
|
max-width: 100% !important;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-content__title--mobile {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry-content__title--desktop {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
30
GUI/src/components/HomeEntrieWithImagePreset.vue
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import HomeEntrie from '@/components/HomeEntrie.vue';
|
||||||
|
const props = defineProps({
|
||||||
|
title: String,
|
||||||
|
image: String,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<home-entrie
|
||||||
|
:title="props.title"
|
||||||
|
extra-beside-text
|
||||||
|
:extra-width-percent="50"
|
||||||
|
title-in-split-right
|
||||||
|
>
|
||||||
|
<template #text>
|
||||||
|
<slot name="text"></slot>
|
||||||
|
</template>
|
||||||
|
<template #extra>
|
||||||
|
<v-img class="rounded-lg" :src="props.image" height="400" cover> </v-img>
|
||||||
|
</template>
|
||||||
|
</home-entrie>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.paddingIn {
|
||||||
|
padding-left: 75px;
|
||||||
|
padding-right: 75px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
43
GUI/src/components/ResponsiveTabsOrList.vue
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { useDisplay } from 'vuetify';
|
||||||
|
|
||||||
|
export interface TabItem {
|
||||||
|
value: string;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
items: TabItem[];
|
||||||
|
tabColor?: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const display = useDisplay();
|
||||||
|
const tab = ref('1');
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<!-- Desktop: Tabs mit einzelner Anzeige -->
|
||||||
|
<template v-if="!display.mobile.value">
|
||||||
|
<v-tabs :color="tabColor ?? '#b62b2b'" v-model="tab">
|
||||||
|
<v-tab v-for="item in items" :key="item.value" :value="item.value">
|
||||||
|
{{ item.label }}
|
||||||
|
</v-tab>
|
||||||
|
</v-tabs>
|
||||||
|
|
||||||
|
<v-divider></v-divider>
|
||||||
|
|
||||||
|
<v-tabs-window v-model="tab">
|
||||||
|
<v-tabs-window-item v-for="item in items" :key="item.value" :value="item.value">
|
||||||
|
<slot :name="item.value"></slot>
|
||||||
|
</v-tabs-window-item>
|
||||||
|
</v-tabs-window>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Mobile: Alle Einträge untereinander -->
|
||||||
|
<template v-else>
|
||||||
|
<template v-for="item in items" :key="item.value">
|
||||||
|
<slot :name="item.value"></slot>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
:root{
|
||||||
|
--default-radius: 7px;
|
||||||
|
--red-color: #b62b2b
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pointer{
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
import App from './App.vue'
|
import App from './Layout.vue'
|
||||||
import router from './router'
|
import router from './router'
|
||||||
import vuetify from './plugins/vuetify'
|
import vuetify from './plugins/vuetify'
|
||||||
|
|
||||||
|
|||||||
0
GUI/src/plugins/colorPattlet.ts
Normal file
66
GUI/src/plugins/routesLayout.ts
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import Home from '@/routes/Home.vue'
|
||||||
|
import NotFound from '@/routes/404NotFound.vue'
|
||||||
|
import type { RouteRecordRaw } from 'vue-router'
|
||||||
|
import Login from '@/routes/authentication/Login.vue';
|
||||||
|
|
||||||
|
export enum Visibility {
|
||||||
|
Hidden,
|
||||||
|
Authenticated,
|
||||||
|
Unauthenticated,
|
||||||
|
Authorized,
|
||||||
|
Public,
|
||||||
|
Footer,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface LayoutRoute {
|
||||||
|
path: string,
|
||||||
|
name: string,
|
||||||
|
description: string,
|
||||||
|
icon: string,
|
||||||
|
disableFooter?: boolean,
|
||||||
|
visible: Visibility,
|
||||||
|
meta?: RouteRecordRaw
|
||||||
|
}
|
||||||
|
|
||||||
|
export const routes: LayoutRoute[] = [
|
||||||
|
{
|
||||||
|
path: "/",
|
||||||
|
name: "Startseite",
|
||||||
|
description: "Übersicht der Anwendung",
|
||||||
|
icon: "mdi-home",
|
||||||
|
visible: Visibility.Public,
|
||||||
|
meta: {
|
||||||
|
name: 'Home',
|
||||||
|
path: '/',
|
||||||
|
component: Home
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/login",
|
||||||
|
name: "Login",
|
||||||
|
description: "Logge dich ein",
|
||||||
|
icon: "mdi-login",
|
||||||
|
visible: Visibility.Hidden,
|
||||||
|
meta: {
|
||||||
|
path: "/login",
|
||||||
|
name: 'Login',
|
||||||
|
component: Login
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/impressum",
|
||||||
|
name: "Impressum",
|
||||||
|
description: "Impressum der Anwendung",
|
||||||
|
icon: "mdi-file-document",
|
||||||
|
visible: Visibility.Footer
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/notFound",
|
||||||
|
name: "Nicht Gefunden",
|
||||||
|
description: "Diese Seite wurde nicht gefunden",
|
||||||
|
icon: "mdi-information-outline",
|
||||||
|
visible: Visibility.Hidden,
|
||||||
|
meta: { path: '/:pathMatch(.*)*', name: 'NotFound', component: NotFound },
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
import { createRouter, createWebHistory } from 'vue-router'
|
import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router';
|
||||||
|
import { routes } from '@/plugins/routesLayout'
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(import.meta.env.BASE_URL),
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
routes: [],
|
routes: routes.filter(x => x.meta !== undefined).map(x => x.meta) as RouteRecordRaw[]
|
||||||
})
|
})
|
||||||
|
|
||||||
export default router
|
export default router
|
||||||
|
|||||||
34
GUI/src/routes/404NotFound.vue
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<v-container fluid class="fill-height d-flex flex-column justify-center align-center">
|
||||||
|
<h1 class="error-title text-h1 font-weight-bold">404</h1>
|
||||||
|
<p class="error-message text-h5">Page not found</p>
|
||||||
|
|
||||||
|
<router-link
|
||||||
|
to="/"
|
||||||
|
class="text-blue text-decoration-none font-weight-medium mt-5"
|
||||||
|
>
|
||||||
|
Zurück zur Startseite
|
||||||
|
</router-link>
|
||||||
|
</v-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.error-title {
|
||||||
|
font-size: 3rem;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-message {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
opacity: 0.85;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
a:hover {
|
||||||
|
text-decoration: underline !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
142
GUI/src/routes/Home.vue
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import CarouselItemWithTitle from '@/components/CarouselItemWithTitle.vue';
|
||||||
|
import HomeEntrie from '@/components/HomeEntrie.vue';
|
||||||
|
import HomeEntrieWithImagePreset from '@/components/HomeEntrieWithImagePreset.vue';
|
||||||
|
import ResponsiveTabsOrList from '@/components/ResponsiveTabsOrList.vue';
|
||||||
|
|
||||||
|
const tabItems = [
|
||||||
|
{ value: '1', label: 'Nikolausturnier' },
|
||||||
|
{ value: '2', label: 'Wettkämpfe' },
|
||||||
|
{ value: '3', label: 'Kinoabende' },
|
||||||
|
];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<v-container fluid>
|
||||||
|
<v-img class="rounded-lg" height="40vh" src="/static/images/startPage.png" cover />
|
||||||
|
<main>
|
||||||
|
<v-container
|
||||||
|
id="main"
|
||||||
|
:style="{
|
||||||
|
maxWidth: $vuetify.display.smAndDown ? '100vw' : '70vw',
|
||||||
|
}"
|
||||||
|
fluid
|
||||||
|
>
|
||||||
|
<home-entrie title="Herzlich Willkommen!">
|
||||||
|
<template #text>
|
||||||
|
<b>Schön, dass du den Weg zu uns gefunden hast!</b> Egal, ob du schon lange dabei bist
|
||||||
|
oder das erste Mal von Judo hörst – wir freuen uns, dich bald (wieder) in unserem
|
||||||
|
<b>Judozentrum</b>
|
||||||
|
(Dojo) begrüßen zu dürfen.
|
||||||
|
</template>
|
||||||
|
</home-entrie>
|
||||||
|
<home-entrie
|
||||||
|
title="Was ist Judo?"
|
||||||
|
extra-beside-text
|
||||||
|
:extra-width-percent="50"
|
||||||
|
title-in-split-right
|
||||||
|
>
|
||||||
|
<template #text>
|
||||||
|
Judo ist eine Sportart, welche den ganzen Körper fordert, fördert und beansprucht. Es
|
||||||
|
werden also <b>sämtliche Muskelgruppen, sowie das Köpfchen bei uns trainiert. </b>
|
||||||
|
<b>Judo bedeutet übersetzt “der sanfte Weg”.</b> Daraus lässt sich ja schon erahnen,
|
||||||
|
dass Judo eine Kampfsportart ist bei der das "Schlagen" oder "Treten" des Gegners
|
||||||
|
verboten ist. Beim Judo beschränkt man sich rein auf das <b>Werfen</b>,
|
||||||
|
<b>fixieren am Boden</b>, <b>Hebeln</b> und <b>Hebeln</b> (nur für die älteren) des
|
||||||
|
Partners/Gegners.
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #extra>
|
||||||
|
<v-carousel
|
||||||
|
show-arrows="hover"
|
||||||
|
hide-delimiter-background
|
||||||
|
class="rounded-lg"
|
||||||
|
cycle
|
||||||
|
interval="8000"
|
||||||
|
>
|
||||||
|
<carousel-item-with-title text="Werfen" image="/static/images/whatsJudo/throw.jpg" />
|
||||||
|
<carousel-item-with-title
|
||||||
|
text="fixieren am Boden"
|
||||||
|
image="/static/images/whatsJudo/hold.jpg"
|
||||||
|
/>
|
||||||
|
<carousel-item-with-title
|
||||||
|
text="Trainieren sämtlicher Muskelgruppen"
|
||||||
|
image="/static/images/whatsJudo/stabil.jpg"
|
||||||
|
/>
|
||||||
|
</v-carousel>
|
||||||
|
</template>
|
||||||
|
</home-entrie>
|
||||||
|
<home-entrie
|
||||||
|
title="Unsere Werte beim Judo"
|
||||||
|
extra-beside-text
|
||||||
|
:extra-width-percent="50"
|
||||||
|
title-in-split-right
|
||||||
|
>
|
||||||
|
<template #text>
|
||||||
|
Zu dem Spielen beim Judo die Werte wie <b>Respekt</b>, <b>Disziplin</b> und
|
||||||
|
<b>Selbstbehauptung</b> eine große Rolle und so werden auch diese Werte bei uns
|
||||||
|
gefördert.
|
||||||
|
</template>
|
||||||
|
<template #extra>
|
||||||
|
<v-timeline align="start" side="end">
|
||||||
|
<v-timeline-item dot-color="blue">
|
||||||
|
<h6 class="text-h6">Respekt</h6>
|
||||||
|
</v-timeline-item>
|
||||||
|
<v-timeline-item dot-color="green">
|
||||||
|
<h6 class="text-h6">Selbstbehauptung</h6>
|
||||||
|
</v-timeline-item>
|
||||||
|
<v-timeline-item dot-color="orange">
|
||||||
|
<h6 class="text-h6">Disziplin</h6>
|
||||||
|
</v-timeline-item>
|
||||||
|
</v-timeline>
|
||||||
|
</template>
|
||||||
|
</home-entrie>
|
||||||
|
|
||||||
|
<home-entrie title="Und sonst so?"></home-entrie>
|
||||||
|
|
||||||
|
<responsive-tabs-or-list :items="tabItems">
|
||||||
|
<template #1>
|
||||||
|
<home-entrie-with-image-preset
|
||||||
|
title="Nikolausturnier"
|
||||||
|
image="/static/images/nikolausturnier/total.png"
|
||||||
|
>
|
||||||
|
<template #text>
|
||||||
|
Ein Highlight im Jahreskalender ist unser alljährliches Nikolausturnier, bei dem
|
||||||
|
Judoka aller Altersgruppen zusammenkommen, um ihre Fähigkeiten zu messen und
|
||||||
|
gemeinsam Spaß zu haben.
|
||||||
|
</template>
|
||||||
|
</home-entrie-with-image-preset>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #2>
|
||||||
|
<home-entrie-with-image-preset
|
||||||
|
title="Wettkämpfe"
|
||||||
|
image="/static/images/wettkampf/total.jpg"
|
||||||
|
>
|
||||||
|
<template #text>
|
||||||
|
Für diejenigen, die sich gerne messen wollen, bieten wir die Teilnahme an
|
||||||
|
Wettkämpfen auf verschiedenen Ebenen an – von lokalen Turnieren bis hin zu
|
||||||
|
nationalen Meisterschaften.
|
||||||
|
</template>
|
||||||
|
</home-entrie-with-image-preset>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #3>
|
||||||
|
<home-entrie-with-image-preset
|
||||||
|
title="Kinoabende"
|
||||||
|
image="/static/images/kinoabend/total.jpg"
|
||||||
|
>
|
||||||
|
<template #text>
|
||||||
|
Bei unseren Kinoabenden schauen wir in lockerer Runde ausgewählte Filme — nicht nur
|
||||||
|
judo‑bezogene, sondern auch beliebte Klassiker und aktuelle Hits. Gemeinsam
|
||||||
|
entspannen, Snacks teilen und über Filme und Sport plaudern.
|
||||||
|
</template>
|
||||||
|
</home-entrie-with-image-preset>
|
||||||
|
</template>
|
||||||
|
</responsive-tabs-or-list>
|
||||||
|
</v-container>
|
||||||
|
</main>
|
||||||
|
</v-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
9
GUI/src/routes/authentication/Login.vue
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<v-container class="">
|
||||||
|
<h1>Potenzieller Login</h1>
|
||||||
|
</v-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped></style>
|
||||||
@@ -15,4 +15,7 @@ export default defineConfig({
|
|||||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
build: {
|
||||||
|
outDir: "../wwwroot"
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
190
README.md
@@ -1 +1,189 @@
|
|||||||
# JudoWeb
|
# JudoWeb
|
||||||
|
|
||||||
|
Eine moderne Webanwendung für das Judoteam Stadtlohn - mit Vereinspräsentation und Verwaltungssystem.
|
||||||
|
|
||||||
|
## Projektbeschreibung
|
||||||
|
|
||||||
|
JudoWeb ist eine Full-Stack-Webanwendung, die zwei Hauptzwecke erfüllt:
|
||||||
|
|
||||||
|
- **Öffentliche Vereinswebsite**: Präsentation des Judoclubs mit Informationen zu Judo, Vereinswerten (Respekt, Disziplin, Selbstbehauptung) und Veranstaltungen
|
||||||
|
- **Verwaltungssystem**: Benutzerverwaltung, Registrierung und Altersgruppenmanagement
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- Responsive Homepage mit Bildkarussell (Würfe, Haltetechniken, Stabilitätstraining)
|
||||||
|
- Veranstaltungsübersicht (Nikolausturnier, Wettkämpfe, Kinoabende)
|
||||||
|
- Benutzerauthentifizierung mit Registrierungsschlüsseln
|
||||||
|
- Altersgruppenmanagement
|
||||||
|
- Dark/Light Theme-Umschaltung
|
||||||
|
- Mobile-optimiertes Design
|
||||||
|
|
||||||
|
## Technologie-Stack
|
||||||
|
|
||||||
|
### Backend
|
||||||
|
- **.NET 9.0** mit ASP.NET Core Web API
|
||||||
|
- **Entity Framework Core 9** - ORM
|
||||||
|
- **ASP.NET Core Identity** - Authentifizierung
|
||||||
|
- **Swagger/Swashbuckle** - API-Dokumentation
|
||||||
|
|
||||||
|
### Frontend
|
||||||
|
- **Vue.js 3** mit TypeScript
|
||||||
|
- **Vite** - Build-Tool und Entwicklungsserver
|
||||||
|
- **Vuetify 3** - Material Design Komponenten
|
||||||
|
- **Vue Router** - Client-seitiges Routing
|
||||||
|
|
||||||
|
### Datenbank
|
||||||
|
- **SQLite** (Entwicklung)
|
||||||
|
- **PostgreSQL** (Produktion)
|
||||||
|
|
||||||
|
### Zusätzliche Services
|
||||||
|
- **Plunk** - E-Mail-Versand
|
||||||
|
|
||||||
|
## Projektstruktur
|
||||||
|
|
||||||
|
```
|
||||||
|
JudoWeb/
|
||||||
|
├── API/ # Backend (.NET)
|
||||||
|
│ ├── Controllers/ # API-Endpunkte
|
||||||
|
│ │ ├── AgeGroupController.cs
|
||||||
|
│ │ ├── AuthController.cs
|
||||||
|
│ │ └── RegistrationKeyController.cs
|
||||||
|
│ ├── Models/
|
||||||
|
│ │ ├── Internal/ # Domain-Modelle
|
||||||
|
│ │ └── Outgoing/ # DTOs
|
||||||
|
│ ├── Database/ # EF Core Kontext
|
||||||
|
│ ├── Repository/ # Datenzugriff
|
||||||
|
│ ├── Services/ # Business-Logik
|
||||||
|
│ └── Migrations/ # Datenbank-Migrationen
|
||||||
|
│
|
||||||
|
├── GUI/ # Frontend (Vue.js)
|
||||||
|
│ ├── src/
|
||||||
|
│ │ ├── routes/ # Seiten-Komponenten
|
||||||
|
│ │ ├── components/ # Wiederverwendbare Komponenten
|
||||||
|
│ │ ├── plugins/ # Vue-Plugins
|
||||||
|
│ │ └── router/ # Router-Konfiguration
|
||||||
|
│ └── public/static/ # Statische Assets
|
||||||
|
│
|
||||||
|
└── JudoWeb.sln # Visual Studio Solution
|
||||||
|
```
|
||||||
|
|
||||||
|
## Installation & Setup
|
||||||
|
|
||||||
|
### Voraussetzungen
|
||||||
|
|
||||||
|
- [.NET 9.0 SDK](https://dotnet.microsoft.com/download)
|
||||||
|
- [Node.js](https://nodejs.org/) (LTS-Version empfohlen)
|
||||||
|
- Optional: [Visual Studio 2022](https://visualstudio.microsoft.com/) oder [VS Code](https://code.visualstudio.com/)
|
||||||
|
|
||||||
|
### Frontend einrichten
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd GUI
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Backend einrichten
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd API
|
||||||
|
dotnet restore
|
||||||
|
```
|
||||||
|
|
||||||
|
## Entwicklung starten
|
||||||
|
|
||||||
|
### Frontend (mit Hot-Reload)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd GUI
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Der Entwicklungsserver startet unter `http://localhost:5173`
|
||||||
|
|
||||||
|
### Backend
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd API
|
||||||
|
dotnet run
|
||||||
|
```
|
||||||
|
|
||||||
|
Die API ist verfügbar unter:
|
||||||
|
- HTTP: `http://localhost:5246`
|
||||||
|
- HTTPS: `https://localhost:7248`
|
||||||
|
- Swagger UI: `https://localhost:7248/swagger`
|
||||||
|
|
||||||
|
## Verfügbare Scripts
|
||||||
|
|
||||||
|
### Frontend (GUI/)
|
||||||
|
|
||||||
|
| Befehl | Beschreibung |
|
||||||
|
|--------|--------------|
|
||||||
|
| `npm run dev` | Startet Entwicklungsserver |
|
||||||
|
| `npm run build` | Erstellt Produktions-Build |
|
||||||
|
| `npm run test:unit` | Führt Unit-Tests aus |
|
||||||
|
| `npm run type-check` | TypeScript-Typprüfung |
|
||||||
|
| `npm run format` | Code-Formatierung mit Prettier |
|
||||||
|
|
||||||
|
### Backend (API/)
|
||||||
|
|
||||||
|
| Befehl | Beschreibung |
|
||||||
|
|--------|--------------|
|
||||||
|
| `dotnet run` | Startet Entwicklungsserver |
|
||||||
|
| `dotnet build` | Kompiliert das Projekt |
|
||||||
|
| `dotnet publish -c Release` | Erstellt Produktions-Build |
|
||||||
|
| `dotnet ef database update` | Wendet Migrationen an |
|
||||||
|
|
||||||
|
## Datenbank-Konfiguration
|
||||||
|
|
||||||
|
Die Anwendung wählt automatisch die Datenbank basierend auf der Konfiguration:
|
||||||
|
|
||||||
|
1. **PostgreSQL** (Produktion): Wenn `PostgresConnection` in `appsettings.json` oder als Umgebungsvariable gesetzt ist
|
||||||
|
2. **SQLite** (Entwicklung): Standardmäßig wird `app.db` verwendet
|
||||||
|
|
||||||
|
Datenbank-Migrationen werden beim Anwendungsstart automatisch ausgeführt.
|
||||||
|
|
||||||
|
### Beispiel PostgreSQL-Konfiguration
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ConnectionStrings": {
|
||||||
|
"PostgresConnection": "Host=localhost;Database=judoweb;Username=user;Password=pass"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## API-Endpunkte
|
||||||
|
|
||||||
|
| Endpunkt | Beschreibung |
|
||||||
|
|----------|--------------|
|
||||||
|
| `GET /api/ageGroups` | Alle Altersgruppen abrufen |
|
||||||
|
| `POST /api/ageGroups` | Neue Altersgruppe erstellen |
|
||||||
|
| `PUT /api/ageGroups/{id}` | Altersgruppe aktualisieren |
|
||||||
|
| `DELETE /api/ageGroups/{id}` | Altersgruppe löschen |
|
||||||
|
| `POST /api/account/register` | Benutzer registrieren |
|
||||||
|
| `POST /api/account/login` | Benutzer anmelden |
|
||||||
|
| `GET /api/registrationKey` | Registrierungsschlüssel abrufen |
|
||||||
|
|
||||||
|
Vollständige API-Dokumentation ist über Swagger UI verfügbar.
|
||||||
|
|
||||||
|
## Produktion
|
||||||
|
|
||||||
|
### Frontend bauen
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd GUI
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
Das Build wird in `../wwwroot/` ausgegeben und vom Backend als statische Dateien serviert.
|
||||||
|
|
||||||
|
### Backend publizieren
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd API
|
||||||
|
dotnet publish -c Release -o ./publish
|
||||||
|
```
|
||||||
|
|
||||||
|
## Lizenz
|
||||||
|
|
||||||
|
Dieses Projekt ist proprietär und gehört dem Judoteam Stadtlohn.
|
||||||
|
|||||||