Skip to content

Commit 16ab2ba

Browse files
committed
refactor: align backend and frontend types
1 parent f8e2026 commit 16ab2ba

File tree

16 files changed

+287
-84
lines changed

16 files changed

+287
-84
lines changed

frontend/app/components/AdminTokensTable.vue

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,10 @@
2626
<td class="px-4 py-3 text-sm">
2727
<div class="space-y-1.5">
2828
<div class="flex items-center gap-2">
29-
<template v-if="token.disabled">
30-
<span>{{ token.token }}</span>
31-
<UTooltip text="Token is disabled" :arrow="true">
32-
<UIcon name="i-heroicons-lock-closed-20-solid" class="size-4 text-muted" />
33-
</UTooltip>
34-
</template>
35-
<template v-else>
36-
<NuxtLink :to="`/t/${token.token}`">{{ token.token }}</NuxtLink>
37-
</template>
29+
<NuxtLink :to="`/${token.disabled ? 'f' : 't'}/${token.token}`">{{ token.token }}</NuxtLink>
30+
<UTooltip text="Token is disabled" :arrow="true" v-if="token.disabled">
31+
<UIcon name="i-heroicons-lock-closed-20-solid" class="size-4 text-muted" />
32+
</UTooltip>
3833
</div>
3934
</div>
4035
</td>

frontend/app/components/AdminUploadsTable.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
No uploads found
2222
</td>
2323
</tr>
24-
<tr v-else v-for="upload in uploads" :key="upload.id" class="hover:bg-elevated/50 transition-colors">
24+
<tr v-else v-for="upload in uploads" :key="upload.public_id" class="hover:bg-elevated/50 transition-colors">
2525
<td class="px-4 py-3 text-sm">
2626
<div class="space-y-1">
2727
<UPopover mode="hover" :content="{ align: 'start' }" :ui="{ content: 'p-3' }">

frontend/app/components/UploadsTable.vue

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111
</tr>
1212
</thead>
1313
<tbody class="bg-default divide-y divide-default">
14-
<tr v-for="row in rows" :key="row.id" class="hover:bg-elevated/50 transition-colors">
15-
<td class="px-4 py-3 text-sm">{{ row.id }}</td>
14+
<tr v-for="(row, index) in rows" :key="row.public_id" class="hover:bg-elevated/50 transition-colors">
15+
<td class="px-4 py-3 text-sm">{{ index+1 }}</td>
1616
<td class="px-4 py-3 text-sm">
1717
<UPopover mode="hover" :content="{ align: 'start' }" :ui="{ content: 'p-3' }">
1818
<NuxtLink v-if="allowDownloads && row.download_url && row.status === 'completed'" :href="row.download_url" target="_blank"
@@ -43,11 +43,12 @@
4343
:color="getStatusColor(row.status)"
4444
variant="soft"
4545
:icon="getStatusIcon(row.status)">
46-
{{ row.status }}
46+
<span v-if="row.status === 'postprocessing'">Processing</span>
47+
<span v-else>{{ row.status }}</span>
4748
</UBadge>
4849
</td>
4950
<td class="px-4 py-3 text-sm">
50-
<span v-if="row.status === 'completed'" class="font-medium">
51+
<span v-if="row.status === 'completed' || row.status === 'postprocessing'" class="font-medium">
5152
{{ formatBytes(row.size_bytes ?? row.upload_length ?? 0) }}
5253
</span>
5354
<span v-else class="text-sm">
@@ -74,7 +75,7 @@
7475
Resume
7576
</UButton>
7677

