rhythia-api 241.0.0 → 243.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,107 @@
1
+ CREATE OR REPLACE FUNCTION public.admin_remove_score(user_id integer, score_id bigint)
2
+ RETURNS boolean
3
+ LANGUAGE plpgsql
4
+ SECURITY DEFINER
5
+ AS $function$
6
+ DECLARE
7
+ removed_score public.scores%ROWTYPE;
8
+ best_score RECORD;
9
+ total_sp numeric;
10
+ spin_total_sp numeric;
11
+ BEGIN
12
+ DELETE FROM public.scores
13
+ WHERE id = score_id
14
+ AND "userId" = user_id
15
+ RETURNING * INTO removed_score;
16
+
17
+ IF removed_score.id IS NULL THEN
18
+ RETURN false;
19
+ END IF;
20
+
21
+ SELECT id, awarded_sp
22
+ INTO best_score
23
+ FROM public.scores
24
+ WHERE "userId" = user_id
25
+ AND "beatmapHash" = removed_score."beatmapHash"
26
+ AND passed = true
27
+ ORDER BY awarded_sp DESC NULLS LAST
28
+ LIMIT 1;
29
+
30
+ IF best_score.id IS NULL THEN
31
+ DELETE FROM public.leaderboard_map_user
32
+ WHERE "userId" = user_id
33
+ AND "beatmapHash" = removed_score."beatmapHash";
34
+ ELSE
35
+ INSERT INTO public.leaderboard_map_user (
36
+ "beatmapHash",
37
+ "userId",
38
+ best_score_id,
39
+ best_sp
40
+ )
41
+ VALUES (
42
+ removed_score."beatmapHash",
43
+ user_id,
44
+ best_score.id,
45
+ best_score.awarded_sp
46
+ )
47
+ ON CONFLICT ("beatmapHash", "userId")
48
+ DO UPDATE SET
49
+ best_score_id = excluded.best_score_id,
50
+ best_sp = excluded.best_sp;
51
+ END IF;
52
+
53
+ WITH best_scores AS (
54
+ SELECT DISTINCT ON ("beatmapHash")
55
+ "beatmapHash",
56
+ awarded_sp
57
+ FROM public.scores
58
+ WHERE "userId" = user_id
59
+ AND passed = true
60
+ AND awarded_sp <> 0
61
+ ORDER BY "beatmapHash", awarded_sp DESC
62
+ ),
63
+ weighted AS (
64
+ SELECT
65
+ awarded_sp,
66
+ power(0.97, row_number() over (ORDER BY awarded_sp DESC) - 1) AS weight
67
+ FROM best_scores
68
+ )
69
+ SELECT COALESCE(round(sum(awarded_sp * weight) FILTER (WHERE weight >= 0.05), 2), 0)
70
+ INTO total_sp
71
+ FROM weighted;
72
+
73
+ WITH best_scores AS (
74
+ SELECT DISTINCT ON ("beatmapHash")
75
+ "beatmapHash",
76
+ awarded_sp
77
+ FROM public.scores
78
+ WHERE "userId" = user_id
79
+ AND passed = true
80
+ AND spin = true
81
+ AND awarded_sp <> 0
82
+ ORDER BY "beatmapHash", awarded_sp DESC
83
+ ),
84
+ weighted AS (
85
+ SELECT
86
+ awarded_sp,
87
+ power(0.97, row_number() over (ORDER BY awarded_sp DESC) - 1) AS weight
88
+ FROM best_scores
89
+ )
90
+ SELECT COALESCE(round(sum(awarded_sp * weight) FILTER (WHERE weight >= 0.05), 2), 0)
91
+ INTO spin_total_sp
92
+ FROM weighted;
93
+
94
+ UPDATE public.profiles
95
+ SET
96
+ play_count = (
97
+ SELECT count(*)
98
+ FROM public.scores
99
+ WHERE "userId" = user_id
100
+ ),
101
+ skill_points = total_sp,
102
+ spin_skill_points = spin_total_sp
103
+ WHERE id = user_id;
104
+
105
+ RETURN true;
106
+ END;
107
+ $function$
@@ -0,0 +1,22 @@
1
+ CREATE OR REPLACE FUNCTION public.admin_update_profile(user_id integer, profile_data jsonb)
2
+ RETURNS SETOF public.profiles
3
+ LANGUAGE plpgsql
4
+ SECURITY DEFINER
5
+ AS $function$
6
+ BEGIN
7
+ PERFORM set_config('app.bypass_profile_limits', 'on', true);
8
+
9
+ RETURN QUERY
10
+ UPDATE public.profiles
11
+ SET
12
+ username = CASE WHEN profile_data ? 'username' THEN profile_data->>'username' ELSE profiles.username END,
13
+ "computedUsername" = CASE WHEN profile_data ? 'username' THEN lower(profile_data->>'username') ELSE profiles."computedUsername" END,
14
+ avatar_url = CASE WHEN profile_data ? 'avatar_url' THEN profile_data->>'avatar_url' ELSE profiles.avatar_url END,
15
+ flag = CASE WHEN profile_data ? 'flag' THEN profile_data->>'flag' ELSE profiles.flag END,
16
+ profile_image = CASE WHEN profile_data ? 'profile_image' THEN profile_data->>'profile_image' ELSE profiles.profile_image END,
17
+ verified = CASE WHEN profile_data ? 'verified' THEN (profile_data->>'verified')::boolean ELSE profiles.verified END,
18
+ "verificationDeadline" = CASE WHEN profile_data ? 'verified' THEN 2524608000000 ELSE profiles."verificationDeadline" END
19
+ WHERE id = user_id
20
+ RETURNING profiles.*;
21
+ END;
22
+ $function$
@@ -0,0 +1,48 @@
1
+ DROP FUNCTION IF EXISTS public.get_beatmaps_v2(integer, integer, text, text, text, double precision, double precision, double precision, double precision, bigint, text);
2
+
3
+ CREATE OR REPLACE FUNCTION public.get_beatmaps_v2(page_number integer DEFAULT 1, items_per_page integer DEFAULT 50, text_filter text DEFAULT NULL::text, author_filter text DEFAULT NULL::text, tags_filter text DEFAULT NULL::text, max_stars double precision DEFAULT NULL::double precision, min_length double precision DEFAULT NULL::double precision, max_length double precision DEFAULT NULL::double precision, min_stars double precision DEFAULT NULL::double precision, creator_filter bigint DEFAULT NULL::bigint, status_filter text DEFAULT NULL::text)
4
+ RETURNS TABLE(total_count bigint, owner bigint, created_at timestamp with time zone, id bigint, status text, qualified boolean, tags text, video_url text, map_hash text, playcount bigint, ranked boolean, beatmap_file text, image text, star_rating double precision, difficulty bigint, length double precision, title text, owner_username text)
5
+ LANGUAGE sql
6
+ STABLE
7
+ AS $function$
8
+ SELECT
9
+ COUNT(*) OVER () AS total_count,
10
+ bp.owner::bigint,
11
+ bp.created_at,
12
+ bp.id::bigint,
13
+ bp.status,
14
+ bp.qualified,
15
+ bp.tags,
16
+ bp.video_url,
17
+ bp."mapHash",
18
+ b.playcount::bigint,
19
+ b.ranked,
20
+ b."beatmapFile",
21
+ b.image,
22
+ b."starRating"::double precision,
23
+ b.difficulty::bigint,
24
+ b.length::double precision,
25
+ b.title,
26
+ p.username
27
+ FROM public."beatmapPages" bp
28
+ INNER JOIN public.beatmaps b ON b."beatmapHash" = bp."latestBeatmapHash"
29
+ INNER JOIN public.profiles p ON p.id = bp.owner
30
+ WHERE (text_filter IS NULL OR b.title ILIKE '%' || text_filter || '%' OR p.username ILIKE '%' || text_filter || '%')
31
+ AND (author_filter IS NULL OR p.username ILIKE '%' || author_filter || '%')
32
+ AND (tags_filter IS NULL OR bp.tags ILIKE '%' || tags_filter || '%')
33
+ AND (min_stars IS NULL OR b."starRating" > min_stars)
34
+ AND (max_stars IS NULL OR b."starRating" < max_stars)
35
+ AND (min_length IS NULL OR b.length > min_length)
36
+ AND (max_length IS NULL OR b.length < max_length)
37
+ AND (creator_filter IS NULL OR bp.owner = creator_filter)
38
+ AND (
39
+ status_filter IS NULL
40
+ OR (UPPER(status_filter) = 'QUALIFIED' AND bp.qualified = true)
41
+ OR (UPPER(status_filter) <> 'QUALIFIED' AND bp.status = status_filter)
42
+ )
43
+ ORDER BY
44
+ CASE WHEN UPPER(status_filter) = 'RANKED' THEN bp.ranked_at END DESC NULLS LAST,
45
+ CASE WHEN UPPER(status_filter) = 'RANKED' THEN NULL ELSE bp.created_at END DESC NULLS LAST
46
+ LIMIT items_per_page
47
+ OFFSET ((page_number - 1) * items_per_page);
48
+ $function$
@@ -1,38 +1,47 @@
1
- CREATE OR REPLACE FUNCTION public.get_top_scores_for_beatmap3(beatmap_hash text)
2
- RETURNS TABLE(id bigint, awarded_sp numeric, created_at timestamp without time zone, misses integer, mods json, passed boolean, "replayHwid" text, "songId" text, speed numeric, spin boolean, "userId" bigint, username text, avatar_url text, accuracy numeric)
3
- LANGUAGE sql
4
- STABLE
5
- AS $function$
6
- WITH bm AS (
7
- SELECT "noteCount"
8
- FROM beatmaps
9
- WHERE "beatmapHash" = beatmap_hash
10
- )
11
- SELECT
12
- s.id,
13
- s.awarded_sp,
14
- s.created_at,
15
- s.misses,
16
- s.mods,
17
- s.passed,
18
- s."replayHwid",
19
- s."songId",
20
- s.speed,
21
- s.spin,
22
- s."userId",
23
- p.username,
24
- p.avatar_url,
25
- CASE
26
- WHEN bm."noteCount" > 0 THEN
27
- ROUND(((bm."noteCount" - s.misses) / bm."noteCount" * 100)::numeric, 2)
28
- ELSE 0
29
- END AS accuracy
30
- FROM leaderboard_map_user l
31
- JOIN scores s ON s.id = l.best_score_id
32
- JOIN profiles p ON s."userId" = p.id
33
- CROSS JOIN bm
34
- WHERE l."beatmapHash" = beatmap_hash
35
- AND p.ban <> 'excluded'
36
- ORDER BY l.best_sp DESC
37
- LIMIT 10;
38
- $function$
1
+ DROP FUNCTION IF EXISTS public.get_top_scores_for_beatmap3(beatmap_hash text);
2
+
3
+ CREATE OR REPLACE FUNCTION public.get_top_scores_for_beatmap3(beatmap_hash text, score_limit integer DEFAULT 50)
4
+ RETURNS TABLE(id bigint, awarded_sp numeric, created_at timestamp without time zone, misses integer, mods json, passed boolean, "replayHwid" text, "songId" text, speed numeric, spin boolean, "userId" bigint, username text, avatar_url text, accuracy numeric)
5
+ LANGUAGE sql
6
+ STABLE
7
+ AS $function$
8
+ WITH bm AS (
9
+ SELECT "noteCount"
10
+ FROM beatmaps
11
+ WHERE "beatmapHash" = beatmap_hash
12
+ )
13
+ SELECT
14
+ s.id,
15
+ s.awarded_sp,
16
+ s.created_at,
17
+ s.misses,
18
+ s.mods,
19
+ s.passed,
20
+ s."replayHwid",
21
+ s."songId",
22
+ s.speed,
23
+ s.spin,
24
+ s."userId",
25
+ p.username,
26
+ p.avatar_url,
27
+ CASE
28
+ WHEN bm."noteCount" > 0 THEN
29
+ ROUND(((bm."noteCount" - s.misses) / bm."noteCount" * 100)::numeric, 2)
30
+ ELSE 0
31
+ END AS accuracy
32
+ FROM leaderboard_map_user l
33
+ JOIN scores s ON s.id = l.best_score_id
34
+ JOIN profiles p ON s."userId" = p.id
35
+ CROSS JOIN bm
36
+ WHERE l."beatmapHash" = beatmap_hash
37
+ AND s.passed = true
38
+ AND p.ban <> 'excluded'
39
+ AND EXISTS (
40
+ SELECT 1
41
+ FROM scores recent
42
+ WHERE recent."userId" = s."userId"
43
+ AND recent.created_at >= now() - interval '30 days'
44
+ )
45
+ ORDER BY l.best_sp DESC
46
+ LIMIT LEAST(GREATEST(COALESCE(score_limit, 50), 1), 50);
47
+ $function$
@@ -0,0 +1,66 @@
1
+ CREATE OR REPLACE FUNCTION public.handle_profile_flag_change()
2
+ RETURNS trigger
3
+ LANGUAGE plpgsql
4
+ AS $function$
5
+ begin
6
+ if new.flag is not distinct from old.flag then
7
+ return new;
8
+ end if;
9
+
10
+ if current_setting('app.bypass_profile_limits', true) = 'on' then
11
+ return new;
12
+ end if;
13
+
14
+ if exists (
15
+ select 1
16
+ from public."profileFlags" pf
17
+ where pf.profile_id = old.id
18
+ ) then
19
+ raise exception 'Flag can only be changed once'
20
+ using errcode = 'P0001';
21
+ end if;
22
+
23
+ insert into public."profileFlags" (profile_id, flag, changed_at)
24
+ values (old.id, old.flag, now());
25
+
26
+ return new;
27
+ end;
28
+ $function$;
29
+
30
+ CREATE OR REPLACE FUNCTION public.handle_profile_username_change()
31
+ RETURNS trigger
32
+ LANGUAGE plpgsql
33
+ AS $function$
34
+ declare
35
+ latest_change_at timestamptz;
36
+ begin
37
+ if new.username is not distinct from old.username then
38
+ return new;
39
+ end if;
40
+
41
+ if current_setting('app.bypass_profile_limits', true) = 'on' then
42
+ return new;
43
+ end if;
44
+
45
+ select pu.changed_at
46
+ into latest_change_at
47
+ from public."profileUsernames" pu
48
+ where pu.profile_id = old.id
49
+ order by pu.changed_at desc
50
+ limit 1;
51
+
52
+ if latest_change_at is not null
53
+ and latest_change_at + interval '6 months' > now() then
54
+ raise exception 'Username can only be changed once every 6 months'
55
+ using errcode = 'P0001',
56
+ detail = 'next_username_change_at=' || (latest_change_at + interval '6 months');
57
+ end if;
58
+
59
+ if old.username is not null and btrim(old.username) <> '' then
60
+ insert into public."profileUsernames" (profile_id, username, changed_at)
61
+ values (old.id, old.username, now());
62
+ end if;
63
+
64
+ return new;
65
+ end;
66
+ $function$;