rhythia-api 168.0.0 → 170.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,70 @@
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 { getUserBySession } from "../utils/getUserBySession";
6
+ import { User } from "@supabase/supabase-js";
7
+
8
+ export const Schema = {
9
+ input: z.strictObject({
10
+ session: z.string(),
11
+ name: z.string(),
12
+ acronym: z.string(),
13
+ }),
14
+ output: z.object({
15
+ error: z.string().optional(),
16
+ }),
17
+ };
18
+
19
+ export async function POST(request: Request) {
20
+ return protectedApi({
21
+ request,
22
+ schema: Schema,
23
+ authorization: validUser,
24
+ activity: handler,
25
+ });
26
+ }
27
+
28
+ export async function handler(data: (typeof Schema)["input"]["_type"]) {
29
+ const user = (await getUserBySession(data.session)) as User;
30
+ let { data: queryUserData, error: userError } = await supabase
31
+ .from("profiles")
32
+ .select("*")
33
+ .eq("uid", user.id)
34
+ .single();
35
+
36
+ if (!queryUserData) {
37
+ return NextResponse.json({ error: "Can't find user" });
38
+ }
39
+
40
+ if (queryUserData.clan !== null) {
41
+ return NextResponse.json({
42
+ error: "You can't create clans while in a clan.",
43
+ });
44
+ }
45
+
46
+ if (queryUserData.ban !== "cool") {
47
+ return NextResponse.json({
48
+ error: "You are have an active ban, can't create a clan.",
49
+ });
50
+ }
51
+
52
+ if (data.name.length < 3 || data.name.length > 32) {
53
+ return NextResponse.json({
54
+ error: "Clan name must be between 3 and 32 characters.",
55
+ });
56
+ }
57
+ if (data.acronym.length < 3 || data.acronym.length > 6) {
58
+ return NextResponse.json({
59
+ error: "Acronyms must be of length between 3 and 6",
60
+ });
61
+ }
62
+
63
+ await supabase.from("clans").upsert({
64
+ name: data.name,
65
+ owner: queryUserData.id,
66
+ acronym: data.acronym.toUpperCase(),
67
+ });
68
+
69
+ return NextResponse.json({});
70
+ }
@@ -0,0 +1,90 @@
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 { getUserBySession } from "../utils/getUserBySession";
6
+ import { User } from "@supabase/supabase-js";
7
+
8
+ export const Schema = {
9
+ input: z.strictObject({
10
+ session: z.string(),
11
+ id: z.number(),
12
+ name: z.string(),
13
+ avatar_url: z.string(),
14
+ description: z.string(),
15
+ acronym: z.string(),
16
+ }),
17
+ output: z.object({
18
+ error: z.string().optional(),
19
+ }),
20
+ };
21
+
22
+ export async function POST(request: Request) {
23
+ return protectedApi({
24
+ request,
25
+ schema: Schema,
26
+ authorization: validUser,
27
+ activity: handler,
28
+ });
29
+ }
30
+
31
+ export async function handler(data: (typeof Schema)["input"]["_type"]) {
32
+ const user = (await getUserBySession(data.session)) as User;
33
+ let { data: queryUserData, error: userError } = await supabase
34
+ .from("profiles")
35
+ .select("*")
36
+ .eq("uid", user.id)
37
+ .single();
38
+
39
+ if (!queryUserData) {
40
+ return NextResponse.json({ error: "Can't find user" });
41
+ }
42
+
43
+ let { data: queryClanData, error: clanError } = await supabase
44
+ .from("clans")
45
+ .select("*")
46
+ .eq("id", data.id)
47
+ .single();
48
+
49
+ if (!queryClanData) {
50
+ return NextResponse.json({ error: "No such clan ID" });
51
+ }
52
+
53
+ if (queryClanData.owner !== queryUserData.id) {
54
+ return NextResponse.json({ error: "You are not the owner" });
55
+ }
56
+
57
+ if (queryUserData.ban !== "cool") {
58
+ return NextResponse.json({
59
+ error: "You are have an active ban, can't edit a clan.",
60
+ });
61
+ }
62
+
63
+ if (data.name.length < 3 || data.name.length > 32) {
64
+ return NextResponse.json({
65
+ error: "Clan name must be between 3 and 32 characters.",
66
+ });
67
+ }
68
+
69
+ if (data.acronym.length < 3 || data.acronym.length > 6) {
70
+ return NextResponse.json({
71
+ error: "Acronyms must be of length between 3 and 6",
72
+ });
73
+ }
74
+
75
+ if (data.description.length > 24000) {
76
+ return NextResponse.json({
77
+ error: "Description length exceeds 24000 characters",
78
+ });
79
+ }
80
+
81
+ await supabase.from("clans").upsert({
82
+ id: data.id,
83
+ name: data.name,
84
+ avatar_url: data.avatar_url,
85
+ description: data.description,
86
+ acronym: data.acronym.toUpperCase(),
87
+ });
88
+
89
+ return NextResponse.json({});
90
+ }
@@ -10,6 +10,7 @@ export const Schema = {
10
10
  session: z.string(),
11
11
  data: z.object({
12
12
  avatar_url: z.string().optional(),
13
+ profile_image: z.string().optional(),
13
14
  username: z.string().optional(),
14
15
  }),
15
16
  }),
