rhythia-api 185.0.0 → 187.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 (57) 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 -168
  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 -77
  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 +132 -130
  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 -82
  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 +21 -20
  43. package/index.html +2 -2
  44. package/index.ts +867 -866
  45. package/package.json +1 -1
  46. package/types/database.ts +800 -798
  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
@@ -1,130 +1,130 @@
1
- import { NextResponse } from "next/server";
2
- import z from "zod";
3
- import { protectedApi } from "../utils/requestUtils";
4
- import { supabase } from "../utils/supabase";
5
-
6
- export const Schema = {
7
- input: z.strictObject({
8
- session: z.string(),
9
- collection: z.number(),
10
- }),
11
- output: z.object({
12
- collection: z.object({
13
- title: z.string(),
14
- description: z.string(),
15
- owner: z.object({
16
- id: z.number(),
17
- username: z.string(),
18
- }),
19
- isList: z.boolean(),
20
- beatmaps: z.array(
21
- z.object({
22
- id: z.number(),
23
- playcount: z.number().nullable().optional(),
24
- created_at: z.string().nullable().optional(),
25
- difficulty: z.number().nullable().optional(),
26
- length: z.number().nullable().optional(),
27
- title: z.string().nullable().optional(),
28
- ranked: z.boolean().nullable().optional(),
29
- beatmapFile: z.string().nullable().optional(),
30
- image: z.string().nullable().optional(),
31
- starRating: z.number().nullable().optional(),
32
- owner: z.number().nullable().optional(),
33
- ownerUsername: z.string().nullable().optional(),
34
- status: z.string().nullable().optional(),
35
- tags: z.string().nullable().optional(),
36
- })
37
- ),
38
- }),
39
- error: z.string().optional(),
40
- }),
41
- };
42
-
43
- export async function POST(request: Request) {
44
- return protectedApi({
45
- request,
46
- schema: Schema,
47
- authorization: () => {},
48
- activity: handler,
49
- });
50
- }
51
-
52
- export async function handler(data: (typeof Schema)["input"]["_type"]) {
53
- let { data: queryCollectionData, error: collectionError } = await supabase
54
- .from("beatmapCollections")
55
- .select(
56
- `
57
- *,
58
- profiles!inner(
59
- id,
60
- username
61
- )
62
- `
63
- )
64
- .eq("id", data.collection)
65
- .single();
66
-
67
- if (!queryCollectionData) {
68
- return NextResponse.json({ error: "Can't find collection" });
69
- }
70
-
71
- let { data: queryBeatmaps, error: beatmapsError } = await supabase
72
- .from("collectionRelations")
73
- .select(
74
- `
75
- *,
76
- beatmapPages!inner(
77
- owner,
78
- created_at,
79
- id,
80
- status,
81
- tags,
82
- beatmaps!inner(
83
- playcount,
84
- ranked,
85
- beatmapFile,
86
- image,
87
- starRating,
88
- difficulty,
89
- length,
90
- title
91
- ),
92
- profiles!inner(
93
- username
94
- )
95
- )
96
- `
97
- )
98
- .eq("collection", data.collection);
99
-
100
- const formattedBeatmaps =
101
- queryBeatmaps?.map((relation) => ({
102
- id: relation.beatmapPages.id,
103
- playcount: relation.beatmapPages.beatmaps.playcount,
104
- created_at: relation.beatmapPages.created_at,
105
- difficulty: relation.beatmapPages.beatmaps.difficulty,
106
- length: relation.beatmapPages.beatmaps.length,
107
- title: relation.beatmapPages.beatmaps.title,
108
- ranked: relation.beatmapPages.beatmaps.ranked,
109
- beatmapFile: relation.beatmapPages.beatmaps.beatmapFile,
110
- image: relation.beatmapPages.beatmaps.image,
111
- starRating: relation.beatmapPages.beatmaps.starRating,
112
- owner: relation.beatmapPages.owner,
113
- ownerUsername: relation.beatmapPages.profiles.username,
114
- status: relation.beatmapPages.status,
115
- tags: relation.beatmapPages.tags,
116
- })) || [];
117
-
118
- return NextResponse.json({
119
- collection: {
120
- owner: {
121
- username: queryCollectionData.profiles.username,
122
- id: queryCollectionData.profiles.id,
123
- },
124
- isList: queryCollectionData.is_list,
125
- title: queryCollectionData.title,
126
- description: queryCollectionData.description,
127
- beatmaps: formattedBeatmaps,
128
- },
129
- });
130
- }
1
+ import { NextResponse } from "next/server";
2
+ import z from "zod";
3
+ import { protectedApi } from "../utils/requestUtils";
4
+ import { supabase } from "../utils/supabase";
5
+
6
+ export const Schema = {
7
+ input: z.strictObject({
8
+ session: z.string(),
9
+ collection: z.number(),
10
+ }),
11
+ output: z.object({
12
+ collection: z.object({
13
+ title: z.string(),
14
+ description: z.string(),
15
+ owner: z.object({
16
+ id: z.number(),
17
+ username: z.string(),
18
+ }),
19
+ isList: z.boolean(),
20
+ beatmaps: z.array(
21
+ z.object({
22
+ id: z.number(),
23
+ playcount: z.number().nullable().optional(),
24
+ created_at: z.string().nullable().optional(),
25
+ difficulty: z.number().nullable().optional(),
26
+ length: z.number().nullable().optional(),
27
+ title: z.string().nullable().optional(),
28
+ ranked: z.boolean().nullable().optional(),
29
+ beatmapFile: z.string().nullable().optional(),
30
+ image: z.string().nullable().optional(),
31
+ starRating: z.number().nullable().optional(),
32
+ owner: z.number().nullable().optional(),
33
+ ownerUsername: z.string().nullable().optional(),
34
+ status: z.string().nullable().optional(),
35
+ tags: z.string().nullable().optional(),
36
+ })
37
+ ),
38
+ }),
39
+ error: z.string().optional(),
40
+ }),
41
+ };
42
+
43
+ export async function POST(request: Request) {
44
+ return protectedApi({
45
+ request,
46
+ schema: Schema,
47
+ authorization: () => {},
48
+ activity: handler,
49
+ });
50
+ }
51
+
52
+ export async function handler(data: (typeof Schema)["input"]["_type"]) {
53
+ let { data: queryCollectionData, error: collectionError } = await supabase
54
+ .from("beatmapCollections")
55
+ .select(
56
+ `
57
+ *,
58
+ profiles!inner(
59
+ id,
60
+ username
61
+ )
62
+ `
63
+ )
64
+ .eq("id", data.collection)
65
+ .single();
66
+
67
+ if (!queryCollectionData) {
68
+ return NextResponse.json({ error: "Can't find collection" });
69
+ }
70
+
71
+ let { data: queryBeatmaps, error: beatmapsError } = await supabase
72
+ .from("collectionRelations")
73
+ .select(
74
+ `
75
+ *,
76
+ beatmapPages!inner(
77
+ owner,
78
+ created_at,
79
+ id,
80
+ status,
81
+ tags,
82
+ beatmaps!inner(
83
+ playcount,
84
+ ranked,
85
+ beatmapFile,
86
+ image,
87
+ starRating,
88
+ difficulty,
89
+ length,
90
+ title
91
+ ),
92
+ profiles!inner(
93
+ username
94
+ )
95
+ )
96
+ `
97
+ )
98
+ .eq("collection", data.collection);
99
+
100
+ const formattedBeatmaps =
101
+ queryBeatmaps?.map((relation) => ({
102
+ id: relation.beatmapPages.id,
103
+ playcount: relation.beatmapPages.beatmaps.playcount,
104
+ created_at: relation.beatmapPages.created_at,
105
+ difficulty: relation.beatmapPages.beatmaps.difficulty,
106
+ length: relation.beatmapPages.beatmaps.length,
107
+ title: relation.beatmapPages.beatmaps.title,
108
+ ranked: relation.beatmapPages.beatmaps.ranked,
109
+ beatmapFile: relation.beatmapPages.beatmaps.beatmapFile,
110
+ image: relation.beatmapPages.beatmaps.image,
111
+ starRating: relation.beatmapPages.beatmaps.starRating,
112
+ owner: relation.beatmapPages.owner,
113
+ ownerUsername: relation.beatmapPages.profiles.username,
114
+ status: relation.beatmapPages.status,
115
+ tags: relation.beatmapPages.tags,
116
+ })) || [];
117
+
118
+ return NextResponse.json({
119
+ collection: {
120
+ owner: {
121
+ username: queryCollectionData.profiles.username,
122
+ id: queryCollectionData.profiles.id,
123
+ },
124
+ isList: queryCollectionData.is_list,
125
+ title: queryCollectionData.title,
126
+ description: queryCollectionData.description,
127
+ beatmaps: formattedBeatmaps,
128
+ },
129
+ });
130
+ }
@@ -1,130 +1,132 @@
1
- import { NextResponse } from "next/server";
2
- import z from "zod";
3
- import { protectedApi } from "../utils/requestUtils";
4
- import { supabase } from "../utils/supabase";
5
-
6
- export const Schema = {
7
- input: z.strictObject({
8
- session: z.string(),
9
- page: z.number().optional().default(1),
10
- itemsPerPage: z.number().optional().default(10),
11
- owner: z.number().optional(), // Added owner field
12
- search: z.string().optional(), // Added string field
13
- }),
14
- output: z.object({
15
- collections: z.array(
16
- z.object({
17
- id: z.number(),
18
- title: z.string(),
19
- description: z.string(),
20
- owner: z.number(),
21
- ownerUsername: z.string(),
22
- ownerAvatarUrl: z.string(),
23
- beatmapCount: z.number(),
24
- starRatingDistribution: z.array(
25
- z.object({
26
- stars: z.number(),
27
- count: z.number(),
28
- })
29
- ),
30
- createdAt: z.string(),
31
- })
32
- ),
33
- totalPages: z.number(),
34
- error: z.string().optional(),
35
- }),
36
- };
37
-
38
- export async function POST(request: Request) {
39
- return protectedApi({
40
- request,
41
- schema: Schema,
42
- authorization: () => {},
43
- activity: handler,
44
- });
45
- }
46
-
47
- export async function handler(data: (typeof Schema)["input"]["_type"]) {
48
- const { data: collections, error } = await supabase
49
- .rpc("get_collections_v4", {
50
- page_number: data.page,
51
- items_per_page: data.itemsPerPage,
52
- owner_filter: typeof data.owner === "number" ? data.owner : undefined,
53
- search_query: data.search || undefined,
54
- })
55
- .returns<
56
- {
57
- id: number;
58
- title: string;
59
- description: string;
60
- created_at: string;
61
- owner: number;
62
- owner_username: string;
63
- owner_avatar_url: string;
64
- beatmap_count: number;
65
- star1: number;
66
- star2: number;
67
- star3: number;
68
- star4: number;
69
- star5: number;
70
- star6: number;
71
- star7: number;
72
- star8: number;
73
- star9: number;
74
- star10: number;
75
- star11: number;
76
- star12: number;
77
- star13: number;
78
- star14: number;
79
- star15: number;
80
- star16: number;
81
- star17: number;
82
- star18: number;
83
- total_pages: number;
84
- }[]
85
- >();
86
-
87
- if (error) {
88
- return NextResponse.json({ error });
89
- }
90
-
91
- // Get the total pages from the first row (all rows will have the same value)
92
- const totalPages = collections?.[0]?.total_pages ?? 1;
93
-
94
- const formattedCollections =
95
- collections?.map((collection) => ({
96
- id: collection.id,
97
- title: collection.title,
98
- description: collection.description,
99
- owner: collection.owner,
100
- ownerUsername: collection.owner_username,
101
- ownerAvatarUrl: collection.owner_avatar_url,
102
- beatmapCount: collection.beatmap_count,
103
- starRatingDistribution: [
104
- { stars: 1, count: collection.star1 },
105
- { stars: 2, count: collection.star2 },
106
- { stars: 3, count: collection.star3 },
107
- { stars: 4, count: collection.star4 },
108
- { stars: 5, count: collection.star5 },
109
- { stars: 6, count: collection.star6 },
110
- { stars: 7, count: collection.star7 },
111
- { stars: 8, count: collection.star8 },
112
- { stars: 9, count: collection.star9 },
113
- { stars: 10, count: collection.star10 },
114
- { stars: 11, count: collection.star11 },
115
- { stars: 12, count: collection.star12 },
116
- { stars: 13, count: collection.star13 },
117
- { stars: 14, count: collection.star14 },
118
- { stars: 15, count: collection.star15 },
119
- { stars: 16, count: collection.star16 },
120
- { stars: 17, count: collection.star17 },
121
- { stars: 18, count: collection.star18 },
122
- ],
123
- createdAt: collection.created_at,
124
- })) || [];
125
-
126
- return NextResponse.json({
127
- collections: formattedCollections,
128
- totalPages,
129
- });
130
- }
1
+ import { NextResponse } from "next/server";
2
+ import z from "zod";
3
+ import { protectedApi } from "../utils/requestUtils";
4
+ import { supabase } from "../utils/supabase";
5
+
6
+ export const Schema = {
7
+ input: z.strictObject({
8
+ session: z.string(),
9
+ page: z.number().optional().default(1),
10
+ itemsPerPage: z.number().optional().default(10),
11
+ owner: z.number().optional(), // Added owner field
12
+ search: z.string().optional(), // Added string field
13
+ minBeatmaps: z.number().optional(), // Added string field
14
+ }),
15
+ output: z.object({
16
+ collections: z.array(
17
+ z.object({
18
+ id: z.number(),
19
+ title: z.string(),
20
+ description: z.string(),
21
+ owner: z.number(),
22
+ ownerUsername: z.string(),
23
+ ownerAvatarUrl: z.string(),
24
+ beatmapCount: z.number(),
25
+ starRatingDistribution: z.array(
26
+ z.object({
27
+ stars: z.number(),
28
+ count: z.number(),
29
+ })
30
+ ),
31
+ createdAt: z.string(),
32
+ })
33
+ ),
34
+ totalPages: z.number(),
35
+ error: z.string().optional(),
36
+ }),
37
+ };
38
+
39
+ export async function POST(request: Request) {
40
+ return protectedApi({
41
+ request,
42
+ schema: Schema,
43
+ authorization: () => {},
44
+ activity: handler,
45
+ });
46
+ }
47
+
48
+ export async function handler(data: (typeof Schema)["input"]["_type"]) {
49
+ const { data: collections, error } = await supabase
50
+ .rpc("get_collections_v4", {
51
+ page_number: data.page,
52
+ items_per_page: data.itemsPerPage,
53
+ owner_filter: typeof data.owner === "number" ? data.owner : undefined,
54
+ search_query: data.search || undefined,
55
+ min_beatmaps: data.minBeatmaps || 0,
56
+ })
57
+ .returns<
58
+ {
59
+ id: number;
60
+ title: string;
61
+ description: string;
62
+ created_at: string;
63
+ owner: number;
64
+ owner_username: string;
65
+ owner_avatar_url: string;
66
+ beatmap_count: number;
67
+ star1: number;
68
+ star2: number;
69
+ star3: number;
70
+ star4: number;
71
+ star5: number;
72
+ star6: number;
73
+ star7: number;
74
+ star8: number;
75
+ star9: number;
76
+ star10: number;
77
+ star11: number;
78
+ star12: number;
79
+ star13: number;
80
+ star14: number;
81
+ star15: number;
82
+ star16: number;
83
+ star17: number;
84
+ star18: number;
85
+ total_pages: number;
86
+ }[]
87
+ >();
88
+
89
+ if (error) {
90
+ return NextResponse.json({ error });
91
+ }
92
+
93
+ // Get the total pages from the first row (all rows will have the same value)
94
+ const totalPages = collections?.[0]?.total_pages ?? 1;
95
+
96
+ const formattedCollections =
97
+ collections?.map((collection) => ({
98
+ id: collection.id,
99
+ title: collection.title,
100
+ description: collection.description,
101
+ owner: collection.owner,
102
+ ownerUsername: collection.owner_username,
103
+ ownerAvatarUrl: collection.owner_avatar_url,
104
+ beatmapCount: collection.beatmap_count,
105
+ starRatingDistribution: [
106
+ { stars: 1, count: collection.star1 },
107
+ { stars: 2, count: collection.star2 },
108
+ { stars: 3, count: collection.star3 },
109
+ { stars: 4, count: collection.star4 },
110
+ { stars: 5, count: collection.star5 },
111
+ { stars: 6, count: collection.star6 },
112
+ { stars: 7, count: collection.star7 },
113
+ { stars: 8, count: collection.star8 },
114
+ { stars: 9, count: collection.star9 },
115
+ { stars: 10, count: collection.star10 },
116
+ { stars: 11, count: collection.star11 },
117
+ { stars: 12, count: collection.star12 },
118
+ { stars: 13, count: collection.star13 },
119
+ { stars: 14, count: collection.star14 },
120
+ { stars: 15, count: collection.star15 },
121
+ { stars: 16, count: collection.star16 },
122
+ { stars: 17, count: collection.star17 },
123
+ { stars: 18, count: collection.star18 },
124
+ ],
125
+ createdAt: collection.created_at,
126
+ })) || [];
127
+
128
+ return NextResponse.json({
129
+ collections: formattedCollections,
130
+ totalPages,
131
+ });
132
+ }