feat: sync monoforum and collectible emoji status
Sync telesrv bd15657 (feat(account): implement collectible emoji status). Skipped telesrv docs changes per public sync rules.
This commit is contained in:
parent
edb7057757
commit
0c99ae0a9d
91 changed files with 4061 additions and 693 deletions
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE channel_messages DROP COLUMN IF EXISTS suggested_post;
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
ALTER TABLE channel_messages
|
||||
ADD COLUMN suggested_post jsonb NOT NULL DEFAULT '{}'::jsonb;
|
||||
|
||||
-- A monoforum is a virtual per-saved-peer container, never an ordinary joined megagroup.
|
||||
DELETE FROM channel_dialogs d
|
||||
USING channels c
|
||||
WHERE d.channel_id = c.id AND c.monoforum;
|
||||
|
||||
DELETE FROM user_channel_member_index i
|
||||
USING channels c
|
||||
WHERE i.channel_id = c.id AND c.monoforum;
|
||||
|
||||
DELETE FROM channel_members m
|
||||
USING channels c
|
||||
WHERE m.channel_id = c.id AND c.monoforum;
|
||||
|
||||
UPDATE channels c
|
||||
SET participants_count = 0,
|
||||
admins_count = 0,
|
||||
updated_at = now()
|
||||
WHERE c.monoforum AND (c.participants_count <> 0 OR c.admins_count <> 0);
|
||||
|
||||
-- Older generic sends from non-admin users are deterministically their own saved-peer dialog.
|
||||
UPDATE channel_messages m
|
||||
SET saved_peer_type = 'user',
|
||||
saved_peer_id = m.sender_user_id
|
||||
FROM channels mono
|
||||
WHERE mono.id = m.channel_id
|
||||
AND mono.monoforum
|
||||
AND NOT m.deleted
|
||||
AND m.saved_peer_id = 0
|
||||
AND m.action = '{}'::jsonb
|
||||
AND m.sender_user_id <> 0
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM channel_members parent_member
|
||||
WHERE parent_member.channel_id = mono.linked_monoforum_id
|
||||
AND parent_member.user_id = m.sender_user_id
|
||||
AND parent_member.status = 'active'
|
||||
AND parent_member.role IN ('creator', 'admin')
|
||||
);
|
||||
|
||||
UPDATE channel_update_events e
|
||||
SET payload = jsonb_set(
|
||||
e.payload,
|
||||
'{message,SavedPeer}',
|
||||
jsonb_build_object('Type', 'user', 'ID', m.sender_user_id),
|
||||
true
|
||||
)
|
||||
FROM channel_messages m, channels mono
|
||||
WHERE mono.id = m.channel_id
|
||||
AND mono.monoforum
|
||||
AND e.channel_id = m.channel_id
|
||||
AND e.message_id = m.id
|
||||
AND m.saved_peer_type = 'user'
|
||||
AND m.saved_peer_id = m.sender_user_id
|
||||
AND COALESCE((e.payload #>> '{message,SavedPeer,ID}')::bigint, 0) = 0;
|
||||
|
||||
UPDATE channel_messages m
|
||||
SET send_snapshot = jsonb_set(
|
||||
m.send_snapshot,
|
||||
'{message,SavedPeer}',
|
||||
jsonb_build_object('Type', 'user', 'ID', m.sender_user_id),
|
||||
true
|
||||
)
|
||||
FROM channels mono
|
||||
WHERE mono.id = m.channel_id
|
||||
AND mono.monoforum
|
||||
AND m.random_id <> 0
|
||||
AND m.saved_peer_type = 'user'
|
||||
AND m.saved_peer_id = m.sender_user_id
|
||||
AND COALESCE((m.send_snapshot #>> '{message,SavedPeer,ID}')::bigint, 0) = 0;
|
||||
|
||||
-- Remove the impossible join service message without creating a pts gap: retain the event row as noop.
|
||||
UPDATE channel_update_events e
|
||||
SET event_type = 'noop', message_id = 0, sender_user_id = 0, user_ids = '[]'::jsonb, payload = '{}'::jsonb
|
||||
FROM channel_messages m, channels mono
|
||||
WHERE mono.id = m.channel_id
|
||||
AND mono.monoforum
|
||||
AND e.channel_id = m.channel_id
|
||||
AND e.message_id = m.id
|
||||
AND m.action->>'Type' = 'chat_joined';
|
||||
|
||||
UPDATE channel_messages m
|
||||
SET deleted = true
|
||||
FROM channels mono
|
||||
WHERE mono.id = m.channel_id
|
||||
AND mono.monoforum
|
||||
AND m.action->>'Type' = 'chat_joined';
|
||||
|
||||
UPDATE channels mono
|
||||
SET top_message_id = COALESCE((
|
||||
SELECT max(m.id)
|
||||
FROM channel_messages m
|
||||
WHERE m.channel_id = mono.id AND NOT m.deleted
|
||||
), 0),
|
||||
updated_at = now()
|
||||
WHERE mono.monoforum;
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
ALTER TABLE public.channel_messages
|
||||
DROP CONSTRAINT IF EXISTS channel_messages_paid_message_stars_check,
|
||||
DROP COLUMN IF EXISTS paid_message_stars;
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
ALTER TABLE public.channel_messages
|
||||
ADD COLUMN paid_message_stars bigint NOT NULL DEFAULT 0,
|
||||
ADD CONSTRAINT channel_messages_paid_message_stars_check CHECK (paid_message_stars >= 0);
|
||||
61
deploy/migrations/0114_collectible_emoji_status.down.sql
Normal file
61
deploy/migrations/0114_collectible_emoji_status.down.sql
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
DROP TRIGGER IF EXISTS unique_star_gifts_clear_invalid_emoji_status ON public.unique_star_gifts;
|
||||
DROP FUNCTION IF EXISTS public.telesrv_clear_invalid_collectible_emoji_status();
|
||||
|
||||
UPDATE public.documents d
|
||||
SET attributes = COALESCE((
|
||||
SELECT jsonb_agg(
|
||||
CASE
|
||||
WHEN item->>'kind' = 'custom_emoji' AND COALESCE((item->>'text_color')::boolean, false) THEN
|
||||
(item - 'text_color') || jsonb_build_object('kind', 'sticker')
|
||||
ELSE item
|
||||
END
|
||||
ORDER BY ord
|
||||
)
|
||||
FROM jsonb_array_elements(d.attributes) WITH ORDINALITY AS attrs(item, ord)
|
||||
), '[]'::jsonb)
|
||||
WHERE d.id IN (SELECT document_id FROM public.star_gift_collectible_patterns);
|
||||
|
||||
ALTER TABLE public.user_update_events DROP CONSTRAINT IF EXISTS user_update_events_type_check;
|
||||
ALTER TABLE public.user_update_events ADD CONSTRAINT user_update_events_type_check CHECK (
|
||||
(event_type)::text = ANY (ARRAY[
|
||||
'new_message', 'read_history_inbox', 'read_history_outbox', 'read_message_contents',
|
||||
'edit_message', 'web_page', 'message_reactions', 'message_poll', 'draft_message', 'quick_replies',
|
||||
'new_quick_reply', 'delete_quick_reply', 'quick_reply_message', 'delete_quick_reply_messages',
|
||||
'contacts_reset', 'dialog_pinned', 'pinned_dialogs', 'pinned_messages', 'dialog_unread_mark',
|
||||
'peer_settings', 'peer_story_blocked', 'user_phone', 'delete_messages', 'dialog_filter',
|
||||
'dialog_filter_order', 'dialog_filters', 'folder_peers', 'channel_available_messages',
|
||||
'channel_view_forum_as_messages', 'channel_state', 'saved_dialog_pinned',
|
||||
'pinned_saved_dialogs', 'story', 'read_stories', 'sent_story_reaction',
|
||||
'new_story_reaction', 'noop', 'read_channel_discussion_inbox',
|
||||
'read_channel_discussion_outbox'
|
||||
]::text[])
|
||||
);
|
||||
ALTER TABLE public.user_update_events DROP COLUMN IF EXISTS emoji_status_payload;
|
||||
|
||||
ALTER TABLE public.users DROP CONSTRAINT IF EXISTS users_deletion_state_check;
|
||||
ALTER TABLE public.users DROP CONSTRAINT IF EXISTS users_emoji_status_shape_check;
|
||||
ALTER TABLE public.users
|
||||
DROP COLUMN IF EXISTS emoji_status_collectible,
|
||||
DROP COLUMN IF EXISTS emoji_status_collectible_id;
|
||||
|
||||
ALTER TABLE public.users
|
||||
ADD CONSTRAINT users_deletion_state_check CHECK (
|
||||
(deleted_at IS NULL AND deletion_source = '' AND deletion_reason = '')
|
||||
OR
|
||||
(deleted_at IS NOT NULL
|
||||
AND deletion_source IN (
|
||||
'manual', 'forgot_password', 'tos_decline', 'password_reset_expiry',
|
||||
'account_ttl', 'freeze_expiry'
|
||||
)
|
||||
AND account_delete_at IS NULL
|
||||
AND phone = '' AND first_name = '' AND last_name = '' AND username = ''
|
||||
AND country_code = '' AND about = '' AND verified = false AND support = false
|
||||
AND premium_expires_at IS NULL
|
||||
AND emoji_status_document_id = 0 AND emoji_status_until = 0
|
||||
AND color_set = false AND color = 0 AND color_background_emoji_id = 0
|
||||
AND profile_color_set = false AND profile_color = 0
|
||||
AND profile_color_background_emoji_id = 0
|
||||
AND birthday_day = 0 AND birthday_month = 0 AND birthday_year = 0
|
||||
AND personal_channel_id = 0 AND last_seen_at = 0
|
||||
AND octet_length(deletion_reason) <= 1024)
|
||||
);
|
||||
166
deploy/migrations/0114_collectible_emoji_status.up.sql
Normal file
166
deploy/migrations/0114_collectible_emoji_status.up.sql
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
-- Complete collectible emoji-status state: users persist the selected unique
|
||||
-- gift plus an immutable render snapshot, and the account update log persists
|
||||
-- the exact status payload for online dispatch/offline difference replay.
|
||||
ALTER TABLE public.users
|
||||
ADD COLUMN emoji_status_collectible_id bigint REFERENCES public.unique_star_gifts(id),
|
||||
ADD COLUMN emoji_status_collectible jsonb DEFAULT '{}'::jsonb NOT NULL;
|
||||
|
||||
ALTER TABLE public.users
|
||||
ADD CONSTRAINT users_emoji_status_shape_check CHECK (
|
||||
emoji_status_document_id >= 0
|
||||
AND emoji_status_until >= 0
|
||||
AND (emoji_status_document_id > 0 OR emoji_status_until = 0)
|
||||
AND (
|
||||
(
|
||||
emoji_status_collectible_id IS NULL
|
||||
AND emoji_status_collectible = '{}'::jsonb
|
||||
) OR (
|
||||
emoji_status_collectible_id IS NOT NULL
|
||||
AND emoji_status_collectible_id > 0
|
||||
AND emoji_status_document_id > 0
|
||||
AND jsonb_typeof(emoji_status_collectible) = 'object'
|
||||
AND emoji_status_collectible <> '{}'::jsonb
|
||||
AND emoji_status_collectible ? 'collectible_id'
|
||||
AND emoji_status_collectible ? 'document_id'
|
||||
AND emoji_status_collectible ? 'title'
|
||||
AND emoji_status_collectible ? 'slug'
|
||||
AND emoji_status_collectible ? 'pattern_document_id'
|
||||
AND (emoji_status_collectible->>'collectible_id')::bigint = emoji_status_collectible_id
|
||||
AND (emoji_status_collectible->>'document_id')::bigint = emoji_status_document_id
|
||||
AND (emoji_status_collectible->>'pattern_document_id')::bigint > 0
|
||||
AND length(emoji_status_collectible->>'title') > 0
|
||||
AND length(emoji_status_collectible->>'slug') > 0
|
||||
AND (emoji_status_collectible->>'center_color')::integer BETWEEN 0 AND 16777215
|
||||
AND (emoji_status_collectible->>'edge_color')::integer BETWEEN 0 AND 16777215
|
||||
AND (emoji_status_collectible->>'pattern_color')::integer BETWEEN 0 AND 16777215
|
||||
AND (emoji_status_collectible->>'text_color')::integer BETWEEN 0 AND 16777215
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
ALTER TABLE public.user_update_events
|
||||
ADD COLUMN emoji_status_payload jsonb DEFAULT '{}'::jsonb NOT NULL;
|
||||
|
||||
ALTER TABLE public.user_update_events DROP CONSTRAINT IF EXISTS user_update_events_type_check;
|
||||
ALTER TABLE public.user_update_events ADD CONSTRAINT user_update_events_type_check CHECK (
|
||||
(event_type)::text = ANY (ARRAY[
|
||||
'new_message', 'read_history_inbox', 'read_history_outbox', 'read_message_contents',
|
||||
'edit_message', 'web_page', 'message_reactions', 'message_poll', 'draft_message', 'quick_replies',
|
||||
'new_quick_reply', 'delete_quick_reply', 'quick_reply_message', 'delete_quick_reply_messages',
|
||||
'contacts_reset', 'dialog_pinned', 'pinned_dialogs', 'pinned_messages', 'dialog_unread_mark',
|
||||
'peer_settings', 'peer_story_blocked', 'user_phone', 'user_emoji_status', 'delete_messages',
|
||||
'dialog_filter', 'dialog_filter_order', 'dialog_filters', 'folder_peers',
|
||||
'channel_available_messages', 'channel_view_forum_as_messages', 'channel_state',
|
||||
'saved_dialog_pinned', 'pinned_saved_dialogs', 'story', 'read_stories',
|
||||
'sent_story_reaction', 'new_story_reaction', 'noop',
|
||||
'read_channel_discussion_inbox', 'read_channel_discussion_outbox'
|
||||
]::text[])
|
||||
);
|
||||
|
||||
-- Android applies the collectible backdrop's pattern_color only to a
|
||||
-- documentAttributeCustomEmoji with text_color=true. Existing imports stored
|
||||
-- these pattern documents as ordinary stickers, so repair them in place.
|
||||
UPDATE public.documents d
|
||||
SET attributes = COALESCE((
|
||||
SELECT jsonb_agg(
|
||||
CASE
|
||||
WHEN item->>'kind' = 'sticker' THEN
|
||||
jsonb_set(
|
||||
jsonb_set(item, '{kind}', '"custom_emoji"'::jsonb, false),
|
||||
'{text_color}', 'true'::jsonb, true
|
||||
)
|
||||
ELSE item
|
||||
END
|
||||
ORDER BY ord
|
||||
)
|
||||
FROM jsonb_array_elements(d.attributes) WITH ORDINALITY AS attrs(item, ord)
|
||||
), '[]'::jsonb)
|
||||
WHERE d.id IN (SELECT document_id FROM public.star_gift_collectible_patterns);
|
||||
|
||||
-- A transferred/exported/burned gift can no longer remain as the previous
|
||||
-- owner's status. Keep the durable user state valid even if the lifecycle
|
||||
-- mutation did not originate from an account RPC.
|
||||
CREATE OR REPLACE FUNCTION public.telesrv_clear_invalid_collectible_emoji_status()
|
||||
RETURNS trigger LANGUAGE plpgsql AS $$
|
||||
DECLARE
|
||||
cleared_user_id bigint;
|
||||
cleared_pts integer;
|
||||
event_date integer;
|
||||
BEGIN
|
||||
event_date := EXTRACT(EPOCH FROM clock_timestamp())::integer;
|
||||
FOR cleared_user_id IN
|
||||
UPDATE public.users u
|
||||
SET emoji_status_document_id = 0,
|
||||
emoji_status_until = 0,
|
||||
emoji_status_collectible_id = NULL,
|
||||
emoji_status_collectible = '{}'::jsonb,
|
||||
updated_at = now()
|
||||
WHERE u.emoji_status_collectible_id = NEW.id
|
||||
AND (
|
||||
NEW.burned
|
||||
OR NEW.owner_address <> ''
|
||||
OR NEW.owner_peer_type IS DISTINCT FROM 'user'
|
||||
OR NEW.owner_peer_id IS DISTINCT FROM u.id
|
||||
)
|
||||
RETURNING u.id
|
||||
LOOP
|
||||
INSERT INTO public.user_update_watermarks (user_id, contiguous_pts)
|
||||
VALUES (cleared_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 = cleared_user_id
|
||||
RETURNING contiguous_pts INTO cleared_pts;
|
||||
|
||||
INSERT INTO public.user_update_events (
|
||||
user_id, pts, pts_count, date, event_type,
|
||||
peer_type, peer_id, emoji_status_payload
|
||||
) VALUES (
|
||||
cleared_user_id, cleared_pts, 1, event_date, 'user_emoji_status',
|
||||
'user', cleared_user_id, '{}'::jsonb
|
||||
);
|
||||
|
||||
-- No session is the origin of a lifecycle invalidation: every online
|
||||
-- device receives it, and offline devices replay the same event.
|
||||
INSERT INTO public.dispatch_outbox (
|
||||
target_user_id, pts, event_type, exclude_auth_key_id, exclude_session_id
|
||||
) VALUES (
|
||||
cleared_user_id, cleared_pts, 'user_emoji_status', 0, 0
|
||||
);
|
||||
END LOOP;
|
||||
RETURN NEW;
|
||||
END
|
||||
$$;
|
||||
|
||||
CREATE TRIGGER unique_star_gifts_clear_invalid_emoji_status
|
||||
AFTER UPDATE OF owner_peer_type, owner_peer_id, owner_address, burned
|
||||
ON public.unique_star_gifts
|
||||
FOR EACH ROW EXECUTE FUNCTION public.telesrv_clear_invalid_collectible_emoji_status();
|
||||
|
||||
-- Deleted-user tombstones must not retain the new collectible facts.
|
||||
ALTER TABLE public.users DROP CONSTRAINT IF EXISTS users_deletion_state_check;
|
||||
ALTER TABLE public.users
|
||||
ADD CONSTRAINT users_deletion_state_check CHECK (
|
||||
(deleted_at IS NULL AND deletion_source = '' AND deletion_reason = '')
|
||||
OR
|
||||
(deleted_at IS NOT NULL
|
||||
AND deletion_source IN (
|
||||
'manual', 'forgot_password', 'tos_decline', 'password_reset_expiry',
|
||||
'account_ttl', 'freeze_expiry'
|
||||
)
|
||||
AND account_delete_at IS NULL
|
||||
AND phone = '' AND first_name = '' AND last_name = '' AND username = ''
|
||||
AND country_code = '' AND about = '' AND verified = false AND support = false
|
||||
AND premium_expires_at IS NULL
|
||||
AND emoji_status_document_id = 0 AND emoji_status_until = 0
|
||||
AND emoji_status_collectible_id IS NULL
|
||||
AND emoji_status_collectible = '{}'::jsonb
|
||||
AND color_set = false AND color = 0 AND color_background_emoji_id = 0
|
||||
AND profile_color_set = false AND profile_color = 0
|
||||
AND profile_color_background_emoji_id = 0
|
||||
AND birthday_day = 0 AND birthday_month = 0 AND birthday_year = 0
|
||||
AND personal_channel_id = 0 AND last_seen_at = 0
|
||||
AND octet_length(deletion_reason) <= 1024)
|
||||
);
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
CREATE TEMP TABLE telesrv_pattern_document_repair_rollback ON COMMIT DROP AS
|
||||
SELECT old_document_id, new_document_id
|
||||
FROM public.star_gift_pattern_document_repairs;
|
||||
|
||||
CREATE TEMP TABLE telesrv_rollback_collectible_wearers ON COMMIT DROP AS
|
||||
SELECT u.id AS user_id, r.old_document_id, r.new_document_id
|
||||
FROM public.users u
|
||||
JOIN telesrv_pattern_document_repair_rollback r
|
||||
ON (u.emoji_status_collectible->>'pattern_document_id')::bigint = r.new_document_id
|
||||
WHERE u.emoji_status_collectible_id IS NOT NULL;
|
||||
|
||||
UPDATE public.users u
|
||||
SET emoji_status_collectible = jsonb_set(
|
||||
u.emoji_status_collectible,
|
||||
'{pattern_document_id}',
|
||||
to_jsonb(w.old_document_id),
|
||||
false
|
||||
),
|
||||
updated_at = now()
|
||||
FROM telesrv_rollback_collectible_wearers w
|
||||
WHERE u.id = w.user_id;
|
||||
|
||||
UPDATE public.user_update_events e
|
||||
SET emoji_status_payload = jsonb_set(
|
||||
e.emoji_status_payload,
|
||||
'{collectible,pattern_document_id}',
|
||||
to_jsonb(r.old_document_id),
|
||||
false
|
||||
)
|
||||
FROM telesrv_pattern_document_repair_rollback r
|
||||
WHERE e.event_type = 'user_emoji_status'
|
||||
AND (e.emoji_status_payload #>> '{collectible,pattern_document_id}')::bigint = r.new_document_id;
|
||||
|
||||
ALTER TABLE public.star_gift_collectible_patterns
|
||||
DISABLE TRIGGER star_gift_collectible_pattern_guard;
|
||||
UPDATE public.star_gift_collectible_patterns p
|
||||
SET document_id = r.old_document_id
|
||||
FROM telesrv_pattern_document_repair_rollback r
|
||||
WHERE p.document_id = r.new_document_id;
|
||||
ALTER TABLE public.star_gift_collectible_patterns
|
||||
ENABLE TRIGGER star_gift_collectible_pattern_guard;
|
||||
|
||||
INSERT INTO public.user_update_watermarks (user_id, contiguous_pts)
|
||||
SELECT user_id, 0 FROM telesrv_rollback_collectible_wearers
|
||||
ON CONFLICT (user_id) DO NOTHING;
|
||||
|
||||
CREATE TEMP TABLE telesrv_collectible_pattern_rollback_events (
|
||||
user_id bigint PRIMARY KEY,
|
||||
pts integer NOT NULL
|
||||
) ON COMMIT DROP;
|
||||
|
||||
WITH bumped AS (
|
||||
UPDATE public.user_update_watermarks w
|
||||
SET contiguous_pts = contiguous_pts + 1,
|
||||
updated_at = now()
|
||||
FROM telesrv_rollback_collectible_wearers wearer
|
||||
WHERE w.user_id = wearer.user_id
|
||||
RETURNING w.user_id, w.contiguous_pts
|
||||
)
|
||||
INSERT INTO telesrv_collectible_pattern_rollback_events (user_id, pts)
|
||||
SELECT user_id, contiguous_pts FROM bumped;
|
||||
|
||||
INSERT INTO public.user_update_events (
|
||||
user_id, pts, pts_count, date, event_type,
|
||||
peer_type, peer_id, emoji_status_payload
|
||||
)
|
||||
SELECT
|
||||
c.user_id,
|
||||
c.pts,
|
||||
1,
|
||||
EXTRACT(EPOCH FROM clock_timestamp())::integer,
|
||||
'user_emoji_status',
|
||||
'user',
|
||||
c.user_id,
|
||||
jsonb_strip_nulls(jsonb_build_object(
|
||||
'document_id', u.emoji_status_document_id,
|
||||
'until', CASE WHEN u.emoji_status_until > 0 THEN u.emoji_status_until ELSE NULL END,
|
||||
'collectible', u.emoji_status_collectible
|
||||
))
|
||||
FROM telesrv_collectible_pattern_rollback_events c
|
||||
JOIN public.users u ON u.id = c.user_id;
|
||||
|
||||
INSERT INTO public.dispatch_outbox (
|
||||
target_user_id, pts, event_type, exclude_auth_key_id, exclude_session_id
|
||||
)
|
||||
SELECT user_id, pts, 'user_emoji_status', 0, 0
|
||||
FROM telesrv_collectible_pattern_rollback_events;
|
||||
|
||||
DELETE FROM public.file_blobs f
|
||||
USING telesrv_pattern_document_repair_rollback r
|
||||
WHERE f.location_key = 'doc:' || r.new_document_id::text
|
||||
OR f.location_key LIKE 'doc:' || r.new_document_id::text || ':%';
|
||||
|
||||
DROP TABLE public.star_gift_pattern_document_repairs;
|
||||
|
||||
DELETE FROM public.documents d
|
||||
USING telesrv_pattern_document_repair_rollback r
|
||||
WHERE d.id = r.new_document_id;
|
||||
|
|
@ -0,0 +1,172 @@
|
|||
-- Telegram clients cache Document metadata by id indefinitely. Migration 0114
|
||||
-- corrected collectible pattern attributes in place, but an Android client that
|
||||
-- had already cached the old sticker shape would never observe that correction.
|
||||
-- Allocate a new immutable document identity for every pre-0117 pattern, keep a
|
||||
-- durable old/new map for audit and rollback, and point the published attribute
|
||||
-- at the clone. The blob bytes do not change, so the new location keys alias the
|
||||
-- same immutable backend object.
|
||||
CREATE TABLE public.star_gift_pattern_document_repairs (
|
||||
old_document_id bigint PRIMARY KEY,
|
||||
new_document_id bigint UNIQUE NOT NULL,
|
||||
repaired_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
CONSTRAINT star_gift_pattern_document_repairs_ids_check CHECK (
|
||||
old_document_id > 0 AND new_document_id > 0 AND old_document_id <> new_document_id
|
||||
)
|
||||
);
|
||||
|
||||
INSERT INTO public.star_gift_pattern_document_repairs (old_document_id, new_document_id)
|
||||
SELECT DISTINCT
|
||||
p.document_id,
|
||||
((('x' || substr(md5(p.document_id::text || ':collectible-pattern-custom-emoji:v1'), 1, 16))
|
||||
::bit(64)::bigint) & 9223372036854775807)
|
||||
FROM public.star_gift_collectible_patterns p;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM public.star_gift_pattern_document_repairs r
|
||||
JOIN public.documents d ON d.id = r.new_document_id
|
||||
) THEN
|
||||
RAISE EXCEPTION 'collectible pattern document repair id collision';
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
INSERT INTO public.documents (
|
||||
id, access_hash, file_reference, date, mime_type, size, dc_id,
|
||||
attributes, thumbs, created_at
|
||||
)
|
||||
SELECT
|
||||
r.new_document_id,
|
||||
d.access_hash,
|
||||
d.file_reference,
|
||||
d.date,
|
||||
d.mime_type,
|
||||
d.size,
|
||||
d.dc_id,
|
||||
COALESCE((
|
||||
SELECT jsonb_agg(
|
||||
CASE
|
||||
WHEN item->>'kind' IN ('sticker', 'custom_emoji') THEN
|
||||
jsonb_set(
|
||||
jsonb_set(item, '{kind}', '"custom_emoji"'::jsonb, false),
|
||||
'{text_color}', 'true'::jsonb, true
|
||||
)
|
||||
ELSE item
|
||||
END
|
||||
ORDER BY ord
|
||||
)
|
||||
FROM jsonb_array_elements(d.attributes) WITH ORDINALITY AS attrs(item, ord)
|
||||
), '[]'::jsonb),
|
||||
d.thumbs,
|
||||
now()
|
||||
FROM public.star_gift_pattern_document_repairs r
|
||||
JOIN public.documents d ON d.id = r.old_document_id;
|
||||
|
||||
INSERT INTO public.file_blobs (
|
||||
location_key, backend, object_key, size, sha256, mime_type, created_at
|
||||
)
|
||||
SELECT
|
||||
'doc:' || r.new_document_id::text ||
|
||||
substr(f.location_key, length('doc:' || r.old_document_id::text) + 1),
|
||||
f.backend,
|
||||
f.object_key,
|
||||
f.size,
|
||||
f.sha256,
|
||||
f.mime_type,
|
||||
now()
|
||||
FROM public.star_gift_pattern_document_repairs r
|
||||
JOIN public.file_blobs f
|
||||
ON f.location_key = 'doc:' || r.old_document_id::text
|
||||
OR f.location_key LIKE 'doc:' || r.old_document_id::text || ':%';
|
||||
|
||||
-- This is a repair of a previously invalid document identity, not a mutation of
|
||||
-- the published appearance. Attribute id/name/animation/rarity remain intact.
|
||||
ALTER TABLE public.star_gift_collectible_patterns
|
||||
DISABLE TRIGGER star_gift_collectible_pattern_guard;
|
||||
UPDATE public.star_gift_collectible_patterns p
|
||||
SET document_id = r.new_document_id
|
||||
FROM public.star_gift_pattern_document_repairs r
|
||||
WHERE p.document_id = r.old_document_id;
|
||||
ALTER TABLE public.star_gift_collectible_patterns
|
||||
ENABLE TRIGGER star_gift_collectible_pattern_guard;
|
||||
|
||||
-- Capture active wearers before rewriting their immutable render snapshots.
|
||||
CREATE TEMP TABLE telesrv_repaired_collectible_wearers ON COMMIT DROP AS
|
||||
SELECT u.id AS user_id, r.old_document_id, r.new_document_id
|
||||
FROM public.users u
|
||||
JOIN public.star_gift_pattern_document_repairs r
|
||||
ON (u.emoji_status_collectible->>'pattern_document_id')::bigint = r.old_document_id
|
||||
WHERE u.emoji_status_collectible_id IS NOT NULL;
|
||||
|
||||
UPDATE public.users u
|
||||
SET emoji_status_collectible = jsonb_set(
|
||||
u.emoji_status_collectible,
|
||||
'{pattern_document_id}',
|
||||
to_jsonb(w.new_document_id),
|
||||
false
|
||||
),
|
||||
updated_at = now()
|
||||
FROM telesrv_repaired_collectible_wearers w
|
||||
WHERE u.id = w.user_id;
|
||||
|
||||
-- An offline client may still replay an older status event, so repair every
|
||||
-- durable snapshot. A fresh event is appended below for clients that already
|
||||
-- consumed the old pts and therefore need a new convergence edge.
|
||||
UPDATE public.user_update_events e
|
||||
SET emoji_status_payload = jsonb_set(
|
||||
e.emoji_status_payload,
|
||||
'{collectible,pattern_document_id}',
|
||||
to_jsonb(r.new_document_id),
|
||||
false
|
||||
)
|
||||
FROM public.star_gift_pattern_document_repairs r
|
||||
WHERE e.event_type = 'user_emoji_status'
|
||||
AND (e.emoji_status_payload #>> '{collectible,pattern_document_id}')::bigint = r.old_document_id;
|
||||
|
||||
INSERT INTO public.user_update_watermarks (user_id, contiguous_pts)
|
||||
SELECT user_id, 0 FROM telesrv_repaired_collectible_wearers
|
||||
ON CONFLICT (user_id) DO NOTHING;
|
||||
|
||||
CREATE TEMP TABLE telesrv_collectible_pattern_correction_events (
|
||||
user_id bigint PRIMARY KEY,
|
||||
pts integer NOT NULL
|
||||
) ON COMMIT DROP;
|
||||
|
||||
WITH bumped AS (
|
||||
UPDATE public.user_update_watermarks w
|
||||
SET contiguous_pts = contiguous_pts + 1,
|
||||
updated_at = now()
|
||||
FROM telesrv_repaired_collectible_wearers wearer
|
||||
WHERE w.user_id = wearer.user_id
|
||||
RETURNING w.user_id, w.contiguous_pts
|
||||
)
|
||||
INSERT INTO telesrv_collectible_pattern_correction_events (user_id, pts)
|
||||
SELECT user_id, contiguous_pts FROM bumped;
|
||||
|
||||
INSERT INTO public.user_update_events (
|
||||
user_id, pts, pts_count, date, event_type,
|
||||
peer_type, peer_id, emoji_status_payload
|
||||
)
|
||||
SELECT
|
||||
c.user_id,
|
||||
c.pts,
|
||||
1,
|
||||
EXTRACT(EPOCH FROM clock_timestamp())::integer,
|
||||
'user_emoji_status',
|
||||
'user',
|
||||
c.user_id,
|
||||
jsonb_strip_nulls(jsonb_build_object(
|
||||
'document_id', u.emoji_status_document_id,
|
||||
'until', CASE WHEN u.emoji_status_until > 0 THEN u.emoji_status_until ELSE NULL END,
|
||||
'collectible', u.emoji_status_collectible
|
||||
))
|
||||
FROM telesrv_collectible_pattern_correction_events c
|
||||
JOIN public.users u ON u.id = c.user_id;
|
||||
|
||||
INSERT INTO public.dispatch_outbox (
|
||||
target_user_id, pts, event_type, exclude_auth_key_id, exclude_session_id
|
||||
)
|
||||
SELECT user_id, pts, 'user_emoji_status', 0, 0
|
||||
FROM telesrv_collectible_pattern_correction_events;
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
CREATE TEMP TABLE telesrv_pattern_preview_repair_rollback ON COMMIT DROP AS
|
||||
SELECT old_document_id, new_document_id
|
||||
FROM public.star_gift_pattern_preview_document_repairs;
|
||||
|
||||
CREATE TEMP TABLE telesrv_pattern_preview_rollback_wearers ON COMMIT DROP AS
|
||||
SELECT u.id AS user_id, r.old_document_id, r.new_document_id
|
||||
FROM public.users u
|
||||
JOIN telesrv_pattern_preview_repair_rollback r
|
||||
ON (u.emoji_status_collectible->>'pattern_document_id')::bigint = r.new_document_id
|
||||
WHERE u.emoji_status_collectible_id IS NOT NULL;
|
||||
|
||||
UPDATE public.users u
|
||||
SET emoji_status_collectible = jsonb_set(
|
||||
u.emoji_status_collectible,
|
||||
'{pattern_document_id}',
|
||||
to_jsonb(w.old_document_id),
|
||||
false
|
||||
),
|
||||
updated_at = now()
|
||||
FROM telesrv_pattern_preview_rollback_wearers w
|
||||
WHERE u.id = w.user_id;
|
||||
|
||||
UPDATE public.user_update_events e
|
||||
SET emoji_status_payload = jsonb_set(
|
||||
e.emoji_status_payload,
|
||||
'{collectible,pattern_document_id}',
|
||||
to_jsonb(r.old_document_id),
|
||||
false
|
||||
)
|
||||
FROM telesrv_pattern_preview_repair_rollback r
|
||||
WHERE e.event_type = 'user_emoji_status'
|
||||
AND (e.emoji_status_payload #>> '{collectible,pattern_document_id}')::bigint = r.new_document_id;
|
||||
|
||||
ALTER TABLE public.star_gift_collectible_patterns
|
||||
DISABLE TRIGGER star_gift_collectible_pattern_guard;
|
||||
UPDATE public.star_gift_collectible_patterns p
|
||||
SET document_id = r.old_document_id
|
||||
FROM telesrv_pattern_preview_repair_rollback r
|
||||
WHERE p.document_id = r.new_document_id;
|
||||
ALTER TABLE public.star_gift_collectible_patterns
|
||||
ENABLE TRIGGER star_gift_collectible_pattern_guard;
|
||||
|
||||
INSERT INTO public.user_update_watermarks (user_id, contiguous_pts)
|
||||
SELECT user_id, 0 FROM telesrv_pattern_preview_rollback_wearers
|
||||
ON CONFLICT (user_id) DO NOTHING;
|
||||
|
||||
CREATE TEMP TABLE telesrv_pattern_preview_rollback_events (
|
||||
user_id bigint PRIMARY KEY,
|
||||
pts integer NOT NULL
|
||||
) ON COMMIT DROP;
|
||||
|
||||
WITH bumped AS (
|
||||
UPDATE public.user_update_watermarks w
|
||||
SET contiguous_pts = contiguous_pts + 1,
|
||||
updated_at = now()
|
||||
FROM telesrv_pattern_preview_rollback_wearers wearer
|
||||
WHERE w.user_id = wearer.user_id
|
||||
RETURNING w.user_id, w.contiguous_pts
|
||||
)
|
||||
INSERT INTO telesrv_pattern_preview_rollback_events (user_id, pts)
|
||||
SELECT user_id, contiguous_pts FROM bumped;
|
||||
|
||||
INSERT INTO public.user_update_events (
|
||||
user_id, pts, pts_count, date, event_type,
|
||||
peer_type, peer_id, emoji_status_payload
|
||||
)
|
||||
SELECT
|
||||
c.user_id,
|
||||
c.pts,
|
||||
1,
|
||||
EXTRACT(EPOCH FROM clock_timestamp())::integer,
|
||||
'user_emoji_status',
|
||||
'user',
|
||||
c.user_id,
|
||||
jsonb_strip_nulls(jsonb_build_object(
|
||||
'document_id', u.emoji_status_document_id,
|
||||
'until', CASE WHEN u.emoji_status_until > 0 THEN u.emoji_status_until ELSE NULL END,
|
||||
'collectible', u.emoji_status_collectible
|
||||
))
|
||||
FROM telesrv_pattern_preview_rollback_events c
|
||||
JOIN public.users u ON u.id = c.user_id;
|
||||
|
||||
INSERT INTO public.dispatch_outbox (
|
||||
target_user_id, pts, event_type, exclude_auth_key_id, exclude_session_id
|
||||
)
|
||||
SELECT user_id, pts, 'user_emoji_status', 0, 0
|
||||
FROM telesrv_pattern_preview_rollback_events;
|
||||
|
||||
DELETE FROM public.file_blobs f
|
||||
USING telesrv_pattern_preview_repair_rollback r
|
||||
WHERE f.location_key = 'doc:' || r.new_document_id::text
|
||||
OR f.location_key LIKE 'doc:' || r.new_document_id::text || ':%';
|
||||
|
||||
DROP TABLE public.star_gift_pattern_preview_document_repairs;
|
||||
|
||||
DELETE FROM public.documents d
|
||||
USING telesrv_pattern_preview_repair_rollback r
|
||||
WHERE d.id = r.new_document_id;
|
||||
|
|
@ -0,0 +1,168 @@
|
|||
-- DrKLO's profile-header path creates collectible patterns with
|
||||
-- CACHE_TYPE_ALERT_PREVIEW_STATIC. For application/x-tgsticker that client
|
||||
-- only loads the main TGS when Document.thumbs is non-empty; an empty list is
|
||||
-- routed to a null thumbnail location and the pattern remains a flat backdrop.
|
||||
--
|
||||
-- Document metadata is cached by id, so adding a thumb to an already published
|
||||
-- id would not repair existing Android installations. Clone every affected
|
||||
-- published pattern under a new immutable identity, add an inline PhotoPathSize
|
||||
-- placeholder, alias the unchanged main blob, and converge active/durable emoji
|
||||
-- status snapshots to the new id.
|
||||
CREATE TABLE public.star_gift_pattern_preview_document_repairs (
|
||||
old_document_id bigint PRIMARY KEY,
|
||||
new_document_id bigint UNIQUE NOT NULL,
|
||||
repaired_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
CONSTRAINT star_gift_pattern_preview_document_repairs_ids_check CHECK (
|
||||
old_document_id > 0 AND new_document_id > 0 AND old_document_id <> new_document_id
|
||||
)
|
||||
);
|
||||
|
||||
INSERT INTO public.star_gift_pattern_preview_document_repairs (old_document_id, new_document_id)
|
||||
SELECT DISTINCT
|
||||
p.document_id,
|
||||
((('x' || substr(md5(p.document_id::text || ':collectible-pattern-android-preview:v2'), 1, 16))
|
||||
::bit(64)::bigint) & 9223372036854775807)
|
||||
FROM public.star_gift_collectible_patterns p
|
||||
JOIN public.documents d ON d.id = p.document_id
|
||||
WHERE jsonb_array_length(d.thumbs) = 0;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM public.star_gift_pattern_preview_document_repairs r
|
||||
JOIN public.documents d ON d.id = r.new_document_id
|
||||
) OR EXISTS (
|
||||
SELECT 1
|
||||
FROM public.star_gift_pattern_preview_document_repairs r
|
||||
JOIN public.file_blobs f
|
||||
ON f.location_key = 'doc:' || r.new_document_id::text
|
||||
OR f.location_key LIKE 'doc:' || r.new_document_id::text || ':%'
|
||||
) THEN
|
||||
RAISE EXCEPTION 'collectible pattern preview document repair id collision';
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
INSERT INTO public.documents (
|
||||
id, access_hash, file_reference, date, mime_type, size, dc_id,
|
||||
attributes, thumbs, created_at
|
||||
)
|
||||
SELECT
|
||||
r.new_document_id,
|
||||
d.access_hash,
|
||||
d.file_reference,
|
||||
d.date,
|
||||
d.mime_type,
|
||||
d.size,
|
||||
d.dc_id,
|
||||
d.attributes,
|
||||
jsonb_build_array(jsonb_build_object(
|
||||
'kind', 'path',
|
||||
'type', 'j',
|
||||
'bytes', 'GQalBdxhTX54SARIBGNsfE4Imk4HooCjlLqhhYOHSIxMjEybVa1VkICfhqqRqquGigRYjgFNkXmHA0cGhwM='
|
||||
)),
|
||||
now()
|
||||
FROM public.star_gift_pattern_preview_document_repairs r
|
||||
JOIN public.documents d ON d.id = r.old_document_id;
|
||||
|
||||
INSERT INTO public.file_blobs (
|
||||
location_key, backend, object_key, size, sha256, mime_type, created_at
|
||||
)
|
||||
SELECT
|
||||
'doc:' || r.new_document_id::text ||
|
||||
substr(f.location_key, length('doc:' || r.old_document_id::text) + 1),
|
||||
f.backend,
|
||||
f.object_key,
|
||||
f.size,
|
||||
f.sha256,
|
||||
f.mime_type,
|
||||
now()
|
||||
FROM public.star_gift_pattern_preview_document_repairs r
|
||||
JOIN public.file_blobs f
|
||||
ON f.location_key = 'doc:' || r.old_document_id::text
|
||||
OR f.location_key LIKE 'doc:' || r.old_document_id::text || ':%';
|
||||
|
||||
ALTER TABLE public.star_gift_collectible_patterns
|
||||
DISABLE TRIGGER star_gift_collectible_pattern_guard;
|
||||
UPDATE public.star_gift_collectible_patterns p
|
||||
SET document_id = r.new_document_id
|
||||
FROM public.star_gift_pattern_preview_document_repairs r
|
||||
WHERE p.document_id = r.old_document_id;
|
||||
ALTER TABLE public.star_gift_collectible_patterns
|
||||
ENABLE TRIGGER star_gift_collectible_pattern_guard;
|
||||
|
||||
CREATE TEMP TABLE telesrv_pattern_preview_repaired_wearers ON COMMIT DROP AS
|
||||
SELECT u.id AS user_id, r.old_document_id, r.new_document_id
|
||||
FROM public.users u
|
||||
JOIN public.star_gift_pattern_preview_document_repairs r
|
||||
ON (u.emoji_status_collectible->>'pattern_document_id')::bigint = r.old_document_id
|
||||
WHERE u.emoji_status_collectible_id IS NOT NULL;
|
||||
|
||||
UPDATE public.users u
|
||||
SET emoji_status_collectible = jsonb_set(
|
||||
u.emoji_status_collectible,
|
||||
'{pattern_document_id}',
|
||||
to_jsonb(w.new_document_id),
|
||||
false
|
||||
),
|
||||
updated_at = now()
|
||||
FROM telesrv_pattern_preview_repaired_wearers w
|
||||
WHERE u.id = w.user_id;
|
||||
|
||||
UPDATE public.user_update_events e
|
||||
SET emoji_status_payload = jsonb_set(
|
||||
e.emoji_status_payload,
|
||||
'{collectible,pattern_document_id}',
|
||||
to_jsonb(r.new_document_id),
|
||||
false
|
||||
)
|
||||
FROM public.star_gift_pattern_preview_document_repairs r
|
||||
WHERE e.event_type = 'user_emoji_status'
|
||||
AND (e.emoji_status_payload #>> '{collectible,pattern_document_id}')::bigint = r.old_document_id;
|
||||
|
||||
INSERT INTO public.user_update_watermarks (user_id, contiguous_pts)
|
||||
SELECT user_id, 0 FROM telesrv_pattern_preview_repaired_wearers
|
||||
ON CONFLICT (user_id) DO NOTHING;
|
||||
|
||||
CREATE TEMP TABLE telesrv_pattern_preview_correction_events (
|
||||
user_id bigint PRIMARY KEY,
|
||||
pts integer NOT NULL
|
||||
) ON COMMIT DROP;
|
||||
|
||||
WITH bumped AS (
|
||||
UPDATE public.user_update_watermarks w
|
||||
SET contiguous_pts = contiguous_pts + 1,
|
||||
updated_at = now()
|
||||
FROM telesrv_pattern_preview_repaired_wearers wearer
|
||||
WHERE w.user_id = wearer.user_id
|
||||
RETURNING w.user_id, w.contiguous_pts
|
||||
)
|
||||
INSERT INTO telesrv_pattern_preview_correction_events (user_id, pts)
|
||||
SELECT user_id, contiguous_pts FROM bumped;
|
||||
|
||||
INSERT INTO public.user_update_events (
|
||||
user_id, pts, pts_count, date, event_type,
|
||||
peer_type, peer_id, emoji_status_payload
|
||||
)
|
||||
SELECT
|
||||
c.user_id,
|
||||
c.pts,
|
||||
1,
|
||||
EXTRACT(EPOCH FROM clock_timestamp())::integer,
|
||||
'user_emoji_status',
|
||||
'user',
|
||||
c.user_id,
|
||||
jsonb_strip_nulls(jsonb_build_object(
|
||||
'document_id', u.emoji_status_document_id,
|
||||
'until', CASE WHEN u.emoji_status_until > 0 THEN u.emoji_status_until ELSE NULL END,
|
||||
'collectible', u.emoji_status_collectible
|
||||
))
|
||||
FROM telesrv_pattern_preview_correction_events c
|
||||
JOIN public.users u ON u.id = c.user_id;
|
||||
|
||||
INSERT INTO public.dispatch_outbox (
|
||||
target_user_id, pts, event_type, exclude_auth_key_id, exclude_session_id
|
||||
)
|
||||
SELECT user_id, pts, 'user_emoji_status', 0, 0
|
||||
FROM telesrv_pattern_preview_correction_events;
|
||||
Loading…
Add table
Add a link
Reference in a new issue