fix: address review findings — error handling, migration safety, CI audit
Backend: - migration 003: apply pixel→grid transform per-element (CASE WHEN > 50) instead of per-row, preventing double-conversion of mixed-scale rooms; skip empty arrays via json_array_length guard to avoid NULL assignment - attendance.rs: log layout JSON parse errors instead of silently swallowing them with .ok() - tutors.rs: check rows_affected() in set_tutor_active and return 404 for non-existent IDs; remap FK constraint errors on delete to 409 so concurrent inserts between conflict-check and DELETE don't surface as 500 Frontend: - live/[slotId]: expose polling failures to the tutor via error banner instead of only console.error - s/[code]: split checkin into two try/catch blocks so a successful POST followed by a failed reload doesn't report failure to the student; fix dead '409' string detection to match actual server error 'seat taken' - rooms/[roomId]: remove duplicate onMount fetch; add .catch() to $effect - tutors: expose loadTutors failures via error banner, not just console - rooms: fix bare catch in createRoom (captures error, shows message); add try/catch to onMount rooms load CI: - sync cargo audit --ignore RUSTSEC-2023-0071 with Makefile; the advisory is in rsa which sqlx-mysql retains in the lock file even when the mysql feature is disabled — aws_lc_rs correctly removes it from the active tree
This commit is contained in:
@@ -22,6 +22,7 @@
|
||||
let loading = $state(true);
|
||||
|
||||
let pollInterval: ReturnType<typeof setInterval> | null = null;
|
||||
let loadError = $state<string | null>(null);
|
||||
|
||||
onMount(async () => {
|
||||
await loadData();
|
||||
@@ -73,7 +74,8 @@
|
||||
if (found) break;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
loadError = e instanceof Error ? e.message : 'Daten konnten nicht geladen werden.';
|
||||
console.error('[live/loadData]', e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,6 +139,12 @@
|
||||
|
||||
<div style="padding:28px 36px;display:flex;flex-direction:column;gap:22px">
|
||||
|
||||
{#if loadError}
|
||||
<div style="background:rgba(138,44,31,0.06);border:1px solid var(--accent);color:var(--accent);padding:10px 14px;border-radius:4px;font-size:13px">
|
||||
Fehler beim Laden: {loadError}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if loading}
|
||||
<div style="padding:48px;text-align:center">
|
||||
<span class="small" style="color:var(--ink-4)">Wird geladen…</span>
|
||||
|
||||
@@ -5,19 +5,31 @@
|
||||
|
||||
let rooms = $state<Room[]>([]);
|
||||
let newRoomName = $state('');
|
||||
let errorMsg = $state<string | null>(null);
|
||||
|
||||
onMount(async () => {
|
||||
rooms = await api.admin.rooms.list();
|
||||
try {
|
||||
rooms = await api.admin.rooms.list();
|
||||
} catch (e) {
|
||||
errorMsg = e instanceof Error ? e.message : 'Räume konnten nicht geladen werden.';
|
||||
}
|
||||
});
|
||||
|
||||
async function createRoom() {
|
||||
if (!newRoomName.trim()) return;
|
||||
errorMsg = null;
|
||||
try {
|
||||
await api.admin.rooms.create(newRoomName, []);
|
||||
newRoomName = '';
|
||||
} catch (e) {
|
||||
errorMsg = e instanceof Error ? e.message : 'Raum konnte nicht erstellt werden.';
|
||||
console.error('[rooms/createRoom]', e);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
rooms = await api.admin.rooms.list();
|
||||
} catch {
|
||||
console.error('failed to fetch rooms');
|
||||
} catch (e) {
|
||||
console.error('[rooms/createRoom/list]', e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +50,12 @@
|
||||
<h1 class="h1" style="font-family:var(--serif)">Raumlayout-Editor</h1>
|
||||
</div>
|
||||
|
||||
{#if errorMsg}
|
||||
<div style="background:rgba(138,44,31,0.06);border:1px solid var(--accent);color:var(--accent);padding:10px 14px;border-radius:4px;font-size:13px;margin-bottom:12px">
|
||||
{errorMsg}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="card" style="overflow:hidden">
|
||||
<div style="padding:14px 18px;border-bottom:1px solid var(--rule);display:flex;align-items:center;justify-content:space-between">
|
||||
<div class="serif" style="font-size:18px;font-weight:500">Räume</div>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import { onMount } from 'svelte';
|
||||
import { api } from '$lib/api';
|
||||
import RoomCanvas from '$lib/RoomCanvas.svelte';
|
||||
import type { Room, LayoutElement } from '$lib/types';
|
||||
@@ -12,13 +11,13 @@
|
||||
let errorMsg = $state<string | null>(null);
|
||||
let snapToGrid = $state(true);
|
||||
|
||||
onMount(async () => {
|
||||
room = await api.admin.rooms.get(roomId);
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (roomId) {
|
||||
api.admin.rooms.get(roomId).then((r: Room) => { room = r; });
|
||||
api.admin.rooms.get(roomId)
|
||||
.then((r: Room) => { room = r; })
|
||||
.catch((e: unknown) => {
|
||||
errorMsg = e instanceof Error ? e.message : 'Raum konnte nicht geladen werden.';
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
let tutors = $state<Tutor[]>([]);
|
||||
let loading = $state(true);
|
||||
let loadError = $state<string | null>(null);
|
||||
|
||||
let newTutor = $state({
|
||||
name: '',
|
||||
@@ -23,8 +24,10 @@
|
||||
async function loadTutors() {
|
||||
try {
|
||||
tutors = await api.admin.tutors.list();
|
||||
loadError = null;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
loadError = e instanceof Error ? e.message : 'Tutor:innen konnten nicht geladen werden.';
|
||||
console.error('[tutors/loadTutors]', e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,12 +87,18 @@
|
||||
<UnderlineStroke width={120} />
|
||||
</div>
|
||||
|
||||
{#if loadError}
|
||||
<div style="background:rgba(138,44,31,0.06);border:1px solid var(--accent);color:var(--accent);padding:10px 14px;border-radius:4px;font-size:13px;margin-bottom:12px">
|
||||
{loadError}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="card" style="overflow:hidden">
|
||||
{#if loading}
|
||||
<div style="padding:32px;text-align:center">
|
||||
<span class="small" style="color:var(--ink-4)">Wird geladen…</span>
|
||||
</div>
|
||||
{:else if tutors.length === 0}
|
||||
{:else if tutors.length === 0 && !loadError}
|
||||
<div style="padding:32px;text-align:center">
|
||||
<span class="small" style="color:var(--ink-4)">Keine Tutor:innen gefunden.</span>
|
||||
</div>
|
||||
|
||||
@@ -82,17 +82,21 @@
|
||||
if (!selectedStudent) return;
|
||||
try {
|
||||
await api.checkin.post(code, selectedStudent.id, seatId);
|
||||
await loadInfo();
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
if (e.message?.includes('already checked in') || e.message?.includes('409')) {
|
||||
errorMsg = 'Dieser Platz ist bereits belegt.';
|
||||
} else {
|
||||
errorMsg = e.message ?? 'Einchecken fehlgeschlagen.';
|
||||
}
|
||||
errorMsg = e.message.includes('seat taken')
|
||||
? 'Dieser Platz ist bereits belegt.'
|
||||
: (e.message || 'Einchecken fehlgeschlagen.');
|
||||
} else {
|
||||
errorMsg = 'Einchecken fehlgeschlagen.';
|
||||
errorMsg = 'Einchecken fehlgeschlagen.';
|
||||
}
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await loadInfo();
|
||||
} catch (e) {
|
||||
step = 'confirmed';
|
||||
console.error('[checkin/loadInfo]', e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user