rhythia-api 233.0.0 → 235.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.
- package/.codex +0 -0
- package/.env +1 -12
- package/README.md +4 -4
- package/api/acceptInvite.ts +1 -1
- package/api/addCollectionMap.ts +1 -1
- package/api/chartPublicStats.ts +1 -1
- package/api/checkQualified.ts +93 -93
- package/api/createBeatmap.ts +53 -62
- package/api/createBeatmapPage.ts +1 -1
- package/api/createClan.ts +1 -1
- package/api/createCollection.ts +1 -1
- package/api/createInvite.ts +1 -1
- package/api/createSupporter.ts +1 -1
- package/api/deleteBeatmapPage.ts +2 -5
- package/api/deleteCollection.ts +1 -1
- package/api/deleteCollectionMap.ts +1 -1
- package/api/editAboutMe.ts +1 -1
- package/api/editClan.ts +1 -1
- package/api/editCollection.ts +1 -2
- package/api/editProfile.ts +1 -1
- package/api/enhancedSearch.ts +113 -113
- package/api/executeAdminOperation.ts +1 -22
- package/api/getAvatarUploadUrl.ts +1 -1
- package/api/getBadgeLeaders.ts +1 -1
- package/api/getBadgedUsers.ts +1 -1
- package/api/getBeatmapComments.ts +1 -1
- package/api/getBeatmapPage.ts +74 -106
- package/api/getBeatmapPageById.ts +70 -109
- package/api/getBeatmapStarRating.ts +1 -1
- package/api/getBeatmaps.ts +123 -93
- package/api/getClan.ts +1 -1
- package/api/getClans.ts +1 -1
- package/api/getCollection.ts +1 -1
- package/api/getCollections.ts +1 -1
- package/api/getInventory.ts +1 -1
- package/api/getLeaderboard.ts +1 -1
- package/api/getMapUploadUrl.ts +2 -2
- package/api/getOnlinePlayers.ts +1 -1
- package/api/getPassToken.ts +1 -1
- package/api/getProfile.ts +51 -31
- package/api/getPublicStats.ts +5 -5
- package/api/getRawStarRating.ts +1 -1
- package/api/getScore.ts +1 -1
- package/api/getStoryBeatmaps.ts +1 -1
- package/api/getTimestamp.ts +1 -1
- package/api/getUserScores.ts +19 -19
- package/api/getVerified.ts +1 -1
- package/api/getVideoUploadUrl.ts +1 -1
- package/api/postBeatmapComment.ts +1 -1
- package/api/qualifyMap.ts +97 -92
- package/api/rankMapsArchive.ts +20 -20
- package/api/searchUsers.ts +1 -1
- package/api/setPasskey.ts +1 -1
- package/api/submitScore.ts +1 -6
- package/api/submitScoreInternal.ts +461 -449
- package/api/updateBeatmapPage.ts +1 -1
- package/api/vetoMap.ts +101 -101
- package/index.ts +180 -167
- package/package.json +7 -12
- package/queries/admin_delete_user.sql +39 -39
- package/queries/admin_exclude_user.sql +21 -21
- package/queries/admin_invalidate_ranked_scores.sql +18 -18
- package/queries/admin_log_action.sql +10 -10
- package/queries/admin_profanity_clear.sql +29 -29
- package/queries/admin_remove_all_scores.sql +29 -29
- package/queries/admin_restrict_user.sql +21 -21
- package/queries/admin_search_users.sql +24 -24
- package/queries/admin_silence_user.sql +21 -21
- package/queries/admin_unban_user.sql +21 -21
- package/queries/enhanced_search.sql +217 -217
- package/queries/get_badge_leaderboard.sql +50 -50
- package/queries/get_clan_leaderboard.sql +68 -68
- package/queries/get_collections_v4.sql +109 -109
- package/queries/get_top_scores_for_beatmap.sql +44 -44
- package/queries/get_top_scores_for_beatmap3.sql +38 -0
- package/queries/get_user_by_email.sql +32 -32
- package/queries/get_user_scores_lastday.sql +47 -47
- package/queries/get_user_scores_reign.sql +31 -31
- package/queries/get_user_scores_top_and_stats.sql +84 -84
- package/queries/grant_special_badges.sql +69 -69
- package/types/database.ts +1288 -1248
- package/utils/beatmapTopScores.ts +84 -0
- package/utils/mapLifecycleWebhook.ts +287 -277
- package/utils/requestGeo.ts +13 -0
- package/utils/requestUtils.ts +127 -127
- package/utils/response.ts +11 -0
- package/worker.ts +189 -0
- package/wrangler.jsonc +10 -0
- package/index.html +0 -3
- package/vercel.json +0 -13
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
CREATE OR REPLACE FUNCTION public.get_user_scores_reign(userid integer)
|
|
2
|
-
RETURNS jsonb
|
|
3
|
-
LANGUAGE sql
|
|
4
|
-
AS $function$
|
|
5
|
-
with r as (
|
|
6
|
-
select * from public.get_user_reigning_scores(userid, 10)
|
|
7
|
-
)
|
|
8
|
-
select coalesce(
|
|
9
|
-
jsonb_agg(
|
|
10
|
-
jsonb_build_object(
|
|
11
|
-
'id', r.id,
|
|
12
|
-
'awarded_sp', r.awarded_sp,
|
|
13
|
-
'created_at', r.created_at,
|
|
14
|
-
'misses', r.misses,
|
|
15
|
-
'mods', r.mods,
|
|
16
|
-
'passed', r.passed,
|
|
17
|
-
'replay_url', s.replay_url,
|
|
18
|
-
'songId', r.songid,
|
|
19
|
-
'speed', r.speed,
|
|
20
|
-
'spin', r.spin,
|
|
21
|
-
'beatmapHash', r.beatmaphash,
|
|
22
|
-
'beatmapTitle', r.beatmaptitle,
|
|
23
|
-
'beatmapNotes', r.notes,
|
|
24
|
-
'difficulty', r.difficulty
|
|
25
|
-
)
|
|
26
|
-
),
|
|
27
|
-
'[]'::jsonb
|
|
28
|
-
)
|
|
29
|
-
from r
|
|
30
|
-
left join public.scores s on s.id = r.id;
|
|
31
|
-
$function$
|
|
1
|
+
CREATE OR REPLACE FUNCTION public.get_user_scores_reign(userid integer)
|
|
2
|
+
RETURNS jsonb
|
|
3
|
+
LANGUAGE sql
|
|
4
|
+
AS $function$
|
|
5
|
+
with r as (
|
|
6
|
+
select * from public.get_user_reigning_scores(userid, 10)
|
|
7
|
+
)
|
|
8
|
+
select coalesce(
|
|
9
|
+
jsonb_agg(
|
|
10
|
+
jsonb_build_object(
|
|
11
|
+
'id', r.id,
|
|
12
|
+
'awarded_sp', r.awarded_sp,
|
|
13
|
+
'created_at', r.created_at,
|
|
14
|
+
'misses', r.misses,
|
|
15
|
+
'mods', r.mods,
|
|
16
|
+
'passed', r.passed,
|
|
17
|
+
'replay_url', s.replay_url,
|
|
18
|
+
'songId', r.songid,
|
|
19
|
+
'speed', r.speed,
|
|
20
|
+
'spin', r.spin,
|
|
21
|
+
'beatmapHash', r.beatmaphash,
|
|
22
|
+
'beatmapTitle', r.beatmaptitle,
|
|
23
|
+
'beatmapNotes', r.notes,
|
|
24
|
+
'difficulty', r.difficulty
|
|
25
|
+
)
|
|
26
|
+
),
|
|
27
|
+
'[]'::jsonb
|
|
28
|
+
)
|
|
29
|
+
from r
|
|
30
|
+
left join public.scores s on s.id = r.id;
|
|
31
|
+
$function$
|
|
@@ -1,84 +1,84 @@
|
|
|
1
|
-
CREATE OR REPLACE FUNCTION public.get_user_scores_top_and_stats(userid integer, limit_param integer)
|
|
2
|
-
RETURNS jsonb
|
|
3
|
-
LANGUAGE sql
|
|
4
|
-
AS $function$
|
|
5
|
-
with filtered as materialized (
|
|
6
|
-
select
|
|
7
|
-
s.created_at,
|
|
8
|
-
s.id,
|
|
9
|
-
s.passed,
|
|
10
|
-
s."userId",
|
|
11
|
-
s.awarded_sp,
|
|
12
|
-
s."beatmapHash",
|
|
13
|
-
s.misses,
|
|
14
|
-
s.replay_url,
|
|
15
|
-
s."songId",
|
|
16
|
-
s.speed,
|
|
17
|
-
s.spin
|
|
18
|
-
from public.scores s
|
|
19
|
-
where s."userId" = userid
|
|
20
|
-
and s.passed = true
|
|
21
|
-
and s.awarded_sp is not null
|
|
22
|
-
and s.awarded_sp <> 0
|
|
23
|
-
),
|
|
24
|
-
stats as (
|
|
25
|
-
select
|
|
26
|
-
count(*)::int as total_scores,
|
|
27
|
-
count(*) filter (where spin = true)::int as spin_scores
|
|
28
|
-
from filtered
|
|
29
|
-
),
|
|
30
|
-
top_rows as (
|
|
31
|
-
select *
|
|
32
|
-
from (
|
|
33
|
-
select distinct on (f."beatmapHash")
|
|
34
|
-
f.created_at,
|
|
35
|
-
f.id,
|
|
36
|
-
f.passed,
|
|
37
|
-
f."userId",
|
|
38
|
-
f.awarded_sp,
|
|
39
|
-
f."beatmapHash",
|
|
40
|
-
f.misses,
|
|
41
|
-
f.replay_url,
|
|
42
|
-
f."songId",
|
|
43
|
-
f.speed,
|
|
44
|
-
f.spin
|
|
45
|
-
from filtered f
|
|
46
|
-
order by f."beatmapHash", f.awarded_sp desc
|
|
47
|
-
) best_per_hash
|
|
48
|
-
order by awarded_sp desc
|
|
49
|
-
limit least(coalesce(limit_param, 100), 100)
|
|
50
|
-
),
|
|
51
|
-
top_json as (
|
|
52
|
-
select coalesce(
|
|
53
|
-
jsonb_agg(
|
|
54
|
-
jsonb_build_object(
|
|
55
|
-
'created_at', t.created_at,
|
|
56
|
-
'id', t.id,
|
|
57
|
-
'passed', t.passed,
|
|
58
|
-
'userId', t."userId",
|
|
59
|
-
'awarded_sp', t.awarded_sp,
|
|
60
|
-
'beatmapHash', t."beatmapHash",
|
|
61
|
-
'misses', t.misses,
|
|
62
|
-
'replay_url', t.replay_url,
|
|
63
|
-
'rank', null, -- keep key for Zod compatibility
|
|
64
|
-
'songId', t."songId",
|
|
65
|
-
'beatmapDifficulty', b.difficulty,
|
|
66
|
-
'beatmapNotes', b."noteCount",
|
|
67
|
-
'beatmapTitle', b.title,
|
|
68
|
-
'speed', t.speed,
|
|
69
|
-
'spin', t.spin
|
|
70
|
-
)
|
|
71
|
-
),
|
|
72
|
-
'[]'::jsonb
|
|
73
|
-
) as value
|
|
74
|
-
from top_rows t
|
|
75
|
-
left join public.beatmaps b on b."beatmapHash" = t."beatmapHash"
|
|
76
|
-
)
|
|
77
|
-
select jsonb_build_object(
|
|
78
|
-
'top', (select value from top_json),
|
|
79
|
-
'stats', jsonb_build_object(
|
|
80
|
-
'totalScores', coalesce((select total_scores from stats), 0),
|
|
81
|
-
'spinScores', coalesce((select spin_scores from stats), 0)
|
|
82
|
-
)
|
|
83
|
-
);
|
|
84
|
-
$function$
|
|
1
|
+
CREATE OR REPLACE FUNCTION public.get_user_scores_top_and_stats(userid integer, limit_param integer)
|
|
2
|
+
RETURNS jsonb
|
|
3
|
+
LANGUAGE sql
|
|
4
|
+
AS $function$
|
|
5
|
+
with filtered as materialized (
|
|
6
|
+
select
|
|
7
|
+
s.created_at,
|
|
8
|
+
s.id,
|
|
9
|
+
s.passed,
|
|
10
|
+
s."userId",
|
|
11
|
+
s.awarded_sp,
|
|
12
|
+
s."beatmapHash",
|
|
13
|
+
s.misses,
|
|
14
|
+
s.replay_url,
|
|
15
|
+
s."songId",
|
|
16
|
+
s.speed,
|
|
17
|
+
s.spin
|
|
18
|
+
from public.scores s
|
|
19
|
+
where s."userId" = userid
|
|
20
|
+
and s.passed = true
|
|
21
|
+
and s.awarded_sp is not null
|
|
22
|
+
and s.awarded_sp <> 0
|
|
23
|
+
),
|
|
24
|
+
stats as (
|
|
25
|
+
select
|
|
26
|
+
count(*)::int as total_scores,
|
|
27
|
+
count(*) filter (where spin = true)::int as spin_scores
|
|
28
|
+
from filtered
|
|
29
|
+
),
|
|
30
|
+
top_rows as (
|
|
31
|
+
select *
|
|
32
|
+
from (
|
|
33
|
+
select distinct on (f."beatmapHash")
|
|
34
|
+
f.created_at,
|
|
35
|
+
f.id,
|
|
36
|
+
f.passed,
|
|
37
|
+
f."userId",
|
|
38
|
+
f.awarded_sp,
|
|
39
|
+
f."beatmapHash",
|
|
40
|
+
f.misses,
|
|
41
|
+
f.replay_url,
|
|
42
|
+
f."songId",
|
|
43
|
+
f.speed,
|
|
44
|
+
f.spin
|
|
45
|
+
from filtered f
|
|
46
|
+
order by f."beatmapHash", f.awarded_sp desc
|
|
47
|
+
) best_per_hash
|
|
48
|
+
order by awarded_sp desc
|
|
49
|
+
limit least(coalesce(limit_param, 100), 100)
|
|
50
|
+
),
|
|
51
|
+
top_json as (
|
|
52
|
+
select coalesce(
|
|
53
|
+
jsonb_agg(
|
|
54
|
+
jsonb_build_object(
|
|
55
|
+
'created_at', t.created_at,
|
|
56
|
+
'id', t.id,
|
|
57
|
+
'passed', t.passed,
|
|
58
|
+
'userId', t."userId",
|
|
59
|
+
'awarded_sp', t.awarded_sp,
|
|
60
|
+
'beatmapHash', t."beatmapHash",
|
|
61
|
+
'misses', t.misses,
|
|
62
|
+
'replay_url', t.replay_url,
|
|
63
|
+
'rank', null, -- keep key for Zod compatibility
|
|
64
|
+
'songId', t."songId",
|
|
65
|
+
'beatmapDifficulty', b.difficulty,
|
|
66
|
+
'beatmapNotes', b."noteCount",
|
|
67
|
+
'beatmapTitle', b.title,
|
|
68
|
+
'speed', t.speed,
|
|
69
|
+
'spin', t.spin
|
|
70
|
+
)
|
|
71
|
+
),
|
|
72
|
+
'[]'::jsonb
|
|
73
|
+
) as value
|
|
74
|
+
from top_rows t
|
|
75
|
+
left join public.beatmaps b on b."beatmapHash" = t."beatmapHash"
|
|
76
|
+
)
|
|
77
|
+
select jsonb_build_object(
|
|
78
|
+
'top', (select value from top_json),
|
|
79
|
+
'stats', jsonb_build_object(
|
|
80
|
+
'totalScores', coalesce((select total_scores from stats), 0),
|
|
81
|
+
'spinScores', coalesce((select spin_scores from stats), 0)
|
|
82
|
+
)
|
|
83
|
+
);
|
|
84
|
+
$function$
|
|
@@ -1,69 +1,69 @@
|
|
|
1
|
-
CREATE OR REPLACE FUNCTION public.grant_special_badges(p_user_id integer, p_beatmap_id integer, p_spin boolean DEFAULT false, p_passed boolean DEFAULT true)
|
|
2
|
-
RETURNS json
|
|
3
|
-
LANGUAGE plpgsql
|
|
4
|
-
SECURITY DEFINER
|
|
5
|
-
AS $function$
|
|
6
|
-
DECLARE
|
|
7
|
-
badge_granted JSON := '{"granted": false, "badge": null}';
|
|
8
|
-
current_badges JSONB;
|
|
9
|
-
new_badge_name TEXT := NULL;
|
|
10
|
-
BEGIN
|
|
11
|
-
-- Only proceed if the score was a pass
|
|
12
|
-
IF NOT p_passed THEN
|
|
13
|
-
RETURN badge_granted;
|
|
14
|
-
END IF;
|
|
15
|
-
|
|
16
|
-
-- Get current badges from user profile (cast to JSONB)
|
|
17
|
-
SELECT COALESCE(badges::jsonb, '[]'::jsonb) INTO current_badges
|
|
18
|
-
FROM profiles
|
|
19
|
-
WHERE id = p_user_id;
|
|
20
|
-
|
|
21
|
-
-- Check each special map and determine badge to grant
|
|
22
|
-
CASE p_beatmap_id
|
|
23
|
-
WHEN 5368 THEN
|
|
24
|
-
new_badge_name := 'The Start of an Era';
|
|
25
|
-
WHEN 6178 THEN
|
|
26
|
-
new_badge_name := 'New Farm';
|
|
27
|
-
WHEN 4701 THEN
|
|
28
|
-
-- Spinnin (only if using spin gamestyle)
|
|
29
|
-
IF p_spin THEN
|
|
30
|
-
new_badge_name := 'Spinnin';
|
|
31
|
-
END IF;
|
|
32
|
-
WHEN 5216 THEN
|
|
33
|
-
new_badge_name := 'Old Farm';
|
|
34
|
-
WHEN 4305 THEN
|
|
35
|
-
new_badge_name := 'Birb';
|
|
36
|
-
WHEN 5094 THEN
|
|
37
|
-
new_badge_name := 'Flamingos';
|
|
38
|
-
WHEN 4285 THEN
|
|
39
|
-
new_badge_name := 'Cats!';
|
|
40
|
-
ELSE
|
|
41
|
-
-- No special badge for this beatmap
|
|
42
|
-
RETURN badge_granted;
|
|
43
|
-
END CASE;
|
|
44
|
-
|
|
45
|
-
-- If no badge should be granted (e.g., spin condition not met), return
|
|
46
|
-
IF new_badge_name IS NULL THEN
|
|
47
|
-
RETURN badge_granted;
|
|
48
|
-
END IF;
|
|
49
|
-
|
|
50
|
-
-- Check if user already has this badge
|
|
51
|
-
IF current_badges ? new_badge_name THEN
|
|
52
|
-
-- Badge already exists (using JSONB containment operator)
|
|
53
|
-
RETURN badge_granted;
|
|
54
|
-
END IF;
|
|
55
|
-
|
|
56
|
-
-- Append new badge to the existing array (both operands are JSONB now)
|
|
57
|
-
UPDATE profiles
|
|
58
|
-
SET badges = (badges::jsonb || jsonb_build_array(new_badge_name))::json
|
|
59
|
-
WHERE id = p_user_id;
|
|
60
|
-
|
|
61
|
-
-- Return success response
|
|
62
|
-
badge_granted := json_build_object(
|
|
63
|
-
'granted', true,
|
|
64
|
-
'badge', new_badge_name
|
|
65
|
-
);
|
|
66
|
-
|
|
67
|
-
RETURN badge_granted;
|
|
68
|
-
END;
|
|
69
|
-
$function$
|
|
1
|
+
CREATE OR REPLACE FUNCTION public.grant_special_badges(p_user_id integer, p_beatmap_id integer, p_spin boolean DEFAULT false, p_passed boolean DEFAULT true)
|
|
2
|
+
RETURNS json
|
|
3
|
+
LANGUAGE plpgsql
|
|
4
|
+
SECURITY DEFINER
|
|
5
|
+
AS $function$
|
|
6
|
+
DECLARE
|
|
7
|
+
badge_granted JSON := '{"granted": false, "badge": null}';
|
|
8
|
+
current_badges JSONB;
|
|
9
|
+
new_badge_name TEXT := NULL;
|
|
10
|
+
BEGIN
|
|
11
|
+
-- Only proceed if the score was a pass
|
|
12
|
+
IF NOT p_passed THEN
|
|
13
|
+
RETURN badge_granted;
|
|
14
|
+
END IF;
|
|
15
|
+
|
|
16
|
+
-- Get current badges from user profile (cast to JSONB)
|
|
17
|
+
SELECT COALESCE(badges::jsonb, '[]'::jsonb) INTO current_badges
|
|
18
|
+
FROM profiles
|
|
19
|
+
WHERE id = p_user_id;
|
|
20
|
+
|
|
21
|
+
-- Check each special map and determine badge to grant
|
|
22
|
+
CASE p_beatmap_id
|
|
23
|
+
WHEN 5368 THEN
|
|
24
|
+
new_badge_name := 'The Start of an Era';
|
|
25
|
+
WHEN 6178 THEN
|
|
26
|
+
new_badge_name := 'New Farm';
|
|
27
|
+
WHEN 4701 THEN
|
|
28
|
+
-- Spinnin (only if using spin gamestyle)
|
|
29
|
+
IF p_spin THEN
|
|
30
|
+
new_badge_name := 'Spinnin';
|
|
31
|
+
END IF;
|
|
32
|
+
WHEN 5216 THEN
|
|
33
|
+
new_badge_name := 'Old Farm';
|
|
34
|
+
WHEN 4305 THEN
|
|
35
|
+
new_badge_name := 'Birb';
|
|
36
|
+
WHEN 5094 THEN
|
|
37
|
+
new_badge_name := 'Flamingos';
|
|
38
|
+
WHEN 4285 THEN
|
|
39
|
+
new_badge_name := 'Cats!';
|
|
40
|
+
ELSE
|
|
41
|
+
-- No special badge for this beatmap
|
|
42
|
+
RETURN badge_granted;
|
|
43
|
+
END CASE;
|
|
44
|
+
|
|
45
|
+
-- If no badge should be granted (e.g., spin condition not met), return
|
|
46
|
+
IF new_badge_name IS NULL THEN
|
|
47
|
+
RETURN badge_granted;
|
|
48
|
+
END IF;
|
|
49
|
+
|
|
50
|
+
-- Check if user already has this badge
|
|
51
|
+
IF current_badges ? new_badge_name THEN
|
|
52
|
+
-- Badge already exists (using JSONB containment operator)
|
|
53
|
+
RETURN badge_granted;
|
|
54
|
+
END IF;
|
|
55
|
+
|
|
56
|
+
-- Append new badge to the existing array (both operands are JSONB now)
|
|
57
|
+
UPDATE profiles
|
|
58
|
+
SET badges = (badges::jsonb || jsonb_build_array(new_badge_name))::json
|
|
59
|
+
WHERE id = p_user_id;
|
|
60
|
+
|
|
61
|
+
-- Return success response
|
|
62
|
+
badge_granted := json_build_object(
|
|
63
|
+
'granted', true,
|
|
64
|
+
'badge', new_badge_name
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
RETURN badge_granted;
|
|
68
|
+
END;
|
|
69
|
+
$function$
|