rhythia-api 174.0.0 → 176.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
+ collection: z.number(),
12
+ beatmapPage: z.number(),
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
+ let { data: queryCollectionData, error: collectionError } = await supabase
41
+ .from("beatmapCollections")
42
+ .select("*")
43
+ .eq("id", data.collection)
44
+ .single();
45
+
46
+ let { data: queryBeatmapData, error: beatmapData } = await supabase
47
+ .from("beatmapPages")
48
+ .select("*")
49
+ .eq("id", data.beatmapPage)
50
+ .single();
51
+
52
+ if (!queryCollectionData) {
53
+ return NextResponse.json({ error: "Can't find collection" });
54
+ }
55
+
56
+ if (!queryBeatmapData) {
57
+ return NextResponse.json({ error: "Can't find beatmap page" });
58
+ }
59
+
60
+ if (queryCollectionData.owner !== queryUserData.id) {
61
+ return NextResponse.json({ error: "You can't update foreign collections" });
62
+ }
63
+
64
+ await supabase.from("collectionRelations").insert({
65
+ collection: data.collection,
66
+ beatmapPage: data.beatmapPage,
67
+ });
68
+
69
+ return NextResponse.json({});
70
+ }
@@ -0,0 +1,46 @@
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
+ title: z.string(),
12
+ }),
13
+ output: z.object({
14
+ error: z.string().optional(),
15
+ }),
16
+ };
17
+
18
+ export async function POST(request: Request) {
19
+ return protectedApi({
20
+ request,
21
+ schema: Schema,
22
+ authorization: validUser,
23
+ activity: handler,
24
+ });
25
+ }
26
+
27
+ export async function handler(data: (typeof Schema)["input"]["_type"]) {
28
+ const user = (await getUserBySession(data.session)) as User;
29
+ let { data: queryUserData, error: userError } = await supabase
30
+ .from("profiles")
31
+ .select("*")
32
+ .eq("uid", user.id)
33
+ .single();
34
+
35
+ if (!queryUserData) {
36
+ return NextResponse.json({ error: "Can't find user" });
37
+ }
38
+
39
+ await supabase.from("beatmapCollections").insert({
40
+ title: data.title,
41
+ description: "",
42
+ owner: queryUserData.id,
43
+ });
44
+
45
+ return NextResponse.json({});
46
+ }
@@ -0,0 +1,112 @@
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
+ beatmaps: z.array(
16
+ z.object({
17
+ id: z.number(),
18
+ playcount: z.number().nullable().optional(),
19
+ created_at: z.string().nullable().optional(),
20
+ difficulty: z.number().nullable().optional(),
21
+ length: z.number().nullable().optional(),
22
+ title: z.string().nullable().optional(),
23
+ ranked: z.boolean().nullable().optional(),
24
+ beatmapFile: z.string().nullable().optional(),
25
+ image: z.string().nullable().optional(),
26
+ starRating: z.number().nullable().optional(),
27
+ owner: z.number().nullable().optional(),
28
+ ownerUsername: z.string().nullable().optional(),
29
+ status: z.string().nullable().optional(),
30
+ tags: z.string().nullable().optional(),
31
+ })
32
+ ),
33
+ }),
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
+ let { data: queryCollectionData, error: collectionError } = await supabase
49
+ .from("beatmapCollections")
50
+ .select("*")
51
+ .eq("id", data.collection)
52
+ .single();
53
+
54
+ if (!queryCollectionData) {
55
+ return NextResponse.json({ error: "Can't find collection" });
56
+ }
57
+
58
+ let { data: queryBeatmaps, error: beatmapsError } = await supabase
59
+ .from("collectionRelations")
60
+ .select(
61
+ `
62
+ *,
63
+ beatmapPages!inner(
64
+ owner,
65
+ created_at,
66
+ id,
67
+ status,
68
+ tags,
69
+ beatmaps!inner(
70
+ playcount,
71
+ ranked,
72
+ beatmapFile,
73
+ image,
74
+ starRating,
75
+ difficulty,
76
+ length,
77
+ title
78
+ ),
79
+ profiles!inner(
80
+ username
81
+ )
82
+ )
83
+ `
84
+ )
85
+ .eq("collection", data.collection);
86
+
87
+ const formattedBeatmaps =
88
+ queryBeatmaps?.map((relation) => ({
89
+ id: relation.beatmapPages.id,
90
+ playcount: relation.beatmapPages.beatmaps.playcount,
91
+ created_at: relation.beatmapPages.created_at,
92
+ difficulty: relation.beatmapPages.beatmaps.difficulty,
93
+ length: relation.beatmapPages.beatmaps.length,
94
+ title: relation.beatmapPages.beatmaps.title,
95
+ ranked: relation.beatmapPages.beatmaps.ranked,
96
+ beatmapFile: relation.beatmapPages.beatmaps.beatmapFile,
97
+ image: relation.beatmapPages.beatmaps.image,
98
+ starRating: relation.beatmapPages.beatmaps.starRating,
99
+ owner: relation.beatmapPages.owner,
100
+ ownerUsername: relation.beatmapPages.profiles.username,
101
+ status: relation.beatmapPages.status,
102
+ tags: relation.beatmapPages.tags,
103
+ })) || [];
104
+
105
+ return NextResponse.json({
106
+ collection: {
107
+ title: queryCollectionData.title,
108
+ description: queryCollectionData.description,
109
+ beatmaps: formattedBeatmaps,
110
+ },
111
+ });
112
+ }
@@ -0,0 +1,101 @@
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
+ }),
12
+ output: z.object({
13
+ collections: z.array(
14
+ z.object({
15
+ id: z.number(),
16
+ title: z.string(),
17
+ description: z.string(),
18
+ beatmapCount: z.number(),
19
+ starRatingDistribution: z.array(
20
+ z.object({
21
+ stars: z.number(),
22
+ count: z.number(),
23
+ })
24
+ ),
25
+ createdAt: z.string(),
26
+ })
27
+ ),
28
+ totalPages: z.number(),
29
+ error: z.string().optional(),
30
+ }),
31
+ };
32
+
33
+ export async function POST(request: Request) {
34
+ return protectedApi({
35
+ request,
36
+ schema: Schema,
37
+ authorization: () => {},
38
+ activity: handler,
39
+ });
40
+ }
41
+
42
+ export async function handler(data: (typeof Schema)["input"]["_type"]) {
43
+ const { data: collections, error } = await supabase
44
+ .rpc("get_collections_v1", {
45
+ page_number: data.page,
46
+ items_per_page: data.itemsPerPage,
47
+ })
48
+ .returns<
49
+ {
50
+ id: number;
51
+ title: string;
52
+ description: string;
53
+ created_at: string;
54
+ beatmap_count: number;
55
+ star1: number;
56
+ star2: number;
57
+ star3: number;
58
+ star4: number;
59
+ star5: number;
60
+ star6: number;
61
+ star7: number;
62
+ star8: number;
63
+ star9: number;
64
+ star10: number;
65
+ total_pages: number;
66
+ }[]
67
+ >();
68
+
69
+ if (error) {
70
+ return NextResponse.json({ error: "Error fetching collections" });
71
+ }
72
+
73
+ // Get the total pages from the first row (all rows will have the same value)
74
+ const totalPages = collections?.[0]?.total_pages ?? 1;
75
+
76
+ const formattedCollections =
77
+ collections?.map((collection) => ({
78
+ id: collection.id,
79
+ title: collection.title,
80
+ description: collection.description,
81
+ beatmapCount: collection.beatmap_count,
82
+ starRatingDistribution: [
83
+ { stars: 1, count: collection.star1 },
84
+ { stars: 2, count: collection.star2 },
85
+ { stars: 3, count: collection.star3 },
86
+ { stars: 4, count: collection.star4 },
87
+ { stars: 5, count: collection.star5 },
88
+ { stars: 6, count: collection.star6 },
89
+ { stars: 7, count: collection.star7 },
90
+ { stars: 8, count: collection.star8 },
91
+ { stars: 9, count: collection.star9 },
92
+ { stars: 10, count: collection.star10 },
93
+ ],
94
+ createdAt: collection.created_at,
95
+ })) || [];
96
+
97
+ return NextResponse.json({
98
+ collections: formattedCollections,
99
+ totalPages,
100
+ });
101
+ }
@@ -1,7 +1,8 @@
1
1
  import { NextResponse } from "next/server";
