mirror of
https://github.com/screentinker/screentinker.git
synced 2026-05-15 07:32:23 -06:00
ScreenTinker - open source digital signage management software. MIT License, all features included, no license gates. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
140 lines
5.9 KiB
JavaScript
140 lines
5.9 KiB
JavaScript
const express = require('express');
|
|
const router = express.Router();
|
|
const { db } = require('../db/database');
|
|
const { requireAuth, requireAdmin, requireSuperAdmin } = require('../middleware/auth');
|
|
const { getUserPlan, getUserDeviceCount, getUserStorageMB } = require('../middleware/subscription');
|
|
const config = require('../config');
|
|
|
|
// Get all plans
|
|
router.get('/plans', (req, res) => {
|
|
const plans = db.prepare('SELECT * FROM plans WHERE active = 1 ORDER BY sort_order ASC').all();
|
|
res.json(plans);
|
|
});
|
|
|
|
// Get current user's subscription info
|
|
router.get('/me', requireAuth, (req, res) => {
|
|
const plan = getUserPlan(req.user.id);
|
|
const deviceCount = getUserDeviceCount(req.user.id);
|
|
const storageMB = getUserStorageMB(req.user.id);
|
|
|
|
res.json({
|
|
plan: {
|
|
id: plan.plan_id,
|
|
name: plan.plan_name,
|
|
display_name: plan.plan_display_name,
|
|
max_devices: plan.max_devices,
|
|
max_storage_mb: plan.max_storage_mb,
|
|
remote_control: !!plan.remote_control,
|
|
remote_url: !!plan.remote_url,
|
|
priority_support: !!plan.priority_support,
|
|
price_monthly: plan.price_monthly,
|
|
price_yearly: plan.price_yearly,
|
|
},
|
|
usage: {
|
|
devices: deviceCount,
|
|
devices_limit: plan.max_devices,
|
|
storage_mb: storageMB,
|
|
storage_limit_mb: plan.max_storage_mb,
|
|
},
|
|
subscription: {
|
|
status: plan.subscription_status,
|
|
ends: plan.subscription_ends,
|
|
stripe_customer_id: plan.stripe_customer_id,
|
|
stripe_subscription_id: plan.stripe_subscription_id,
|
|
},
|
|
trial: {
|
|
active: plan.trial_active || false,
|
|
days_left: plan.trial_days_left || 0,
|
|
end: plan.trial_end ? new Date(plan.trial_end * 1000).toISOString() : null,
|
|
plan: plan.trial_plan || null,
|
|
},
|
|
self_hosted: config.selfHosted,
|
|
});
|
|
});
|
|
|
|
// Admin: assign plan to user
|
|
router.post('/assign', requireAuth, requireSuperAdmin, (req, res) => {
|
|
const { user_id, plan_id } = req.body;
|
|
if (!user_id || !plan_id) return res.status(400).json({ error: 'user_id and plan_id required' });
|
|
|
|
const plan = db.prepare('SELECT * FROM plans WHERE id = ?').get(plan_id);
|
|
if (!plan) return res.status(404).json({ error: 'Plan not found' });
|
|
|
|
const user = db.prepare('SELECT * FROM users WHERE id = ?').get(user_id);
|
|
if (!user) return res.status(404).json({ error: 'User not found' });
|
|
|
|
db.prepare("UPDATE users SET plan_id = ?, subscription_status = 'active', updated_at = strftime('%s','now') WHERE id = ?")
|
|
.run(plan_id, user_id);
|
|
|
|
res.json({ success: true, plan: plan.display_name });
|
|
});
|
|
|
|
// Admin: update plan details
|
|
router.put('/plans/:id', requireAuth, requireAdmin, (req, res) => {
|
|
const plan = db.prepare('SELECT * FROM plans WHERE id = ?').get(req.params.id);
|
|
if (!plan) return res.status(404).json({ error: 'Plan not found' });
|
|
|
|
const { display_name, max_devices, max_storage_mb, remote_control, remote_url,
|
|
priority_support, price_monthly, price_yearly, active } = req.body;
|
|
|
|
const updates = [];
|
|
const values = [];
|
|
if (display_name !== undefined) { updates.push('display_name = ?'); values.push(display_name); }
|
|
if (max_devices !== undefined) { updates.push('max_devices = ?'); values.push(max_devices); }
|
|
if (max_storage_mb !== undefined) { updates.push('max_storage_mb = ?'); values.push(max_storage_mb); }
|
|
if (remote_control !== undefined) { updates.push('remote_control = ?'); values.push(remote_control ? 1 : 0); }
|
|
if (remote_url !== undefined) { updates.push('remote_url = ?'); values.push(remote_url ? 1 : 0); }
|
|
if (priority_support !== undefined) { updates.push('priority_support = ?'); values.push(priority_support ? 1 : 0); }
|
|
if (price_monthly !== undefined) { updates.push('price_monthly = ?'); values.push(price_monthly); }
|
|
if (price_yearly !== undefined) { updates.push('price_yearly = ?'); values.push(price_yearly); }
|
|
if (active !== undefined) { updates.push('active = ?'); values.push(active ? 1 : 0); }
|
|
|
|
if (updates.length > 0) {
|
|
values.push(req.params.id);
|
|
db.prepare(`UPDATE plans SET ${updates.join(', ')} WHERE id = ?`).run(...values);
|
|
}
|
|
|
|
const updated = db.prepare('SELECT * FROM plans WHERE id = ?').get(req.params.id);
|
|
res.json(updated);
|
|
});
|
|
|
|
// Admin: create custom plan
|
|
router.post('/plans', requireAuth, requireAdmin, (req, res) => {
|
|
const { id, name, display_name, max_devices, max_storage_mb, remote_control,
|
|
remote_url, priority_support, price_monthly, price_yearly } = req.body;
|
|
|
|
if (!id || !name || !display_name) return res.status(400).json({ error: 'id, name, and display_name required' });
|
|
|
|
const existing = db.prepare('SELECT id FROM plans WHERE id = ?').get(id);
|
|
if (existing) return res.status(409).json({ error: 'Plan ID already exists' });
|
|
|
|
const maxOrder = db.prepare('SELECT MAX(sort_order) as max_order FROM plans').get().max_order || 0;
|
|
|
|
db.prepare(`
|
|
INSERT INTO plans (id, name, display_name, max_devices, max_storage_mb, remote_control, remote_url,
|
|
priority_support, price_monthly, price_yearly, sort_order)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
`).run(id, name, display_name, max_devices || 2, max_storage_mb || 500,
|
|
remote_control ? 1 : 0, remote_url ? 1 : 0, priority_support ? 1 : 0,
|
|
price_monthly || 0, price_yearly || 0, maxOrder + 1);
|
|
|
|
const plan = db.prepare('SELECT * FROM plans WHERE id = ?').get(id);
|
|
res.status(201).json(plan);
|
|
});
|
|
|
|
// Stripe webhook (if configured)
|
|
router.post('/webhook/stripe', express.raw({ type: 'application/json' }), (req, res) => {
|
|
if (!config.stripeSecretKey) return res.status(404).json({ error: 'Stripe not configured' });
|
|
|
|
// TODO: Implement Stripe webhook handling
|
|
// - customer.subscription.created -> activate plan
|
|
// - customer.subscription.updated -> update plan
|
|
// - customer.subscription.deleted -> downgrade to free
|
|
// - invoice.payment_succeeded -> extend subscription
|
|
// - invoice.payment_failed -> mark as past_due
|
|
|
|
res.json({ received: true });
|
|
});
|
|
|
|
module.exports = router;
|