rhythia-api 174.0.0 → 175.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,72 @@
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
+ }),
10
+ output: z.object({
11
+ collections: z.array(
12
+ z.object({
13
+ id: z.number(),
14
+ title: z.string(),
15
+ description: z.string(),
16
+ beatmapCount: z.number(),
17
+ createdAt: z.string().nullable().optional(),
18
+ updatedAt: z.string().nullable().optional(),
19
+ })
20
+ ),
21
+ error: z.string().optional(),
22
+ }),
23
+ };
24
+
25
+ export async function POST(request: Request) {
26
+ return protectedApi({
27
+ request,
28
+ schema: Schema,
29
+ authorization: () => {},
30
+ activity: handler,
31
+ });
32
+ }
33
+
34
+ export async function handler(data: (typeof Schema)["input"]["_type"]) {
35
+ // First get all collections
36
+ let { data: collections, error: collectionsError } = await supabase
37
+ .from("beatmapCollections")
38
+ .select(
39
+ `
40
+ *,
41
+ collectionRelations!left(count)
42
+ `
43
+ )
44
+ .returns<
45
+ Array<{
46
+ id: number;
47
+ title: string;
48
+ description: string;
49
+ created_at: string | null;
50
+ updated_at: string | null;
51
+ collectionRelations: Array<{ count: number }>;
52
+ }>
53
+ >();
54
+
55
+ if (collectionsError) {
56
+ return NextResponse.json({ error: "Error fetching collections" });
57
+ }
58
+
59
+ const formattedCollections =
60
+ collections?.map((collection) => ({
61
+ id: collection.id,
62
+ title: collection.title,
63
+ description: collection.description,
64
+ beatmapCount: collection.collectionRelations?.[0]?.count ?? 0,
65
+ createdAt: collection.created_at,
66
+ updatedAt: collection.updated_at,
67
+ })) || [];
68
+
69
+ return NextResponse.json({
70
+ collections: formattedCollections,
71
+ });
72
+ }
@@ -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,69 @@ 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
+ }),
495
+ output: z.object({
496
+ collections: z.array(
497
+ z.object({
498
+ id: z.number(),
499
+ title: z.string(),
500
+ description: z.string(),
501
+ beatmapCount: z.number(),
502
+ createdAt: z.string().nullable().optional(),
503
+ updatedAt: z.string().nullable().optional(),
504
+ })
505
+ ),
506
+ error: z.string().optional(),
507
+ }),
508
+ };*/
509
+ import { Schema as GetCollections } from "./api/getCollections"
510
+ export { Schema as SchemaGetCollections } from "./api/getCollections"
511
+ export const getCollections = handleApi({url:"/api/getCollections",...GetCollections})
512
+
417
513
  // ./api/getLeaderboard.ts API
418
514
 
419
515
  /*
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rhythia-api",
3
- "version": "174.0.0",
3
+ "version": "175.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
@@ -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)