2
2
  import z from "zod";
3
3
  import { protectedApi } from "../utils/requestUtils";
4
- import { calculatePerformancePoints, rateMapNotes } from "../utils/star-calc";
4
+ import { rateMapNotes } from "../utils/star-calc";
5
+ import { calculatePerformancePoints } from "./submitScore";
5
6
 
6
7
  export const Schema = {
7
8
  input: z.strictObject({
package/index.ts CHANGED
@@ -1,5 +1,22 @@
1
1
  import { handleApi } from "./handleApi"
2
2
 
3
+ // ./api/addCollectionMap.ts API
4
+
5
+ /*
6
+ export const Schema = {
7
+ input: z.strictObject({
8
+ session: z.string(),
9
+ collection: z.number(),
10
+ beatmapPage: z.number(),
11
+ }),
12
+ output: z.object({
13
+ error: z.string().optional(),
14
+ }),
15
+ };*/
16
+ import { Schema as AddCollectionMap } from "./api/addCollectionMap"
17
+ export { Schema as SchemaAddCollectionMap } from "./api/addCollectionMap"
18
+ export const addCollectionMap = handleApi({url:"/api/addCollectionMap",...AddCollectionMap})
19
+
3
20
  // ./api/approveMap.ts API
4
21
 
5
22
  /*
@@ -78,6 +95,22 @@ import { Schema as CreateClan } from "./api/createClan"
78
95
  export { Schema as SchemaCreateClan } from "./api/createClan"
79
96
  export const createClan = handleApi({url:"/api/createClan",...CreateClan})
80
97
 
98
+ // ./api/createCollection.ts API
99
+
100
+ /*
101
+ export const Schema = {
102
+ input: z.strictObject({
103
+ session: z.string(),
104
+ title: z.string(),
105
+ }),
106
+ output: z.object({
107
+ error: z.string().optional(),
108
+ }),
109
+ };*/
110
+ import { Schema as CreateCollection } from "./api/createCollection"
111
+ export { Schema as SchemaCreateCollection } from "./api/createCollection"
112
+ export const createCollection = handleApi({url:"/api/createCollection",...CreateCollection})
113
+
81
114
  // ./api/deleteBeatmapPage.ts API
82
115
 
83
116
  /*
@@ -414,6 +447,77 @@ import { Schema as GetClan } from "./api/getClan"
414
447
  export { Schema as SchemaGetClan } from "./api/getClan"
415
448
  export const getClan = handleApi({url:"/api/getClan",...GetClan})
416
449
 
450
+ // ./api/getCollection.ts API
451
+
452
+ /*
453
+ export const Schema = {
454
+ input: z.strictObject({
455
+ session: z.string(),
456
+ collection: z.number(),
457
+ }),
458
+ output: z.object({
459
+ collection: z.object({
460
+ title: z.string(),
461
+ description: z.string(),
462
+ beatmaps: z.array(
463
+ z.object({
464
+ id: z.number(),
465
+ playcount: z.number().nullable().optional(),
466
+ created_at: z.string().nullable().optional(),
467
+ difficulty: z.number().nullable().optional(),
468
+ length: z.number().nullable().optional(),
469
+ title: z.string().nullable().optional(),
470
+ ranked: z.boolean().nullable().optional(),
471
+ beatmapFile: z.string().nullable().optional(),
472
+ image: z.string().nullable().optional(),
473
+ starRating: z.number().nullable().optional(),
474
+ owner: z.number().nullable().optional(),
475
+ ownerUsername: z.string().nullable().optional(),
476
+ status: z.string().nullable().optional(),
477
+ tags: z.string().nullable().optional(),
478
+ })
479
+ ),
480
+ }),
481
+ error: z.string().optional(),
482
+ }),
483
+ };*/
484
+ import { Schema as GetCollection } from "./api/getCollection"
485
+ export { Schema as SchemaGetCollection } from "./api/getCollection"
486
+ export const getCollection = handleApi({url:"/api/getCollection",...GetCollection})
487
+
488
+ // ./api/getCollections.ts API
489
+
490
+ /*
491
+ export const Schema = {
492
+ input: z.strictObject({
493
+ session: z.string(),
494
+ page: z.number().optional().default(1),
495
+ itemsPerPage: z.number().optional().default(10),
496
+ }),
497
+ output: z.object({
498
+ collections: z.array(
499
+ z.object({
500
+ id: z.number(),
501
+ title: z.string(),
502
+ description: z.string(),
503
+ beatmapCount: z.number(),
504
+ starRatingDistribution: z.array(
505
+ z.object({
506
+ stars: z.number(),
507
+ count: z.number(),
508
+ })
509
+ ),
510
+ createdAt: z.string(),
511
+ })
512
+ ),
513
+ totalPages: z.number(),
514
+ error: z.string().optional(),
515
+ }),
516
+ };*/
517
+ import { Schema as GetCollections } from "./api/getCollections"
518
+ export { Schema as SchemaGetCollections } from "./api/getCollections"
519
+ export const getCollections = handleApi({url:"/api/getCollections",...GetCollections})
520
+
417
521
  // ./api/getLeaderboard.ts API
418
522
 
419
523
  /*
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rhythia-api",
3
- "version": "174.0.0",
3
+ "version": "176.0.0",
4
4
  "main": "index.ts",
5
5
  "author": "online-contributors",
6
6
  "scripts": {
package/types/database.ts CHANGED
@@ -9,6 +9,38 @@ export type Json =
9
9
  export type Database = {
10
10
  public: {
11
11
  Tables: {
12
+ beatmapCollections: {
13
+ Row: {
14
+ created_at: string
15
+ description: string
16
+ id: number
17
+ owner: number
18
+ title: string
19
+ }
20
+ Insert: {
21
+ created_at?: string
22
+ description: string
23
+ id?: number
24
+ owner: number
25
+ title: string
26
+ }
27
+ Update: {
28
+ created_at?: string
29
+ description?: string
30
+ id?: number
31
+ owner?: number
32
+ title?: string
33
+ }
34
+ Relationships: [
35
+ {
36
+ foreignKeyName: "beatmapCollections_owner_fkey"
37
+ columns: ["owner"]
38
+ isOneToOne: false
39
+ referencedRelation: "profiles"
40
+ referencedColumns: ["id"]
41
+ },
42
+ ]
43
+ }
12
44
  beatmapPageComments: {
13
45
  Row: {
14
46
  beatmapPage: number
@@ -215,6 +247,42 @@ export type Database = {
215
247
  },
216
248
  ]
217
249
  }
250
+ collectionRelations: {
251
+ Row: {
252
+ beatmapPage: number | null
253
+ collection: number
254
+ created_at: string
255
+ id: number
256
+ }
257
+ Insert: {
258
+ beatmapPage?: number | null
259
+ collection: number
260
+ created_at?: string
261
+ id?: number
262
+ }
263
+ Update: {
264
+ beatmapPage?: number | null
265
+ collection?: number
266
+ created_at?: string
267
+ id?: number
268
+ }
269
+ Relationships: [
270
+ {
271
+ foreignKeyName: "collectionRelations_beatmapPage_fkey"
272
+ columns: ["beatmapPage"]
273
+ isOneToOne: false
274
+ referencedRelation: "beatmapPages"
275
+ referencedColumns: ["id"]
276
+ },
277
+ {
278
+ foreignKeyName: "collectionRelations_collection_fkey"
279
+ columns: ["collection"]
280
+ isOneToOne: false
281
+ referencedRelation: "beatmapCollections"
282
+ referencedColumns: ["id"]
283
+ },
284
+ ]
285
+ }
218
286
  discordWebhooks: {
219
287
  Row: {
220
288
  id: number
@@ -437,7 +505,30 @@ export type Database = {
437
505
  [_ in never]: never
438
506
  }
439
507
  Functions: {
440
- [_ in never]: never
508
+ get_collections_v1: {
509
+ Args: {
510
+ page_number?: number
511
+ items_per_page?: number
512
+ }
513
+ Returns: {
514
+ id: number
515
+ title: string
516
+ description: string
517
+ created_at: string
518
+ beatmap_count: number
519
+ star1: number
520
+ star2: number
521
+ star3: number
522
+ star4: number
523
+ star5: number
524
+ star6: number
525
+ star7: number
526
+ star8: number
527
+ star9: number
528
+ star10: number
529
+ total_pages: number
530
+ }[]
531
+ }
441
532
  }
442
533
  Enums: {
443
534
  banTypes: "cool" | "silenced" | "restricted" | "excluded"
@@ -4,19 +4,6 @@ import { sampleMap } from "./osuUtils";
4
4
  import { SSPMParsedMap } from "./sspmParser";
5
5
  import { SSPMMap, V1SSPMParser } from "./sspmv1Parser";
6
6
 
7
- function easeInExpoDeq(x: number) {
8
- return x === 0 ? 0 : Math.pow(2, 35 * x - 35);
9
- }
10
-
11
- export function calculatePerformancePoints(
12
- starRating: number,
13
- accuracy: number
14
- ) {
15
- return Math.round(
16
- Math.pow((starRating * easeInExpoDeq(accuracy) * 100) / 2, 2) / 1000
17
- );
18
- }
19
-
20
7
  export function rateMap(map: SSPMParsedMap) {
21
8
  let notes = map.markers
22
9
  .filter((marker) => marker.type === 0)