rhythia-api 187.0.0 → 189.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.
- package/api/acceptInvite.ts +79 -0
- package/api/createBeatmap.ts +40 -0
- package/api/createInvite.ts +66 -0
- package/api/getClans.ts +45 -0
- package/api/postBeatmapComment.ts +3 -0
- package/api/submitScore.ts +1 -1
- package/index.ts +63 -11
- package/package.json +5 -2
- package/types/database.ts +843 -800
|
@@ -0,0 +1,79 @@
|
|
|
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
|
+
import short from "short-uuid";
|
|
8
|
+
import { error } from "console";
|
|
9
|
+
|
|
10
|
+
export const Schema = {
|
|
11
|
+
input: z.strictObject({
|
|
12
|
+
session: z.string(),
|
|
13
|
+
code: z.string(),
|
|
14
|
+
}),
|
|
15
|
+
output: z.object({
|
|
16
|
+
error: z.string().optional(),
|
|
17
|
+
}),
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export async function POST(request: Request) {
|
|
21
|
+
return protectedApi({
|
|
22
|
+
request,
|
|
23
|
+
schema: Schema,
|
|
24
|
+
authorization: validUser,
|
|
25
|
+
activity: handler,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function handler(data: (typeof Schema)["input"]["_type"]) {
|
|
30
|
+
const user = (await getUserBySession(data.session)) as User;
|
|
31
|
+
let { data: queryUserData, error: userError } = await supabase
|
|
32
|
+
.from("profiles")
|
|
33
|
+
.select("*")
|
|
34
|
+
.eq("uid", user.id)
|
|
35
|
+
.single();
|
|
36
|
+
|
|
37
|
+
if (!queryUserData) {
|
|
38
|
+
return NextResponse.json({ error: "Can't find user" });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let { data: codeData } = await supabase
|
|
42
|
+
.from("invites")
|
|
43
|
+
.select("*")
|
|
44
|
+
.eq("code", data.code)
|
|
45
|
+
.single();
|
|
46
|
+
|
|
47
|
+
if (!codeData || (codeData && codeData.used)) {
|
|
48
|
+
return NextResponse.json({ error: "Can't find code, or used" });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (codeData.type == "clan") {
|
|
52
|
+
if (queryUserData.clan) {
|
|
53
|
+
return NextResponse.json({
|
|
54
|
+
error: "You can't join another clan while being in a clan",
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
let { data: queryClanData, error: clanError } = await supabase
|
|
59
|
+
.from("clans")
|
|
60
|
+
.select("*")
|
|
61
|
+
.eq("id", Number(codeData.resourceId))
|
|
62
|
+
.single();
|
|
63
|
+
|
|
64
|
+
if (!queryClanData) {
|
|
65
|
+
return NextResponse.json({
|
|
66
|
+
error: "No such clan",
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
await supabase.from("profiles").upsert({
|
|
71
|
+
id: queryUserData.id,
|
|
72
|
+
clan: queryClanData?.id,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
return NextResponse.json({});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return NextResponse.json({ error: "Unknown invite type" });
|
|
79
|
+
}
|
package/api/createBeatmap.ts
CHANGED
|
@@ -7,6 +7,8 @@ import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
|
|
|
7
7
|
import { rateMap } from "../utils/star-calc";
|
|
8
8
|
import { getUserBySession } from "../utils/getUserBySession";
|
|
9
9
|
import { User } from "@supabase/supabase-js";
|
|
10
|
+
import { NodeHttpHandler } from "@aws-sdk/node-http-handler";
|
|
11
|
+
|
|
10
12
|
const s3Client = new S3Client({
|
|
11
13
|
region: "auto",
|
|
12
14
|
endpoint: "https://s3.eu-central-003.backblazeb2.com",
|
|
@@ -14,8 +16,46 @@ const s3Client = new S3Client({
|
|
|
14
16
|
secretAccessKey: process.env.SECRET_BUCKET || "",
|
|
15
17
|
accessKeyId: process.env.ACCESS_BUCKET || "",
|
|
16
18
|
},
|
|
19
|
+
requestChecksumCalculation: "WHEN_REQUIRED",
|
|
17
20
|
});
|
|
18
21
|
|
|
22
|
+
// Remove ALL validation and checksum middleware
|
|
23
|
+
const middlewareToRemove = [
|
|
24
|
+
"build:checksum",
|
|
25
|
+
"build:content-checksum",
|
|
26
|
+
"build:content-md5",
|
|
27
|
+
"validate",
|
|
28
|
+
"validateChecksum",
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
s3Client.middlewareStack.add(
|
|
32
|
+
(next) =>
|
|
33
|
+
async (args): Promise<any> => {
|
|
34
|
+
const request = args.request as RequestInit;
|
|
35
|
+
|
|
36
|
+
// Remove checksum headers
|
|
37
|
+
const headers = request.headers as Record<string, string>;
|
|
38
|
+
delete headers["x-amz-checksum-crc32"];
|
|
39
|
+
delete headers["x-amz-checksum-crc32c"];
|
|
40
|
+
delete headers["x-amz-checksum-sha1"];
|
|
41
|
+
delete headers["x-amz-checksum-sha256"];
|
|
42
|
+
request.headers = headers;
|
|
43
|
+
|
|
44
|
+
Object.entries(request.headers).forEach(
|
|
45
|
+
// @ts-ignore
|
|
46
|
+
([key, value]: [string, string]): void => {
|
|
47
|
+
if (!request.headers) {
|
|
48
|
+
request.headers = {};
|
|
49
|
+
}
|
|
50
|
+
(request.headers as Record<string, string>)[key] = value;
|
|
51
|
+
}
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
return next(args);
|
|
55
|
+
},
|
|
56
|
+
{ step: "build", name: "customHeaders" }
|
|
57
|
+
);
|
|
58
|
+
|
|
19
59
|
export const Schema = {
|
|
20
60
|
input: z.strictObject({
|
|
21
61
|
url: z.string(),
|
|
@@ -0,0 +1,66 @@
|
|
|
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
|
+
import short from "short-uuid";
|
|
8
|
+
|
|
9
|
+
export const Schema = {
|
|
10
|
+
input: z.strictObject({
|
|
11
|
+
session: z.string(),
|
|
12
|
+
type: z.string(),
|
|
13
|
+
resourceId: z.string(),
|
|
14
|
+
}),
|
|
15
|
+
output: z.object({
|
|
16
|
+
code: z.string().optional(),
|
|
17
|
+
error: z.string().optional(),
|
|
18
|
+
}),
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export async function POST(request: Request) {
|
|
22
|
+
return protectedApi({
|
|
23
|
+
request,
|
|
24
|
+
schema: Schema,
|
|
25
|
+
authorization: validUser,
|
|
26
|
+
activity: handler,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function handler(data: (typeof Schema)["input"]["_type"]) {
|
|
31
|
+
const user = (await getUserBySession(data.session)) as User;
|
|
32
|
+
let { data: queryUserData, error: userError } = await supabase
|
|
33
|
+
.from("profiles")
|
|
34
|
+
.select("*")
|
|
35
|
+
.eq("uid", user.id)
|
|
36
|
+
.single();
|
|
37
|
+
|
|
38
|
+
if (!queryUserData) {
|
|
39
|
+
return NextResponse.json({ error: "Can't find user" });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (data.type !== "clan") {
|
|
43
|
+
return NextResponse.json({
|
|
44
|
+
error: "Invites can only be created for clans",
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (data.resourceId !== queryUserData.clan?.toString()) {
|
|
49
|
+
return NextResponse.json({
|
|
50
|
+
error: "You can't create invite for a clan that you aren't owner of.",
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const invite = short.generate();
|
|
55
|
+
|
|
56
|
+
const upsertResult = await supabase
|
|
57
|
+
.from("invites")
|
|
58
|
+
.upsert({
|
|
59
|
+
code: invite,
|
|
60
|
+
type: "clan",
|
|
61
|
+
resourceId: data.resourceId,
|
|
62
|
+
})
|
|
63
|
+
.select();
|
|
64
|
+
|
|
65
|
+
return NextResponse.json({ code: invite });
|
|
66
|
+
}
|
package/api/getClans.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
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
|
+
page: z.number(),
|
|
11
|
+
session: z.any(),
|
|
12
|
+
}),
|
|
13
|
+
output: z.object({
|
|
14
|
+
clanData: z.array(
|
|
15
|
+
z.object({
|
|
16
|
+
id: z.number(),
|
|
17
|
+
name: z.string(),
|
|
18
|
+
acronym: z.string().nullable(),
|
|
19
|
+
avatar_url: z.string().nullable(),
|
|
20
|
+
description: z.string().nullable(),
|
|
21
|
+
member_count: z.number(),
|
|
22
|
+
total_skill_points: z.number(),
|
|
23
|
+
total_pages: z.number(),
|
|
24
|
+
})
|
|
25
|
+
),
|
|
26
|
+
error: z.string().optional(),
|
|
27
|
+
}),
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export async function POST(request: Request) {
|
|
31
|
+
return protectedApi({
|
|
32
|
+
request,
|
|
33
|
+
schema: Schema,
|
|
34
|
+
authorization: validUser,
|
|
35
|
+
activity: handler,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function handler(data: (typeof Schema)["input"]["_type"]) {
|
|
40
|
+
const { data: clanData, error } = await supabase.rpc("get_clan_leaderboard", {
|
|
41
|
+
page_number: data.page,
|
|
42
|
+
items_per_page: 25,
|
|
43
|
+
});
|
|
44
|
+
return NextResponse.json({ clanData, error });
|
|
45
|
+
}
|
|
@@ -42,6 +42,9 @@ export async function handler({
|
|
|
42
42
|
if (!userData) return NextResponse.json({ error: "No user." });
|
|
43
43
|
if (userData.ban !== "cool") return NextResponse.json({ error: "Error" });
|
|
44
44
|
|
|
45
|
+
if (content.length > 256)
|
|
46
|
+
return NextResponse.json({ error: "Comment exceeds length." });
|
|
47
|
+
|
|
45
48
|
const upserted = await supabase
|
|
46
49
|
.from("beatmapPageComments")
|
|
47
50
|
.upsert({
|
package/api/submitScore.ts
CHANGED
|
@@ -198,7 +198,7 @@ export async function handler({
|
|
|
198
198
|
|
|
199
199
|
if (
|
|
200
200
|
beatmaps.starRating &&
|
|
201
|
-
Math.abs(beatmaps.starRating - data.virtualStars) < 0.
|
|
201
|
+
Math.abs(beatmaps.starRating - data.virtualStars) < 0.1
|
|
202
202
|
) {
|
|
203
203
|
awarded_sp = calculatePerformancePoints(
|
|
204
204
|
data.speed * beatmaps.starRating * multiplierMod,
|
package/index.ts
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
import { handleApi } from "./handleApi"
|
|
2
2
|
|
|
3
|
+
// ./api/acceptInvite.ts API
|
|
4
|
+
|
|
5
|
+
/*
|
|
6
|
+
export const Schema = {
|
|
7
|
+
input: z.strictObject({
|
|
8
|
+
session: z.string(),
|
|
9
|
+
code: z.string(),
|
|
10
|
+
}),
|
|
11
|
+
output: z.object({
|
|
12
|
+
error: z.string().optional(),
|
|
13
|
+
}),
|
|
14
|
+
};*/
|
|
15
|
+
import { Schema as AcceptInvite } from "./api/acceptInvite"
|
|
16
|
+
export { Schema as SchemaAcceptInvite } from "./api/acceptInvite"
|
|
17
|
+
export const acceptInvite = handleApi({url:"/api/acceptInvite",...AcceptInvite})
|
|
18
|
+
|
|
3
19
|
// ./api/addCollectionMap.ts API
|
|
4
20
|
|
|
5
21
|
/*
|
|
@@ -47,17 +63,7 @@ export const chartPublicStats = handleApi({url:"/api/chartPublicStats",...ChartP
|
|
|
47
63
|
// ./api/createBeatmap.ts API
|
|
48
64
|
|
|
49
65
|
/*
|
|
50
|
-
|
|
51
|
-
input: z.strictObject({
|
|
52
|
-
url: z.string(),
|
|
53
|
-
session: z.string(),
|
|
54
|
-
updateFlag: z.boolean().optional(),
|
|
55
|
-
}),
|
|
56
|
-
output: z.strictObject({
|
|
57
|
-
hash: z.string().optional(),
|
|
58
|
-
error: z.string().optional(),
|
|
59
|
-
}),
|
|
60
|
-
};*/
|
|
66
|
+
*/
|
|
61
67
|
import { Schema as CreateBeatmap } from "./api/createBeatmap"
|
|
62
68
|
export { Schema as SchemaCreateBeatmap } from "./api/createBeatmap"
|
|
63
69
|
export const createBeatmap = handleApi({url:"/api/createBeatmap",...CreateBeatmap})
|
|
@@ -112,6 +118,24 @@ import { Schema as CreateCollection } from "./api/createCollection"
|
|
|
112
118
|
export { Schema as SchemaCreateCollection } from "./api/createCollection"
|
|
113
119
|
export const createCollection = handleApi({url:"/api/createCollection",...CreateCollection})
|
|
114
120
|
|
|
121
|
+
// ./api/createInvite.ts API
|
|
122
|
+
|
|
123
|
+
/*
|
|
124
|
+
export const Schema = {
|
|
125
|
+
input: z.strictObject({
|
|
126
|
+
session: z.string(),
|
|
127
|
+
type: z.string(),
|
|
128
|
+
resourceId: z.string(),
|
|
129
|
+
}),
|
|
130
|
+
output: z.object({
|
|
131
|
+
code: z.string().optional(),
|
|
132
|
+
error: z.string().optional(),
|
|
133
|
+
}),
|
|
134
|
+
};*/
|
|
135
|
+
import { Schema as CreateInvite } from "./api/createInvite"
|
|
136
|
+
export { Schema as SchemaCreateInvite } from "./api/createInvite"
|
|
137
|
+
export const createInvite = handleApi({url:"/api/createInvite",...CreateInvite})
|
|
138
|
+
|
|
115
139
|
// ./api/deleteBeatmapPage.ts API
|
|
116
140
|
|
|
117
141
|
/*
|
|
@@ -500,6 +524,34 @@ import { Schema as GetClan } from "./api/getClan"
|
|
|
500
524
|
export { Schema as SchemaGetClan } from "./api/getClan"
|
|
501
525
|
export const getClan = handleApi({url:"/api/getClan",...GetClan})
|
|
502
526
|
|
|
527
|
+
// ./api/getClans.ts API
|
|
528
|
+
|
|
529
|
+
/*
|
|
530
|
+
export const Schema = {
|
|
531
|
+
input: z.strictObject({
|
|
532
|
+
page: z.number(),
|
|
533
|
+
session: z.any(),
|
|
534
|
+
}),
|
|
535
|
+
output: z.object({
|
|
536
|
+
clanData: z.array(
|
|
537
|
+
z.object({
|
|
538
|
+
id: z.number(),
|
|
539
|
+
name: z.string(),
|
|
540
|
+
acronym: z.string().nullable(),
|
|
541
|
+
avatar_url: z.string().nullable(),
|
|
542
|
+
description: z.string().nullable(),
|
|
543
|
+
member_count: z.number(),
|
|
544
|
+
total_skill_points: z.number(),
|
|
545
|
+
total_pages: z.number(),
|
|
546
|
+
})
|
|
547
|
+
),
|
|
548
|
+
error: z.string().optional(),
|
|
549
|
+
}),
|
|
550
|
+
};*/
|
|
551
|
+
import { Schema as GetClans } from "./api/getClans"
|
|
552
|
+
export { Schema as SchemaGetClans } from "./api/getClans"
|
|
553
|
+
export const getClans = handleApi({url:"/api/getClans",...GetClans})
|
|
554
|
+
|
|
503
555
|
// ./api/getCollection.ts API
|
|
504
556
|
|
|
505
557
|
/*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rhythia-api",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "189.0.0",
|
|
4
4
|
"main": "index.ts",
|
|
5
5
|
"author": "online-contributors",
|
|
6
6
|
"scripts": {
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
"dependencies": {
|
|
16
16
|
"@2toad/profanity": "^3.0.0",
|
|
17
17
|
"@aws-sdk/client-s3": "^3.637.0",
|
|
18
|
+
"@aws-sdk/node-http-handler": "^3.374.0",
|
|
18
19
|
"@aws-sdk/s3-request-presigner": "^3.637.0",
|
|
19
20
|
"@netlify/zip-it-and-ship-it": "^9.37.9",
|
|
20
21
|
"@noble/ciphers": "^1.0.0",
|
|
@@ -40,10 +41,12 @@
|
|
|
40
41
|
"osu-parsers": "^4.1.7",
|
|
41
42
|
"osu-standard-stable": "^5.0.0",
|
|
42
43
|
"sharp": "^0.33.5",
|
|
44
|
+
"short-uuid": "^5.2.0",
|
|
43
45
|
"simple-git": "^3.25.0",
|
|
44
|
-
"supabase": "^2.
|
|
46
|
+
"supabase": "^2.15.8",
|
|
45
47
|
"tsx": "^4.17.0",
|
|
46
48
|
"utf-8-validate": "^6.0.4",
|
|
49
|
+
"uuid": "^11.1.0",
|
|
47
50
|
"validator": "^13.12.0",
|
|
48
51
|
"zero-width": "^1.0.29",
|
|
49
52
|
"zod": "^3.23.8"
|