rhythia-api 231.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.
- 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 -83
- 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 -0
- 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 +1 -1
- 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 -86
- package/api/rankMapsArchive.ts +8 -1
- 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 -94
- package/index.ts +173 -120
- 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 -0
- 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 -1224
- package/utils/beatmapTopScores.ts +84 -0
- package/utils/mapLifecycleWebhook.ts +287 -0
- 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
package/api/getPublicStats.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { NextResponse } from "
|
|
1
|
+
import { NextResponse } from "../utils/response";
|
|
2
2
|
import z from "zod";
|
|
3
3
|
import { protectedApi } from "../utils/requestUtils";
|
|
4
4
|
import { supabase } from "../utils/supabase";
|
|
@@ -69,11 +69,11 @@ export async function POST(request: Request) {
|
|
|
69
69
|
export async function handler(data: (typeof Schema)["input"]["_type"]) {
|
|
70
70
|
const countProfilesQuery = await supabase
|
|
71
71
|
.from("profiles")
|
|
72
|
-
.select("*", { count: "
|
|
72
|
+
.select("*", { count: "estimated", head: true });
|
|
73
73
|
|
|
74
74
|
const countBeatmapsQuery = await supabase
|
|
75
75
|
.from("beatmaps")
|
|
76
|
-
.select("*", { count: "
|
|
76
|
+
.select("*", { count: "estimated", head: true });
|
|
77
77
|
|
|
78
78
|
let { data: beatmapPage, error: errorlast } = await supabase
|
|
79
79
|
.from("beatmapPages")
|
|
@@ -128,12 +128,12 @@ export async function handler(data: (typeof Schema)["input"]["_type"]) {
|
|
|
128
128
|
|
|
129
129
|
const countScoresQuery = await supabase
|
|
130
130
|
.from("scores")
|
|
131
|
-
.select("id", { count: "
|
|
131
|
+
.select("id", { count: "estimated", head: true });
|
|
132
132
|
|
|
133
133
|
// 30 minutes activity
|
|
134
134
|
const countOnline = await supabase
|
|
135
135
|
.from("profileActivities")
|
|
136
|
-
.select("*", { count: "
|
|
136
|
+
.select("*", { count: "estimated", head: true })
|
|
137
137
|
.gt("last_activity", Date.now() - 1800000);
|
|
138
138
|
|
|
139
139
|
const countChart = await supabase
|
package/api/getRawStarRating.ts
CHANGED
package/api/getScore.ts
CHANGED
package/api/getStoryBeatmaps.ts
CHANGED
package/api/getTimestamp.ts
CHANGED
package/api/getUserScores.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { NextResponse } from "
|
|
1
|
+
import { NextResponse } from "../utils/response";
|
|
2
2
|
import z from "zod";
|
|
3
3
|
import { getCacheValue, setCacheValue } from "../utils/cache";
|
|
4
4
|
import { protectedApi } from "../utils/requestUtils";
|
|
@@ -18,12 +18,12 @@ export const Schema = {
|
|
|
18
18
|
awarded_sp: z.number().nullable(),
|
|
19
19
|
beatmapHash: z.string().nullable(),
|
|
20
20
|
created_at: z.string(),
|
|
21
|
-
id: z.number(),
|
|
22
|
-
misses: z.number().nullable(),
|
|
23
|
-
passed: z.boolean().nullable(),
|
|
24
|
-
replay_url: z.string().nullable().optional(),
|
|
25
|
-
songId: z.string().nullable(),
|
|
26
|
-
userId: z.number().nullable(),
|
|
21
|
+
id: z.number(),
|
|
22
|
+
misses: z.number().nullable(),
|
|
23
|
+
passed: z.boolean().nullable(),
|
|
24
|
+
replay_url: z.string().nullable().optional(),
|
|
25
|
+
songId: z.string().nullable(),
|
|
26
|
+
userId: z.number().nullable(),
|
|
27
27
|
beatmapDifficulty: z.number().optional().nullable(),
|
|
28
28
|
beatmapNotes: z.number().optional().nullable(),
|
|
29
29
|
beatmapTitle: z.string().optional().nullable(),
|
|
@@ -38,12 +38,12 @@ export const Schema = {
|
|
|
38
38
|
id: z.number(),
|
|
39
39
|
awarded_sp: z.number().nullable(),
|
|
40
40
|
created_at: z.string(),
|
|
41
|
-
misses: z.number().nullable(),
|
|
42
|
-
mods: z.record(z.unknown()),
|
|
43
|
-
passed: z.boolean().nullable(),
|
|
44
|
-
replay_url: z.string().nullable().optional(),
|
|
45
|
-
songId: z.string().nullable(),
|
|
46
|
-
speed: z.number().nullable(),
|
|
41
|
+
misses: z.number().nullable(),
|
|
42
|
+
mods: z.record(z.unknown()),
|
|
43
|
+
passed: z.boolean().nullable(),
|
|
44
|
+
replay_url: z.string().nullable().optional(),
|
|
45
|
+
songId: z.string().nullable(),
|
|
46
|
+
speed: z.number().nullable(),
|
|
47
47
|
spin: z.boolean(),
|
|
48
48
|
beatmapHash: z.string().nullable(),
|
|
49
49
|
beatmapTitle: z.string().nullable(),
|
|
@@ -58,12 +58,12 @@ export const Schema = {
|
|
|
58
58
|
awarded_sp: z.number().nullable(),
|
|
59
59
|
beatmapHash: z.string().nullable(),
|
|
60
60
|
created_at: z.string(),
|
|
61
|
-
id: z.number(),
|
|
62
|
-
misses: z.number().nullable(),
|
|
63
|
-
passed: z.boolean().nullable(),
|
|
64
|
-
replay_url: z.string().nullable().optional(),
|
|
65
|
-
rank: z.string().nullable(),
|
|
66
|
-
songId: z.string().nullable(),
|
|
61
|
+
id: z.number(),
|
|
62
|
+
misses: z.number().nullable(),
|
|
63
|
+
passed: z.boolean().nullable(),
|
|
64
|
+
replay_url: z.string().nullable().optional(),
|
|
65
|
+
rank: z.string().nullable(),
|
|
66
|
+
songId: z.string().nullable(),
|
|
67
67
|
userId: z.number().nullable(),
|
|
68
68
|
beatmapDifficulty: z.number().optional().nullable(),
|
|
69
69
|
beatmapNotes: z.number().optional().nullable(),
|
package/api/getVerified.ts
CHANGED
package/api/getVideoUploadUrl.ts
CHANGED
package/api/qualifyMap.ts
CHANGED
|
@@ -1,86 +1,97 @@
|
|
|
1
|
-
import { NextResponse } from "
|
|
2
|
-
import z from "zod";
|
|
3
|
-
import { protectedApi, validUser } from "../utils/requestUtils";
|
|
4
|
-
import { supabase } from "../utils/supabase";
|
|
5
|
-
import { getUserBySession } from "../utils/getUserBySession";
|
|
6
|
-
import { User } from "@supabase/supabase-js";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
.
|
|
35
|
-
.
|
|
36
|
-
.
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
.
|
|
52
|
-
.
|
|
53
|
-
.
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
1
|
+
import { NextResponse } from "../utils/response";
|
|
2
|
+
import z from "zod";
|
|
3
|
+
import { protectedApi, validUser } from "../utils/requestUtils";
|
|
4
|
+
import { supabase } from "../utils/supabase";
|
|
5
|
+
import { getUserBySession } from "../utils/getUserBySession";
|
|
6
|
+
import { User } from "@supabase/supabase-js";
|
|
7
|
+
import { postMapLifecycleWebhook } from "../utils/mapLifecycleWebhook";
|
|
8
|
+
|
|
9
|
+
const QUALIFY_BADGES = ["Team Ranked"];
|
|
10
|
+
|
|
11
|
+
export const Schema = {
|
|
12
|
+
input: z.strictObject({
|
|
13
|
+
session: z.string(),
|
|
14
|
+
mapId: z.number(),
|
|
15
|
+
}),
|
|
16
|
+
output: z.object({
|
|
17
|
+
error: z.string().optional(),
|
|
18
|
+
qualifiedAt: z.string().optional(),
|
|
19
|
+
}),
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export async function POST(request: Request) {
|
|
23
|
+
return protectedApi({
|
|
24
|
+
request,
|
|
25
|
+
schema: Schema,
|
|
26
|
+
authorization: validUser,
|
|
27
|
+
activity: handler,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function handler(data: (typeof Schema)["input"]["_type"]) {
|
|
32
|
+
const user = (await getUserBySession(data.session)) as User;
|
|
33
|
+
const { data: queryUserData } = await supabase
|
|
34
|
+
.from("profiles")
|
|
35
|
+
.select("*")
|
|
36
|
+
.eq("uid", user.id)
|
|
37
|
+
.single();
|
|
38
|
+
|
|
39
|
+
if (!queryUserData) {
|
|
40
|
+
return NextResponse.json({ error: "Can't find user" });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const tags = (queryUserData?.badges || []) as string[];
|
|
44
|
+
const hasQualifyAccess = QUALIFY_BADGES.some((badge) => tags.includes(badge));
|
|
45
|
+
|
|
46
|
+
if (!hasQualifyAccess) {
|
|
47
|
+
return NextResponse.json({ error: "Only management can qualify maps!" });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const { data: mapData } = await supabase
|
|
51
|
+
.from("beatmapPages")
|
|
52
|
+
.select("id,owner,status,qualified,qualifiedAt")
|
|
53
|
+
.eq("id", data.mapId)
|
|
54
|
+
.single();
|
|
55
|
+
|
|
56
|
+
if (!mapData) {
|
|
57
|
+
return NextResponse.json({ error: "Bad map" });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (mapData.owner === queryUserData.id) {
|
|
61
|
+
return NextResponse.json({ error: "Can't qualify own map" });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (mapData.status === "RANKED") {
|
|
65
|
+
return NextResponse.json({ error: "Map is already ranked" });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (mapData.qualified) {
|
|
69
|
+
return NextResponse.json({
|
|
70
|
+
error: "Map is already qualified",
|
|
71
|
+
qualifiedAt: mapData.qualifiedAt || undefined,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const qualifiedAt = new Date().toISOString();
|
|
76
|
+
const { error: updateError } = await supabase.from("beatmapPages").upsert({
|
|
77
|
+
id: data.mapId,
|
|
78
|
+
qualified: true,
|
|
79
|
+
qualifiedAt,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
if (updateError) {
|
|
83
|
+
return NextResponse.json({ error: updateError.message });
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
await postMapLifecycleWebhook({
|
|
87
|
+
mapId: data.mapId,
|
|
88
|
+
event: "qualified",
|
|
89
|
+
candidateQualifierUsername: tags.includes("Candidate")
|
|
90
|
+
? queryUserData.username ||
|
|
91
|
+
queryUserData.computedUsername ||
|
|
92
|
+
`User #${queryUserData.id}`
|
|
93
|
+
: undefined,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
return NextResponse.json({ qualifiedAt });
|
|
97
|
+
}
|
package/api/rankMapsArchive.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { NextResponse } from "
|
|
1
|
+
import { NextResponse } from "../utils/response";
|
|
2
2
|
import z from "zod";
|
|
3
3
|
import { protectedApi, validUser } from "../utils/requestUtils";
|
|
4
4
|
import { supabase } from "../utils/supabase";
|
|
5
5
|
import { getUserBySession } from "../utils/getUserBySession";
|
|
6
6
|
import { User } from "@supabase/supabase-js";
|
|
7
|
+
import { postMapLifecycleWebhook } from "../utils/mapLifecycleWebhook";
|
|
7
8
|
|
|
8
9
|
export const Schema = {
|
|
9
10
|
input: z.strictObject({
|
|
@@ -64,6 +65,12 @@ export async function handler(data: (typeof Schema)["input"]["_type"]) {
|
|
|
64
65
|
nominations: [queryUserData.id, queryUserData.id],
|
|
65
66
|
status: "RANKED",
|
|
66
67
|
ranked_at: Date.now(),
|
|
68
|
+
qualified: false,
|
|
69
|
+
qualifiedAt: null,
|
|
70
|
+
});
|
|
71
|
+
await postMapLifecycleWebhook({
|
|
72
|
+
mapId: element.id,
|
|
73
|
+
event: "ranked",
|
|
67
74
|
});
|
|
68
75
|
}
|
|
69
76
|
|
package/api/searchUsers.ts
CHANGED
package/api/setPasskey.ts
CHANGED
package/api/submitScore.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { NextResponse } from "
|
|
1
|
+
import { NextResponse } from "../utils/response";
|
|
2
2
|
import z from "zod";
|
|
3
3
|
import { protectedApi, validUser } from "../utils/requestUtils";
|
|
4
4
|
import { supabase } from "../utils/supabase";
|
|
@@ -303,11 +303,6 @@ export async function handler({
|
|
|
303
303
|
console.log("p3");
|
|
304
304
|
|
|
305
305
|
await invalidateCachePrefix(`userscore:${userData.id}`);
|
|
306
|
-
const beatmapIsRanked =
|
|
307
|
-
beatmapPages?.status === "RANKED" || beatmapPages?.status === "APPROVED";
|
|
308
|
-
if (beatmapIsRanked) {
|
|
309
|
-
await invalidateCachePrefix(`beatmap-scores:${data.mapHash}`);
|
|
310
|
-
}
|
|
311
306
|
|
|
312
307
|
// Grant special badges if applicable
|
|
313
308
|
if (passed && beatmapPages && !data.mods.includes("mod_nofail")) {
|