mirror of
https://github.com/screentinker/screentinker.git
synced 2026-06-19 12:43:14 -06:00
White-label is stored per-workspace (white_labels.workspace_id); unbranded and
new workspaces - and the login page - fell back to hardcoded ScreenTinker. Add a
single platform default that everything inherits beneath the per-workspace layer.
Resolution (lib/branding.js): workspace row -> custom-domain match -> platform
default -> hardcoded ScreenTinker. Row-level override: a workspace with its own
row keeps it (current behavior); only row-less workspaces inherit the default,
so editing the default propagates instantly (no row-copying at creation).
The platform default is a white_labels row with a FIXED id ('platform-default'),
not a "workspace_id IS NULL" sentinel - legacy pre-multitenancy rows can also
have a null workspace_id, which would be ambiguous.
- routes/admin.js: GET/PUT /api/admin/branding (requirePlatformAdmin) to read/
upsert the single platform-default row; audit-logged.
- server.js: public GET /api/branding (domain match -> platform default ->
hardcoded) for pre-login/pre-workspace contexts.
- routes/white-label.js: authed GET now falls back to the platform default
(was hardcoded) for row-less workspaces.
- Frontend: login page resolves + applies branding (logo, name, colors, favicon,
custom CSS) pre-auth; Admin page gets a "Default branding" form.
Tests: resolver order incl. legacy null-ws safety; admin GET/PUT (single row,
upsert, platform-admin-only 403). Full suite 37/37. Verified end-to-end:
public + authed + login-page all inherit the platform default; per-workspace
override preserved.
Closes #15.
54 lines
2.1 KiB
JavaScript
54 lines
2.1 KiB
JavaScript
'use strict';
|
|
|
|
// Issue #15: instance-level default white-label branding.
|
|
//
|
|
// Branding is stored per-workspace in white_labels (keyed by workspace_id). This
|
|
// adds a single "platform default" row that every workspace inherits unless it
|
|
// has set its own. Resolution order:
|
|
// 1. the current workspace's row (per-workspace override; unchanged)
|
|
// 2. a custom-domain match (public/pre-login white-label hosts)
|
|
// 3. the platform-default row (instance default, #15)
|
|
// 4. hardcoded ScreenTinker fallback
|
|
//
|
|
// The platform-default row is identified by a FIXED id (not "workspace_id IS
|
|
// NULL"): legacy pre-multitenancy white_labels rows can also have a null
|
|
// workspace_id, so a null-scope sentinel would be ambiguous. A fixed id is not.
|
|
//
|
|
// Override is ROW-LEVEL: a workspace that has any row uses it wholesale; only
|
|
// workspaces with NO row fall through to the platform default. No row-copying at
|
|
// creation, so editing the platform default propagates everywhere instantly.
|
|
|
|
const PLATFORM_DEFAULT_ID = 'platform-default';
|
|
|
|
const HARDCODED_BRANDING = {
|
|
brand_name: 'ScreenTinker',
|
|
logo_url: null,
|
|
favicon_url: null,
|
|
primary_color: '#3B82F6',
|
|
secondary_color: '#1E293B',
|
|
bg_color: '#111827',
|
|
custom_css: null,
|
|
hide_branding: 0,
|
|
};
|
|
|
|
// The single platform-default row (fixed id), or null if none has been set.
|
|
function platformDefaultRow(db) {
|
|
return db.prepare('SELECT * FROM white_labels WHERE id = ?').get(PLATFORM_DEFAULT_ID) || null;
|
|
}
|
|
|
|
// Resolve effective branding for a context. Pass whichever you have:
|
|
// { workspaceId } for the authed app, { domain } for the public/login path.
|
|
function resolveBranding(db, { workspaceId = null, domain = null } = {}) {
|
|
if (workspaceId) {
|
|
const wl = db.prepare('SELECT * FROM white_labels WHERE workspace_id = ?').get(workspaceId);
|
|
if (wl) return wl;
|
|
}
|
|
if (domain) {
|
|
const wl = db.prepare('SELECT * FROM white_labels WHERE custom_domain = ?').get(domain);
|
|
if (wl) return wl;
|
|
}
|
|
return platformDefaultRow(db) || { ...HARDCODED_BRANDING };
|
|
}
|
|
|
|
module.exports = { resolveBranding, platformDefaultRow, HARDCODED_BRANDING, PLATFORM_DEFAULT_ID };
|