77-
<UButton v-if="row.status !== 'completed' && (row.upload_offset ?? 0) < (row.upload_length ?? 0)"
78+
<UButton v-if="row.status !== 'completed' && (row.status === 'postprocessing' || (row.upload_offset ?? 0) < (row.upload_length ?? 0))"
7879
color="error" variant="soft" size="xs" icon="i-heroicons-x-mark-20-solid" @click="$emit('cancel', row)">
7980
Cancel
8081
</UButton>
@@ -107,6 +108,7 @@ function getStatusColor(status: string): 'success' | 'error' | 'warning' | 'prim
107108
case 'paused': return 'warning';
108109
case 'uploading':
109110
case 'in_progress':
111+
case 'postprocessing':
110112
case 'initiating': return 'primary';
111113
default: return 'neutral';
112114
}
@@ -120,6 +122,7 @@ function getStatusIcon(status: string): string {
120122
case 'paused': return 'i-heroicons-pause-circle-20-solid';
121123
case 'uploading':
122124
case 'in_progress': return 'i-heroicons-arrow-path-20-solid';
125+
case 'postprocessing': return 'i-heroicons-cog-6-tooth-20-solid';
123126
case 'initiating': return 'i-heroicons-arrow-up-tray-20-solid';
124127
case 'pending': return 'i-heroicons-clock-20-solid';
125128
default: return 'i-heroicons-question-mark-circle-20-solid';

frontend/app/composables/useTokenInfo.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { ref, computed } from 'vue'
22
import type { TokenInfo } from '~/types/token'
3+
import type { ApiError } from '~/types/uploads'
34

45
export function useTokenInfo(tokenValue: Ref<string>) {
56
const tokenInfo = ref<TokenInfo | null>(null)
@@ -23,8 +24,8 @@ export function useTokenInfo(tokenValue: Ref<string>) {
2324
isDisabled.value = false
2425
try {
2526
const { $apiFetch } = useNuxtApp()
26-
const data = await $apiFetch('/api/tokens/' + tokenValue.value)
27-
tokenInfo.value = data as any
27+
const data = await $apiFetch<TokenInfo>('/api/tokens/' + tokenValue.value)
28+
tokenInfo.value = data
2829
notFound.value = false
2930

3031
// Check token status based on returned data
@@ -36,10 +37,11 @@ export function useTokenInfo(tokenValue: Ref<string>) {
3637
}
3738
isDisabled.value = tokenInfo.value.disabled || false
3839
}
39-
} catch (err: any) {
40+
} catch (err) {
41+
const error = err as ApiError
4042
tokenInfo.value = null
4143
notFound.value = true
42-
tokenError.value = err?.data?.detail || err?.message || 'Failed to load token info.'
44+
tokenError.value = error?.data?.detail || error?.message || 'Failed to load token info.'
4345
}
4446
}
4547

frontend/app/composables/useTusUpload.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,13 @@ import type { Slot } from '~/types/uploads'
33
import type { TokenInfo } from '~/types/token'
44

55
export function useTusUpload() {
6-
async function startTusUpload(slot: Slot, uploadUrl: string, file: File, tokenInfo: TokenInfo | null) {
6+
async function startTusUpload(
7+
slot: Slot,
8+
uploadUrl: string,
9+
file: File,
10+
tokenInfo: TokenInfo | null,
11+
onUploadComplete?: (slot: Slot) => void
12+
) {
713
slot.status = 'uploading'
814
slot.paused = false
915
return new Promise<void>((resolve, reject) => {
@@ -17,7 +23,7 @@ export function useTusUpload() {
1723
filename: file.name,
1824
filetype: file.type,
1925
},
20-
onError(error: any) {
26+
onError(error: Error) {
2127
slot.error = error.message
2228
slot.status = 'error'
2329
slot.tusUpload = undefined
@@ -29,9 +35,14 @@ export function useTusUpload() {
2935
slot.status = 'uploading'
3036
},
3137
onSuccess() {
32-
slot.status = 'completed'
38+
slot.status = 'postprocessing'
3339
slot.progress = 100
3440
slot.tusUpload = undefined
41+
42+
if (onUploadComplete) {
43+
onUploadComplete(slot)
44+
}
45+
3546
resolve()
3647
},
3748
})
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { ref } from 'vue'
2+
import type { Slot } from '~/types/uploads'
3+
import type { TokenInfo } from '~/types/token'
4+
5+
export function useUploadPolling() {
6+
const pollingIntervals = ref<Map<string, NodeJS.Timeout>>(new Map())
7+
8+
async function pollUploadStatus(uploadId: string, token: string, slot: Slot, onComplete?: () => void) {
9+
if (pollingIntervals.value.has(uploadId)) {
10+
return
11+
}
12+
13+
const interval = setInterval(async () => {
14+
try {
15+
const { $apiFetch } = useNuxtApp()
16+
const data = await $apiFetch<TokenInfo>(`/api/tokens/${token}`)
17+
const upload = data.uploads?.find((u) => u.public_id === uploadId)
18+
19+
if (!upload) {
20+
stopPolling(uploadId)
21+
return
22+
}
23+
24+
if (upload.status === 'completed') {
25+
slot.status = 'completed'
26+
stopPolling(uploadId)
27+
if (onComplete) {
28+
onComplete()
29+
}
30+
} else if (upload.status === 'failed') {
31+
slot.status = 'error'
32+
const errorMsg = upload.meta_data?.error
33+
slot.error = typeof errorMsg === 'string' ? errorMsg : 'Processing failed'
34+
stopPolling(uploadId)
35+
}
36+
} catch (err) {
37+
console.error('Failed to poll upload status:', err)
38+
}
39+
}, 2000)
40+
41+
pollingIntervals.value.set(uploadId, interval)
42+
}
43+
44+
function stopPolling(uploadId: string) {
45+
const interval = pollingIntervals.value.get(uploadId)
46+
if (interval) {
47+
clearInterval(interval)
48+
pollingIntervals.value.delete(uploadId)
49+
}
50+
}
51+
52+
function stopAllPolling() {
53+
pollingIntervals.value.forEach((interval) => clearInterval(interval))
54+
pollingIntervals.value.clear()
55+
}
56+
57+
return {
58+
pollUploadStatus,
59+
stopPolling,
60+
stopAllPolling,
61+
}
62+
}

frontend/app/pages/admin/index.vue

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ import AdminTokenForm from "~/components/AdminTokenForm.vue";
126126
import AdminTokensTable from "~/components/AdminTokensTable.vue";
127127
import AdminUploadsTable from "~/components/AdminUploadsTable.vue";
128128
import type { AdminToken } from "~/types/token";
129-
import type { UploadRow } from "~/types/uploads";
129+
import type { UploadRow, ApiError } from "~/types/uploads";
130130
131131
definePageMeta({ middleware: "admin" });
132132
@@ -187,8 +187,8 @@ async function fetchTokens() {
187187
});
188188
tokens.value = res.tokens;
189189
totalTokens.value = res.total;
190-
} catch (err: any) {
191-
handleAuthError(err);
190+
} catch (err) {
191+
handleAuthError(err as ApiError);
192192
} finally {
193193
loadingTokens.value = false;
194194
}
@@ -201,8 +201,8 @@ async function handleCreate(payload: Record<string, any>) {
201201
toast.add({ title: "Token created", color: "success", icon: "i-heroicons-check-circle-20-solid" });
202202
createOpen.value = false;
203203
await fetchTokens();
204-
} catch (err: any) {
205-
handleAuthError(err);
204+
} catch (err) {
205+
handleAuthError(err as ApiError);
206206
} finally {
207207
creating.value = false;
208208
}
@@ -221,8 +221,8 @@ async function handleUpdate(payload: Record<string, any>) {
221221
toast.add({ title: "Token updated", color: "success", icon: "i-heroicons-check-circle-20-solid" });
222222
editOpen.value = false;
223223
await fetchTokens();
224-
} catch (err: any) {
225-
handleAuthError(err);
224+
} catch (err) {
225+
handleAuthError(err as ApiError);
226226
} finally {
227227
savingEdit.value = false;
228228
}
@@ -245,8 +245,8 @@ async function confirmDelete() {
245245
toast.add({ title: "Token deleted", color: "success", icon: "i-heroicons-check-circle-20-solid" });
246246
deleteOpen.value = false;
247247
await fetchTokens();
248-
} catch (err: any) {
249-
handleAuthError(err);
248+
} catch (err) {
249+
handleAuthError(err as ApiError);
250250
} finally {
251251
deleting.value = false;
252252
}
@@ -281,21 +281,21 @@ async function confirmDeleteUpload() {
281281
if (!deleteUploadTarget.value) return;
282282
deletingUpload.value = true;
283283
try {
284-
await $apiFetch(`/api/admin/uploads/${deleteUploadTarget.value.id}`, { method: "DELETE" });
284+
await $apiFetch(`/api/admin/uploads/${deleteUploadTarget.value.public_id}`, { method: "DELETE" });
285285
toast.add({ title: "Upload deleted", color: "success", icon: "i-heroicons-check-circle-20-solid" });
286286
deleteUploadOpen.value = false;
287287
// Refresh the uploads list
288288
if (uploadsToken.value) {
289289
await openUploads(uploadsToken.value);
290290
}
291-
} catch (err: any) {
292-
handleAuthError(err);
291+
} catch (err) {
292+
handleAuthError(err as ApiError);
293293
} finally {
294294
deletingUpload.value = false;
295295
}
296296
}
297297
298-
function handleAuthError(err: any) {
298+
function handleAuthError(err: ApiError) {
299299
if (err?.response?.status === 401 || err?.status === 401) {
300300
adminToken.value = null;
301301
toast.add({

frontend/app/pages/f/[token].vue

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@
9292
</tr>
9393
</thead>
9494
<tbody class="bg-default divide-y divide-default">
95-
<tr v-for="upload in uploads" :key="upload.id" class="hover:bg-elevated/50 transition-colors">
95+
<tr v-for="upload in uploads" :key="upload.public_id" class="hover:bg-elevated/50 transition-colors">
9696
<td class="px-4 py-3 text-sm">
9797
<UPopover mode="hover" :content="{ align: 'start' }" :ui="{ content: 'p-3' }">
9898
<div class="flex items-center gap-2">
@@ -114,7 +114,7 @@
114114
<div class="space-y-2">
115115
<div class="grid grid-cols-[auto_1fr] gap-2">
116116
<span class="text-muted font-medium">ID:</span>
117-
<span>{{ upload.id }}</span>
117+
<span>{{ upload.public_id }}</span>
118118
</div>
119119
<div v-if="upload.mimetype" class="grid grid-cols-[auto_1fr] gap-2">
120120
<span class="text-muted font-medium">Type:</span>
@@ -202,6 +202,7 @@ function getStatusColor(status: string): 'success' | 'error' | 'warning' | 'neut
202202
case 'error':
203203
case 'validation_failed': return 'error'
204204
case 'in_progress':
205+
case 'postprocessing':
205206
case 'uploading': return 'warning'
206207
default: return 'neutral'
207208
}

frontend/app/pages/index.vue

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030

3131
<script setup lang="ts">
3232
import { computed, ref } from "vue";
33+
import type { ApiError } from "~/types/uploads";
3334
3435
const route = useRoute();
3536
const toast = useToast();
@@ -58,9 +59,10 @@ const onSubmit = async () => {
5859
await $apiFetch("/api/admin/validate");
5960
toast.add({ title: "Signed in", color: "success", icon: "i-heroicons-check-circle-20-solid" });
6061
await navigateTo(redirectTo.value);
61-
} catch (err: any) {
62+
} catch (err) {
63+
const apiError = err as ApiError;
6264
adminToken.value = null;
63-
error.value = err?.data?.detail || err?.message || "Invalid api key";
65+
error.value = apiError?.data?.detail || apiError?.message || "Invalid api key";
6466
console.log("Admin sign-in error:", err, error.value);
6567
} finally {
6668
loading.value = false;

0 commit comments

Comments
 (0)