Initial commit — Drive, an S3 file browser with WOPI editing
Lightweight replacement for the upstream La Suite Numérique drive (Django/Celery/Next.js) built as a single Deno binary. Server (Deno + Hono): - S3 file operations via AWS SigV4 (no SDK) with pre-signed URLs - WOPI host for Collabora Online (CheckFileInfo, GetFile, PutFile, locks) - Ory Kratos session auth + CSRF protection - Ory Keto permission model (OPL namespaces, not yet wired to routes) - PostgreSQL metadata with recursive folder sizes - S3 backfill API for registering files uploaded outside the UI - OpenTelemetry tracing + metrics (opt-in via OTEL_ENABLED) Frontend (React 19 + Cunningham v4 + react-aria): - File browser with GridList, keyboard nav, multi-select - Collabora editor iframe (full-screen, form POST, postMessage) - Profile menu, waffle menu, drag-drop upload, asset type badges - La Suite integration service theming (runtime CSS) Testing (549 tests): - 235 server unit tests (Deno) — 90%+ coverage - 278 UI unit tests (Vitest) — 90%+ coverage - 11 E2E tests (Playwright) - 12 integration service tests (Playwright) - 13 WOPI integration tests (Playwright + Docker Compose + Collabora) MIT licensed.
This commit is contained in:
105
main.ts
Normal file
105
main.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
import { Hono } from "hono";
|
||||
import { serveStatic } from "hono/deno";
|
||||
import { tracingMiddleware, metricsMiddleware } from "./server/telemetry.ts";
|
||||
import { authMiddleware, sessionHandler } from "./server/auth.ts";
|
||||
import { csrfMiddleware } from "./server/csrf.ts";
|
||||
import {
|
||||
listFiles,
|
||||
getFile,
|
||||
createFile,
|
||||
updateFile,
|
||||
deleteFile,
|
||||
restoreFile,
|
||||
downloadFile,
|
||||
getUploadUrl,
|
||||
completeUpload,
|
||||
listRecent,
|
||||
listFavorites,
|
||||
toggleFavorite,
|
||||
listTrash,
|
||||
} from "./server/files.ts";
|
||||
import { createFolder, listFolderChildren } from "./server/folders.ts";
|
||||
import { backfillHandler } from "./server/backfill.ts";
|
||||
import {
|
||||
wopiCheckFileInfo,
|
||||
wopiGetFile,
|
||||
wopiPutFile,
|
||||
wopiPostAction,
|
||||
generateWopiTokenHandler,
|
||||
} from "./server/wopi/handler.ts";
|
||||
|
||||
const app = new Hono();
|
||||
|
||||
// OpenTelemetry tracing + metrics (must be before auth)
|
||||
app.use("/*", tracingMiddleware);
|
||||
app.use("/*", metricsMiddleware);
|
||||
|
||||
// Health check — no auth
|
||||
app.get("/health", (c) =>
|
||||
c.json({ ok: true, time: new Date().toISOString() }));
|
||||
|
||||
// Auth middleware on everything except /health
|
||||
app.use("/*", async (c, next) => {
|
||||
if (c.req.path === "/health") return await next();
|
||||
return await authMiddleware(c, next);
|
||||
});
|
||||
|
||||
// CSRF protection
|
||||
app.use("/*", csrfMiddleware);
|
||||
|
||||
// ── Auth ────────────────────────────────────────────────────────────────────
|
||||
app.get("/api/auth/session", sessionHandler);
|
||||
|
||||
// ── File operations ─────────────────────────────────────────────────────────
|
||||
app.get("/api/files", listFiles);
|
||||
app.post("/api/files", createFile);
|
||||
app.get("/api/files/:id", getFile);
|
||||
app.put("/api/files/:id", updateFile);
|
||||
app.delete("/api/files/:id", deleteFile);
|
||||
app.post("/api/files/:id/restore", restoreFile);
|
||||
app.get("/api/files/:id/download", downloadFile);
|
||||
app.post("/api/files/:id/upload-url", getUploadUrl);
|
||||
app.post("/api/files/:id/complete-upload", completeUpload);
|
||||
|
||||
// ── Folders ─────────────────────────────────────────────────────────────────
|
||||
app.post("/api/folders", createFolder);
|
||||
app.get("/api/folders/:id/children", listFolderChildren);
|
||||
|
||||
// ── User state ──────────────────────────────────────────────────────────────
|
||||
app.get("/api/recent", listRecent);
|
||||
app.get("/api/favorites", listFavorites);
|
||||
app.put("/api/files/:id/favorite", toggleFavorite);
|
||||
app.get("/api/trash", listTrash);
|
||||
|
||||
// ── Admin (session-auth, not exposed via ingress) ───────────────────────────
|
||||
app.post("/api/admin/backfill", backfillHandler);
|
||||
|
||||
// ── WOPI endpoints (token-auth, bypasses Kratos via auth.ts) ────────────────
|
||||
app.get("/wopi/files/:id", wopiCheckFileInfo);
|
||||
app.get("/wopi/files/:id/contents", wopiGetFile);
|
||||
app.post("/wopi/files/:id/contents", wopiPutFile);
|
||||
app.post("/wopi/files/:id", wopiPostAction);
|
||||
|
||||
// ── WOPI token generation (session-auth) ────────────────────────────────────
|
||||
app.post("/api/wopi/token", generateWopiTokenHandler);
|
||||
|
||||
// ── Static files from ui/dist ───────────────────────────────────────────────
|
||||
app.use(
|
||||
"/*",
|
||||
serveStatic({
|
||||
root: "./ui/dist",
|
||||
}),
|
||||
);
|
||||
|
||||
// SPA fallback
|
||||
app.use(
|
||||
"/*",
|
||||
serveStatic({
|
||||
root: "./ui/dist",
|
||||
path: "index.html",
|
||||
}),
|
||||
);
|
||||
|
||||
const port = parseInt(Deno.env.get("PORT") ?? "3000", 10);
|
||||
console.log(`Drive listening on :${port}`);
|
||||
Deno.serve({ port }, app.fetch);
|
||||
Reference in New Issue
Block a user