core: consolidate badges schema (#7513)

This commit is contained in:
spaced4ndy
2026-09-15 10:15:34 +00:00
committed by GitHub
parent bac8d187ba
commit 069efad7a2
12 changed files with 247 additions and 397 deletions
@@ -6,7 +6,7 @@ module BadgeService.Store.Postgres.Migrations (badgeServiceSchemaMigrations) whe
import Data.List (sortOn)
import Data.Text (Text)
import Simplex.Chat.Store.Postgres.Migrations.M20261001_user_badges (badgeSchema, badgeSchemaDown, withPrefix)
import Simplex.Chat.Store.Postgres.Migrations.M20260915_user_badges (badgeSchema, badgeSchemaDown, withPrefix)
import Simplex.Messaging.Agent.Store.Shared (Migration (..))
import Text.RawString.QQ (r)
@@ -30,8 +30,6 @@ m20260806_badge_service_schema =
<> withPrefix
servicePrefix
[r|
ALTER TABLE @payments ADD COLUMN receipt_hash BYTEA;
CREATE TABLE @badge_codes(
badge_code_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
code_hash BYTEA NOT NULL,
@@ -77,3 +75,11 @@ DROP INDEX @idx_badge_purchases_code;
[r|
DROP TABLE @badge_codes;
|]
{- TODO [badges] deferred with the draft in M20260915_user_badges, service only.
ALTER TABLE @payments ADD COLUMN receipt_hash BYTEA;
-- down
ALTER TABLE @payments DROP COLUMN receipt_hash;
-}
@@ -8,7 +8,7 @@ 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.M20261001_user_badges (badgeSchema, badgeSchemaDown, withPrefix)
import Simplex.Chat.Store.SQLite.Migrations.M20260915_user_badges (badgeSchema, badgeSchemaDown, withPrefix)
import Simplex.Messaging.Agent.Store.Shared (Migration (..))
badgeServiceSchemaMigrations :: [Migration]
@@ -31,8 +31,6 @@ m20260806_badge_service_schema =
<> withPrefix
servicePrefix
[sql|
ALTER TABLE @payments ADD COLUMN receipt_hash BLOB;
CREATE TABLE @badge_codes(
badge_code_id INTEGER PRIMARY KEY AUTOINCREMENT,
code_hash BLOB NOT NULL,
@@ -78,3 +76,11 @@ DROP INDEX @idx_badge_purchases_code;
[sql|
DROP TABLE @badge_codes;
|]
{- TODO [badges] deferred with the draft in M20260915_user_badges, service only.
ALTER TABLE @payments ADD COLUMN receipt_hash BLOB;
-- down
ALTER TABLE @payments DROP COLUMN receipt_hash;
-}
@@ -7,7 +7,7 @@
## 1. Client schema
`src/Simplex/Chat/Store/SQLite/Migrations/M20261001_user_badges.hs` — SQLite only; the Postgres variant is written when the schema is final; registered in the migrations list and cabal at delivery step 2.
`src/Simplex/Chat/Store/SQLite/Migrations/M20260915_user_badges.hs` — SQLite only; the Postgres variant is written when the schema is final; registered in the migrations list and cabal at delivery step 2.
- Table mapping to UX §3:
- `badges``badge_purchases`
@@ -401,7 +401,7 @@ Each UX plan point and its implementation home:
## Delivery order
1. `mkBadgeStatus`: the +7-day recipient display grace and the shifted `BSExpiredOld` boundary (UX 2.11) — released before purchases (UX §7).
2. Register migration `M20261001_user_badges` (migrations list + cabal + regenerated `chat_schema.sql` / `chat_lint.sql`); the Postgres variant of the migration; the catalog seed from app config (UX §3 prices); store functions (`Store/Badges.hs`): get-or-create with lock; last-ledger-row reads; verbatim replica inserts.
2. Register migration `M20260915_user_badges` (migrations list + cabal + regenerated `chat_schema.sql` / `chat_lint.sql`); the Postgres variant of the migration; the catalog seed from app config (UX §3 prices); store functions (`Store/Badges.hs`): get-or-create with lock; last-ledger-row reads; verbatim replica inserts.
3. Instances for the types in `Badges.hs` and `Badges/Service.hs` (§3) + roundtrip tests.
4. RPC codec: `docs/protocol/badges-rpc.schema.json` packets ↔ `Badges/Service.hs` types; catalog reconciliation on every `badgeCatalog` response.
5. BadgeManager §6: worker, locks, timer; reconcile/apply/presentation/alert steps; events.
+1 -1
View File
@@ -81,7 +81,7 @@ CREATE TABLE charges(
UNIQUE(payment_id, provider_charge_ref)
);
-- badgeLedgerTable constant (M20261001_user_badges), re-used literally
-- badgeLedgerTable constant (M20260915_user_badges), re-used literally
CREATE TABLE badge_ledger(
entry_id INTEGER PRIMARY KEY AUTOINCREMENT,
entry_uuid TEXT NOT NULL,
+1 -1
View File
@@ -28,7 +28,7 @@ Order: one path end to end first, then the paths that need external systems. Eac
- Store functions (`Store/Badges.hs`): get-or-create under the user lock, last-entry reads, verbatim replica writes.
- `BadgeManager`: worker, locks, reconcile, apply, presentation. The timer and `CEvtBadgeAlert` follow later.
- JSON instances for `Badges/Service.hs` and `Badges/Store.hs``taggedObjectJSON`, with roundtrip tests.
- Register `M20261001_user_badges` in the migrations list and cabal; regenerate `chat_schema.sql` and `chat_lint.sql`.
- Register `M20260915_user_badges` in the migrations list and cabal; regenerate `chat_schema.sql` and `chat_lint.sql`.
## 3. Store integration — iOS, Android
+2 -2
View File
@@ -165,7 +165,7 @@ library
Simplex.Chat.Store.Postgres.Migrations.M20260822_forward_link
Simplex.Chat.Store.Postgres.Migrations.M20260828_file_expiry
Simplex.Chat.Store.Postgres.Migrations.M20260904_file_badges
Simplex.Chat.Store.Postgres.Migrations.M20261001_user_badges
Simplex.Chat.Store.Postgres.Migrations.M20260915_user_badges
else
exposed-modules:
Simplex.Chat.Archive
@@ -340,7 +340,7 @@ library
Simplex.Chat.Store.SQLite.Migrations.M20260822_forward_link
Simplex.Chat.Store.SQLite.Migrations.M20260828_file_expiry
Simplex.Chat.Store.SQLite.Migrations.M20260904_file_badges
Simplex.Chat.Store.SQLite.Migrations.M20261001_user_badges
Simplex.Chat.Store.SQLite.Migrations.M20260915_user_badges
other-modules:
Paths_simplex_chat
hs-source-dirs:
@@ -50,7 +50,7 @@ import Simplex.Chat.Store.Postgres.Migrations.M20260813_auto_accept_group_invita
import Simplex.Chat.Store.Postgres.Migrations.M20260822_forward_link
import Simplex.Chat.Store.Postgres.Migrations.M20260828_file_expiry
import Simplex.Chat.Store.Postgres.Migrations.M20260904_file_badges
import Simplex.Chat.Store.Postgres.Migrations.M20261001_user_badges
import Simplex.Chat.Store.Postgres.Migrations.M20260915_user_badges
import Simplex.Messaging.Agent.Store.Shared (Migration (..))
schemaMigrations :: [(String, Text, Maybe Text)]
@@ -101,7 +101,7 @@ schemaMigrations =
("20260822_forward_link", m20260822_forward_link, Just down_m20260822_forward_link),
("20260828_file_expiry", m20260828_file_expiry, Just down_m20260828_file_expiry),
("20260904_file_badges", m20260904_file_badges, Just down_m20260904_file_badges),
("20261001_user_badges", m20261001_user_badges, Just down_m20261001_user_badges)
("20260915_user_badges", m20260915_user_badges, Just down_m20260915_user_badges)
]
-- | The list of migrations in ascending order by date
@@ -1,7 +1,7 @@
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Store.Postgres.Migrations.M20261001_user_badges where
module Simplex.Chat.Store.Postgres.Migrations.M20260915_user_badges where
import Data.Text (Text)
import qualified Data.Text as T
@@ -58,18 +58,6 @@ CREATE INDEX @idx_payments_provider_ref ON @payments(provider, provider_ref);
CREATE INDEX @idx_payments_invoice ON @payments(invoice_id);
CREATE TABLE @subscription_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,
@@ -97,14 +85,90 @@ CREATE TABLE @badge_purchases(
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)
UNIQUE(purchase_key)
);
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_anchor_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
);
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 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);
CREATE INDEX @idx_badge_issuances_entry ON @badge_issuances(entry_id);
CREATE UNIQUE INDEX @idx_badge_issuances_purchase_entry ON @badge_issuances(badge_purchase_id, entry_id);
|]
badgeSchemaTablesDown :: Text
badgeSchemaTablesDown =
[r|
DROP INDEX @idx_badge_issuances_purchase;
DROP INDEX @idx_badge_issuances_entry;
DROP INDEX @idx_badge_issuances_purchase_entry;
DROP TABLE @badge_issuances;
DROP INDEX @idx_badge_ledger_uuid;
DROP INDEX @idx_badge_ledger_purchase;
DROP TABLE @badge_ledger;
DROP TABLE @badge_purchases;
DROP INDEX @idx_payments_provider_ref;
DROP INDEX @idx_payments_invoice;
DROP TABLE @payments;
DROP TABLE @invoices;
DROP INDEX @idx_badge_offers_price;
DROP TABLE @badge_offers;
DROP TABLE @badge_prices;
|]
{- TODO [badges] deferred draft schema for paid purchases, subscriptions, upgrades and transfers.
CREATE TABLE @subscription_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)
);
ALTER TABLE @badge_purchases ADD COLUMN payment_id TEXT REFERENCES @payments;
CREATE UNIQUE INDEX @idx_badge_purchases_payment ON @badge_purchases(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,
@@ -136,30 +200,13 @@ CREATE TABLE @badge_subscription_changes(
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_anchor_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 @subscription_charges,
from_purchase_id BIGINT REFERENCES @badge_purchases,
to_purchase_id BIGINT REFERENCES @badge_purchases
);
ALTER TABLE @badge_ledger ADD COLUMN payment_id TEXT REFERENCES @payments;
CREATE UNIQUE INDEX @idx_badge_ledger_uuid ON @badge_ledger(entry_uuid);
ALTER TABLE @badge_ledger ADD COLUMN charge_id TEXT REFERENCES @subscription_charges;
CREATE INDEX @idx_badge_ledger_purchase ON @badge_ledger(badge_purchase_id, entry_id);
ALTER TABLE @badge_ledger ADD COLUMN from_purchase_id BIGINT REFERENCES @badge_purchases;
ALTER TABLE @badge_ledger ADD COLUMN to_purchase_id BIGINT REFERENCES @badge_purchases;
CREATE INDEX @idx_badge_ledger_payment ON @badge_ledger(payment_id);
@@ -169,58 +216,28 @@ 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);
CREATE INDEX @idx_badge_issuances_entry ON @badge_issuances(entry_id);
CREATE UNIQUE INDEX @idx_badge_issuances_purchase_entry ON @badge_issuances(badge_purchase_id, entry_id);
|]
badgeSchemaTablesDown :: Text
badgeSchemaTablesDown =
[r|
DROP INDEX @idx_badge_issuances_purchase;
DROP INDEX @idx_badge_issuances_entry;
DROP INDEX @idx_badge_issuances_purchase_entry;
DROP TABLE @badge_issuances;
DROP INDEX @idx_badge_ledger_uuid;
DROP INDEX @idx_badge_ledger_purchase;
-- down
DROP INDEX @idx_badge_ledger_payment;
DROP INDEX @idx_badge_ledger_charge;
DROP INDEX @idx_badge_ledger_from_purchase;
DROP INDEX @idx_badge_ledger_to_purchase;
DROP TABLE @badge_ledger;
ALTER TABLE @badge_ledger DROP COLUMN payment_id;
ALTER TABLE @badge_ledger DROP COLUMN charge_id;
ALTER TABLE @badge_ledger DROP COLUMN from_purchase_id;
ALTER TABLE @badge_ledger DROP COLUMN to_purchase_id;
DROP INDEX @idx_badge_subscription_changes_purchase;
DROP TABLE @badge_subscription_changes;
DROP INDEX @idx_badge_invoices_purchase;
DROP INDEX @idx_badge_invoices_offer;
DROP INDEX @idx_badge_invoices_price;
DROP TABLE @badge_invoices;
DROP TABLE @badge_purchases;
DROP INDEX @idx_badge_purchases_payment;
ALTER TABLE @badge_purchases DROP COLUMN payment_id;
DROP TABLE @subscription_charges;
DROP INDEX @idx_payments_provider_ref;
DROP INDEX @idx_payments_invoice;
DROP TABLE @payments;
DROP TABLE @invoices;
DROP INDEX @idx_badge_offers_price;
DROP TABLE @badge_offers;
DROP TABLE @badge_prices;
|]
-}
m20261001_user_badges :: Text
m20261001_user_badges =
m20260915_user_badges :: Text
m20260915_user_badges =
badgeSchema ""
<> [r|
ALTER TABLE badge_purchases ADD COLUMN user_id BIGINT REFERENCES users ON DELETE CASCADE;
@@ -233,10 +250,6 @@ 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 payments ADD COLUMN receipt_code TEXT;
ALTER TABLE badge_ledger ADD COLUMN entry_type_unknown SMALLINT NOT NULL DEFAULT 0;
ALTER TABLE badge_ledger ADD COLUMN entry_type_value TEXT;
@@ -267,8 +280,8 @@ ALTER TABLE badge_purchases ADD COLUMN badge_code_redemption_id BIGINT REFERENCE
CREATE UNIQUE INDEX idx_badge_purchases_code_redemption ON badge_purchases(badge_code_redemption_id);
|]
down_m20261001_user_badges :: Text
down_m20261001_user_badges =
down_m20260915_user_badges :: Text
down_m20260915_user_badges =
[r|
DROP INDEX idx_badge_purchases_code_redemption;
DROP INDEX idx_badge_purchases_user;
@@ -280,3 +293,14 @@ ALTER TABLE users DROP COLUMN shown_badge_id;
DROP INDEX idx_badge_code_redemptions_user;
DROP TABLE badge_code_redemptions;
|]
{- TODO [badges] deferred with the draft above, client only.
ALTER TABLE payments ADD COLUMN evidence BYTEA;
ALTER TABLE payments ADD COLUMN receipt_code TEXT;
-- down
ALTER TABLE payments DROP COLUMN evidence;
ALTER TABLE payments DROP COLUMN receipt_code;
-}
@@ -191,17 +191,6 @@ ALTER TABLE test_chat_schema.badge_code_redemptions ALTER COLUMN badge_code_rede
CREATE TABLE test_chat_schema.badge_invoices (
invoice_id text NOT NULL,
badge_purchase_id bigint NOT NULL,
price_id text NOT NULL,
offer_id text,
months smallint NOT NULL,
created_at timestamp with time zone NOT NULL
);
CREATE TABLE test_chat_schema.badge_issuances (
issuance_id text NOT NULL,
badge_purchase_id bigint NOT NULL,
@@ -231,10 +220,6 @@ CREATE TABLE test_chat_schema.badge_ledger (
entry_type text NOT NULL,
entry_credit_type text,
entry_debit_type text,
payment_id text,
charge_id text,
from_purchase_id bigint,
to_purchase_id bigint,
entry_type_unknown smallint DEFAULT 0 NOT NULL,
entry_type_value text,
balance_checked smallint
@@ -282,7 +267,6 @@ CREATE TABLE test_chat_schema.badge_purchases (
master_key bytea NOT NULL,
initial_badge_type text NOT NULL,
current_badge_type text NOT NULL,
payment_id text,
status text NOT NULL,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL,
@@ -307,22 +291,6 @@ ALTER TABLE test_chat_schema.badge_purchases ALTER COLUMN badge_purchase_id ADD
CREATE TABLE test_chat_schema.badge_subscription_changes (
change_id text NOT NULL,
badge_purchase_id bigint NOT NULL,
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 timestamp with time zone,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL
);
CREATE TABLE test_chat_schema.calls (
call_id bigint NOT NULL,
contact_id bigint NOT NULL,
@@ -1378,9 +1346,7 @@ CREATE TABLE test_chat_schema.payments (
grace_until timestamp with time zone,
cancelled smallint DEFAULT 0 NOT NULL,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL,
evidence bytea,
receipt_code text
updated_at timestamp with time zone NOT NULL
);
@@ -1679,19 +1645,6 @@ CREATE TABLE test_chat_schema.snd_files (
CREATE TABLE test_chat_schema.subscription_charges (
charge_id text NOT NULL,
payment_id text NOT NULL,
provider_charge_ref text NOT NULL,
period_start timestamp with time zone NOT NULL,
period_end timestamp with time zone NOT NULL,
amount bigint NOT NULL,
currency text NOT NULL,
charged_at timestamp with time zone NOT NULL
);
CREATE TABLE test_chat_schema.usage_conditions (
usage_conditions_id bigint NOT NULL,
conditions_commit text NOT NULL,
@@ -1804,11 +1757,6 @@ ALTER TABLE ONLY test_chat_schema.badge_code_redemptions
ALTER TABLE ONLY test_chat_schema.badge_invoices
ADD CONSTRAINT badge_invoices_pkey PRIMARY KEY (invoice_id);
ALTER TABLE ONLY test_chat_schema.badge_issuances
ADD CONSTRAINT badge_issuances_pkey PRIMARY KEY (issuance_id);
@@ -1829,11 +1777,6 @@ ALTER TABLE ONLY test_chat_schema.badge_prices
ALTER TABLE ONLY test_chat_schema.badge_purchases
ADD CONSTRAINT badge_purchases_payment_id_key UNIQUE (payment_id);
ALTER TABLE ONLY test_chat_schema.badge_purchases
ADD CONSTRAINT badge_purchases_pkey PRIMARY KEY (badge_purchase_id);
@@ -1844,11 +1787,6 @@ ALTER TABLE ONLY test_chat_schema.badge_purchases
ALTER TABLE ONLY test_chat_schema.badge_subscription_changes
ADD CONSTRAINT badge_subscription_changes_pkey PRIMARY KEY (change_id);
ALTER TABLE ONLY test_chat_schema.calls
ADD CONSTRAINT calls_pkey PRIMARY KEY (call_id);
@@ -2164,16 +2102,6 @@ ALTER TABLE ONLY test_chat_schema.snd_files
ALTER TABLE ONLY test_chat_schema.subscription_charges
ADD CONSTRAINT subscription_charges_payment_id_provider_charge_ref_key UNIQUE (payment_id, provider_charge_ref);
ALTER TABLE ONLY test_chat_schema.subscription_charges
ADD CONSTRAINT subscription_charges_pkey PRIMARY KEY (charge_id);
ALTER TABLE ONLY test_chat_schema.usage_conditions
ADD CONSTRAINT usage_conditions_conditions_commit_key UNIQUE (conditions_commit);
@@ -2230,18 +2158,6 @@ CREATE INDEX idx_badge_code_redemptions_user ON test_chat_schema.badge_code_rede
CREATE INDEX idx_badge_invoices_offer ON test_chat_schema.badge_invoices USING btree (offer_id);
CREATE INDEX idx_badge_invoices_price ON test_chat_schema.badge_invoices USING btree (price_id);
CREATE INDEX idx_badge_invoices_purchase ON test_chat_schema.badge_invoices USING btree (badge_purchase_id);
CREATE INDEX idx_badge_issuances_entry ON test_chat_schema.badge_issuances USING btree (entry_id);
@@ -2254,26 +2170,10 @@ CREATE UNIQUE INDEX idx_badge_issuances_purchase_entry ON test_chat_schema.badge
CREATE INDEX idx_badge_ledger_charge ON test_chat_schema.badge_ledger USING btree (charge_id);
CREATE INDEX idx_badge_ledger_from_purchase ON test_chat_schema.badge_ledger USING btree (from_purchase_id);
CREATE INDEX idx_badge_ledger_payment ON test_chat_schema.badge_ledger USING btree (payment_id);
CREATE INDEX idx_badge_ledger_purchase ON test_chat_schema.badge_ledger USING btree (badge_purchase_id, entry_id);
CREATE INDEX idx_badge_ledger_to_purchase ON test_chat_schema.badge_ledger USING btree (to_purchase_id);
CREATE UNIQUE INDEX idx_badge_ledger_uuid ON test_chat_schema.badge_ledger USING btree (entry_uuid);
@@ -2290,10 +2190,6 @@ CREATE INDEX idx_badge_purchases_user ON test_chat_schema.badge_purchases USING
CREATE INDEX idx_badge_subscription_changes_purchase ON test_chat_schema.badge_subscription_changes USING btree (badge_purchase_id);
CREATE INDEX idx_calls_chat_item_id ON test_chat_schema.calls USING btree (chat_item_id);
@@ -3067,26 +2963,6 @@ ALTER TABLE ONLY test_chat_schema.badge_code_redemptions
ALTER TABLE ONLY test_chat_schema.badge_invoices
ADD CONSTRAINT badge_invoices_badge_purchase_id_fkey FOREIGN KEY (badge_purchase_id) REFERENCES test_chat_schema.badge_purchases(badge_purchase_id) ON DELETE CASCADE;
ALTER TABLE ONLY test_chat_schema.badge_invoices
ADD CONSTRAINT badge_invoices_invoice_id_fkey FOREIGN KEY (invoice_id) REFERENCES test_chat_schema.invoices(invoice_id) ON DELETE CASCADE;
ALTER TABLE ONLY test_chat_schema.badge_invoices
ADD CONSTRAINT badge_invoices_offer_id_fkey FOREIGN KEY (offer_id) REFERENCES test_chat_schema.badge_offers(offer_id);
ALTER TABLE ONLY test_chat_schema.badge_invoices
ADD CONSTRAINT badge_invoices_price_id_fkey FOREIGN KEY (price_id) REFERENCES test_chat_schema.badge_prices(price_id);
ALTER TABLE ONLY test_chat_schema.badge_issuances
ADD CONSTRAINT badge_issuances_badge_purchase_id_fkey FOREIGN KEY (badge_purchase_id) REFERENCES test_chat_schema.badge_purchases(badge_purchase_id) ON DELETE CASCADE;
@@ -3102,26 +2978,6 @@ ALTER TABLE ONLY test_chat_schema.badge_ledger
ALTER TABLE ONLY test_chat_schema.badge_ledger
ADD CONSTRAINT badge_ledger_charge_id_fkey FOREIGN KEY (charge_id) REFERENCES test_chat_schema.subscription_charges(charge_id);
ALTER TABLE ONLY test_chat_schema.badge_ledger
ADD CONSTRAINT badge_ledger_from_purchase_id_fkey FOREIGN KEY (from_purchase_id) REFERENCES test_chat_schema.badge_purchases(badge_purchase_id);
ALTER TABLE ONLY test_chat_schema.badge_ledger
ADD CONSTRAINT badge_ledger_payment_id_fkey FOREIGN KEY (payment_id) REFERENCES test_chat_schema.payments(payment_id);
ALTER TABLE ONLY test_chat_schema.badge_ledger
ADD CONSTRAINT badge_ledger_to_purchase_id_fkey FOREIGN KEY (to_purchase_id) REFERENCES test_chat_schema.badge_purchases(badge_purchase_id);
ALTER TABLE ONLY test_chat_schema.badge_offers
ADD CONSTRAINT badge_offers_price_id_fkey FOREIGN KEY (price_id) REFERENCES test_chat_schema.badge_prices(price_id);
@@ -3132,21 +2988,11 @@ ALTER TABLE ONLY test_chat_schema.badge_purchases
ALTER TABLE ONLY test_chat_schema.badge_purchases
ADD CONSTRAINT badge_purchases_payment_id_fkey FOREIGN KEY (payment_id) REFERENCES test_chat_schema.payments(payment_id);
ALTER TABLE ONLY test_chat_schema.badge_purchases
ADD CONSTRAINT badge_purchases_user_id_fkey FOREIGN KEY (user_id) REFERENCES test_chat_schema.users(user_id) ON DELETE CASCADE;
ALTER TABLE ONLY test_chat_schema.badge_subscription_changes
ADD CONSTRAINT badge_subscription_changes_badge_purchase_id_fkey FOREIGN KEY (badge_purchase_id) REFERENCES test_chat_schema.badge_purchases(badge_purchase_id) ON DELETE CASCADE;
ALTER TABLE ONLY test_chat_schema.calls
ADD CONSTRAINT calls_chat_item_id_fkey FOREIGN KEY (chat_item_id) REFERENCES test_chat_schema.chat_items(chat_item_id) ON DELETE CASCADE;
@@ -3802,11 +3648,6 @@ ALTER TABLE ONLY test_chat_schema.snd_files
ALTER TABLE ONLY test_chat_schema.subscription_charges
ADD CONSTRAINT subscription_charges_payment_id_fkey FOREIGN KEY (payment_id) REFERENCES test_chat_schema.payments(payment_id) ON DELETE CASCADE;
ALTER TABLE ONLY test_chat_schema.user_contact_links
ADD CONSTRAINT user_contact_links_group_id_fkey FOREIGN KEY (group_id) REFERENCES test_chat_schema.groups(group_id) ON DELETE CASCADE;
+2 -2
View File
@@ -173,7 +173,7 @@ import Simplex.Chat.Store.SQLite.Migrations.M20260813_auto_accept_group_invitati
import Simplex.Chat.Store.SQLite.Migrations.M20260822_forward_link
import Simplex.Chat.Store.SQLite.Migrations.M20260828_file_expiry
import Simplex.Chat.Store.SQLite.Migrations.M20260904_file_badges
import Simplex.Chat.Store.SQLite.Migrations.M20261001_user_badges
import Simplex.Chat.Store.SQLite.Migrations.M20260915_user_badges
import Simplex.Messaging.Agent.Store.Shared (Migration (..))
schemaMigrations :: [(String, Query, Maybe Query)]
@@ -347,7 +347,7 @@ schemaMigrations =
("20260822_forward_link", m20260822_forward_link, Just down_m20260822_forward_link),
("20260828_file_expiry", m20260828_file_expiry, Just down_m20260828_file_expiry),
("20260904_file_badges", m20260904_file_badges, Just down_m20260904_file_badges),
("20261001_user_badges", m20261001_user_badges, Just down_m20261001_user_badges)
("20260915_user_badges", m20260915_user_badges, Just down_m20260915_user_badges)
]
-- | The list of migrations in ascending order by date
@@ -1,7 +1,7 @@
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
module Simplex.Chat.Store.SQLite.Migrations.M20261001_user_badges where
module Simplex.Chat.Store.SQLite.Migrations.M20260915_user_badges where
import Data.Text (Text)
import qualified Data.Text as T
@@ -59,18 +59,6 @@ CREATE INDEX @idx_payments_provider_ref ON @payments(provider, provider_ref);
CREATE INDEX @idx_payments_invoice ON @payments(invoice_id);
CREATE TABLE @subscription_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,
amount INTEGER NOT NULL,
currency TEXT NOT NULL,
charged_at TEXT NOT NULL,
UNIQUE(payment_id, provider_charge_ref)
) STRICT;
CREATE TABLE @badge_prices(
price_id TEXT NOT NULL PRIMARY KEY,
badge_type TEXT NOT NULL,
@@ -98,14 +86,90 @@ CREATE TABLE @badge_purchases(
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)
UNIQUE(purchase_key)
) STRICT;
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,
change_months INTEGER NOT NULL,
balance_months INTEGER NOT NULL,
balance_start_ts TEXT NOT NULL,
balance_anchor_ts TEXT NOT NULL,
balance_badge_type TEXT NOT NULL,
was_paused_since TEXT,
service_created_at TEXT NOT NULL,
created_at TEXT NOT NULL,
entry_type TEXT NOT NULL,
entry_credit_type TEXT,
entry_debit_type TEXT
) STRICT;
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 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
) STRICT;
CREATE INDEX @idx_badge_issuances_purchase ON @badge_issuances(badge_purchase_id, issuance_id);
CREATE INDEX @idx_badge_issuances_entry ON @badge_issuances(entry_id);
CREATE UNIQUE INDEX @idx_badge_issuances_purchase_entry ON @badge_issuances(badge_purchase_id, entry_id);
|]
badgeSchemaTablesDown :: Query
badgeSchemaTablesDown =
[sql|
DROP INDEX @idx_badge_issuances_purchase;
DROP INDEX @idx_badge_issuances_entry;
DROP INDEX @idx_badge_issuances_purchase_entry;
DROP TABLE @badge_issuances;
DROP INDEX @idx_badge_ledger_uuid;
DROP INDEX @idx_badge_ledger_purchase;
DROP TABLE @badge_ledger;
DROP TABLE @badge_purchases;
DROP INDEX @idx_payments_provider_ref;
DROP INDEX @idx_payments_invoice;
DROP TABLE @payments;
DROP TABLE @invoices;
DROP INDEX @idx_badge_offers_price;
DROP TABLE @badge_offers;
DROP TABLE @badge_prices;
|]
{- TODO [badges] deferred draft schema for paid purchases, subscriptions, upgrades and transfers.
CREATE TABLE @subscription_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,
amount INTEGER NOT NULL,
currency TEXT NOT NULL,
charged_at TEXT NOT NULL,
UNIQUE(payment_id, provider_charge_ref)
) STRICT;
ALTER TABLE @badge_purchases ADD COLUMN payment_id TEXT REFERENCES @payments;
CREATE UNIQUE INDEX @idx_badge_purchases_payment ON @badge_purchases(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,
@@ -137,30 +201,13 @@ CREATE TABLE @badge_subscription_changes(
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,
change_months INTEGER NOT NULL,
balance_months INTEGER NOT NULL,
balance_start_ts TEXT NOT NULL,
balance_anchor_ts TEXT NOT NULL,
balance_badge_type TEXT NOT NULL,
was_paused_since TEXT,
service_created_at TEXT NOT NULL,
created_at TEXT NOT NULL,
entry_type TEXT NOT NULL,
entry_credit_type TEXT,
entry_debit_type TEXT,
payment_id TEXT REFERENCES @payments,
charge_id TEXT REFERENCES @subscription_charges,
from_purchase_id INTEGER REFERENCES @badge_purchases,
to_purchase_id INTEGER REFERENCES @badge_purchases
) STRICT;
ALTER TABLE @badge_ledger ADD COLUMN payment_id TEXT REFERENCES @payments;
CREATE UNIQUE INDEX @idx_badge_ledger_uuid ON @badge_ledger(entry_uuid);
ALTER TABLE @badge_ledger ADD COLUMN charge_id TEXT REFERENCES @subscription_charges;
CREATE INDEX @idx_badge_ledger_purchase ON @badge_ledger(badge_purchase_id, entry_id);
ALTER TABLE @badge_ledger ADD COLUMN from_purchase_id INTEGER REFERENCES @badge_purchases;
ALTER TABLE @badge_ledger ADD COLUMN to_purchase_id INTEGER REFERENCES @badge_purchases;
CREATE INDEX @idx_badge_ledger_payment ON @badge_ledger(payment_id);
@@ -170,58 +217,28 @@ 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
) STRICT;
CREATE INDEX @idx_badge_issuances_purchase ON @badge_issuances(badge_purchase_id, issuance_id);
CREATE INDEX @idx_badge_issuances_entry ON @badge_issuances(entry_id);
CREATE UNIQUE INDEX @idx_badge_issuances_purchase_entry ON @badge_issuances(badge_purchase_id, entry_id);
|]
badgeSchemaTablesDown :: Query
badgeSchemaTablesDown =
[sql|
DROP INDEX @idx_badge_issuances_purchase;
DROP INDEX @idx_badge_issuances_entry;
DROP INDEX @idx_badge_issuances_purchase_entry;
DROP TABLE @badge_issuances;
DROP INDEX @idx_badge_ledger_uuid;
DROP INDEX @idx_badge_ledger_purchase;
-- down
DROP INDEX @idx_badge_ledger_payment;
DROP INDEX @idx_badge_ledger_charge;
DROP INDEX @idx_badge_ledger_from_purchase;
DROP INDEX @idx_badge_ledger_to_purchase;
DROP TABLE @badge_ledger;
ALTER TABLE @badge_ledger DROP COLUMN payment_id;
ALTER TABLE @badge_ledger DROP COLUMN charge_id;
ALTER TABLE @badge_ledger DROP COLUMN from_purchase_id;
ALTER TABLE @badge_ledger DROP COLUMN to_purchase_id;
DROP INDEX @idx_badge_subscription_changes_purchase;
DROP TABLE @badge_subscription_changes;
DROP INDEX @idx_badge_invoices_purchase;
DROP INDEX @idx_badge_invoices_offer;
DROP INDEX @idx_badge_invoices_price;
DROP TABLE @badge_invoices;
DROP TABLE @badge_purchases;
DROP INDEX @idx_badge_purchases_payment;
ALTER TABLE @badge_purchases DROP COLUMN payment_id;
DROP TABLE @subscription_charges;
DROP INDEX @idx_payments_provider_ref;
DROP INDEX @idx_payments_invoice;
DROP TABLE @payments;
DROP TABLE @invoices;
DROP INDEX @idx_badge_offers_price;
DROP TABLE @badge_offers;
DROP TABLE @badge_prices;
|]
-}
m20261001_user_badges :: Query
m20261001_user_badges =
m20260915_user_badges :: Query
m20260915_user_badges =
badgeSchema ""
<> [sql|
ALTER TABLE badge_purchases ADD COLUMN user_id INTEGER REFERENCES users ON DELETE CASCADE;
@@ -234,10 +251,6 @@ 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 payments ADD COLUMN receipt_code TEXT;
ALTER TABLE badge_ledger ADD COLUMN entry_type_unknown INTEGER NOT NULL DEFAULT 0;
ALTER TABLE badge_ledger ADD COLUMN entry_type_value TEXT;
@@ -268,8 +281,8 @@ ALTER TABLE badge_purchases ADD COLUMN badge_code_redemption_id INTEGER REFERENC
CREATE UNIQUE INDEX idx_badge_purchases_code_redemption ON badge_purchases(badge_code_redemption_id);
|]
down_m20261001_user_badges :: Query
down_m20261001_user_badges =
down_m20260915_user_badges :: Query
down_m20260915_user_badges =
[sql|
DROP INDEX idx_badge_purchases_code_redemption;
DROP INDEX idx_badge_purchases_user;
@@ -281,3 +294,14 @@ ALTER TABLE users DROP COLUMN shown_badge_id;
DROP INDEX idx_badge_code_redemptions_user;
DROP TABLE badge_code_redemptions;
|]
{- TODO [badges] deferred with the draft above, client only.
ALTER TABLE payments ADD COLUMN evidence BLOB;
ALTER TABLE payments ADD COLUMN receipt_code TEXT;
-- down
ALTER TABLE payments DROP COLUMN evidence;
ALTER TABLE payments DROP COLUMN receipt_code;
-}
@@ -899,20 +899,6 @@ CREATE TABLE payments(
cancelled INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
,
evidence BLOB,
receipt_code TEXT
) STRICT;
CREATE TABLE subscription_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,
amount INTEGER NOT NULL,
currency TEXT NOT NULL,
charged_at TEXT NOT NULL,
UNIQUE(payment_id, provider_charge_ref)
) STRICT;
CREATE TABLE badge_prices(
price_id TEXT NOT NULL PRIMARY KEY,
@@ -937,7 +923,6 @@ CREATE TABLE badge_purchases(
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,
@@ -947,29 +932,7 @@ CREATE TABLE badge_purchases(
alert_acked_episode TEXT,
alert_snooze_until TEXT,
badge_code_redemption_id INTEGER REFERENCES badge_code_redemptions,
UNIQUE(purchase_key),
UNIQUE(payment_id)
) STRICT;
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
) STRICT;
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
UNIQUE(purchase_key)
) STRICT;
CREATE TABLE badge_ledger(
entry_id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -985,11 +948,7 @@ CREATE TABLE badge_ledger(
created_at TEXT NOT NULL,
entry_type TEXT NOT NULL,
entry_credit_type TEXT,
entry_debit_type TEXT,
payment_id TEXT REFERENCES payments,
charge_id TEXT REFERENCES subscription_charges,
from_purchase_id INTEGER REFERENCES badge_purchases,
to_purchase_id INTEGER REFERENCES badge_purchases
entry_debit_type TEXT
,
entry_type_unknown INTEGER NOT NULL DEFAULT 0,
entry_type_value TEXT,
@@ -1557,21 +1516,11 @@ CREATE UNIQUE INDEX idx_file_badge_proofs_file_id_kind ON file_badge_proofs(
CREATE INDEX idx_payments_provider_ref ON payments(provider, provider_ref);
CREATE INDEX idx_payments_invoice ON payments(invoice_id);
CREATE INDEX idx_badge_offers_price ON badge_offers(price_id);
CREATE INDEX idx_badge_invoices_purchase ON badge_invoices(badge_purchase_id);
CREATE INDEX idx_badge_invoices_offer ON badge_invoices(offer_id);
CREATE INDEX idx_badge_invoices_price ON badge_invoices(price_id);
CREATE INDEX idx_badge_subscription_changes_purchase ON badge_subscription_changes(
badge_purchase_id
);
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 INDEX idx_badge_issuances_purchase ON badge_issuances(
badge_purchase_id,
issuance_id