feat: initial impl of file proxy

This commit is contained in:
2025-10-21 23:45:04 +00:00
parent 6eded27121
commit 6234c5efd3
24 changed files with 420 additions and 125 deletions

View File

@@ -0,0 +1,4 @@
CONVEX_URL=
# api key used to auth with the convex backend
# use the drexa cli to generate an api key, then add the api key to the api key table via the convex dashboard
API_KEY=

14
apps/file-proxy/auth.ts Normal file
View File

@@ -0,0 +1,14 @@
import { createMiddleware } from "hono/factory"
export type ApiKeyContextVariable = {
apiKey: string
}
const apiKeyMiddleware = createMiddleware<{ Variables: ApiKeyContextVariable }>(
async (c, next) => {
c.set("apiKey", process.env.API_KEY)
await next()
},
)
export { apiKeyMiddleware }

View File

@@ -0,0 +1,16 @@
import { ConvexHttpClient } from "convex/browser"
import { createMiddleware } from "hono/factory"
const _client = new ConvexHttpClient(process.env.CONVEX_URL)
export type ConvexContextVariables = {
convex: ConvexHttpClient
}
export const convexMiddleware = createMiddleware<{
Variables: ConvexContextVariables
}>(async (c, next) => {
c.var
c.set("convex", _client)
await next()
})

6
apps/file-proxy/env.d.ts vendored Normal file
View File

@@ -0,0 +1,6 @@
declare module "bun" {
interface Env {
CONVEX_URL: string
API_KEY: string
}
}

View File

@@ -1,12 +1,39 @@
import { Hono } from "hono"
import { api } from "@fileone/convex/api"
import { newRouter } from "./router"
const h = new Hono().basePath("/files")
const r = newRouter().basePath("/files")
h.get("/:fileId", async (c) => {
const fileId = c.req.param("fileId")
if (!fileId) {
return c.json({ error: "File ID is required" }, 400)
r.get(":shareToken", async (c) => {
const shareToken = c.req.param("shareToken")
if (!shareToken) {
return c.json({ error: "not found" }, 404)
}
const fileShare = await c.var.convex.query(api.fileshare.findFileShare, {
apiKey: c.var.apiKey,
shareToken,
})
if (!fileShare) {
return c.json({ error: "not found" }, 404)
}
const fileUrl = await c.var.convex.query(api.filesystem.getStorageUrl, {
apiKey: c.var.apiKey,
storageId: fileShare.storageId,
})
if (!fileUrl) {
return c.json({ error: "not found" }, 404)
}
const fileResponse = await fetch(fileUrl)
if (!fileResponse.ok) {
return c.json({ error: "not found" }, 404)
}
return new Response(fileResponse.body, {
status: fileResponse.status,
headers: fileResponse.headers,
})
})
export { h as files }
export { r as files }

View File

@@ -1,10 +1,16 @@
import { Hono } from "hono"
import { handleFileRequest } from "./files"
import { apiKeyMiddleware } from "./auth"
import { convexMiddleware } from "./convex"
import { files } from "./files"
Bun.serve({
routes: {
"/files/:fileId": {
GET: handleFileRequest,
},
},
})
const app = new Hono()
app.use(convexMiddleware)
app.use(apiKeyMiddleware)
app.route("/", files)
export default {
port: 8081,
fetch: app.fetch,
}

View File

@@ -3,6 +3,9 @@
"module": "index.ts",
"type": "module",
"private": true,
"scripts": {
"dev": "bun --hot run index.ts"
},
"devDependencies": {
"@types/bun": "latest"
},
@@ -11,6 +14,7 @@
},
"dependencies": {
"@fileone/convex": "workspace:*",
"arktype": "^2.1.23",
"convex": "^1.28.0",
"hono": "^4.10.1"
}

View File

@@ -1,7 +1,11 @@
import type { RouterTypes } from "bun"
import { Hono } from "hono"
import type { ApiKeyContextVariable } from "./auth"
import type { ConvexContextVariables } from "./convex"
function router<
R extends { [K in keyof R]: RouterTypes.RouteValue<Extract<K, string>> },
>(routes: R): R {
return routes
type ContextVariables = ConvexContextVariables & ApiKeyContextVariable
export function newRouter() {
return new Hono<{
Variables: ContextVariables
}>()
}