v0.12.0 codes for FCM and rebranded jama to RosterChirp
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "jama-backend",
|
||||
"name": "rosterchirp-backend",
|
||||
"version": "0.11.25",
|
||||
"description": "TeamChat backend server",
|
||||
"main": "src/index.js",
|
||||
@@ -12,13 +12,13 @@
|
||||
"cookie-parser": "^1.4.6",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.18.2",
|
||||
"firebase-admin": "^12.0.0",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"nanoid": "^3.3.7",
|
||||
"node-fetch": "^2.7.0",
|
||||
"sharp": "^0.33.2",
|
||||
"socket.io": "^4.6.1",
|
||||
"web-push": "^3.6.7",
|
||||
"csv-parse": "^5.5.6",
|
||||
"pg": "^8.11.3"
|
||||
},
|
||||
|
||||
@@ -41,10 +41,10 @@ app.use('/api/about', require('./routes/about'));
|
||||
app.use('/api/help', require('./routes/help'));
|
||||
app.use('/api/push', pushRouter);
|
||||
|
||||
// JAMA-HOST control plane — only registered when APP_TYPE=host
|
||||
// RosterChirp-Host control plane — only registered when APP_TYPE=host
|
||||
if (APP_TYPE === 'host') {
|
||||
app.use('/api/host', require('./routes/host'));
|
||||
console.log('[Server] JAMA-HOST control plane enabled at /api/host');
|
||||
console.log('[Server] RosterChirp-Host control plane enabled at /api/host');
|
||||
}
|
||||
|
||||
// ── Link preview proxy ────────────────────────────────────────────────────────
|
||||
@@ -67,7 +67,7 @@ app.get('/manifest.json', async (req, res) => {
|
||||
const s = {};
|
||||
for (const r of rows) s[r.key] = r.value;
|
||||
|
||||
const appName = s.app_name || process.env.APP_NAME || 'jama';
|
||||
const appName = s.app_name || process.env.APP_NAME || 'rosterchirp';
|
||||
const icon192 = s.pwa_icon_192 || '/icons/icon-192.png';
|
||||
const icon512 = s.pwa_icon_512 || '/icons/icon-512.png';
|
||||
|
||||
@@ -396,7 +396,7 @@ initDb().then(async () => {
|
||||
console.warn('[Server] Could not load tenant cache:', e.message);
|
||||
}
|
||||
}
|
||||
server.listen(PORT, () => console.log(`[Server] jama listening on port ${PORT}`));
|
||||
server.listen(PORT, () => console.log(`[Server] RosterChirp listening on port ${PORT}`));
|
||||
}).catch(err => {
|
||||
console.error('[Server] DB init failed:', err);
|
||||
process.exit(1);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* db.js — Postgres database layer for jama
|
||||
* db.js — Postgres database layer for rosterchirp
|
||||
*
|
||||
* APP_TYPE environment variable controls tenancy:
|
||||
* selfhost (default) → single schema 'public', one Postgres database
|
||||
@@ -32,8 +32,8 @@ if (APP_TYPE !== 'host') APP_TYPE = 'selfhost'; // only two valid values
|
||||
const pool = new Pool({
|
||||
host: process.env.DB_HOST || 'db',
|
||||
port: parseInt(process.env.DB_PORT || '5432'),
|
||||
database: process.env.DB_NAME || 'jama',
|
||||
user: process.env.DB_USER || 'jama',
|
||||
database: process.env.DB_NAME || 'rosterchirp',
|
||||
user: process.env.DB_USER || 'rosterchirp',
|
||||
password: process.env.DB_PASSWORD || '',
|
||||
max: 20,
|
||||
idleTimeoutMillis: 30000,
|
||||
@@ -52,12 +52,12 @@ function resolveSchema(req) {
|
||||
if (APP_TYPE === 'selfhost') return 'public';
|
||||
|
||||
const host = (req.headers.host || '').toLowerCase().split(':')[0];
|
||||
const baseDomain = (process.env.HOST_DOMAIN || 'jamachat.com').toLowerCase();
|
||||
const baseDomain = (process.env.HOST_DOMAIN || 'rosterchirp.com').toLowerCase();
|
||||
|
||||
// Internal requests (Docker health checks, localhost) → public schema
|
||||
if (host === 'localhost' || host === '127.0.0.1' || host === '::1') return 'public';
|
||||
|
||||
// Subdomain: team1.jamachat.com → tenant_team1
|
||||
// Subdomain: team1.rosterchirp.com → tenant_team1
|
||||
if (host.endsWith(`.${baseDomain}`)) {
|
||||
const slug = host.slice(0, -(baseDomain.length + 1));
|
||||
if (!slug || slug === 'www') throw new Error(`Invalid tenant slug: ${slug}`);
|
||||
@@ -198,7 +198,7 @@ async function runMigrations(schema) {
|
||||
|
||||
async function seedSettings(schema) {
|
||||
const defaults = [
|
||||
['app_name', process.env.APP_NAME || 'jama'],
|
||||
['app_name', process.env.APP_NAME || 'rosterchirp'],
|
||||
['logo_url', ''],
|
||||
['pw_reset_active', process.env.ADMPW_RESET === 'true' ? 'true' : 'false'],
|
||||
['icon_newchat', ''],
|
||||
@@ -213,7 +213,7 @@ async function seedSettings(schema) {
|
||||
['feature_branding', 'false'],
|
||||
['feature_group_manager', 'false'],
|
||||
['feature_schedule_manager', 'false'],
|
||||
['app_type', 'JAMA-Chat'],
|
||||
['app_type', 'RosterChirp-Chat'],
|
||||
['team_group_managers', ''],
|
||||
['team_schedule_managers', ''],
|
||||
['team_tool_managers', ''],
|
||||
@@ -269,7 +269,7 @@ async function seedUserGroups(schema) {
|
||||
|
||||
async function seedAdmin(schema) {
|
||||
const strip = s => (s || '').replace(/^['"]+|['"]+$/g, '').trim();
|
||||
const adminEmail = strip(process.env.ADMIN_EMAIL) || 'admin@jama.local';
|
||||
const adminEmail = strip(process.env.ADMIN_EMAIL) || 'admin@rosterchirp.local';
|
||||
const adminName = strip(process.env.ADMIN_NAME) || 'Admin User';
|
||||
const adminPass = strip(process.env.ADMIN_PASS) || 'Admin@1234';
|
||||
const pwReset = process.env.ADMPW_RESET === 'true';
|
||||
@@ -350,11 +350,11 @@ async function initDb() {
|
||||
await seedAdmin('public');
|
||||
await seedUserGroups('public');
|
||||
|
||||
// Host mode: the public schema is the host's own workspace — always full JAMA-Team plan.
|
||||
// Host mode: the public schema is the host's own workspace — always full RosterChirp-Team plan.
|
||||
// ON CONFLICT DO UPDATE ensures existing installs get corrected on restart too.
|
||||
if (APP_TYPE === 'host') {
|
||||
const hostPlan = [
|
||||
['app_type', 'JAMA-Team'],
|
||||
['app_type', 'RosterChirp-Team'],
|
||||
['feature_branding', 'true'],
|
||||
['feature_group_manager', 'true'],
|
||||
['feature_schedule_manager', 'true'],
|
||||
@@ -365,7 +365,7 @@ async function initDb() {
|
||||
[key, value]
|
||||
);
|
||||
}
|
||||
console.log('[DB] Host mode: public schema upgraded to JAMA-Team plan');
|
||||
console.log('[DB] Host mode: public schema upgraded to RosterChirp-Team plan');
|
||||
}
|
||||
|
||||
console.log('[DB] Initialisation complete');
|
||||
|
||||
5
backend/src/models/migrations/007_fcm_push.sql
Normal file
5
backend/src/models/migrations/007_fcm_push.sql
Normal file
@@ -0,0 +1,5 @@
|
||||
-- Migration 007: FCM push — add fcm_token column, relax NOT NULL on legacy web-push columns
|
||||
ALTER TABLE push_subscriptions ADD COLUMN IF NOT EXISTS fcm_token TEXT;
|
||||
ALTER TABLE push_subscriptions ALTER COLUMN endpoint DROP NOT NULL;
|
||||
ALTER TABLE push_subscriptions ALTER COLUMN p256dh DROP NOT NULL;
|
||||
ALTER TABLE push_subscriptions ALTER COLUMN auth DROP NOT NULL;
|
||||
4
backend/src/models/migrations/008_rebrand.sql
Normal file
4
backend/src/models/migrations/008_rebrand.sql
Normal file
@@ -0,0 +1,4 @@
|
||||
-- Migration 008: Rebrand — update app_type values from JAMA-* to RosterChirp-*
|
||||
UPDATE settings SET value = 'RosterChirp-Chat' WHERE key = 'app_type' AND value = 'JAMA-Chat';
|
||||
UPDATE settings SET value = 'RosterChirp-Brand' WHERE key = 'app_type' AND value = 'JAMA-Brand';
|
||||
UPDATE settings SET value = 'RosterChirp-Team' WHERE key = 'app_type' AND value = 'JAMA-Team';
|
||||
@@ -28,10 +28,10 @@ router.get('/', (req, res) => {
|
||||
const about = {
|
||||
...DEFAULTS,
|
||||
...overrides,
|
||||
version: process.env.JAMA_VERSION || process.env.TEAMCHAT_VERSION || 'dev',
|
||||
version: process.env.ROSTERCHIRP_VERSION || process.env.TEAMCHAT_VERSION || 'dev',
|
||||
// Always expose original app identity — not overrideable via about.json or settings
|
||||
default_app_name: 'jama',
|
||||
default_logo: '/icons/jama.png',
|
||||
default_app_name: 'rosterchirp',
|
||||
default_logo: '/icons/rosterchirp.png',
|
||||
};
|
||||
|
||||
// Never expose docker_image — removed from UI
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* routes/host.js — JAMA-HOST control plane
|
||||
* routes/host.js — RosterChirp-Host control plane
|
||||
*
|
||||
* All routes require the HOST_ADMIN_KEY header.
|
||||
* These routes operate on the 'public' schema (tenant registry).
|
||||
@@ -141,7 +141,7 @@ router.post('/tenants', async (req, res) => {
|
||||
process.env.ADMIN_PASS = origPass;
|
||||
|
||||
// 5. Set app_type based on plan
|
||||
const planAppType = { chat: 'JAMA-Chat', brand: 'JAMA-Brand', team: 'JAMA-Team' }[plan] || 'JAMA-Chat';
|
||||
const planAppType = { chat: 'RosterChirp-Chat', brand: 'RosterChirp-Brand', team: 'RosterChirp-Team' }[plan] || 'RosterChirp-Chat';
|
||||
await exec(schemaName, "UPDATE settings SET value=$1 WHERE key='app_type'", [planAppType]);
|
||||
if (plan === 'brand' || plan === 'team') {
|
||||
await exec(schemaName, "UPDATE settings SET value='true' WHERE key='feature_branding'");
|
||||
@@ -161,7 +161,7 @@ router.post('/tenants', async (req, res) => {
|
||||
// 7. Reload domain cache
|
||||
await reloadTenantCache();
|
||||
|
||||
const baseDomain = process.env.HOST_DOMAIN || 'jamachat.com';
|
||||
const baseDomain = process.env.HOST_DOMAIN || 'rosterchirp.com';
|
||||
const tenant = tr.rows[0];
|
||||
tenant.url = `https://${slug}.${baseDomain}`;
|
||||
|
||||
@@ -220,7 +220,7 @@ router.patch('/tenants/:slug', async (req, res) => {
|
||||
await exec(s, "UPDATE settings SET value=CASE WHEN $1 IN ('brand','team') THEN 'true' ELSE 'false' END WHERE key='feature_branding'", [plan]);
|
||||
await exec(s, "UPDATE settings SET value=CASE WHEN $1 = 'team' THEN 'true' ELSE 'false' END WHERE key='feature_group_manager'", [plan]);
|
||||
await exec(s, "UPDATE settings SET value=CASE WHEN $1 = 'team' THEN 'true' ELSE 'false' END WHERE key='feature_schedule_manager'", [plan]);
|
||||
const planAppType = { chat: 'JAMA-Chat', brand: 'JAMA-Brand', team: 'JAMA-Team' }[plan] || 'JAMA-Chat';
|
||||
const planAppType = { chat: 'RosterChirp-Chat', brand: 'RosterChirp-Brand', team: 'RosterChirp-Team' }[plan] || 'RosterChirp-Chat';
|
||||
await exec(s, "UPDATE settings SET value=$1 WHERE key='app_type'", [planAppType]);
|
||||
}
|
||||
|
||||
@@ -310,7 +310,7 @@ router.get('/status', async (req, res) => {
|
||||
try {
|
||||
const tenantCount = await queryOne('public', 'SELECT COUNT(*) AS count FROM tenants');
|
||||
const active = await queryOne('public', "SELECT COUNT(*) AS count FROM tenants WHERE status='active'");
|
||||
const baseDomain = process.env.HOST_DOMAIN || 'jamachat.com';
|
||||
const baseDomain = process.env.HOST_DOMAIN || 'rosterchirp.com';
|
||||
res.json({
|
||||
ok: true,
|
||||
appType: process.env.APP_TYPE || 'selfhost',
|
||||
|
||||
@@ -1,50 +1,62 @@
|
||||
const express = require('express');
|
||||
const webpush = require('web-push');
|
||||
const router = express.Router();
|
||||
const { query, queryOne, queryResult, exec } = require('../models/db');
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { query, queryOne, exec } = require('../models/db');
|
||||
const { authMiddleware } = require('../middleware/auth');
|
||||
|
||||
// VAPID keys are stored in settings; lazily initialised on first request
|
||||
let vapidPublicKey = null;
|
||||
// ── Firebase Admin ─────────────────────────────────────────────────────────────
|
||||
let firebaseAdmin = null;
|
||||
let firebaseApp = null;
|
||||
|
||||
async function getVapidKeys(schema) {
|
||||
const pub = await queryOne(schema, "SELECT value FROM settings WHERE key = 'vapid_public'");
|
||||
const priv = await queryOne(schema, "SELECT value FROM settings WHERE key = 'vapid_private'");
|
||||
if (!pub?.value || !priv?.value) {
|
||||
const keys = webpush.generateVAPIDKeys();
|
||||
await exec(schema,
|
||||
"INSERT INTO settings (key,value) VALUES ('vapid_public',$1) ON CONFLICT(key) DO UPDATE SET value=$1",
|
||||
[keys.publicKey]
|
||||
);
|
||||
await exec(schema,
|
||||
"INSERT INTO settings (key,value) VALUES ('vapid_private',$1) ON CONFLICT(key) DO UPDATE SET value=$1",
|
||||
[keys.privateKey]
|
||||
);
|
||||
console.log('[Push] Generated new VAPID keys');
|
||||
return keys;
|
||||
}
|
||||
return { publicKey: pub.value, privateKey: priv.value };
|
||||
}
|
||||
|
||||
async function initWebPush(schema) {
|
||||
const keys = await getVapidKeys(schema);
|
||||
webpush.setVapidDetails('mailto:admin@jama.local', keys.publicKey, keys.privateKey);
|
||||
return keys.publicKey;
|
||||
}
|
||||
|
||||
// Called from index.js socket push notifications — schema comes from caller
|
||||
async function sendPushToUser(schema, userId, payload) {
|
||||
function getMessaging() {
|
||||
if (firebaseApp) return firebaseAdmin.messaging(firebaseApp);
|
||||
const json = process.env.FIREBASE_SERVICE_ACCOUNT;
|
||||
if (!json) return null;
|
||||
try {
|
||||
if (!vapidPublicKey) vapidPublicKey = await initWebPush(schema);
|
||||
const subs = await query(schema, 'SELECT * FROM push_subscriptions WHERE user_id = $1', [userId]);
|
||||
firebaseAdmin = require('firebase-admin');
|
||||
const svc = JSON.parse(json);
|
||||
firebaseApp = firebaseAdmin.initializeApp({
|
||||
credential: firebaseAdmin.credential.cert(svc),
|
||||
});
|
||||
console.log('[Push] Firebase Admin initialised');
|
||||
return firebaseAdmin.messaging(firebaseApp);
|
||||
} catch (e) {
|
||||
console.error('[Push] Firebase Admin init failed:', e.message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Helpers ────────────────────────────────────────────────────────────────────
|
||||
|
||||
// Called from index.js socket push notifications
|
||||
async function sendPushToUser(schema, userId, payload) {
|
||||
const messaging = getMessaging();
|
||||
if (!messaging) return;
|
||||
try {
|
||||
const subs = await query(schema,
|
||||
'SELECT * FROM push_subscriptions WHERE user_id = $1 AND fcm_token IS NOT NULL',
|
||||
[userId]
|
||||
);
|
||||
for (const sub of subs) {
|
||||
try {
|
||||
await webpush.sendNotification(
|
||||
{ endpoint: sub.endpoint, keys: { p256dh: sub.p256dh, auth: sub.auth } },
|
||||
JSON.stringify(payload)
|
||||
);
|
||||
await messaging.send({
|
||||
token: sub.fcm_token,
|
||||
data: {
|
||||
title: payload.title || 'New Message',
|
||||
body: payload.body || '',
|
||||
url: payload.url || '/',
|
||||
groupId: payload.groupId ? String(payload.groupId) : '',
|
||||
},
|
||||
android: { priority: 'high' },
|
||||
webpush: { headers: { Urgency: 'high' } },
|
||||
});
|
||||
} catch (err) {
|
||||
if (err.statusCode === 410 || err.statusCode === 404) {
|
||||
// Remove stale tokens
|
||||
const stale = [
|
||||
'messaging/registration-token-not-registered',
|
||||
'messaging/invalid-registration-token',
|
||||
'messaging/invalid-argument',
|
||||
];
|
||||
if (stale.includes(err.code)) {
|
||||
await exec(schema, 'DELETE FROM push_subscriptions WHERE id = $1', [sub.id]);
|
||||
}
|
||||
}
|
||||
@@ -54,56 +66,47 @@ async function sendPushToUser(schema, userId, payload) {
|
||||
}
|
||||
}
|
||||
|
||||
router.get('/vapid-public', async (req, res) => {
|
||||
try {
|
||||
if (!vapidPublicKey) vapidPublicKey = await initWebPush(req.schema);
|
||||
res.json({ publicKey: vapidPublicKey });
|
||||
} catch (e) { res.status(500).json({ error: e.message }); }
|
||||
// ── Routes ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
// Public — frontend fetches this to initialise the Firebase JS SDK
|
||||
router.get('/firebase-config', (req, res) => {
|
||||
const apiKey = process.env.FIREBASE_API_KEY;
|
||||
const projectId = process.env.FIREBASE_PROJECT_ID;
|
||||
const messagingSenderId = process.env.FIREBASE_MESSAGING_SENDER_ID;
|
||||
const appId = process.env.FIREBASE_APP_ID;
|
||||
const vapidKey = process.env.FIREBASE_VAPID_KEY;
|
||||
|
||||
if (!apiKey || !projectId || !messagingSenderId || !appId) {
|
||||
return res.status(503).json({ error: 'FCM not configured' });
|
||||
}
|
||||
res.json({ apiKey, projectId, messagingSenderId, appId, vapidKey });
|
||||
});
|
||||
|
||||
// Register / refresh an FCM token for the logged-in user
|
||||
router.post('/subscribe', authMiddleware, async (req, res) => {
|
||||
const { endpoint, keys } = req.body;
|
||||
if (!endpoint || !keys?.p256dh || !keys?.auth)
|
||||
return res.status(400).json({ error: 'Invalid subscription' });
|
||||
const { fcmToken } = req.body;
|
||||
if (!fcmToken) return res.status(400).json({ error: 'fcmToken required' });
|
||||
try {
|
||||
const device = req.device || 'desktop';
|
||||
await exec(req.schema,
|
||||
'DELETE FROM push_subscriptions WHERE endpoint = $1 OR (user_id = $2 AND device = $3)',
|
||||
[endpoint, req.user.id, device]
|
||||
'DELETE FROM push_subscriptions WHERE user_id = $1 AND device = $2',
|
||||
[req.user.id, device]
|
||||
);
|
||||
await exec(req.schema,
|
||||
'INSERT INTO push_subscriptions (user_id, device, endpoint, p256dh, auth) VALUES ($1,$2,$3,$4,$5)',
|
||||
[req.user.id, device, endpoint, keys.p256dh, keys.auth]
|
||||
'INSERT INTO push_subscriptions (user_id, device, fcm_token) VALUES ($1, $2, $3)',
|
||||
[req.user.id, device, fcmToken]
|
||||
);
|
||||
res.json({ success: true });
|
||||
} catch (e) { res.status(500).json({ error: e.message }); }
|
||||
});
|
||||
|
||||
router.post('/generate-vapid', authMiddleware, async (req, res) => {
|
||||
if (req.user.role !== 'admin') return res.status(403).json({ error: 'Admins only' });
|
||||
try {
|
||||
const keys = webpush.generateVAPIDKeys();
|
||||
await exec(req.schema,
|
||||
"INSERT INTO settings (key,value) VALUES ('vapid_public',$1) ON CONFLICT(key) DO UPDATE SET value=$1",
|
||||
[keys.publicKey]
|
||||
);
|
||||
await exec(req.schema,
|
||||
"INSERT INTO settings (key,value) VALUES ('vapid_private',$1) ON CONFLICT(key) DO UPDATE SET value=$1",
|
||||
[keys.privateKey]
|
||||
);
|
||||
webpush.setVapidDetails('mailto:admin@jama.local', keys.publicKey, keys.privateKey);
|
||||
vapidPublicKey = keys.publicKey;
|
||||
res.json({ publicKey: keys.publicKey });
|
||||
} catch (e) { res.status(500).json({ error: e.message }); }
|
||||
});
|
||||
|
||||
// Remove the FCM token for the logged-in user / device
|
||||
router.post('/unsubscribe', authMiddleware, async (req, res) => {
|
||||
const { endpoint } = req.body;
|
||||
if (!endpoint) return res.status(400).json({ error: 'Endpoint required' });
|
||||
try {
|
||||
const device = req.device || 'desktop';
|
||||
await exec(req.schema,
|
||||
'DELETE FROM push_subscriptions WHERE user_id = $1 AND endpoint = $2',
|
||||
[req.user.id, endpoint]
|
||||
'DELETE FROM push_subscriptions WHERE user_id = $1 AND device = $2',
|
||||
[req.user.id, device]
|
||||
);
|
||||
res.json({ success: true });
|
||||
} catch (e) { res.status(500).json({ error: e.message }); }
|
||||
|
||||
@@ -37,7 +37,7 @@ router.get('/', async (req, res) => {
|
||||
for (const r of rows) obj[r.key] = r.value;
|
||||
const admin = await queryOne(req.schema, 'SELECT email FROM users WHERE is_default_admin = TRUE');
|
||||
if (admin) obj.admin_email = admin.email;
|
||||
obj.app_version = process.env.JAMA_VERSION || 'dev';
|
||||
obj.app_version = process.env.ROSTERCHIRP_VERSION || 'dev';
|
||||
obj.user_pass = process.env.USER_PASS || 'user@1234';
|
||||
// Tell the frontend whether this request came from the HOST_DOMAIN.
|
||||
// Used to show/hide the Control Panel menu item — only visible on the host's own domain.
|
||||
@@ -105,7 +105,7 @@ router.patch('/colors', authMiddleware, adminMiddleware, async (req, res) => {
|
||||
|
||||
router.post('/reset', authMiddleware, adminMiddleware, async (req, res) => {
|
||||
try {
|
||||
const originalName = process.env.APP_NAME || 'jama';
|
||||
const originalName = process.env.APP_NAME || 'rosterchirp';
|
||||
await exec(req.schema, "UPDATE settings SET value=$1, updated_at=NOW() WHERE key='app_name'", [originalName]);
|
||||
await exec(req.schema, "UPDATE settings SET value='', updated_at=NOW() WHERE key='logo_url'");
|
||||
await exec(req.schema, "UPDATE settings SET value='', updated_at=NOW() WHERE key IN ('icon_newchat','icon_groupinfo','pwa_icon_192','pwa_icon_512','color_title','color_title_dark','color_avatar_public','color_avatar_dm')");
|
||||
@@ -114,9 +114,9 @@ router.post('/reset', authMiddleware, adminMiddleware, async (req, res) => {
|
||||
});
|
||||
|
||||
const VALID_CODES = {
|
||||
'JAMA-TEAM-2024': { appType:'JAMA-Team', branding:true, groupManager:true, scheduleManager:true },
|
||||
'JAMA-BRAND-2024': { appType:'JAMA-Brand', branding:true, groupManager:false, scheduleManager:false },
|
||||
'JAMA-FULL-2024': { appType:'JAMA-Team', branding:true, groupManager:true, scheduleManager:true },
|
||||
'ROSTERCHIRP-TEAM-2024': { appType:'RosterChirp-Team', branding:true, groupManager:true, scheduleManager:true },
|
||||
'ROSTERCHIRP-BRAND-2024': { appType:'RosterChirp-Brand', branding:true, groupManager:false, scheduleManager:false },
|
||||
'ROSTERCHIRP-FULL-2024': { appType:'RosterChirp-Team', branding:true, groupManager:true, scheduleManager:true },
|
||||
};
|
||||
|
||||
router.post('/register', authMiddleware, adminMiddleware, async (req, res) => {
|
||||
@@ -124,11 +124,11 @@ router.post('/register', authMiddleware, adminMiddleware, async (req, res) => {
|
||||
try {
|
||||
if (!code?.trim()) {
|
||||
await setSetting(req.schema, 'registration_code', '');
|
||||
await setSetting(req.schema, 'app_type', 'JAMA-Chat');
|
||||
await setSetting(req.schema, 'app_type', 'RosterChirp-Chat');
|
||||
await setSetting(req.schema, 'feature_branding', 'false');
|
||||
await setSetting(req.schema, 'feature_group_manager', 'false');
|
||||
await setSetting(req.schema, 'feature_schedule_manager', 'false');
|
||||
return res.json({ success:true, features:{branding:false,groupManager:false,scheduleManager:false,appType:'JAMA-Chat'} });
|
||||
return res.json({ success:true, features:{branding:false,groupManager:false,scheduleManager:false,appType:'RosterChirp-Chat'} });
|
||||
}
|
||||
const match = VALID_CODES[code.trim().toUpperCase()];
|
||||
if (!match) return res.status(400).json({ error: 'Invalid registration code' });
|
||||
|
||||
@@ -7,7 +7,7 @@ async function getLinkPreview(url) {
|
||||
|
||||
const res = await fetch(url, {
|
||||
signal: controller.signal,
|
||||
headers: { 'User-Agent': 'JamaBot/1.0' }
|
||||
headers: { 'User-Agent': 'RosterChirpBot/1.0' }
|
||||
});
|
||||
clearTimeout(timeout);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user