immersive2/server/api/user.js
Michael Mainguy a772372b2b Add public URL sharing with express-pouchdb sync
- Add express-pouchdb for self-hosted PouchDB sync server
- Public databases (/db/public/:db) accessible without auth
- Add auth middleware for public/private database access
- Simplify share button to copy current URL to clipboard
- Move feature config from static JSON to dynamic API endpoint
- Add PouchDB sync to PouchData class for real-time collaboration
- Fix Express 5 compatibility by patching req.query
- Skip express.json() for /pouchdb routes (stream handling)
- Remove unused PouchdbPersistenceManager and old share system

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 11:49:56 -06:00

156 lines
3.4 KiB
JavaScript

import { Router } from "express";
const router = Router();
// Feature configurations by tier
const FEATURE_CONFIGS = {
none: {
tier: 'none',
pages: {
examples: 'off',
documentation: 'off',
pricing: 'off',
vrExperience: 'off',
},
features: {
createDiagram: 'off',
createFromTemplate: 'off',
manageDiagrams: 'off',
shareCollaborate: 'off',
privateDesigns: 'off',
encryptedDesigns: 'off',
editData: 'off',
config: 'off',
enterImmersive: 'off',
launchMetaQuest: 'off',
},
limits: {
maxDiagrams: 0,
maxCollaborators: 0,
storageQuotaMB: 0,
},
},
free: {
tier: 'free',
pages: {
examples: 'on',
documentation: 'on',
pricing: 'on',
vrExperience: 'on',
},
features: {
createDiagram: 'on',
createFromTemplate: 'coming-soon',
manageDiagrams: 'on',
shareCollaborate: 'on',
privateDesigns: 'coming-soon',
encryptedDesigns: 'pro',
editData: 'on',
config: 'on',
enterImmersive: 'on',
launchMetaQuest: 'on',
},
limits: {
maxDiagrams: 6,
maxCollaborators: 0,
storageQuotaMB: 100,
},
},
basic: {
tier: 'basic',
pages: {
examples: 'on',
documentation: 'on',
pricing: 'on',
vrExperience: 'on',
},
features: {
createDiagram: 'on',
createFromTemplate: 'on',
manageDiagrams: 'on',
shareCollaborate: 'on',
privateDesigns: 'on',
encryptedDesigns: 'pro',
editData: 'on',
config: 'on',
enterImmersive: 'on',
launchMetaQuest: 'on',
},
limits: {
maxDiagrams: 25,
maxCollaborators: 0,
storageQuotaMB: 500,
},
},
pro: {
tier: 'pro',
pages: {
examples: 'on',
documentation: 'on',
pricing: 'on',
vrExperience: 'on',
},
features: {
createDiagram: 'on',
createFromTemplate: 'on',
manageDiagrams: 'on',
shareCollaborate: 'on',
privateDesigns: 'on',
encryptedDesigns: 'on',
editData: 'on',
config: 'on',
enterImmersive: 'on',
launchMetaQuest: 'on',
},
limits: {
maxDiagrams: -1,
maxCollaborators: -1,
storageQuotaMB: -1,
},
},
};
// Default tier for authenticated users without a specific tier
const DEFAULT_TIER = 'basic';
/**
* GET /api/user/features
* Returns feature configuration for the current user
*
* Query params:
* - tier: Override tier for testing (e.g., ?tier=pro)
*/
router.get("/features", (req, res) => {
// Allow tier override via query param for testing
const tierOverride = req.query.tier;
// TODO: In production, determine tier from JWT token or user database
// For now, use query param override or default to 'basic'
const tier = tierOverride && FEATURE_CONFIGS[tierOverride]
? tierOverride
: DEFAULT_TIER;
const config = FEATURE_CONFIGS[tier];
console.log(`[User] Returning feature config for tier: ${tier}`);
res.json(config);
});
/**
* GET /api/user/features/:tier
* Returns feature configuration for a specific tier (for testing/admin)
*/
router.get("/features/:tier", (req, res) => {
const { tier } = req.params;
const config = FEATURE_CONFIGS[tier];
if (!config) {
return res.status(404).json({ error: `Unknown tier: ${tier}` });
}
console.log(`[User] Returning feature config for tier: ${tier}`);
res.json(config);
});
export default router;