feat: add delete functionality for exercises and routines

This commit is contained in:
2026-01-31 00:18:30 +01:00
parent 81d6b70673
commit 8300331276
5 changed files with 107 additions and 41 deletions

View File

@@ -79,6 +79,23 @@ internal static class ExerciseEndpoints
.WithSummary("Update exercise") .WithSummary("Update exercise")
.WithDescription("Updates the name of an exercise for the specified user."); .WithDescription("Updates the name of an exercise for the specified user.");
group.MapDelete("/users/{userId}/exercises/{exerciseId:int}", async (string userId, int exerciseId, AppDbContext db) =>
{
var user = await UserProvisioning.EnsureUserAsync(db, userId);
var exercise = await db.Exercises.FirstOrDefaultAsync(e => e.Id == exerciseId && e.UserId == user.Id);
if (exercise is null)
{
return Results.NotFound();
}
db.Exercises.Remove(exercise);
await db.SaveChangesAsync();
return Results.NoContent();
})
.WithSummary("Delete exercise")
.WithDescription("Deletes an exercise for the specified user.");
return group; return group;
} }
} }

View File

@@ -160,6 +160,23 @@ internal static class RoutineEndpoints
.WithSummary("Update routine") .WithSummary("Update routine")
.WithDescription("Updates routine metadata and exercise ordering."); .WithDescription("Updates routine metadata and exercise ordering.");
group.MapDelete("/users/{userId}/routines/{routineId:int}", async (string userId, int routineId, AppDbContext db) =>
{
var user = await UserProvisioning.EnsureUserAsync(db, userId);
var routine = await db.Routines.FirstOrDefaultAsync(r => r.Id == routineId && r.UserId == user.Id);
if (routine is null)
{
return Results.NotFound();
}
db.Routines.Remove(routine);
await db.SaveChangesAsync();
return Results.NoContent();
})
.WithSummary("Delete routine")
.WithDescription("Deletes a routine and its associated data for the specified user.");
return group; return group;
} }
} }

View File

@@ -50,7 +50,10 @@
else else
{ {
<div class="item-title">@exercise.Name</div> <div class="item-title">@exercise.Name</div>
<button class="ghost" @onclick="() => StartEdit(exercise)">Edit</button> <div class="actions">
<button class="ghost" @onclick="() => StartEdit(exercise)" aria-label="Edit exercise">✏️</button>
<button class="ghost" @onclick="() => DeleteExerciseAsync(exercise.Id)" aria-label="Delete exercise">🗑️</button>
</div>
} }
</div> </div>
} }
@@ -148,4 +151,10 @@
CancelEdit(); CancelEdit();
} }
private async Task DeleteExerciseAsync(int exerciseId)
{
await Api.DeleteExerciseAsync(UserContext.UserId, exerciseId);
ExerciseList.RemoveAll(e => e.Id == exerciseId);
}
} }

View File

@@ -25,6 +25,8 @@
</section> </section>
} }
@if (EditingRoutine is null)
{
@if (ShowCreateRoutine) @if (ShowCreateRoutine)
{ {
<section class="card"> <section class="card">
@@ -43,6 +45,8 @@
</section> </section>
} }
@if (ExerciseList.Count > 0 || RoutineList.Count > 0)
{
<section class="card"> <section class="card">
<h2>Your Routines</h2> <h2>Your Routines</h2>
@if (IsLoading) @if (IsLoading)
@@ -64,7 +68,8 @@
<div class="item-subtitle">@string.Join(" · ", routine.Exercises.Select(e => e.Name))</div> <div class="item-subtitle">@string.Join(" · ", routine.Exercises.Select(e => e.Name))</div>
</div> </div>
<div class="actions"> <div class="actions">
<button class="ghost" @onclick="() => StartEdit(routine)">Edit</button> <button class="ghost" @onclick="() => StartEdit(routine)" aria-label="Edit routine">✏️</button>
<button class="ghost" @onclick="() => DeleteRoutineAsync(routine.Id)" aria-label="Delete routine">🗑️</button>
<button class="primary" @onclick="() => StartRun(routine)">Start</button> <button class="primary" @onclick="() => StartRun(routine)">Start</button>
</div> </div>
</div> </div>
@@ -72,6 +77,8 @@
</div> </div>
} }
</section> </section>
}
}
@if (EditingRoutine is not null) @if (EditingRoutine is not null)
{ {
@@ -246,6 +253,12 @@
CancelEdit(); CancelEdit();
} }
private async Task DeleteRoutineAsync(int routineId)
{
await Api.DeleteRoutineAsync(UserContext.UserId, routineId);
RoutineList.RemoveAll(r => r.Id == routineId);
}
private async Task StartRun(RoutineDto routine) private async Task StartRun(RoutineDto routine)
{ {
ActiveRun = routine; ActiveRun = routine;

View File

@@ -37,6 +37,11 @@ public class ApiClient
return await response.Content.ReadFromJsonAsync<ExerciseDto>(); return await response.Content.ReadFromJsonAsync<ExerciseDto>();
} }
public async Task DeleteExerciseAsync(string userId, int exerciseId)
{
await _http.DeleteAsync($"/api/users/{userId}/exercises/{exerciseId}");
}
public async Task<List<RoutineDto>> GetRoutinesAsync(string userId) public async Task<List<RoutineDto>> GetRoutinesAsync(string userId)
{ {
return await _http.GetFromJsonAsync<List<RoutineDto>>($"/api/users/{userId}/routines") ?? new List<RoutineDto>(); return await _http.GetFromJsonAsync<List<RoutineDto>>($"/api/users/{userId}/routines") ?? new List<RoutineDto>();
@@ -59,6 +64,11 @@ public class ApiClient
return await response.Content.ReadFromJsonAsync<RoutineDto>(); return await response.Content.ReadFromJsonAsync<RoutineDto>();
} }
public async Task DeleteRoutineAsync(string userId, int routineId)
{
await _http.DeleteAsync($"/api/users/{userId}/routines/{routineId}");
}
public async Task<RoutineRunSummaryDto> GetLastRunAsync(string userId, int routineId) public async Task<RoutineRunSummaryDto> GetLastRunAsync(string userId, int routineId)
{ {
return await _http.GetFromJsonAsync<RoutineRunSummaryDto>($"/api/users/{userId}/routines/{routineId}/last-run") return await _http.GetFromJsonAsync<RoutineRunSummaryDto>($"/api/users/{userId}/routines/{routineId}/last-run")