rhythia-api 186.0.0 → 188.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 (60) hide show
  1. package/.prettierrc.json +6 -6
  2. package/api/acceptInvite.ts +79 -0
  3. package/api/addCollectionMap.ts +82 -82
  4. package/api/approveMap.ts +78 -78
  5. package/api/chartPublicStats.ts +32 -32
  6. package/api/createBeatmap.ts +208 -168
  7. package/api/createBeatmapPage.ts +64 -64
  8. package/api/createClan.ts +81 -81
  9. package/api/createCollection.ts +58 -58
  10. package/api/createInvite.ts +66 -0
  11. package/api/deleteBeatmapPage.ts +77 -77
  12. package/api/deleteCollection.ts +59 -59
  13. package/api/deleteCollectionMap.ts +71 -71
  14. package/api/editAboutMe.ts +91 -91
  15. package/api/editClan.ts +90 -90
  16. package/api/editCollection.ts +77 -77
  17. package/api/editProfile.ts +123 -123
  18. package/api/getAvatarUploadUrl.ts +85 -85
  19. package/api/getBadgedUsers.ts +56 -56
  20. package/api/getBeatmapComments.ts +57 -57
  21. package/api/getBeatmapPage.ts +106 -106
  22. package/api/getBeatmapPageById.ts +99 -99
  23. package/api/getBeatmapStarRating.ts +53 -53
  24. package/api/getBeatmaps.ts +159 -159
  25. package/api/getClan.ts +77 -77
  26. package/api/getClans.ts +44 -0
  27. package/api/getCollection.ts +130 -130
  28. package/api/getCollections.ts +132 -132
  29. package/api/getLeaderboard.ts +136 -136
  30. package/api/getMapUploadUrl.ts +93 -93
  31. package/api/getPassToken.ts +55 -55
  32. package/api/getProfile.ts +146 -146
  33. package/api/getPublicStats.ts +180 -180
  34. package/api/getRawStarRating.ts +57 -57
  35. package/api/getScore.ts +85 -85
  36. package/api/getTimestamp.ts +23 -23
  37. package/api/getUserScores.ts +175 -175
  38. package/api/nominateMap.ts +82 -82
  39. package/api/postBeatmapComment.ts +62 -59
  40. package/api/rankMapsArchive.ts +64 -64
  41. package/api/searchUsers.ts +56 -56
  42. package/api/setPasskey.ts +59 -59
  43. package/api/submitScore.ts +433 -433
  44. package/api/updateBeatmapPage.ts +229 -229
  45. package/handleApi.ts +21 -20
  46. package/index.html +2 -2
  47. package/index.ts +914 -863
  48. package/package.json +5 -2
  49. package/types/database.ts +43 -0
  50. package/utils/getUserBySession.ts +48 -48
  51. package/utils/requestUtils.ts +87 -87
  52. package/utils/security.ts +20 -20
  53. package/utils/star-calc/index.ts +72 -72
  54. package/utils/star-calc/osuUtils.ts +53 -53
  55. package/utils/star-calc/sspmParser.ts +398 -398
  56. package/utils/star-calc/sspmv1Parser.ts +165 -165
  57. package/utils/supabase.ts +13 -13
  58. package/utils/test +4 -4
  59. package/utils/validateToken.ts +7 -7
  60. 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,132 +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
- 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
- }
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
+ }