core: client and service schema for badge purchases

This commit is contained in:
Evgeny @ SimpleX Chat
2026-08-09 16:28:52 +00:00
parent 7ce2e6583e
commit 8fdff7750f
5 changed files with 423 additions and 133 deletions
@@ -1,11 +1,12 @@
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
module BadgeService.Store.Postgres.Migrations (badgeServiceSchemaMigrations) where
import Data.List (sortOn)
import Data.Text (Text)
import qualified Data.Text as T
import Simplex.Chat.Store.Postgres.Migrations.M20260731_user_badges (badgeSchema, badgeSchemaDown, withPrefix)
import Simplex.Messaging.Agent.Store.Shared (Migration (..))
import Text.RawString.QQ (r)
@@ -19,19 +20,18 @@ schemaMigrations =
[ ("20260806_badge_service_schema", m20260806_badge_service_schema, Just down_m20260806_badge_service_schema)
]
-- the client tables are in the same database, so the service tables are the same names with this prefix
servicePrefix :: Text
servicePrefix = "sx_badge_service_"
m20260806_badge_service_schema :: Text
m20260806_badge_service_schema =
T.pack
[r|
CREATE TABLE sx_badge_service_test(
test_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
created_at TIMESTAMPTZ NOT NULL DEFAULT (now())
);
|]
badgeSchema servicePrefix
<> withPrefix
servicePrefix
[r|
ALTER TABLE @badge_purchases ADD COLUMN receipt_hash BYTEA;
|]
down_m20260806_badge_service_schema :: Text
down_m20260806_badge_service_schema =
T.pack
[r|
DROP TABLE sx_badge_service_test;
|]
down_m20260806_badge_service_schema = badgeSchemaDown servicePrefix
@@ -1,11 +1,14 @@
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
module BadgeService.Store.SQLite.Migrations (badgeServiceSchemaMigrations) where
import Data.List (sortOn)
import Data.Text (Text)
import Database.SQLite.Simple (Query (..))
import Database.SQLite.Simple.QQ (sql)
import Simplex.Chat.Store.SQLite.Migrations.M20260731_user_badges (badgeSchema, badgeSchemaDown, withPrefix)
import Simplex.Messaging.Agent.Store.Shared (Migration (..))
badgeServiceSchemaMigrations :: [Migration]
@@ -18,17 +21,18 @@ schemaMigrations =
[ ("20260806_badge_service_schema", m20260806_badge_service_schema, Just down_m20260806_badge_service_schema)
]
-- the client tables are in the same database, so the service tables are the same names with this prefix
servicePrefix :: Text
servicePrefix = "sx_badge_service_"
m20260806_badge_service_schema :: Query
m20260806_badge_service_schema =
[sql|
CREATE TABLE sx_badge_service_test(
test_id INTEGER PRIMARY KEY AUTOINCREMENT,
created_at TEXT NOT NULL DEFAULT(datetime('now'))
);
|]
badgeSchema servicePrefix
<> withPrefix
servicePrefix
[sql|
ALTER TABLE @badge_purchases ADD COLUMN receipt_hash BLOB;
|]
down_m20260806_badge_service_schema :: Query
down_m20260806_badge_service_schema =
[sql|
DROP TABLE sx_badge_service_test;
|]
down_m20260806_badge_service_schema = badgeSchemaDown servicePrefix
+2
View File
@@ -155,6 +155,7 @@ library
Simplex.Chat.Store.Postgres.Migrations.M20260716_signed_history
Simplex.Chat.Store.Postgres.Migrations.M20260720_server_roles
Simplex.Chat.Store.Postgres.Migrations.M20260723_contact_request_rejection
Simplex.Chat.Store.Postgres.Migrations.M20260731_user_badges
else
exposed-modules:
Simplex.Chat.Archive
@@ -325,6 +326,7 @@ library
Simplex.Chat.Store.SQLite.Migrations.M20260716_signed_history
Simplex.Chat.Store.SQLite.Migrations.M20260720_server_roles
Simplex.Chat.Store.SQLite.Migrations.M20260723_contact_request_rejection
Simplex.Chat.Store.SQLite.Migrations.M20260731_user_badges
other-modules:
Paths_simplex_chat
hs-source-dirs:
@@ -0,0 +1,226 @@
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Store.Postgres.Migrations.M20260731_user_badges where
import Data.Text (Text)
import qualified Data.Text as T
import Text.RawString.QQ (r)
-- the same tables in the client and in the badge service; '@' is replaced with the table name prefix
badgeSchema :: Text -> Text
badgeSchema pfx = withPrefix pfx badgeSchemaTables
badgeSchemaDown :: Text -> Text
badgeSchemaDown pfx = withPrefix pfx badgeSchemaTablesDown
withPrefix :: Text -> Text -> Text
withPrefix = T.replace "@"
badgeSchemaTables :: Text
badgeSchemaTables =
[r|
CREATE TABLE @invoices(
invoice_id TEXT NOT NULL PRIMARY KEY,
provider TEXT NOT NULL,
price BIGINT NOT NULL,
discount_amount BIGINT,
credit_amount BIGINT,
amount BIGINT NOT NULL,
currency TEXT NOT NULL,
payment_url TEXT,
payment_address TEXT,
payment_crypto_amount TEXT,
expires_at TIMESTAMPTZ NOT NULL,
status TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
CREATE TABLE @payments(
payment_id TEXT NOT NULL PRIMARY KEY,
invoice_id TEXT REFERENCES @invoices,
provider TEXT NOT NULL,
provider_ref TEXT,
amount BIGINT,
currency TEXT,
status TEXT NOT NULL,
exception TEXT,
renews_at TIMESTAMPTZ,
grace_until TIMESTAMPTZ,
cancelled SMALLINT NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX @idx_payments_provider_ref ON @payments(provider, provider_ref);
CREATE INDEX @idx_payments_invoice ON @payments(invoice_id);
CREATE TABLE @charges(
charge_id TEXT NOT NULL PRIMARY KEY,
payment_id TEXT NOT NULL REFERENCES @payments ON DELETE CASCADE,
provider_charge_ref TEXT NOT NULL,
period_start TIMESTAMPTZ NOT NULL,
period_end TIMESTAMPTZ NOT NULL,
amount BIGINT NOT NULL,
currency TEXT NOT NULL,
charged_at TIMESTAMPTZ NOT NULL,
UNIQUE(payment_id, provider_charge_ref)
);
CREATE TABLE @badge_prices(
price_id TEXT NOT NULL PRIMARY KEY,
badge_type TEXT NOT NULL,
month_price BIGINT NOT NULL,
currency TEXT NOT NULL,
status TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL
);
CREATE TABLE @badge_offers(
offer_id TEXT NOT NULL PRIMARY KEY,
price_id TEXT REFERENCES @badge_prices,
months SMALLINT NOT NULL,
free_months SMALLINT,
discount SMALLINT,
status TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL
);
CREATE TABLE @badge_purchases(
badge_purchase_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
purchase_key BYTEA NOT NULL,
master_key BYTEA NOT NULL,
initial_badge_type TEXT NOT NULL,
current_badge_type TEXT NOT NULL,
payment_id TEXT REFERENCES @payments,
status TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL,
UNIQUE(purchase_key),
UNIQUE(payment_id)
);
CREATE TABLE @badge_invoices(
invoice_id TEXT NOT NULL PRIMARY KEY REFERENCES @invoices ON DELETE CASCADE,
badge_purchase_id BIGINT NOT NULL REFERENCES @badge_purchases ON DELETE CASCADE,
price_id TEXT NOT NULL REFERENCES @badge_prices,
offer_id TEXT REFERENCES @badge_offers,
months SMALLINT NOT NULL,
created_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX @idx_badge_invoices_purchase ON @badge_invoices(badge_purchase_id);
CREATE TABLE @badge_subscription_changes(
change_id TEXT NOT NULL PRIMARY KEY,
badge_purchase_id BIGINT NOT NULL REFERENCES @badge_purchases ON DELETE CASCADE,
from_badge_type TEXT NOT NULL,
to_badge_type TEXT NOT NULL,
from_provider_ref TEXT,
to_provider_ref TEXT,
effective TEXT NOT NULL,
status TEXT NOT NULL,
effective_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX @idx_badge_subscription_changes_purchase ON @badge_subscription_changes(badge_purchase_id);
CREATE TABLE @badge_ledger(
entry_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
entry_uuid TEXT NOT NULL,
badge_purchase_id BIGINT NOT NULL REFERENCES @badge_purchases ON DELETE CASCADE,
change_months SMALLINT NOT NULL,
balance_months SMALLINT NOT NULL,
balance_start_ts TIMESTAMPTZ NOT NULL,
balance_badge_type TEXT NOT NULL,
was_paused_since TIMESTAMPTZ,
service_created_at TIMESTAMPTZ NOT NULL,
created_at TIMESTAMPTZ NOT NULL,
entry_type TEXT NOT NULL,
entry_credit_type TEXT,
entry_debit_type TEXT,
payment_id TEXT REFERENCES @payments,
charge_id TEXT REFERENCES @charges,
from_purchase_id BIGINT REFERENCES @badge_purchases,
to_purchase_id BIGINT REFERENCES @badge_purchases
);
CREATE UNIQUE INDEX @idx_badge_ledger_uuid ON @badge_ledger(entry_uuid);
CREATE INDEX @idx_badge_ledger_purchase ON @badge_ledger(badge_purchase_id, entry_id);
CREATE INDEX @idx_badge_ledger_payment ON @badge_ledger(payment_id);
CREATE INDEX @idx_badge_ledger_charge ON @badge_ledger(charge_id);
CREATE INDEX @idx_badge_ledger_from_purchase ON @badge_ledger(from_purchase_id);
CREATE INDEX @idx_badge_ledger_to_purchase ON @badge_ledger(to_purchase_id);
CREATE TABLE @badge_issuances(
issuance_id TEXT NOT NULL PRIMARY KEY,
badge_purchase_id BIGINT NOT NULL REFERENCES @badge_purchases ON DELETE CASCADE,
entry_id BIGINT REFERENCES @badge_ledger,
badge_type TEXT NOT NULL,
period_start TIMESTAMPTZ NOT NULL,
period_end TIMESTAMPTZ NOT NULL,
expiry TIMESTAMPTZ NOT NULL,
credential BYTEA NOT NULL,
created_at TIMESTAMPTZ NOT NULL
);
CREATE INDEX @idx_badge_issuances_purchase ON @badge_issuances(badge_purchase_id, issuance_id);
|]
badgeSchemaTablesDown :: Text
badgeSchemaTablesDown =
[r|
DROP TABLE @badge_issuances;
DROP TABLE @badge_ledger;
DROP TABLE @badge_subscription_changes;
DROP TABLE @badge_invoices;
DROP TABLE @badge_purchases;
DROP TABLE @charges;
DROP TABLE @payments;
DROP TABLE @invoices;
DROP TABLE @badge_offers;
DROP TABLE @badge_prices;
|]
m20260731_user_badges :: Text
m20260731_user_badges =
badgeSchema ""
<> [r|
ALTER TABLE badge_purchases ADD COLUMN user_id BIGINT REFERENCES users ON DELETE CASCADE;
ALTER TABLE badge_purchases ADD COLUMN purchase_priv_key BYTEA;
ALTER TABLE badge_purchases ADD COLUMN receipt_code TEXT;
ALTER TABLE badge_purchases ADD COLUMN alert_acked_kind TEXT;
ALTER TABLE badge_purchases ADD COLUMN alert_acked_episode TEXT;
ALTER TABLE badge_purchases ADD COLUMN alert_snooze_until TIMESTAMPTZ;
ALTER TABLE payments ADD COLUMN evidence BYTEA;
ALTER TABLE badge_ledger ADD COLUMN entry_type_unknown SMALLINT NOT NULL DEFAULT 0;
ALTER TABLE badge_ledger ADD COLUMN entry_type_value TEXT;
CREATE INDEX idx_badge_purchases_user ON badge_purchases(user_id);
ALTER TABLE users ADD COLUMN shown_badge_id BIGINT REFERENCES badge_purchases ON DELETE SET NULL;
|]
down_m20260731_user_badges :: Text
down_m20260731_user_badges =
[r|
ALTER TABLE users DROP COLUMN shown_badge_id;
|]
<> badgeSchemaDown ""
@@ -1,52 +1,50 @@
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Store.SQLite.Migrations.M20260731_user_badges where
import Database.SQLite.Simple (Query)
import Data.Text (Text)
import qualified Data.Text as T
import Database.SQLite.Simple (Query (..))
import Database.SQLite.Simple.QQ (sql)
m20260731_user_badges :: Query
m20260731_user_badges =
-- the same tables in the client and in the badge service; '@' is replaced with the table name prefix
badgeSchema :: Text -> Query
badgeSchema pfx = withPrefix pfx badgeSchemaTables
badgeSchemaDown :: Text -> Query
badgeSchemaDown pfx = withPrefix pfx badgeSchemaTablesDown
withPrefix :: Text -> Query -> Query
withPrefix pfx = Query . T.replace "@" pfx . fromQuery
badgeSchemaTables :: Query
badgeSchemaTables =
[sql|
CREATE TABLE badge_prices(
price_id TEXT NOT NULL PRIMARY KEY,
badge_type TEXT NOT NULL,
month_price INTEGER NOT NULL,
CREATE TABLE @invoices(
invoice_id TEXT NOT NULL PRIMARY KEY,
provider TEXT NOT NULL,
price INTEGER NOT NULL,
discount_amount INTEGER,
credit_amount INTEGER,
amount INTEGER NOT NULL,
currency TEXT NOT NULL,
payment_url TEXT,
payment_address TEXT,
payment_crypto_amount TEXT,
expires_at TEXT NOT NULL,
status TEXT NOT NULL,
created_at TEXT NOT NULL
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE TABLE badge_offers(
offer_id TEXT NOT NULL PRIMARY KEY,
price_id TEXT REFERENCES badge_prices,
months INTEGER NOT NULL,
discount_type TEXT NOT NULL,
free_months INTEGER,
discount INTEGER,
status TEXT NOT NULL,
created_at TEXT NOT NULL
);
CREATE TABLE payments(
payment_id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL REFERENCES users ON DELETE CASCADE,
purchase_key BLOB NOT NULL,
badge_type TEXT NOT NULL,
price_id TEXT REFERENCES badge_prices,
offer_id TEXT REFERENCES badge_offers,
invoice_uuid TEXT,
months INTEGER,
amount INTEGER,
currency TEXT,
CREATE TABLE @payments(
payment_id TEXT NOT NULL PRIMARY KEY,
invoice_id TEXT REFERENCES @invoices,
provider TEXT NOT NULL,
provider_ref TEXT,
invoice_url TEXT,
invoice_address TEXT,
invoice_crypto_amount TEXT,
invoice_expires_at TEXT,
evidence BLOB,
receipt_code TEXT,
amount INTEGER,
currency TEXT,
status TEXT NOT NULL,
exception TEXT,
renews_at TEXT,
@@ -56,14 +54,13 @@ CREATE TABLE payments(
updated_at TEXT NOT NULL
);
CREATE INDEX idx_payments_purchase_key ON payments(purchase_key);
CREATE INDEX @idx_payments_provider_ref ON @payments(provider, provider_ref);
CREATE UNIQUE INDEX idx_payments_invoice_uuid ON payments(invoice_uuid);
CREATE INDEX @idx_payments_invoice ON @payments(invoice_id);
CREATE TABLE charges(
charge_id INTEGER PRIMARY KEY,
payment_id INTEGER NOT NULL REFERENCES payments,
invoice_uuid TEXT NOT NULL,
CREATE TABLE @charges(
charge_id TEXT NOT NULL PRIMARY KEY,
payment_id TEXT NOT NULL REFERENCES @payments ON DELETE CASCADE,
provider_charge_ref TEXT NOT NULL,
period_start TEXT NOT NULL,
period_end TEXT NOT NULL,
@@ -73,71 +70,70 @@ CREATE TABLE charges(
UNIQUE(payment_id, provider_charge_ref)
);
CREATE TABLE badge_purchases(
badge_purchase_id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL REFERENCES users ON DELETE CASCADE,
purchase_key BLOB NOT NULL,
purchase_priv_key BLOB NOT NULL,
master_key BLOB NOT NULL,
CREATE TABLE @badge_prices(
price_id TEXT NOT NULL PRIMARY KEY,
badge_type TEXT NOT NULL,
price_id TEXT REFERENCES badge_prices,
offer_id TEXT REFERENCES badge_offers,
payment_id INTEGER NOT NULL REFERENCES payments,
month_price INTEGER NOT NULL,
currency TEXT NOT NULL,
status TEXT NOT NULL,
badge_key_idx INTEGER,
badge_signature BLOB,
badge_expiry TEXT,
alert_acked_kind TEXT,
alert_acked_episode TEXT,
alert_snooze_until TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
UNIQUE(purchase_key)
);
CREATE UNIQUE INDEX idx_badge_purchases_live ON badge_purchases(user_id, (badge_type = 'investor'))
WHERE status IN ('acquiring', 'issued');
ALTER TABLE users ADD COLUMN shown_badge_id INTEGER REFERENCES badge_purchases ON DELETE SET NULL;
|]
<> badgeLedgerTable
<> [sql|
ALTER TABLE badge_ledger ADD COLUMN entry_type_unknown INTEGER NOT NULL DEFAULT 0;
ALTER TABLE badge_ledger ADD COLUMN entry_type_value TEXT;
CREATE UNIQUE INDEX idx_badge_ledger_uuid ON badge_ledger(entry_uuid);
CREATE INDEX idx_badge_ledger_purchase ON badge_ledger(badge_purchase_id, entry_id);
CREATE INDEX idx_badge_ledger_invoice ON badge_ledger(invoice_id);
CREATE INDEX idx_badge_ledger_charge ON badge_ledger(charge_id);
CREATE INDEX idx_badge_ledger_from_purchase ON badge_ledger(from_purchase_id);
CREATE INDEX idx_badge_ledger_to_purchase ON badge_ledger(to_purchase_id);
CREATE TABLE badge_issuances(
issuance_id INTEGER PRIMARY KEY,
badge_purchase_id INTEGER NOT NULL REFERENCES badge_purchases ON DELETE CASCADE,
period_start TEXT,
period_end TEXT,
expiry TEXT,
entry_id INTEGER REFERENCES badge_ledger,
created_at TEXT NOT NULL
);
CREATE INDEX idx_badge_issuances_purchase ON badge_issuances(badge_purchase_id, issuance_id);
|]
CREATE TABLE @badge_offers(
offer_id TEXT NOT NULL PRIMARY KEY,
price_id TEXT REFERENCES @badge_prices,
months INTEGER NOT NULL,
free_months INTEGER,
discount INTEGER,
status TEXT NOT NULL,
created_at TEXT NOT NULL
);
badgeLedgerTable :: Query
badgeLedgerTable =
[sql|
CREATE TABLE badge_ledger(
CREATE TABLE @badge_purchases(
badge_purchase_id INTEGER PRIMARY KEY AUTOINCREMENT,
purchase_key BLOB NOT NULL,
master_key BLOB NOT NULL,
initial_badge_type TEXT NOT NULL,
current_badge_type TEXT NOT NULL,
payment_id TEXT REFERENCES @payments,
status TEXT NOT NULL,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
UNIQUE(purchase_key),
UNIQUE(payment_id)
);
CREATE TABLE @badge_invoices(
invoice_id TEXT NOT NULL PRIMARY KEY REFERENCES @invoices ON DELETE CASCADE,
badge_purchase_id INTEGER NOT NULL REFERENCES @badge_purchases ON DELETE CASCADE,
price_id TEXT NOT NULL REFERENCES @badge_prices,
offer_id TEXT REFERENCES @badge_offers,
months INTEGER NOT NULL,
created_at TEXT NOT NULL
);
CREATE INDEX @idx_badge_invoices_purchase ON @badge_invoices(badge_purchase_id);
CREATE TABLE @badge_subscription_changes(
change_id TEXT NOT NULL PRIMARY KEY,
badge_purchase_id INTEGER NOT NULL REFERENCES @badge_purchases ON DELETE CASCADE,
from_badge_type TEXT NOT NULL,
to_badge_type TEXT NOT NULL,
from_provider_ref TEXT,
to_provider_ref TEXT,
effective TEXT NOT NULL,
status TEXT NOT NULL,
effective_at TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE INDEX @idx_badge_subscription_changes_purchase ON @badge_subscription_changes(badge_purchase_id);
CREATE TABLE @badge_ledger(
entry_id INTEGER PRIMARY KEY AUTOINCREMENT,
entry_uuid TEXT NOT NULL,
badge_purchase_id INTEGER NOT NULL REFERENCES badge_purchases ON DELETE CASCADE,
badge_purchase_id INTEGER NOT NULL REFERENCES @badge_purchases ON DELETE CASCADE,
change_months INTEGER NOT NULL,
balance_months INTEGER NOT NULL,
balance_start_ts TEXT NOT NULL,
@@ -148,22 +144,84 @@ CREATE TABLE badge_ledger(
entry_type TEXT NOT NULL,
entry_credit_type TEXT,
entry_debit_type TEXT,
invoice_id INTEGER REFERENCES payments,
charge_id INTEGER REFERENCES charges,
from_purchase_id INTEGER REFERENCES badge_purchases,
to_purchase_id INTEGER REFERENCES badge_purchases
payment_id TEXT REFERENCES @payments,
charge_id TEXT REFERENCES @charges,
from_purchase_id INTEGER REFERENCES @badge_purchases,
to_purchase_id INTEGER REFERENCES @badge_purchases
);
CREATE UNIQUE INDEX @idx_badge_ledger_uuid ON @badge_ledger(entry_uuid);
CREATE INDEX @idx_badge_ledger_purchase ON @badge_ledger(badge_purchase_id, entry_id);
CREATE INDEX @idx_badge_ledger_payment ON @badge_ledger(payment_id);
CREATE INDEX @idx_badge_ledger_charge ON @badge_ledger(charge_id);
CREATE INDEX @idx_badge_ledger_from_purchase ON @badge_ledger(from_purchase_id);
CREATE INDEX @idx_badge_ledger_to_purchase ON @badge_ledger(to_purchase_id);
CREATE TABLE @badge_issuances(
issuance_id TEXT NOT NULL PRIMARY KEY,
badge_purchase_id INTEGER NOT NULL REFERENCES @badge_purchases ON DELETE CASCADE,
entry_id INTEGER REFERENCES @badge_ledger,
badge_type TEXT NOT NULL,
period_start TEXT NOT NULL,
period_end TEXT NOT NULL,
expiry TEXT NOT NULL,
credential BLOB NOT NULL,
created_at TEXT NOT NULL
);
CREATE INDEX @idx_badge_issuances_purchase ON @badge_issuances(badge_purchase_id, issuance_id);
|]
badgeSchemaTablesDown :: Query
badgeSchemaTablesDown =
[sql|
DROP TABLE @badge_issuances;
DROP TABLE @badge_ledger;
DROP TABLE @badge_subscription_changes;
DROP TABLE @badge_invoices;
DROP TABLE @badge_purchases;
DROP TABLE @charges;
DROP TABLE @payments;
DROP TABLE @invoices;
DROP TABLE @badge_offers;
DROP TABLE @badge_prices;
|]
m20260731_user_badges :: Query
m20260731_user_badges =
badgeSchema ""
<> [sql|
ALTER TABLE badge_purchases ADD COLUMN user_id INTEGER REFERENCES users ON DELETE CASCADE;
ALTER TABLE badge_purchases ADD COLUMN purchase_priv_key BLOB;
ALTER TABLE badge_purchases ADD COLUMN receipt_code TEXT;
ALTER TABLE badge_purchases ADD COLUMN alert_acked_kind TEXT;
ALTER TABLE badge_purchases ADD COLUMN alert_acked_episode TEXT;
ALTER TABLE badge_purchases ADD COLUMN alert_snooze_until TEXT;
ALTER TABLE payments ADD COLUMN evidence BLOB;
ALTER TABLE badge_ledger ADD COLUMN entry_type_unknown INTEGER NOT NULL DEFAULT 0;
ALTER TABLE badge_ledger ADD COLUMN entry_type_value TEXT;
CREATE INDEX idx_badge_purchases_user ON badge_purchases(user_id);
ALTER TABLE users ADD COLUMN shown_badge_id INTEGER REFERENCES badge_purchases ON DELETE SET NULL;
|]
down_m20260731_user_badges :: Query
down_m20260731_user_badges =
[sql|
DROP TABLE badge_issuances;
DROP TABLE badge_ledger;
ALTER TABLE users DROP COLUMN shown_badge_id;
DROP TABLE badge_purchases;
DROP TABLE charges;
DROP TABLE payments;
DROP TABLE badge_offers;
DROP TABLE badge_prices;
|]
<> badgeSchemaDown ""