rhythia-api 197.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.
@@ -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
+ }
@@ -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": "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: {