Skip to content

sandmor/docshare

Repository files navigation

DocShare

Screenshot

Opinionated, authenticated Markdown knowledge base with a rich ProseMirror editor, automatic saving, S3-backed asset uploads, per‑user quota & size enforcement, and shareable read‑only viewer pages – all built on Next.js 16 (App Router + RSC), React 19, tRPC 11, Prisma 7, Clerk, shadcn/ui & TailwindCSS 4.

✨ Core Features

  • Authenticated workspace (Clerk) – all /api + /documents routes protected by edge middleware.
  • Rich Markdown authoring powered by a ProseMirror-based editor with:
    • Inline formatting: bold, italic, underline, strikethrough, subscript, superscript, inline code
    • Block types: headings (H1-H6), paragraphs, blockquotes, horizontal rules
    • Lists: bullet lists, ordered lists, task lists with checkboxes
    • Code blocks with syntax highlighting (via refractor) and language selector
    • Math equations via KaTeX (inline and block)
    • Mermaid diagram support with live preview, SVG/PNG export, and expanded full-page overlay
    • Link editing with inline bubble menu for quick URL edits
    • Image uploads with alignment controls, drag-to-resize, and captions
  • Block-level editing with drag handle for reordering and contextual menus for block actions (turn into, duplicate, delete)
  • Clipboard intelligence: Copy/paste as Markdown, automatic Markdown detection on paste, styled HTML export
  • Autosave (1s debounce + background beacon on unload / navigation) with optimistic sidebar ordering
  • Responsive design with mobile-friendly bottom toolbar positioning
  • Import existing .md files – server derives title from front‑matter or first # heading
  • Export raw Markdown (/api/download/:docId)
  • Embedded asset management: direct S3 presigned POST uploads; unused assets soft‑deleted on content update
  • Per‑user file quota (100MB) & document size enforcement (configurable)
  • Shareable read‑only viewer pages (/viewer/:docId) with SSR prefetch + hydration via TanStack Query
  • File garbage collection API endpoint (/api/cleanup) for cleaning up soft-deleted files with configurable grace period
  • Fully typed end‑to‑end API via tRPC (no separate REST client types needed)

🧱 Domain Model (Prisma)

Document(id, userId, title, content, createdAt, updatedAt)
File(id, userId, fileKey, fileName, fileType, fileSize?, documentId?, deletedAt?, createdAt, updatedAt)

Relations: Document 1 - n File. Files are soft‑deleted (set deletedAt) when no longer referenced in the document body (/api/file/<fileKey> pattern).

🔌 High‑Level Architecture

Layer Purpose
Next.js App Router (RSC) Layout composition, server data prefetch (Query Client + tRPC).
tRPC 11 Type‑safe procedures (document.*).
Prisma 7 Postgres ORM with @prisma/adapter-pg; generated client in lib/generated/prisma.
Clerk Auth context injection in tRPC + middleware route protection.
S3-Compatible Storage Binary asset storage; presigned POST for direct browser uploads.
Zustand Store Local editor state + saved vs dirty diffing with version-based document switching.
TanStack Query Client cache hydration + reactivity for procedures.
ProseMirror Rich text editing with custom schema, node views, and plugins.
Mermaid (iframe-sandboxed) Diagram rendering via isolated iframe for security.

Save Cycle

  1. User edits title/content (Zustand current state updates isSaved=false).
  2. Debounced effect (1s) triggers document.setDocument mutation.
  3. On success: saved state updated; documents list re‑ordered with updated doc first (manual cache manipulation).
  4. On unload / route change: a navigator.sendBeacon ensures last edits persist even if the debounce window not reached.

File Lifecycle

  1. User requests upload: client calls /api/upload with file metadata.
  2. Server checks remaining quota, creates DB File (size null) + returns presigned POST (URL + fields + key).
  3. Browser uploads directly to S3.
  4. Client invokes confirmUpload server action with fileKey + documentId – server queries S3 HEAD to record fileSize and associates file to document.
  5. On document save, server parses markdown for /api/file/<key> references; unreferenced files are soft‑deleted.
  6. Cleanup endpoint (/api/cleanup) permanently removes files past grace period from both S3 and database.

🛠️ Local Development

1. Prerequisites

  • Node.js 20+
  • pnpm 10.14+ (see packageManager field)
  • Postgres database
  • S3-compatible storage bucket & credentials
  • Clerk application (publishable + secret keys)

2. Clone & Install

pnpm install

3. Environment Variables

Create .env.local (or supply via deployment platform). Suggested template:

DATABASE_URL=postgresql://user:pass@host:5432/db
DOCUMENT_MAX_BYTES=200000
NEXT_PUBLIC_DOCUMENT_MAX_BYTES=200000
AWS_REGION=us-east-1
AWS_S3_BUCKET_NAME=your-bucket
AWS_ACCESS_KEY_ID=...
AWS_SECRET_ACCESS_KEY=...
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=...
CLERK_SECRET_KEY=...
CLEANUP_API_KEY=your-secret-key-for-cleanup

