From 6234c5efd31399d742383d55f1c26413f435bd43 Mon Sep 17 00:00:00 2001 From: kenneth Date: Tue, 21 Oct 2025 23:45:04 +0000 Subject: [PATCH] feat: initial impl of file proxy --- AGENTS.md | 1 + apps/drive-web/.env.sample | 4 +- .../src/files/file-preview-dialog.tsx | 14 +-- apps/drive-web/src/files/file-share.ts | 3 + .../src/files/image-preview-dialog.tsx | 53 +++----- .../directories.$directoryId.tsx | 114 +++++++++--------- apps/file-proxy/.env.sample | 4 + apps/file-proxy/auth.ts | 14 +++ apps/file-proxy/convex.ts | 16 +++ apps/file-proxy/env.d.ts | 6 + apps/file-proxy/files.ts | 41 +++++-- apps/file-proxy/index.ts | 22 ++-- apps/file-proxy/package.json | 4 + apps/file-proxy/router.ts | 14 ++- bun.lock | 9 ++ packages/convex/_generated/api.d.ts | 6 + packages/convex/fileshare.ts | 12 ++ packages/convex/filesystem.ts | 20 +++ packages/convex/functions.ts | 19 +++ packages/convex/model/filepreview.ts | 36 ++++++ packages/convex/model/fileshare.ts | 83 +++++++++++++ packages/convex/model/filesystem.ts | 32 +++++ packages/convex/schema.ts | 13 +- packages/convex/shared/filesystem.ts | 5 + 24 files changed, 420 insertions(+), 125 deletions(-) create mode 100644 apps/drive-web/src/files/file-share.ts create mode 100644 apps/file-proxy/.env.sample create mode 100644 apps/file-proxy/auth.ts create mode 100644 apps/file-proxy/env.d.ts create mode 100644 packages/convex/fileshare.ts create mode 100644 packages/convex/model/filepreview.ts create mode 100644 packages/convex/model/fileshare.ts diff --git a/AGENTS.md b/AGENTS.md index 7f74c84..c0770c8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -6,6 +6,7 @@ backend: convex This project uses npm workspaces. - `packages/convex` - convex functions and models - `apps/drive-web` - frontend dashboard +- `apps/file-proxy` - proxies uploaded files via opaque share tokens - `packages/path` - path utils # General Guidelines diff --git a/apps/drive-web/.env.sample b/apps/drive-web/.env.sample index 644cb2c..837fc12 100644 --- a/apps/drive-web/.env.sample +++ b/apps/drive-web/.env.sample @@ -1,4 +1,6 @@ # this is the url to the convex instance (NOT THE DASHBOARD) VITE_CONVEX_URL= # this is the convex url for invoking http actions -VITE_CONVEX_SITE_URL= \ No newline at end of file +VITE_CONVEX_SITE_URL= +# this is the url to the file proxy +FILE_PROXY_URL= \ No newline at end of file diff --git a/apps/drive-web/src/files/file-preview-dialog.tsx b/apps/drive-web/src/files/file-preview-dialog.tsx index 905eb95..b6ba23a 100644 --- a/apps/drive-web/src/files/file-preview-dialog.tsx +++ b/apps/drive-web/src/files/file-preview-dialog.tsx @@ -1,20 +1,20 @@ -import type { Doc } from "@fileone/convex/dataModel" +import type { OpenedFile } from "@fileone/convex/filesystem" import { ImagePreviewDialog } from "./image-preview-dialog" export function FilePreviewDialog({ - file, + openedFile, onClose, }: { - file: Doc<"files"> + openedFile: OpenedFile onClose: () => void }) { - if (!file) return null - - switch (file.mimeType) { + switch (openedFile.file.mimeType) { case "image/jpeg": case "image/png": case "image/gif": - return + return ( + + ) default: return null } diff --git a/apps/drive-web/src/files/file-share.ts b/apps/drive-web/src/files/file-share.ts new file mode 100644 index 0000000..c81b7ac --- /dev/null +++ b/apps/drive-web/src/files/file-share.ts @@ -0,0 +1,3 @@ +export function fileShareUrl(shareToken: string) { + return `${import.meta.env.VITE_FILE_PROXY_URL}/files/${shareToken}` +} diff --git a/apps/drive-web/src/files/image-preview-dialog.tsx b/apps/drive-web/src/files/image-preview-dialog.tsx index 998faee..dfe89b0 100644 --- a/apps/drive-web/src/files/image-preview-dialog.tsx +++ b/apps/drive-web/src/files/image-preview-dialog.tsx @@ -1,7 +1,5 @@ -import { api } from "@fileone/convex/api" -import type { Doc } from "@fileone/convex/dataModel" +import type { OpenedFile } from "@fileone/convex/filesystem" import { DialogTitle } from "@radix-ui/react-dialog" -import { useQuery as useConvexQuery } from "convex/react" import { atom, useAtom, useAtomValue, useSetAtom } from "jotai" import { DownloadIcon, @@ -18,9 +16,8 @@ import { DialogClose, DialogContent, DialogHeader, - DialogOverlay, } from "@/components/ui/dialog" -import { LoadingSpinner } from "@/components/ui/loading-spinner" +import { fileShareUrl } from "./file-share" const zoomLevelAtom = atom( 1, @@ -35,15 +32,12 @@ const zoomLevelAtom = atom( ) export function ImagePreviewDialog({ - file, + openedFile, onClose, }: { - file: Doc<"files"> + openedFile: OpenedFile onClose: () => void }) { - const fileUrl = useConvexQuery(api.filesystem.fetchFileUrl, { - fileId: file._id, - }) const setZoomLevel = useSetAtom(zoomLevelAtom) useEffect( @@ -62,23 +56,12 @@ export function ImagePreviewDialog({ } }} > - - {!fileUrl ? ( - - ) : null} - - {fileUrl ? : null} + ) } -function PreviewContent({ - fileUrl, - file, -}: { - fileUrl: string - file: Doc<"files"> -}) { +function PreviewContent({ openedFile }: { openedFile: OpenedFile }) { return ( - {file.name} + {openedFile.file.name}
- +
- +
) } -function Toolbar({ fileUrl, file }: { fileUrl: string; file: Doc<"files"> }) { +function Toolbar({ openedFile }: { openedFile: OpenedFile }) { const setZoomLevel = useSetAtom(zoomLevelAtom) const zoomInterval = useRef | null>(null) @@ -159,8 +142,8 @@ function Toolbar({ fileUrl, file }: { fileUrl: string; file: Doc<"files"> }) {