rhythia-api 208.0.0 → 209.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.
@@ -34,22 +34,33 @@ export async function POST(request: Request): Promise<NextResponse> {
34
34
  export async function handler(
35
35
  data: (typeof Schema)["input"]["_type"]
36
36
  ): Promise<NextResponse<(typeof Schema)["output"]["_type"]>> {
37
- if (data.data.username !== undefined && data.data.username.length === 0) {
38
- return NextResponse.json(
39
- {
40
- error: "Username can't be empty",
41
- },
42
- { status: 404 }
43
- );
44
- }
37
+ if (data.data.username !== undefined) {
38
+ if (data.data.username.length < 3) {
39
+ return NextResponse.json(
40
+ {
41
+ error: "Username must be at least 3 characters long",
42
+ },
43
+ { status: 404 }
44
+ );
45
+ }
45
46
 
46
- if (data.data.username && data.data.username.length > 20) {
47
- return NextResponse.json(
48
- {
49
- error: "Username too long.",
50
- },
51
- { status: 404 }
52
- );
47
+ if (data.data.username.length > 20) {
48
+ return NextResponse.json(
49
+ {
50
+ error: "Username too long.",
51
+ },
52
+ { status: 404 }
53
+ );
54
+ }
55
+
56
+ if (!/^[a-z0-9]+$/i.test(data.data.username)) {
57
+ return NextResponse.json(
58
+ {
59
+ error: "Username can only contain letters (a-z) and numbers (0-9)",
60
+ },
61
+ { status: 404 }
62
+ );
63
+ }
53
64
  }
54
65
 
55
66
  if (validator.trim(data.data.username || "") !== (data.data.username || "")) {
@@ -11,6 +11,8 @@ const adminOperations = {
11
11
  deleteUser: z.object({ userId: z.number() }),
12
12
  changeFlag: z.object({ userId: z.number(), flag: z.string() }),
13
13
  changeBadges: z.object({ userId: z.number(), badges: z.string() }),
14
+ addBadge: z.object({ userId: z.number(), badge: z.string() }),
15
+ removeBadge: z.object({ userId: z.number(), badge: z.string() }),
14
16
  excludeUser: z.object({ userId: z.number() }),
15
17
  restrictUser: z.object({ userId: z.number() }),
16
18
  silenceUser: z.object({ userId: z.number() }),
@@ -73,6 +75,14 @@ const OperationParam = z.discriminatedUnion("operation", [
73
75
  operation: z.literal("changeBadges"),
74
76
  params: adminOperations.changeBadges,
75
77
  }),
78
+ z.object({
79
+ operation: z.literal("addBadge"),
80
+ params: adminOperations.addBadge,
81
+ }),
82
+ z.object({
83
+ operation: z.literal("removeBadge"),
84
+ params: adminOperations.removeBadge,
85
+ }),
76
86
  z.object({
77
87
  operation: z.literal("getScoresPaginated"),
78
88
  params: adminOperations.getScoresPaginated,
@@ -217,7 +227,55 @@ export async function handler(
217
227
  })
218
228
  .select();
219
229
  }
230
+ break;
231
+
232
+ case "addBadge":
233
+ // Allow only developers to modify badges.
234
+ if ((queryUserData.badges as string[]).includes("Developer")) {
235
+ // Get current badges
236
+ const { data: targetUser } = await supabase
237
+ .from("profiles")
238
+ .select("badges")
239
+ .eq("id", params.userId)
240
+ .single();
241
+
242
+ const currentBadges = (targetUser?.badges || []) as string[];
243
+ if (!currentBadges.includes(params.badge)) {
244
+ currentBadges.push(params.badge);
245
+ result = await supabase
246
+ .from("profiles")
247
+ .upsert({
248
+ id: params.userId,
249
+ badges: currentBadges,
250
+ })
251
+ .select();
252
+ } else {
253
+ result = { data: targetUser, error: null };
254
+ }
255
+ }
256
+ break;
220
257
 
258
+ case "removeBadge":
259
+ // Allow only developers to modify badges.
260
+ if ((queryUserData.badges as string[]).includes("Developer")) {
261
+ // Get current badges
262
+ const { data: targetUser } = await supabase
263
+ .from("profiles")
264
+ .select("badges")
265
+ .eq("id", params.userId)
266
+ .single();
267
+
268
+ const currentBadges = (targetUser?.badges || []) as string[];
269
+ const updatedBadges = currentBadges.filter(b => b !== params.badge);
270
+
271
+ result = await supabase
272
+ .from("profiles")
273
+ .upsert({
274
+ id: params.userId,
275
+ badges: updatedBadges,
276
+ })
277
+ .select();
278
+ }
221
279
  break;
222
280
 
223
281
  case "getScoresPaginated":
package/api/getProfile.ts CHANGED
@@ -98,13 +98,8 @@ export async function handler(
98
98
  avatar_url:
99
99
  "https://rhthia-avatars.s3.eu-central-003.backblazeb2.com/user-avatar-1725309193296-72002e6b-321c-4f60-a692-568e0e75147d",
100
100
  badges: [],
101
- username: `${user.user_metadata.full_name.slice(0, 20)}${Math.round(
102
- Math.random() * 900000 + 100000
103
- )}`,
104
- computedUsername: `${user.user_metadata.full_name.slice(
105
- 0,
106
- 20
107
- )}${Math.round(Math.random() * 900000 + 100000)}`.toLowerCase(),
101
+ username: `user${Math.round(Math.random() * 900000 + 100000)}`,
102
+ computedUsername: `user${Math.round(Math.random() * 900000 + 100000)}`.toLowerCase(),
108
103
  flag: (geo.country || "US").toUpperCase(),
109
104
  created_at: Date.now(),
110
105
  }).select(`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rhythia-api",
3
- "version": "208.0.0",
3
+ "version": "209.0.0",
4
4
  "main": "index.ts",
5
5
  "author": "online-contributors-cunev",
6
6
  "scripts": {