v0.1.0:24 — Keysat licensing service end-to-end
Daemon, StartOS wrapper, admin SPA, public buy/thank-you pages, discount codes, free-license redemption, Apply-discount UX, self-licensing, and v0.1.0 release notes.
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
-- Initial schema for the licensing service.
|
||||
--
|
||||
-- SQLite is used in WAL mode; all tables are intentionally flat and indexed
|
||||
-- for the common query paths (validate by key_id, list by product, look up by
|
||||
-- invoice_id from BTCPay webhooks).
|
||||
|
||||
PRAGMA foreign_keys = ON;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS products (
|
||||
id TEXT PRIMARY KEY, -- UUID v4
|
||||
slug TEXT NOT NULL UNIQUE, -- human-friendly id used in URLs
|
||||
name TEXT NOT NULL,
|
||||
description TEXT NOT NULL DEFAULT '',
|
||||
price_sats INTEGER NOT NULL, -- price in satoshis
|
||||
active INTEGER NOT NULL DEFAULT 1, -- boolean; 0 hides from listings
|
||||
metadata_json TEXT NOT NULL DEFAULT '{}', -- arbitrary developer metadata
|
||||
created_at TEXT NOT NULL, -- ISO-8601 UTC
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_products_active ON products(active);
|
||||
|
||||
-- Invoices track BTCPay payment attempts. One invoice maps to at most one
|
||||
-- license. If payment never completes, the invoice just sits in 'pending' /
|
||||
-- 'expired' and no license is ever issued.
|
||||
CREATE TABLE IF NOT EXISTS invoices (
|
||||
id TEXT PRIMARY KEY, -- UUID v4 (our id)
|
||||
btcpay_invoice_id TEXT NOT NULL UNIQUE, -- id from BTCPay Server
|
||||
product_id TEXT NOT NULL,
|
||||
status TEXT NOT NULL, -- pending | settled | expired | invalid
|
||||
buyer_email TEXT, -- optional, supplied at purchase
|
||||
buyer_note TEXT, -- optional purchase note
|
||||
amount_sats INTEGER NOT NULL,
|
||||
checkout_url TEXT NOT NULL, -- BTCPay checkout URL
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL,
|
||||
FOREIGN KEY (product_id) REFERENCES products(id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_invoices_btcpay_id ON invoices(btcpay_invoice_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_invoices_status ON invoices(status);
|
||||
|
||||
-- Licenses are the issued proofs-of-purchase. The `key_id` is what a client
|
||||
-- presents when validating; the actual user-facing license key string is a
|
||||
-- signed envelope containing this id plus metadata (see crypto module).
|
||||
CREATE TABLE IF NOT EXISTS licenses (
|
||||
id TEXT PRIMARY KEY, -- UUID v4, also the `license_id` in the signed payload
|
||||
product_id TEXT NOT NULL,
|
||||
invoice_id TEXT UNIQUE, -- NULL for manually-issued / comped licenses
|
||||
status TEXT NOT NULL, -- active | revoked
|
||||
fingerprint TEXT, -- optional machine fingerprint locked on first validation
|
||||
bound_identity TEXT, -- optional user identity (email, pubkey, etc.) locked on first use
|
||||
issued_at TEXT NOT NULL,
|
||||
revoked_at TEXT,
|
||||
revocation_reason TEXT,
|
||||
metadata_json TEXT NOT NULL DEFAULT '{}',
|
||||
FOREIGN KEY (product_id) REFERENCES products(id),
|
||||
FOREIGN KEY (invoice_id) REFERENCES invoices(id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_licenses_product ON licenses(product_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_licenses_status ON licenses(status);
|
||||
|
||||
-- Audit log of validation attempts. Useful for abuse detection and for
|
||||
-- developers building rate-limiting policies on top.
|
||||
CREATE TABLE IF NOT EXISTS validation_log (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
license_id TEXT,
|
||||
product_id TEXT,
|
||||
fingerprint TEXT,
|
||||
result TEXT NOT NULL, -- ok | bad_signature | revoked | product_mismatch | fingerprint_mismatch | not_found
|
||||
client_ip TEXT,
|
||||
user_agent TEXT,
|
||||
occurred_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_validation_license ON validation_log(license_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_validation_time ON validation_log(occurred_at);
|
||||
|
||||
-- Server-wide signing key. Stored here (rather than on disk) so a SQLite
|
||||
-- backup captures the full server state. The private key is PEM-encoded.
|
||||
-- Generated on first boot if no row exists.
|
||||
CREATE TABLE IF NOT EXISTS server_keys (
|
||||
id INTEGER PRIMARY KEY CHECK (id = 1), -- singleton
|
||||
algorithm TEXT NOT NULL, -- 'ed25519'
|
||||
public_key_pem TEXT NOT NULL,
|
||||
private_key_pem TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL
|
||||
);
|
||||
Reference in New Issue
Block a user