This repository has been archived on 2026-03-27. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
drive/main.ts
Sienna Meridian Satterwhite 58237d9e44 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.
2026-03-25 18:28:37 +00:00

106 lines
3.9 KiB
TypeScript

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);