Merge remote-tracking branch 'upstream/main' into dev
This commit is contained in:
commit
6b29556ef8
836 changed files with 1598388 additions and 64684 deletions
10
deploy/migrations/0086_auth_key_protocol_expiry.down.sql
Normal file
10
deploy/migrations/0086_auth_key_protocol_expiry.down.sql
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
ALTER TABLE public.temp_auth_key_bindings
|
||||
DROP CONSTRAINT IF EXISTS temp_auth_key_bindings_perm_auth_key_id_fkey;
|
||||
|
||||
DROP INDEX IF EXISTS public.auth_keys_temporary_expiry_seek_idx;
|
||||
|
||||
ALTER TABLE public.auth_keys
|
||||
DROP CONSTRAINT IF EXISTS auth_keys_expires_at_valid;
|
||||
|
||||
ALTER TABLE public.auth_keys
|
||||
DROP COLUMN IF EXISTS expires_at;
|
||||
161
deploy/migrations/0086_auth_key_protocol_expiry.up.sql
Normal file
161
deploy/migrations/0086_auth_key_protocol_expiry.up.sql
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
-- Preserve the protocol key kind/lifetime established by p_q_inner_data(_temp).
|
||||
-- A temporary key must expire at the MTProto edge; it must never fall through
|
||||
-- to the RPC router and be mistaken for an independent permanent identity.
|
||||
ALTER TABLE public.auth_keys
|
||||
ADD COLUMN expires_at integer NOT NULL DEFAULT -1;
|
||||
|
||||
ALTER TABLE public.auth_keys
|
||||
ADD CONSTRAINT auth_keys_expires_at_valid CHECK (expires_at >= -1);
|
||||
|
||||
-- auth_keys.expires_at is the only protocol-lifetime fact. Retention seeks this
|
||||
-- partial index so unbound temporary handshakes and bound keys follow the same
|
||||
-- bounded cleanup path; the binding-side expiry index remains only for legacy
|
||||
-- rollback compatibility.
|
||||
CREATE INDEX auth_keys_temporary_expiry_seek_idx
|
||||
ON public.auth_keys (expires_at, auth_key_id)
|
||||
WHERE expires_at > 0;
|
||||
|
||||
-- Existing bound temporary keys are unambiguous and can be backfilled from the
|
||||
-- durable bind record. Unclassified historical keys remain -1 and are rejected
|
||||
-- once with protocol -404 after restart, forcing a clean handshake instead of
|
||||
-- guessing that they are permanent. New handshakes always write 0 (permanent)
|
||||
-- or their positive absolute expiry before dh_gen_ok.
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM public.temp_auth_key_bindings
|
||||
WHERE expires_at <= 0
|
||||
) THEN
|
||||
RAISE EXCEPTION 'invalid non-positive temporary auth key expiry; repair before migration 0086';
|
||||
END IF;
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM public.temp_auth_key_bindings AS b
|
||||
LEFT JOIN public.auth_keys AS k ON k.auth_key_id = b.perm_auth_key_id
|
||||
WHERE k.auth_key_id IS NULL
|
||||
) THEN
|
||||
RAISE EXCEPTION 'temporary auth key binding references missing permanent key; repair before migration 0086';
|
||||
END IF;
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM public.temp_auth_key_bindings
|
||||
WHERE temp_auth_key_id = perm_auth_key_id
|
||||
) THEN
|
||||
RAISE EXCEPTION 'temporary auth key self-binding; repair before migration 0086';
|
||||
END IF;
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM public.temp_auth_key_bindings AS temp_role
|
||||
JOIN public.temp_auth_key_bindings AS perm_role
|
||||
ON perm_role.perm_auth_key_id = temp_role.temp_auth_key_id
|
||||
) THEN
|
||||
RAISE EXCEPTION 'auth key appears in both temporary and permanent roles; repair before migration 0086';
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
UPDATE public.auth_keys AS k
|
||||
SET expires_at = b.expires_at
|
||||
FROM public.temp_auth_key_bindings AS b
|
||||
WHERE k.auth_key_id = b.temp_auth_key_id;
|
||||
|
||||
-- Do not normalize an early telesrv bug during reads. If a deployment contains
|
||||
-- an authorization written against a bound temp key, stop the migration and
|
||||
-- require an explicit data repair after inspecting the corresponding perm key.
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM public.authorizations AS a
|
||||
JOIN public.temp_auth_key_bindings AS b
|
||||
ON b.temp_auth_key_id = a.auth_key_id
|
||||
) THEN
|
||||
RAISE EXCEPTION 'invalid authorization on temporary auth key; repair before migration 0086';
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
-- Every durable table keyed by business/device auth identity must also be free
|
||||
-- of bound temporary IDs. These tables intentionally do not FK to auth_keys
|
||||
-- because several retain historical delivery facts; silently deleting the key
|
||||
-- would therefore strand an identity split instead of repairing it. The
|
||||
-- physical-session exclusion tuple in dispatch_outbox is deliberately omitted:
|
||||
-- it stores raw auth_key_id + session_id and a temporary raw key is valid there.
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM public.update_states AS s
|
||||
JOIN public.temp_auth_key_bindings AS b ON b.temp_auth_key_id = s.auth_key_id
|
||||
) THEN
|
||||
RAISE EXCEPTION 'update state references temporary auth key; repair before migration 0086';
|
||||
END IF;
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM public.bootstrap_update_jobs AS j
|
||||
JOIN public.temp_auth_key_bindings AS b ON b.temp_auth_key_id = j.auth_key_id
|
||||
) THEN
|
||||
RAISE EXCEPTION 'bootstrap update job references temporary auth key; repair before migration 0086';
|
||||
END IF;
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM public.secret_qts_watermarks AS q
|
||||
JOIN public.temp_auth_key_bindings AS b ON b.temp_auth_key_id = q.auth_key_id
|
||||
) THEN
|
||||
RAISE EXCEPTION 'secret qts watermark references temporary auth key; repair before migration 0086';
|
||||
END IF;
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM public.encrypted_message_queue AS q
|
||||
JOIN public.temp_auth_key_bindings AS b ON b.temp_auth_key_id = q.receiver_auth_key_id
|
||||
) THEN
|
||||
RAISE EXCEPTION 'encrypted message queue references temporary auth key; repair before migration 0086';
|
||||
END IF;
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM public.encrypted_state_event_delivery AS d
|
||||
JOIN public.temp_auth_key_bindings AS b ON b.temp_auth_key_id = d.auth_key_id
|
||||
) THEN
|
||||
RAISE EXCEPTION 'encrypted state delivery references temporary auth key; repair before migration 0086';
|
||||
END IF;
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM public.encrypted_state_events AS e
|
||||
JOIN public.temp_auth_key_bindings AS b ON b.temp_auth_key_id = e.target_auth_key_id
|
||||
) THEN
|
||||
RAISE EXCEPTION 'encrypted state event targets temporary auth key; repair before migration 0086';
|
||||
END IF;
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM public.secret_chats AS c
|
||||
JOIN public.temp_auth_key_bindings AS b
|
||||
ON b.temp_auth_key_id = c.admin_auth_key_id
|
||||
OR b.temp_auth_key_id = c.participant_auth_key_id
|
||||
) THEN
|
||||
RAISE EXCEPTION 'secret chat references temporary auth key; repair before migration 0086';
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
-- An authorization or the permanent side of a temp binding proves that the key
|
||||
-- is permanent. Logged-out, unreferenced legacy keys cannot be proven either
|
||||
-- way and intentionally keep the -1 sentinel described above.
|
||||
UPDATE public.auth_keys AS k
|
||||
SET expires_at = 0
|
||||
WHERE k.expires_at = -1
|
||||
AND (
|
||||
EXISTS (
|
||||
SELECT 1
|
||||
FROM public.authorizations AS a
|
||||
WHERE a.auth_key_id = k.auth_key_id
|
||||
)
|
||||
OR EXISTS (
|
||||
SELECT 1
|
||||
FROM public.temp_auth_key_bindings AS b
|
||||
WHERE b.perm_auth_key_id = k.auth_key_id
|
||||
)
|
||||
);
|
||||
|
||||
-- This FK is the durable serialization boundary between auth.bindTempAuthKey
|
||||
-- and permanent-key revoke/destroy. RESTRICT makes a concurrent delete fail
|
||||
-- closed; deletion paths remove referenced temp keys first and retry on the
|
||||
-- narrow FK race, so no committed binding can ever point at a missing perm key.
|
||||
ALTER TABLE public.temp_auth_key_bindings
|
||||
ADD CONSTRAINT temp_auth_key_bindings_perm_auth_key_id_fkey
|
||||
FOREIGN KEY (perm_auth_key_id)
|
||||
REFERENCES public.auth_keys(auth_key_id)
|
||||
ON DELETE RESTRICT;
|
||||
7
deploy/migrations/0087_auth_key_session_layers.down.sql
Normal file
7
deploy/migrations/0087_auth_key_session_layers.down.sql
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
DROP TABLE IF EXISTS public.auth_key_session_layers;
|
||||
|
||||
ALTER TABLE public.auth_keys
|
||||
DROP CONSTRAINT IF EXISTS auth_keys_layer_observation_id_valid,
|
||||
DROP COLUMN IF EXISTS layer_observation_id;
|
||||
|
||||
DROP SEQUENCE IF EXISTS public.auth_key_layer_observation_seq;
|
||||
38
deploy/migrations/0087_auth_key_session_layers.up.sql
Normal file
38
deploy/migrations/0087_auth_key_session_layers.up.sql
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
-- Short-lived, durable per-session invokeWithLayer watermark. The in-process
|
||||
-- exact-profile registry prevents ordinary replay rollback, while this table
|
||||
-- closes the durable commit/restart and cross-instance replacement-connection
|
||||
-- window for client msg_ids that are still fresh enough to be accepted by
|
||||
-- MTProto. It does not broadcast profile changes into an already-live remote
|
||||
-- physical connection; that connection remains frozen until its own selector.
|
||||
CREATE SEQUENCE public.auth_key_layer_observation_seq AS bigint;
|
||||
|
||||
ALTER TABLE public.auth_keys
|
||||
ADD COLUMN layer_observation_id bigint NOT NULL DEFAULT 0,
|
||||
ADD CONSTRAINT auth_keys_layer_observation_id_valid
|
||||
CHECK (layer_observation_id >= 0);
|
||||
|
||||
CREATE TABLE public.auth_key_session_layers (
|
||||
raw_auth_key_id bigint NOT NULL,
|
||||
session_id bigint NOT NULL,
|
||||
layer integer NOT NULL,
|
||||
msg_id bigint NOT NULL,
|
||||
observation_id bigint NOT NULL,
|
||||
expires_at timestamptz NOT NULL,
|
||||
PRIMARY KEY (raw_auth_key_id, session_id),
|
||||
CONSTRAINT auth_key_session_layers_auth_key_fkey
|
||||
FOREIGN KEY (raw_auth_key_id)
|
||||
REFERENCES public.auth_keys(auth_key_id)
|
||||
ON DELETE CASCADE,
|
||||
CONSTRAINT auth_key_session_layers_layer_valid CHECK (layer > 0),
|
||||
CONSTRAINT auth_key_session_layers_msg_id_valid
|
||||
CHECK (
|
||||
msg_id > 0
|
||||
AND msg_id % 4 = 0
|
||||
AND (msg_id & 4294967295) <> 0
|
||||
),
|
||||
CONSTRAINT auth_key_session_layers_observation_id_valid
|
||||
CHECK (observation_id > 0)
|
||||
);
|
||||
|
||||
CREATE INDEX auth_key_session_layers_expiry_idx
|
||||
ON public.auth_key_session_layers (expires_at, raw_auth_key_id, session_id);
|
||||
10
deploy/migrations/0088_account_freeze_state.down.sql
Normal file
10
deploy/migrations/0088_account_freeze_state.down.sql
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
ALTER TABLE account_restrictions
|
||||
DROP CONSTRAINT IF EXISTS account_restrictions_freeze_shape_check,
|
||||
DROP COLUMN IF EXISTS frozen_since,
|
||||
DROP COLUMN IF EXISTS frozen_until,
|
||||
DROP COLUMN IF EXISTS appeal_url;
|
||||
|
||||
ALTER INDEX account_restrictions_frozen_idx
|
||||
RENAME TO account_send_restrictions_frozen_idx;
|
||||
|
||||
ALTER TABLE account_restrictions RENAME TO account_send_restrictions;
|
||||
34
deploy/migrations/0088_account_freeze_state.up.sql
Normal file
34
deploy/migrations/0088_account_freeze_state.up.sql
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
ALTER TABLE account_send_restrictions RENAME TO account_restrictions;
|
||||
|
||||
ALTER INDEX account_send_restrictions_frozen_idx
|
||||
RENAME TO account_restrictions_frozen_idx;
|
||||
|
||||
ALTER TABLE account_restrictions
|
||||
ADD COLUMN frozen_since timestamptz,
|
||||
ADD COLUMN frozen_until timestamptz,
|
||||
ADD COLUMN appeal_url text NOT NULL DEFAULT '';
|
||||
|
||||
-- Existing send-frozen rows become valid account freezes explicitly during
|
||||
-- migration; read paths never synthesize the missing state.
|
||||
UPDATE account_restrictions
|
||||
SET frozen_since = updated_at,
|
||||
frozen_until = updated_at + interval '7 days',
|
||||
appeal_url = 'https://t.me/SpamBot'
|
||||
WHERE frozen;
|
||||
|
||||
UPDATE account_restrictions
|
||||
SET frozen_since = NULL,
|
||||
frozen_until = NULL,
|
||||
appeal_url = ''
|
||||
WHERE NOT frozen;
|
||||
|
||||
ALTER TABLE account_restrictions
|
||||
ADD CONSTRAINT account_restrictions_freeze_shape_check CHECK (
|
||||
(frozen AND frozen_since IS NOT NULL AND frozen_until IS NOT NULL
|
||||
AND frozen_until > frozen_since
|
||||
AND frozen_until <= to_timestamp(2147483647)
|
||||
AND appeal_url <> '' AND octet_length(appeal_url) <= 2048)
|
||||
OR
|
||||
(NOT frozen AND frozen_since IS NULL AND frozen_until IS NULL
|
||||
AND appeal_url = '')
|
||||
);
|
||||
17
deploy/migrations/0089_star_gift_catalog.down.sql
Normal file
17
deploy/migrations/0089_star_gift_catalog.down.sql
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
DROP TRIGGER IF EXISTS star_gift_catalog_changed ON public.star_gift_catalog;
|
||||
DROP FUNCTION IF EXISTS public.telesrv_notify_star_gift_catalog_changed();
|
||||
|
||||
ALTER TABLE public.peer_star_gifts
|
||||
DROP CONSTRAINT IF EXISTS peer_star_gifts_catalog_revision_fk,
|
||||
DROP COLUMN IF EXISTS catalog_revision_id;
|
||||
DROP INDEX IF EXISTS public.peer_star_gifts_gift_idx;
|
||||
|
||||
ALTER TABLE public.star_gift_catalog
|
||||
DROP CONSTRAINT IF EXISTS star_gift_catalog_active_revision_fk;
|
||||
DROP TABLE IF EXISTS public.star_gift_catalog_revisions;
|
||||
DROP TABLE IF EXISTS public.star_gift_catalog;
|
||||
DROP SEQUENCE IF EXISTS public.star_gift_catalog_revision_id_seq;
|
||||
DROP SEQUENCE IF EXISTS public.star_gift_catalog_gift_id_seq;
|
||||
|
||||
DELETE FROM public.read_model_versions
|
||||
WHERE model = 'star_gift_catalog' AND owner_user_id = 0 AND peer_type = '' AND peer_id = 0;
|
||||
81
deploy/migrations/0089_star_gift_catalog.up.sql
Normal file
81
deploy/migrations/0089_star_gift_catalog.up.sql
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
-- Durable, administrator-managed regular Star Gift catalog. The previous seven-item
|
||||
-- animated_emoji-derived directory was development-only and is intentionally not migrated.
|
||||
-- Existing received rows refer to those non-durable definitions, so clear them instead of
|
||||
-- manufacturing a read-time compatibility fallback that could no longer reconstruct assets.
|
||||
|
||||
CREATE SEQUENCE public.star_gift_catalog_gift_id_seq AS bigint START WITH 9000000000000001;
|
||||
CREATE SEQUENCE public.star_gift_catalog_revision_id_seq AS bigint START WITH 1;
|
||||
|
||||
CREATE TABLE public.star_gift_catalog (
|
||||
gift_id bigint DEFAULT nextval('public.star_gift_catalog_gift_id_seq') NOT NULL,
|
||||
active_revision_id bigint NOT NULL,
|
||||
enabled boolean DEFAULT true NOT NULL,
|
||||
sort_order integer DEFAULT 0 NOT NULL,
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
updated_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
CONSTRAINT star_gift_catalog_pkey PRIMARY KEY (gift_id)
|
||||
);
|
||||
|
||||
CREATE TABLE public.star_gift_catalog_revisions (
|
||||
id bigint DEFAULT nextval('public.star_gift_catalog_revision_id_seq') NOT NULL,
|
||||
gift_id bigint NOT NULL,
|
||||
revision integer NOT NULL,
|
||||
title text DEFAULT '' NOT NULL,
|
||||
stars bigint NOT NULL,
|
||||
convert_stars bigint NOT NULL,
|
||||
document_id bigint NOT NULL,
|
||||
animation_json jsonb NOT NULL,
|
||||
animation_sha256 bytea NOT NULL,
|
||||
source_name text DEFAULT '' NOT NULL,
|
||||
source_format text NOT NULL,
|
||||
width integer NOT NULL,
|
||||
height integer NOT NULL,
|
||||
frame_rate double precision DEFAULT 0 NOT NULL,
|
||||
in_point double precision DEFAULT 0 NOT NULL,
|
||||
out_point double precision DEFAULT 0 NOT NULL,
|
||||
created_by text DEFAULT '' NOT NULL,
|
||||
command_id text DEFAULT '' NOT NULL,
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
CONSTRAINT star_gift_catalog_revisions_pkey PRIMARY KEY (id),
|
||||
CONSTRAINT star_gift_catalog_revisions_gift_revision_uniq UNIQUE (gift_id, revision),
|
||||
CONSTRAINT star_gift_catalog_revisions_document_uniq UNIQUE (document_id),
|
||||
CONSTRAINT star_gift_catalog_revision_price_check CHECK (stars > 0 AND convert_stars >= 0 AND convert_stars <= stars),
|
||||
CONSTRAINT star_gift_catalog_revision_shape_check CHECK (width = 512 AND height = 512 AND jsonb_typeof(animation_json) = 'object'),
|
||||
CONSTRAINT star_gift_catalog_revision_source_check CHECK (source_format IN ('tgs', 'lottie')),
|
||||
CONSTRAINT star_gift_catalog_revision_gift_fk FOREIGN KEY (gift_id)
|
||||
REFERENCES public.star_gift_catalog(gift_id) ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED,
|
||||
CONSTRAINT star_gift_catalog_revision_document_fk FOREIGN KEY (document_id)
|
||||
REFERENCES public.documents(id) ON DELETE RESTRICT
|
||||
);
|
||||
|
||||
ALTER TABLE public.star_gift_catalog
|
||||
ADD CONSTRAINT star_gift_catalog_active_revision_fk FOREIGN KEY (active_revision_id)
|
||||
REFERENCES public.star_gift_catalog_revisions(id) ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED;
|
||||
|
||||
CREATE INDEX star_gift_catalog_enabled_order_idx
|
||||
ON public.star_gift_catalog (sort_order, gift_id) WHERE enabled;
|
||||
CREATE INDEX star_gift_catalog_revisions_gift_idx
|
||||
ON public.star_gift_catalog_revisions (gift_id, revision DESC);
|
||||
|
||||
DELETE FROM public.peer_star_gifts;
|
||||
ALTER TABLE public.peer_star_gifts
|
||||
ADD COLUMN catalog_revision_id bigint NOT NULL,
|
||||
ADD CONSTRAINT peer_star_gifts_catalog_revision_fk FOREIGN KEY (catalog_revision_id)
|
||||
REFERENCES public.star_gift_catalog_revisions(id) ON DELETE RESTRICT;
|
||||
CREATE INDEX peer_star_gifts_catalog_revision_idx
|
||||
ON public.peer_star_gifts (catalog_revision_id);
|
||||
CREATE INDEX peer_star_gifts_gift_idx
|
||||
ON public.peer_star_gifts (gift_id);
|
||||
|
||||
CREATE FUNCTION public.telesrv_notify_star_gift_catalog_changed() RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
PERFORM public.telesrv_bump_read_model_version('star_gift_catalog', 0, '', 0);
|
||||
RETURN NULL;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE TRIGGER star_gift_catalog_changed
|
||||
AFTER INSERT OR UPDATE OR DELETE ON public.star_gift_catalog
|
||||
FOR EACH STATEMENT EXECUTE FUNCTION public.telesrv_notify_star_gift_catalog_changed();
|
||||
31
deploy/migrations/0090_star_gift_collectibles.down.sql
Normal file
31
deploy/migrations/0090_star_gift_collectibles.down.sql
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
DROP TRIGGER IF EXISTS star_gift_collectible_backdrop_guard ON public.star_gift_collectible_backdrops;
|
||||
DROP TRIGGER IF EXISTS star_gift_collectible_pattern_guard ON public.star_gift_collectible_patterns;
|
||||
DROP TRIGGER IF EXISTS star_gift_collectible_model_guard ON public.star_gift_collectible_models;
|
||||
DROP TRIGGER IF EXISTS star_gift_collectible_revision_guard ON public.star_gift_collectible_revisions;
|
||||
DROP FUNCTION IF EXISTS public.telesrv_guard_collectible_attribute();
|
||||
DROP FUNCTION IF EXISTS public.telesrv_guard_collectible_revision();
|
||||
|
||||
DROP TABLE IF EXISTS public.star_gift_collection_items;
|
||||
DROP TABLE IF EXISTS public.star_gift_collections;
|
||||
DROP TABLE IF EXISTS public.star_gift_upgrade_commands;
|
||||
|
||||
DROP INDEX IF EXISTS public.peer_star_gifts_unique_gift_uniq;
|
||||
ALTER TABLE public.peer_star_gifts
|
||||
DROP CONSTRAINT IF EXISTS peer_star_gifts_pinned_order_check,
|
||||
DROP CONSTRAINT IF EXISTS peer_star_gifts_terminal_state_check,
|
||||
DROP CONSTRAINT IF EXISTS peer_star_gifts_unique_gift_fk,
|
||||
DROP COLUMN IF EXISTS pinned_order,
|
||||
DROP COLUMN IF EXISTS upgrade_msg_id,
|
||||
DROP COLUMN IF EXISTS unique_gift_id;
|
||||
|
||||
DROP TABLE IF EXISTS public.unique_star_gifts;
|
||||
|
||||
ALTER TABLE public.star_gift_catalog
|
||||
DROP CONSTRAINT IF EXISTS star_gift_catalog_collectible_revision_fk,
|
||||
DROP COLUMN IF EXISTS collectible_revision_id;
|
||||
|
||||
DROP TABLE IF EXISTS public.star_gift_collectible_backdrops;
|
||||
DROP TABLE IF EXISTS public.star_gift_collectible_patterns;
|
||||
DROP TABLE IF EXISTS public.star_gift_collectible_models;
|
||||
DROP TABLE IF EXISTS public.star_gift_collectible_revisions;
|
||||
DROP SEQUENCE IF EXISTS public.unique_star_gift_id_seq;
|
||||
238
deploy/migrations/0090_star_gift_collectibles.up.sql
Normal file
238
deploy/migrations/0090_star_gift_collectibles.up.sql
Normal file
|
|
@ -0,0 +1,238 @@
|
|||
-- Collectible Star Gifts: immutable published attribute pools, unique gift instances and
|
||||
-- peer-owned gift collections. Marketplace/transfer/auction/craft/TON state is intentionally
|
||||
-- absent from this schema.
|
||||
|
||||
CREATE SEQUENCE public.unique_star_gift_id_seq AS bigint START WITH 9200000000000001;
|
||||
|
||||
CREATE TABLE public.star_gift_collectible_revisions (
|
||||
id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
gift_id bigint NOT NULL REFERENCES public.star_gift_catalog(gift_id) ON DELETE RESTRICT,
|
||||
revision integer NOT NULL,
|
||||
upgrade_stars bigint NOT NULL,
|
||||
supply_total integer NOT NULL,
|
||||
issued integer DEFAULT 0 NOT NULL,
|
||||
slug_prefix text NOT NULL,
|
||||
status text DEFAULT 'draft' NOT NULL,
|
||||
created_by text DEFAULT '' NOT NULL,
|
||||
command_id text DEFAULT '' NOT NULL,
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
published_at timestamp with time zone,
|
||||
CONSTRAINT star_gift_collectible_revision_uniq UNIQUE (gift_id, revision),
|
||||
CONSTRAINT star_gift_collectible_command_uniq UNIQUE (gift_id, command_id),
|
||||
CONSTRAINT star_gift_collectible_price_check CHECK (upgrade_stars > 0),
|
||||
CONSTRAINT star_gift_collectible_supply_check CHECK (supply_total > 0 AND issued >= 0 AND issued <= supply_total),
|
||||
CONSTRAINT star_gift_collectible_slug_check CHECK (slug_prefix ~ '^[a-z0-9][a-z0-9-]{0,47}$'),
|
||||
CONSTRAINT star_gift_collectible_status_check CHECK (status IN ('draft', 'published')),
|
||||
CONSTRAINT star_gift_collectible_publish_check CHECK (
|
||||
(status = 'draft' AND published_at IS NULL) OR
|
||||
(status = 'published' AND published_at IS NOT NULL)
|
||||
)
|
||||
);
|
||||
|
||||
CREATE TABLE public.star_gift_collectible_models (
|
||||
id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
collectible_revision_id bigint NOT NULL REFERENCES public.star_gift_collectible_revisions(id) ON DELETE CASCADE,
|
||||
name text NOT NULL,
|
||||
document_id bigint NOT NULL REFERENCES public.documents(id) ON DELETE RESTRICT,
|
||||
animation_json jsonb NOT NULL,
|
||||
animation_sha256 bytea NOT NULL,
|
||||
source_name text DEFAULT '' NOT NULL,
|
||||
source_format text NOT NULL,
|
||||
width integer NOT NULL,
|
||||
height integer NOT NULL,
|
||||
frame_rate double precision DEFAULT 0 NOT NULL,
|
||||
in_point double precision DEFAULT 0 NOT NULL,
|
||||
out_point double precision DEFAULT 0 NOT NULL,
|
||||
rarity_permille integer NOT NULL,
|
||||
sort_order integer DEFAULT 0 NOT NULL,
|
||||
CONSTRAINT star_gift_collectible_model_name_uniq UNIQUE (collectible_revision_id, name),
|
||||
CONSTRAINT star_gift_collectible_model_id_revision_uniq UNIQUE (id, collectible_revision_id),
|
||||
CONSTRAINT star_gift_collectible_model_rarity_check CHECK (rarity_permille BETWEEN 1 AND 1000),
|
||||
CONSTRAINT star_gift_collectible_model_shape_check CHECK (width = 512 AND height = 512 AND jsonb_typeof(animation_json) = 'object'),
|
||||
CONSTRAINT star_gift_collectible_model_source_check CHECK (source_format IN ('tgs', 'lottie'))
|
||||
);
|
||||
|
||||
CREATE TABLE public.star_gift_collectible_patterns (
|
||||
id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
collectible_revision_id bigint NOT NULL REFERENCES public.star_gift_collectible_revisions(id) ON DELETE CASCADE,
|
||||
name text NOT NULL,
|
||||
document_id bigint NOT NULL REFERENCES public.documents(id) ON DELETE RESTRICT,
|
||||
animation_json jsonb NOT NULL,
|
||||
animation_sha256 bytea NOT NULL,
|
||||
source_name text DEFAULT '' NOT NULL,
|
||||
source_format text NOT NULL,
|
||||
width integer NOT NULL,
|
||||
height integer NOT NULL,
|
||||
frame_rate double precision DEFAULT 0 NOT NULL,
|
||||
in_point double precision DEFAULT 0 NOT NULL,
|
||||
out_point double precision DEFAULT 0 NOT NULL,
|
||||
rarity_permille integer NOT NULL,
|
||||
sort_order integer DEFAULT 0 NOT NULL,
|
||||
CONSTRAINT star_gift_collectible_pattern_name_uniq UNIQUE (collectible_revision_id, name),
|
||||
CONSTRAINT star_gift_collectible_pattern_id_revision_uniq UNIQUE (id, collectible_revision_id),
|
||||
CONSTRAINT star_gift_collectible_pattern_rarity_check CHECK (rarity_permille BETWEEN 1 AND 1000),
|
||||
CONSTRAINT star_gift_collectible_pattern_shape_check CHECK (width = 512 AND height = 512 AND jsonb_typeof(animation_json) = 'object'),
|
||||
CONSTRAINT star_gift_collectible_pattern_source_check CHECK (source_format IN ('tgs', 'lottie'))
|
||||
);
|
||||
|
||||
CREATE TABLE public.star_gift_collectible_backdrops (
|
||||
id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
collectible_revision_id bigint NOT NULL REFERENCES public.star_gift_collectible_revisions(id) ON DELETE CASCADE,
|
||||
name text NOT NULL,
|
||||
backdrop_id integer NOT NULL,
|
||||
center_color integer NOT NULL,
|
||||
edge_color integer NOT NULL,
|
||||
pattern_color integer NOT NULL,
|
||||
text_color integer NOT NULL,
|
||||
rarity_permille integer NOT NULL,
|
||||
sort_order integer DEFAULT 0 NOT NULL,
|
||||
CONSTRAINT star_gift_collectible_backdrop_name_uniq UNIQUE (collectible_revision_id, name),
|
||||
CONSTRAINT star_gift_collectible_backdrop_display_uniq UNIQUE (collectible_revision_id, backdrop_id),
|
||||
CONSTRAINT star_gift_collectible_backdrop_id_revision_uniq UNIQUE (id, collectible_revision_id),
|
||||
CONSTRAINT star_gift_collectible_backdrop_rarity_check CHECK (rarity_permille BETWEEN 1 AND 1000),
|
||||
CONSTRAINT star_gift_collectible_backdrop_color_check CHECK (
|
||||
center_color BETWEEN 0 AND 16777215 AND edge_color BETWEEN 0 AND 16777215 AND
|
||||
pattern_color BETWEEN 0 AND 16777215 AND text_color BETWEEN 0 AND 16777215
|
||||
)
|
||||
);
|
||||
|
||||
ALTER TABLE public.star_gift_catalog
|
||||
ADD COLUMN collectible_revision_id bigint,
|
||||
ADD CONSTRAINT star_gift_catalog_collectible_revision_fk FOREIGN KEY (collectible_revision_id)
|
||||
REFERENCES public.star_gift_collectible_revisions(id) ON DELETE RESTRICT;
|
||||
|
||||
CREATE TABLE public.unique_star_gifts (
|
||||
id bigint DEFAULT nextval('public.unique_star_gift_id_seq') PRIMARY KEY,
|
||||
gift_id bigint NOT NULL REFERENCES public.star_gift_catalog(gift_id) ON DELETE RESTRICT,
|
||||
collectible_revision_id bigint NOT NULL REFERENCES public.star_gift_collectible_revisions(id) ON DELETE RESTRICT,
|
||||
source_saved_gift_id bigint NOT NULL REFERENCES public.peer_star_gifts(id) ON DELETE RESTRICT,
|
||||
title text DEFAULT '' NOT NULL,
|
||||
slug text NOT NULL,
|
||||
num integer NOT NULL,
|
||||
owner_peer_type text NOT NULL,
|
||||
owner_peer_id bigint NOT NULL,
|
||||
model_attribute_id bigint NOT NULL,
|
||||
pattern_attribute_id bigint NOT NULL,
|
||||
backdrop_attribute_id bigint NOT NULL,
|
||||
keep_original_details boolean DEFAULT false NOT NULL,
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
updated_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
CONSTRAINT unique_star_gift_slug_uniq UNIQUE (slug),
|
||||
CONSTRAINT unique_star_gift_number_uniq UNIQUE (gift_id, num),
|
||||
CONSTRAINT unique_star_gift_source_uniq UNIQUE (source_saved_gift_id),
|
||||
CONSTRAINT unique_star_gift_owner_check CHECK (owner_peer_type IN ('user', 'channel') AND owner_peer_id > 0),
|
||||
CONSTRAINT unique_star_gift_num_check CHECK (num > 0),
|
||||
CONSTRAINT unique_star_gift_model_fk FOREIGN KEY (model_attribute_id, collectible_revision_id)
|
||||
REFERENCES public.star_gift_collectible_models(id, collectible_revision_id) ON DELETE RESTRICT,
|
||||
CONSTRAINT unique_star_gift_pattern_fk FOREIGN KEY (pattern_attribute_id, collectible_revision_id)
|
||||
REFERENCES public.star_gift_collectible_patterns(id, collectible_revision_id) ON DELETE RESTRICT,
|
||||
CONSTRAINT unique_star_gift_backdrop_fk FOREIGN KEY (backdrop_attribute_id, collectible_revision_id)
|
||||
REFERENCES public.star_gift_collectible_backdrops(id, collectible_revision_id) ON DELETE RESTRICT
|
||||
);
|
||||
|
||||
ALTER TABLE public.peer_star_gifts
|
||||
ADD COLUMN unique_gift_id bigint,
|
||||
ADD COLUMN upgrade_msg_id integer DEFAULT 0 NOT NULL,
|
||||
ADD COLUMN pinned_order integer DEFAULT 0 NOT NULL,
|
||||
ADD CONSTRAINT peer_star_gifts_unique_gift_fk FOREIGN KEY (unique_gift_id)
|
||||
REFERENCES public.unique_star_gifts(id) ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED,
|
||||
ADD CONSTRAINT peer_star_gifts_terminal_state_check CHECK (NOT converted OR unique_gift_id IS NULL),
|
||||
ADD CONSTRAINT peer_star_gifts_pinned_order_check CHECK (pinned_order >= 0);
|
||||
|
||||
CREATE UNIQUE INDEX peer_star_gifts_unique_gift_uniq ON public.peer_star_gifts(unique_gift_id)
|
||||
WHERE unique_gift_id IS NOT NULL;
|
||||
CREATE INDEX unique_star_gifts_owner_idx ON public.unique_star_gifts(owner_peer_type, owner_peer_id, id DESC);
|
||||
|
||||
CREATE TABLE public.star_gift_upgrade_commands (
|
||||
user_id bigint NOT NULL,
|
||||
command_key text NOT NULL,
|
||||
source_saved_gift_id bigint NOT NULL REFERENCES public.peer_star_gifts(id) ON DELETE RESTRICT,
|
||||
form_id bigint DEFAULT 0 NOT NULL,
|
||||
unique_gift_id bigint NOT NULL REFERENCES public.unique_star_gifts(id) ON DELETE RESTRICT,
|
||||
balance_after bigint NOT NULL,
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
CONSTRAINT star_gift_upgrade_commands_pkey PRIMARY KEY (user_id, command_key),
|
||||
CONSTRAINT star_gift_upgrade_command_source_uniq UNIQUE (source_saved_gift_id)
|
||||
);
|
||||
|
||||
CREATE TABLE public.star_gift_collections (
|
||||
collection_id integer GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
owner_peer_type text NOT NULL,
|
||||
owner_peer_id bigint NOT NULL,
|
||||
title text NOT NULL,
|
||||
sort_order integer DEFAULT 0 NOT NULL,
|
||||
hash bigint DEFAULT 0 NOT NULL,
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
updated_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
CONSTRAINT star_gift_collection_owner_check CHECK (owner_peer_type IN ('user', 'channel') AND owner_peer_id > 0),
|
||||
CONSTRAINT star_gift_collection_title_check CHECK (char_length(title) BETWEEN 1 AND 12),
|
||||
CONSTRAINT star_gift_collection_owner_id_uniq UNIQUE (owner_peer_type, owner_peer_id, collection_id)
|
||||
);
|
||||
|
||||
CREATE INDEX star_gift_collections_owner_order_idx
|
||||
ON public.star_gift_collections(owner_peer_type, owner_peer_id, sort_order, collection_id);
|
||||
|
||||
CREATE TABLE public.star_gift_collection_items (
|
||||
collection_id integer NOT NULL REFERENCES public.star_gift_collections(collection_id) ON DELETE CASCADE,
|
||||
saved_gift_id bigint NOT NULL REFERENCES public.peer_star_gifts(id) ON DELETE CASCADE,
|
||||
sort_order integer DEFAULT 0 NOT NULL,
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
CONSTRAINT star_gift_collection_items_pkey PRIMARY KEY (collection_id, saved_gift_id)
|
||||
);
|
||||
|
||||
CREATE INDEX star_gift_collection_items_order_idx
|
||||
ON public.star_gift_collection_items(collection_id, sort_order, saved_gift_id);
|
||||
CREATE INDEX star_gift_collection_items_saved_idx
|
||||
ON public.star_gift_collection_items(saved_gift_id, collection_id);
|
||||
|
||||
-- Published definitions are immutable. The only permitted update is incrementing issued while
|
||||
-- all definition columns remain byte-for-byte equal.
|
||||
CREATE FUNCTION public.telesrv_guard_collectible_revision() RETURNS trigger
|
||||
LANGUAGE plpgsql AS $$
|
||||
BEGIN
|
||||
IF OLD.status = 'published' THEN
|
||||
IF TG_OP = 'DELETE' THEN
|
||||
RAISE EXCEPTION 'published collectible revision is immutable';
|
||||
END IF;
|
||||
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;
|
||||
END IF;
|
||||
IF TG_OP = 'DELETE' THEN
|
||||
RETURN OLD;
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE TRIGGER star_gift_collectible_revision_guard
|
||||
BEFORE UPDATE OR DELETE ON public.star_gift_collectible_revisions
|
||||
FOR EACH ROW EXECUTE FUNCTION public.telesrv_guard_collectible_revision();
|
||||
|
||||
CREATE FUNCTION public.telesrv_guard_collectible_attribute() RETURNS trigger
|
||||
LANGUAGE plpgsql AS $$
|
||||
DECLARE
|
||||
revision_status text;
|
||||
BEGIN
|
||||
SELECT status INTO revision_status FROM public.star_gift_collectible_revisions
|
||||
WHERE id = COALESCE(OLD.collectible_revision_id, NEW.collectible_revision_id);
|
||||
IF revision_status = 'published' THEN
|
||||
RAISE EXCEPTION 'published collectible attributes are immutable';
|
||||
END IF;
|
||||
IF TG_OP = 'DELETE' THEN
|
||||
RETURN OLD;
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE TRIGGER star_gift_collectible_model_guard BEFORE UPDATE OR DELETE ON public.star_gift_collectible_models
|
||||
FOR EACH ROW EXECUTE FUNCTION public.telesrv_guard_collectible_attribute();
|
||||
CREATE TRIGGER star_gift_collectible_pattern_guard BEFORE UPDATE OR DELETE ON public.star_gift_collectible_patterns
|
||||
FOR EACH ROW EXECUTE FUNCTION public.telesrv_guard_collectible_attribute();
|
||||
CREATE TRIGGER star_gift_collectible_backdrop_guard BEFORE UPDATE OR DELETE ON public.star_gift_collectible_backdrops
|
||||
FOR EACH ROW EXECUTE FUNCTION public.telesrv_guard_collectible_attribute();
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
ALTER TABLE public.peer_star_gifts
|
||||
DROP CONSTRAINT IF EXISTS peer_star_gifts_prepaid_upgrade_check,
|
||||
DROP COLUMN IF EXISTS prepaid_upgrade_stars;
|
||||
15
deploy/migrations/0091_star_gift_prepaid_upgrade.up.sql
Normal file
15
deploy/migrations/0091_star_gift_prepaid_upgrade.up.sql
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
ALTER TABLE public.peer_star_gifts
|
||||
ADD COLUMN IF NOT EXISTS prepaid_upgrade_stars bigint DEFAULT 0 NOT NULL;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_constraint
|
||||
WHERE conrelid = 'public.peer_star_gifts'::regclass
|
||||
AND conname = 'peer_star_gifts_prepaid_upgrade_check'
|
||||
) THEN
|
||||
ALTER TABLE public.peer_star_gifts
|
||||
ADD CONSTRAINT peer_star_gifts_prepaid_upgrade_check CHECK (prepaid_upgrade_stars >= 0);
|
||||
END IF;
|
||||
END;
|
||||
$$;
|
||||
21
deploy/migrations/0092_collectible_issuance_guard.down.sql
Normal file
21
deploy/migrations/0092_collectible_issuance_guard.down.sql
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
CREATE OR REPLACE FUNCTION public.telesrv_guard_collectible_revision() RETURNS trigger
|
||||
LANGUAGE plpgsql AS $$
|
||||
BEGIN
|
||||
IF OLD.status = 'published' THEN
|
||||
IF TG_OP = 'DELETE' THEN
|
||||
RAISE EXCEPTION 'published collectible revision is immutable';
|
||||
END IF;
|
||||
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;
|
||||
END IF;
|
||||
IF TG_OP = 'DELETE' THEN
|
||||
RETURN OLD;
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
28
deploy/migrations/0092_collectible_issuance_guard.up.sql
Normal file
28
deploy/migrations/0092_collectible_issuance_guard.up.sql
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
-- Tighten the published collectible invariant. Issuance is the only mutable
|
||||
-- field and every committed upgrade advances it exactly once.
|
||||
|
||||
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;
|
||||
$$;
|
||||
Loading…
Add table
Add a link
Reference in a new issue