rhythia-api 197.0.0 → 199.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.
@@ -0,0 +1,212 @@
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
+ profanityClear: z.object({ userId: z.number() }),
16
+ searchUsers: z.object({ searchText: z.string() }),
17
+ removeAllScores: z.object({ userId: z.number() }),
18
+ invalidateRankedScores: z.object({ userId: z.number() }),
19
+ unbanUser: z.object({ userId: z.number() }),
20
+ } as const;
21
+
22
+ // Create a discriminated union type for operation parameters
23
+ const OperationParam = z.discriminatedUnion("operation", [
24
+ z.object({
25
+ operation: z.literal("deleteUser"),
26
+ params: adminOperations.deleteUser,
27
+ }),
28
+ z.object({
29
+ operation: z.literal("excludeUser"),
30
+ params: adminOperations.excludeUser,
31
+ }),
32
+ z.object({
33
+ operation: z.literal("restrictUser"),
34
+ params: adminOperations.restrictUser,
35
+ }),
36
+ z.object({
37
+ operation: z.literal("silenceUser"),
38
+ params: adminOperations.silenceUser,
39
+ }),
40
+ z.object({
41
+ operation: z.literal("searchUsers"),
42
+ params: adminOperations.searchUsers,
43
+ }),
44
+ z.object({
45
+ operation: z.literal("profanityClear"),
46
+ params: adminOperations.profanityClear,
47
+ }),
48
+ z.object({
49
+ operation: z.literal("removeAllScores"),
50
+ params: adminOperations.removeAllScores,
51
+ }),
52
+ z.object({
53
+ operation: z.literal("invalidateRankedScores"),
54
+ params: adminOperations.invalidateRankedScores,
55
+ }),
56
+ z.object({
57
+ operation: z.literal("unbanUser"),
58
+ params: adminOperations.unbanUser,
59
+ }),
60
+ ]);
61
+
62
+ export const Schema = {
63
+ input: z.strictObject({
64
+ session: z.string(),
65
+ data: OperationParam,
66
+ }),
67
+ output: z.object({
68
+ success: z.boolean(),
69
+ result: z.any().optional(),
70
+ error: z.string().optional(),
71
+ }),
72
+ };
73
+
74
+ export async function POST(request: Request): Promise<NextResponse> {
75
+ return protectedApi({
76
+ request,
77
+ schema: Schema,
78
+ authorization: () => {},
79
+ activity: handler,
80
+ });
81
+ }
82
+
83
+ export async function handler(
84
+ data: (typeof Schema)["input"]["_type"]
85
+ ): Promise<NextResponse<(typeof Schema)["output"]["_type"]>> {
86
+ // Get user from session
87
+ const user = (await getUserBySession(data.session)) as User;
88
+
89
+ // Get user's profile data
90
+ const { data: queryUserData, error: userError } = await supabase
91
+ .from("profiles")
92
+ .select("*")
93
+ .eq("uid", user.id)
94
+ .single();
95
+
96
+ if (userError || !queryUserData) {
97
+ return NextResponse.json(
98
+ {
99
+ success: false,
100
+ error: "User cannot be retrieved from session",
101
+ },
102
+ { status: 404 }
103
+ );
104
+ }
105
+ const tags = (queryUserData?.badges || []) as string[];
106
+ // Check if user has "Global Moderator" badge
107
+ const isGlobalModerator = tags.includes("Global Moderator");
108
+
109
+ if (!isGlobalModerator) {
110
+ return NextResponse.json(
111
+ {
112
+ success: false,
113
+ error: "Unauthorized. Only Global Moderators can perform this action.",
114
+ },
115
+ { status: 403 }
116
+ );
117
+ }
118
+
119
+ // Execute the requested admin operation
120
+ try {
121
+ let result;
122
+ const operation = data.data.operation;
123
+ const params = data.data.params as any;
124
+
125
+ switch (operation) {
126
+ case "deleteUser":
127
+ result = await supabase.rpc("admin_delete_user", {
128
+ user_id: params.userId,
129
+ });
130
+ break;
131
+
132
+ case "excludeUser":
133
+ result = await supabase.rpc("admin_exclude_user", {
134
+ user_id: params.userId,
135
+ });
136
+ break;
137
+
138
+ case "restrictUser":
139
+ result = await supabase.rpc("admin_restrict_user", {
140
+ user_id: params.userId,
141
+ });
142
+ break;
143
+
144
+ case "silenceUser":
145
+ result = await supabase.rpc("admin_silence_user", {
146
+ user_id: params.userId,
147
+ });
148
+ break;
149
+
150
+ case "searchUsers":
151
+ result = await supabase.rpc("admin_search_users", {
152
+ search_text: params.searchText,
153
+ });
154
+ break;
155
+
156
+ case "profanityClear":
157
+ result = await supabase.rpc("admin_profanity_clear", {
158
+ user_id: params.userId,
159
+ });
160
+ break;
161
+
162
+ case "removeAllScores":
163
+ result = await supabase.rpc("admin_remove_all_scores", {
164
+ user_id: params.userId,
165
+ });
166
+ break;
167
+
168
+ case "invalidateRankedScores":
169
+ result = await supabase.rpc("admin_invalidate_ranked_scores", {
170
+ user_id: params.userId,
171
+ });
172
+ break;
173
+
174
+ case "unbanUser":
175
+ result = await supabase.rpc("admin_unban_user", {
176
+ user_id: params.userId,
177
+ });
178
+ break;
179
+ }
180
+
181
+ // Log the admin action
182
+ await supabase.rpc("admin_log_action", {
183
+ admin_id: queryUserData.id,
184
+ action_type: operation,
185
+ target_id: "userId" in params ? params.userId : null,
186
+ details: { params },
187
+ });
188
+
189
+ if (result.error) {
190
+ return NextResponse.json(
191
+ {
192
+ success: false,
193
+ error: result.error.message,
194
+ },
195
+ { status: 500 }
196
+ );
197
+ }
198
+
199
+ return NextResponse.json({
200
+ success: true,
201
+ result: result.data,
202
+ });
203
+ } catch (err: any) {
204
+ return NextResponse.json(
205
+ {
206
+ success: false,
207
+ error: err.message || "An error occurred during the operation",
208
+ },
209
+ { status: 500 }
210
+ );
211
+ }
212
+ }
@@ -35,7 +35,7 @@ export async function handler(
35
35
  NextResponse.json({});
36
36
  }
