Riscrivi backend per hosting Node/MariaDB/PM2 (no Cloudflare)
L'utente ha un hosting Virtualmin esistente (musictools.djluza.com)
con Apache + Node 20 + MariaDB + PM2. Tutta l'infrastruttura
Cloudflare (Workers/D1/R2/Pages) e' rimpiazzata con un backend
Express che gira sul server gia' presente, a costo zero.
Cambia:
- server/src/*.ts (Workers) -> server/src/*.js (Node ESM puro)
- D1 (sqlite) -> MariaDB 10.11 via mysql2/promise
- R2 (storage) -> filesystem locale ~/builds/ + signed URL HMAC
- wrangler.toml -> ecosystem.config.cjs (PM2)
- Aggiunto src/migrate.js: runner SQL idempotente
Endpoints invariati - l'app desktop non vede differenze:
- POST /api/license/{activate,validate,deactivate}
- GET /api/latest, /api/download
- POST /api/webhook/lemonsqueezy
- GET /api/health
README riscritto con istruzioni complete: creazione DB,
deploy via rsync/git, config Apache reverse proxy via
Virtualmin, gestione PM2, backup, workflow release.
Email rimane su Resend (deliverability) - free tier sufficiente.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
1 parent
339dd4f3cc
commit
2d8290579b
22 files changed
+892
-790
No files matched your search
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* Webhook Lemon Squeezy.
|
||||
* URL pubblico: https://musictools.djluza.com/api/webhook/lemonsqueezy
|
||||
* Eventi gestiti: order_created, order_refunded.
|
||||
*/
|
||||
|
||||
import crypto from "node:crypto";
|
||||
import { one, exec } from "./db.js";
|
||||
import { generateLicenseKey } from "./license.js";
|
||||
import { sendLicenseEmail } from "./email.js";
|
||||
|
||||
const now = () => Math.floor(Date.now() / 1000);
|
||||
|
||||
function hmacHex(secret, data) {
|
||||
return crypto.createHmac("sha256", secret).update(data).digest("hex");
|
||||
}
|
||||
|
||||
function timingSafe(a, b) {
|
||||
if (a.length !== b.length) return false;
|
||||
const A = Buffer.from(a), B = Buffer.from(b);
|
||||
return crypto.timingSafeEqual(A, B);
|
||||
}
|
||||
|
||||
export async function webhook(req, res) {
|
||||
// express.raw() salva il body come Buffer in req.body
|
||||
const raw = req.body instanceof Buffer ? req.body.toString("utf-8") : "";
|
||||
const sig = req.get("X-Signature") || "";
|
||||
const secret = process.env.LEMONSQUEEZY_SIGNING_SECRET;
|
||||
if (!sig || !secret) return res.status(400).json({ error: "Missing signature" });
|
||||
|
||||
const expected = hmacHex(secret, raw);
|
||||
if (!timingSafe(sig, expected)) {
|
||||
return res.status(401).json({ error: "Invalid signature" });
|
||||
}
|
||||
|
||||
let payload;
|
||||
try { payload = JSON.parse(raw); }
|
||||
catch { return res.status(400).json({ error: "Invalid JSON" }); }
|
||||
|
||||
const eventName = payload?.meta?.event_name || "";
|
||||
const attrs = payload?.data?.attributes || {};
|
||||
const email = String(attrs.user_email || "").trim().toLowerCase();
|
||||
const orderId = String(payload?.data?.id || attrs.order_number || "");
|
||||
if (!email || !orderId) {
|
||||
return res.status(400).json({ error: "Missing email or order_id" });
|
||||
}
|
||||
|
||||
const t = now();
|
||||
|
||||
if (eventName === "order_created") {
|
||||
const key = generateLicenseKey();
|
||||
try {
|
||||
await exec(
|
||||
`INSERT INTO licenses
|
||||
(license_key, email, status, source, order_id, created_at, updated_at)
|
||||
VALUES (?, ?, 'active', 'lemonsqueezy', ?, ?, ?)`,
|
||||
[key, email, orderId, t, t],
|
||||
);
|
||||
} catch (e) {
|
||||
// duplicate webhook delivery
|
||||
if (e?.code === "ER_DUP_ENTRY") {
|
||||
return res.json({ ok: true, duplicate: true });
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
try {
|
||||
await sendLicenseEmail(email, key);
|
||||
} catch (e) {
|
||||
console.error("[email] send failed:", e?.message || e);
|
||||
}
|
||||
return res.json({ ok: true });
|
||||
}
|
||||
|
||||
if (eventName === "order_refunded") {
|
||||
await exec(
|
||||
`UPDATE licenses SET status='refunded', updated_at=? WHERE order_id=?`,
|
||||
[t, orderId],
|
||||
);
|
||||
return res.json({ ok: true });
|
||||
}
|
||||
|
||||
res.json({ ok: true, ignored: eventName });
|
||||
}
|
||||
Reference in new issue
Block a user