rhythia-api 182.0.0 → 185.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.
Files changed (58) hide show
  1. package/.prettierrc.json +6 -6
  2. package/api/addCollectionMap.ts +82 -82
  3. package/api/approveMap.ts +78 -78
  4. package/api/chartPublicStats.ts +32 -32
  5. package/api/createBeatmap.ts +168 -156
  6. package/api/createBeatmapPage.ts +64 -64
  7. package/api/createClan.ts +81 -81
  8. package/api/createCollection.ts +58 -58
  9. package/api/deleteBeatmapPage.ts +77 -72
  10. package/api/deleteCollection.ts +59 -59
  11. package/api/deleteCollectionMap.ts +71 -71
  12. package/api/editAboutMe.ts +91 -91
  13. package/api/editClan.ts +90 -90
  14. package/api/editCollection.ts +77 -77
  15. package/api/editProfile.ts +123 -123
  16. package/api/getAvatarUploadUrl.ts +85 -85
  17. package/api/getBadgedUsers.ts +56 -56
  18. package/api/getBeatmapComments.ts +57 -57
  19. package/api/getBeatmapPage.ts +106 -106
  20. package/api/getBeatmapPageById.ts +99 -99
  21. package/api/getBeatmapStarRating.ts +53 -53
  22. package/api/getBeatmaps.ts +159 -159
  23. package/api/getClan.ts +77 -77
  24. package/api/getCollection.ts +130 -130
  25. package/api/getCollections.ts +130 -126
  26. package/api/getLeaderboard.ts +136 -136
  27. package/api/getMapUploadUrl.ts +93 -93
  28. package/api/getPassToken.ts +55 -55
  29. package/api/getProfile.ts +146 -146
  30. package/api/getPublicStats.ts +180 -180
  31. package/api/getRawStarRating.ts +57 -57
  32. package/api/getScore.ts +85 -85
  33. package/api/getTimestamp.ts +23 -23
  34. package/api/getUserScores.ts +175 -175
  35. package/api/nominateMap.ts +82 -69
  36. package/api/postBeatmapComment.ts +59 -59
  37. package/api/rankMapsArchive.ts +64 -64
  38. package/api/searchUsers.ts +56 -56
  39. package/api/setPasskey.ts +59 -59
  40. package/api/submitScore.ts +433 -433
  41. package/api/updateBeatmapPage.ts +229 -229
  42. package/handleApi.ts +20 -20
  43. package/index.html +2 -2
  44. package/index.ts +866 -864
  45. package/package.json +2 -2
  46. package/types/database.ts +114 -1
  47. package/utils/getUserBySession.ts +48 -48
  48. package/utils/requestUtils.ts +87 -87
  49. package/utils/security.ts +20 -20
  50. package/utils/star-calc/index.ts +72 -72
  51. package/utils/star-calc/osuUtils.ts +53 -53
  52. package/utils/star-calc/sspmParser.ts +398 -398
  53. package/utils/star-calc/sspmv1Parser.ts +165 -165
  54. package/utils/supabase.ts +13 -13
  55. package/utils/test +4 -4
  56. package/utils/validateToken.ts +7 -7
  57. package/vercel.json +12 -12
  58. package/package-lock.json +0 -8913
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rhythia-api",
3
- "version": "182.0.0",
3
+ "version": "185.0.0",
4
4
  "main": "index.ts",
5
5
  "author": "online-contributors",
