{"id":51222049,"url":"https://github.com/imsumit28/collabdocs","last_synced_at":"2026-06-28T08:00:59.113Z","repository":{"id":354854776,"uuid":"1225580939","full_name":"imsumit28/CollabDocs","owner":"imsumit28","description":"Real-time collaborative document editor with AI writing assistance, live cursors, and version history — built with Next.js, Node.js, Y.js CRDT, and DeepSeek v4 AI.","archived":false,"fork":false,"pushed_at":"2026-06-26T15:08:11.000Z","size":2157,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-26T16:26:27.317Z","etag":null,"topics":["collabrative-editor","crdt","document-editor","mongodb","nextjs","nodejs","open-source","reactjs","real-time-synchronization","socket-io","typescript"],"latest_commit_sha":null,"homepage":"https://collabdocs2026.vercel.app/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/imsumit28.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-30T12:30:51.000Z","updated_at":"2026-06-26T15:09:40.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/imsumit28/CollabDocs","commit_stats":null,"previous_names":["imsumit28/collabdocs"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/imsumit28/CollabDocs","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/imsumit28%2FCollabDocs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/imsumit28%2FCollabDocs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/imsumit28%2FCollabDocs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/imsumit28%2FCollabDocs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/imsumit28","download_url":"https://codeload.github.com/imsumit28/CollabDocs/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/imsumit28%2FCollabDocs/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34881384,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-28T02:00:05.809Z","response_time":54,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["collabrative-editor","crdt","document-editor","mongodb","nextjs","nodejs","open-source","reactjs","real-time-synchronization","socket-io","typescript"],"created_at":"2026-06-28T08:00:55.443Z","updated_at":"2026-06-28T08:00:59.102Z","avatar_url":"https://github.com/imsumit28.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# CollabDocs\n\n![CollabDocs Banner](./banner.png)\n\n[![CI](https://github.com/imsumit28/CollabDocs/actions/workflows/ci.yml/badge.svg)](https://github.com/imsumit28/CollabDocs/actions/workflows/ci.yml)\n[![Node.js](https://img.shields.io/badge/Node.js-20-green?logo=node.js)](https://nodejs.org)\n[![Next.js](https://img.shields.io/badge/Next.js-14-black?logo=next.js)](https://nextjs.org)\n[![MongoDB](https://img.shields.io/badge/MongoDB-Atlas-brightgreen?logo=mongodb)](https://mongodb.com)\n[![Socket.IO](https://img.shields.io/badge/Socket.IO-4-010101?logo=socket.io)](https://socket.io)\n[![TypeScript](https://img.shields.io/badge/TypeScript-5-3178C6?logo=typescript)](https://www.typescriptlang.org)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)\n\n\u003e A production-ready real-time collaborative document editor — think Google Docs, built from scratch. Multiple users edit simultaneously with live cursors, conflict-free CRDT sync, AI writing assistance, and version history.\n\n**[Live Demo](https://collabdocs2026.vercel.app)** · **[API Docs](https://collabdocs2026.vercel.app/api/docs)** · **[GitHub](https://github.com/imsumit28/CollabDocs)**\n\n---\n\n## Table of Contents\n\n- [Features](#features)\n- [Architecture](#architecture)\n- [Tech Stack](#tech-stack)\n- [Setup](#setup)\n- [Design Decisions](#design-decisions)\n- [Testing](#testing)\n- [API Reference](#api-reference)\n- [Security](#security)\n- [Deployment](#deployment)\n- [Documentation](#documentation)\n- [Roadmap](#roadmap)\n- [Contributing](#contributing)\n- [License](#license)\n\n---\n\n## Features\n\n**Real-Time Collaboration**\n- **Live co-editing** — Multiple users type simultaneously via Y.js CRDT + Socket.IO. Changes propagate in ~100ms with zero conflicts.\n- **Live cursors** — Each collaborator gets a unique colour cursor with their name label, updated in real time.\n- **Comments** — Inline comments anchored to text ranges, with reply threads and resolve/reopen flow.\n- **Notifications** — In-app notification bell for shares, comments, and @mentions (both in comments and typed `@username` in the document body), with unread badge and mark-as-read.\n- **Suggestions mode** — Track Changes-style mode built with free TipTap extensions. No paid Pro license required.\n- **Version history** — Browse and restore past snapshots of any document.\n- **Auto-save** — Documents persist every 5 seconds of inactivity via debounced writes to MongoDB.\n- **Offline \u0026 installable (PWA)** — Install to your home screen/desktop; opened documents stay editable offline (Y.js + IndexedDB) and merge automatically on reconnect. A service worker caches the app shell with an offline fallback page.\n\n**User Features**\n- **AI writing assistant** — Improve prose, fix grammar, summarise, expand, simplify, shift tone, translate, outline, brainstorm, and generate titles. Responses **stream in token-by-token**. Powered by DeepSeek (OpenAI-compatible API).\n- **Sharing** — Invite specific people by email (View or Edit) — from either the editor or the dashboard — or share via link with View or Edit permission levels.\n- **Search** — Server-side search across all your documents by title *and* content (a plain-text mirror is kept in sync on save; run `npm run backfill:search` once to index documents created before this feature).\n- **Folders** — Organise your documents into folders from the sidebar; move docs in/out from the card menu. Deleting a folder keeps its documents (they return to root).\n- **Export** — Download as PDF or DOCX.\n- **Authentication** — Email/password with JWT + Google OAuth, email verification, and secure password reset (tokenised, single-use, 1-hour expiry).\n- **Account settings** — Update profile (name, username, avatar) and change password from a dedicated settings page.\n\n**Production-Ready**\n- **173 server tests** — Auth, documents, folders, comments, versions, search, notifications, and real-time sync covered at ~70% overall, plus client component tests (Jest + React Testing Library) and browser E2E (Playwright).\n- **Structured logging** — Leveled JSON logs via pino (pretty-printed in dev), with HTTP request logging and secret redaction.\n- **Interactive API docs** — Swagger/OpenAPI UI at `/api/docs` with request examples.\n- **Security-first** — Rate limiting, input validation, CORS, Helmet headers, XSS/CSRF protection.\n- **Full TypeScript** — End-to-end type safety across client and server.\n\n---\n\n## Architecture\n\n### System Overview\n\n```mermaid\ngraph TB\n    subgraph Clients[\"Browser Clients\"]\n        B1[\"User A\"]\n        B2[\"User B\"]\n    end\n\n    subgraph Frontend[\"Frontend · Vercel\"]\n        NX[\"Next.js 14\\nApp Router\"]\n        TE[\"TipTap Editor\\n(ProseMirror)\"]\n        YC[\"Y.js CRDT\\nClient\"]\n    end\n\n    subgraph Backend[\"Backend · Render\"]\n        EX[\"Express REST API\\nPort 4000\"]\n        SO[\"Socket.IO Server\"]\n        YS[\"Y.js Sync Engine\\n(in-memory Y.Doc per room)\"]\n    end\n\n    subgraph DataLayer[\"Data Layer\"]\n        MG[(\"MongoDB Atlas\\nDocuments · Users\\nComments · Versions\")]\n        RD[(\"Redis · Upstash\\nSocket.IO Pub/Sub\\n(planned)\")]\n    end\n\n    subgraph External[\"External Services\"]\n        GR[\"DeepSeek AI\\n(OpenAI-compatible)\"]\n        GO[\"Google OAuth 2.0\"]\n    end\n\n    B1 \u003c--\u003e|HTTPS| NX\n    B2 \u003c--\u003e|HTTPS| NX\n    NX \u003c--\u003e|\"REST API (JWT)\"| EX\n    NX \u003c--\u003e|\"WebSocket\\n(Y.js binary deltas)\"| SO\n    SO --\u003e YS\n    YS \u003c--\u003e|\"5 s debounce write\"| MG\n    EX \u003c--\u003e|Queries| MG\n    SO \u003c--\u003e|Pub/Sub fan-out| RD\n    EX --\u003e GR\n    EX --\u003e GO\n```\n\n### Real-Time Collaboration Flow\n\n```\nBrowser A                  Server                  Browser B\n   │                          │                          │\n   │── JWT handshake ────────►│ verify token             │\n   │                          │                          │\n   │── doc:join { docId } ───►│ check access             │\n   │                          │ init Y.Doc (or load DB)  │\n   │◄── yjs:sync (full state)─│                          │\n   │                          │◄── doc:join ─────────────│\n   │                          │─── yjs:sync ────────────►│\n   │                          │                          │\n   │ [user types]             │                          │\n   │── yjs:update (delta) ───►│ apply to Y.Doc           │\n   │                          │── yjs:update ───────────►│ (relay)\n   │                          │                          │\n   │                          │  [5 s debounce]          │\n   │                          │  save to MongoDB         │\n   │◄── doc:saved ────────────│─── doc:saved ───────────►│\n   │                          │                          │\n   │ [tab closed]             │                          │\n   │── disconnect             │ emit doc:awareness       │\n   │                          │ flush Y.Doc if last user │\n```\n\n1. **Auth** — JWT access token sent in Socket.IO handshake. Rejected connections never reach event handlers.\n2. **Join** — Server loads serialised `Y.Doc` from MongoDB into a shared in-memory instance for the room, then sends full state to the joining client.\n3. **Update relay** — Every keystroke produces a tiny binary Y.js delta. The server applies it to the in-memory `Y.Doc` and fans it out to all peers. No round-trip serialisation.\n4. **Persistence** — A 5-second debounce timer resets on every update. On expiry the server encodes the `Y.Doc` and writes to MongoDB as a `Buffer`. This bounds write amplification to ≤ 12 writes/minute regardless of typing speed.\n5. **Disconnect cleanup** — The handler re-broadcasts the updated presence list (deduplicated by user ID for multi-tab) and flushes the `Y.Doc` if the room is now empty.\n\n### Directory Structure\n\n```\ncollabdocs/\n├── client/                    # Next.js 14 frontend\n│   ├── app/\n│   │   ├── (auth)/            # Login + Signup pages\n│   │   ├── dashboard/         # Document list\n│   │   └── doc/[id]/          # Editor + collaboration\n│   ├── components/            # Shared UI components\n│   ├── contexts/              # AuthContext, ToastContext\n│   └── lib/                   # API client, Socket.IO singleton, Y.js provider\n│\n├── server/                    # Node.js + Express backend\n│   └── src/\n│       ├── routes/           # auth, documents, versions, ai, export, comments\n│       ├── socket/           # Socket.IO server + Y.js sync engine\n│       ├── models/           # Mongoose schemas (User, Document, Comment, Version)\n│       ├── middleware/       # JWT auth, rate limiting\n│       ├── utils/            # JWT helpers, validation, env validation\n│       ├── swagger.ts        # OpenAPI 3.0 spec\n│       └── __tests__/        # Jest test suites\n│\n└── docs/                      # Documentation hub\n    ├── API.md                 # Complete REST API reference\n    ├── TESTING.md             # Testing guide\n    ├── QUICK_START.md         # Fast local setup\n    └── CHANGELOG.md           # Version history\n```\n\n---\n\n## Tech Stack\n\n| Layer | Technology | Why |\n|-------|-----------|-----|\n| **Frontend** | Next.js 14, React 18 | App Router, SSR, file-based routing |\n| **Editor** | TipTap (ProseMirror) | Extensible rich-text with CRDT bindings |\n| **Real-Time** | Socket.IO 4, Y.js | CRDT sync + WebSocket transport |\n| **Backend** | Node.js, Express, TypeScript | Familiar, fast, type-safe |\n| **Database** | MongoDB (Mongoose) | Schema-flexible for documents/binary Y.js state |\n| **Cache/Scale** | Redis (Upstash) *(optional)* | Socket.IO event fan-out across instances (not full multi-instance Y.Doc scaling — see Design Decisions) |\n| **Auth** | JWT (HS256), Google OAuth (Passport.js) | Stateless, XSS-safe token strategy |\n| **AI** | DeepSeek API (OpenAI-compatible) | Fast inference |\n| **Logging** | pino + pino-http | Structured leveled JSON logs (pretty in dev), aggregator-friendly |\n| **Styling** | Tailwind CSS | Utility-first, consistent design tokens |\n| **Hosting** | Vercel + Render | Zero-config deploys from GitHub |\n\n---\n\n## Setup\n\n### Prerequisites\n\n| Service | Where to get it | Required? |\n|---------|----------------|-----------|\n| Node.js 20+ | [nodejs.org](https://nodejs.org) | Yes |\n| MongoDB Atlas | [cloud.mongodb.com](https://cloud.mongodb.com) — free M0 cluster | Yes |\n| DeepSeek API key | [platform.deepseek.com](https://platform.deepseek.com) | Yes (AI features) |\n| Google Cloud project | [console.cloud.google.com](https://console.cloud.google.com) | Optional (OAuth only) |\n| Upstash Redis | [upstash.com](https://upstash.com) — free database | Optional (future scaling) |\n\n### 1. Clone and install\n\n```bash\ngit clone https://github.com/imsumit28/CollabDocs.git\ncd CollabDocs\nnpm install\n```\n\n### 2. Configure environment variables\n\n```bash\ncp server/.env.example server/.env\ncp client/.env.example client/.env.local\n```\n\n**`server/.env` — key variables to fill in:**\n\n```bash\n# MongoDB: get connection string from Atlas \u003e Connect \u003e Drivers\nMONGODB_URI=mongodb+srv://\u003cuser\u003e:\u003cpassword\u003e@cluster.mongodb.net/collabdocs\n\n# Redis: optional — only needed for horizontal scaling across multiple instances\n# In future: get from Upstash console \u003e REST API \u003e REDIS_URL\n# REDIS_URL=redis://default:\u003cpassword\u003e@\u003chost\u003e:\u003cport\u003e\n\n# Generate with: node -e \"console.log(require('crypto').randomBytes(32).toString('hex'))\"\nJWT_ACCESS_SECRET=\u003c32-char-hex\u003e\nJWT_REFRESH_SECRET=\u003c32-char-hex\u003e\n\n# DeepSeek: copy from platform.deepseek.com \u003e API Keys\nDEEPSEEK_API_KEY=sk-...\nDEEPSEEK_MODEL=deepseek-v4-flash\n\n# Google OAuth (optional): create at console.cloud.google.com \u003e Credentials\nGOOGLE_CLIENT_ID=...apps.googleusercontent.com\nGOOGLE_CLIENT_SECRET=...\n\n# These work as-is for local dev\nPORT=4000\nNODE_ENV=development\nCLIENT_URL=http://localhost:3000\nAPI_URL=http://localhost:4000\n```\n\n**`client/.env.local` — two variables, both point to the backend:**\n\n```bash\nNEXT_PUBLIC_API_URL=http://localhost:4000\nNEXT_PUBLIC_SOCKET_URL=http://localhost:4000\n```\n\n### 3. Run in development\n\n```bash\nnpm run dev\n# Frontend → http://localhost:3000\n# Backend  → http://localhost:4000\n# API Docs → http://localhost:4000/api/docs\n```\n\nOr run individually:\n\n```bash\nnpm run dev --workspace=client   # Frontend only\nnpm run dev --workspace=server   # Backend only\n```\n\n### Troubleshooting\n\n| Problem | Fix |\n|---------|-----|\n| MongoDB connection error | Whitelist your IP in Atlas → Network Access → Add IP |\n| Socket.IO fails in browser | Check `NEXT_PUBLIC_SOCKET_URL` matches the running backend port |\n| Port 3000/4000 in use | `npx kill-port 3000 4000` |\n| Tests failing after env change | `npm run test -- --clearCache` |\n\n---\n\n## Design Decisions\n\n### 1. Y.js CRDT instead of Operational Transformation\n\nThe central architecture question for any collaborative editor is: how do you merge concurrent edits?\n\n| | OT (Google Docs approach) | CRDT (CollabDocs) |\n|---|---|---|\n| Conflict resolution | Server serialises all ops, transforms concurrent ones | Each client merges independently — always converges |\n| Server role | Central arbiter required | Dumb relay — no conflict logic needed |\n| Offline support | Hard — requires reconnect protocol | Built-in — merge on reconnect automatically |\n| Horizontal scaling | Difficult without sticky sessions | Easier in principle — deltas are commutative — though this app's server still holds per-instance state (see note below) |\n\n**Why CRDT:** The server is mostly a relay. It applies deltas to an in-memory `Y.Doc` and broadcasts them, with no conflict-resolution logic. The CRDT model *makes* horizontal scaling tractable (deltas commute, so order doesn't matter), but this implementation still keeps an authoritative `Y.Doc` per instance for persistence — so it targets a single backend instance today. See [Design Decision 4](#4-single-instance-real-time-with-a-redis-adapter-for-event-fan-out) for what multi-instance would require.\n\n**How conflicts resolve in practice:**\n\n```\nInitial:  \"Hello\"\nUser A (offline): inserts \" World\" at pos 5  →  \"Hello World\"\nUser B (offline): inserts \" There\" at pos 5  →  \"Hello There\"\n\nAfter sync: both clients converge to \"Hello World There\"\n(peer ID ordering determines sequence — same result everywhere)\n```\n\nInsertions never destroy each other. Deletions become tombstones internally. Cursor positions are Y.js *relative positions* (anchored to a character identity, not an index), so remote edits never misplace your cursor.\n\n---\n\n### 2. JWT in memory + HttpOnly refresh cookies\n\nStoring JWTs in `localStorage` means any XSS payload can exfiltrate them. The strategy here:\n\n- **Access token (15 min)** — held in React state only. Never written to the DOM or storage. Lost on page refresh (by design).\n- **Refresh token (7 days)** — stored in an `HttpOnly`, `Secure`, `SameSite=Strict` cookie. JavaScript cannot read it; the browser sends it automatically.\n\nOn page load the client silently calls `/api/auth/refresh` — the browser sends the cookie, the server returns a new access token in the JSON body. This gives the \"stay logged in\" UX without exposing credentials to JS.\n\n---\n\n### 3. Debounced MongoDB writes (5 s)\n\nNaive approach: write to the database on every keystroke. At 5 chars/second, that's 300 writes/minute per active user.\n\nCollabDocs instead keeps a per-room `Y.Doc` in memory and resets a 5-second debounce timer on every update. On expiry, one write happens. This bounds write amplification to **≤ 12 writes/minute regardless of typing speed** while keeping data loss risk to ≤ 5 seconds of edits.\n\n---\n\n### 4. Single-instance real-time, with a Redis adapter for event fan-out\n\nThe app is designed to run as a **single backend instance** (Render free tier), and that's the configuration it's correct for.\n\n`@socket.io/redis-adapter` is integrated and activates automatically when `REDIS_URL` is set. It solves *one* part of multi-instance scaling — Socket.IO event fan-out, so presence and relayed updates reach sockets connected to other instances.\n\n**It does not, by itself, make the app safe to run on multiple instances.** Each instance keeps its own authoritative `Y.Doc` for a document in memory and only applies the updates from sockets connected to *that* instance, then debounce-persists the whole state to MongoDB. With two instances editing the same document, their in-memory copies diverge and the periodic save becomes last-writer-wins — edits can be lost.\n\nTrue horizontal scaling would require one of:\n- **Sticky routing per document** (all sockets for a given doc land on the same instance), or\n- **A shared Y.js persistence/sync layer** (e.g. a dedicated y-websocket/y-redis service that owns the authoritative document) so no single app instance holds private state.\n\nBoth are out of scope for the free single-instance deployment; this is called out honestly rather than claimed as \"scale by adding an env var.\"\n\n---\n\n### 5. TipTap (ProseMirror) over Slate or Quill\n\nTipTap has a first-class `y-prosemirror` binding for Y.js CRDT sync, and its extension system made building comments, suggestions mode, slash commands, and @mentions straightforward. Slate would have required building the Y.js binding from scratch. Quill is significantly more constrained for custom extensions.\n\n---\n\n## Testing\n\n```bash\nnpm run test --workspace=server              # Run all tests + coverage report\nnpm run test:watch --workspace=server        # Watch mode\nnpm run test:ci --workspace=server           # CI mode (strict coverage threshold)\n```\n\n**Server** tests run against an in-memory MongoDB (`mongodb-memory-server`) — no local database or paid cluster required, so the suite runs offline and in CI out of the box. **Client** tests use Jest + React Testing Library (via `next/jest`), and **end-to-end** tests use Playwright with all API calls mocked via route interception (no backend or DB needed — deterministic and free).\n\n```bash\nnpm run test:ci --workspace=client   # Client component tests\nnpm run test:e2e --workspace=client  # Playwright E2E (auto-starts the app)\n```\n\n| Module | Test Cases |\n|--------|-----------|\n| Auth Routes | 33 |\n| Document Routes | 35 |\n| Folder Routes | 11 |\n| Comment Routes | 19 |\n| Version Routes | 10 |\n| Export Routes | 5 |\n| AI (validation + streaming) | 8 |\n| Document access (authz) | 9 |\n| Notifications (routes + mentions) | 13 |\n| Search backfill | 5 |\n| Env / health | 10 |\n| WebSocket / CRDT + API docs | ~15 |\n| **Server overall** | **173 tests · ~70% coverage** |\n| Client (Jest) | 7 |\n| End-to-end (Playwright) | 5 |\n\nTests cover: signup/login/refresh/logout, document CRUD/pagination and access control, folders (create/rename/delete, move, folder-scoped listing), comment authorization, share-token permission resolution, version snapshot/restore, PDF/DOCX export, AI input-length limits + streaming, in-document and comment @mention notifications, the content-search backfill, env/health checks, Y.js CRDT concurrent edits and offline merges, plus browser E2E for the login and password-reset flows.\n\nSee [docs/TESTING.md](docs/TESTING.md) for the full testing guide.\n\n---\n\n## API Reference\n\nInteractive Swagger UI available at `http://localhost:4000/api/docs` when the server is running.\n\n**Key endpoints:**\n\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| `POST` | `/api/auth/signup` | Create account |\n| `POST` | `/api/auth/login` | Login, receive access token + refresh cookie |\n| `GET` | `/api/auth/me` | Current user profile |\n| `PATCH` | `/api/auth/me` | Update display name, username, or avatar |\n| `POST` | `/api/auth/change-password` | Change password (verifies current, re-issues session) |\n| `POST` | `/api/auth/refresh` | Exchange refresh cookie for new access token |\n| `POST` | `/api/auth/forgot-password` | Request a password-reset email (generic response — no account enumeration) |\n| `POST` | `/api/auth/reset-password` | Set a new password using a valid reset token |\n| `GET` | `/api/documents` | List owned + shared documents (add `?page=N\u0026limit=M` for a `{ items, total, page, totalPages, hasMore }` envelope; omit for the full array) |\n| `GET` | `/api/documents/search?q=` | Search owned + shared docs by title and content |\n| `POST` | `/api/documents` | Create document |\n| `GET` | `/api/documents/:id` | Get document content |\n| `PATCH` | `/api/documents/:id` | Update title and/or move to a folder (`{ folderId }`, owner only; `null` = root) |\n| `DELETE` | `/api/documents/:id` | Soft-delete (trash) |\n| `POST` | `/api/documents/:id/share` | Generate share link |\n| `GET` | `/api/documents/:id/collaborators` | List people with access (owner + collaborators) |\n| `POST` | `/api/documents/:id/collaborators` | Invite/update a collaborator by email (owner only) |\n| `DELETE` | `/api/documents/:id/collaborators/:userId` | Remove a collaborator (owner only) |\n| `GET` | `/api/folders` | List my folders with document counts |\n| `POST` | `/api/folders` | Create a folder |\n| `PATCH` | `/api/folders/:id` | Rename a folder (owner only) |\n| `DELETE` | `/api/folders/:id` | Delete a folder (its documents return to root) |\n| `GET` | `/api/versions/:docId` | List document versions |\n| `POST` | `/api/versions/:docId` | Save named snapshot |\n| `POST` | `/api/comments` | Add inline comment |\n| `GET` | `/api/comments/:docId` | List comments |\n| `PATCH` | `/api/comments/:id/resolve` | Resolve comment |\n| `GET` | `/api/notifications` | List my notifications + unread count |\n| `PATCH` | `/api/notifications/:id/read` | Mark a notification read |\n| `POST` | `/api/notifications/read-all` | Mark all my notifications read |\n| `POST` | `/api/ai/{improve,grammar,summarize,expand,simplify,tone,outline,brainstorm,translate,title}` | AI writing actions. Add `?stream=1` to stream the response as plain-text chunks; otherwise returns `{ result }` |\n| `POST` | `/api/export/:id/pdf` | Export as PDF |\n| `POST` | `/api/export/:id/docx` | Export as DOCX |\n\n**WebSocket events (Socket.IO):**\n\n| Event | Direction | Description |\n|-------|-----------|-------------|\n| `doc:join` | client → server | Join a document room. Payload `{ docId, shareToken? }` — `shareToken` is required for link-based access by non-collaborators and is verified against the document's active share link. |\n| `doc:permission` | server → client | The joining user's authorized permission (`owner` / `edit` / `view`). Viewers receive a read-only editor and their `yjs:update` writes are rejected server-side. |\n| `yjs:sync` | server → client | Full Y.Doc state on join |\n| `yjs:update` | bidirectional | Binary Y.js delta (keystroke). Only relayed/applied for participants with edit access. |\n| `doc:awareness` | bidirectional | Cursor position + user presence |\n| `doc:saved` | server → client | Persistence confirmation |\n\nSee [docs/API.md](docs/API.md) for curl examples and full schema documentation.\n\n---\n\n## Security\n\n- **JWT strategy** — Access tokens in memory (not `localStorage`), refresh tokens in `HttpOnly` cookies — XSS cannot exfiltrate credentials.\n- **Rate limiting** — Auth endpoints: 5 req/15 min. AI endpoint: 30 req/hour per user.\n- **Input validation** — Email format, password strength (configurable), display name length, document title, comment body, AI input length.\n- **Helmet.js** — Strict Content Security Policy, X-Frame-Options, HSTS, and other security headers.\n- **bcryptjs** — Passwords hashed with 12 salt rounds.\n- **CORS** — Restricted to configured `CLIENT_URL` origin.\n- **Environment validation** — Server refuses to start if required secrets are missing or too short.\n- **Email verification enforcement** — Optional (`REQUIRE_EMAIL_VERIFICATION=true`): unverified password accounts can't log in. OAuth accounts are always verified.\n- **Graceful shutdown** — On `SIGTERM`/`SIGINT` the server persists every open document room before exiting, so edits within the debounce window survive a deploy/restart.\n- **DB-aware health check** — `/health` returns `503` when the database is unreachable so load balancers and uptime monitors can detect a degraded instance.\n\nSee [SECURITY.md](SECURITY.md) for threat model, hardening checklist, and incident reporting.\n\n---\n\n## Deployment\n\n### Frontend → Vercel\n\n1. Push to GitHub\n2. Import the repo in Vercel, set root to `client`\n3. Add environment variable: `NEXT_PUBLIC_API_URL=https://your-backend.onrender.com`\n4. Add: `NEXT_PUBLIC_SOCKET_URL=https://your-backend.onrender.com`\n\n### Backend → Render\n\n1. Create a new Web Service, connect the GitHub repo\n2. Set **Root Directory** to `server`\n3. Build command: `npm install \u0026\u0026 npm run build`\n4. Start command: `npm run start`\n5. Add all variables from `server/.env.example` (MongoDB URI, JWT secrets, etc.) — `REDIS_URL` is optional\n\n**Production env checklist:**\n\n```bash\n# Generate strong JWT secrets\nnode -e \"console.log(require('crypto').randomBytes(32).toString('hex'))\"\n\nNODE_ENV=production\nPASSWORD_MIN_LENGTH=12\nPASSWORD_REQUIRE_UPPERCASE=true\nPASSWORD_REQUIRE_NUMBERS=true\nREQUIRE_EMAIL_VERIFICATION=true   # once SMTP_* is configured\n```\n\n---\n\n## Documentation\n\nAll project documentation lives in the [`docs/`](docs/) hub:\n\n| Document | What's inside |\n|----------|---------------|\n| [Quick Start](docs/QUICK_START.md) | Fastest path to a running local instance |\n| [API Reference](docs/API.md) | Every REST endpoint with curl examples and schemas |\n| [Testing Guide](docs/TESTING.md) | How the test suites are organized and how to run them |\n| [Changelog](docs/CHANGELOG.md) | Version history and notable changes |\n\nProject policies live at the root: [Contributing](CONTRIBUTING.md) · [Code of Conduct](CODE_OF_CONDUCT.md) · [Security Policy](SECURITY.md) · [License](LICENSE).\n\n---\n\n## Roadmap\n\nCollabDocs is feature-complete for its core use case. Planned enhancements, roughly in priority order:\n\n- [ ] **True horizontal scaling** — replace the per-instance in-memory Y.Doc with a shared `y-websocket`/`y-redis` sync layer so document state is consistent across multiple backend instances (today the Redis adapter only fans out Socket.IO events — see [Design Decisions](#design-decisions)).\n- [ ] **Anonymous share-link access** — let link-only visitors read documents and comments over REST (currently share tokens are honored on the WebSocket join but REST endpoints still require an account).\n- [ ] **Inline @mention autocomplete** — a TipTap mention dropdown in the editor; today in-document mentions are detected from typed `@handle` text.\n- [ ] **Transactional email provider** — wire a real SMTP/email service for verification and password-reset mail (development currently logs the link to the console).\n- [ ] **Nested folders** — multi-level folder hierarchy (folders are flat/single-level today).\n- [ ] **Raster PWA icons** — add 192px/512px PNG icons for broader install support across platforms.\n\nHave an idea? Open a [Discussion](https://github.com/imsumit28/CollabDocs/discussions) or a feature request.\n\n---\n\n## Contributing\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md) for dev setup, code standards, and the PR process.\n\nKey commands:\n\n```bash\nnpm run type-check          # TypeScript validation (both workspaces)\nnpm run lint                # ESLint\nnpm run test --workspace=server   # Run tests\n```\n\nPre-commit hooks (Husky + lint-staged) run ESLint and Prettier automatically.\n\nEvery push and pull request to `main` runs the [CI workflow](.github/workflows/ci.yml): type-check, lint, the full server test suite (with coverage, against an in-memory MongoDB), and a production client build.\n\n---\n\n## License\n\nMIT — see [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fimsumit28%2Fcollabdocs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fimsumit28%2Fcollabdocs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fimsumit28%2Fcollabdocs/lists"}