feat: sync official star gift lifecycle tooling
Sync telesrv 4c0e2d9 (feat: complete official star gift lifecycle and admin tooling). Public adjustments: skipped private docs/deploy-nginx/README source changes, kept iamxvbaba/td public dependency, replaced local/private sample IP and orange seed label.
This commit is contained in:
parent
f2c2fd0236
commit
14bf7d1e20
92 changed files with 14768 additions and 727 deletions
|
|
@ -0,0 +1,72 @@
|
|||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM public.star_gift_collectible_models
|
||||
WHERE rarity_kind <> 'permille' OR crafted
|
||||
) THEN
|
||||
RAISE EXCEPTION 'cannot downgrade while categorical/crafted collectible models exist';
|
||||
END IF;
|
||||
END;
|
||||
$$;
|
||||
|
||||
DROP INDEX IF EXISTS public.star_gift_catalog_revisions_official_source_idx;
|
||||
|
||||
ALTER TABLE public.star_gift_collectible_backdrops
|
||||
DROP CONSTRAINT star_gift_collectible_backdrop_rarity_check,
|
||||
ALTER COLUMN rarity_permille SET NOT NULL,
|
||||
DROP COLUMN rarity_kind,
|
||||
ADD CONSTRAINT star_gift_collectible_backdrop_rarity_check CHECK (rarity_permille BETWEEN 1 AND 1000);
|
||||
|
||||
ALTER TABLE public.star_gift_collectible_patterns
|
||||
DROP CONSTRAINT star_gift_collectible_pattern_official_document_check,
|
||||
DROP CONSTRAINT star_gift_collectible_pattern_rarity_check,
|
||||
ALTER COLUMN rarity_permille SET NOT NULL,
|
||||
DROP COLUMN official_document_id,
|
||||
DROP COLUMN rarity_kind,
|
||||
ADD CONSTRAINT star_gift_collectible_pattern_rarity_check CHECK (rarity_permille BETWEEN 1 AND 1000);
|
||||
|
||||
ALTER TABLE public.star_gift_collectible_models
|
||||
DROP CONSTRAINT star_gift_collectible_model_official_document_check,
|
||||
DROP CONSTRAINT star_gift_collectible_model_rarity_check,
|
||||
ALTER COLUMN rarity_permille SET NOT NULL,
|
||||
DROP COLUMN official_document_id,
|
||||
DROP COLUMN crafted,
|
||||
DROP COLUMN rarity_kind,
|
||||
ADD CONSTRAINT star_gift_collectible_model_rarity_check CHECK (rarity_permille BETWEEN 1 AND 1000);
|
||||
|
||||
ALTER TABLE public.star_gift_collectible_revisions
|
||||
DROP CONSTRAINT star_gift_collectible_official_source_check,
|
||||
DROP COLUMN source_manifest_sha256,
|
||||
DROP COLUMN official_gift_id;
|
||||
|
||||
ALTER TABLE public.star_gift_catalog_revisions
|
||||
DROP CONSTRAINT star_gift_catalog_official_source_check,
|
||||
DROP COLUMN official_source,
|
||||
DROP COLUMN source_manifest_sha256,
|
||||
DROP COLUMN official_gift_id;
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.telesrv_guard_collectible_revision() RETURNS trigger
|
||||
LANGUAGE plpgsql AS $$
|
||||
BEGIN
|
||||
IF TG_OP = 'DELETE' THEN
|
||||
IF OLD.status = 'published' THEN
|
||||
RAISE EXCEPTION 'published collectible revision is immutable';
|
||||
END IF;
|
||||
RETURN OLD;
|
||||
END IF;
|
||||
|
||||
IF OLD.status = 'published' THEN
|
||||
IF NEW.gift_id <> OLD.gift_id OR NEW.revision <> OLD.revision OR
|
||||
NEW.upgrade_stars <> OLD.upgrade_stars OR NEW.supply_total <> OLD.supply_total OR
|
||||
NEW.slug_prefix <> OLD.slug_prefix OR NEW.status <> OLD.status OR
|
||||
NEW.created_by <> OLD.created_by OR NEW.command_id <> OLD.command_id OR
|
||||
NEW.created_at <> OLD.created_at OR NEW.published_at <> OLD.published_at THEN
|
||||
RAISE EXCEPTION 'published collectible revision is immutable';
|
||||
END IF;
|
||||
IF NEW.issued <> OLD.issued + 1 THEN
|
||||
RAISE EXCEPTION 'published collectible issuance must advance exactly once';
|
||||
END IF;
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
88
deploy/migrations/0093_official_star_gift_attributes.up.sql
Normal file
88
deploy/migrations/0093_official_star_gift_attributes.up.sql
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
-- Preserve the complete Layer 228 official collectible attribute shape. Display rarity is
|
||||
-- distinct from regular-upgrade selection eligibility, and official provenance is recorded
|
||||
-- on both immutable revisions created by one import command.
|
||||
|
||||
ALTER TABLE public.star_gift_catalog_revisions
|
||||
ADD COLUMN official_gift_id bigint,
|
||||
ADD COLUMN source_manifest_sha256 bytea,
|
||||
ADD COLUMN official_source jsonb,
|
||||
ADD CONSTRAINT star_gift_catalog_official_source_check CHECK (
|
||||
(official_gift_id IS NULL AND source_manifest_sha256 IS NULL AND official_source IS NULL) OR
|
||||
(official_gift_id > 0 AND source_manifest_sha256 IS NOT NULL AND official_source IS NOT NULL AND
|
||||
octet_length(source_manifest_sha256) = 32 AND jsonb_typeof(official_source) = 'object')
|
||||
);
|
||||
|
||||
ALTER TABLE public.star_gift_collectible_revisions
|
||||
ADD COLUMN official_gift_id bigint,
|
||||
ADD COLUMN source_manifest_sha256 bytea,
|
||||
ADD CONSTRAINT star_gift_collectible_official_source_check CHECK (
|
||||
(official_gift_id IS NULL AND source_manifest_sha256 IS NULL) OR
|
||||
(official_gift_id > 0 AND source_manifest_sha256 IS NOT NULL AND octet_length(source_manifest_sha256) = 32)
|
||||
);
|
||||
|
||||
ALTER TABLE public.star_gift_collectible_models
|
||||
DROP CONSTRAINT star_gift_collectible_model_rarity_check,
|
||||
ALTER COLUMN rarity_permille DROP NOT NULL,
|
||||
ADD COLUMN rarity_kind text DEFAULT 'permille' NOT NULL,
|
||||
ADD COLUMN crafted boolean DEFAULT false NOT NULL,
|
||||
ADD COLUMN official_document_id bigint,
|
||||
ADD CONSTRAINT star_gift_collectible_model_rarity_check CHECK (
|
||||
rarity_kind IN ('permille', 'uncommon', 'rare', 'epic', 'legendary') AND
|
||||
((rarity_kind = 'permille' AND rarity_permille BETWEEN 1 AND 1000 AND NOT crafted) OR
|
||||
(rarity_kind <> 'permille' AND rarity_permille IS NULL AND crafted))
|
||||
),
|
||||
ADD CONSTRAINT star_gift_collectible_model_official_document_check CHECK (
|
||||
official_document_id IS NULL OR official_document_id > 0
|
||||
);
|
||||
|
||||
ALTER TABLE public.star_gift_collectible_patterns
|
||||
DROP CONSTRAINT star_gift_collectible_pattern_rarity_check,
|
||||
ALTER COLUMN rarity_permille DROP NOT NULL,
|
||||
ADD COLUMN rarity_kind text DEFAULT 'permille' NOT NULL,
|
||||
ADD COLUMN official_document_id bigint,
|
||||
ADD CONSTRAINT star_gift_collectible_pattern_rarity_check CHECK (
|
||||
rarity_kind = 'permille' AND rarity_permille BETWEEN 1 AND 1000
|
||||
),
|
||||
ADD CONSTRAINT star_gift_collectible_pattern_official_document_check CHECK (
|
||||
official_document_id IS NULL OR official_document_id > 0
|
||||
);
|
||||
|
||||
ALTER TABLE public.star_gift_collectible_backdrops
|
||||
DROP CONSTRAINT star_gift_collectible_backdrop_rarity_check,
|
||||
ALTER COLUMN rarity_permille DROP NOT NULL,
|
||||
ADD COLUMN rarity_kind text DEFAULT 'permille' NOT NULL,
|
||||
ADD CONSTRAINT star_gift_collectible_backdrop_rarity_check CHECK (
|
||||
rarity_kind = 'permille' AND rarity_permille BETWEEN 1 AND 1000
|
||||
);
|
||||
|
||||
CREATE INDEX star_gift_catalog_revisions_official_source_idx
|
||||
ON public.star_gift_catalog_revisions(official_gift_id, id DESC)
|
||||
WHERE official_gift_id IS NOT NULL;
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.telesrv_guard_collectible_revision() RETURNS trigger
|
||||
LANGUAGE plpgsql AS $$
|
||||
BEGIN
|
||||
IF TG_OP = 'DELETE' THEN
|
||||
IF OLD.status = 'published' THEN
|
||||
RAISE EXCEPTION 'published collectible revision is immutable';
|
||||
END IF;
|
||||
RETURN OLD;
|
||||
END IF;
|
||||
|
||||
IF OLD.status = 'published' THEN
|
||||
IF NEW.gift_id <> OLD.gift_id OR NEW.revision <> OLD.revision OR
|
||||
NEW.upgrade_stars <> OLD.upgrade_stars OR NEW.supply_total <> OLD.supply_total OR
|
||||
NEW.slug_prefix <> OLD.slug_prefix OR NEW.status <> OLD.status OR
|
||||
NEW.created_by <> OLD.created_by OR NEW.command_id <> OLD.command_id OR
|
||||
NEW.created_at <> OLD.created_at OR NEW.published_at <> OLD.published_at OR
|
||||
NEW.official_gift_id IS DISTINCT FROM OLD.official_gift_id OR
|
||||
NEW.source_manifest_sha256 IS DISTINCT FROM OLD.source_manifest_sha256 THEN
|
||||
RAISE EXCEPTION 'published collectible revision is immutable';
|
||||
END IF;
|
||||
IF NEW.issued <> OLD.issued + 1 THEN
|
||||
RAISE EXCEPTION 'published collectible issuance must advance exactly once';
|
||||
END IF;
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
34
deploy/migrations/0094_star_gift_catalog_shape.down.sql
Normal file
34
deploy/migrations/0094_star_gift_catalog_shape.down.sql
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
DROP TABLE IF EXISTS public.star_gift_user_purchases;
|
||||
|
||||
ALTER TABLE public.star_gift_catalog_revisions
|
||||
DROP CONSTRAINT IF EXISTS star_gift_catalog_revision_background_check,
|
||||
DROP CONSTRAINT IF EXISTS star_gift_catalog_revision_auction_check,
|
||||
DROP CONSTRAINT IF EXISTS star_gift_catalog_revision_released_by_check,
|
||||
DROP CONSTRAINT IF EXISTS star_gift_catalog_revision_supply_check,
|
||||
DROP COLUMN IF EXISTS background_text_color,
|
||||
DROP COLUMN IF EXISTS background_edge_color,
|
||||
DROP COLUMN IF EXISTS background_center_color,
|
||||
DROP COLUMN IF EXISTS upgrade_variants,
|
||||
DROP COLUMN IF EXISTS auction_start_date,
|
||||
DROP COLUMN IF EXISTS gifts_per_round,
|
||||
DROP COLUMN IF EXISTS auction_slug,
|
||||
DROP COLUMN IF EXISTS locked_until_date,
|
||||
DROP COLUMN IF EXISTS per_user_total,
|
||||
DROP COLUMN IF EXISTS released_by_peer_id,
|
||||
DROP COLUMN IF EXISTS released_by_peer_type,
|
||||
DROP COLUMN IF EXISTS availability_total,
|
||||
DROP COLUMN IF EXISTS auction,
|
||||
DROP COLUMN IF EXISTS peer_color_available,
|
||||
DROP COLUMN IF EXISTS limited_per_user,
|
||||
DROP COLUMN IF EXISTS require_premium,
|
||||
DROP COLUMN IF EXISTS birthday,
|
||||
DROP COLUMN IF EXISTS sold_out,
|
||||
DROP COLUMN IF EXISTS limited;
|
||||
|
||||
ALTER TABLE public.star_gift_catalog
|
||||
DROP CONSTRAINT IF EXISTS star_gift_catalog_inventory_check,
|
||||
DROP COLUMN IF EXISTS last_sale_date,
|
||||
DROP COLUMN IF EXISTS first_sale_date,
|
||||
DROP COLUMN IF EXISTS availability_resale,
|
||||
DROP COLUMN IF EXISTS resell_min_stars,
|
||||
DROP COLUMN IF EXISTS availability_remains;
|
||||
68
deploy/migrations/0094_star_gift_catalog_shape.up.sql
Normal file
68
deploy/migrations/0094_star_gift_catalog_shape.up.sql
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
-- Preserve the complete Layer 228 regular StarGift shape. Release facts are immutable
|
||||
-- catalog-revision data; inventory and sale timestamps belong to the mutable catalog
|
||||
-- aggregate. Per-user ownership limits are enforced by the transaction boundary rather
|
||||
-- than reconstructed from peer_star_gifts after the fact.
|
||||
|
||||
ALTER TABLE public.star_gift_catalog
|
||||
ADD COLUMN availability_remains integer DEFAULT 0 NOT NULL,
|
||||
ADD COLUMN availability_resale bigint DEFAULT 0 NOT NULL,
|
||||
ADD COLUMN resell_min_stars bigint DEFAULT 0 NOT NULL,
|
||||
ADD COLUMN first_sale_date integer DEFAULT 0 NOT NULL,
|
||||
ADD COLUMN last_sale_date integer DEFAULT 0 NOT NULL,
|
||||
ADD CONSTRAINT star_gift_catalog_inventory_check CHECK (
|
||||
availability_remains >= 0 AND availability_resale >= 0 AND resell_min_stars >= 0 AND
|
||||
first_sale_date >= 0 AND last_sale_date >= 0 AND
|
||||
(last_sale_date = 0 OR first_sale_date > 0) AND
|
||||
(first_sale_date = 0 OR last_sale_date = 0 OR last_sale_date >= first_sale_date)
|
||||
);
|
||||
|
||||
ALTER TABLE public.star_gift_catalog_revisions
|
||||
ADD COLUMN limited boolean DEFAULT false NOT NULL,
|
||||
ADD COLUMN sold_out boolean DEFAULT false NOT NULL,
|
||||
ADD COLUMN birthday boolean DEFAULT false NOT NULL,
|
||||
ADD COLUMN require_premium boolean DEFAULT false NOT NULL,
|
||||
ADD COLUMN limited_per_user boolean DEFAULT false NOT NULL,
|
||||
ADD COLUMN peer_color_available boolean DEFAULT false NOT NULL,
|
||||
ADD COLUMN auction boolean DEFAULT false NOT NULL,
|
||||
ADD COLUMN availability_total integer DEFAULT 0 NOT NULL,
|
||||
ADD COLUMN released_by_peer_type text,
|
||||
ADD COLUMN released_by_peer_id bigint,
|
||||
ADD COLUMN per_user_total integer DEFAULT 0 NOT NULL,
|
||||
ADD COLUMN locked_until_date integer DEFAULT 0 NOT NULL,
|
||||
ADD COLUMN auction_slug text DEFAULT '' NOT NULL,
|
||||
ADD COLUMN gifts_per_round integer DEFAULT 0 NOT NULL,
|
||||
ADD COLUMN auction_start_date integer DEFAULT 0 NOT NULL,
|
||||
ADD COLUMN upgrade_variants integer DEFAULT 0 NOT NULL,
|
||||
ADD COLUMN background_center_color integer,
|
||||
ADD COLUMN background_edge_color integer,
|
||||
ADD COLUMN background_text_color integer,
|
||||
ADD CONSTRAINT star_gift_catalog_revision_supply_check CHECK (
|
||||
availability_total >= 0 AND
|
||||
per_user_total >= 0 AND locked_until_date >= 0 AND upgrade_variants >= 0 AND
|
||||
((limited AND availability_total > 0) OR (NOT limited AND availability_total = 0)) AND
|
||||
(NOT sold_out OR limited) AND
|
||||
((limited_per_user AND per_user_total > 0) OR (NOT limited_per_user AND per_user_total = 0))
|
||||
),
|
||||
ADD CONSTRAINT star_gift_catalog_revision_released_by_check CHECK (
|
||||
(released_by_peer_type IS NULL AND released_by_peer_id IS NULL) OR
|
||||
(released_by_peer_type IN ('user', 'chat', 'channel') AND released_by_peer_id > 0)
|
||||
),
|
||||
ADD CONSTRAINT star_gift_catalog_revision_auction_check CHECK (
|
||||
(auction AND limited AND auction_slug <> '' AND gifts_per_round > 0 AND auction_start_date > 0) OR
|
||||
(NOT auction AND auction_slug = '' AND gifts_per_round = 0 AND auction_start_date = 0)
|
||||
),
|
||||
ADD CONSTRAINT star_gift_catalog_revision_background_check CHECK (
|
||||
(background_center_color IS NULL AND background_edge_color IS NULL AND background_text_color IS NULL) OR
|
||||
(background_center_color BETWEEN 0 AND 16777215 AND
|
||||
background_edge_color BETWEEN 0 AND 16777215 AND
|
||||
background_text_color BETWEEN 0 AND 16777215)
|
||||
);
|
||||
|
||||
CREATE TABLE public.star_gift_user_purchases (
|
||||
user_id bigint NOT NULL,
|
||||
gift_id bigint NOT NULL REFERENCES public.star_gift_catalog(gift_id) ON DELETE RESTRICT,
|
||||
purchased_count integer DEFAULT 0 NOT NULL,
|
||||
updated_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
CONSTRAINT star_gift_user_purchases_pkey PRIMARY KEY (user_id, gift_id),
|
||||
CONSTRAINT star_gift_user_purchases_count_check CHECK (user_id > 0 AND purchased_count >= 0)
|
||||
);
|
||||
76
deploy/migrations/0095_star_gift_lifecycle.down.sql
Normal file
76
deploy/migrations/0095_star_gift_lifecycle.down.sql
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
DROP TRIGGER IF EXISTS peer_unique_star_gift_owner_guard ON public.peer_star_gifts;
|
||||
DROP TRIGGER IF EXISTS unique_star_gift_owner_guard ON public.unique_star_gifts;
|
||||
DROP FUNCTION IF EXISTS public.telesrv_check_unique_star_gift_owner();
|
||||
DROP TRIGGER IF EXISTS star_gift_listing_guard ON public.star_gift_listings;
|
||||
DROP FUNCTION IF EXISTS public.telesrv_guard_star_gift_listing();
|
||||
|
||||
DROP TABLE IF EXISTS public.ton_transactions;
|
||||
DROP TABLE IF EXISTS public.ton_balances;
|
||||
DROP TABLE IF EXISTS public.star_gift_auction_acquired;
|
||||
DROP TABLE IF EXISTS public.star_gift_auction_bid_payments;
|
||||
DROP TABLE IF EXISTS public.star_gift_auction_bids;
|
||||
DROP TABLE IF EXISTS public.star_gift_auctions;
|
||||
DROP TABLE IF EXISTS public.star_gift_withdrawal_requests;
|
||||
DROP TABLE IF EXISTS public.star_gift_notification_settings;
|
||||
DROP TABLE IF EXISTS public.star_gift_craft_commands;
|
||||
DROP TABLE IF EXISTS public.star_gift_transfer_commands;
|
||||
DROP TABLE IF EXISTS public.star_gift_purchase_commands;
|
||||
DROP TABLE IF EXISTS public.star_gift_drop_details_commands;
|
||||
DROP TABLE IF EXISTS public.star_gift_prepaid_upgrade_commands;
|
||||
DROP TABLE IF EXISTS public.star_gift_offers;
|
||||
DROP TABLE IF EXISTS public.star_gift_sales;
|
||||
DROP TABLE IF EXISTS public.star_gift_listings;
|
||||
|
||||
ALTER TABLE public.unique_star_gifts
|
||||
DROP CONSTRAINT IF EXISTS unique_star_gift_value_check,
|
||||
DROP CONSTRAINT IF EXISTS unique_star_gift_original_owner_check,
|
||||
DROP CONSTRAINT IF EXISTS unique_star_gift_host_peer_check,
|
||||
DROP CONSTRAINT IF EXISTS unique_star_gift_theme_peer_check,
|
||||
DROP CONSTRAINT IF EXISTS unique_star_gift_released_by_check,
|
||||
DROP CONSTRAINT IF EXISTS unique_star_gift_owner_check,
|
||||
DROP COLUMN IF EXISTS last_sale_amount,
|
||||
DROP COLUMN IF EXISTS last_sale_currency,
|
||||
DROP COLUMN IF EXISTS last_sale_date,
|
||||
DROP COLUMN IF EXISTS craft_chance_permille,
|
||||
DROP COLUMN IF EXISTS offer_min_stars,
|
||||
DROP COLUMN IF EXISTS host_peer_id,
|
||||
DROP COLUMN IF EXISTS host_peer_type,
|
||||
DROP COLUMN IF EXISTS theme_peer_id,
|
||||
DROP COLUMN IF EXISTS theme_peer_type,
|
||||
DROP COLUMN IF EXISTS value_usd_amount,
|
||||
DROP COLUMN IF EXISTS value_currency,
|
||||
DROP COLUMN IF EXISTS value_amount,
|
||||
DROP COLUMN IF EXISTS released_by_peer_id,
|
||||
DROP COLUMN IF EXISTS released_by_peer_type,
|
||||
DROP COLUMN IF EXISTS gift_address,
|
||||
DROP COLUMN IF EXISTS owner_address,
|
||||
DROP COLUMN IF EXISTS owner_name,
|
||||
DROP COLUMN IF EXISTS crafted,
|
||||
DROP COLUMN IF EXISTS original_owner_peer_id,
|
||||
DROP COLUMN IF EXISTS original_owner_peer_type,
|
||||
DROP COLUMN IF EXISTS burned,
|
||||
DROP COLUMN IF EXISTS theme_available,
|
||||
DROP COLUMN IF EXISTS resale_ton_only,
|
||||
DROP COLUMN IF EXISTS require_premium;
|
||||
|
||||
UPDATE public.unique_star_gifts u
|
||||
SET owner_peer_type=p.owner_peer_type, owner_peer_id=p.owner_peer_id
|
||||
FROM public.peer_star_gifts p
|
||||
WHERE p.unique_gift_id=u.id AND (u.owner_peer_type IS NULL OR u.owner_peer_id IS NULL);
|
||||
ALTER TABLE public.unique_star_gifts
|
||||
ALTER COLUMN owner_peer_type SET NOT NULL,
|
||||
ALTER COLUMN owner_peer_id SET NOT NULL,
|
||||
ADD CONSTRAINT unique_star_gift_owner_check CHECK (owner_peer_type IN ('user','channel') AND owner_peer_id>0);
|
||||
|
||||
ALTER TABLE public.peer_star_gifts
|
||||
DROP COLUMN IF EXISTS prepaid_upgrade_hash,
|
||||
DROP COLUMN IF EXISTS gift_num,
|
||||
DROP CONSTRAINT IF EXISTS peer_star_gifts_lifecycle_check,
|
||||
DROP COLUMN IF EXISTS can_craft_at,
|
||||
DROP COLUMN IF EXISTS drop_original_details_stars,
|
||||
DROP COLUMN IF EXISTS can_resell_at,
|
||||
DROP COLUMN IF EXISTS can_transfer_at,
|
||||
DROP COLUMN IF EXISTS can_export_at,
|
||||
DROP COLUMN IF EXISTS transfer_stars,
|
||||
DROP COLUMN IF EXISTS lifecycle_status,
|
||||
ADD CONSTRAINT peer_star_gifts_terminal_state_check CHECK (NOT converted OR unique_gift_id IS NULL);
|
||||
425
deploy/migrations/0095_star_gift_lifecycle.up.sql
Normal file
425
deploy/migrations/0095_star_gift_lifecycle.up.sql
Normal file
|
|
@ -0,0 +1,425 @@
|
|||
-- Complete collectible Star Gift lifecycle: ownership state, transfer/resale, purchase
|
||||
-- offers, crafting, auctions, notification preferences and the explicit TON boundary.
|
||||
|
||||
ALTER TABLE public.peer_star_gifts
|
||||
DROP CONSTRAINT IF EXISTS peer_star_gifts_terminal_state_check,
|
||||
ADD COLUMN lifecycle_status text DEFAULT 'active' NOT NULL,
|
||||
ADD COLUMN transfer_stars bigint DEFAULT 0 NOT NULL,
|
||||
ADD COLUMN prepaid_upgrade_hash text DEFAULT '' NOT NULL,
|
||||
ADD COLUMN gift_num integer DEFAULT 0 NOT NULL,
|
||||
ADD COLUMN can_export_at integer DEFAULT 0 NOT NULL,
|
||||
ADD COLUMN can_transfer_at integer DEFAULT 0 NOT NULL,
|
||||
ADD COLUMN can_resell_at integer DEFAULT 0 NOT NULL,
|
||||
ADD COLUMN drop_original_details_stars bigint DEFAULT 0 NOT NULL,
|
||||
ADD COLUMN can_craft_at integer DEFAULT 0 NOT NULL;
|
||||
|
||||
UPDATE public.peer_star_gifts SET lifecycle_status='converted' WHERE converted;
|
||||
|
||||
ALTER TABLE public.peer_star_gifts
|
||||
ADD CONSTRAINT peer_star_gifts_lifecycle_check CHECK (
|
||||
lifecycle_status IN ('active', 'converted', 'burned', 'exported') AND
|
||||
transfer_stars >= 0 AND gift_num >= 0 AND can_export_at >= 0 AND can_transfer_at >= 0 AND
|
||||
can_resell_at >= 0 AND drop_original_details_stars >= 0 AND can_craft_at >= 0 AND
|
||||
((lifecycle_status='converted' AND converted AND unique_gift_id IS NULL) OR
|
||||
(lifecycle_status='active' AND NOT converted) OR
|
||||
(lifecycle_status IN ('burned','exported') AND NOT converted AND unique_gift_id IS NOT NULL))
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX peer_star_gifts_prepaid_upgrade_hash_uniq
|
||||
ON public.peer_star_gifts(prepaid_upgrade_hash) WHERE prepaid_upgrade_hash<>'';
|
||||
|
||||
ALTER TABLE public.unique_star_gifts
|
||||
ALTER COLUMN owner_peer_type DROP NOT NULL,
|
||||
ALTER COLUMN owner_peer_id DROP NOT NULL,
|
||||
DROP CONSTRAINT IF EXISTS unique_star_gift_owner_check,
|
||||
ADD COLUMN require_premium boolean DEFAULT false NOT NULL,
|
||||
ADD COLUMN resale_ton_only boolean DEFAULT false NOT NULL,
|
||||
ADD COLUMN theme_available boolean DEFAULT false NOT NULL,
|
||||
ADD COLUMN burned boolean DEFAULT false NOT NULL,
|
||||
ADD COLUMN crafted boolean DEFAULT false NOT NULL,
|
||||
ADD COLUMN original_owner_peer_type text,
|
||||
ADD COLUMN original_owner_peer_id bigint,
|
||||
ADD COLUMN owner_name text DEFAULT '' NOT NULL,
|
||||
ADD COLUMN owner_address text DEFAULT '' NOT NULL,
|
||||
ADD COLUMN gift_address text DEFAULT '' NOT NULL,
|
||||
ADD COLUMN released_by_peer_type text,
|
||||
ADD COLUMN released_by_peer_id bigint,
|
||||
ADD COLUMN value_amount bigint DEFAULT 0 NOT NULL,
|
||||
ADD COLUMN value_currency text DEFAULT '' NOT NULL,
|
||||
ADD COLUMN value_usd_amount bigint DEFAULT 0 NOT NULL,
|
||||
ADD COLUMN theme_peer_type text,
|
||||
ADD COLUMN theme_peer_id bigint,
|
||||
ADD COLUMN host_peer_type text,
|
||||
ADD COLUMN host_peer_id bigint,
|
||||
ADD COLUMN offer_min_stars integer DEFAULT 0 NOT NULL,
|
||||
ADD COLUMN craft_chance_permille integer DEFAULT 0 NOT NULL,
|
||||
ADD COLUMN last_sale_date integer DEFAULT 0 NOT NULL,
|
||||
ADD COLUMN last_sale_currency text DEFAULT '' NOT NULL,
|
||||
ADD COLUMN last_sale_amount bigint DEFAULT 0 NOT NULL,
|
||||
ADD CONSTRAINT unique_star_gift_owner_check CHECK (
|
||||
(owner_peer_type IN ('user','channel') AND owner_peer_id > 0 AND owner_address='') OR
|
||||
(owner_peer_type IS NULL AND owner_peer_id IS NULL AND owner_address<>'')
|
||||
),
|
||||
ADD CONSTRAINT unique_star_gift_released_by_check CHECK (
|
||||
(released_by_peer_type IS NULL AND released_by_peer_id IS NULL) OR
|
||||
(released_by_peer_type IN ('user','channel') AND released_by_peer_id > 0)
|
||||
),
|
||||
ADD CONSTRAINT unique_star_gift_theme_peer_check CHECK (
|
||||
(theme_peer_type IS NULL AND theme_peer_id IS NULL) OR
|
||||
(theme_peer_type IN ('user','channel') AND theme_peer_id > 0)
|
||||
),
|
||||
ADD CONSTRAINT unique_star_gift_host_peer_check CHECK (
|
||||
(host_peer_type IS NULL AND host_peer_id IS NULL) OR
|
||||
(host_peer_type IN ('user','channel') AND host_peer_id > 0)
|
||||
),
|
||||
ADD CONSTRAINT unique_star_gift_value_check CHECK (
|
||||
value_amount >= 0 AND value_usd_amount >= 0 AND offer_min_stars >= 0 AND
|
||||
craft_chance_permille BETWEEN 0 AND 1000 AND last_sale_date >= 0 AND last_sale_amount >= 0 AND
|
||||
((value_currency='' AND value_amount=0) OR value_currency<>'') AND
|
||||
((last_sale_currency='' AND last_sale_amount=0 AND last_sale_date=0) OR
|
||||
(last_sale_currency IN ('XTR','TON') AND last_sale_amount>0 AND last_sale_date>0)) AND
|
||||
(NOT burned OR owner_address='') AND
|
||||
((owner_address='' AND gift_address='') OR (owner_address<>'' AND gift_address<>''))
|
||||
);
|
||||
|
||||
UPDATE public.unique_star_gifts u
|
||||
SET original_owner_peer_type=p.owner_peer_type, original_owner_peer_id=p.owner_peer_id
|
||||
FROM public.peer_star_gifts p WHERE p.id=u.source_saved_gift_id;
|
||||
|
||||
ALTER TABLE public.unique_star_gifts
|
||||
ALTER COLUMN original_owner_peer_type SET NOT NULL,
|
||||
ALTER COLUMN original_owner_peer_id SET NOT NULL,
|
||||
ADD CONSTRAINT unique_star_gift_original_owner_check CHECK (
|
||||
original_owner_peer_type IN ('user','channel') AND original_owner_peer_id>0
|
||||
);
|
||||
|
||||
CREATE TABLE public.star_gift_listings (
|
||||
unique_gift_id bigint PRIMARY KEY REFERENCES public.unique_star_gifts(id) ON DELETE RESTRICT,
|
||||
seller_peer_type text NOT NULL,
|
||||
seller_peer_id bigint NOT NULL,
|
||||
currency text NOT NULL,
|
||||
amount bigint NOT NULL,
|
||||
listed_at integer NOT NULL,
|
||||
updated_at integer NOT NULL,
|
||||
version bigint DEFAULT 1 NOT NULL,
|
||||
CONSTRAINT star_gift_listing_seller_check CHECK (seller_peer_type IN ('user','channel') AND seller_peer_id>0),
|
||||
CONSTRAINT star_gift_listing_amount_check CHECK (currency IN ('XTR','TON') AND amount>0 AND listed_at>0 AND updated_at>=listed_at)
|
||||
);
|
||||
CREATE INDEX star_gift_listings_gift_price_idx ON public.star_gift_listings(currency, amount, unique_gift_id);
|
||||
CREATE INDEX star_gift_listings_updated_idx ON public.star_gift_listings(updated_at DESC, unique_gift_id DESC);
|
||||
|
||||
CREATE TABLE public.star_gift_sales (
|
||||
id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
unique_gift_id bigint NOT NULL REFERENCES public.unique_star_gifts(id) ON DELETE RESTRICT,
|
||||
seller_peer_type text NOT NULL,
|
||||
seller_peer_id bigint NOT NULL,
|
||||
buyer_peer_type text NOT NULL,
|
||||
buyer_peer_id bigint NOT NULL,
|
||||
currency text NOT NULL,
|
||||
amount bigint NOT NULL,
|
||||
commission_amount bigint DEFAULT 0 NOT NULL,
|
||||
sold_at integer NOT NULL,
|
||||
command_key text NOT NULL,
|
||||
CONSTRAINT star_gift_sales_command_uniq UNIQUE(command_key),
|
||||
CONSTRAINT star_gift_sales_peer_check CHECK (
|
||||
seller_peer_type IN ('user','channel') AND seller_peer_id>0 AND
|
||||
buyer_peer_type IN ('user','channel') AND buyer_peer_id>0),
|
||||
CONSTRAINT star_gift_sales_amount_check CHECK (
|
||||
currency IN ('XTR','TON') AND amount>0 AND commission_amount>=0 AND commission_amount<=amount AND sold_at>0)
|
||||
);
|
||||
CREATE INDEX star_gift_sales_unique_date_idx ON public.star_gift_sales(unique_gift_id, sold_at DESC);
|
||||
|
||||
CREATE TABLE public.star_gift_offers (
|
||||
id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
buyer_user_id bigint NOT NULL,
|
||||
owner_peer_type text NOT NULL,
|
||||
owner_peer_id bigint NOT NULL,
|
||||
unique_gift_id bigint NOT NULL REFERENCES public.unique_star_gifts(id) ON DELETE RESTRICT,
|
||||
currency text NOT NULL,
|
||||
amount bigint NOT NULL,
|
||||
random_id bigint NOT NULL,
|
||||
offer_msg_id integer DEFAULT 0 NOT NULL,
|
||||
buyer_msg_id integer DEFAULT 0 NOT NULL,
|
||||
status text DEFAULT 'pending' NOT NULL,
|
||||
created_at integer NOT NULL,
|
||||
expires_at integer NOT NULL,
|
||||
resolved_at integer DEFAULT 0 NOT NULL,
|
||||
balance_after bigint DEFAULT 0 NOT NULL,
|
||||
expiry_notified boolean DEFAULT false NOT NULL,
|
||||
CONSTRAINT star_gift_offer_random_uniq UNIQUE(buyer_user_id, random_id),
|
||||
CONSTRAINT star_gift_offer_owner_msg_uniq UNIQUE(owner_peer_type, owner_peer_id, offer_msg_id),
|
||||
CONSTRAINT star_gift_offer_peer_check CHECK (buyer_user_id>0 AND owner_peer_type IN ('user','channel') AND owner_peer_id>0),
|
||||
CONSTRAINT star_gift_offer_amount_check CHECK (currency IN ('XTR','TON') AND amount>0),
|
||||
CONSTRAINT star_gift_offer_status_check CHECK (status IN ('pending','accepted','declined','expired','cancelled')),
|
||||
CONSTRAINT star_gift_offer_time_check CHECK (created_at>0 AND expires_at>created_at AND resolved_at>=0 AND
|
||||
((status='pending' AND resolved_at=0) OR (status<>'pending' AND resolved_at>=created_at)))
|
||||
);
|
||||
CREATE INDEX star_gift_offers_pending_expiry_idx ON public.star_gift_offers(expires_at, id) WHERE status='pending';
|
||||
CREATE INDEX star_gift_offers_unique_pending_idx ON public.star_gift_offers(unique_gift_id, id) WHERE status='pending';
|
||||
|
||||
CREATE TABLE public.star_gift_transfer_commands (
|
||||
actor_user_id bigint NOT NULL,
|
||||
command_key text NOT NULL,
|
||||
unique_gift_id bigint NOT NULL REFERENCES public.unique_star_gifts(id) ON DELETE RESTRICT,
|
||||
from_peer_type text NOT NULL,
|
||||
from_peer_id bigint NOT NULL,
|
||||
to_peer_type text NOT NULL,
|
||||
to_peer_id bigint NOT NULL,
|
||||
charge_stars bigint DEFAULT 0 NOT NULL,
|
||||
balance_after bigint DEFAULT 0 NOT NULL,
|
||||
created_at integer NOT NULL,
|
||||
CONSTRAINT star_gift_transfer_commands_pkey PRIMARY KEY(actor_user_id, command_key),
|
||||
CONSTRAINT star_gift_transfer_command_peer_check CHECK (
|
||||
actor_user_id>0 AND from_peer_type IN ('user','channel') AND from_peer_id>0 AND
|
||||
to_peer_type IN ('user','channel') AND to_peer_id>0 AND charge_stars>=0 AND created_at>0)
|
||||
);
|
||||
|
||||
CREATE TABLE public.star_gift_purchase_commands (
|
||||
buyer_user_id bigint NOT NULL,
|
||||
command_key text NOT NULL,
|
||||
gift_id bigint NOT NULL REFERENCES public.star_gift_catalog(gift_id) ON DELETE RESTRICT,
|
||||
recipient_peer_type text NOT NULL,
|
||||
recipient_peer_id bigint NOT NULL,
|
||||
saved_gift_id bigint NOT NULL REFERENCES public.peer_star_gifts(id) ON DELETE RESTRICT,
|
||||
form_id bigint NOT NULL,
|
||||
charge_stars bigint NOT NULL,
|
||||
balance_after bigint NOT NULL,
|
||||
created_at integer NOT NULL,
|
||||
CONSTRAINT star_gift_purchase_commands_pkey PRIMARY KEY(buyer_user_id,command_key),
|
||||
CONSTRAINT star_gift_purchase_commands_form_uniq UNIQUE(buyer_user_id,form_id),
|
||||
CONSTRAINT star_gift_purchase_command_shape_check CHECK (
|
||||
buyer_user_id>0 AND recipient_peer_type IN ('user','channel') AND recipient_peer_id>0 AND
|
||||
form_id>0 AND charge_stars>0 AND balance_after>=0 AND created_at>0)
|
||||
);
|
||||
|
||||
CREATE TABLE public.star_gift_prepaid_upgrade_commands (
|
||||
payer_user_id bigint NOT NULL,
|
||||
command_key text NOT NULL,
|
||||
saved_gift_id bigint NOT NULL REFERENCES public.peer_star_gifts(id) ON DELETE RESTRICT,
|
||||
form_id bigint NOT NULL,
|
||||
charge_stars bigint NOT NULL,
|
||||
balance_after bigint NOT NULL,
|
||||
created_at integer NOT NULL,
|
||||
CONSTRAINT star_gift_prepaid_upgrade_commands_pkey PRIMARY KEY(payer_user_id, command_key),
|
||||
CONSTRAINT star_gift_prepaid_upgrade_commands_form_uniq UNIQUE(payer_user_id, form_id),
|
||||
CONSTRAINT star_gift_prepaid_upgrade_command_shape_check CHECK (
|
||||
payer_user_id>0 AND form_id>0 AND charge_stars>0 AND balance_after>=0 AND created_at>0)
|
||||
);
|
||||
|
||||
CREATE TABLE public.star_gift_drop_details_commands (
|
||||
user_id bigint NOT NULL,
|
||||
command_key text NOT NULL,
|
||||
saved_gift_id bigint NOT NULL REFERENCES public.peer_star_gifts(id) ON DELETE RESTRICT,
|
||||
unique_gift_id bigint NOT NULL REFERENCES public.unique_star_gifts(id) ON DELETE RESTRICT,
|
||||
form_id bigint NOT NULL,
|
||||
charge_stars bigint NOT NULL,
|
||||
balance_after bigint NOT NULL,
|
||||
created_at integer NOT NULL,
|
||||
CONSTRAINT star_gift_drop_details_commands_pkey PRIMARY KEY(user_id, command_key),
|
||||
CONSTRAINT star_gift_drop_details_commands_form_uniq UNIQUE(user_id, form_id),
|
||||
CONSTRAINT star_gift_drop_details_command_shape_check CHECK (
|
||||
user_id>0 AND form_id>0 AND charge_stars>0 AND balance_after>=0 AND created_at>0)
|
||||
);
|
||||
|
||||
CREATE TABLE public.star_gift_craft_commands (
|
||||
user_id bigint NOT NULL,
|
||||
command_key text NOT NULL,
|
||||
input_unique_gift_ids bigint[] NOT NULL,
|
||||
gift_id bigint NOT NULL REFERENCES public.star_gift_catalog(gift_id) ON DELETE RESTRICT,
|
||||
success boolean NOT NULL,
|
||||
result_unique_gift_id bigint REFERENCES public.unique_star_gifts(id) ON DELETE RESTRICT,
|
||||
chance_permille integer NOT NULL,
|
||||
created_at integer NOT NULL,
|
||||
CONSTRAINT star_gift_craft_commands_pkey PRIMARY KEY(user_id, command_key),
|
||||
CONSTRAINT star_gift_craft_shape_check CHECK (
|
||||
user_id>0 AND cardinality(input_unique_gift_ids) BETWEEN 1 AND 4 AND
|
||||
chance_permille BETWEEN 0 AND 1000 AND created_at>0 AND
|
||||
((success AND result_unique_gift_id IS NOT NULL) OR (NOT success AND result_unique_gift_id IS NULL)))
|
||||
);
|
||||
|
||||
CREATE TABLE public.star_gift_notification_settings (
|
||||
user_id bigint NOT NULL,
|
||||
channel_id bigint NOT NULL,
|
||||
enabled boolean DEFAULT true NOT NULL,
|
||||
updated_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
CONSTRAINT star_gift_notification_settings_pkey PRIMARY KEY(user_id, channel_id),
|
||||
CONSTRAINT star_gift_notification_settings_peer_check CHECK (user_id>0 AND channel_id>0)
|
||||
);
|
||||
|
||||
CREATE TABLE public.star_gift_withdrawal_requests (
|
||||
id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
unique_gift_id bigint NOT NULL REFERENCES public.unique_star_gifts(id) ON DELETE RESTRICT,
|
||||
owner_user_id bigint NOT NULL,
|
||||
provider text NOT NULL,
|
||||
provider_request_id text NOT NULL,
|
||||
url text NOT NULL,
|
||||
status text DEFAULT 'pending' NOT NULL,
|
||||
created_at integer NOT NULL,
|
||||
expires_at integer NOT NULL,
|
||||
completed_at integer DEFAULT 0 NOT NULL,
|
||||
CONSTRAINT star_gift_withdrawal_request_unique UNIQUE(unique_gift_id),
|
||||
CONSTRAINT star_gift_withdrawal_provider_request_uniq UNIQUE(provider, provider_request_id),
|
||||
CONSTRAINT star_gift_withdrawal_shape_check CHECK (
|
||||
owner_user_id>0 AND provider<>'' AND provider_request_id<>'' AND url<>'' AND
|
||||
status IN ('pending','completed','failed') AND created_at>0 AND expires_at>created_at AND completed_at>=0)
|
||||
);
|
||||
|
||||
CREATE TABLE public.star_gift_auctions (
|
||||
gift_id bigint PRIMARY KEY REFERENCES public.star_gift_catalog(gift_id) ON DELETE RESTRICT,
|
||||
slug text NOT NULL UNIQUE,
|
||||
version integer DEFAULT 1 NOT NULL,
|
||||
start_date integer NOT NULL,
|
||||
end_date integer NOT NULL,
|
||||
round_duration integer NOT NULL,
|
||||
gifts_per_round integer NOT NULL,
|
||||
total_rounds integer NOT NULL,
|
||||
current_round integer DEFAULT 0 NOT NULL,
|
||||
next_round_at integer NOT NULL,
|
||||
last_gift_num integer DEFAULT 0 NOT NULL,
|
||||
gifts_left integer NOT NULL,
|
||||
min_bid_amount bigint NOT NULL,
|
||||
status text DEFAULT 'pending' NOT NULL,
|
||||
updated_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
CONSTRAINT star_gift_auction_shape_check CHECK (
|
||||
version>0 AND start_date>0 AND end_date>start_date AND round_duration>0 AND
|
||||
gifts_per_round>0 AND total_rounds>0 AND current_round BETWEEN 0 AND total_rounds AND
|
||||
next_round_at>=start_date AND last_gift_num>=0 AND gifts_left>=0 AND min_bid_amount>0 AND
|
||||
status IN ('pending','active','completed','cancelled'))
|
||||
);
|
||||
|
||||
CREATE TABLE public.star_gift_auction_bids (
|
||||
gift_id bigint NOT NULL REFERENCES public.star_gift_auctions(gift_id) ON DELETE RESTRICT,
|
||||
bidder_user_id bigint NOT NULL,
|
||||
recipient_peer_type text NOT NULL,
|
||||
recipient_peer_id bigint NOT NULL,
|
||||
amount bigint NOT NULL,
|
||||
bid_date integer NOT NULL,
|
||||
hide_name boolean DEFAULT false NOT NULL,
|
||||
message text DEFAULT '' NOT NULL,
|
||||
returned boolean DEFAULT false NOT NULL,
|
||||
acquired_count integer DEFAULT 0 NOT NULL,
|
||||
active boolean DEFAULT true NOT NULL,
|
||||
version bigint DEFAULT 1 NOT NULL,
|
||||
CONSTRAINT star_gift_auction_bids_pkey PRIMARY KEY(gift_id, bidder_user_id),
|
||||
CONSTRAINT star_gift_auction_bid_peer_check CHECK (
|
||||
bidder_user_id>0 AND recipient_peer_type IN ('user','channel') AND recipient_peer_id>0),
|
||||
CONSTRAINT star_gift_auction_bid_amount_check CHECK (amount>0 AND bid_date>0 AND acquired_count>=0 AND version>0)
|
||||
);
|
||||
CREATE INDEX star_gift_auction_bids_rank_idx ON public.star_gift_auction_bids(gift_id, amount DESC, bid_date, bidder_user_id) WHERE active;
|
||||
|
||||
CREATE TABLE public.star_gift_auction_bid_payments (
|
||||
user_id bigint NOT NULL,
|
||||
form_id bigint NOT NULL,
|
||||
gift_id bigint NOT NULL REFERENCES public.star_gift_auctions(gift_id) ON DELETE RESTRICT,
|
||||
bid_amount bigint NOT NULL,
|
||||
balance_after bigint NOT NULL,
|
||||
created_at integer NOT NULL,
|
||||
CONSTRAINT star_gift_auction_bid_payments_pkey PRIMARY KEY(user_id, form_id),
|
||||
CONSTRAINT star_gift_auction_bid_payment_shape_check CHECK (
|
||||
user_id>0 AND form_id>0 AND bid_amount>0 AND balance_after>=0 AND created_at>0)
|
||||
);
|
||||
|
||||
CREATE TABLE public.star_gift_auction_acquired (
|
||||
id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
gift_id bigint NOT NULL REFERENCES public.star_gift_auctions(gift_id) ON DELETE RESTRICT,
|
||||
bidder_user_id bigint NOT NULL,
|
||||
recipient_peer_type text NOT NULL,
|
||||
recipient_peer_id bigint NOT NULL,
|
||||
saved_gift_id bigint REFERENCES public.peer_star_gifts(id) ON DELETE RESTRICT,
|
||||
bid_amount bigint NOT NULL,
|
||||
round integer NOT NULL,
|
||||
pos integer NOT NULL,
|
||||
gift_num integer,
|
||||
acquired_at integer NOT NULL,
|
||||
hide_name boolean DEFAULT false NOT NULL,
|
||||
message text DEFAULT '' NOT NULL,
|
||||
CONSTRAINT star_gift_auction_acquired_round_pos_uniq UNIQUE(gift_id, round, pos),
|
||||
CONSTRAINT star_gift_auction_acquired_shape_check CHECK (
|
||||
bidder_user_id>0 AND recipient_peer_type IN ('user','channel') AND recipient_peer_id>0 AND
|
||||
bid_amount>0 AND round>0 AND pos>0 AND acquired_at>0 AND (gift_num IS NULL OR gift_num>0))
|
||||
);
|
||||
CREATE INDEX star_gift_auction_acquired_user_idx ON public.star_gift_auction_acquired(bidder_user_id, gift_id, id);
|
||||
|
||||
CREATE TABLE public.ton_balances (
|
||||
user_id bigint PRIMARY KEY,
|
||||
balance_nanoton bigint DEFAULT 0 NOT NULL,
|
||||
granted boolean DEFAULT false NOT NULL,
|
||||
updated_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
CONSTRAINT ton_balances_check CHECK (user_id>0 AND balance_nanoton>=0)
|
||||
);
|
||||
|
||||
CREATE TABLE public.ton_transactions (
|
||||
id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
user_id bigint NOT NULL,
|
||||
amount_nanoton bigint NOT NULL,
|
||||
reason text NOT NULL,
|
||||
peer_type text,
|
||||
peer_id bigint,
|
||||
gift_id bigint,
|
||||
date integer NOT NULL,
|
||||
CONSTRAINT ton_transaction_amount_check CHECK (user_id>0 AND amount_nanoton<>0 AND date>0),
|
||||
CONSTRAINT ton_transaction_peer_check CHECK (
|
||||
(peer_type IS NULL AND peer_id IS NULL) OR (peer_type IN ('user','channel') AND peer_id>0))
|
||||
);
|
||||
CREATE INDEX ton_transactions_user_idx ON public.ton_transactions(user_id, id DESC);
|
||||
|
||||
CREATE FUNCTION public.telesrv_guard_star_gift_listing() RETURNS trigger LANGUAGE plpgsql AS $$
|
||||
DECLARE
|
||||
gift_owner_type text;
|
||||
gift_owner_id bigint;
|
||||
gift_burned boolean;
|
||||
BEGIN
|
||||
SELECT owner_peer_type, owner_peer_id, burned
|
||||
INTO gift_owner_type, gift_owner_id, gift_burned
|
||||
FROM public.unique_star_gifts WHERE id=NEW.unique_gift_id FOR SHARE;
|
||||
IF gift_burned OR gift_owner_type IS DISTINCT FROM NEW.seller_peer_type OR gift_owner_id IS DISTINCT FROM NEW.seller_peer_id THEN
|
||||
RAISE EXCEPTION 'star gift listing owner/state mismatch';
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
CREATE TRIGGER star_gift_listing_guard BEFORE INSERT OR UPDATE ON public.star_gift_listings
|
||||
FOR EACH ROW EXECUTE FUNCTION public.telesrv_guard_star_gift_listing();
|
||||
|
||||
CREATE FUNCTION public.telesrv_check_unique_star_gift_owner() RETURNS trigger LANGUAGE plpgsql AS $$
|
||||
DECLARE
|
||||
unique_id bigint;
|
||||
gift_owner_type text;
|
||||
gift_owner_id bigint;
|
||||
gift_owner_address text;
|
||||
gift_burned boolean;
|
||||
saved_status text;
|
||||
saved_owner_type text;
|
||||
saved_owner_id bigint;
|
||||
BEGIN
|
||||
IF TG_TABLE_NAME = 'unique_star_gifts' THEN
|
||||
unique_id := COALESCE(NEW.id, OLD.id);
|
||||
ELSE
|
||||
unique_id := COALESCE(NEW.unique_gift_id, OLD.unique_gift_id);
|
||||
END IF;
|
||||
IF unique_id IS NULL THEN RETURN NULL; END IF;
|
||||
SELECT owner_peer_type, owner_peer_id, owner_address, burned
|
||||
INTO gift_owner_type, gift_owner_id, gift_owner_address, gift_burned
|
||||
FROM public.unique_star_gifts WHERE id=unique_id;
|
||||
IF NOT FOUND THEN RETURN NULL; END IF;
|
||||
SELECT lifecycle_status, owner_peer_type, owner_peer_id
|
||||
INTO saved_status, saved_owner_type, saved_owner_id
|
||||
FROM public.peer_star_gifts WHERE unique_gift_id=unique_id;
|
||||
IF NOT FOUND THEN RAISE EXCEPTION 'unique star gift missing saved aggregate'; END IF;
|
||||
IF gift_burned THEN
|
||||
IF saved_status <> 'burned' THEN RAISE EXCEPTION 'burned unique star gift has live saved aggregate'; END IF;
|
||||
ELSIF gift_owner_address <> '' THEN
|
||||
IF saved_status <> 'exported' THEN RAISE EXCEPTION 'exported unique star gift has non-exported saved aggregate'; END IF;
|
||||
ELSIF saved_status <> 'active' OR gift_owner_type IS DISTINCT FROM saved_owner_type OR gift_owner_id IS DISTINCT FROM saved_owner_id THEN
|
||||
RAISE EXCEPTION 'unique star gift owner mismatch';
|
||||
END IF;
|
||||
RETURN NULL;
|
||||
END;
|
||||
$$;
|
||||
CREATE CONSTRAINT TRIGGER unique_star_gift_owner_guard
|
||||
AFTER INSERT OR UPDATE ON public.unique_star_gifts DEFERRABLE INITIALLY DEFERRED
|
||||
FOR EACH ROW EXECUTE FUNCTION public.telesrv_check_unique_star_gift_owner();
|
||||
CREATE CONSTRAINT TRIGGER peer_unique_star_gift_owner_guard
|
||||
AFTER INSERT OR UPDATE ON public.peer_star_gifts DEFERRABLE INITIALLY DEFERRED
|
||||
FOR EACH ROW WHEN (NEW.unique_gift_id IS NOT NULL)
|
||||
EXECUTE FUNCTION public.telesrv_check_unique_star_gift_owner();
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
DROP INDEX IF EXISTS public.star_gift_auction_acquired_delivery_idx;
|
||||
DROP INDEX IF EXISTS public.star_gift_auctions_due_idx;
|
||||
DROP INDEX IF EXISTS public.star_gift_offers_resolution_outbox_idx;
|
||||
|
||||
ALTER TABLE public.star_gift_offers
|
||||
RENAME COLUMN resolution_notified TO expiry_notified;
|
||||
16
deploy/migrations/0096_star_gift_lifecycle_sweeper.up.sql
Normal file
16
deploy/migrations/0096_star_gift_lifecycle_sweeper.up.sql
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
-- Durable lifecycle sweep support. 0095 originally named this column after the
|
||||
-- first use case (expiry); cancelled offers use the same outbox boundary.
|
||||
ALTER TABLE public.star_gift_offers
|
||||
RENAME COLUMN expiry_notified TO resolution_notified;
|
||||
|
||||
CREATE INDEX star_gift_offers_resolution_outbox_idx
|
||||
ON public.star_gift_offers(id)
|
||||
WHERE status IN ('expired','cancelled') AND NOT resolution_notified;
|
||||
|
||||
CREATE INDEX star_gift_auctions_due_idx
|
||||
ON public.star_gift_auctions(status, next_round_at, gift_id)
|
||||
WHERE status IN ('pending','active');
|
||||
|
||||
CREATE INDEX star_gift_auction_acquired_delivery_idx
|
||||
ON public.star_gift_auction_acquired(gift_id, id)
|
||||
WHERE saved_gift_id IS NULL;
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
DROP TABLE IF EXISTS public.star_gift_conversions;
|
||||
DROP TABLE IF EXISTS public.channel_stars_transactions;
|
||||
DROP TABLE IF EXISTS public.channel_stars_balances;
|
||||
40
deploy/migrations/0097_star_gift_peer_stars_ledger.up.sql
Normal file
40
deploy/migrations/0097_star_gift_peer_stars_ledger.up.sql
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
-- Owner-scoped Stars revenue for channel Star Gifts. These are internal
|
||||
-- telesrv ledgers only; no blockchain, wallet, TON node or Fragment endpoint is
|
||||
-- contacted. Conversion is recorded as one aggregate terminal transition.
|
||||
|
||||
CREATE TABLE public.channel_stars_balances (
|
||||
channel_id bigint PRIMARY KEY,
|
||||
balance bigint DEFAULT 0 NOT NULL,
|
||||
updated_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
CONSTRAINT channel_stars_balances_shape_check CHECK (channel_id>0 AND balance>=0)
|
||||
);
|
||||
|
||||
CREATE TABLE public.channel_stars_transactions (
|
||||
id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
channel_id bigint NOT NULL,
|
||||
actor_user_id bigint NOT NULL,
|
||||
amount bigint NOT NULL,
|
||||
reason text NOT NULL,
|
||||
peer_type text DEFAULT '' NOT NULL,
|
||||
peer_id bigint DEFAULT 0 NOT NULL,
|
||||
gift_id bigint,
|
||||
date integer NOT NULL,
|
||||
CONSTRAINT channel_stars_transactions_shape_check CHECK (
|
||||
channel_id>0 AND actor_user_id>0 AND amount<>0 AND date>0 AND
|
||||
((peer_type='' AND peer_id=0) OR (peer_type IN ('user','channel') AND peer_id>0)))
|
||||
);
|
||||
CREATE INDEX channel_stars_transactions_channel_idx
|
||||
ON public.channel_stars_transactions(channel_id,id DESC);
|
||||
|
||||
CREATE TABLE public.star_gift_conversions (
|
||||
saved_gift_id bigint PRIMARY KEY REFERENCES public.peer_star_gifts(id) ON DELETE RESTRICT,
|
||||
actor_user_id bigint NOT NULL,
|
||||
owner_peer_type text NOT NULL,
|
||||
owner_peer_id bigint NOT NULL,
|
||||
amount bigint NOT NULL,
|
||||
balance_after bigint NOT NULL,
|
||||
converted_at integer NOT NULL,
|
||||
CONSTRAINT star_gift_conversions_shape_check CHECK (
|
||||
actor_user_id>0 AND owner_peer_type IN ('user','channel') AND owner_peer_id>0 AND
|
||||
amount>=0 AND balance_after>=0 AND converted_at>0)
|
||||
);
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
DROP TABLE IF EXISTS public.channel_ton_transactions;
|
||||
DROP TABLE IF EXISTS public.channel_ton_balances;
|
||||
25
deploy/migrations/0098_star_gift_channel_ton_ledger.up.sql
Normal file
25
deploy/migrations/0098_star_gift_channel_ton_ledger.up.sql
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
-- TON-denominated channel marketplace proceeds remain a telesrv-local ledger.
|
||||
-- No wallet, chain node, smart contract, Fragment or external RPC is involved.
|
||||
CREATE TABLE public.channel_ton_balances (
|
||||
channel_id bigint PRIMARY KEY,
|
||||
balance_nanoton bigint DEFAULT 0 NOT NULL,
|
||||
updated_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
CONSTRAINT channel_ton_balances_shape_check CHECK (channel_id>0 AND balance_nanoton>=0)
|
||||
);
|
||||
|
||||
CREATE TABLE public.channel_ton_transactions (
|
||||
id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
channel_id bigint NOT NULL,
|
||||
actor_user_id bigint NOT NULL,
|
||||
amount_nanoton bigint NOT NULL,
|
||||
reason text NOT NULL,
|
||||
peer_type text DEFAULT '' NOT NULL,
|
||||
peer_id bigint DEFAULT 0 NOT NULL,
|
||||
gift_id bigint,
|
||||
date integer NOT NULL,
|
||||
CONSTRAINT channel_ton_transactions_shape_check CHECK (
|
||||
channel_id>0 AND actor_user_id>0 AND amount_nanoton<>0 AND date>0 AND
|
||||
((peer_type='' AND peer_id=0) OR (peer_type IN ('user','channel') AND peer_id>0)))
|
||||
);
|
||||
CREATE INDEX channel_ton_transactions_channel_idx
|
||||
ON public.channel_ton_transactions(channel_id,id DESC);
|
||||
20
deploy/migrations/0099_star_gift_signed_form_ids.down.sql
Normal file
20
deploy/migrations/0099_star_gift_signed_form_ids.down.sql
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
ALTER TABLE public.star_gift_purchase_commands
|
||||
DROP CONSTRAINT star_gift_purchase_command_shape_check,
|
||||
ADD CONSTRAINT star_gift_purchase_command_shape_check CHECK (
|
||||
buyer_user_id>0 AND recipient_peer_type IN ('user','channel') AND recipient_peer_id>0 AND
|
||||
form_id>0 AND charge_stars>0 AND balance_after>=0 AND created_at>0) NOT VALID;
|
||||
|
||||
ALTER TABLE public.star_gift_prepaid_upgrade_commands
|
||||
DROP CONSTRAINT star_gift_prepaid_upgrade_command_shape_check,
|
||||
ADD CONSTRAINT star_gift_prepaid_upgrade_command_shape_check CHECK (
|
||||
payer_user_id>0 AND form_id>0 AND charge_stars>0 AND balance_after>=0 AND created_at>0) NOT VALID;
|
||||
|
||||
ALTER TABLE public.star_gift_drop_details_commands
|
||||
DROP CONSTRAINT star_gift_drop_details_command_shape_check,
|
||||
ADD CONSTRAINT star_gift_drop_details_command_shape_check CHECK (
|
||||
user_id>0 AND form_id>0 AND charge_stars>0 AND balance_after>=0 AND created_at>0) NOT VALID;
|
||||
|
||||
ALTER TABLE public.star_gift_auction_bid_payments
|
||||
DROP CONSTRAINT star_gift_auction_bid_payment_shape_check,
|
||||
ADD CONSTRAINT star_gift_auction_bid_payment_shape_check CHECK (
|
||||
user_id>0 AND form_id>0 AND bid_amount>0 AND balance_after>=0 AND created_at>0) NOT VALID;
|
||||
20
deploy/migrations/0099_star_gift_signed_form_ids.up.sql
Normal file
20
deploy/migrations/0099_star_gift_signed_form_ids.up.sql
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
ALTER TABLE public.star_gift_purchase_commands
|
||||
DROP CONSTRAINT star_gift_purchase_command_shape_check,
|
||||
ADD CONSTRAINT star_gift_purchase_command_shape_check CHECK (
|
||||
buyer_user_id>0 AND recipient_peer_type IN ('user','channel') AND recipient_peer_id>0 AND
|
||||
form_id<>0 AND charge_stars>0 AND balance_after>=0 AND created_at>0);
|
||||
|
||||
ALTER TABLE public.star_gift_prepaid_upgrade_commands
|
||||
DROP CONSTRAINT star_gift_prepaid_upgrade_command_shape_check,
|
||||
ADD CONSTRAINT star_gift_prepaid_upgrade_command_shape_check CHECK (
|
||||
payer_user_id>0 AND form_id<>0 AND charge_stars>0 AND balance_after>=0 AND created_at>0);
|
||||
|
||||
ALTER TABLE public.star_gift_drop_details_commands
|
||||
DROP CONSTRAINT star_gift_drop_details_command_shape_check,
|
||||
ADD CONSTRAINT star_gift_drop_details_command_shape_check CHECK (
|
||||
user_id>0 AND form_id<>0 AND charge_stars>0 AND balance_after>=0 AND created_at>0);
|
||||
|
||||
ALTER TABLE public.star_gift_auction_bid_payments
|
||||
DROP CONSTRAINT star_gift_auction_bid_payment_shape_check,
|
||||
ADD CONSTRAINT star_gift_auction_bid_payment_shape_check CHECK (
|
||||
user_id>0 AND form_id<>0 AND bid_amount>0 AND balance_after>=0 AND created_at>0);
|
||||
35
deploy/migrations/0100_star_gift_upgrade_semantics.down.sql
Normal file
35
deploy/migrations/0100_star_gift_upgrade_semantics.down.sql
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
UPDATE public.message_boxes
|
||||
SET media = jsonb_set(media, '{service_action,star_gift,upgrade_stars}', media #> '{service_action,star_gift,upgrade_price_stars}', true)
|
||||
#- '{service_action,star_gift,upgrade_price_stars}'
|
||||
WHERE media #>> '{service_action,kind}' = 'star_gift'
|
||||
AND media #> '{service_action,star_gift,upgrade_price_stars}' IS NOT NULL;
|
||||
|
||||
UPDATE public.channel_messages
|
||||
SET action = jsonb_set(action, '{StarGift,upgrade_stars}', action #> '{StarGift,upgrade_price_stars}', true)
|
||||
#- '{StarGift,upgrade_price_stars}'
|
||||
WHERE action #>> '{Type}' = 'star_gift'
|
||||
AND action #> '{StarGift,upgrade_price_stars}' IS NOT NULL;
|
||||
|
||||
UPDATE public.channel_admin_log_events
|
||||
SET message = jsonb_set(message, '{Action,StarGift,upgrade_stars}', message #> '{Action,StarGift,upgrade_price_stars}', true)
|
||||
#- '{Action,StarGift,upgrade_price_stars}'
|
||||
WHERE message #>> '{Action,Type}' = 'star_gift'
|
||||
AND message #> '{Action,StarGift,upgrade_price_stars}' IS NOT NULL;
|
||||
|
||||
UPDATE public.channel_admin_log_events
|
||||
SET prev_message = jsonb_set(prev_message, '{Action,StarGift,upgrade_stars}', prev_message #> '{Action,StarGift,upgrade_price_stars}', true)
|
||||
#- '{Action,StarGift,upgrade_price_stars}'
|
||||
WHERE prev_message #>> '{Action,Type}' = 'star_gift'
|
||||
AND prev_message #> '{Action,StarGift,upgrade_price_stars}' IS NOT NULL;
|
||||
|
||||
UPDATE public.channel_admin_log_events
|
||||
SET new_message = jsonb_set(new_message, '{Action,StarGift,upgrade_stars}', new_message #> '{Action,StarGift,upgrade_price_stars}', true)
|
||||
#- '{Action,StarGift,upgrade_price_stars}'
|
||||
WHERE new_message #>> '{Action,Type}' = 'star_gift'
|
||||
AND new_message #> '{Action,StarGift,upgrade_price_stars}' IS NOT NULL;
|
||||
|
||||
ALTER TABLE public.star_gift_upgrade_commands
|
||||
DROP CONSTRAINT IF EXISTS star_gift_upgrade_command_replay_shape_check,
|
||||
DROP COLUMN IF EXISTS keep_original_details,
|
||||
DROP COLUMN IF EXISTS require_prepaid,
|
||||
DROP COLUMN IF EXISTS charge_stars;
|
||||
79
deploy/migrations/0100_star_gift_upgrade_semantics.up.sql
Normal file
79
deploy/migrations/0100_star_gift_upgrade_semantics.up.sql
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
-- Split the two TL fields that TDesktop consumes differently:
|
||||
-- StarGift.upgrade_stars = current paid-upgrade price
|
||||
-- messageActionStarGift.upgrade_stars = amount already prepaid by sender
|
||||
-- Also persist the immutable command envelope required to replay an upgrade
|
||||
-- after the saved gift has entered its unique terminal state.
|
||||
|
||||
ALTER TABLE public.star_gift_upgrade_commands
|
||||
ADD COLUMN charge_stars bigint NOT NULL DEFAULT 0,
|
||||
ADD COLUMN require_prepaid boolean NOT NULL DEFAULT false,
|
||||
ADD COLUMN keep_original_details boolean NOT NULL DEFAULT false;
|
||||
|
||||
UPDATE public.star_gift_upgrade_commands c
|
||||
SET charge_stars = CASE WHEN c.form_id = 0 THEN 0 ELSE r.upgrade_stars END,
|
||||
require_prepaid = (c.form_id = 0),
|
||||
keep_original_details = u.keep_original_details
|
||||
FROM public.unique_star_gifts u
|
||||
JOIN public.star_gift_collectible_revisions r ON r.id = u.collectible_revision_id
|
||||
WHERE u.id = c.unique_gift_id;
|
||||
|
||||
ALTER TABLE public.star_gift_upgrade_commands
|
||||
ADD CONSTRAINT star_gift_upgrade_command_replay_shape_check CHECK (
|
||||
(require_prepaid AND form_id = 0 AND charge_stars = 0)
|
||||
OR
|
||||
(NOT require_prepaid AND form_id <> 0 AND charge_stars > 0)
|
||||
);
|
||||
|
||||
-- Private message boxes are the canonical durable snapshots used by history,
|
||||
-- live outbox delivery and updates.getDifference.
|
||||
UPDATE public.message_boxes
|
||||
SET media = CASE
|
||||
WHEN COALESCE((media #>> '{service_action,star_gift,prepaid_upgrade}')::boolean, false)
|
||||
THEN jsonb_set(media, '{service_action,star_gift,upgrade_price_stars}', media #> '{service_action,star_gift,upgrade_stars}', true)
|
||||
ELSE jsonb_set(media, '{service_action,star_gift,upgrade_price_stars}', media #> '{service_action,star_gift,upgrade_stars}', true)
|
||||
#- '{service_action,star_gift,upgrade_stars}'
|
||||
END
|
||||
WHERE media #>> '{service_action,kind}' = 'star_gift'
|
||||
AND media #> '{service_action,star_gift,upgrade_stars}' IS NOT NULL;
|
||||
|
||||
-- Channel service-message and Recent Actions snapshots use exported Go field
|
||||
-- names for their outer objects and the same snake_case StarGift payload.
|
||||
UPDATE public.channel_messages
|
||||
SET action = CASE
|
||||
WHEN COALESCE((action #>> '{StarGift,prepaid_upgrade}')::boolean, false)
|
||||
THEN jsonb_set(action, '{StarGift,upgrade_price_stars}', action #> '{StarGift,upgrade_stars}', true)
|
||||
ELSE jsonb_set(action, '{StarGift,upgrade_price_stars}', action #> '{StarGift,upgrade_stars}', true)
|
||||
#- '{StarGift,upgrade_stars}'
|
||||
END
|
||||
WHERE action #>> '{Type}' = 'star_gift'
|
||||
AND action #> '{StarGift,upgrade_stars}' IS NOT NULL;
|
||||
|
||||
UPDATE public.channel_admin_log_events
|
||||
SET message = CASE
|
||||
WHEN COALESCE((message #>> '{Action,StarGift,prepaid_upgrade}')::boolean, false)
|
||||
THEN jsonb_set(message, '{Action,StarGift,upgrade_price_stars}', message #> '{Action,StarGift,upgrade_stars}', true)
|
||||
ELSE jsonb_set(message, '{Action,StarGift,upgrade_price_stars}', message #> '{Action,StarGift,upgrade_stars}', true)
|
||||
#- '{Action,StarGift,upgrade_stars}'
|
||||
END
|
||||
WHERE message #>> '{Action,Type}' = 'star_gift'
|
||||
AND message #> '{Action,StarGift,upgrade_stars}' IS NOT NULL;
|
||||
|
||||
UPDATE public.channel_admin_log_events
|
||||
SET prev_message = CASE
|
||||
WHEN COALESCE((prev_message #>> '{Action,StarGift,prepaid_upgrade}')::boolean, false)
|
||||
THEN jsonb_set(prev_message, '{Action,StarGift,upgrade_price_stars}', prev_message #> '{Action,StarGift,upgrade_stars}', true)
|
||||
ELSE jsonb_set(prev_message, '{Action,StarGift,upgrade_price_stars}', prev_message #> '{Action,StarGift,upgrade_stars}', true)
|
||||
#- '{Action,StarGift,upgrade_stars}'
|
||||
END
|
||||
WHERE prev_message #>> '{Action,Type}' = 'star_gift'
|
||||
AND prev_message #> '{Action,StarGift,upgrade_stars}' IS NOT NULL;
|
||||
|
||||
UPDATE public.channel_admin_log_events
|
||||
SET new_message = CASE
|
||||
WHEN COALESCE((new_message #>> '{Action,StarGift,prepaid_upgrade}')::boolean, false)
|
||||
THEN jsonb_set(new_message, '{Action,StarGift,upgrade_price_stars}', new_message #> '{Action,StarGift,upgrade_stars}', true)
|
||||
ELSE jsonb_set(new_message, '{Action,StarGift,upgrade_price_stars}', new_message #> '{Action,StarGift,upgrade_stars}', true)
|
||||
#- '{Action,StarGift,upgrade_stars}'
|
||||
END
|
||||
WHERE new_message #>> '{Action,Type}' = 'star_gift'
|
||||
AND new_message #> '{Action,StarGift,upgrade_stars}' IS NOT NULL;
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
-- The up migration emits user-visible durable update facts. Rewinding pts or
|
||||
-- deleting events that may already have been delivered would create a hole in
|
||||
-- updates.getDifference, so rollback intentionally preserves those facts.
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
-- Migration 0100 corrected the durable Star Gift service-message JSON, but a
|
||||
-- TDesktop that had already cached the old message would otherwise keep using
|
||||
-- the conflated outer upgrade_stars field. Publish one durable edit event for
|
||||
-- every private message box whose paid-upgrade price was split by 0100.
|
||||
--
|
||||
-- This is deliberately a pts event, not a cache-only notification: history,
|
||||
-- updates.getDifference and online outbox delivery must all expose the same
|
||||
-- corrected message snapshot. The migration transaction keeps the message
|
||||
-- pts, user watermark, durable event and dispatch task atomic.
|
||||
DO $$
|
||||
DECLARE
|
||||
gift_box record;
|
||||
next_pts integer;
|
||||
event_date integer := EXTRACT(EPOCH FROM clock_timestamp())::integer;
|
||||
BEGIN
|
||||
FOR gift_box IN
|
||||
SELECT owner_user_id, box_id, peer_type, peer_id
|
||||
FROM public.message_boxes
|
||||
WHERE media #>> '{service_action,kind}' = 'star_gift'
|
||||
AND jsonb_typeof(media #> '{service_action,star_gift,upgrade_price_stars}') = 'number'
|
||||
AND (media #>> '{service_action,star_gift,upgrade_price_stars}')::bigint > 0
|
||||
ORDER BY owner_user_id, box_id
|
||||
LOOP
|
||||
INSERT INTO public.user_update_watermarks (user_id, contiguous_pts)
|
||||
VALUES (gift_box.owner_user_id, 0)
|
||||
ON CONFLICT (user_id) DO NOTHING;
|
||||
|
||||
UPDATE public.user_update_watermarks
|
||||
SET contiguous_pts = contiguous_pts + 1,
|
||||
updated_at = now()
|
||||
WHERE user_id = gift_box.owner_user_id
|
||||
RETURNING contiguous_pts INTO next_pts;
|
||||
|
||||
UPDATE public.message_boxes
|
||||
SET pts = next_pts
|
||||
WHERE owner_user_id = gift_box.owner_user_id
|
||||
AND box_id = gift_box.box_id;
|
||||
|
||||
INSERT INTO public.user_update_events (
|
||||
user_id,
|
||||
pts,
|
||||
pts_count,
|
||||
date,
|
||||
event_type,
|
||||
message_box_id,
|
||||
peer_type,
|
||||
peer_id
|
||||
) VALUES (
|
||||
gift_box.owner_user_id,
|
||||
next_pts,
|
||||
1,
|
||||
event_date,
|
||||
'edit_message',
|
||||
gift_box.box_id,
|
||||
gift_box.peer_type,
|
||||
gift_box.peer_id
|
||||
);
|
||||
|
||||
INSERT INTO public.dispatch_outbox (
|
||||
target_user_id,
|
||||
pts,
|
||||
event_type,
|
||||
exclude_auth_key_id,
|
||||
exclude_session_id
|
||||
) VALUES (
|
||||
gift_box.owner_user_id,
|
||||
next_pts,
|
||||
'edit_message',
|
||||
0,
|
||||
0
|
||||
);
|
||||
END LOOP;
|
||||
END
|
||||
$$;
|
||||
1
deploy/migrations/0102_star_gift_purchase_forms.down.sql
Normal file
1
deploy/migrations/0102_star_gift_purchase_forms.down.sql
Normal file
|
|
@ -0,0 +1 @@
|
|||
DROP TABLE IF EXISTS public.star_gift_purchase_forms;
|
||||
23
deploy/migrations/0102_star_gift_purchase_forms.up.sql
Normal file
23
deploy/migrations/0102_star_gift_purchase_forms.up.sql
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
CREATE TABLE public.star_gift_purchase_forms (
|
||||
buyer_user_id bigint NOT NULL,
|
||||
form_id bigint NOT NULL,
|
||||
gift_id bigint NOT NULL REFERENCES public.star_gift_catalog(gift_id) ON DELETE RESTRICT,
|
||||
revision_id bigint NOT NULL REFERENCES public.star_gift_catalog_revisions(id) ON DELETE RESTRICT,
|
||||
recipient_peer_type text NOT NULL,
|
||||
recipient_peer_id bigint NOT NULL,
|
||||
include_upgrade boolean DEFAULT false NOT NULL,
|
||||
hide_name boolean DEFAULT false NOT NULL,
|
||||
message text DEFAULT '' NOT NULL,
|
||||
charge_stars bigint NOT NULL,
|
||||
issued_at integer NOT NULL,
|
||||
expires_at integer NOT NULL,
|
||||
CONSTRAINT star_gift_purchase_forms_pkey PRIMARY KEY (buyer_user_id, form_id),
|
||||
CONSTRAINT star_gift_purchase_form_shape_check CHECK (
|
||||
buyer_user_id > 0 AND form_id <> 0 AND gift_id > 0 AND revision_id > 0 AND
|
||||
recipient_peer_type IN ('user', 'channel') AND recipient_peer_id > 0 AND
|
||||
charge_stars > 0 AND issued_at > 0 AND expires_at = issued_at + 600 AND
|
||||
char_length(message) <= 128)
|
||||
);
|
||||
|
||||
CREATE INDEX star_gift_purchase_forms_expiry_idx
|
||||
ON public.star_gift_purchase_forms (expires_at, buyer_user_id, form_id);
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
-- Projection repair events are durable account history and are intentionally
|
||||
-- not erased on rollback. Only remove the replay receipt column.
|
||||
ALTER TABLE public.star_gift_upgrade_commands
|
||||
DROP CONSTRAINT IF EXISTS star_gift_upgrade_command_source_edit_pts_check,
|
||||
DROP COLUMN IF EXISTS source_edit_pts;
|
||||
191
deploy/migrations/0103_star_gift_upgrade_message_links.up.sql
Normal file
191
deploy/migrations/0103_star_gift_upgrade_message_links.up.sql
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
-- A user-owned upgraded gift has one stable protocol identity: the original
|
||||
-- gift service-message id. The unique service message points back to it via
|
||||
-- saved_id, while the original message points forward to the box-local unique
|
||||
-- service message via upgrade_msg_id. Both projections must be durable pts
|
||||
-- edits so history, live delivery and updates.getDifference agree.
|
||||
|
||||
ALTER TABLE public.star_gift_upgrade_commands
|
||||
ADD COLUMN source_edit_pts integer DEFAULT 0 NOT NULL,
|
||||
ADD CONSTRAINT star_gift_upgrade_command_source_edit_pts_check CHECK (source_edit_pts >= 0);
|
||||
|
||||
DO $$
|
||||
DECLARE
|
||||
gift record;
|
||||
source_root record;
|
||||
upgrade_root record;
|
||||
pair record;
|
||||
next_pts integer;
|
||||
event_date integer := EXTRACT(EPOCH FROM clock_timestamp())::integer;
|
||||
repaired_source_media jsonb;
|
||||
repaired_unique_media jsonb;
|
||||
private_media jsonb;
|
||||
BEGIN
|
||||
FOR gift IN
|
||||
SELECT id, owner_peer_id, msg_id, upgrade_msg_id
|
||||
FROM public.peer_star_gifts
|
||||
WHERE owner_peer_type = 'user'
|
||||
AND unique_gift_id IS NOT NULL
|
||||
AND lifecycle_status = 'active'
|
||||
AND msg_id > 0
|
||||
AND upgrade_msg_id > 0
|
||||
ORDER BY id
|
||||
LOOP
|
||||
SELECT private_message_id, message_sender_id
|
||||
INTO STRICT source_root
|
||||
FROM public.message_boxes
|
||||
WHERE owner_user_id = gift.owner_peer_id
|
||||
AND box_id = gift.msg_id
|
||||
AND NOT deleted;
|
||||
|
||||
SELECT private_message_id, message_sender_id
|
||||
INTO STRICT upgrade_root
|
||||
FROM public.message_boxes
|
||||
WHERE owner_user_id = gift.owner_peer_id
|
||||
AND box_id = gift.upgrade_msg_id
|
||||
AND NOT deleted;
|
||||
|
||||
FOR pair IN
|
||||
SELECT source_box.owner_user_id,
|
||||
source_box.box_id AS source_box_id,
|
||||
source_box.peer_type AS source_peer_type,
|
||||
source_box.peer_id AS source_peer_id,
|
||||
source_box.media AS source_media,
|
||||
unique_box.box_id AS unique_box_id,
|
||||
unique_box.peer_type AS unique_peer_type,
|
||||
unique_box.peer_id AS unique_peer_id,
|
||||
unique_box.media AS unique_media
|
||||
FROM public.message_boxes source_box
|
||||
JOIN public.message_boxes unique_box
|
||||
ON unique_box.owner_user_id = source_box.owner_user_id
|
||||
AND unique_box.message_sender_id = upgrade_root.message_sender_id
|
||||
AND unique_box.private_message_id = upgrade_root.private_message_id
|
||||
AND NOT unique_box.deleted
|
||||
WHERE source_box.message_sender_id = source_root.message_sender_id
|
||||
AND source_box.private_message_id = source_root.private_message_id
|
||||
AND NOT source_box.deleted
|
||||
ORDER BY source_box.owner_user_id
|
||||
LOOP
|
||||
IF pair.source_media #>> '{service_action,kind}' <> 'star_gift' THEN
|
||||
RAISE EXCEPTION 'saved gift % source box % has invalid service action', gift.id, pair.source_box_id;
|
||||
END IF;
|
||||
IF pair.unique_media #>> '{service_action,kind}' <> 'star_gift_unique' THEN
|
||||
RAISE EXCEPTION 'saved gift % unique box % has invalid service action', gift.id, pair.unique_box_id;
|
||||
END IF;
|
||||
|
||||
repaired_source_media := jsonb_set(
|
||||
pair.source_media,
|
||||
'{service_action,star_gift,upgrade_msg_id}',
|
||||
to_jsonb(pair.unique_box_id::bigint),
|
||||
true
|
||||
) #- '{service_action,star_gift,can_upgrade}';
|
||||
repaired_unique_media := jsonb_set(
|
||||
pair.unique_media,
|
||||
'{service_action,star_gift_unique,saved_id}',
|
||||
to_jsonb(pair.source_box_id::bigint),
|
||||
true
|
||||
);
|
||||
|
||||
IF pair.source_media IS DISTINCT FROM repaired_source_media THEN
|
||||
INSERT INTO public.user_update_watermarks (user_id, contiguous_pts)
|
||||
VALUES (pair.owner_user_id, 0)
|
||||
ON CONFLICT (user_id) DO NOTHING;
|
||||
|
||||
UPDATE public.user_update_watermarks
|
||||
SET contiguous_pts = contiguous_pts + 1,
|
||||
updated_at = now()
|
||||
WHERE user_id = pair.owner_user_id
|
||||
RETURNING contiguous_pts INTO next_pts;
|
||||
|
||||
UPDATE public.message_boxes
|
||||
SET media = repaired_source_media,
|
||||
pts = next_pts
|
||||
WHERE owner_user_id = pair.owner_user_id
|
||||
AND box_id = pair.source_box_id
|
||||
AND NOT deleted;
|
||||
|
||||
INSERT INTO public.user_update_events (
|
||||
user_id, pts, pts_count, date, event_type,
|
||||
message_box_id, peer_type, peer_id
|
||||
) VALUES (
|
||||
pair.owner_user_id, next_pts, 1, event_date, 'edit_message',
|
||||
pair.source_box_id, pair.source_peer_type, pair.source_peer_id
|
||||
);
|
||||
|
||||
INSERT INTO public.dispatch_outbox (
|
||||
target_user_id, pts, event_type,
|
||||
exclude_auth_key_id, exclude_session_id
|
||||
) VALUES (pair.owner_user_id, next_pts, 'edit_message', 0, 0);
|
||||
|
||||
IF pair.owner_user_id = gift.owner_peer_id THEN
|
||||
UPDATE public.star_gift_upgrade_commands
|
||||
SET source_edit_pts = next_pts
|
||||
WHERE source_saved_gift_id = gift.id;
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
IF pair.unique_media IS DISTINCT FROM repaired_unique_media THEN
|
||||
INSERT INTO public.user_update_watermarks (user_id, contiguous_pts)
|
||||
VALUES (pair.owner_user_id, 0)
|
||||
ON CONFLICT (user_id) DO NOTHING;
|
||||
|
||||
UPDATE public.user_update_watermarks
|
||||
SET contiguous_pts = contiguous_pts + 1,
|
||||
updated_at = now()
|
||||
WHERE user_id = pair.owner_user_id
|
||||
RETURNING contiguous_pts INTO next_pts;
|
||||
|
||||
UPDATE public.message_boxes
|
||||
SET media = repaired_unique_media,
|
||||
pts = next_pts
|
||||
WHERE owner_user_id = pair.owner_user_id
|
||||
AND box_id = pair.unique_box_id
|
||||
AND NOT deleted;
|
||||
|
||||
INSERT INTO public.user_update_events (
|
||||
user_id, pts, pts_count, date, event_type,
|
||||
message_box_id, peer_type, peer_id
|
||||
) VALUES (
|
||||
pair.owner_user_id, next_pts, 1, event_date, 'edit_message',
|
||||
pair.unique_box_id, pair.unique_peer_type, pair.unique_peer_id
|
||||
);
|
||||
|
||||
INSERT INTO public.dispatch_outbox (
|
||||
target_user_id, pts, event_type,
|
||||
exclude_auth_key_id, exclude_session_id
|
||||
) VALUES (pair.owner_user_id, next_pts, 'edit_message', 0, 0);
|
||||
END IF;
|
||||
END LOOP;
|
||||
|
||||
SELECT media INTO STRICT private_media
|
||||
FROM public.message_boxes
|
||||
WHERE message_sender_id = source_root.message_sender_id
|
||||
AND private_message_id = source_root.private_message_id
|
||||
AND NOT deleted
|
||||
ORDER BY (owner_user_id = message_sender_id) DESC, owner_user_id
|
||||
LIMIT 1;
|
||||
UPDATE public.private_messages
|
||||
SET media = private_media
|
||||
WHERE sender_user_id = source_root.message_sender_id
|
||||
AND id = source_root.private_message_id;
|
||||
|
||||
SELECT media INTO STRICT private_media
|
||||
FROM public.message_boxes
|
||||
WHERE message_sender_id = upgrade_root.message_sender_id
|
||||
AND private_message_id = upgrade_root.private_message_id
|
||||
AND NOT deleted
|
||||
ORDER BY (owner_user_id = message_sender_id) DESC, owner_user_id
|
||||
LIMIT 1;
|
||||
UPDATE public.private_messages
|
||||
SET media = private_media
|
||||
WHERE sender_user_id = upgrade_root.message_sender_id
|
||||
AND id = upgrade_root.private_message_id;
|
||||
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM public.star_gift_upgrade_commands
|
||||
WHERE source_saved_gift_id = gift.id AND source_edit_pts <= 0
|
||||
) THEN
|
||||
RAISE EXCEPTION 'saved gift % is missing its owner source edit receipt', gift.id;
|
||||
END IF;
|
||||
END LOOP;
|
||||
END
|
||||
$$;
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
-- This migration repairs invalid persisted capabilities and intentionally does
|
||||
-- not restore them on downgrade: advertising Craft without an official crafted
|
||||
-- model would reintroduce a user-visible operation that can never succeed.
|
||||
SELECT 1;
|
||||
109
deploy/migrations/0104_star_gift_craft_capability.up.sql
Normal file
109
deploy/migrations/0104_star_gift_craft_capability.up.sql
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
-- Craft is a protocol capability, not a generic property of every collectible.
|
||||
-- A non-zero craft_chance_permille is valid only when the immutable official
|
||||
-- collectible revision contains at least one crafted model. Earlier versions
|
||||
-- advertised the default chance for every upgrade, including official sets
|
||||
-- such as Fresh Socks whose snapshot has no crafted model at all.
|
||||
--
|
||||
-- Repair the aggregate and every durable private-message projection together.
|
||||
-- Each visible message edit advances pts and is recoverable through both live
|
||||
-- outbox delivery and updates.getDifference.
|
||||
DO $$
|
||||
DECLARE
|
||||
gift_box record;
|
||||
next_pts integer;
|
||||
event_date integer := EXTRACT(EPOCH FROM clock_timestamp())::integer;
|
||||
repaired_media jsonb;
|
||||
BEGIN
|
||||
UPDATE public.unique_star_gifts unique_gift
|
||||
SET craft_chance_permille = 0,
|
||||
updated_at = now()
|
||||
WHERE unique_gift.craft_chance_permille > 0
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM public.star_gift_collectible_models model
|
||||
WHERE model.collectible_revision_id = unique_gift.collectible_revision_id
|
||||
AND model.crafted
|
||||
);
|
||||
|
||||
UPDATE public.peer_star_gifts saved_gift
|
||||
SET can_craft_at = 0
|
||||
FROM public.unique_star_gifts unique_gift
|
||||
WHERE unique_gift.id = saved_gift.unique_gift_id
|
||||
AND unique_gift.craft_chance_permille = 0
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM public.star_gift_collectible_models model
|
||||
WHERE model.collectible_revision_id = unique_gift.collectible_revision_id
|
||||
AND model.crafted
|
||||
);
|
||||
|
||||
FOR gift_box IN
|
||||
SELECT box.owner_user_id,
|
||||
box.box_id,
|
||||
box.peer_type,
|
||||
box.peer_id,
|
||||
box.message_sender_id,
|
||||
box.private_message_id,
|
||||
box.media
|
||||
FROM public.message_boxes box
|
||||
JOIN public.unique_star_gifts unique_gift
|
||||
ON unique_gift.id = (box.media #>> '{service_action,star_gift_unique,gift,ID}')::bigint
|
||||
WHERE box.media #>> '{service_action,kind}' = 'star_gift_unique'
|
||||
AND jsonb_typeof(box.media #> '{service_action,star_gift_unique,gift,CraftChancePermille}') = 'number'
|
||||
AND (box.media #>> '{service_action,star_gift_unique,gift,CraftChancePermille}')::integer > 0
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM public.star_gift_collectible_models model
|
||||
WHERE model.collectible_revision_id = unique_gift.collectible_revision_id
|
||||
AND model.crafted
|
||||
)
|
||||
ORDER BY box.owner_user_id, box.box_id
|
||||
LOOP
|
||||
repaired_media := gift_box.media
|
||||
#- '{service_action,star_gift_unique,gift,CraftChancePermille}'
|
||||
#- '{service_action,star_gift_unique,can_craft_at}';
|
||||
|
||||
INSERT INTO public.user_update_watermarks (user_id, contiguous_pts)
|
||||
VALUES (gift_box.owner_user_id, 0)
|
||||
ON CONFLICT (user_id) DO NOTHING;
|
||||
|
||||
UPDATE public.user_update_watermarks
|
||||
SET contiguous_pts = contiguous_pts + 1,
|
||||
updated_at = now()
|
||||
WHERE user_id = gift_box.owner_user_id
|
||||
RETURNING contiguous_pts INTO next_pts;
|
||||
|
||||
UPDATE public.message_boxes
|
||||
SET media = repaired_media,
|
||||
pts = next_pts
|
||||
WHERE owner_user_id = gift_box.owner_user_id
|
||||
AND box_id = gift_box.box_id
|
||||
AND NOT deleted;
|
||||
|
||||
UPDATE public.private_messages
|
||||
SET media = media
|
||||
#- '{service_action,star_gift_unique,gift,CraftChancePermille}'
|
||||
#- '{service_action,star_gift_unique,can_craft_at}',
|
||||
sender_snapshot = sender_snapshot
|
||||
#- '{message,Media,service_action,star_gift_unique,gift,CraftChancePermille}'
|
||||
#- '{message,Media,service_action,star_gift_unique,can_craft_at}'
|
||||
WHERE sender_user_id = gift_box.message_sender_id
|
||||
AND id = gift_box.private_message_id;
|
||||
|
||||
INSERT INTO public.user_update_events (
|
||||
user_id, pts, pts_count, date, event_type,
|
||||
message_box_id, peer_type, peer_id
|
||||
) VALUES (
|
||||
gift_box.owner_user_id, next_pts, 1, event_date, 'edit_message',
|
||||
gift_box.box_id, gift_box.peer_type, gift_box.peer_id
|
||||
);
|
||||
|
||||
INSERT INTO public.dispatch_outbox (
|
||||
target_user_id, pts, event_type,
|
||||
exclude_auth_key_id, exclude_session_id
|
||||
) VALUES (
|
||||
gift_box.owner_user_id, next_pts, 'edit_message', 0, 0
|
||||
);
|
||||
END LOOP;
|
||||
END
|
||||
$$;
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
ALTER TABLE public.star_gift_craft_commands
|
||||
DROP CONSTRAINT IF EXISTS star_gift_craft_source_edit_shape_check,
|
||||
DROP COLUMN IF EXISTS source_edit_pts;
|
||||
|
||||
-- Burned/crafted lifecycle facts and emitted edit events are authoritative
|
||||
-- business history. Downgrade intentionally does not resurrect consumed gifts.
|
||||
SELECT 1;
|
||||
195
deploy/migrations/0105_star_gift_craft_projection.up.sql
Normal file
195
deploy/migrations/0105_star_gift_craft_projection.up.sql
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
-- Craft outcomes consume their inputs permanently. Keep the aggregate, every
|
||||
-- visible messageActionStarGiftUnique snapshot, pts/outbox delivery and command
|
||||
-- replay receipt in one state model.
|
||||
ALTER TABLE public.star_gift_craft_commands
|
||||
ADD COLUMN source_edit_pts integer[] DEFAULT ARRAY[]::integer[] NOT NULL;
|
||||
|
||||
DO $$
|
||||
DECLARE
|
||||
gift_box record;
|
||||
next_pts integer;
|
||||
event_date integer := EXTRACT(EPOCH FROM clock_timestamp())::integer;
|
||||
repaired_media jsonb;
|
||||
BEGIN
|
||||
-- A successful legacy command would require reconstructing the crafted
|
||||
-- model snapshot, not merely flipping flags. Fail fast instead of silently
|
||||
-- fabricating that projection; the development database must be rebuilt or
|
||||
-- repaired explicitly if such a row ever exists.
|
||||
IF EXISTS (SELECT 1 FROM public.star_gift_craft_commands WHERE success) THEN
|
||||
RAISE EXCEPTION 'cannot migrate legacy successful craft command without an exact crafted message projection';
|
||||
END IF;
|
||||
|
||||
-- upgrade_msg_id means the current owner's unique service-message
|
||||
-- projection, not permanently the first upgrade message. Ownership moves
|
||||
-- replace msg_id with the new transfer/resale/offer message; repair rows
|
||||
-- written before that invariant was enforced.
|
||||
UPDATE public.peer_star_gifts saved_gift
|
||||
SET upgrade_msg_id = saved_gift.msg_id
|
||||
WHERE saved_gift.owner_peer_type = 'user'
|
||||
AND saved_gift.unique_gift_id IS NOT NULL
|
||||
AND saved_gift.msg_id > 0
|
||||
AND EXISTS (
|
||||
SELECT 1
|
||||
FROM public.message_boxes box
|
||||
WHERE box.owner_user_id = saved_gift.owner_peer_id
|
||||
AND box.box_id = saved_gift.msg_id
|
||||
AND NOT box.deleted
|
||||
AND box.media #>> '{service_action,kind}' = 'star_gift_unique'
|
||||
AND (box.media #>> '{service_action,star_gift_unique,gift,ID}')::bigint = saved_gift.unique_gift_id
|
||||
);
|
||||
|
||||
UPDATE public.peer_star_gifts
|
||||
SET upgrade_msg_id = 0
|
||||
WHERE owner_peer_type = 'channel'
|
||||
AND unique_gift_id IS NOT NULL
|
||||
AND upgrade_msg_id <> 0;
|
||||
|
||||
UPDATE public.unique_star_gifts
|
||||
SET burned = true,
|
||||
craft_chance_permille = 0,
|
||||
offer_min_stars = 0,
|
||||
updated_at = now()
|
||||
WHERE burned;
|
||||
|
||||
UPDATE public.peer_star_gifts
|
||||
SET lifecycle_status = 'burned',
|
||||
unsaved = true,
|
||||
pinned_order = 0,
|
||||
transfer_stars = 0,
|
||||
can_export_at = 0,
|
||||
can_transfer_at = 0,
|
||||
can_resell_at = 0,
|
||||
drop_original_details_stars = 0,
|
||||
can_craft_at = 0
|
||||
WHERE lifecycle_status = 'burned';
|
||||
|
||||
FOR gift_box IN
|
||||
SELECT box.owner_user_id,
|
||||
box.box_id,
|
||||
box.peer_type,
|
||||
box.peer_id,
|
||||
box.message_sender_id,
|
||||
box.private_message_id,
|
||||
box.media
|
||||
FROM public.message_boxes box
|
||||
JOIN public.unique_star_gifts unique_gift
|
||||
ON unique_gift.id = (box.media #>> '{service_action,star_gift_unique,gift,ID}')::bigint
|
||||
WHERE box.media #>> '{service_action,kind}' = 'star_gift_unique'
|
||||
AND unique_gift.burned
|
||||
AND (
|
||||
COALESCE((box.media #>> '{service_action,star_gift_unique,gift,Burned}')::boolean, false) = false
|
||||
OR COALESCE((box.media #>> '{service_action,star_gift_unique,gift,CraftChancePermille}')::integer, 0) <> 0
|
||||
OR box.media #> '{service_action,star_gift_unique,saved}' IS NOT NULL
|
||||
OR box.media #> '{service_action,star_gift_unique,can_craft_at}' IS NOT NULL
|
||||
)
|
||||
ORDER BY box.owner_user_id, box.box_id
|
||||
LOOP
|
||||
repaired_media := jsonb_set(
|
||||
jsonb_set(
|
||||
gift_box.media
|
||||
#- '{service_action,star_gift_unique,gift,CraftChancePermille}'
|
||||
#- '{service_action,star_gift_unique,saved}'
|
||||
#- '{service_action,star_gift_unique,can_export_at}'
|
||||
#- '{service_action,star_gift_unique,transfer_stars}'
|
||||
#- '{service_action,star_gift_unique,resale_amount}'
|
||||
#- '{service_action,star_gift_unique,can_transfer_at}'
|
||||
#- '{service_action,star_gift_unique,can_resell_at}'
|
||||
#- '{service_action,star_gift_unique,drop_original_details_stars}'
|
||||
#- '{service_action,star_gift_unique,can_craft_at}',
|
||||
'{service_action,star_gift_unique,gift,Burned}', 'true'::jsonb, true),
|
||||
'{service_action,star_gift_unique,gift,OfferMinStars}', '0'::jsonb, true);
|
||||
|
||||
INSERT INTO public.user_update_watermarks (user_id, contiguous_pts)
|
||||
VALUES (gift_box.owner_user_id, 0)
|
||||
ON CONFLICT (user_id) DO NOTHING;
|
||||
|
||||
UPDATE public.user_update_watermarks
|
||||
SET contiguous_pts = contiguous_pts + 1,
|
||||
updated_at = now()
|
||||
WHERE user_id = gift_box.owner_user_id
|
||||
RETURNING contiguous_pts INTO next_pts;
|
||||
|
||||
UPDATE public.message_boxes
|
||||
SET media = repaired_media,
|
||||
pts = next_pts
|
||||
WHERE owner_user_id = gift_box.owner_user_id
|
||||
AND box_id = gift_box.box_id
|
||||
AND NOT deleted;
|
||||
|
||||
UPDATE public.private_messages
|
||||
SET media = jsonb_set(
|
||||
jsonb_set(
|
||||
media
|
||||
#- '{service_action,star_gift_unique,gift,CraftChancePermille}'
|
||||
#- '{service_action,star_gift_unique,saved}'
|
||||
#- '{service_action,star_gift_unique,can_export_at}'
|
||||
#- '{service_action,star_gift_unique,transfer_stars}'
|
||||
#- '{service_action,star_gift_unique,resale_amount}'
|
||||
#- '{service_action,star_gift_unique,can_transfer_at}'
|
||||
#- '{service_action,star_gift_unique,can_resell_at}'
|
||||
#- '{service_action,star_gift_unique,drop_original_details_stars}'
|
||||
#- '{service_action,star_gift_unique,can_craft_at}',
|
||||
'{service_action,star_gift_unique,gift,Burned}', 'true'::jsonb, true),
|
||||
'{service_action,star_gift_unique,gift,OfferMinStars}', '0'::jsonb, true),
|
||||
sender_snapshot = jsonb_set(
|
||||
jsonb_set(
|
||||
sender_snapshot
|
||||
#- '{message,Media,service_action,star_gift_unique,gift,CraftChancePermille}'
|
||||
#- '{message,Media,service_action,star_gift_unique,saved}'
|
||||
#- '{message,Media,service_action,star_gift_unique,can_export_at}'
|
||||
#- '{message,Media,service_action,star_gift_unique,transfer_stars}'
|
||||
#- '{message,Media,service_action,star_gift_unique,resale_amount}'
|
||||
#- '{message,Media,service_action,star_gift_unique,can_transfer_at}'
|
||||
#- '{message,Media,service_action,star_gift_unique,can_resell_at}'
|
||||
#- '{message,Media,service_action,star_gift_unique,drop_original_details_stars}'
|
||||
#- '{message,Media,service_action,star_gift_unique,can_craft_at}',
|
||||
'{message,Media,service_action,star_gift_unique,gift,Burned}', 'true'::jsonb, true),
|
||||
'{message,Media,service_action,star_gift_unique,gift,OfferMinStars}', '0'::jsonb, true)
|
||||
WHERE sender_user_id = gift_box.message_sender_id
|
||||
AND id = gift_box.private_message_id;
|
||||
|
||||
INSERT INTO public.user_update_events (
|
||||
user_id, pts, pts_count, date, event_type,
|
||||
message_box_id, peer_type, peer_id
|
||||
) VALUES (
|
||||
gift_box.owner_user_id, next_pts, 1, event_date, 'edit_message',
|
||||
gift_box.box_id, gift_box.peer_type, gift_box.peer_id
|
||||
);
|
||||
|
||||
INSERT INTO public.dispatch_outbox (
|
||||
target_user_id, pts, event_type,
|
||||
exclude_auth_key_id, exclude_session_id
|
||||
) VALUES (
|
||||
gift_box.owner_user_id, next_pts, 'edit_message', 0, 0
|
||||
);
|
||||
END LOOP;
|
||||
|
||||
UPDATE public.star_gift_craft_commands command
|
||||
SET source_edit_pts = (
|
||||
SELECT array_agg(box.pts ORDER BY input.ordinality)::integer[] AS pts
|
||||
FROM unnest(command.input_unique_gift_ids) WITH ORDINALITY AS input(unique_gift_id, ordinality)
|
||||
JOIN public.unique_star_gifts unique_gift ON unique_gift.id = input.unique_gift_id
|
||||
JOIN public.peer_star_gifts saved_gift ON saved_gift.id = unique_gift.source_saved_gift_id
|
||||
JOIN public.message_boxes box
|
||||
ON box.owner_user_id = command.user_id
|
||||
AND box.box_id = saved_gift.upgrade_msg_id
|
||||
AND NOT box.deleted
|
||||
)
|
||||
WHERE NOT command.success;
|
||||
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM public.star_gift_craft_commands
|
||||
WHERE cardinality(source_edit_pts) <> cardinality(input_unique_gift_ids)
|
||||
OR array_position(source_edit_pts, 0) IS NOT NULL
|
||||
) THEN
|
||||
RAISE EXCEPTION 'craft command is missing an exact source message edit receipt';
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
ALTER TABLE public.star_gift_craft_commands
|
||||
ADD CONSTRAINT star_gift_craft_source_edit_shape_check CHECK (
|
||||
cardinality(source_edit_pts) = cardinality(input_unique_gift_ids)
|
||||
AND array_position(source_edit_pts, 0) IS NULL
|
||||
);
|
||||
Loading…
Add table
Add a link
Reference in a new issue