@@ -4703,6 +4703,92 @@ end;
47034703$$;
47044704
47054705
4706+ --
4707+ -- Name: handle_tastemaker(); Type: FUNCTION; Schema: public; Owner: -
4708+ --
4709+
4710+ CREATE FUNCTION public .handle_tastemaker() RETURNS trigger
4711+ LANGUAGE plpgsql
4712+ AS $_$
4713+ declare
4714+ track_hex text ;
4715+ track_id_int bigint ;
4716+ owner_id_int int ;
4717+ action_str text ;
4718+ begin
4719+ -- WHEN clause on the trigger gates challenge_id='t', but defend in
4720+ -- depth here too in case the trigger is invoked another way.
4721+ if new .challenge_id <> ' t' then
4722+ return null ;
4723+ end if;
4724+
4725+ -- Parse trailing hex segment "<user_hex>:t:<track_hex>" → track_id.
4726+ track_hex := split_part(new .specifier , ' :' , 3 );
4727+ if track_hex !~ ' ^[0-9a-f]+$' then
4728+ return null ;
4729+ end if;
4730+ track_id_int := (' x' || lpad(track_hex, 16 , ' 0' ))::bit (64 )::bigint ;
4731+ if track_id_int <= 0 then
4732+ return null ;
4733+ end if;
4734+
4735+ select t .owner_id
4736+ into owner_id_int
4737+ from tracks t
4738+ where t .track_id = track_id_int
4739+ and t .is_current = true
4740+ limit 1 ;
4741+ if owner_id_int is null then
4742+ return null ;
4743+ end if;
4744+
4745+ -- Repost takes precedence over save when a user is in both lists for
4746+ -- the same track — matches apps' dedupe_notifications_by_group_id
4747+ -- where repost_notifications win over save_notifications.
4748+ if exists (
4749+ select 1
4750+ from reposts
4751+ where user_id = new .user_id
4752+ and repost_item_id = track_id_int
4753+ and repost_type = ' track'
4754+ and is_current = true
4755+ and is_delete = false
4756+ ) then
4757+ action_str := ' repost' ;
4758+ else
4759+ action_str := ' save' ;
4760+ end if;
4761+
4762+ insert into notification
4763+ (blocknumber, user_ids, timestamp , type, specifier, group_id, data)
4764+ values
4765+ (
4766+ new .completed_blocknumber ,
4767+ ARRAY[new .user_id ],
4768+ new .completed_at ,
4769+ ' tastemaker' ,
4770+ track_id_int::text ,
4771+ ' tastemaker_user_id:' || new .user_id || ' :tastemaker_item_id:' || track_id_int,
4772+ jsonb_build_object(
4773+ ' tastemaker_item_id' , track_id_int,
4774+ ' tastemaker_item_type' , ' track' ,
4775+ ' tastemaker_item_owner_id' , owner_id_int,
4776+ ' action' , action_str,
4777+ ' tastemaker_user_id' , new .user_id
4778+ )
4779+ )
4780+ on conflict do nothing;
4781+
4782+ return null ;
4783+
4784+ exception
4785+ when others then
4786+ raise warning ' An error occurred in %: %' , tg_name, sqlerrm;
4787+ return null ;
4788+ end;
4789+ $_$;
4790+
4791+
47064792--
47074793-- Name: handle_track(); Type: FUNCTION; Schema: public; Owner: -
47084794--
@@ -4954,6 +5040,99 @@ end;
49545040$$;
49555041
49565042
5043+ --
5044+ -- Name: handle_trending(); Type: FUNCTION; Schema: public; Owner: -
5045+ --
5046+
5047+ CREATE FUNCTION public .handle_trending() RETURNS trigger
5048+ LANGUAGE plpgsql
5049+ AS $$
5050+ declare
5051+ rank_int int ;
5052+ week_date date ;
5053+ entity_id_str text ;
5054+ entity_id_int bigint ;
5055+ notif_type text ;
5056+ trend_type text ;
5057+ ts_epoch bigint ;
5058+ data_jsonb jsonb;
5059+ begin
5060+ if new .challenge_id not in (' tt' , ' tut' ) then
5061+ return null ;
5062+ end if;
5063+
5064+ case new .challenge_id
5065+ when ' tt' then notif_type := ' trending' ; trend_type := ' TRACKS' ;
5066+ when ' tut' then notif_type := ' trending_underground' ; trend_type := ' UNDERGROUND_TRACKS' ;
5067+ end case;
5068+
5069+ -- Specifier: "<YYYY-MM-DD>:<rank>"
5070+ begin
5071+ week_date := split_part(new .specifier , ' :' , 1 )::date ;
5072+ rank_int := split_part(new .specifier , ' :' , 2 )::int ;
5073+ exception when others then
5074+ return null ;
5075+ end;
5076+
5077+ -- Recover entity id from the trending_results row the processor wrote
5078+ -- earlier in this transaction. PK is (rank, type, version, week); we
5079+ -- pin to NEW.user_id so we ignore any unrelated version rows.
5080+ select id
5081+ into entity_id_str
5082+ from trending_results
5083+ where rank = rank_int
5084+ and type = trend_type
5085+ and week = week_date
5086+ and user_id = new .user_id
5087+ limit 1 ;
5088+ if entity_id_str is null then
5089+ return null ;
5090+ end if;
5091+ begin
5092+ entity_id_int := entity_id_str::bigint ;
5093+ exception when others then
5094+ return null ;
5095+ end;
5096+
5097+ -- timestamp suffix matches apps: epoch seconds of the recompute. We
5098+ -- use completed_at which is set by UpsertUserChallenge to now() on
5099+ -- the first insert — close enough to the recompute moment.
5100+ ts_epoch := extract(epoch from new .completed_at )::bigint ;
5101+
5102+ data_jsonb := jsonb_build_object(
5103+ ' time_range' , ' week' ,
5104+ ' genre' , ' all' ,
5105+ ' rank' , rank_int,
5106+ ' track_id' , entity_id_int
5107+ );
5108+
5109+ insert into notification
5110+ (blocknumber, user_ids, timestamp , type, specifier, group_id, data)
5111+ values
5112+ (
5113+ new .completed_blocknumber ,
5114+ ARRAY[new .user_id ],
5115+ new .completed_at ,
5116+ notif_type,
5117+ entity_id_int::text ,
5118+ notif_type
5119+ || ' :time_range:week:genre:all:rank:' || rank_int
5120+ || ' :track_id:' || entity_id_int
5121+ || ' :timestamp:' || ts_epoch,
5122+ data_jsonb
5123+ )
5124+ on conflict do nothing;
5125+
5126+ return null ;
5127+
5128+ exception
5129+ when others then
5130+ raise warning ' An error occurred in %: %' , tg_name, sqlerrm;
5131+ return null ;
5132+ end;
5133+ $$;
5134+
5135+
49575136--
49585137-- Name: handle_usdc_purchase(); Type: FUNCTION; Schema: public; Owner: -
49595138--
@@ -13952,6 +14131,13 @@ CREATE TRIGGER on_sol_usdc_withdrawal AFTER INSERT ON public.sol_claimable_accou
1395214131CREATE TRIGGER on_supporter_rank_up AFTER INSERT ON public .supporter_rank_ups FOR EACH ROW EXECUTE FUNCTION public .handle_supporter_rank_up ();
1395314132
1395414133
14134+ --
14135+ -- Name: user_challenges on_tastemaker_user_challenge; Type: TRIGGER; Schema: public; Owner: -
14136+ --
14137+
14138+ CREATE TRIGGER on_tastemaker_user_challenge AFTER INSERT ON public .user_challenges FOR EACH ROW WHEN (((new .challenge_id )::text = ' t' ::text )) EXECUTE FUNCTION public .handle_tastemaker ();
14139+
14140+
1395514141--
1395614142-- Name: tracks on_track; Type: TRIGGER; Schema: public; Owner: -
1395714143--
@@ -13966,6 +14152,13 @@ CREATE TRIGGER on_track AFTER INSERT OR UPDATE ON public.tracks FOR EACH ROW EXE
1396614152CREATE TRIGGER on_track_notify_pending_purchase_revalidation AFTER INSERT OR UPDATE OF blocknumber ON public .tracks FOR EACH ROW EXECUTE FUNCTION public .notify_pending_purchase_revalidation ();
1396714153
1396814154
14155+ --
14156+ -- Name: user_challenges on_trending_user_challenge; Type: TRIGGER; Schema: public; Owner: -
14157+ --
14158+
14159+ CREATE TRIGGER on_trending_user_challenge AFTER INSERT ON public .user_challenges FOR EACH ROW WHEN (((new .challenge_id )::text = ANY ((ARRAY[' tt' ::character varying, ' tut' ::character varying])::text []))) EXECUTE FUNCTION public .handle_trending ();
14160+
14161+
1396914162--
1397014163-- Name: usdc_purchases on_usdc_purchase; Type: TRIGGER; Schema: public; Owner: -
1397114164--
0 commit comments