37
37
 
38
- console.log("CHEATER: " + queryData);
38
+ console.log("CHEATER: " + JSON.stringify(queryData));
39
39
 
40
40
  return NextResponse.json({});
41
41
  }
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
  /*
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rhythia-api",
3
- "version": "197.0.0",
3
+ "version": "199.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,102 @@ 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
+ | {
620
+ Args: {
621
+ admin_id: number
622
+ action_type: string
623
+ target_id: number
624
+ details?: Json
625
+ }
626
+ Returns: undefined
627
+ }
628
+ | {
629
+ Args: {
630
+ admin_id: number
631
+ action_type: string
632
+ target_id: string
633
+ details?: Json
634
+ }
635
+ Returns: undefined
636
+ }
637
+ admin_profanity_clear: {
638
+ Args: {
639
+ user_id: number
640
+ }
641
+ Returns: boolean
642
+ }
643
+ admin_remove_all_scores: {
644
+ Args: {
645
+ user_id: number
646
+ }
647
+ Returns: number
648
+ }
649
+ admin_restrict_user: {
650
+ Args: {
651
+ user_id: number
652
+ }
653
+ Returns: boolean
654
+ }
655
+ admin_search_users: {
656
+ Args: {
657
+ search_text: string
658
+ }
659
+ Returns: {
660
+ about_me: string | null
661
+ avatar_url: string | null
662
+ badges: Json | null
663
+ ban: Database["public"]["Enums"]["banTypes"] | null
664
+ bannedAt: number | null
665
+ clan: number | null
666
+ computedUsername: string | null
667
+ created_at: number | null
668
+ flag: string | null
669
+ id: number
670
+ mu_rank: number
671
+ play_count: number | null
672
+ profile_image: string | null
673
+ sigma_rank: number | null
674
+ skill_points: number | null
675
+ spin_skill_points: number
676
+ squares_hit: number | null
677
+ total_score: number | null
678
+ uid: string | null
679
+ username: string | null
680
+ verificationDeadline: number
681
+ verified: boolean | null
682
+ }[]
683
+ }
684
+ admin_silence_user: {
685
+ Args: {
686
+ user_id: number
687
+ }
688
+ Returns: boolean
689
+ }
690
+ admin_unban_user: {
691
+ Args: {
692
+ user_id: number
693
+ }
694
+ Returns: boolean
695
+ }
544
696
  get_clan_leaderboard: {
545
697
  Args: {
546
698
  page_number?: number
@@ -755,6 +907,7 @@ export type Database = {
755
907
  userid: number
756
908
  username: string
757
909
  avatar_url: string
910
+ accuracy: number
758
911
  }[]
759
912
  }
760
913
  get_top_scores_for_beatmap2: {