From 6075ea579731fd859beea76eb0579526b2c0cd00 Mon Sep 17 00:00:00 2001 From: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Date: Fri, 28 Aug 2026 10:20:13 +0000 Subject: [PATCH] core: register badges migration (#7429) --- .../BadgeService/Store/Postgres/Migrations.hs | 6 + .../BadgeService/Store/SQLite/Migrations.hs | 10 +- src/Simplex/Chat/Store/Postgres/Migrations.hs | 4 +- .../Migrations/M20261001_user_badges.hs | 15 + .../Store/Postgres/Migrations/chat_schema.sql | 462 +++++++++++++++++- src/Simplex/Chat/Store/SQLite/Migrations.hs | 4 +- .../Migrations/M20261001_user_badges.hs | 37 +- .../Store/SQLite/Migrations/chat_schema.sql | 180 ++++++- 8 files changed, 700 insertions(+), 18 deletions(-) diff --git a/apps/simplex-badge-service/src/BadgeService/Store/Postgres/Migrations.hs b/apps/simplex-badge-service/src/BadgeService/Store/Postgres/Migrations.hs index daa6905033..18d589b037 100644 --- a/apps/simplex-badge-service/src/BadgeService/Store/Postgres/Migrations.hs +++ b/apps/simplex-badge-service/src/BadgeService/Store/Postgres/Migrations.hs @@ -54,6 +54,10 @@ CREATE TABLE @badge_code_invoices( months SMALLINT NOT NULL, created_at TIMESTAMPTZ NOT NULL ); + +CREATE INDEX @idx_badge_code_invoices_offer ON @badge_code_invoices(offer_id); + +CREATE INDEX @idx_badge_code_invoices_price ON @badge_code_invoices(price_id); |] down_m20260806_badge_service_schema :: Text @@ -61,6 +65,8 @@ down_m20260806_badge_service_schema = withPrefix servicePrefix [r| +DROP INDEX @idx_badge_code_invoices_offer; +DROP INDEX @idx_badge_code_invoices_price; DROP TABLE @badge_code_invoices; DROP INDEX @idx_badge_purchases_code; diff --git a/apps/simplex-badge-service/src/BadgeService/Store/SQLite/Migrations.hs b/apps/simplex-badge-service/src/BadgeService/Store/SQLite/Migrations.hs index d96c779094..2851813c8c 100644 --- a/apps/simplex-badge-service/src/BadgeService/Store/SQLite/Migrations.hs +++ b/apps/simplex-badge-service/src/BadgeService/Store/SQLite/Migrations.hs @@ -42,7 +42,7 @@ CREATE TABLE @badge_codes( redeemed_at TEXT, created_at TEXT NOT NULL, UNIQUE(code_hash) -); +) STRICT; ALTER TABLE @badge_purchases ADD COLUMN badge_code_id INTEGER REFERENCES @badge_codes; @@ -54,7 +54,11 @@ CREATE TABLE @badge_code_invoices( offer_id TEXT REFERENCES @badge_offers, months INTEGER NOT NULL, created_at TEXT NOT NULL -); +) STRICT; + +CREATE INDEX @idx_badge_code_invoices_offer ON @badge_code_invoices(offer_id); + +CREATE INDEX @idx_badge_code_invoices_price ON @badge_code_invoices(price_id); |] down_m20260806_badge_service_schema :: Query @@ -62,6 +66,8 @@ down_m20260806_badge_service_schema = withPrefix servicePrefix [sql| +DROP INDEX @idx_badge_code_invoices_offer; +DROP INDEX @idx_badge_code_invoices_price; DROP TABLE @badge_code_invoices; DROP INDEX @idx_badge_purchases_code; diff --git a/src/Simplex/Chat/Store/Postgres/Migrations.hs b/src/Simplex/Chat/Store/Postgres/Migrations.hs index a8b4958de4..862a604da3 100644 --- a/src/Simplex/Chat/Store/Postgres/Migrations.hs +++ b/src/Simplex/Chat/Store/Postgres/Migrations.hs @@ -48,6 +48,7 @@ import Simplex.Chat.Store.Postgres.Migrations.M20260720_server_roles import Simplex.Chat.Store.Postgres.Migrations.M20260723_contact_request_rejection import Simplex.Chat.Store.Postgres.Migrations.M20260813_auto_accept_group_invitations import Simplex.Chat.Store.Postgres.Migrations.M20260822_forward_link +import Simplex.Chat.Store.Postgres.Migrations.M20261001_user_badges import Simplex.Messaging.Agent.Store.Shared (Migration (..)) schemaMigrations :: [(String, Text, Maybe Text)] @@ -95,7 +96,8 @@ schemaMigrations = ("20260720_server_roles", m20260720_server_roles, Just down_m20260720_server_roles), ("20260723_contact_request_rejection", m20260723_contact_request_rejection, Just down_m20260723_contact_request_rejection), ("20260813_auto_accept_group_invitations", m20260813_auto_accept_group_invitations, Just down_m20260813_auto_accept_group_invitations), - ("20260822_forward_link", m20260822_forward_link, Just down_m20260822_forward_link) + ("20260822_forward_link", m20260822_forward_link, Just down_m20260822_forward_link), + ("20261001_user_badges", m20261001_user_badges, Just down_m20261001_user_badges) ] -- | The list of migrations in ascending order by date diff --git a/src/Simplex/Chat/Store/Postgres/Migrations/M20261001_user_badges.hs b/src/Simplex/Chat/Store/Postgres/Migrations/M20261001_user_badges.hs index fd3024966d..0aa9153b2c 100644 --- a/src/Simplex/Chat/Store/Postgres/Migrations/M20261001_user_badges.hs +++ b/src/Simplex/Chat/Store/Postgres/Migrations/M20261001_user_badges.hs @@ -89,6 +89,8 @@ CREATE TABLE @badge_offers( created_at TIMESTAMPTZ NOT NULL ); +CREATE INDEX @idx_badge_offers_price ON @badge_offers(price_id); + CREATE TABLE @badge_purchases( badge_purchase_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, purchase_key BYTEA NOT NULL, @@ -114,6 +116,10 @@ CREATE TABLE @badge_invoices( 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 TABLE @badge_subscription_changes( change_id TEXT NOT NULL PRIMARY KEY, badge_purchase_id BIGINT NOT NULL REFERENCES @badge_purchases ON DELETE CASCADE, @@ -175,12 +181,15 @@ CREATE TABLE @badge_issuances( ); 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); |] badgeSchemaTablesDown :: Text badgeSchemaTablesDown = [r| DROP INDEX @idx_badge_issuances_purchase; +DROP INDEX @idx_badge_issuances_entry; DROP TABLE @badge_issuances; DROP INDEX @idx_badge_ledger_uuid; DROP INDEX @idx_badge_ledger_purchase; @@ -192,6 +201,8 @@ DROP TABLE @badge_ledger; 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 TABLE @subscription_charges; @@ -199,6 +210,7 @@ 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; |] @@ -229,6 +241,8 @@ 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; +CREATE INDEX idx_users_shown_badge ON users(shown_badge_id); + CREATE TABLE badge_code_redemptions( badge_code_redemption_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, user_id BIGINT NOT NULL REFERENCES users ON DELETE CASCADE, @@ -252,6 +266,7 @@ down_m20261001_user_badges = [r| DROP INDEX idx_badge_purchases_code_redemption; DROP INDEX idx_badge_purchases_user; +DROP INDEX idx_users_shown_badge; ALTER TABLE users DROP COLUMN shown_badge_id; |] <> badgeSchemaDown "" diff --git a/src/Simplex/Chat/Store/Postgres/Migrations/chat_schema.sql b/src/Simplex/Chat/Store/Postgres/Migrations/chat_schema.sql index ab6384cb29..fbc663f45d 100644 --- a/src/Simplex/Chat/Store/Postgres/Migrations/chat_schema.sql +++ b/src/Simplex/Chat/Store/Postgres/Migrations/chat_schema.sql @@ -168,6 +168,159 @@ CREATE TABLE test_chat_schema.app_settings ( +CREATE TABLE test_chat_schema.badge_code_redemptions ( + badge_code_redemption_id bigint NOT NULL, + user_id bigint NOT NULL, + code text NOT NULL, + purchase_key bytea NOT NULL, + purchase_priv_key bytea NOT NULL, + master_key bytea NOT NULL, + created_at timestamp with time zone NOT NULL +); + + + +ALTER TABLE test_chat_schema.badge_code_redemptions ALTER COLUMN badge_code_redemption_id ADD GENERATED ALWAYS AS IDENTITY ( + SEQUENCE NAME test_chat_schema.badge_code_redemptions_badge_code_redemption_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + + +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, + entry_id bigint, + badge_type text NOT NULL, + period_start timestamp with time zone NOT NULL, + period_end timestamp with time zone NOT NULL, + expiry timestamp with time zone NOT NULL, + credential bytea NOT NULL, + created_at timestamp with time zone NOT NULL +); + + + +CREATE TABLE test_chat_schema.badge_ledger ( + entry_id bigint NOT NULL, + entry_uuid text NOT NULL, + badge_purchase_id bigint NOT NULL, + change_months smallint NOT NULL, + balance_months smallint NOT NULL, + balance_start_ts timestamp with time zone NOT NULL, + balance_badge_type text NOT NULL, + was_paused_since timestamp with time zone, + service_created_at timestamp with time zone NOT NULL, + created_at timestamp with time zone NOT NULL, + 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 +); + + + +ALTER TABLE test_chat_schema.badge_ledger ALTER COLUMN entry_id ADD GENERATED ALWAYS AS IDENTITY ( + SEQUENCE NAME test_chat_schema.badge_ledger_entry_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + + +CREATE TABLE test_chat_schema.badge_offers ( + offer_id text NOT NULL, + price_id text, + months smallint NOT NULL, + free_months smallint, + discount smallint, + status text NOT NULL, + created_at timestamp with time zone NOT NULL +); + + + +CREATE TABLE test_chat_schema.badge_prices ( + price_id text NOT NULL, + badge_type text NOT NULL, + month_price bigint NOT NULL, + currency text NOT NULL, + status text NOT NULL, + created_at timestamp with time zone NOT NULL +); + + + +CREATE TABLE test_chat_schema.badge_purchases ( + badge_purchase_id bigint NOT NULL, + 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, + status text NOT NULL, + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL, + user_id bigint, + purchase_priv_key bytea, + alert_acked_kind text, + alert_acked_episode text, + alert_snooze_until timestamp with time zone, + badge_code_redemption_id bigint +); + + + +ALTER TABLE test_chat_schema.badge_purchases ALTER COLUMN badge_purchase_id ADD GENERATED ALWAYS AS IDENTITY ( + SEQUENCE NAME test_chat_schema.badge_purchases_badge_purchase_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1 +); + + + +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, @@ -999,7 +1152,7 @@ CREATE TABLE test_chat_schema.groups ( public_member_count bigint, relay_request_retries bigint DEFAULT 0 NOT NULL, relay_request_delay bigint DEFAULT 0 NOT NULL, - relay_request_execute_at timestamp with time zone DEFAULT '1970-01-01 01:00:00+01'::timestamp with time zone NOT NULL, + relay_request_execute_at timestamp with time zone DEFAULT '1970-01-01 04:00:00+04'::timestamp with time zone NOT NULL, relay_inactive_at timestamp with time zone, relay_sent_web_domain text, roster_version bigint, @@ -1027,6 +1180,26 @@ ALTER TABLE test_chat_schema.groups ALTER COLUMN group_id ADD GENERATED ALWAYS A +CREATE TABLE test_chat_schema.invoices ( + invoice_id text NOT NULL, + 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_currency text, + payment_crypto_amount text, + expires_at timestamp with time zone NOT NULL, + status text NOT NULL, + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL +); + + + CREATE TABLE test_chat_schema.known_servers ( server_id bigint NOT NULL, host text NOT NULL, @@ -1160,6 +1333,26 @@ ALTER TABLE test_chat_schema.operator_usage_conditions ALTER COLUMN operator_usa +CREATE TABLE test_chat_schema.payments ( + payment_id text NOT NULL, + invoice_id text, + provider text NOT NULL, + provider_ref text, + amount bigint, + currency text, + status text NOT NULL, + exception text, + subscription_renews_at timestamp with time zone, + 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 +); + + + CREATE TABLE test_chat_schema.pending_group_messages ( pending_group_message_id bigint NOT NULL, group_member_id bigint NOT NULL, @@ -1454,6 +1647,19 @@ 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, @@ -1516,7 +1722,8 @@ CREATE TABLE test_chat_schema.users ( auto_accept_member_contacts smallint DEFAULT 0 NOT NULL, is_user_chat_relay smallint DEFAULT 0 NOT NULL, client_service smallint DEFAULT 0 NOT NULL, - auto_accept_group_invitations smallint DEFAULT 0 NOT NULL + auto_accept_group_invitations smallint DEFAULT 0 NOT NULL, + shown_badge_id bigint ); @@ -1555,6 +1762,61 @@ ALTER TABLE test_chat_schema.xftp_file_descriptions ALTER COLUMN file_descr_id A +ALTER TABLE ONLY test_chat_schema.badge_code_redemptions + ADD CONSTRAINT badge_code_redemptions_code_key UNIQUE (code); + + + +ALTER TABLE ONLY test_chat_schema.badge_code_redemptions + ADD CONSTRAINT badge_code_redemptions_pkey PRIMARY KEY (badge_code_redemption_id); + + + +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); + + + +ALTER TABLE ONLY test_chat_schema.badge_ledger + ADD CONSTRAINT badge_ledger_pkey PRIMARY KEY (entry_id); + + + +ALTER TABLE ONLY test_chat_schema.badge_offers + ADD CONSTRAINT badge_offers_pkey PRIMARY KEY (offer_id); + + + +ALTER TABLE ONLY test_chat_schema.badge_prices + ADD CONSTRAINT badge_prices_pkey PRIMARY KEY (price_id); + + + +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); + + + +ALTER TABLE ONLY test_chat_schema.badge_purchases + ADD CONSTRAINT badge_purchases_purchase_key_key UNIQUE (purchase_key); + + + +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); @@ -1745,6 +2007,11 @@ ALTER TABLE ONLY test_chat_schema.groups +ALTER TABLE ONLY test_chat_schema.invoices + ADD CONSTRAINT invoices_pkey PRIMARY KEY (invoice_id); + + + ALTER TABLE ONLY test_chat_schema.known_servers ADD CONSTRAINT known_servers_pkey PRIMARY KEY (server_id); @@ -1780,6 +2047,11 @@ ALTER TABLE ONLY test_chat_schema.operator_usage_conditions +ALTER TABLE ONLY test_chat_schema.payments + ADD CONSTRAINT payments_pkey PRIMARY KEY (payment_id); + + + ALTER TABLE ONLY test_chat_schema.pending_group_messages ADD CONSTRAINT pending_group_messages_pkey PRIMARY KEY (pending_group_message_id); @@ -1855,6 +2127,16 @@ 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); @@ -1907,6 +2189,70 @@ CREATE INDEX files_note_folder_id ON test_chat_schema.files USING btree (note_fo +CREATE INDEX idx_badge_code_redemptions_user ON test_chat_schema.badge_code_redemptions USING btree (user_id); + + + +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); + + + +CREATE INDEX idx_badge_issuances_purchase ON test_chat_schema.badge_issuances USING btree (badge_purchase_id, issuance_id); + + + +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); + + + +CREATE INDEX idx_badge_offers_price ON test_chat_schema.badge_offers USING btree (price_id); + + + +CREATE UNIQUE INDEX idx_badge_purchases_code_redemption ON test_chat_schema.badge_purchases USING btree (badge_code_redemption_id); + + + +CREATE INDEX idx_badge_purchases_user ON test_chat_schema.badge_purchases USING btree (user_id); + + + +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); @@ -2511,6 +2857,14 @@ CREATE INDEX idx_operator_usage_conditions_server_operator_id ON test_chat_schem +CREATE INDEX idx_payments_invoice ON test_chat_schema.payments USING btree (invoice_id); + + + +CREATE INDEX idx_payments_provider_ref ON test_chat_schema.payments USING btree (provider, provider_ref); + + + CREATE INDEX idx_pending_group_messages_group_member_id ON test_chat_schema.pending_group_messages USING btree (group_member_id); @@ -2639,6 +2993,10 @@ CREATE UNIQUE INDEX idx_user_contact_links_group_id ON test_chat_schema.user_con +CREATE INDEX idx_users_shown_badge ON test_chat_schema.users USING btree (shown_badge_id); + + + CREATE INDEX idx_xftp_file_descriptions_user_id ON test_chat_schema.xftp_file_descriptions USING btree (user_id); @@ -2659,6 +3017,91 @@ CREATE TRIGGER tr_group_members_update_update_summary AFTER UPDATE ON test_chat_ +ALTER TABLE ONLY test_chat_schema.badge_code_redemptions + ADD CONSTRAINT badge_code_redemptions_user_id_fkey FOREIGN KEY (user_id) REFERENCES test_chat_schema.users(user_id) ON DELETE CASCADE; + + + +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; + + + +ALTER TABLE ONLY test_chat_schema.badge_issuances + ADD CONSTRAINT badge_issuances_entry_id_fkey FOREIGN KEY (entry_id) REFERENCES test_chat_schema.badge_ledger(entry_id); + + + +ALTER TABLE ONLY test_chat_schema.badge_ledger + ADD CONSTRAINT badge_ledger_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_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); + + + +ALTER TABLE ONLY test_chat_schema.badge_purchases + ADD CONSTRAINT badge_purchases_badge_code_redemption_id_fkey FOREIGN KEY (badge_code_redemption_id) REFERENCES test_chat_schema.badge_code_redemptions(badge_code_redemption_id); + + + +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; @@ -3199,6 +3642,11 @@ ALTER TABLE ONLY test_chat_schema.operator_usage_conditions +ALTER TABLE ONLY test_chat_schema.payments + ADD CONSTRAINT payments_invoice_id_fkey FOREIGN KEY (invoice_id) REFERENCES test_chat_schema.invoices(invoice_id); + + + ALTER TABLE ONLY test_chat_schema.pending_group_messages ADD CONSTRAINT pending_group_messages_group_member_id_fkey FOREIGN KEY (group_member_id) REFERENCES test_chat_schema.group_members(group_member_id) ON DELETE CASCADE; @@ -3304,6 +3752,11 @@ 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; @@ -3314,6 +3767,11 @@ ALTER TABLE ONLY test_chat_schema.user_contact_links +ALTER TABLE ONLY test_chat_schema.users + ADD CONSTRAINT users_shown_badge_id_fkey FOREIGN KEY (shown_badge_id) REFERENCES test_chat_schema.badge_purchases(badge_purchase_id) ON DELETE SET NULL; + + + ALTER TABLE ONLY test_chat_schema.xftp_file_descriptions ADD CONSTRAINT xftp_file_descriptions_user_id_fkey FOREIGN KEY (user_id) REFERENCES test_chat_schema.users(user_id) ON DELETE CASCADE; diff --git a/src/Simplex/Chat/Store/SQLite/Migrations.hs b/src/Simplex/Chat/Store/SQLite/Migrations.hs index de5acc3c82..d420af7f4b 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations.hs @@ -171,6 +171,7 @@ import Simplex.Chat.Store.SQLite.Migrations.M20260720_server_roles import Simplex.Chat.Store.SQLite.Migrations.M20260723_contact_request_rejection import Simplex.Chat.Store.SQLite.Migrations.M20260813_auto_accept_group_invitations import Simplex.Chat.Store.SQLite.Migrations.M20260822_forward_link +import Simplex.Chat.Store.SQLite.Migrations.M20261001_user_badges import Simplex.Messaging.Agent.Store.Shared (Migration (..)) schemaMigrations :: [(String, Query, Maybe Query)] @@ -341,7 +342,8 @@ schemaMigrations = ("20260720_server_roles", m20260720_server_roles, Just down_m20260720_server_roles), ("20260723_contact_request_rejection", m20260723_contact_request_rejection, Just down_m20260723_contact_request_rejection), ("20260813_auto_accept_group_invitations", m20260813_auto_accept_group_invitations, Just down_m20260813_auto_accept_group_invitations), - ("20260822_forward_link", m20260822_forward_link, Just down_m20260822_forward_link) + ("20260822_forward_link", m20260822_forward_link, Just down_m20260822_forward_link), + ("20261001_user_badges", m20261001_user_badges, Just down_m20261001_user_badges) ] -- | The list of migrations in ascending order by date diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/M20261001_user_badges.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20261001_user_badges.hs index 94b2fbc41e..af91e24af0 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/M20261001_user_badges.hs +++ b/src/Simplex/Chat/Store/SQLite/Migrations/M20261001_user_badges.hs @@ -37,7 +37,7 @@ CREATE TABLE @invoices( status TEXT NOT NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL -); +) STRICT; CREATE TABLE @payments( payment_id TEXT NOT NULL PRIMARY KEY, @@ -53,7 +53,7 @@ CREATE TABLE @payments( cancelled INTEGER NOT NULL DEFAULT 0, created_at TEXT NOT NULL, updated_at TEXT NOT NULL -); +) STRICT; CREATE INDEX @idx_payments_provider_ref ON @payments(provider, provider_ref); @@ -69,7 +69,7 @@ CREATE TABLE @subscription_charges( 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, @@ -78,7 +78,7 @@ CREATE TABLE @badge_prices( currency TEXT NOT NULL, status TEXT NOT NULL, created_at TEXT NOT NULL -); +) STRICT; CREATE TABLE @badge_offers( offer_id TEXT NOT NULL PRIMARY KEY, @@ -88,7 +88,9 @@ CREATE TABLE @badge_offers( discount INTEGER, status TEXT NOT NULL, created_at TEXT NOT NULL -); +) STRICT; + +CREATE INDEX @idx_badge_offers_price ON @badge_offers(price_id); CREATE TABLE @badge_purchases( badge_purchase_id INTEGER PRIMARY KEY AUTOINCREMENT, @@ -102,7 +104,7 @@ CREATE TABLE @badge_purchases( updated_at TEXT NOT NULL, UNIQUE(purchase_key), UNIQUE(payment_id) -); +) STRICT; CREATE TABLE @badge_invoices( invoice_id TEXT NOT NULL PRIMARY KEY REFERENCES @invoices ON DELETE CASCADE, @@ -111,10 +113,14 @@ CREATE TABLE @badge_invoices( offer_id TEXT REFERENCES @badge_offers, months INTEGER NOT NULL, created_at TEXT NOT NULL -); +) STRICT; 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 TABLE @badge_subscription_changes( change_id TEXT NOT NULL PRIMARY KEY, badge_purchase_id INTEGER NOT NULL REFERENCES @badge_purchases ON DELETE CASCADE, @@ -127,7 +133,7 @@ CREATE TABLE @badge_subscription_changes( effective_at TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL -); +) STRICT; CREATE INDEX @idx_badge_subscription_changes_purchase ON @badge_subscription_changes(badge_purchase_id); @@ -149,7 +155,7 @@ CREATE TABLE @badge_ledger( charge_id TEXT REFERENCES @subscription_charges, from_purchase_id INTEGER REFERENCES @badge_purchases, to_purchase_id INTEGER REFERENCES @badge_purchases -); +) STRICT; CREATE UNIQUE INDEX @idx_badge_ledger_uuid ON @badge_ledger(entry_uuid); @@ -173,15 +179,18 @@ CREATE TABLE @badge_issuances( 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); |] badgeSchemaTablesDown :: Query badgeSchemaTablesDown = [sql| DROP INDEX @idx_badge_issuances_purchase; +DROP INDEX @idx_badge_issuances_entry; DROP TABLE @badge_issuances; DROP INDEX @idx_badge_ledger_uuid; DROP INDEX @idx_badge_ledger_purchase; @@ -193,6 +202,8 @@ DROP TABLE @badge_ledger; 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 TABLE @subscription_charges; @@ -200,6 +211,7 @@ 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; |] @@ -230,6 +242,8 @@ 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; +CREATE INDEX idx_users_shown_badge ON users(shown_badge_id); + CREATE TABLE badge_code_redemptions( badge_code_redemption_id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL REFERENCES users ON DELETE CASCADE, @@ -239,7 +253,7 @@ CREATE TABLE badge_code_redemptions( master_key BLOB NOT NULL, created_at TEXT NOT NULL, UNIQUE(code) -); +) STRICT; CREATE INDEX idx_badge_code_redemptions_user ON badge_code_redemptions(user_id); @@ -253,6 +267,7 @@ down_m20261001_user_badges = [sql| DROP INDEX idx_badge_purchases_code_redemption; DROP INDEX idx_badge_purchases_user; +DROP INDEX idx_users_shown_badge; ALTER TABLE users DROP COLUMN shown_badge_id; |] <> badgeSchemaDown "" diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql b/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql index a488d7da21..059b39ec89 100644 --- a/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql +++ b/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql @@ -54,7 +54,8 @@ CREATE TABLE users( auto_accept_member_contacts INTEGER NOT NULL DEFAULT 0, is_user_chat_relay INTEGER NOT NULL DEFAULT 0, client_service INTEGER NOT NULL DEFAULT 0, - auto_accept_group_invitations INTEGER NOT NULL DEFAULT 0, -- 1 for active user + auto_accept_group_invitations INTEGER NOT NULL DEFAULT 0, + shown_badge_id INTEGER REFERENCES badge_purchases ON DELETE SET NULL, -- 1 for active user FOREIGN KEY(user_id, local_display_name) REFERENCES display_names(user_id, local_display_name) ON DELETE RESTRICT @@ -851,6 +852,152 @@ CREATE TABLE rcv_roster_transfers( created_at TEXT NOT NULL DEFAULT(datetime('now')), updated_at TEXT NOT NULL DEFAULT(datetime('now')) ) STRICT; +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_currency TEXT, + payment_crypto_amount TEXT, + expires_at TEXT NOT NULL, + status TEXT NOT NULL, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL +) STRICT; +CREATE TABLE payments( + payment_id TEXT NOT NULL PRIMARY KEY, + invoice_id TEXT REFERENCES invoices, + provider TEXT NOT NULL, + provider_ref TEXT, + amount INTEGER, + currency TEXT, + status TEXT NOT NULL, + exception TEXT, + subscription_renews_at TEXT, + grace_until TEXT, + 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, + badge_type TEXT NOT NULL, + month_price INTEGER NOT NULL, + currency TEXT NOT NULL, + status TEXT NOT NULL, + created_at TEXT NOT NULL +) STRICT; +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 +) STRICT; +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, + user_id INTEGER REFERENCES users ON DELETE CASCADE, + purchase_priv_key BLOB, + alert_acked_kind TEXT, + 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 +) 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_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 + , + entry_type_unknown INTEGER NOT NULL DEFAULT 0, + entry_type_value TEXT +) STRICT; +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 TABLE badge_code_redemptions( + badge_code_redemption_id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL REFERENCES users ON DELETE CASCADE, + code TEXT NOT NULL, + purchase_key BLOB NOT NULL, + purchase_priv_key BLOB NOT NULL, + master_key BLOB NOT NULL, + created_at TEXT NOT NULL, + UNIQUE(code) +) STRICT; CREATE INDEX contact_profiles_index ON contact_profiles( display_name, full_name @@ -1385,6 +1532,37 @@ CREATE INDEX idx_files_roster_transfer_id ON files(roster_transfer_id); CREATE INDEX idx_chat_items_item_signed_by_group_member_id ON chat_items( item_signed_by_group_member_id ); +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 +); +CREATE INDEX idx_badge_issuances_entry ON badge_issuances(entry_id); +CREATE INDEX idx_badge_purchases_user ON badge_purchases(user_id); +CREATE INDEX idx_users_shown_badge ON users(shown_badge_id); +CREATE INDEX idx_badge_code_redemptions_user ON badge_code_redemptions( + user_id +); +CREATE UNIQUE INDEX idx_badge_purchases_code_redemption ON badge_purchases( + badge_code_redemption_id +); CREATE TRIGGER on_group_members_insert_update_summary AFTER INSERT ON group_members FOR EACH ROW