rhythia-api 196.0.0 → 198.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/createInvite.ts +13 -1
- package/api/createSupporter.ts +2 -2
- package/api/executeAdminOperation.ts +205 -0
- package/api/getVerified.ts +41 -0
- package/api/submitScore.ts +4 -4
- package/index.ts +31 -0
- package/package.json +1 -1
- package/types/database.ts +137 -0
package/api/createInvite.ts
CHANGED
|
@@ -45,7 +45,19 @@ export async function handler(data: (typeof Schema)["input"]["_type"]) {
|
|
|
45
45
|
});
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
let { data: clanQuery, error: clanError } = await supabase
|
|
49
|
+
.from("clans")
|
|
50
|
+
.select("*")
|
|
51
|
+
.eq("id", queryUserData.clan || -1)
|
|
52
|
+
.single();
|
|
53
|
+
|
|
54
|
+
if (!clanQuery) {
|
|
55
|
+
return NextResponse.json({
|
|
56
|
+
error: "You're not in a clan",
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (data.resourceId !== (clanQuery.owner || -1).toString()) {
|
|
49
61
|
return NextResponse.json({
|
|
50
62
|
error: "You can't create invite for a clan that you aren't owner of.",
|
|
51
63
|
});
|
package/api/createSupporter.ts
CHANGED
|
@@ -98,7 +98,7 @@ export async function handler(
|
|
|
98
98
|
let { data: queryData, error } = await supabase
|
|
99
99
|
.from("profiles")
|
|
100
100
|
.select("*")
|
|
101
|
-
.eq("computedUsername", data.data.support_note)
|
|
101
|
+
.eq("computedUsername", data.data.support_note.toLowerCase())
|
|
102
102
|
.single();
|
|
103
103
|
|
|
104
104
|
if (queryData) {
|
|
@@ -110,7 +110,7 @@ export async function handler(
|
|
|
110
110
|
let { data: queryData, error } = await supabase
|
|
111
111
|
.from("profiles")
|
|
112
112
|
.select("*")
|
|
113
|
-
.eq("computedUsername", data.data.supporter_name)
|
|
113
|
+
.eq("computedUsername", data.data.supporter_name.toLowerCase())
|
|
114
114
|
.single();
|
|
115
115
|
|
|
116
116
|
if (queryData) {
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import z from "zod";
|
|
3
|
+
import { Database } from "../types/database";
|
|
4
|
+
import { protectedApi, validUser } from "../utils/requestUtils";
|
|
5
|
+
import { supabase } from "../utils/supabase";
|
|
6
|
+
import { getUserBySession } from "../utils/getUserBySession";
|
|
7
|
+
import { User } from "@supabase/supabase-js";
|
|
8
|
+
|
|
9
|
+
// Define supported admin operations and their parameter types
|
|
10
|
+
const adminOperations = {
|
|
11
|
+
deleteUser: z.object({ userId: z.number() }),
|
|
12
|
+
excludeUser: z.object({ userId: z.number() }),
|
|
13
|
+
restrictUser: z.object({ userId: z.number() }),
|
|
14
|
+
silenceUser: z.object({ userId: z.number() }),
|
|
15
|
+
searchUsers: z.object({ searchText: z.string() }),
|
|
16
|
+
removeAllScores: z.object({ userId: z.number() }),
|
|
17
|
+
invalidateRankedScores: z.object({ userId: z.number() }),
|
|
18
|
+
unbanUser: z.object({ userId: z.number() }),
|
|
19
|
+
} as const;
|
|
20
|
+
|
|
21
|
+
// Create a discriminated union type for operation parameters
|
|
22
|
+
const OperationParam = z.discriminatedUnion("operation", [
|
|
23
|
+
z.object({
|
|
24
|
+
operation: z.literal("deleteUser"),
|
|
25
|
+
params: adminOperations.deleteUser,
|
|
26
|
+
}),
|
|
27
|
+
z.object({
|
|
28
|
+
operation: z.literal("excludeUser"),
|
|
29
|
+
params: adminOperations.excludeUser,
|
|
30
|
+
}),
|
|
31
|
+
z.object({
|
|
32
|
+
operation: z.literal("restrictUser"),
|
|
33
|
+
params: adminOperations.restrictUser,
|
|
34
|
+
}),
|
|
35
|
+
z.object({
|
|
36
|
+
operation: z.literal("silenceUser"),
|
|
37
|
+
params: adminOperations.silenceUser,
|
|
38
|
+
}),
|
|
39
|
+
z.object({
|
|
40
|
+
operation: z.literal("searchUsers"),
|
|
41
|
+
params: adminOperations.searchUsers,
|
|
42
|
+
}),
|
|
43
|
+
z.object({
|
|
44
|
+
operation: z.literal("removeAllScores"),
|
|
45
|
+
params: adminOperations.removeAllScores,
|
|
46
|
+
}),
|
|
47
|
+
z.object({
|
|
48
|
+
operation: z.literal("invalidateRankedScores"),
|
|
49
|
+
params: adminOperations.invalidateRankedScores,
|
|
50
|
+
}),
|
|
51
|
+
z.object({
|
|
52
|
+
operation: z.literal("unbanUser"),
|
|
53
|
+
params: adminOperations.unbanUser,
|
|
54
|
+
}),
|
|
55
|
+
]);
|
|
56
|
+
|
|
57
|
+
export const Schema = {
|
|
58
|
+
input: z.strictObject({
|
|
59
|
+
session: z.string(),
|
|
60
|
+
data: OperationParam,
|
|
61
|
+
}),
|
|
62
|
+
output: z.object({
|
|
63
|
+
success: z.boolean(),
|
|
64
|
+
result: z.any().optional(),
|
|
65
|
+
error: z.string().optional(),
|
|
66
|
+
}),
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export async function POST(request: Request): Promise<NextResponse> {
|
|
70
|
+
return protectedApi({
|
|
71
|
+
request,
|
|
72
|
+
schema: Schema,
|
|
73
|
+
authorization: () => {},
|
|
74
|
+
activity: handler,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export async function handler(
|
|
79
|
+
data: (typeof Schema)["input"]["_type"]
|
|
80
|
+
): Promise<NextResponse<(typeof Schema)["output"]["_type"]>> {
|
|
81
|
+
// Get user from session
|
|
82
|
+
const user = (await getUserBySession(data.session)) as User;
|
|
83
|
+
|
|
84
|
+
// Get user's profile data
|
|
85
|
+
const { data: queryUserData, error: userError } = await supabase
|
|
86
|
+
.from("profiles")
|
|
87
|
+
.select("*")
|
|
88
|
+
.eq("uid", user.id)
|
|
89
|
+
.single();
|
|
90
|
+
|
|
91
|
+
if (userError || !queryUserData) {
|
|
92
|
+
return NextResponse.json(
|
|
93
|
+
{
|
|
94
|
+
success: false,
|
|
95
|
+
error: "User cannot be retrieved from session",
|
|
96
|
+
},
|
|
97
|
+
{ status: 404 }
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Check if user has "Global Moderator" badge
|
|
102
|
+
const badges = queryUserData.badges as Record<string, any> | null;
|
|
103
|
+
const isGlobalModerator =
|
|
104
|
+
badges &&
|
|
105
|
+
Array.isArray(badges.badges) &&
|
|
106
|
+
badges.badges.some((badge: any) => badge === "Global Moderator");
|
|
107
|
+
|
|
108
|
+
if (!isGlobalModerator) {
|
|
109
|
+
return NextResponse.json(
|
|
110
|
+
{
|
|
111
|
+
success: false,
|
|
112
|
+
error: "Unauthorized. Only Global Moderators can perform this action.",
|
|
113
|
+
},
|
|
114
|
+
{ status: 403 }
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Execute the requested admin operation
|
|
119
|
+
try {
|
|
120
|
+
let result;
|
|
121
|
+
const operation = data.data.operation;
|
|
122
|
+
const params = data.data.params as any;
|
|
123
|
+
|
|
124
|
+
switch (operation) {
|
|
125
|
+
case "deleteUser":
|
|
126
|
+
result = await supabase.rpc("admin_delete_user", {
|
|
127
|
+
user_id: params.userId,
|
|
128
|
+
});
|
|
129
|
+
break;
|
|
130
|
+
|
|
131
|
+
case "excludeUser":
|
|
132
|
+
result = await supabase.rpc("admin_exclude_user", {
|
|
133
|
+
user_id: params.userId,
|
|
134
|
+
});
|
|
135
|
+
break;
|
|
136
|
+
|
|
137
|
+
case "restrictUser":
|
|
138
|
+
result = await supabase.rpc("admin_restrict_user", {
|
|
139
|
+
user_id: params.userId,
|
|
140
|
+
});
|
|
141
|
+
break;
|
|
142
|
+
|
|
143
|
+
case "silenceUser":
|
|
144
|
+
result = await supabase.rpc("admin_silence_user", {
|
|
145
|
+
user_id: params.userId,
|
|
146
|
+
});
|
|
147
|
+
break;
|
|
148
|
+
|
|
149
|
+
case "searchUsers":
|
|
150
|
+
result = await supabase.rpc("admin_search_users", {
|
|
151
|
+
search_text: params.searchText,
|
|
152
|
+
});
|
|
153
|
+
break;
|
|
154
|
+
|
|
155
|
+
case "removeAllScores":
|
|
156
|
+
result = await supabase.rpc("admin_remove_all_scores", {
|
|
157
|
+
user_id: params.userId,
|
|
158
|
+
});
|
|
159
|
+
break;
|
|
160
|
+
|
|
161
|
+
case "invalidateRankedScores":
|
|
162
|
+
result = await supabase.rpc("admin_invalidate_ranked_scores", {
|
|
163
|
+
user_id: params.userId,
|
|
164
|
+
});
|
|
165
|
+
break;
|
|
166
|
+
|
|
167
|
+
case "unbanUser":
|
|
168
|
+
result = await supabase.rpc("admin_unban_user", {
|
|
169
|
+
user_id: params.userId,
|
|
170
|
+
});
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Log the admin action
|
|
175
|
+
await supabase.rpc("admin_log_action", {
|
|
176
|
+
admin_id: queryUserData.id,
|
|
177
|
+
action_type: operation,
|
|
178
|
+
target_id: "userId" in params ? params.userId : null,
|
|
179
|
+
details: { params },
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
if (result.error) {
|
|
183
|
+
return NextResponse.json(
|
|
184
|
+
{
|
|
185
|
+
success: false,
|
|
186
|
+
error: result.error.message,
|
|
187
|
+
},
|
|
188
|
+
{ status: 500 }
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return NextResponse.json({
|
|
193
|
+
success: true,
|
|
194
|
+
result: result.data,
|
|
195
|
+
});
|
|
196
|
+
} catch (err: any) {
|
|
197
|
+
return NextResponse.json(
|
|
198
|
+
{
|
|
199
|
+
success: false,
|
|
200
|
+
error: err.message || "An error occurred during the operation",
|
|
201
|
+
},
|
|
202
|
+
{ status: 500 }
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
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 { User } from "@supabase/supabase-js";
|
|
6
|
+
import { getUserBySession } from "../utils/getUserBySession";
|
|
7
|
+
|
|
8
|
+
export const Schema = {
|
|
9
|
+
input: z.strictObject({
|
|
10
|
+
session: z.string(),
|
|
11
|
+
}),
|
|
12
|
+
output: z.strictObject({}),
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export async function POST(request: Request): Promise<NextResponse> {
|
|
16
|
+
return protectedApi({
|
|
17
|
+
request,
|
|
18
|
+
schema: Schema,
|
|
19
|
+
authorization: () => {},
|
|
20
|
+
activity: handler,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function handler(
|
|
25
|
+
data: (typeof Schema)["input"]["_type"]
|
|
26
|
+
): Promise<NextResponse<(typeof Schema)["output"]["_type"]>> {
|
|
27
|
+
const user = (await getUserBySession(data.session)) as User;
|
|
28
|
+
let { data: queryData, error } = await supabase
|
|
29
|
+
.from("profiles")
|
|
30
|
+
.select("*")
|
|
31
|
+
.eq("uid", user.id)
|
|
32
|
+
.single();
|
|
33
|
+
|
|
34
|
+
if (!queryData) {
|
|
35
|
+
NextResponse.json({});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
console.log("CHEATER: " + JSON.stringify(queryData));
|
|
39
|
+
|
|
40
|
+
return NextResponse.json({});
|
|
41
|
+
}
|
package/api/submitScore.ts
CHANGED
|
@@ -112,7 +112,7 @@ export async function handler({
|
|
|
112
112
|
{
|
|
113
113
|
error: "User doesn't exist",
|
|
114
114
|
},
|
|
115
|
-
{ status:
|
|
115
|
+
{ status: 400 }
|
|
116
116
|
);
|
|
117
117
|
|
|
118
118
|
console.log(userData);
|
|
@@ -143,7 +143,7 @@ export async function handler({
|
|
|
143
143
|
{
|
|
144
144
|
error: "Map not submitted",
|
|
145
145
|
},
|
|
146
|
-
{ status:
|
|
146
|
+
{ status: 400 }
|
|
147
147
|
);
|
|
148
148
|
}
|
|
149
149
|
|
|
@@ -152,7 +152,7 @@ export async function handler({
|
|
|
152
152
|
{
|
|
153
153
|
error: "Map not submitted",
|
|
154
154
|
},
|
|
155
|
-
{ status:
|
|
155
|
+
{ status: 400 }
|
|
156
156
|
);
|
|
157
157
|
}
|
|
158
158
|
|
|
@@ -162,7 +162,7 @@ export async function handler({
|
|
|
162
162
|
{
|
|
163
163
|
error: "Wrong map",
|
|
164
164
|
},
|
|
165
|
-
{ status:
|
|
165
|
+
{ status: 400 }
|
|
166
166
|
);
|
|
167
167
|
}
|
|
168
168
|
|
package/index.ts
CHANGED
|
@@ -308,6 +308,24 @@ import { Schema as EditProfile } from "./api/editProfile"
|
|
|
308
308
|
export { Schema as SchemaEditProfile } from "./api/editProfile"
|
|
309
309
|
export const editProfile = handleApi({url:"/api/editProfile",...EditProfile})
|
|
310
310
|
|
|
311
|
+
// ./api/executeAdminOperation.ts API
|
|
312
|
+
|
|
313
|
+
/*
|
|
314
|
+
export const Schema = {
|
|
315
|
+
input: z.strictObject({
|
|
316
|
+
session: z.string(),
|
|
317
|
+
data: OperationParam,
|
|
318
|
+
}),
|
|
319
|
+
output: z.object({
|
|
320
|
+
success: z.boolean(),
|
|
321
|
+
result: z.any().optional(),
|
|
322
|
+
error: z.string().optional(),
|
|
323
|
+
}),
|
|
324
|
+
};*/
|
|
325
|
+
import { Schema as ExecuteAdminOperation } from "./api/executeAdminOperation"
|
|
326
|
+
export { Schema as SchemaExecuteAdminOperation } from "./api/executeAdminOperation"
|
|
327
|
+
export const executeAdminOperation = handleApi({url:"/api/executeAdminOperation",...ExecuteAdminOperation})
|
|
328
|
+
|
|
311
329
|
// ./api/getAvatarUploadUrl.ts API
|
|
312
330
|
|
|
313
331
|
/*
|
|
@@ -1054,6 +1072,19 @@ import { Schema as GetUserScores } from "./api/getUserScores"
|
|
|
1054
1072
|
export { Schema as SchemaGetUserScores } from "./api/getUserScores"
|
|
1055
1073
|
export const getUserScores = handleApi({url:"/api/getUserScores",...GetUserScores})
|
|
1056
1074
|
|
|
1075
|
+
// ./api/getVerified.ts API
|
|
1076
|
+
|
|
1077
|
+
/*
|
|
1078
|
+
export const Schema = {
|
|
1079
|
+
input: z.strictObject({
|
|
1080
|
+
session: z.string(),
|
|
1081
|
+
}),
|
|
1082
|
+
output: z.strictObject({}),
|
|
1083
|
+
};*/
|
|
1084
|
+
import { Schema as GetVerified } from "./api/getVerified"
|
|
1085
|
+
export { Schema as SchemaGetVerified } from "./api/getVerified"
|
|
1086
|
+
export const getVerified = handleApi({url:"/api/getVerified",...GetVerified})
|
|
1087
|
+
|
|
1057
1088
|
// ./api/nominateMap.ts API
|
|
1058
1089
|
|
|
1059
1090
|
/*
|
package/package.json
CHANGED
package/types/database.ts
CHANGED
|
@@ -9,6 +9,62 @@ export type Json =
|
|
|
9
9
|
export type Database = {
|
|
10
10
|
public: {
|
|
11
11
|
Tables: {
|
|
12
|
+
admin_actions: {
|
|
13
|
+
Row: {
|
|
14
|
+
action_type: string
|
|
15
|
+
admin_id: number
|
|
16
|
+
created_at: string | null
|
|
17
|
+
details: Json | null
|
|
18
|
+
id: number
|
|
19
|
+
target_id: number | null
|
|
20
|
+
}
|
|
21
|
+
Insert: {
|
|
22
|
+
action_type: string
|
|
23
|
+
admin_id: number
|
|
24
|
+
created_at?: string | null
|
|
25
|
+
details?: Json | null
|
|
26
|
+
id?: number
|
|
27
|
+
target_id?: number | null
|
|
28
|
+
}
|
|
29
|
+
Update: {
|
|
30
|
+
action_type?: string
|
|
31
|
+
admin_id?: number
|
|
32
|
+
created_at?: string | null
|
|
33
|
+
details?: Json | null
|
|
34
|
+
id?: number
|
|
35
|
+
target_id?: number | null
|
|
36
|
+
}
|
|
37
|
+
Relationships: []
|
|
38
|
+
}
|
|
39
|
+
admin_operations: {
|
|
40
|
+
Row: {
|
|
41
|
+
action_type: string | null
|
|
42
|
+
details: Json
|
|
43
|
+
id: number
|
|
44
|
+
target_id: string | null
|
|
45
|
+
}
|
|
46
|
+
Insert: {
|
|
47
|
+
action_type?: string | null
|
|
48
|
+
details: Json
|
|
49
|
+
id?: number
|
|
50
|
+
target_id?: string | null
|
|
51
|
+
}
|
|
52
|
+
Update: {
|
|
53
|
+
action_type?: string | null
|
|
54
|
+
details?: Json
|
|
55
|
+
id?: number
|
|
56
|
+
target_id?: string | null
|
|
57
|
+
}
|
|
58
|
+
Relationships: [
|
|
59
|
+
{
|
|
60
|
+
foreignKeyName: "admin_operations_id_fkey"
|
|
61
|
+
columns: ["id"]
|
|
62
|
+
isOneToOne: true
|
|
63
|
+
referencedRelation: "profiles"
|
|
64
|
+
referencedColumns: ["id"]
|
|
65
|
+
},
|
|
66
|
+
]
|
|
67
|
+
}
|
|
12
68
|
beatmapCollections: {
|
|
13
69
|
Row: {
|
|
14
70
|
created_at: string
|
|
@@ -541,6 +597,86 @@ export type Database = {
|
|
|
541
597
|
[_ in never]: never
|
|
542
598
|
}
|
|
543
599
|
Functions: {
|
|
600
|
+
admin_delete_user: {
|
|
601
|
+
Args: {
|
|
602
|
+
user_id: number
|
|
603
|
+
}
|
|
604
|
+
Returns: boolean
|
|
605
|
+
}
|
|
606
|
+
admin_exclude_user: {
|
|
607
|
+
Args: {
|
|
608
|
+
user_id: number
|
|
609
|
+
}
|
|
610
|
+
Returns: boolean
|
|
611
|
+
}
|
|
612
|
+
admin_invalidate_ranked_scores: {
|
|
613
|
+
Args: {
|
|
614
|
+
user_id: number
|
|
615
|
+
}
|
|
616
|
+
Returns: number
|
|
617
|
+
}
|
|
618
|
+
admin_log_action: {
|
|
619
|
+
Args: {
|
|
620
|
+
admin_id: number
|
|
621
|
+
action_type: string
|
|
622
|
+
target_id: string
|
|
623
|
+
details?: Json
|
|
624
|
+
}
|
|
625
|
+
Returns: undefined
|
|
626
|
+
}
|
|
627
|
+
admin_remove_all_scores: {
|
|
628
|
+
Args: {
|
|
629
|
+
user_id: number
|
|
630
|
+
}
|
|
631
|
+
Returns: number
|
|
632
|
+
}
|
|
633
|
+
admin_restrict_user: {
|
|
634
|
+
Args: {
|
|
635
|
+
user_id: number
|
|
636
|
+
}
|
|
637
|
+
Returns: boolean
|
|
638
|
+
}
|
|
639
|
+
admin_search_users: {
|
|
640
|
+
Args: {
|
|
641
|
+
search_text: string
|
|
642
|
+
}
|
|
643
|
+
Returns: {
|
|
644
|
+
about_me: string | null
|
|
645
|
+
avatar_url: string | null
|
|
646
|
+
badges: Json | null
|
|
647
|
+
ban: Database["public"]["Enums"]["banTypes"] | null
|
|
648
|
+
bannedAt: number | null
|
|
649
|
+
clan: number | null
|
|
650
|
+
computedUsername: string | null
|
|
651
|
+
created_at: number | null
|
|
652
|
+
flag: string | null
|
|
653
|
+
id: number
|
|
654
|
+
mu_rank: number
|
|
655
|
+
play_count: number | null
|
|
656
|
+
profile_image: string | null
|
|
657
|
+
sigma_rank: number | null
|
|
658
|
+
skill_points: number | null
|
|
659
|
+
spin_skill_points: number
|
|
660
|
+
squares_hit: number | null
|
|
661
|
+
total_score: number | null
|
|
662
|
+
uid: string | null
|
|
663
|
+
username: string | null
|
|
664
|
+
verificationDeadline: number
|
|
665
|
+
verified: boolean | null
|
|
666
|
+
}[]
|
|
667
|
+
}
|
|
668
|
+
admin_silence_user: {
|
|
669
|
+
Args: {
|
|
670
|
+
user_id: number
|
|
671
|
+
}
|
|
672
|
+
Returns: boolean
|
|
673
|
+
}
|
|
674
|
+
admin_unban_user: {
|
|
675
|
+
Args: {
|
|
676
|
+
user_id: number
|
|
677
|
+
}
|
|
678
|
+
Returns: boolean
|
|
679
|
+
}
|
|
544
680
|
get_clan_leaderboard: {
|
|
545
681
|
Args: {
|
|
546
682
|
page_number?: number
|
|
@@ -755,6 +891,7 @@ export type Database = {
|
|
|
755
891
|
userid: number
|
|
756
892
|
username: string
|
|
757
893
|
avatar_url: string
|
|
894
|
+
accuracy: number
|
|
758
895
|
}[]
|
|
759
896
|
}
|
|
760
897
|
get_top_scores_for_beatmap2: {
|