rhythia-api 233.0.0 → 234.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.
Files changed (90) hide show
  1. package/.codex +0 -0
  2. package/.env +1 -12
  3. package/README.md +4 -4
  4. package/api/acceptInvite.ts +1 -1
  5. package/api/addCollectionMap.ts +1 -1
  6. package/api/chartPublicStats.ts +1 -1
  7. package/api/checkQualified.ts +93 -93
  8. package/api/createBeatmap.ts +53 -62
  9. package/api/createBeatmapPage.ts +1 -1
  10. package/api/createClan.ts +1 -1
  11. package/api/createCollection.ts +1 -1
  12. package/api/createInvite.ts +1 -1
  13. package/api/createSupporter.ts +1 -1
  14. package/api/deleteBeatmapPage.ts +2 -5
  15. package/api/deleteCollection.ts +1 -1
  16. package/api/deleteCollectionMap.ts +1 -1
  17. package/api/editAboutMe.ts +1 -1
  18. package/api/editClan.ts +1 -1
  19. package/api/editCollection.ts +1 -2
  20. package/api/editProfile.ts +1 -1
  21. package/api/enhancedSearch.ts +113 -113
  22. package/api/executeAdminOperation.ts +1 -22
  23. package/api/getAvatarUploadUrl.ts +1 -1
  24. package/api/getBadgeLeaders.ts +1 -1
  25. package/api/getBadgedUsers.ts +1 -1
  26. package/api/getBeatmapComments.ts +1 -1
  27. package/api/getBeatmapPage.ts +74 -106
  28. package/api/getBeatmapPageById.ts +70 -109
  29. package/api/getBeatmapStarRating.ts +1 -1
  30. package/api/getBeatmaps.ts +1 -1
  31. package/api/getClan.ts +1 -1
  32. package/api/getClans.ts +1 -1
  33. package/api/getCollection.ts +1 -1
  34. package/api/getCollections.ts +1 -1
  35. package/api/getInventory.ts +1 -1
  36. package/api/getLeaderboard.ts +1 -1
  37. package/api/getMapUploadUrl.ts +2 -2
  38. package/api/getOnlinePlayers.ts +1 -1
  39. package/api/getPassToken.ts +1 -1
  40. package/api/getProfile.ts +51 -31
  41. package/api/getPublicStats.ts +5 -5
  42. package/api/getRawStarRating.ts +1 -1
  43. package/api/getScore.ts +1 -1
  44. package/api/getStoryBeatmaps.ts +1 -1
  45. package/api/getTimestamp.ts +1 -1
  46. package/api/getUserScores.ts +19 -19
  47. package/api/getVerified.ts +1 -1
  48. package/api/getVideoUploadUrl.ts +1 -1
  49. package/api/postBeatmapComment.ts +1 -1
  50. package/api/qualifyMap.ts +97 -92
  51. package/api/rankMapsArchive.ts +20 -20
  52. package/api/searchUsers.ts +1 -1
  53. package/api/setPasskey.ts +1 -1
  54. package/api/submitScore.ts +1 -6
  55. package/api/submitScoreInternal.ts +461 -449
  56. package/api/updateBeatmapPage.ts +1 -1
  57. package/api/vetoMap.ts +101 -101
  58. package/index.ts +165 -153
  59. package/package.json +7 -12
  60. package/queries/admin_delete_user.sql +39 -39
  61. package/queries/admin_exclude_user.sql +21 -21
  62. package/queries/admin_invalidate_ranked_scores.sql +18 -18
  63. package/queries/admin_log_action.sql +10 -10
  64. package/queries/admin_profanity_clear.sql +29 -29
  65. package/queries/admin_remove_all_scores.sql +29 -29
  66. package/queries/admin_restrict_user.sql +21 -21
  67. package/queries/admin_search_users.sql +24 -24
  68. package/queries/admin_silence_user.sql +21 -21
  69. package/queries/admin_unban_user.sql +21 -21
  70. package/queries/enhanced_search.sql +217 -217
  71. package/queries/get_badge_leaderboard.sql +50 -50
  72. package/queries/get_clan_leaderboard.sql +68 -68
  73. package/queries/get_collections_v4.sql +109 -109
  74. package/queries/get_top_scores_for_beatmap.sql +44 -44
  75. package/queries/get_top_scores_for_beatmap3.sql +38 -0
  76. package/queries/get_user_by_email.sql +32 -32
  77. package/queries/get_user_scores_lastday.sql +47 -47
  78. package/queries/get_user_scores_reign.sql +31 -31
  79. package/queries/get_user_scores_top_and_stats.sql +84 -84
  80. package/queries/grant_special_badges.sql +69 -69
  81. package/types/database.ts +1288 -1248
  82. package/utils/beatmapTopScores.ts +84 -0
  83. package/utils/mapLifecycleWebhook.ts +287 -277
  84. package/utils/requestGeo.ts +13 -0
  85. package/utils/requestUtils.ts +127 -127
  86. package/utils/response.ts +11 -0
  87. package/worker.ts +189 -0
  88. package/wrangler.jsonc +10 -0
  89. package/index.html +0 -3
  90. 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$