Skip to content

Commit 4dde5c9

Browse files
New file revoke ui (#39)
* add * remove extra codes * feat: improve animations * Update +page.svelte * fix * Update files.py * Update src/backend/app/models/files.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/frontend/src/lib/queries/files.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/backend/app/routes/admin/files.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Throw errors on auth/network failures instead of masking as empty arrays (#40) * Initial plan * Fix error handling in files query to distinguish auth failures from empty lists Co-authored-by: baseplate-admin <61817579+baseplate-admin@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: baseplate-admin <61817579+baseplate-admin@users.noreply.github.com> ---------
1 parent 446aea9 commit 4dde5c9

File tree

23 files changed

+405
-279
lines changed

23 files changed

+405
-279
lines changed

src/backend/app/main.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@
2626

2727
app.include_router(admin_user_router, prefix="/admin")
2828

29+
from app.routes.admin.files import router as admin_file_router
30+
31+
app.include_router(admin_file_router, prefix="/admin")
32+
2933
from app.routes.config import router as config_router
3034

3135
app.include_router(config_router)

src/backend/app/models/files.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,12 @@
88

99

1010
class FileInformationOut(SQLModel):
11+
id: UUID
1112
filename: str
1213
size: int
1314

1415
download_count: int
15-
created_at: int
16+
created_at: datetime
1617

1718
expires_at: datetime
1819
expire_after_n_download: int
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
from http import HTTPStatus
2+
3+
from fastapi import APIRouter, BackgroundTasks, HTTPException
4+
from sqlmodel import select
5+
6+
from app.deps import CurrentUser, SessionDep
7+
from app.models.files import File, FileInformationOut, FileOut
8+
from app.tasks import delete_expired_file
9+
10+
router = APIRouter()
11+
12+
13+
@router.get("/files", response_model=list[FileInformationOut])
14+
async def show_all_files(
15+
_: CurrentUser, # Only check for login here
16+
session: SessionDep,
17+
):
18+
query = select(File)
19+
result = await session.exec(query)
20+
file_objects = result.all()
21+
return file_objects
22+
23+
24+
@router.delete("/files/{id}")
25+
async def delete_file(
26+
_: CurrentUser,
27+
id: str,
28+
session: SessionDep,
29+
background_tasks: BackgroundTasks,
30+
):
31+
query = select(File).where(File.id == id)
32+
result = await session.exec(query)
33+
file_object = result.one_or_none()
34+
35+
if not file_object:
36+
raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail="File not found")
37+
38+
background_tasks.add_task(delete_expired_file.delay, str(id))
39+
40+
return FileOut(key=file_object.key)

src/backend/app/routes/information.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ async def get_file_information(
3737
raise e
3838

3939
return {
40+
"id": file_record.id,
4041
"filename": file_record.filename,
4142
"size": s3_response.get("ContentLength"),
4243
"download_count": file_record.download_count,

src/backend/scripts/start_celery.sh

100644100755
File mode changed.

src/frontend/src/lib/consts/backend.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ export const FILE_INFO_URL = `${BACKEND_API}/information`;
88

99
export const ADMIN_CONFIG_URL = `${BACKEND_API}/admin/config`;
1010
export const ADMIN_USER_UPDATE_URL = `${BACKEND_API}/admin/user`;
11+
export const ADMIN_FILES_URL = `${BACKEND_API}/admin/files`;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export function resolve_partial_path(path: string) {
2+
return new URL(path, window.location.href).pathname;
3+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { ADMIN_FILES_URL } from '$lib/consts/backend';
2+
import { createQuery, useQueryClient } from '@tanstack/svelte-query';
3+
4+
export type FileInfo = {
5+
id: string;
6+
filename: string;
7+
folder_name?: string;
8+
size?: number;
9+
created_at: string;
10+
expires_at?: string;
11+
expire_after_n_download?: number;
12+
download_count?: number;
13+
};
14+
15+
export const useFilesQuery = () => {
16+
const queryClient = useQueryClient();
17+
18+
const query = createQuery(() => ({
19+
queryKey: ['admin-files'],
20+
queryFn: async () => {
21+
const token = localStorage.getItem('auth_token');
22+
if (!token) {
23+
throw new Error('Not authenticated');
24+
}
25+
26+
const res = await fetch(ADMIN_FILES_URL, {
27+
headers: {
28+
Authorization: `Bearer ${token}`
29+
}
30+
});
31+
32+
if (!res.ok) {
33+
if (res.status === 401) {
34+
throw new Error('Authentication failed');
35+
}
36+
throw new Error(`Failed to fetch files: ${res.statusText}`);
37+
}
38+
return res.json() as Promise<FileInfo[]>;
39+
},
40+
staleTime: 1000 * 60, // 1 minute
41+
retry: false
42+
}));
43+
44+
const revokeFile = async (id: string) => {
45+
const token = localStorage.getItem('auth_token');
46+
if (!token) throw new Error('Not authenticated');
47+
48+
const res = await fetch(`${ADMIN_FILES_URL}/${id}`, {
49+
method: 'DELETE',
50+
headers: {
51+
Authorization: `Bearer ${token}`
52+
}
53+
});
54+
55+
if (res.ok) {
56+
await queryClient.invalidateQueries({ queryKey: ['admin-files'] });
57+
} else {
58+
throw new Error('Failed to revoke file');
59+
}
60+
};
61+
62+
return {
63+
files: query,
64+
revokeFile
65+
};
66+
};

src/frontend/src/routes/(web_crypto)/(navbar_and_footer)/(fancy_grid)/+layout.svelte renamed to src/frontend/src/routes/(navbar_and_footer)/(fancy_grid)/+layout.svelte

File renamed without changes.

src/frontend/src/routes/(web_crypto)/(navbar_and_footer)/(fancy_grid)/+page.svelte renamed to src/frontend/src/routes/(navbar_and_footer)/(fancy_grid)/+page.svelte

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
Check,
1616
Copy,
1717
Lock,
18-
CloudUpload,
18+
Upload,
1919
Download
2020
} from 'lucide-svelte';
2121
import { marked } from '$lib/functions/marked';
@@ -491,7 +491,7 @@
491491
</div>
492492
{:else if isUploadComplete}
493493
<!-- Final Success Screen -->
494-
<div class="flex h-full flex-col items-center justify-center py-12 text-center">
494+
<div class="col-span-1 flex h-full flex-col items-center justify-center py-12 text-center lg:col-span-2">
495495
<div class="mb-6 flex h-20 w-20 items-center justify-center rounded-full bg-green-500/10">
496496
<Lock class="h-10 w-10 text-green-500" />
497497
</div>
@@ -527,30 +527,37 @@
527527
{:else if isUploading}
528528
{#if uploadingInProgress}
529529
<!-- Modern Upload Animation -->
530-
<div class="flex h-25 flex-col items-center justify-center py-20">
531-
<div class="relative mb-8 h-32 w-32">
532-
<!-- Outer spinning ring -->
533-
<div class="absolute inset-0 rounded-full border-4 border-primary/20"></div>
530+
<div class="flex h-full w-full flex-col items-center justify-center p-8">
531+
<div class="relative mb-8 flex h-40 w-40 items-center justify-center">
532+
<!-- Background Layers -->
533+
<div class="absolute inset-0 animate-pulse rounded-full bg-primary/5"></div>
534+
535+
<!-- Static Track -->
536+
<div class="absolute inset-0 rounded-full border-4 border-muted/20"></div>
537+
538+
<!-- Dynamic Rings -->
534539
<div
535-
class="absolute inset-0 animate-spin rounded-full border-4 border-primary border-t-transparent"
536-
style="animation-duration: 1.5s;"
540+
class="absolute inset-0 animate-spin rounded-full border-4 border-transparent border-t-primary shadow-[0_0_15px_-3px_var(--primary)]"
541+
style="animation-duration: 1.5s; animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);"
537542
></div>
538543

539-
<!-- Inner pulsing circle -->
540-
<div class="absolute inset-4 animate-pulse rounded-full bg-primary/10"></div>
544+
<div
545+
class="absolute inset-3 animate-spin rounded-full border-4 border-transparent border-t-primary/70 border-r-primary/30"
546+
style="animation-duration: 2s; animation-direction: reverse; animation-timing-function: linear;"
547+
></div>
541548

542-
<!-- Icon -->
543-
<div class="absolute inset-0 flex items-center justify-center">
544-
<CloudUpload class="h-12 w-12 animate-bounce text-primary" />
549+
<!-- Center Icon -->
550+
<div class="relative z-10">
551+
<Upload class="h-12 w-12 text-primary drop-shadow-md" />
545552
</div>
546553
</div>
547554

548-
<h3 class="mb-2 animate-pulse text-2xl font-semibold">Encrypting & Uploading...</h3>
555+
<h3 class="mb-2 text-2xl font-semibold tracking-tight">Encrypting & Uploading...</h3>
549556
<p class="mb-8 text-muted-foreground">Please wait while we secure your files</p>
550557

551-
<div class="w-full max-w-md space-y-2">
558+
<div class="w-full max-w-md space-y-3">
552559
<Progress value={uploadProgress} class="h-2" />
553-
<div class="flex justify-between text-xs text-muted-foreground">
560+
<div class="flex justify-between text-xs font-medium text-muted-foreground">
554561
<span>{uploadProgress}%</span>
555562
<span>{totalSize}</span>
556563
</div>
@@ -833,7 +840,7 @@
833840
</div>
834841
</ScrollArea>
835842
</div>
836-
{:else}
843+
{:else if !isUploadComplete}
837844
{@render encryptionInfo()}
838845
{/if}
839846
</div>

0 commit comments

Comments
 (0)