package/api/getClan.ts ADDED
@@ -0,0 +1,79 @@
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
+
6
+ export const Schema = {
7
+ input: z.strictObject({
8
+ session: z.string(),
9
+ id: z.number(),
10
+ }),
11
+ output: z.object({
12
+ error: z.string().optional(),
13
+ acronym: z.string(),
14
+ avatar_url: z.string(),
15
+ created_at: z.number(),
16
+ description: z.string(),
17
+ id: z.number(),
18
+ name: z.string(),
19
+ owner: z.number(),
20
+ users: z.array(
21
+ z.object({
22
+ about_me: z.string().nullable(),
23
+ avatar_url: z.string().nullable(),
24
+ profile_image: z.string().nullable(),
25
+ badges: z.any().nullable(),
26
+ created_at: z.number().nullable(),
27
+ flag: z.string().nullable(),
28
+ id: z.number(),
29
+ uid: z.string().nullable(),
30
+ ban: z.string().nullable(),
31
+ username: z.string().nullable(),
32
+ verified: z.boolean().nullable(),
33
+ play_count: z.number().nullable(),
34
+ skill_points: z.number().nullable(),
35
+ squares_hit: z.number().nullable(),
36
+ total_score: z.number().nullable(),
37
+ position: z.number().nullable(),
38
+ is_online: z.boolean(),
39
+ })
40
+ ),
41
+ }),
42
+ };
43
+
44
+ export async function POST(request: Request) {
45
+ return protectedApi({
46
+ request,
47
+ schema: Schema,
48
+ authorization: () => {},
49
+ activity: handler,
50
+ });
51
+ }
52
+
53
+ export async function handler(data: (typeof Schema)["input"]["_type"]) {
54
+ let { data: queryClanData, error: clanError } = await supabase
55
+ .from("clans")
56
+ .select("*")
57
+ .eq("id", data.id)
58
+ .single();
59
+
60
+ if (!queryClanData) {
61
+ return NextResponse.json({ error: "No such clan ID" });
62
+ }
63
+
64
+ let { data: queryUsers, error: usersError } = await supabase
65
+ .from("profiles")
66
+ .select("*")
67
+ .eq("clan", queryClanData.id);
68
+
69
+ return NextResponse.json({
70
+ acronym: queryClanData.acronym,
71
+ avatar_url: queryClanData.avatar_url,
72
+ created_at: queryClanData.created_at,
73
+ description: queryClanData.description,
74
+ id: queryClanData.id,
75
+ name: queryClanData.name,
76
+ owner: queryClanData.owner,
77
+ users: queryUsers,
78
+ });
79
+ }
@@ -89,6 +89,18 @@ export async function handler({
89
89
 
90
90
  const user = (await getUserBySession(session)) as User;
91
91
 
92
+ let { data: leversData } = await supabase
93
+ .from("levers")
94
+ .select("*")
95
+ .eq("id", 1)
96
+ .single();
97
+
98
+ if (leversData && leversData.disable_scores) {
99
+ return NextResponse.json({
100
+ error: "Scores are temporarily disabled",
101
+ });
102
+ }
103
+
92
104
  let { data: userData, error: userError } = await supabase
93
105
  .from("profiles")
94
106
  .select("*")
@@ -174,7 +186,10 @@ export async function handler({
174
186
  multiplierMod *= Math.pow(0.95, data.misses);
175
187
  }
176
188
 
177
- if (beatmaps.starRating && beatmaps.starRating == data.virtualStars) {
189
+ if (
190
+ beatmaps.starRating &&
191
+ Math.abs(beatmaps.starRating - data.virtualStars) < 0.01
192
+ ) {
178
193
  awarded_sp = calculatePerformancePoints(
179
194
  data.speed * beatmaps.starRating * multiplierMod,
180
195
  accurracy
@@ -252,7 +267,7 @@ export async function handler({
252
267
  });
253
268
  console.log("p3");
254
269
 
255
- if (awarded_sp > 99) {
270
+ if (awarded_sp > 99 && userData.ban == "cool") {
256
271
  await postToWebhooks({
257
272
  rp: Math.round(awarded_sp * 100) / 100,
258
273
  username: userData.username || "",
@@ -268,6 +283,12 @@ export async function handler({
268
283
  });
269
284
  }
270
285
 
286
+ if (Math.abs((beatmaps.starRating || 0) - data.virtualStars) > 0.01) {
287
+ return NextResponse.json({
288
+ error: "Map mismatch, no RP points were awarded, please report the bug.",
289
+ });
290
+ }
291
+
271
292
  return NextResponse.json({});
272
293
  }
273
294
 
package/index.ts CHANGED
@@ -50,6 +50,23 @@ import { Schema as CreateBeatmapPage } from "./api/createBeatmapPage"
50
50
  export { Schema as SchemaCreateBeatmapPage } from "./api/createBeatmapPage"
51
51
  export const createBeatmapPage = handleApi({url:"/api/createBeatmapPage",...CreateBeatmapPage})
52
52
 
53
+ // ./api/createClan.ts API
54
+
55
+ /*
56
+ export const Schema = {
57
+ input: z.strictObject({
58
+ session: z.string(),
59
+ name: z.string(),
60
+ acronym: z.string(),
61
+ }),
62
+ output: z.object({
63
+ error: z.string().optional(),
64
+ }),
65
+ };*/
66
+ import { Schema as CreateClan } from "./api/createClan"
67
+ export { Schema as SchemaCreateClan } from "./api/createClan"
68
+ export const createClan = handleApi({url:"/api/createClan",...CreateClan})
69
+
53
70
  // ./api/deleteBeatmapPage.ts API
54
71
 
55
72
  /*
@@ -84,6 +101,26 @@ import { Schema as EditAboutMe } from "./api/editAboutMe"
84
101
  export { Schema as SchemaEditAboutMe } from "./api/editAboutMe"
85
102
  export const editAboutMe = handleApi({url:"/api/editAboutMe",...EditAboutMe})
86
103
 
104
+ // ./api/editClan.ts API
105
+
106
+ /*
107
+ export const Schema = {
108
+ input: z.strictObject({
109
+ session: z.string(),
110
+ id: z.number(),
111
+ name: z.string(),
112
+ avatar_url: z.string(),
113
+ description: z.string(),
114
+ acronym: z.string(),
115
+ }),
116
+ output: z.object({
117
+ error: z.string().optional(),
118
+ }),
119
+ };*/
120
+ import { Schema as EditClan } from "./api/editClan"
121
+ export { Schema as SchemaEditClan } from "./api/editClan"
122
+ export const editClan = handleApi({url:"/api/editClan",...EditClan})
123
+
87
124
  // ./api/editProfile.ts API
88
125
 
89
126
  /*
@@ -92,6 +129,7 @@ export const Schema = {
92
129
  session: z.string(),
93
130
  data: z.object({
94
131
  avatar_url: z.string().optional(),
132
+ profile_image: z.string().optional(),
95
133
  username: z.string().optional(),
96
134
  }),
97
135
  }),
@@ -323,6 +361,50 @@ import { Schema as GetBeatmapStarRating } from "./api/getBeatmapStarRating"
323
361
  export { Schema as SchemaGetBeatmapStarRating } from "./api/getBeatmapStarRating"
324
362
  export const getBeatmapStarRating = handleApi({url:"/api/getBeatmapStarRating",...GetBeatmapStarRating})
325
363
 
364
+ // ./api/getClan.ts API
365
+
366
+ /*
367
+ export const Schema = {
368
+ input: z.strictObject({
369
+ session: z.string(),
370
+ id: z.number(),
371
+ }),
372
+ output: z.object({
373
+ error: z.string().optional(),
374
+ acronym: z.string(),
375
+ avatar_url: z.string(),
376
+ created_at: z.number(),
377
+ description: z.string(),
378
+ id: z.number(),
379
+ name: z.string(),
380
+ owner: z.number(),
381
+ users: z.array(
382
+ z.object({
383
+ about_me: z.string().nullable(),
384
+ avatar_url: z.string().nullable(),
385
+ profile_image: z.string().nullable(),
386
+ badges: z.any().nullable(),
387
+ created_at: z.number().nullable(),
388
+ flag: z.string().nullable(),
389
+ id: z.number(),
390
+ uid: z.string().nullable(),
391
+ ban: z.string().nullable(),
392
+ username: z.string().nullable(),
393
+ verified: z.boolean().nullable(),
394
+ play_count: z.number().nullable(),
395
+ skill_points: z.number().nullable(),
396
+ squares_hit: z.number().nullable(),
397
+ total_score: z.number().nullable(),
398
+ position: z.number().nullable(),
399
+ is_online: z.boolean(),
400
+ })
401
+ ),
402
+ }),
403
+ };*/
404
+ import { Schema as GetClan } from "./api/getClan"
405
+ export { Schema as SchemaGetClan } from "./api/getClan"
406
+ export const getClan = handleApi({url:"/api/getClan",...GetClan})
407
+
326
408
  // ./api/getLeaderboard.ts API
327
409
 
328
410
  /*
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "rhythia-api",
3
- "version": "168.0.0",
3
+ "version": "170.0.0",
4
4
  "main": "index.ts",
5
+ "author": "online-contributors",
5
6
  "scripts": {
6
7
  "update": "bun ./scripts/update.ts",
7
8
  "ci-deploy": "tsx ./scripts/ci-deploy.ts",
package/types/database.ts CHANGED
@@ -153,6 +153,44 @@ export type Database = {
153
153
  }
154
154
  Relationships: []
155
155
  }
156
+ clans: {
157
+ Row: {
158
+ acronym: string | null
159
+ avatar_url: string | null
160
+ created_at: string
161
+ description: string | null
162
+ id: number
163
+ name: string
164
+ owner: number | null
165
+ }
166
+ Insert: {
167
+ acronym?: string | null
168
+ avatar_url?: string | null
169
+ created_at?: string
170
+ description?: string | null
171
+ id?: number
172
+ name?: string
173
+ owner?: number | null
174
+ }
175
+ Update: {
176
+ acronym?: string | null
177
+ avatar_url?: string | null
178
+ created_at?: string
179
+ description?: string | null
180
+ id?: number
181
+ name?: string
182
+ owner?: number | null
183
+ }
184
+ Relationships: [
185
+ {
186
+ foreignKeyName: "clans_owner_fkey"
187
+ columns: ["owner"]
188
+ isOneToOne: false
189
+ referencedRelation: "profiles"
190
+ referencedColumns: ["id"]
191
+ },
192
+ ]
193
+ }
156
194
  discordWebhooks: {
157
195
  Row: {
158
196
  id: number
@@ -168,6 +206,21 @@ export type Database = {
168
206
  }
169
207
  Relationships: []
170
208
  }
209
+ levers: {
210
+ Row: {
211
+ disable_scores: boolean
212
+ id: number
213
+ }
214
+ Insert: {
215
+ disable_scores?: boolean
216
+ id?: number
217
+ }
218
+ Update: {
219
+ disable_scores?: boolean
220
+ id?: number
221
+ }
222
+ Relationships: []
223
+ }
171
224
  passkeys: {
172
225
  Row: {
173
226
  email: string
@@ -216,6 +269,7 @@ export type Database = {
216
269
  badges: Json | null
217
270
  ban: Database["public"]["Enums"]["banTypes"] | null
218
271
  bannedAt: number | null
272
+ clan: number | null
219
273
  computedUsername: string | null
220
274
  created_at: number | null
221
275
  flag: string | null
@@ -238,6 +292,7 @@ export type Database = {
238
292
  badges?: Json | null
239
293
  ban?: Database["public"]["Enums"]["banTypes"] | null
240
294
  bannedAt?: number | null
295
+ clan?: number | null
241
296
  computedUsername?: string | null
242
297
  created_at?: number | null
243
298
  flag?: string | null
@@ -260,6 +315,7 @@ export type Database = {
260
315
  badges?: Json | null
261
316
  ban?: Database["public"]["Enums"]["banTypes"] | null
262
317
  bannedAt?: number | null
318
+ clan?: number | null
263
319
  computedUsername?: string | null
264
320
  created_at?: number | null
265
321
  flag?: string | null
@@ -276,7 +332,15 @@ export type Database = {
276
332
  username?: string | null
277
333
  verified?: boolean | null
278
334
  }
279
- Relationships: []
335
+ Relationships: [
336
+ {
337
+ foreignKeyName: "profiles_clan_fkey"
338
+ columns: ["clan"]
339
+ isOneToOne: false
340
+ referencedRelation: "clans"
341
+ referencedColumns: ["id"]
342
+ },
343
+ ]
280
344
  }
281
345
  scores: {
282
346
  Row: {
@@ -23,7 +23,22 @@ export async function protectedApi({
23
23
  const toParse = await request.json();
24
24
  const data = schema.input.parse(toParse);
25
25
 
26
- console.log({ ...data, session: undefined });
26
+ const dataClone = structuredClone(data);
27
+ if (dataClone) {
28
+ if (dataClone["token"]) {
29
+ dataClone["token"] = "********";
30
+ }
31
+ Object.keys(dataClone).forEach((key) => {
32
+ console.log("KEY: ", key, dataClone[key]);
33
+ if (key == "data") {
34
+ try {
35
+ Object.keys(dataClone[key]).forEach((key2) => {
36
+ console.log("KEY2: ", key2, dataClone[key][key2]);
37
+ });
38
+ } catch (error) {}
39
+ }
40
+ });
41
+ }
27
42
 
28
43
  setActivity(data);
29
44
  if (authorization) {
@@ -1,6 +1,7 @@
1
1
  export function validateIntrinsicToken(token: string) {
2
- if (!process.env.validationCode) return true;
3
- const generatedValidationCode = eval(process.env.validationCode);
4
- console.log(generatedValidationCode, token);
5
- return generatedValidationCode == token;
2
+ return true;
3
+ // if (!process.env.validationCode) return true;
4
+ // const generatedValidationCode = eval(process.env.validationCode);
5
+ // console.log(generatedValidationCode, token);
6
+ // return generatedValidationCode == token;
6
7
  }