Notes:

  • DOCUMENT_MAX_BYTES (server) and NEXT_PUBLIC_DOCUMENT_MAX_BYTES (client) should match; client performs early pre‑check, server is authoritative.
  • Changing size limits requires a dev server restart to update the server constant.
  • CLEANUP_API_KEY is required for the garbage collection endpoint.

4. Database

Generate & apply migrations (already present migrations will apply):

pnpm prisma migrate dev

Generate Prisma Client (runs automatically on migrate):

pnpm prisma generate

5. Run

pnpm dev

Visit http://localhost:3000 – unauthenticated users will be redirected to sign‑in for protected routes.

6. Build Preview

pnpm build && pnpm start

🧾 API Overview (tRPC Procedures)

Procedure Description
document.importMarkdown(content, title?) Create new document from raw markdown (derives title if absent). Size enforced.
document.getById(id) Fetch single document. Public procedure but effective access depends on middleware.
document.getAllDocumentsForUser() List current user documents (desc by updatedAt).
document.newDocument() Create empty document with default title.
document.setDocument({id, title?, content?}) Partial update + file reference sync.
document.renameDocument({id, title}) Title-only update.
document.deleteDocument({id}) Hard delete document + soft-delete associated files.

REST / Route Handlers

Endpoint Method Description
/api/upload POST Returns S3 presigned POST + creates File record (size null). Enforces remaining quota.
/api/file/:key GET Redirects to public S3 object URL (simple indirection layer).
/api/download/:docId GET Raw markdown download with Content-Disposition attachment.
/api/cleanup POST Garbage collection - permanently deletes soft-deleted files past grace period. Requires API key.
/api/cleanup GET Status check - returns counts of pending, in-grace-period, and orphaned files.

Server Action

confirmUpload({fileKey, documentId}) – After direct upload, records file size via S3 HeadObject and associates file.

🔐 Authentication & Authorization

  • Clerk middleware guards /api & /documents routes; /viewer routes are public for read access.
  • Procedure-level auth: protectedProcedure enforces userId presence; public read limited to document.getById inside router but effective exposure depends on middleware.

📦 File & Document Size Enforcement

  • Document: UTF‑8 byte length measured with TextEncoder; error if > DOCUMENT_MAX_BYTES (default: 200KB).
  • Files: Quota is 100MB per user (simple sum of recorded fileSize). Upload route sets dynamic max object size in presigned POST conditions.

♻️ File Garbage Collection

Soft-deleted files are cleaned up via the /api/cleanup endpoint:

# Run cleanup (requires API key)
curl -X POST "https://your-domain.com/api/cleanup" \
  -H "Authorization: Bearer YOUR_CLEANUP_API_KEY"

# Check cleanup status
curl "https://your-domain.com/api/cleanup" \
  -H "Authorization: Bearer YOUR_CLEANUP_API_KEY"

Options:

  • ?gracePeriodHours=24 – Override default 24-hour grace period
  • Maximum 1000 files processed per run

Cleanup handles:

  1. Files soft-deleted past the grace period
  2. Orphaned uploads (files where upload was never confirmed)

🚀 Deployment

Typical (e.g. Vercel):

  1. Provision Postgres & run migrations (prisma migrate deploy).
  2. Set environment variables (including both Clerk keys, size configs, and cleanup API key).
  3. Build with pnpm build.
  4. Start with pnpm start (production). Edge middleware runs at the platform edge.
  5. Set up a cron job to call /api/cleanup periodically (e.g., daily).

Ensure your S3 bucket CORS allows presigned POST from your origin & GET for public viewing.

🧭 Project Structure Highlights

app/
  documents/              # Editor pages (protected)
  viewer/                 # Public read-only rendering
  api/
    cleanup/              # File garbage collection endpoint
    download/             # Markdown export
    file/                 # S3 file proxy
    upload/               # Presigned POST generation
    trpc/                 # tRPC API handler
components/
  editor/
    prosemirror/          # ProseMirror editor implementation
      schema.ts           # Custom schema (code blocks, images, equations, etc.)
      markdown.ts         # Markdown parser/serializer
      plugins/            # Custom plugins (clipboard, drag-handle, block-handle)
      *-node-view.tsx     # Custom node views (code blocks, images, equations)
    auto-save-observer.tsx
    content-editor.tsx
    title-editor.tsx
  layout/                 # Sidebar, header, responsive shell
  viewer/                 # Markdown display components
  ui/                     # shadcn/ui components + custom (mermaid-visualizer, svg-viewer)
lib/
  trpc/                   # tRPC setup (server + client bindings)
  actions/                # Server actions (upload confirmation)
  hooks/                  # Zustand editor store, debounce, media query hooks
  mermaid/                # Iframe-sandboxed Mermaid rendering system
prisma/                   # Schema + migrations

📜 License

MIT


Made with ❤️ using Next.js, tRPC, Prisma & ProseMirror.

About

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published