- Add /db/local/:db path type that stores diagrams locally without syncing - New diagrams now default to local storage (browser-only) - Share button creates public copy when sharing local diagrams - Add storage type badges (Local/Public/Private) in diagram manager - Add GitHub Actions workflow for automated builds - Block local- database requests at server with 404 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
151 lines
5.5 KiB
Markdown
151 lines
5.5 KiB
Markdown
# Self-Hosted Diagram Sharing with Express-PouchDB
|
|
|
|
## Requirements (Confirmed)
|
|
- **Storage**: In-memory (ephemeral) - lost on server restart
|
|
- **Content**: Copy current diagram entities when creating share
|
|
- **Expiration**: No expiration - links work until server restart
|
|
- **Encryption**: None - keep it simple, anyone with link can access
|
|
|
|
## Architecture
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────┐
|
|
│ Express Server (port 3001) │
|
|
├─────────────────────────────────────────────────┤
|
|
│ /api/share/* → Share management API │
|
|
│ /pouchdb/* → express-pouchdb (sync) │
|
|
│ /share/:uuid → Client share route │
|
|
│ /api/* → Existing API routes │
|
|
│ /* → Vite static files │
|
|
└─────────────────────────────────────────────────┘
|
|
│
|
|
└── In-Memory PouchDB (per share UUID)
|
|
```
|
|
|
|
## Implementation Steps
|
|
|
|
### Phase 1: Server-Side Setup
|
|
|
|
#### 1.1 Add Dependencies
|
|
```bash
|
|
npm install express-pouchdb pouchdb-adapter-memory
|
|
```
|
|
|
|
#### 1.2 Create PouchDB Server Service
|
|
**New file: `server/services/pouchdbServer.js`**
|
|
- Initialize PouchDB with memory adapter
|
|
- Track active share databases in a Map
|
|
- Export `getShareDB(shareId)`, `shareExists(shareId)`, `createPouchDBMiddleware()`
|
|
|
|
#### 1.3 Create Share API
|
|
**New file: `server/api/share.js`**
|
|
- `POST /api/share/create` - Generate UUID, create in-memory DB, copy entities
|
|
- `GET /api/share/:id/exists` - Check if share exists
|
|
- `GET /api/share/stats` - Debug endpoint for active shares
|
|
|
|
#### 1.4 Update API Router
|
|
**Edit: `server/api/index.js`**
|
|
- Add `import shareRouter from "./share.js"`
|
|
- Mount at `router.use("/share", shareRouter)`
|
|
|
|
#### 1.5 Mount Express-PouchDB
|
|
**Edit: `server.js`**
|
|
- Import `createPouchDBMiddleware` from pouchdbServer.js
|
|
- Mount at `app.use("/pouchdb", createPouchDBMiddleware())`
|
|
|
|
---
|
|
|
|
### Phase 2: Client-Side Integration
|
|
|
|
#### 2.1 Update URL Parsing
|
|
**Edit: `src/util/functions/getPath.ts`**
|
|
- Add `getPathInfo()` function returning `{ dbName, isShare, shareId }`
|
|
- Detect `/share/:uuid` pattern
|
|
|
|
#### 2.2 Update PouchDB Persistence Manager
|
|
**Edit: `src/integration/database/pouchdbPersistenceManager.ts`**
|
|
|
|
In `initLocal()`:
|
|
- Call `getPathInfo()` to detect share URLs
|
|
- If share: use `share-{uuid}` as local DB name, call `beginShareSync()`
|
|
|
|
Add new method `beginShareSync(shareId)`:
|
|
- Check share exists via `/api/share/:id/exists`
|
|
- Connect to `${origin}/pouchdb/share-${shareId}`
|
|
- Set up presence with `share-${shareId}` as DB name
|
|
- Begin live sync (no encryption)
|
|
|
|
#### 2.3 Add React Route
|
|
**Edit: `src/react/webRouter.tsx`**
|
|
- Add route `{ path: "/share/:uuid", element: <VrExperience isShare={true} /> }`
|
|
- No ProtectedRoute wrapper (public access)
|
|
|
|
#### 2.4 Add Share Button Handler
|
|
**Edit: `src/react/pages/vrExperience.tsx`**
|
|
- Add `isShare` prop
|
|
- Add `handleShare()` function:
|
|
1. Get all entities from local PouchDB
|
|
2. POST to `/api/share/create` with entities
|
|
3. Copy resulting URL to clipboard
|
|
4. Show confirmation
|
|
|
|
---
|
|
|
|
### Phase 3: Presence Integration
|
|
|
|
The WebSocket presence system already routes by database name. Since shares use `share-{uuid}` as the database name, presence works automatically.
|
|
|
|
**Edit: `server/server.js`** (WebSocket server)
|
|
- Update `originIsAllowed()` to allow localhost for development
|
|
|
|
---
|
|
|
|
## Files to Modify
|
|
|
|
| File | Action | Purpose |
|
|
|------|--------|---------|
|
|
| `package.json` | Edit | Add express-pouchdb, pouchdb-adapter-memory |
|
|
| `server.js` | Edit | Mount /pouchdb middleware |
|
|
| `server/api/index.js` | Edit | Add share router |
|
|
| `server/services/pouchdbServer.js` | Create | PouchDB memory initialization |
|
|
| `server/api/share.js` | Create | Share API endpoints |
|
|
| `server/server.js` | Edit | Allow localhost origins |
|
|
| `src/util/functions/getPath.ts` | Edit | Add getPathInfo() |
|
|
| `src/integration/database/pouchdbPersistenceManager.ts` | Edit | Add share sync logic |
|
|
| `src/react/webRouter.tsx` | Edit | Add /share/:uuid route |
|
|
| `src/react/pages/vrExperience.tsx` | Edit | Add share button handler |
|
|
| `src/util/featureConfig.ts` | Edit | Enable shareCollaborate feature |
|
|
|
|
---
|
|
|
|
## User Flow
|
|
|
|
### Creating a Share
|
|
1. User has a diagram open at `/db/public/mydiagram`
|
|
2. Clicks "Share" button
|
|
3. Client fetches all entities from local PouchDB
|
|
4. POSTs to `/api/share/create` with entities
|
|
5. Server creates in-memory DB, copies entities, returns UUID
|
|
6. Client copies `https://server.com/share/{uuid}` to clipboard
|
|
7. User shares link with collaborators
|
|
|
|
### Joining a Share
|
|
1. User navigates to `https://server.com/share/{uuid}`
|
|
2. React Router renders VrExperience with `isShare=true`
|
|
3. PouchdbPersistenceManager detects share URL
|
|
4. Checks `/api/share/:uuid/exists` - returns true
|
|
5. Creates local PouchDB `share-{uuid}`
|
|
6. Connects to `/pouchdb/share-{uuid}` for sync
|
|
7. Entities replicate to local, render in scene
|
|
8. Presence WebSocket connects with `share-{uuid}` as room
|
|
|
|
---
|
|
|
|
## Future Authentication (Not Implemented Now)
|
|
|
|
Structure allows easy addition later:
|
|
- express-pouchdb middleware can be wrapped with auth middleware
|
|
- Share API can require JWT/session tokens
|
|
- Could add password-protected shares
|
|
- Could add read-only vs read-write permissions
|