rhythia-api 229.0.0 → 231.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/api/checkQualified.ts +83 -0
- package/api/getBeatmapPage.ts +65 -28
- package/api/getBeatmapPageById.ts +61 -24
- package/api/getUserScores.ts +18 -15
- package/api/{nominateMap.ts → qualifyMap.ts} +86 -82
- package/api/submitScoreInternal.ts +449 -426
- package/api/{approveMap.ts → vetoMap.ts} +94 -78
- package/handleApi.ts +3 -7
- package/index.ts +134 -85
- package/package.json +10 -2
- package/queries/admin_delete_user.sql +39 -0
- package/queries/admin_exclude_user.sql +21 -0
- package/queries/admin_invalidate_ranked_scores.sql +18 -0
- package/queries/admin_log_action.sql +10 -0
- package/queries/admin_profanity_clear.sql +29 -0
- package/queries/admin_remove_all_scores.sql +29 -0
- package/queries/admin_restrict_user.sql +21 -0
- package/queries/admin_search_users.sql +24 -0
- package/queries/admin_silence_user.sql +21 -0
- package/queries/admin_unban_user.sql +21 -0
- package/queries/get_badge_leaderboard.sql +50 -0
- package/queries/get_clan_leaderboard.sql +68 -0
- package/queries/get_collections_v4.sql +109 -0
- package/queries/get_top_scores_for_beatmap.sql +44 -0
- package/queries/get_user_by_email.sql +32 -0
- package/queries/get_user_scores_lastday.sql +47 -0
- package/queries/get_user_scores_reign.sql +31 -0
- package/queries/get_user_scores_top_and_stats.sql +84 -0
- package/queries/grant_special_badges.sql +69 -0
- package/types/database.ts +1224 -1179
- package/utils/getUserBySession.ts +1 -1
- package/utils/requestUtils.ts +127 -88
|
@@ -1,78 +1,94 @@
|
|
|
1
|
-
import { NextResponse } from "next/server";
|
|
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
|
-
|
|
33
|
-
.
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
.
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
if (mapData
|
|
56
|
-
return NextResponse.json({ error: "
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (
|
|
60
|
-
return NextResponse.json({
|
|
61
|
-
error: "
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
id
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
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
|
+
const VETO_BADGES = ["Team Ranked"];
|
|
9
|
+
|
|
10
|
+
export const Schema = {
|
|
11
|
+
input: z.strictObject({
|
|
12
|
+
session: z.string(),
|
|
13
|
+
mapId: z.number(),
|
|
14
|
+
reason: z.string().min(1).max(1000),
|
|
15
|
+
}),
|
|
16
|
+
output: z.object({
|
|
17
|
+
error: z.string().optional(),
|
|
18
|
+
}),
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export async function POST(request: Request) {
|
|
22
|
+
return protectedApi({
|
|
23
|
+
request,
|
|
24
|
+
schema: Schema,
|
|
25
|
+
authorization: validUser,
|
|
26
|
+
activity: handler,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function handler(data: (typeof Schema)["input"]["_type"]) {
|
|
31
|
+
const user = (await getUserBySession(data.session)) as User;
|
|
32
|
+
const { data: queryUserData } = await supabase
|
|
33
|
+
.from("profiles")
|
|
34
|
+
.select("*")
|
|
35
|
+
.eq("uid", user.id)
|
|
36
|
+
.single();
|
|
37
|
+
|
|
38
|
+
if (!queryUserData) {
|
|
39
|
+
return NextResponse.json({ error: "Can't find user" });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const tags = (queryUserData?.badges || []) as string[];
|
|
43
|
+
const hasVetoAccess = VETO_BADGES.some((badge) => tags.includes(badge));
|
|
44
|
+
|
|
45
|
+
if (!hasVetoAccess) {
|
|
46
|
+
return NextResponse.json({ error: "Only management can veto maps!" });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const { data: mapData } = await supabase
|
|
50
|
+
.from("beatmapPages")
|
|
51
|
+
.select("id,qualified")
|
|
52
|
+
.eq("id", data.mapId)
|
|
53
|
+
.single();
|
|
54
|
+
|
|
55
|
+
if (!mapData) {
|
|
56
|
+
return NextResponse.json({ error: "Bad map" });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!mapData.qualified) {
|
|
60
|
+
return NextResponse.json({
|
|
61
|
+
error: "Only qualified maps can be vetoed",
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const { data: vetoData, error: vetoError } = await supabase
|
|
66
|
+
.from("vetos")
|
|
67
|
+
.insert({
|
|
68
|
+
beatmapPage: data.mapId,
|
|
69
|
+
user: queryUserData.id,
|
|
70
|
+
veto_reason: data.reason,
|
|
71
|
+
})
|
|
72
|
+
.select("id")
|
|
73
|
+
.single();
|
|
74
|
+
|
|
75
|
+
if (vetoError) {
|
|
76
|
+
return NextResponse.json({ error: vetoError.message });
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const { error: updateError } = await supabase.from("beatmapPages").upsert({
|
|
80
|
+
id: data.mapId,
|
|
81
|
+
qualified: false,
|
|
82
|
+
qualifiedAt: null,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
if (updateError) {
|
|
86
|
+
if (vetoData?.id) {
|
|
87
|
+
await supabase.from("vetos").delete().eq("id", vetoData.id);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return NextResponse.json({ error: updateError.message });
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return NextResponse.json({});
|
|
94
|
+
}
|
package/handleApi.ts
CHANGED
|
@@ -2,20 +2,16 @@ import { z } from "zod";
|
|
|
2
2
|
let env = "development";
|
|
3
3
|
import { profanity, CensorType } from "@2toad/profanity";
|
|
4
4
|
export function setEnvironment(
|
|
5
|
-
stage: "development" | "testing" | "production"
|
|
5
|
+
stage: "development" | "testing" | "production"
|
|
6
6
|
) {
|
|
7
7
|
env = stage;
|
|
8
8
|
}
|
|
9
9
|
export function handleApi<
|
|
10
|
-
T extends { url: string; input: z.ZodObject<any>; output: z.ZodObject<any> }
|
|
10
|
+
T extends { url: string; input: z.ZodObject<any>; output: z.ZodObject<any> }
|
|
11
11
|
>(apiSchema: T) {
|
|
12
12
|
profanity.whitelist.addWords(["willy"]);
|
|
13
13
|
return async (input: T["input"]["_type"]): Promise<T["output"]["_type"]> => {
|
|
14
|
-
|
|
15
|
-
if (env == "local") {
|
|
16
|
-
url = `http://localhost:3000${apiSchema.url}`;
|
|
17
|
-
}
|
|
18
|
-
const response = await fetch(url, {
|
|
14
|
+
const response = await fetch(`https://${env}.rhythia.com${apiSchema.url}`, {
|
|
19
15
|
method: "POST",
|
|
20
16
|
body: JSON.stringify(input),
|
|
21
17
|
});
|
package/index.ts
CHANGED
|
@@ -33,22 +33,6 @@ import { Schema as AddCollectionMap } from "./api/addCollectionMap"
|
|
|
33
33
|
export { Schema as SchemaAddCollectionMap } from "./api/addCollectionMap"
|
|
34
34
|
export const addCollectionMap = handleApi({url:"/api/addCollectionMap",...AddCollectionMap})
|
|
35
35
|
|
|
36
|
-
// ./api/approveMap.ts API
|
|
37
|
-
|
|
38
|
-
/*
|
|
39
|
-
export const Schema = {
|
|
40
|
-
input: z.strictObject({
|
|
41
|
-
session: z.string(),
|
|
42
|
-
mapId: z.number(),
|
|
43
|
-
}),
|
|
44
|
-
output: z.object({
|
|
45
|
-
error: z.string().optional(),
|
|
46
|
-
}),
|
|
47
|
-
};*/
|
|
48
|
-
import { Schema as ApproveMap } from "./api/approveMap"
|
|
49
|
-
export { Schema as SchemaApproveMap } from "./api/approveMap"
|
|
50
|
-
export const approveMap = handleApi({url:"/api/approveMap",...ApproveMap})
|
|
51
|
-
|
|
52
36
|
// ./api/chartPublicStats.ts API
|
|
53
37
|
|
|
54
38
|
/*
|
|
@@ -60,6 +44,22 @@ import { Schema as ChartPublicStats } from "./api/chartPublicStats"
|
|
|
60
44
|
export { Schema as SchemaChartPublicStats } from "./api/chartPublicStats"
|
|
61
45
|
export const chartPublicStats = handleApi({url:"/api/chartPublicStats",...ChartPublicStats})
|
|
62
46
|
|
|
47
|
+
// ./api/checkQualified.ts API
|
|
48
|
+
|
|
49
|
+
/*
|
|
50
|
+
export const Schema = {
|
|
51
|
+
input: z.strictObject({
|
|
52
|
+
secret: z.string(),
|
|
53
|
+
}),
|
|
54
|
+
output: z.object({
|
|
55
|
+
error: z.string().optional(),
|
|
56
|
+
updated: z.number(),
|
|
57
|
+
}),
|
|
58
|
+
};*/
|
|
59
|
+
import { Schema as CheckQualified } from "./api/checkQualified"
|
|
60
|
+
export { Schema as SchemaCheckQualified } from "./api/checkQualified"
|
|
61
|
+
export const checkQualified = handleApi({url:"/api/checkQualified",...CheckQualified})
|
|
62
|
+
|
|
63
63
|
// ./api/createBeatmap.ts API
|
|
64
64
|
|
|
65
65
|
/*
|
|
@@ -469,16 +469,30 @@ export const Schema = {
|
|
|
469
469
|
image: z.string().nullable().optional(),
|
|
470
470
|
imageLarge: z.string().nullable().optional(),
|
|
471
471
|
starRating: z.number().nullable().optional(),
|
|
472
|
-
owner: z.number().nullable().optional(),
|
|
473
|
-
ownerUsername: z.string().nullable().optional(),
|
|
474
|
-
ownerAvatar: z.string().nullable().optional(),
|
|
475
|
-
status: z.string().nullable().optional(),
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
472
|
+
owner: z.number().nullable().optional(),
|
|
473
|
+
ownerUsername: z.string().nullable().optional(),
|
|
474
|
+
ownerAvatar: z.string().nullable().optional(),
|
|
475
|
+
status: z.string().nullable().optional(),
|
|
476
|
+
qualified: z.boolean().nullable().optional(),
|
|
477
|
+
qualifiedAt: z.string().nullable().optional(),
|
|
478
|
+
description: z.string().nullable().optional(),
|
|
479
|
+
tags: z.string().nullable().optional(),
|
|
480
|
+
videoUrl: z.string().nullable().optional(),
|
|
481
|
+
vetos: z
|
|
482
|
+
.array(
|
|
483
|
+
z.object({
|
|
484
|
+
id: z.number(),
|
|
485
|
+
userId: z.number().nullable().optional(),
|
|
486
|
+
username: z.string().nullable().optional(),
|
|
487
|
+
avatar_url: z.string().nullable().optional(),
|
|
488
|
+
veto_reason: z.string().nullable().optional(),
|
|
489
|
+
created_at: z.string().nullable().optional(),
|
|
490
|
+
})
|
|
491
|
+
)
|
|
492
|
+
.optional(),
|
|
493
|
+
})
|
|
494
|
+
.optional(),
|
|
495
|
+
}),
|
|
482
496
|
};*/
|
|
483
497
|
import { Schema as GetBeatmapPage } from "./api/getBeatmapPage"
|
|
484
498
|
export { Schema as SchemaGetBeatmapPage } from "./api/getBeatmapPage"
|
|
@@ -528,14 +542,28 @@ export const Schema = {
|
|
|
528
542
|
beatmapFile: z.string().nullable().optional(),
|
|
529
543
|
image: z.string().nullable().optional(),
|
|
530
544
|
starRating: z.number().nullable().optional(),
|
|
531
|
-
owner: z.number().nullable().optional(),
|
|
532
|
-
ownerUsername: z.string().nullable().optional(),
|
|
533
|
-
ownerAvatar: z.string().nullable().optional(),
|
|
534
|
-
status: z.string().nullable().optional(),
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
545
|
+
owner: z.number().nullable().optional(),
|
|
546
|
+
ownerUsername: z.string().nullable().optional(),
|
|
547
|
+
ownerAvatar: z.string().nullable().optional(),
|
|
548
|
+
status: z.string().nullable().optional(),
|
|
549
|
+
qualified: z.boolean().nullable().optional(),
|
|
550
|
+
qualifiedAt: z.string().nullable().optional(),
|
|
551
|
+
videoUrl: z.string().nullable(),
|
|
552
|
+
vetos: z
|
|
553
|
+
.array(
|
|
554
|
+
z.object({
|
|
555
|
+
id: z.number(),
|
|
556
|
+
userId: z.number().nullable().optional(),
|
|
557
|
+
username: z.string().nullable().optional(),
|
|
558
|
+
avatar_url: z.string().nullable().optional(),
|
|
559
|
+
veto_reason: z.string().nullable().optional(),
|
|
560
|
+
created_at: z.string().nullable().optional(),
|
|
561
|
+
})
|
|
562
|
+
)
|
|
563
|
+
.optional(),
|
|
564
|
+
})
|
|
565
|
+
.optional(),
|
|
566
|
+
}),
|
|
539
567
|
};*/
|
|
540
568
|
import { Schema as GetBeatmapPageById } from "./api/getBeatmapPageById"
|
|
541
569
|
export { Schema as SchemaGetBeatmapPageById } from "./api/getBeatmapPageById"
|
|
@@ -1116,11 +1144,12 @@ export const Schema = {
|
|
|
1116
1144
|
awarded_sp: z.number().nullable(),
|
|
1117
1145
|
beatmapHash: z.string().nullable(),
|
|
1118
1146
|
created_at: z.string(),
|
|
1119
|
-
id: z.number(),
|
|
1120
|
-
misses: z.number().nullable(),
|
|
1121
|
-
passed: z.boolean().nullable(),
|
|
1122
|
-
|
|
1123
|
-
|
|
1147
|
+
id: z.number(),
|
|
1148
|
+
misses: z.number().nullable(),
|
|
1149
|
+
passed: z.boolean().nullable(),
|
|
1150
|
+
replay_url: z.string().nullable().optional(),
|
|
1151
|
+
songId: z.string().nullable(),
|
|
1152
|
+
userId: z.number().nullable(),
|
|
1124
1153
|
beatmapDifficulty: z.number().optional().nullable(),
|
|
1125
1154
|
beatmapNotes: z.number().optional().nullable(),
|
|
1126
1155
|
beatmapTitle: z.string().optional().nullable(),
|
|
@@ -1135,11 +1164,12 @@ export const Schema = {
|
|
|
1135
1164
|
id: z.number(),
|
|
1136
1165
|
awarded_sp: z.number().nullable(),
|
|
1137
1166
|
created_at: z.string(),
|
|
1138
|
-
misses: z.number().nullable(),
|
|
1139
|
-
mods: z.record(z.unknown()),
|
|
1140
|
-
passed: z.boolean().nullable(),
|
|
1141
|
-
|
|
1142
|
-
|
|
1167
|
+
misses: z.number().nullable(),
|
|
1168
|
+
mods: z.record(z.unknown()),
|
|
1169
|
+
passed: z.boolean().nullable(),
|
|
1170
|
+
replay_url: z.string().nullable().optional(),
|
|
1171
|
+
songId: z.string().nullable(),
|
|
1172
|
+
speed: z.number().nullable(),
|
|
1143
1173
|
spin: z.boolean(),
|
|
1144
1174
|
beatmapHash: z.string().nullable(),
|
|
1145
1175
|
beatmapTitle: z.string().nullable(),
|
|
@@ -1154,11 +1184,12 @@ export const Schema = {
|
|
|
1154
1184
|
awarded_sp: z.number().nullable(),
|
|
1155
1185
|
beatmapHash: z.string().nullable(),
|
|
1156
1186
|
created_at: z.string(),
|
|
1157
|
-
id: z.number(),
|
|
1158
|
-
misses: z.number().nullable(),
|
|
1159
|
-
passed: z.boolean().nullable(),
|
|
1160
|
-
|
|
1161
|
-
|
|
1187
|
+
id: z.number(),
|
|
1188
|
+
misses: z.number().nullable(),
|
|
1189
|
+
passed: z.boolean().nullable(),
|
|
1190
|
+
replay_url: z.string().nullable().optional(),
|
|
1191
|
+
rank: z.string().nullable(),
|
|
1192
|
+
songId: z.string().nullable(),
|
|
1162
1193
|
userId: z.number().nullable(),
|
|
1163
1194
|
beatmapDifficulty: z.number().optional().nullable(),
|
|
1164
1195
|
beatmapNotes: z.number().optional().nullable(),
|
|
@@ -1213,22 +1244,6 @@ import { Schema as GetVideoUploadUrl } from "./api/getVideoUploadUrl"
|
|
|
1213
1244
|
export { Schema as SchemaGetVideoUploadUrl } from "./api/getVideoUploadUrl"
|
|
1214
1245
|
export const getVideoUploadUrl = handleApi({url:"/api/getVideoUploadUrl",...GetVideoUploadUrl})
|
|
1215
1246
|
|
|
1216
|
-
// ./api/nominateMap.ts API
|
|
1217
|
-
|
|
1218
|
-
/*
|
|
1219
|
-
export const Schema = {
|
|
1220
|
-
input: z.strictObject({
|
|
1221
|
-
session: z.string(),
|
|
1222
|
-
mapId: z.number(),
|
|
1223
|
-
}),
|
|
1224
|
-
output: z.object({
|
|
1225
|
-
error: z.string().optional(),
|
|
1226
|
-
}),
|
|
1227
|
-
};*/
|
|
1228
|
-
import { Schema as NominateMap } from "./api/nominateMap"
|
|
1229
|
-
export { Schema as SchemaNominateMap } from "./api/nominateMap"
|
|
1230
|
-
export const nominateMap = handleApi({url:"/api/nominateMap",...NominateMap})
|
|
1231
|
-
|
|
1232
1247
|
// ./api/postBeatmapComment.ts API
|
|
1233
1248
|
|
|
1234
1249
|
/*
|
|
@@ -1246,6 +1261,23 @@ import { Schema as PostBeatmapComment } from "./api/postBeatmapComment"
|
|
|
1246
1261
|
export { Schema as SchemaPostBeatmapComment } from "./api/postBeatmapComment"
|
|
1247
1262
|
export const postBeatmapComment = handleApi({url:"/api/postBeatmapComment",...PostBeatmapComment})
|
|
1248
1263
|
|
|
1264
|
+
// ./api/qualifyMap.ts API
|
|
1265
|
+
|
|
1266
|
+
/*
|
|
1267
|
+
export const Schema = {
|
|
1268
|
+
input: z.strictObject({
|
|
1269
|
+
session: z.string(),
|
|
1270
|
+
mapId: z.number(),
|
|
1271
|
+
}),
|
|
1272
|
+
output: z.object({
|
|
1273
|
+
error: z.string().optional(),
|
|
1274
|
+
qualifiedAt: z.string().optional(),
|
|
1275
|
+
}),
|
|
1276
|
+
};*/
|
|
1277
|
+
import { Schema as QualifyMap } from "./api/qualifyMap"
|
|
1278
|
+
export { Schema as SchemaQualifyMap } from "./api/qualifyMap"
|
|
1279
|
+
export const qualifyMap = handleApi({url:"/api/qualifyMap",...QualifyMap})
|
|
1280
|
+
|
|
1249
1281
|
// ./api/rankMapsArchive.ts API
|
|
1250
1282
|
|
|
1251
1283
|
/*
|
|
@@ -1335,26 +1367,26 @@ export const submitScore = handleApi({url:"/api/submitScore",...SubmitScore})
|
|
|
1335
1367
|
// ./api/submitScoreInternal.ts API
|
|
1336
1368
|
|
|
1337
1369
|
/*
|
|
1338
|
-
export const Schema = {
|
|
1339
|
-
input: z.strictObject({
|
|
1340
|
-
session: z.string(),
|
|
1341
|
-
secret: z.string(),
|
|
1342
|
-
token: z.string(),
|
|
1343
|
-
data: z.strictObject({
|
|
1344
|
-
onlineMapId: z.number(),
|
|
1345
|
-
misses: z.number(),
|
|
1346
|
-
hits: z.number(),
|
|
1347
|
-
speed: z.number(),
|
|
1348
|
-
mods: z.array(z.string()),
|
|
1349
|
-
spin: z.boolean(),
|
|
1350
|
-
replayBytes: z.string().nullable().optional(),
|
|
1351
|
-
pauses: z.number().nullable().optional(),
|
|
1352
|
-
failTime: z.number().nullable().optional(),
|
|
1353
|
-
}),
|
|
1354
|
-
}),
|
|
1355
|
-
output: z.object({
|
|
1356
|
-
error: z.string().optional(),
|
|
1357
|
-
}),
|
|
1370
|
+
export const Schema = {
|
|
1371
|
+
input: z.strictObject({
|
|
1372
|
+
session: z.string(),
|
|
1373
|
+
secret: z.string(),
|
|
1374
|
+
token: z.string(),
|
|
1375
|
+
data: z.strictObject({
|
|
1376
|
+
onlineMapId: z.number(),
|
|
1377
|
+
misses: z.number(),
|
|
1378
|
+
hits: z.number(),
|
|
1379
|
+
speed: z.number(),
|
|
1380
|
+
mods: z.array(z.string()),
|
|
1381
|
+
spin: z.boolean(),
|
|
1382
|
+
replayBytes: z.string().nullable().optional(),
|
|
1383
|
+
pauses: z.number().nullable().optional(),
|
|
1384
|
+
failTime: z.number().nullable().optional(),
|
|
1385
|
+
}),
|
|
1386
|
+
}),
|
|
1387
|
+
output: z.object({
|
|
1388
|
+
error: z.string().optional(),
|
|
1389
|
+
}),
|
|
1358
1390
|
};*/
|
|
1359
1391
|
import { Schema as SubmitScoreInternal } from "./api/submitScoreInternal"
|
|
1360
1392
|
export { Schema as SchemaSubmitScoreInternal } from "./api/submitScoreInternal"
|
|
@@ -1379,4 +1411,21 @@ export const Schema = {
|
|
|
1379
1411
|
import { Schema as UpdateBeatmapPage } from "./api/updateBeatmapPage"
|
|
1380
1412
|
export { Schema as SchemaUpdateBeatmapPage } from "./api/updateBeatmapPage"
|
|
1381
1413
|
export const updateBeatmapPage = handleApi({url:"/api/updateBeatmapPage",...UpdateBeatmapPage})
|
|
1414
|
+
|
|
1415
|
+
// ./api/vetoMap.ts API
|
|
1416
|
+
|
|
1417
|
+
/*
|
|
1418
|
+
export const Schema = {
|
|
1419
|
+
input: z.strictObject({
|
|
1420
|
+
session: z.string(),
|
|
1421
|
+
mapId: z.number(),
|
|
1422
|
+
reason: z.string().min(1).max(1000),
|
|
1423
|
+
}),
|
|
1424
|
+
output: z.object({
|
|
1425
|
+
error: z.string().optional(),
|
|
1426
|
+
}),
|
|
1427
|
+
};*/
|
|
1428
|
+
import { Schema as VetoMap } from "./api/vetoMap"
|
|
1429
|
+
export { Schema as SchemaVetoMap } from "./api/vetoMap"
|
|
1430
|
+
export const vetoMap = handleApi({url:"/api/vetoMap",...VetoMap})
|
|
1382
1431
|
export { handleApi } from "./handleApi"
|
package/package.json
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rhythia-api",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "231.0.0",
|
|
4
4
|
"main": "index.ts",
|
|
5
5
|
"author": "online-contributors-cunev",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"update": "bun ./scripts/update.ts",
|
|
8
8
|
"ci-deploy": "tsx ./scripts/ci-deploy.ts",
|
|
9
9
|
"test": "tsx ./scripts/test.ts",
|
|
10
|
+
"cache:clear-user-scores": "bun run scripts/clear-user-score-cache.ts",
|
|
11
|
+
"db:optimize-user-scores": "bun run scripts/optimize-user-scores-indexes.ts",
|
|
12
|
+
"query-pull": "bun run scripts/pull-queries.ts",
|
|
13
|
+
"query-push": "bun run scripts/deploy-queries.ts",
|
|
14
|
+
"queries:pull": "bun run scripts/pull-queries.ts",
|
|
15
|
+
"queries:deploy": "bun run scripts/deploy-queries.ts",
|
|
10
16
|
"sync": "npx supabase gen types typescript --project-id \"pfkajngbllcbdzoylrvp\" --schema public > types/database.ts",
|
|
11
17
|
"pipeline:build-api": "tsx ./scripts/build.ts",
|
|
12
18
|
"pipeline:deploy-testing": "tsx ./scripts/build.ts"
|
|
@@ -24,6 +30,7 @@
|
|
|
24
30
|
"@types/bun": "^1.1.6",
|
|
25
31
|
"@types/lodash": "^4.17.7",
|
|
26
32
|
"@types/node": "^22.2.0",
|
|
33
|
+
"@types/pg": "^8.15.5",
|
|
27
34
|
"@types/validator": "^13.12.2",
|
|
28
35
|
"@vercel/edge": "^1.1.2",
|
|
29
36
|
"@vercel/node": "^3.2.8",
|
|
@@ -40,12 +47,13 @@
|
|
|
40
47
|
"osu-classes": "^3.1.0",
|
|
41
48
|
"osu-parsers": "^4.1.7",
|
|
42
49
|
"osu-standard-stable": "^5.0.0",
|
|
50
|
+
"pg": "^8.18.0",
|
|
43
51
|
"redis": "^5.10.0",
|
|
44
52
|
"remote-cloudflare-kv": "^1.0.1",
|
|
45
53
|
"sharp": "^0.33.5",
|
|
46
54
|
"short-uuid": "^5.2.0",
|
|
47
55
|
"simple-git": "^3.25.0",
|
|
48
|
-
"supabase": "^2.76.
|
|
56
|
+
"supabase": "^2.76.16",
|
|
49
57
|
"tsx": "^4.17.0",
|
|
50
58
|
"utf-8-validate": "^6.0.4",
|
|
51
59
|
"uuid": "^11.1.0",
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
CREATE OR REPLACE FUNCTION public.admin_delete_user(user_id integer)
|
|
2
|
+
RETURNS boolean
|
|
3
|
+
LANGUAGE plpgsql
|
|
4
|
+
SECURITY DEFINER
|
|
5
|
+
AS $function$
|
|
6
|
+
DECLARE
|
|
7
|
+
success BOOLEAN;
|
|
8
|
+
BEGIN
|
|
9
|
+
-- Delete scores first due to foreign key constraints
|
|
10
|
+
DELETE FROM public.scores WHERE "userId" = user_id;
|
|
11
|
+
|
|
12
|
+
-- Delete beatmapPageComments
|
|
13
|
+
DELETE FROM public."beatmapPageComments" WHERE owner = user_id;
|
|
14
|
+
|
|
15
|
+
-- Delete beatmapPages owned by user
|
|
16
|
+
DELETE FROM public."beatmapPages" WHERE owner = user_id;
|
|
17
|
+
|
|
18
|
+
-- Delete collections owned by user
|
|
19
|
+
DELETE FROM public."beatmapCollections" WHERE owner = user_id;
|
|
20
|
+
|
|
21
|
+
-- Delete collection relations related to user's collections
|
|
22
|
+
-- (this is handled by cascade if you have it set up)
|
|
23
|
+
|
|
24
|
+
-- Delete passkeys
|
|
25
|
+
DELETE FROM public.passkeys WHERE id = user_id;
|
|
26
|
+
|
|
27
|
+
-- Delete profile activity
|
|
28
|
+
DELETE FROM public."profileActivities"
|
|
29
|
+
WHERE uid = (SELECT uid FROM public.profiles WHERE id = user_id);
|
|
30
|
+
|
|
31
|
+
-- Finally, delete the profile
|
|
32
|
+
DELETE FROM public.profiles WHERE id = user_id;
|
|
33
|
+
|
|
34
|
+
-- Check if deletion was successful
|
|
35
|
+
success := NOT EXISTS (SELECT 1 FROM public.profiles WHERE id = user_id);
|
|
36
|
+
|
|
37
|
+
RETURN success;
|
|
38
|
+
END;
|
|
39
|
+
$function$
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
CREATE OR REPLACE FUNCTION public.admin_exclude_user(user_id integer)
|
|
2
|
+
RETURNS boolean
|
|
3
|
+
LANGUAGE plpgsql
|
|
4
|
+
SECURITY DEFINER
|
|
5
|
+
AS $function$
|
|
6
|
+
DECLARE
|
|
7
|
+
success BOOLEAN;
|
|
8
|
+
BEGIN
|
|
9
|
+
UPDATE public.profiles
|
|
10
|
+
SET ban = 'excluded'::"banTypes",
|
|
11
|
+
"bannedAt" = EXTRACT(EPOCH FROM NOW())::bigint
|
|
12
|
+
WHERE id = user_id;
|
|
13
|
+
|
|
14
|
+
success := EXISTS (
|
|
15
|
+
SELECT 1 FROM public.profiles
|
|
16
|
+
WHERE id = user_id AND ban = 'excluded'::"banTypes"
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
RETURN success;
|
|
20
|
+
END;
|
|
21
|
+
$function$
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
CREATE OR REPLACE FUNCTION public.admin_invalidate_ranked_scores(user_id integer)
|
|
2
|
+
RETURNS integer
|
|
3
|
+
LANGUAGE plpgsql
|
|
4
|
+
SECURITY DEFINER
|
|
5
|
+
AS $function$
|
|
6
|
+
DECLARE
|
|
7
|
+
updated_count INTEGER;
|
|
8
|
+
BEGIN
|
|
9
|
+
-- Update user stats to reflect score invalidation
|
|
10
|
+
UPDATE public.profiles
|
|
11
|
+
SET
|
|
12
|
+
skill_points = 0,
|
|
13
|
+
spin_skill_points = 0
|
|
14
|
+
WHERE id = user_id;
|
|
15
|
+
|
|
16
|
+
RETURN updated_count;
|
|
17
|
+
END;
|
|
18
|
+
$function$
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
CREATE OR REPLACE FUNCTION public.admin_log_action(admin_id integer, action_type text, target_id integer, details jsonb DEFAULT NULL::jsonb)
|
|
2
|
+
RETURNS void
|
|
3
|
+
LANGUAGE plpgsql
|
|
4
|
+
SECURITY DEFINER
|
|
5
|
+
AS $function$
|
|
6
|
+
BEGIN
|
|
7
|
+
INSERT INTO public.admin_actions (admin_id, action_type, target_id, details)
|
|
8
|
+
VALUES (admin_id, action_type, target_id, details);
|
|
9
|
+
END;
|
|
10
|
+
$function$
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
CREATE OR REPLACE FUNCTION public.admin_profanity_clear(user_id integer)
|
|
2
|
+
RETURNS boolean
|
|
3
|
+
LANGUAGE plpgsql
|
|
4
|
+
SECURITY DEFINER
|
|
5
|
+
AS $function$
|
|
6
|
+
DECLARE
|
|
7
|
+
success BOOLEAN;
|
|
8
|
+
random_username TEXT;
|
|
9
|
+
BEGIN
|
|
10
|
+
-- Generate a random username (Player + random number between 10000-99999)
|
|
11
|
+
random_username := 'Player' || (10000 + floor(random() * 90000)::int)::text;
|
|
12
|
+
|
|
13
|
+
-- Update the profile
|
|
14
|
+
UPDATE public.profiles
|
|
15
|
+
SET
|
|
16
|
+
username = random_username,
|
|
17
|
+
"computedUsername" = LOWER(random_username),
|
|
18
|
+
about_me = NULL
|
|
19
|
+
WHERE id = user_id;
|
|
20
|
+
|
|
21
|
+
-- Check if update was successful
|
|
22
|
+
success := EXISTS (
|
|
23
|
+
SELECT 1 FROM public.profiles
|
|
24
|
+
WHERE id = user_id AND username = random_username AND about_me IS NULL
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
RETURN success;
|
|
28
|
+
END;
|
|
29
|
+
$function$
|