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.
@@ -45,7 +45,19 @@ export async function handler(data: (typeof Schema)["input"]["_type"]) {
45
45
  });
46
46
  }
47
47
 
48
- if (data.resourceId !== queryUserData.clan?.toString()) {
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
  });
@@ -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
+ }
@@ -112,7 +112,7 @@ export async function handler({
112
112
  {
113
113
  error: "User doesn't exist",
114
114
  },
115
- { status: 500 }
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: 500 }
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: 500 }
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: 500 }
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rhythia-api",
3
- "version": "196.0.0",
3
+ "version": "198.0.0",
4
4
  "main": "index.ts",
5
5
  "author": "online-contributors-cunev",
6
6
  "scripts": {
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: {