Refactor API endpoints into modules

This commit is contained in:
2026-01-30 23:59:53 +01:00
parent ba9ba119c2
commit e35f45dc4d
7 changed files with 468 additions and 290 deletions

View File

@@ -1,10 +1,5 @@
using System.Text.RegularExpressions;
using ASTRAIN.Api.Data;
using ASTRAIN.Api.Services;
using ASTRAIN.Shared.Dtos;
using ASTRAIN.Shared.Models;
using ASTRAIN.Shared.Requests;
using ASTRAIN.Shared.Responses;
using ASTRAIN.Api.Endpoints;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
@@ -14,9 +9,13 @@ builder.Services.AddCors(options =>
{
options.AddDefaultPolicy(policy =>
{
policy.WithOrigins("http://localhost:5014", "https://localhost:7252")
policy.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod();
// policy.WithOrigins("http://localhost:5016", "http://:5016", "https://localhost:7252")
// .AllowAnyHeader()
// .AllowAnyMethod();
});
});
builder.Services.AddDbContext<AppDbContext>(options =>
@@ -33,7 +32,7 @@ if (app.Environment.IsDevelopment())
app.MapOpenApi();
}
app.UseHttpsRedirection();
// app.UseHttpsRedirection();
app.UseCors();
app.UseDefaultFiles();
app.UseStaticFiles();
@@ -46,289 +45,12 @@ using (var scope = app.Services.CreateScope())
var api = app.MapGroup("/api");
api.MapGet("/health", () => Results.Ok(new { status = "ok" }));
api.MapGet("/users/ensure", async (string? userId, AppDbContext db) =>
{
var user = await EnsureUserAsync(db, userId);
return Results.Ok(new EnsureUserResponse(user.Id));
});
api.MapGet("/users/{userId}", async (string userId, AppDbContext db) =>
{
var user = await EnsureUserAsync(db, userId);
return Results.Ok(new EnsureUserResponse(user.Id));
});
api.MapGet("/users/{userId}/exercises", async (string userId, AppDbContext db) =>
{
var user = await EnsureUserAsync(db, userId);
var items = await db.Exercises
.Where(e => e.UserId == user.Id)
.OrderBy(e => e.Name)
.Select(e => new ExerciseDto(e.Id, e.Name))
.ToListAsync();
return Results.Ok(items);
});
api.MapPost("/users/{userId}/exercises", async (string userId, ExerciseUpsertRequest request, AppDbContext db) =>
{
var user = await EnsureUserAsync(db, userId);
if (string.IsNullOrWhiteSpace(request.Name))
{
return Results.BadRequest("Name is required.");
}
var exercise = new Exercise
{
Name = request.Name.Trim(),
UserId = user.Id
};
db.Exercises.Add(exercise);
await db.SaveChangesAsync();
return Results.Ok(new ExerciseDto(exercise.Id, exercise.Name));
});
api.MapPut("/users/{userId}/exercises/{exerciseId:int}", async (string userId, int exerciseId, ExerciseUpsertRequest request, AppDbContext db) =>
{
var user = await EnsureUserAsync(db, userId);
var exercise = await db.Exercises.FirstOrDefaultAsync(e => e.Id == exerciseId && e.UserId == user.Id);
if (exercise is null)
{
return Results.NotFound();
}
if (string.IsNullOrWhiteSpace(request.Name))
{
return Results.BadRequest("Name is required.");
}
exercise.Name = request.Name.Trim();
await db.SaveChangesAsync();
return Results.Ok(new ExerciseDto(exercise.Id, exercise.Name));
});
api.MapGet("/users/{userId}/routines", async (string userId, AppDbContext db) =>
{
var user = await EnsureUserAsync(db, userId);
var routines = await db.Routines
.Include(r => r.Exercises)
.ThenInclude(re => re.Exercise)
.Where(r => r.UserId == user.Id)
.OrderBy(r => r.Name)
.ToListAsync();
var payload = routines.Select(r => new RoutineDto(
r.Id,
r.Name,
r.Exercises
.OrderBy(re => re.Order)
.Select(re => new RoutineExerciseDto(re.ExerciseId, re.Exercise?.Name ?? string.Empty, re.Order))
.ToList()
));
return Results.Ok(payload);
});
api.MapGet("/users/{userId}/routines/{routineId:int}", async (string userId, int routineId, AppDbContext db) =>
{
var user = await EnsureUserAsync(db, userId);
var routine = await db.Routines
.Include(r => r.Exercises)
.ThenInclude(re => re.Exercise)
.FirstOrDefaultAsync(r => r.Id == routineId && r.UserId == user.Id);
if (routine is null)
{
return Results.NotFound();
}
var payload = new RoutineDto(
routine.Id,
routine.Name,
routine.Exercises
.OrderBy(re => re.Order)
.Select(re => new RoutineExerciseDto(re.ExerciseId, re.Exercise?.Name ?? string.Empty, re.Order))
.ToList());
return Results.Ok(payload);
});
api.MapPost("/users/{userId}/routines", async (string userId, RoutineUpsertRequest request, AppDbContext db) =>
{
var user = await EnsureUserAsync(db, userId);
if (string.IsNullOrWhiteSpace(request.Name))
{
return Results.BadRequest("Name is required.");
}
var routine = new Routine
{
Name = request.Name.Trim(),
UserId = user.Id
};
var exercises = await db.Exercises
.Where(e => e.UserId == user.Id && request.ExerciseIds.Contains(e.Id))
.ToListAsync();
routine.Exercises = request.ExerciseIds
.Select((exerciseId, index) => new RoutineExercise
{
ExerciseId = exerciseId,
Order = index
})
.ToList();
db.Routines.Add(routine);
await db.SaveChangesAsync();
var dto = new RoutineDto(
routine.Id,
routine.Name,
routine.Exercises
.Select((re, index) => new RoutineExerciseDto(re.ExerciseId, exercises.FirstOrDefault(e => e.Id == re.ExerciseId)?.Name ?? string.Empty, index))
.ToList());
return Results.Ok(dto);
});
api.MapPut("/users/{userId}/routines/{routineId:int}", async (string userId, int routineId, RoutineUpsertRequest request, AppDbContext db) =>
{
var user = await EnsureUserAsync(db, userId);
var routine = await db.Routines
.Include(r => r.Exercises)
.FirstOrDefaultAsync(r => r.Id == routineId && r.UserId == user.Id);
if (routine is null)
{
return Results.NotFound();
}
if (string.IsNullOrWhiteSpace(request.Name))
{
return Results.BadRequest("Name is required.");
}
routine.Name = request.Name.Trim();
routine.Exercises.Clear();
foreach (var exerciseId in request.ExerciseIds)
{
routine.Exercises.Add(new RoutineExercise
{
ExerciseId = exerciseId,
Order = routine.Exercises.Count
});
}
await db.SaveChangesAsync();
var exercises = await db.Exercises
.Where(e => e.UserId == user.Id && request.ExerciseIds.Contains(e.Id))
.ToListAsync();
var dto = new RoutineDto(
routine.Id,
routine.Name,
routine.Exercises
.OrderBy(re => re.Order)
.Select(re => new RoutineExerciseDto(re.ExerciseId, exercises.FirstOrDefault(e => e.Id == re.ExerciseId)?.Name ?? string.Empty, re.Order))
.ToList());
return Results.Ok(dto);
});
api.MapGet("/users/{userId}/routines/{routineId:int}/last-run", async (string userId, int routineId, AppDbContext db) =>
{
var user = await EnsureUserAsync(db, userId);
var lastRun = await db.RoutineRuns
.Include(rr => rr.Entries)
.Where(rr => rr.UserId == user.Id && rr.RoutineId == routineId)
.OrderByDescending(rr => rr.PerformedAt)
.FirstOrDefaultAsync();
if (lastRun is null)
{
return Results.Ok(new RoutineRunSummaryDto(DateTime.MinValue, new List<RoutineRunEntryDto>()));
}
var summary = new RoutineRunSummaryDto(
lastRun.PerformedAt,
lastRun.Entries
.Select(e => new RoutineRunEntryDto(e.ExerciseId, e.Weight, e.Completed))
.ToList());
return Results.Ok(summary);
});
api.MapPost("/users/{userId}/routines/{routineId:int}/runs", async (string userId, int routineId, RoutineRunRequest request, AppDbContext db) =>
{
var user = await EnsureUserAsync(db, userId);
var routine = await db.Routines.FirstOrDefaultAsync(r => r.Id == routineId && r.UserId == user.Id);
if (routine is null)
{
return Results.NotFound();
}
var run = new RoutineRun
{
RoutineId = routine.Id,
UserId = user.Id,
PerformedAt = DateTime.UtcNow,
Entries = request.Entries.Select(entry => new RoutineRunEntry
{
ExerciseId = entry.ExerciseId,
Weight = entry.Weight,
Completed = entry.Completed
}).ToList()
};
db.RoutineRuns.Add(run);
await db.SaveChangesAsync();
return Results.Ok(new { run.Id, run.PerformedAt });
});
api.MapHealthEndpoints();
api.MapUserEndpoints();
api.MapExerciseEndpoints();
api.MapRoutineEndpoints();
api.MapRoutineRunEndpoints();
app.MapFallbackToFile("index.html");
app.Run();
static async Task<User> EnsureUserAsync(AppDbContext db, string? userId)
{
if (!string.IsNullOrWhiteSpace(userId) && IsValidUserId(userId))
{
var existing = await db.Users.FirstOrDefaultAsync(u => u.Id == userId);
if (existing is not null)
{
return existing;
}
var created = new User { Id = userId };
db.Users.Add(created);
await db.SaveChangesAsync();
return created;
}
while (true)
{
var newId = UserKeyGenerator.Generate(8);
var exists = await db.Users.AnyAsync(u => u.Id == newId);
if (exists)
{
continue;
}
var user = new User { Id = newId };
db.Users.Add(user);
await db.SaveChangesAsync();
return user;
}
}
static bool IsValidUserId(string userId)
{
return Regex.IsMatch(userId, "^[A-Za-z0-9]{8}$");
}