6
6
  "scripts": {
@@ -41,7 +41,7 @@
41
41
  "osu-standard-stable": "^5.0.0",
42
42
  "sharp": "^0.33.5",
43
43
  "simple-git": "^3.25.0",
44
- "supabase": "^2.1.1",
44
+ "supabase": "^2.9.6",
45
45
  "tsx": "^4.17.0",
46
46
  "utf-8-validate": "^6.0.4",
47
47
  "validator": "^13.12.0",
package/types/database.ts CHANGED
@@ -256,18 +256,21 @@ export type Database = {
256
256
  collection: number
257
257
  created_at: string
258
258
  id: number
259
+ sort: number
259
260
  }
260
261
  Insert: {
261
262
  beatmapPage?: number | null
262
263
  collection: number
263
264
  created_at?: string
264
265
  id?: number
266
+ sort?: number
265
267
  }
266
268
  Update: {
267
269
  beatmapPage?: number | null
268
270
  collection?: number
269
271
  created_at?: string
270
272
  id?: number
273
+ sort?: number
271
274
  }
272
275
  Relationships: [
273
276
  {
@@ -541,10 +544,120 @@ export type Database = {
541
544
  total_pages: number
542
545
  }[]
543
546
  }
544
- get_collections_v2: {
547
+ get_collections_v2:
548
+ | {
549
+ Args: {
550
+ page_number?: number
551
+ items_per_page?: number
552
+ }
553
+ Returns: {
554
+ id: number
555
+ title: string
556
+ description: string
557
+ created_at: string
558
+ owner: number
559
+ owner_username: string
560
+ owner_avatar_url: string
561
+ beatmap_count: number
562
+ star1: number
563
+ star2: number
564
+ star3: number
565
+ star4: number
566
+ star5: number
567
+ star6: number
568
+ star7: number
569
+ star8: number
570
+ star9: number
571
+ star10: number
572
+ star11: number
573
+ star12: number
574
+ star13: number
575
+ star14: number
576
+ star15: number
577
+ star16: number
578
+ star17: number
579
+ star18: number
580
+ total_pages: number
581
+ }[]
582
+ }
583
+ | {
584
+ Args: {
585
+ page_number?: number
586
+ items_per_page?: number
587
+ owner_filter?: number
588
+ }
589
+ Returns: {
590
+ id: number
591
+ title: string
592
+ description: string
593
+ created_at: string
594
+ owner: number
595
+ owner_username: string
596
+ owner_avatar_url: string
597
+ beatmap_count: number
598
+ star1: number
599
+ star2: number
600
+ star3: number
601
+ star4: number
602
+ star5: number
603
+ star6: number
604
+ star7: number
605
+ star8: number
606
+ star9: number
607
+ star10: number
608
+ star11: number
609
+ star12: number
610
+ star13: number
611
+ star14: number
612
+ star15: number
613
+ star16: number
614
+ star17: number
615
+ star18: number
616
+ total_pages: number
617
+ }[]
618
+ }
619
+ get_collections_v3: {
545
620
  Args: {
546
621
  page_number?: number
547
622
  items_per_page?: number
623
+ owner_filter?: number
624
+ }
625
+ Returns: {
626
+ id: number
627
+ title: string
628
+ description: string
629
+ created_at: string
630
+ owner: number
631
+ owner_username: string
632
+ owner_avatar_url: string
633
+ beatmap_count: number
634
+ star1: number
635
+ star2: number
636
+ star3: number
637
+ star4: number
638
+ star5: number
639
+ star6: number
640
+ star7: number
641
+ star8: number
642
+ star9: number
643
+ star10: number
644
+ star11: number
645
+ star12: number
646
+ star13: number
647
+ star14: number
648
+ star15: number
649
+ star16: number
650
+ star17: number
651
+ star18: number
652
+ total_pages: number
653
+ }[]
654
+ }
655
+ get_collections_v4: {
656
+ Args: {
657
+ page_number?: number
658
+ items_per_page?: number
659
+ owner_filter?: number
660
+ search_query?: string
548
661
  }
549
662
  Returns: {
550
663
  id: number
@@ -1,48 +1,48 @@
1
- import { User } from "@supabase/supabase-js";
2
- import { decryptString } from "./security";
3
- import { supabase } from "./supabase";
4
-
5
- export async function getUserBySession(session: string): Promise<User | null> {
6
- const user = (await supabase.auth.getUser(session)).data.user;
7
-
8
- if (user) {
9
- return user;
10
- }
11
-
12
- try {
13
- console.log("trying legacy token");
14
- const decryptedToken = JSON.parse(decryptString(session)) as {
15
- userId: number;
16
- email: string;
17
- passKey: string;
18
- computerName: string;
19
- };
20
-
21
- for (const key of Object.keys(decryptedToken)) {
22
- if (decryptedToken[key] === undefined || decryptedToken[key] === null) {
23
- return null;
24
- }
25
- }
26
-
27
- console.log(decryptedToken);
28
-
29
- let { data: queryPasskey, error } = await supabase
30
- .from("passkeys")
31
- .select("*,profiles(uid)")
32
- .eq("id", decryptedToken.userId)
33
- .eq("email", decryptedToken.email)
34
- .eq("passkey", decryptedToken.passKey)
35
- .single();
36
-
37
- console.log(queryPasskey);
38
-
39
- if (!queryPasskey) {
40
- return null;
41
- }
42
-
43
- return (await supabase.auth.admin.getUserById(queryPasskey.profiles?.uid!))
44
- .data.user;
45
- } catch (error) {}
46
-
47
- return null;
48
- }
1
+ import { User } from "@supabase/supabase-js";
2
+ import { decryptString } from "./security";
3
+ import { supabase } from "./supabase";
4
+
5
+ export async function getUserBySession(session: string): Promise<User | null> {
6
+ const user = (await supabase.auth.getUser(session)).data.user;
7
+
8
+ if (user) {
9
+ return user;
10
+ }
11
+
12
+ try {
13
+ console.log("trying legacy token");
14
+ const decryptedToken = JSON.parse(decryptString(session)) as {
15
+ userId: number;
16
+ email: string;
17
+ passKey: string;
18
+ computerName: string;
19
+ };
20
+
21
+ for (const key of Object.keys(decryptedToken)) {
22
+ if (decryptedToken[key] === undefined || decryptedToken[key] === null) {
23
+ return null;
24
+ }
25
+ }
26
+
27
+ console.log(decryptedToken);
28
+
29
+ let { data: queryPasskey, error } = await supabase
30
+ .from("passkeys")
31
+ .select("*,profiles(uid)")
32
+ .eq("id", decryptedToken.userId)
33
+ .eq("email", decryptedToken.email)
34
+ .eq("passkey", decryptedToken.passKey)
35
+ .single();
36
+
37
+ console.log(queryPasskey);
38
+
39
+ if (!queryPasskey) {
40
+ return null;
41
+ }
42
+
43
+ return (await supabase.auth.admin.getUserById(queryPasskey.profiles?.uid!))
44
+ .data.user;
45
+ } catch (error) {}
46
+
47
+ return null;
48
+ }
@@ -1,87 +1,87 @@
1
- import { NextResponse } from "next/server";
2
- import { set, ZodObject } from "zod";
3
- import { getUserBySession } from "./getUserBySession";
4
- import { supabase } from "./supabase";
5
-
6
- interface Props<
7
- K = (...args: any[]) => Promise<NextResponse<any>>,
8
- T = ZodObject<any>
9
- > {
10
- request: Request;
11
- schema: { input: T; output: T };
12
- authorization?: Function;
13
- activity: K;
14
- }
15
-
16
- export async function protectedApi({
17
- request,
18
- schema,
19
- authorization,
20
- activity,
21
- }: Props) {
22
- try {
23
- const toParse = await request.json();
24
- const data = schema.input.parse(toParse);
25
-
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
- }
42
-
43
- setActivity(data);
44
- if (authorization) {
45
- const authorizationResponse = await authorization(data);
46
- if (authorizationResponse) {
47
- return authorizationResponse;
48
- }
49
- }
50
- return await activity(data, request);
51
- } catch (error) {
52
- return NextResponse.json({ error: error.toString() }, { status: 400 });
53
- }
54
- }
55
-
56
- export async function setActivity(data: Record<string, any>) {
57
- if (data.session) {
58
- const user = (await supabase.auth.getUser(data.session)).data.user;
59
- if (user) {
60
- await supabase.from("profileActivities").upsert({
61
- uid: user.id,
62
- last_activity: Date.now(),
63
- });
64
- }
65
- }
66
- }
67
-
68
- export async function validUser(data) {
69
- if (!data.session) {
70
- return NextResponse.json(
71
- {
72
- error: "Session is missing",
73
- },
74
- { status: 501 }
75
- );
76
- }
77
-
78
- const user = await getUserBySession(data.session);
79
- if (!user) {
80
- return NextResponse.json(
81
- {
82
- error: "Invalid user session",
83
- },
84
- { status: 400 }
85
- );
86
- }
87
- }
1
+ import { NextResponse } from "next/server";
2
+ import { set, ZodObject } from "zod";
3
+ import { getUserBySession } from "./getUserBySession";
4
+ import { supabase } from "./supabase";
5
+
6
+ interface Props<
7
+ K = (...args: any[]) => Promise<NextResponse<any>>,
8
+ T = ZodObject<any>
9
+ > {
10
+ request: Request;
11
+ schema: { input: T; output: T };
12
+ authorization?: Function;
13
+ activity: K;
14
+ }
15
+
16
+ export async function protectedApi({
17
+ request,
18
+ schema,
19
+ authorization,
20
+ activity,
21
+ }: Props) {
22
+ try {
23
+ const toParse = await request.json();
24
+ const data = schema.input.parse(toParse);
25
+
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
+ }
42
+
43
+ setActivity(data);
44
+ if (authorization) {
45
+ const authorizationResponse = await authorization(data);
46
+ if (authorizationResponse) {
47
+ return authorizationResponse;
48
+ }
49
+ }
50
+ return await activity(data, request);
51
+ } catch (error) {
52
+ return NextResponse.json({ error: error.toString() }, { status: 400 });
53
+ }
54
+ }
55
+
56
+ export async function setActivity(data: Record<string, any>) {
57
+ if (data.session) {
58
+ const user = (await supabase.auth.getUser(data.session)).data.user;
59
+ if (user) {
60
+ await supabase.from("profileActivities").upsert({
61
+ uid: user.id,
62
+ last_activity: Date.now(),
63
+ });
64
+ }
65
+ }
66
+ }
67
+
68
+ export async function validUser(data) {
69
+ if (!data.session) {
70
+ return NextResponse.json(
71
+ {
72
+ error: "Session is missing",
73
+ },
74
+ { status: 501 }
75
+ );
76
+ }
77
+
78
+ const user = await getUserBySession(data.session);
79
+ if (!user) {
80
+ return NextResponse.json(
81
+ {
82
+ error: "Invalid user session",
83
+ },
84
+ { status: 400 }
85
+ );
86
+ }
87
+ }
package/utils/security.ts CHANGED
@@ -1,20 +1,20 @@
1
- import { xchacha20poly1305 } from "@noble/ciphers/chacha";
2
- import { managedNonce } from "@noble/ciphers/webcrypto";
3
- import {
4
- bytesToHex,
5
- bytesToUtf8,
6
- hexToBytes,
7
- utf8ToBytes,
8
- } from "@noble/ciphers/utils";
9
- const key = hexToBytes(process.env.TOKEN_SECRET || "");
10
- const chacha = managedNonce(xchacha20poly1305)(key); // manages nonces for you
11
-
12
- export function encryptString(str: string) {
13
- const data = utf8ToBytes(str);
14
- return bytesToHex(chacha.encrypt(data));
15
- }
16
-
17
- export function decryptString(str: string) {
18
- const data = hexToBytes(str);
19
- return bytesToUtf8(chacha.decrypt(data));
20
- }
1
+ import { xchacha20poly1305 } from "@noble/ciphers/chacha";
2
+ import { managedNonce } from "@noble/ciphers/webcrypto";
3
+ import {
4
+ bytesToHex,
5
+ bytesToUtf8,
6
+ hexToBytes,
7
+ utf8ToBytes,
8
+ } from "@noble/ciphers/utils";
9
+ const key = hexToBytes(process.env.TOKEN_SECRET || "");
10
+ const chacha = managedNonce(xchacha20poly1305)(key); // manages nonces for you
11
+
12
+ export function encryptString(str: string) {
13
+ const data = utf8ToBytes(str);
14
+ return bytesToHex(chacha.encrypt(data));
15
+ }
16
+
17
+ export function decryptString(str: string) {
18
+ const data = hexToBytes(str);
19
+ return bytesToUtf8(chacha.decrypt(data));
20
+ }
@@ -1,72 +1,72 @@
1
- import { BeatmapDecoder, BeatmapEncoder } from "osu-parsers";
2
- import { StandardRuleset } from "osu-standard-stable";
3
- import { sampleMap } from "./osuUtils";
4
- import { SSPMParsedMap } from "./sspmParser";
5
- import { SSPMMap, V1SSPMParser } from "./sspmv1Parser";
6
-
7
- export function rateMap(map: SSPMParsedMap) {
8
- let notes = map.markers
9
- .filter((marker) => marker.type === 0)
10
- .map((marker) => ({
11
- time: marker.position,
12
- x: marker.data["field0"].x,
13
- y: marker.data["field0"].y,
14
- }));
15
-
16
- return rate(notes);
17
- }
18
-
19
- export function rateMapNotes(map: [number, number, number][]) {
20
- let notes = map.map((marker) => ({
21
- time: marker[2],
22
- x: marker[0],
23
- y: marker[1],
24
- }));
25
-
26
- return rate(notes);
27
- }
28
-
29
- export function rateMapOld(map: SSPMMap) {
30
- let notes = map.notes.map((marker) => ({
31
- time: marker.position,
32
- x: marker.x,
33
- y: marker.y,
34
- }));
35
-
36
- return rate(notes);
37
- }
38
-
39
- export function rate(
40
- notes: {
41
- time: number;
42
- x: number;
43
- y: number;
44
- }[]
45
- ) {
46
- notes = notes.sort((a, b) => a.time - b.time);
47
- const decoder = new BeatmapDecoder();
48
- const beatmap1 = decoder.decodeFromString(sampleMap);
49
- let i = 0;
50
- while (i < notes.length - 1) {
51
- const note = notes[i];
52
- const nextNote = notes[i + 1];
53
- if (distance(note.x, note.y, nextNote.x, nextNote.y) < 1.25) {
54
- notes.splice(i + 1, 1);
55
- continue;
56
- }
57
-
58
- const hittable = beatmap1.hitObjects[0].clone();
59
- hittable.startX = Math.round((note.x / 2) * 100);
60
- hittable.startY = Math.round((note.y / 2) * 100);
61
- hittable.startTime = note.time;
62
- beatmap1.hitObjects.push(hittable);
63
- i++;
64
- }
65
- const ruleset = new StandardRuleset();
66
- const mods = ruleset.createModCombination("RX");
67
- const difficultyCalculator = ruleset.createDifficultyCalculator(beatmap1);
68
- const difficultyAttributes = difficultyCalculator.calculateWithMods(mods);
69
- return difficultyAttributes.starRating;
70
- }
71
-
72
- const distance = (x1, y1, x2, y2) => Math.hypot(x2 - x1, y2 - y1);
1
+ import { BeatmapDecoder, BeatmapEncoder } from "osu-parsers";
2
+ import { StandardRuleset } from "osu-standard-stable";
3
+ import { sampleMap } from "./osuUtils";
4
+ import { SSPMParsedMap } from "./sspmParser";
5
+ import { SSPMMap, V1SSPMParser } from "./sspmv1Parser";
6
+
7
+ export function rateMap(map: SSPMParsedMap) {
8
+ let notes = map.markers
9
+ .filter((marker) => marker.type === 0)
10
+ .map((marker) => ({
11
+ time: marker.position,
12
+ x: marker.data["field0"].x,
13
+ y: marker.data["field0"].y,
14
+ }));
15
+
16
+ return rate(notes);
17
+ }
18
+
19
+ export function rateMapNotes(map: [number, number, number][]) {
20
+ let notes = map.map((marker) => ({
21
+ time: marker[2],
22
+ x: marker[0],
23
+ y: marker[1],
24
+ }));
25
+
26
+ return rate(notes);
27
+ }
28
+
29
+ export function rateMapOld(map: SSPMMap) {
30
+ let notes = map.notes.map((marker) => ({
31
+ time: marker.position,
32
+ x: marker.x,
33
+ y: marker.y,
34
+ }));
35
+
36
+ return rate(notes);
37
+ }
38
+
39
+ export function rate(
40
+ notes: {
41
+ time: number;
42
+ x: number;
43
+ y: number;
44
+ }[]
45
+ ) {
46
+ notes = notes.sort((a, b) => a.time - b.time);
47
+ const decoder = new BeatmapDecoder();
48
+ const beatmap1 = decoder.decodeFromString(sampleMap);
49
+ let i = 0;
50
+ while (i < notes.length - 1) {
51
+ const note = notes[i];
52
+ const nextNote = notes[i + 1];
53
+ if (distance(note.x, note.y, nextNote.x, nextNote.y) < 1.25) {
54
+ notes.splice(i + 1, 1);
55
+ continue;
56
+ }
57
+
58
+ const hittable = beatmap1.hitObjects[0].clone();
59
+ hittable.startX = Math.round((note.x / 2) * 100);
60
+ hittable.startY = Math.round((note.y / 2) * 100);
61
+ hittable.startTime = note.time;
62
+ beatmap1.hitObjects.push(hittable);
63
+ i++;
64
+ }
65
+ const ruleset = new StandardRuleset();
66
+ const mods = ruleset.createModCombination("RX");
67
+ const difficultyCalculator = ruleset.createDifficultyCalculator(beatmap1);
68
+ const difficultyAttributes = difficultyCalculator.calculateWithMods(mods);
69
+ return difficultyAttributes.starRating;
70
+ }
71
+
72
+ const distance = (x1, y1, x2, y2) => Math.hypot(x2 - x1, y2 - y1);