mirror of
https://github.com/screentinker/screentinker.git
synced 2026-05-15 07:32:23 -06:00
Phase 2.2f: white-label.js scoped to workspace_id; requireWorkspaceAdmin gate; status.js bundle
This commit is contained in:
parent
806c931e43
commit
0d642e4d80
|
|
@ -61,11 +61,13 @@ router.get('/export', (req, res) => {
|
|||
if (!token) return res.status(401).json({ error: 'Token required' });
|
||||
|
||||
let userId;
|
||||
let workspaceId;
|
||||
try {
|
||||
const jwt = require('jsonwebtoken');
|
||||
const config = require('../config');
|
||||
const decoded = jwt.verify(token, config.jwtSecret);
|
||||
userId = decoded.id;
|
||||
workspaceId = decoded.current_workspace_id || null;
|
||||
if (!userId) return res.status(401).json({ error: 'Invalid token' });
|
||||
} catch {
|
||||
return res.status(401).json({ error: 'Invalid token' });
|
||||
|
|
@ -74,6 +76,17 @@ router.get('/export', (req, res) => {
|
|||
const user = db.prepare('SELECT id, email, name, role, auth_provider, plan_id, created_at FROM users WHERE id = ?').get(userId);
|
||||
if (!user) return res.status(404).json({ error: 'User not found' });
|
||||
|
||||
// Phase 2.2f: export workspace-scoped branding. Fall back to first-accessible
|
||||
// workspace if the JWT didn't carry one.
|
||||
if (!workspaceId) {
|
||||
const w = db.prepare(`
|
||||
SELECT w.id FROM workspaces w
|
||||
JOIN workspace_members wm ON wm.workspace_id = w.id
|
||||
WHERE wm.user_id = ? ORDER BY wm.joined_at ASC LIMIT 1
|
||||
`).get(userId);
|
||||
workspaceId = w?.id || null;
|
||||
}
|
||||
|
||||
const devices = db.prepare('SELECT id, name, status, ip_address, android_version, app_version, screen_width, screen_height, created_at FROM devices WHERE user_id = ?').all(userId);
|
||||
const deviceIds = devices.map(d => d.id);
|
||||
const devicePlaceholders = deviceIds.map(() => '?').join(',') || "'__none__'";
|
||||
|
|
@ -102,7 +115,7 @@ router.get('/export', (req, res) => {
|
|||
const groupPlaceholders = groupIds.map(() => '?').join(',') || "'__none__'";
|
||||
const groupMembers = groupIds.length ? db.prepare(`SELECT * FROM device_group_members WHERE group_id IN (${groupPlaceholders})`).all(...groupIds) : [];
|
||||
const alertConfigs = db.prepare('SELECT id, alert_type, enabled, config, created_at FROM alert_configs WHERE user_id = ?').all(userId);
|
||||
const whiteLabel = db.prepare('SELECT * FROM white_labels WHERE user_id = ?').get(userId);
|
||||
const whiteLabel = workspaceId ? db.prepare('SELECT * FROM white_labels WHERE workspace_id = ?').get(workspaceId) : null;
|
||||
|
||||
const exportData = {
|
||||
format: 'screentinker-export-v2',
|
||||
|
|
@ -425,14 +438,14 @@ router.post('/import', importUpload.single('file'), async (req, res) => {
|
|||
db.prepare(`INSERT INTO alert_configs (id, user_id, alert_type, enabled, config, created_at) VALUES (?, ?, ?, ?, ?, ?)`).run(newId, userId, a.alert_type, a.enabled !== undefined ? a.enabled : 1, config, a.created_at || Math.floor(Date.now() / 1000));
|
||||
}
|
||||
|
||||
// Import white label
|
||||
if (data.white_label) {
|
||||
// Import white label - UPSERT into the importer's current workspace.
|
||||
if (data.white_label && workspaceId) {
|
||||
const wl = data.white_label;
|
||||
const existing = db.prepare('SELECT id FROM white_labels WHERE user_id = ?').get(userId);
|
||||
const existing = db.prepare('SELECT id FROM white_labels WHERE workspace_id = ?').get(workspaceId);
|
||||
if (existing) {
|
||||
db.prepare(`UPDATE white_labels SET brand_name=?, logo_url=?, favicon_url=?, primary_color=?, bg_color=?, custom_domain=?, custom_css=?, hide_branding=?, updated_at=strftime('%s','now') WHERE user_id=?`).run(wl.brand_name || 'ScreenTinker', wl.logo_url || null, wl.favicon_url || null, wl.primary_color || '#3B82F6', wl.bg_color || '#111827', wl.custom_domain || null, wl.custom_css || null, wl.hide_branding || 0, userId);
|
||||
db.prepare(`UPDATE white_labels SET brand_name=?, logo_url=?, favicon_url=?, primary_color=?, bg_color=?, custom_domain=?, custom_css=?, hide_branding=?, updated_at=strftime('%s','now') WHERE workspace_id=?`).run(wl.brand_name || 'ScreenTinker', wl.logo_url || null, wl.favicon_url || null, wl.primary_color || '#3B82F6', wl.bg_color || '#111827', wl.custom_domain || null, wl.custom_css || null, wl.hide_branding || 0, workspaceId);
|
||||
} else {
|
||||
db.prepare(`INSERT INTO white_labels (id, user_id, brand_name, logo_url, favicon_url, primary_color, bg_color, custom_domain, custom_css, hide_branding) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(uuid.v4(), userId, wl.brand_name || 'ScreenTinker', wl.logo_url || null, wl.favicon_url || null, wl.primary_color || '#3B82F6', wl.bg_color || '#111827', wl.custom_domain || null, wl.custom_css || null, wl.hide_branding || 0);
|
||||
db.prepare(`INSERT INTO white_labels (id, user_id, workspace_id, brand_name, logo_url, favicon_url, primary_color, bg_color, custom_domain, custom_css, hide_branding) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(uuid.v4(), userId, workspaceId, wl.brand_name || 'ScreenTinker', wl.logo_url || null, wl.favicon_url || null, wl.primary_color || '#3B82F6', wl.bg_color || '#111827', wl.custom_domain || null, wl.custom_css || null, wl.hide_branding || 0);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,30 +2,40 @@ const express = require('express');
|
|||
const router = express.Router();
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
const { db } = require('../db/database');
|
||||
// Phase 2.2f: workspace-scoped branding. POST gated by requireWorkspaceAdmin
|
||||
// per the design doc (branding is a workspace_admin power, not editor).
|
||||
const { requireWorkspaceAdmin } = require('../lib/permissions');
|
||||
|
||||
// Get current user's white-label config
|
||||
// Get current workspace's white-label config.
|
||||
router.get('/', (req, res) => {
|
||||
let wl = db.prepare('SELECT * FROM white_labels WHERE user_id = ?').get(req.user.id);
|
||||
if (!req.workspaceId) {
|
||||
return res.json({ brand_name: 'ScreenTinker', primary_color: '#3B82F6', secondary_color: '#1E293B', bg_color: '#111827', hide_branding: 0 });
|
||||
}
|
||||
let wl = db.prepare('SELECT * FROM white_labels WHERE workspace_id = ?').get(req.workspaceId);
|
||||
if (!wl) {
|
||||
// Return default branding
|
||||
wl = { brand_name: 'ScreenTinker', primary_color: '#3B82F6', secondary_color: '#1E293B', bg_color: '#111827', hide_branding: 0 };
|
||||
}
|
||||
res.json(wl);
|
||||
});
|
||||
|
||||
// Get branding by domain (public, for white-label domains)
|
||||
// Get branding by custom domain (public, unauthenticated - used pre-login by
|
||||
// white-label frontends to resolve their hostname's branding). Keyed by the
|
||||
// globally-unique custom_domain column; no scope check.
|
||||
router.get('/domain/:domain', (req, res) => {
|
||||
const wl = db.prepare('SELECT * FROM white_labels WHERE custom_domain = ?').get(req.params.domain);
|
||||
if (!wl) return res.json({ brand_name: 'ScreenTinker', primary_color: '#3B82F6' });
|
||||
res.json(wl);
|
||||
});
|
||||
|
||||
// Create or update white-label config
|
||||
router.post('/', (req, res) => {
|
||||
// Create or update the current workspace's white-label config. Restricted to
|
||||
// workspace_admin / org_owner / org_admin / platform_admin.
|
||||
router.post('/', requireWorkspaceAdmin, (req, res) => {
|
||||
if (!req.workspaceId) return res.status(403).json({ error: 'No workspace context. Switch to a workspace before configuring branding.' });
|
||||
|
||||
const { brand_name, logo_url, favicon_url, primary_color, secondary_color, bg_color,
|
||||
custom_domain, custom_css, hide_branding } = req.body;
|
||||
|
||||
let wl = db.prepare('SELECT * FROM white_labels WHERE user_id = ?').get(req.user.id);
|
||||
let wl = db.prepare('SELECT * FROM white_labels WHERE workspace_id = ?').get(req.workspaceId);
|
||||
|
||||
if (wl) {
|
||||
const fields = { brand_name, logo_url, favicon_url, primary_color, secondary_color, bg_color, custom_domain, custom_css, hide_branding };
|
||||
|
|
@ -36,19 +46,19 @@ router.post('/', (req, res) => {
|
|||
});
|
||||
if (updates.length) {
|
||||
updates.push("updated_at = strftime('%s','now')");
|
||||
values.push(req.user.id);
|
||||
db.prepare(`UPDATE white_labels SET ${updates.join(', ')} WHERE user_id = ?`).run(...values);
|
||||
values.push(req.workspaceId);
|
||||
db.prepare(`UPDATE white_labels SET ${updates.join(', ')} WHERE workspace_id = ?`).run(...values);
|
||||
}
|
||||
} else {
|
||||
const id = uuidv4();
|
||||
db.prepare(`INSERT INTO white_labels (id, user_id, brand_name, logo_url, favicon_url, primary_color, secondary_color, bg_color, custom_domain, custom_css, hide_branding)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(
|
||||
id, req.user.id, brand_name || 'ScreenTinker', logo_url || null, favicon_url || null,
|
||||
db.prepare(`INSERT INTO white_labels (id, user_id, workspace_id, brand_name, logo_url, favicon_url, primary_color, secondary_color, bg_color, custom_domain, custom_css, hide_branding)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(
|
||||
id, req.user.id, req.workspaceId, brand_name || 'ScreenTinker', logo_url || null, favicon_url || null,
|
||||
primary_color || '#3B82F6', secondary_color || '#1E293B', bg_color || '#111827',
|
||||
custom_domain || null, custom_css || null, hide_branding ? 1 : 0);
|
||||
}
|
||||
|
||||
res.json(db.prepare('SELECT * FROM white_labels WHERE user_id = ?').get(req.user.id));
|
||||
res.json(db.prepare('SELECT * FROM white_labels WHERE workspace_id = ?').get(req.workspaceId));
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
|
|
|||
Loading…
Reference in a new issue