feature/new-raceplanner-app #1

Merged
MasterMito merged 12 commits from feature/new-raceplanner-app into main 2026-04-03 21:47:59 +02:00
6 changed files with 224 additions and 12 deletions
Showing only changes of commit b6962e1024 - Show all commits
+92
View File
@@ -0,0 +1,92 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using RacePlannerApi.Data;
using RacePlannerApi.DTOs;
using RacePlannerApi.Models;
using RacePlannerApi.Services;
namespace RacePlannerApi.Controllers;
[ApiController]
[Route("api/[controller]")]
public class AuthController : ControllerBase
{
private readonly RacePlannerDbContext _context;
private readonly JwtTokenService _jwtService;
public AuthController(RacePlannerDbContext context, JwtTokenService jwtService)
{
_context = context;
_jwtService = jwtService;
}
[HttpPost("register")]
public async Task<ActionResult<AuthResponse>> Register(RegisterRequest request)
{
// Check if email already exists
if (await _context.Users.AnyAsync(u => u.Email == request.Email))
{
return Conflict(new { error = "Email already registered" });
}
// Create new user
var user = new User
{
Email = request.Email,
PasswordHash = BCrypt.Net.BCrypt.HashPassword(request.Password),
Name = request.Name,
Role = request.Role
};
_context.Users.Add(user);
await _context.SaveChangesAsync();
// Generate token
var token = _jwtService.GenerateToken(user);
return Ok(new AuthResponse
{
Token = token,
User = new UserDto
{
Id = user.Id,
Email = user.Email,
Name = user.Name,
Role = user.Role.ToString()
}
});
}
[HttpPost("login")]
public async Task<ActionResult<AuthResponse>> Login(LoginRequest request)
{
// Find user by email
var user = await _context.Users.FirstOrDefaultAsync(u => u.Email == request.Email);
if (user == null)
{
return Unauthorized(new { error = "Invalid credentials" });
}
// Verify password
if (!BCrypt.Net.BCrypt.Verify(request.Password, user.PasswordHash))
{
return Unauthorized(new { error = "Invalid credentials" });
}
// Generate token
var token = _jwtService.GenerateToken(user);
return Ok(new AuthResponse
{
Token = token,
User = new UserDto
{
Id = user.Id,
Email = user.Email,
Name = user.Name,
Role = user.Role.ToString()
}
});
}
}
+45
View File
@@ -0,0 +1,45 @@
using System.ComponentModel.DataAnnotations;
using RacePlannerApi.Models;
namespace RacePlannerApi.DTOs;
public class RegisterRequest
{
[Required]
[EmailAddress]
public string Email { get; set; } = string.Empty;
[Required]
[MinLength(8)]
public string Password { get; set; } = string.Empty;
[Required]
[MinLength(2)]
public string Name { get; set; } = string.Empty;
public UserRole Role { get; set; } = UserRole.Participant;
}
public class LoginRequest
{
[Required]
[EmailAddress]
public string Email { get; set; } = string.Empty;
[Required]
public string Password { get; set; } = string.Empty;
}
public class AuthResponse
{
public string Token { get; set; } = string.Empty;
public UserDto User { get; set; } = null!;
}
public class UserDto
{
public Guid Id { get; set; }
public string Email { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public string Role { get; set; } = string.Empty;
}
+29
View File
@@ -1,5 +1,9 @@
using System.Text;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using RacePlannerApi.Data; using RacePlannerApi.Data;
using RacePlannerApi.Services;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
@@ -8,6 +12,29 @@ builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer(); builder.Services.AddEndpointsApiExplorer();
builder.Services.AddOpenApi(); builder.Services.AddOpenApi();
// Configure JWT Authentication
var jwtKey = builder.Configuration["Jwt:Key"] ?? "your-secret-key-here-minimum-32-characters-long";
var jwtIssuer = builder.Configuration["Jwt:Issuer"] ?? "RacePlannerApi";
var jwtAudience = builder.Configuration["Jwt:Audience"] ?? "RacePlannerClient";
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = jwtIssuer,
ValidAudience = jwtAudience,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtKey))
};
});
// Register services
builder.Services.AddScoped<JwtTokenService>();
// Configure Entity Framework Core with PostgreSQL // Configure Entity Framework Core with PostgreSQL
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") var connectionString = builder.Configuration.GetConnectionString("DefaultConnection")
?? "Host=localhost;Database=RacePlanner;Username=postgres;Password=postgres"; ?? "Host=localhost;Database=RacePlanner;Username=postgres;Password=postgres";
@@ -40,6 +67,8 @@ app.UseHttpsRedirection();
// Apply CORS // Apply CORS
app.UseCors("AllowFrontend"); app.UseCors("AllowFrontend");
// Authentication & Authorization
app.UseAuthentication();
app.UseAuthorization(); app.UseAuthorization();
app.MapControllers(); app.MapControllers();
+3
View File
@@ -7,12 +7,15 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="BCrypt.Net-Next" Version="4.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.5" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.0" /> <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.5"> <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.1" /> <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.1" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.17.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>
+43
View File
@@ -0,0 +1,43 @@
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using Microsoft.IdentityModel.Tokens;
using RacePlannerApi.Models;
namespace RacePlannerApi.Services;
public class JwtTokenService
{
private readonly IConfiguration _configuration;
public JwtTokenService(IConfiguration configuration)
{
_configuration = configuration;
}
public string GenerateToken(User user)
{
var securityKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(_configuration["Jwt:Key"] ?? "your-secret-key-here-minimum-32-characters-long"));
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub, user.Id.ToString()),
new Claim(JwtRegisteredClaimNames.Email, user.Email),
new Claim(JwtRegisteredClaimNames.Name, user.Name),
new Claim("role", user.Role.ToString()),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
var token = new JwtSecurityToken(
issuer: _configuration["Jwt:Issuer"] ?? "RacePlannerApi",
audience: _configuration["Jwt:Audience"] ?? "RacePlannerClient",
claims: claims,
expires: DateTime.Now.AddHours(24),
signingCredentials: credentials
);
return new JwtSecurityTokenHandler().WriteToken(token);
}
}
+12 -12
View File
@@ -1,20 +1,20 @@
## 1. Project Setup ## 1. Project Setup
- [ ] 1.1 Initialize project structure with backend and frontend directories - [x] 1.1 Initialize project structure with backend and frontend directories
- [ ] 1.2 Setup .NET backend (ASP.NET Core Web API with C#) - [x] 1.2 Setup .NET backend (ASP.NET Core Web API with C#)
- [ ] 1.3 Setup Next.js frontend with TypeScript using Bun runtime - [x] 1.3 Setup Next.js frontend with TypeScript using Bun runtime
- [ ] 1.4 Configure ESLint and Prettier for frontend - [x] 1.4 Configure ESLint and Prettier for frontend
- [ ] 1.5 Setup Entity Framework Core with PostgreSQL - [x] 1.5 Setup Entity Framework Core with PostgreSQL
- [ ] 1.6 Create initial database schema migration (EF Core) - [x] 1.6 Create initial database schema migration (EF Core)
## 2. Database Schema ## 2. Database Schema
- [ ] 2.1 Create users table with roles - [x] 2.1 Create users table with roles
- [ ] 2.2 Create events table with organizer foreign key - [x] 2.2 Create events table with organizer foreign key
- [ ] 2.3 Create registrations table with event and user foreign keys - [x] 2.3 Create registrations table with event and user foreign keys
- [ ] 2.4 Create payments table with registration foreign key - [x] 2.4 Create payments table with registration foreign key
- [ ] 2.5 Create announcements table with event foreign key - [x] 2.5 Create announcements table with event foreign key
- [ ] 2.6 Add database indexes for common queries - [x] 2.6 Add database indexes for common queries
## 3. User Authentication (user-auth) ## 3. User Authentication (user-auth)