215 lines
6.0 KiB
C#
215 lines
6.0 KiB
C#
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
|
using Microsoft.IdentityModel.Tokens;
|
|
using System.Security.Claims;
|
|
|
|
var builder = WebApplication.CreateBuilder(args);
|
|
|
|
// Add CORS
|
|
builder.Services.AddCors(options =>
|
|
{
|
|
options.AddPolicy("AllowFrontend", policy =>
|
|
{
|
|
policy.WithOrigins("http://localhost:3030")
|
|
.AllowAnyHeader()
|
|
.AllowAnyMethod()
|
|
.AllowCredentials();
|
|
});
|
|
});
|
|
|
|
// Add SQLite Database
|
|
builder.Services.AddDbContext<TodoDbContext>(options =>
|
|
options.UseSqlite(builder.Configuration.GetConnectionString("DefaultConnection")));
|
|
|
|
// Add Keycloak Authentication
|
|
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
|
.AddJwtBearer(options =>
|
|
{
|
|
options.Authority = "https://terminus.bluelake.cloud/realms/dalex-immo-dev";
|
|
options.RequireHttpsMetadata = true;
|
|
options.Audience = "dalex-proto";
|
|
options.TokenValidationParameters = new TokenValidationParameters
|
|
{
|
|
ValidateIssuer = true,
|
|
ValidateAudience = false,
|
|
ValidateLifetime = true,
|
|
ValidateIssuerSigningKey = true
|
|
};
|
|
});
|
|
|
|
builder.Services.AddAuthorization();
|
|
|
|
var app = builder.Build();
|
|
|
|
// Ensure database is created
|
|
using (var scope = app.Services.CreateScope())
|
|
{
|
|
var db = scope.ServiceProvider.GetRequiredService<TodoDbContext>();
|
|
db.Database.EnsureCreated();
|
|
}
|
|
|
|
app.UseCors("AllowFrontend");
|
|
app.UseAuthentication();
|
|
app.UseAuthorization();
|
|
|
|
// Get all todos for current user
|
|
app.MapGet("/api/todos", async (TodoDbContext db, HttpContext context) =>
|
|
{
|
|
var userId = context.User.FindFirst(ClaimTypes.NameIdentifier)?.Value
|
|
?? context.User.FindFirst("sub")?.Value;
|
|
|
|
if (string.IsNullOrEmpty(userId))
|
|
return Results.Unauthorized();
|
|
|
|
var todos = await db.Todos
|
|
.Where(t => t.UserId == userId)
|
|
.OrderByDescending(t => !t.IsCompleted)
|
|
.ThenByDescending(t => t.CreatedAt)
|
|
.ToListAsync();
|
|
|
|
return Results.Ok(todos);
|
|
})
|
|
.RequireAuthorization();
|
|
|
|
// Get recent todos (exclude old completed ones)
|
|
app.MapGet("/api/todos/recent", async (TodoDbContext db, HttpContext context) =>
|
|
{
|
|
var userId = context.User.FindFirst(ClaimTypes.NameIdentifier)?.Value
|
|
?? context.User.FindFirst("sub")?.Value;
|
|
|
|
if (string.IsNullOrEmpty(userId))
|
|
return Results.Unauthorized();
|
|
|
|
var oneWeekAgo = DateTime.UtcNow.AddDays(-7);
|
|
|
|
var todos = await db.Todos
|
|
.Where(t => t.UserId == userId &&
|
|
(!t.IsCompleted || (t.CompletedAt.HasValue && t.CompletedAt.Value > oneWeekAgo)))
|
|
.OrderBy(t => t.IsCompleted)
|
|
.ThenBy(t => t.CreatedAt)
|
|
.ToListAsync();
|
|
|
|
return Results.Ok(todos);
|
|
})
|
|
.RequireAuthorization();
|
|
|
|
// Create a new todo
|
|
app.MapPost("/api/todos", async (TodoDbContext db, HttpContext context, TodoCreateDto dto) =>
|
|
{
|
|
var userId = context.User.FindFirst(ClaimTypes.NameIdentifier)?.Value
|
|
?? context.User.FindFirst("sub")?.Value;
|
|
|
|
if (string.IsNullOrEmpty(userId))
|
|
return Results.Unauthorized();
|
|
|
|
var todo = new Todo
|
|
{
|
|
UserId = userId,
|
|
Title = dto.Title,
|
|
Description = dto.Description,
|
|
CreatedAt = DateTime.UtcNow,
|
|
IsCompleted = false
|
|
};
|
|
|
|
db.Todos.Add(todo);
|
|
await db.SaveChangesAsync();
|
|
|
|
return Results.Created($"/api/todos/{todo.Id}", todo);
|
|
})
|
|
.RequireAuthorization();
|
|
|
|
// Update a todo
|
|
app.MapPut("/api/todos/{id}", async (TodoDbContext db, HttpContext context, int id, TodoUpdateDto dto) =>
|
|
{
|
|
var userId = context.User.FindFirst(ClaimTypes.NameIdentifier)?.Value
|
|
?? context.User.FindFirst("sub")?.Value;
|
|
|
|
if (string.IsNullOrEmpty(userId))
|
|
return Results.Unauthorized();
|
|
|
|
var todo = await db.Todos.FindAsync(id);
|
|
|
|
if (todo == null || todo.UserId != userId)
|
|
return Results.NotFound();
|
|
|
|
todo.Title = dto.Title ?? todo.Title;
|
|
todo.Description = dto.Description ?? todo.Description;
|
|
|
|
if (dto.IsCompleted.HasValue && dto.IsCompleted.Value != todo.IsCompleted)
|
|
{
|
|
todo.IsCompleted = dto.IsCompleted.Value;
|
|
todo.CompletedAt = dto.IsCompleted.Value ? DateTime.UtcNow : null;
|
|
}
|
|
|
|
await db.SaveChangesAsync();
|
|
|
|
return Results.Ok(todo);
|
|
})
|
|
.RequireAuthorization();
|
|
|
|
// Delete a todo
|
|
app.MapDelete("/api/todos/{id}", async (TodoDbContext db, HttpContext context, int id) =>
|
|
{
|
|
var userId = context.User.FindFirst(ClaimTypes.NameIdentifier)?.Value
|
|
?? context.User.FindFirst("sub")?.Value;
|
|
|
|
if (string.IsNullOrEmpty(userId))
|
|
return Results.Unauthorized();
|
|
|
|
var todo = await db.Todos.FindAsync(id);
|
|
|
|
if (todo == null || todo.UserId != userId)
|
|
return Results.NotFound();
|
|
|
|
db.Todos.Remove(todo);
|
|
await db.SaveChangesAsync();
|
|
|
|
return Results.NoContent();
|
|
})
|
|
.RequireAuthorization();
|
|
|
|
app.Run();
|
|
|
|
// Models
|
|
public class Todo
|
|
{
|
|
public int Id { get; set; }
|
|
public string UserId { get; set; } = string.Empty;
|
|
public string Title { get; set; } = string.Empty;
|
|
public string? Description { get; set; }
|
|
public DateTime CreatedAt { get; set; }
|
|
public DateTime? CompletedAt { get; set; }
|
|
public bool IsCompleted { get; set; }
|
|
}
|
|
|
|
public class TodoCreateDto
|
|
{
|
|
public string Title { get; set; } = string.Empty;
|
|
public string? Description { get; set; }
|
|
}
|
|
|
|
public class TodoUpdateDto
|
|
{
|
|
public string? Title { get; set; }
|
|
public string? Description { get; set; }
|
|
public bool? IsCompleted { get; set; }
|
|
}
|
|
|
|
public class TodoDbContext : DbContext
|
|
{
|
|
public TodoDbContext(DbContextOptions<TodoDbContext> options) : base(options) { }
|
|
|
|
public DbSet<Todo> Todos => Set<Todo>();
|
|
|
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
|
{
|
|
modelBuilder.Entity<Todo>(entity =>
|
|
{
|
|
entity.HasKey(e => e.Id);
|
|
entity.Property(e => e.UserId).IsRequired();
|
|
entity.Property(e => e.Title).IsRequired();
|
|
entity.HasIndex(e => e.UserId);
|
|
});
|
|
}
|
|
}
|