feat: lizenz aendern - PATCH /api/v1/licenses/:key

- Aendert max_activations (z.B. -1 unbegrenzt), email, note, expires_at, status.
- Partielles Update, validiert; nur erlaubte Felder. Tests + README.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
s4luorth
2026-06-07 15:53:31 +02:00
parent e691b675cd
commit b03f18a794
3 changed files with 57 additions and 0 deletions

View File

@@ -594,6 +594,49 @@ app.get('/api/v1/licenses/:key', adminOnly, (req, res) => {
});
});
// Modify a license (e.g. change the seat limit). Partial update; only the
// provided fields are changed. Body (JSON), any of:
// max_activations (>=1 or -1), email, note, expires_at (ISO|null), status (active|disabled)
app.patch('/api/v1/licenses/:key', adminOnly, (req, res) => {
const license = Q.licenseByKey.get(req.params.key);
if (!license) return fail(res, 404, 'not found');
const fields = {};
if ('max_activations' in req.body) {
const max = Number(req.body.max_activations);
if (!Number.isInteger(max) || (max < 1 && max !== -1)) {
return fail(res, 400, 'max_activations must be a positive integer or -1 (unlimited)');
}
fields.max_activations = max;
}
if ('email' in req.body) fields.email = req.body.email ? String(req.body.email).trim() : null;
if ('note' in req.body) fields.note = req.body.note ? String(req.body.note).trim() : null;
if ('expires_at' in req.body) fields.expires_at = req.body.expires_at ? String(req.body.expires_at).trim() : null;
if ('status' in req.body) {
const st = String(req.body.status);
if (!['active', 'disabled'].includes(st)) return fail(res, 400, 'status must be active or disabled');
fields.status = st;
}
const cols = Object.keys(fields); // fixed allow-list above → safe to interpolate
if (!cols.length) return fail(res, 400, 'no updatable fields provided');
const setClause = cols.map((c) => `${c} = @${c}`).join(', ');
db.prepare(`UPDATE licenses SET ${setClause} WHERE id = @id`).run({ ...fields, id: license.id });
const u = Q.licenseByKey.get(req.params.key);
return res.json({
ok: true,
key: u.key,
max_activations: u.max_activations,
status: u.status,
email: u.email,
note: u.note,
expires_at: u.expires_at,
});
});
// Disable a license (revokes it everywhere on next check).
app.post('/api/v1/licenses/:key/disable', adminOnly, (req, res) => {
const license = Q.licenseByKey.get(req.params.key);