feat: auto-retry uploads when rate limited

Backend: extend rate limiter with check_with_retry() that returns the
seconds until the next slot opens. Upload 429 responses now include
retry_after_secs in the JSON body and a Retry-After header.

Frontend: the upload queue catches 429s as RateLimitError, resets the
affected item to pending, schedules processQueue() to resume after the
server-reported wait, and shows a live countdown banner in UploadQueue.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-04-03 18:34:01 +02:00
parent 3dc69e6c6d
commit 6efd2fd3b5
8 changed files with 113 additions and 24 deletions

View File

@@ -1,5 +1,5 @@
<script lang="ts">
import { queueItems, isProcessing, retryItem, removeItem, clearCompleted } from '$lib/upload-queue';
import { queueItems, isProcessing, retryItem, removeItem, clearCompleted, rateLimitRetryAt } from '$lib/upload-queue';
import type { QueueItem } from '$lib/upload-queue';
function formatSize(bytes: number): string {
@@ -28,6 +28,25 @@
let items = $derived($queueItems);
let hasCompleted = $derived(items.some((i) => i.status === 'done'));
// Countdown for rate-limit banner
let countdown = $state(0);
$effect(() => {
const retryAt = $rateLimitRetryAt;
if (!retryAt) {
countdown = 0;
return;
}
countdown = Math.ceil((retryAt - Date.now()) / 1000);
const interval = setInterval(() => {
countdown = Math.ceil((retryAt - Date.now()) / 1000);
if (countdown <= 0) clearInterval(interval);
}, 1000);
return () => clearInterval(interval);
});
</script>
{#if items.length > 0}
@@ -49,6 +68,12 @@
{/if}
</div>
{#if $rateLimitRetryAt && countdown > 0}
<div class="border-b border-amber-100 bg-amber-50 px-4 py-2 text-sm text-amber-800">
Upload-Limit erreicht. Wird in {countdown} Sek. automatisch fortgesetzt.
</div>
{/if}
<ul class="divide-y divide-gray-100">
{#each items as item (item.id)}
<li class="px-4 py-3">