rhythia-api 150.0.0 → 152.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/approveMap.ts +3 -1
- package/api/createBeatmap.ts +24 -1
- package/api/createBeatmapPage.ts +4 -2
- package/api/deleteBeatmapPage.ts +3 -1
- package/api/editAboutMe.ts +3 -1
- package/api/editProfile.ts +3 -1
- package/api/getAvatarUploadUrl.ts +3 -1
- package/api/getBadgedUsers.ts +1 -1
- package/api/getBeatmapPage.ts +3 -0
- package/api/getLeaderboard.ts +5 -3
- package/api/getMapUploadUrl.ts +3 -1
- package/api/getPassToken.ts +56 -0
- package/api/nominateMap.ts +3 -1
- package/api/postBeatmapComment.ts +3 -1
- package/api/rankMapsArchive.ts +3 -1
- package/api/setPasskey.ts +60 -0
- package/api/submitScore.ts +25 -7
- package/api/updateBeatmapPage.ts +5 -3
- package/index.ts +39 -0
- package/package.json +3 -2
- package/types/database.ts +45 -9
- package/utils/getUserBySession.ts +37 -0
- package/utils/requestUtils.ts +3 -15
- package/utils/security.ts +20 -0
- package/utils/star-calc/index.ts +25 -22
package/api/approveMap.ts
CHANGED
|
@@ -2,6 +2,8 @@ import { NextResponse } from "next/server";
|
|
|
2
2
|
import z from "zod";
|
|
3
3
|
import { protectedApi, validUser } from "../utils/requestUtils";
|
|
4
4
|
import { supabase } from "../utils/supabase";
|
|
5
|
+
import { getUserBySession } from "../utils/getUserBySession";
|
|
6
|
+
import { User } from "@supabase/supabase-js";
|
|
5
7
|
|
|
6
8
|
export const Schema = {
|
|
7
9
|
input: z.strictObject({
|
|
@@ -23,7 +25,7 @@ export async function POST(request: Request) {
|
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
export async function handler(data: (typeof Schema)["input"]["_type"]) {
|
|
26
|
-
const user = (await
|
|
28
|
+
const user = (await getUserBySession(data.session)) as User;
|
|
27
29
|
let { data: queryUserData, error: userError } = await supabase
|
|
28
30
|
.from("profiles")
|
|
29
31
|
.select("*")
|
package/api/createBeatmap.ts
CHANGED
|
@@ -5,6 +5,8 @@ import { SSPMParser } from "../utils/star-calc/sspmParser";
|
|
|
5
5
|
import { supabase } from "../utils/supabase";
|
|
6
6
|
import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
|
|
7
7
|
import { rateMap } from "../utils/star-calc";
|
|
8
|
+
import { getUserBySession } from "../utils/getUserBySession";
|
|
9
|
+
import { User } from "@supabase/supabase-js";
|
|
8
10
|
const s3Client = new S3Client({
|
|
9
11
|
region: "auto",
|
|
10
12
|
endpoint: "https://s3.eu-central-003.backblazeb2.com",
|
|
@@ -52,7 +54,7 @@ export async function handler({
|
|
|
52
54
|
const parsedData = parser.parse();
|
|
53
55
|
const digested = parsedData.strings.mapID;
|
|
54
56
|
|
|
55
|
-
const user = (await
|
|
57
|
+
const user = (await getUserBySession(session)) as User;
|
|
56
58
|
let { data: userData, error: userError } = await supabase
|
|
57
59
|
.from("profiles")
|
|
58
60
|
.select("*")
|
|
@@ -101,6 +103,15 @@ export async function handler({
|
|
|
101
103
|
.toBuffer();
|
|
102
104
|
} catch (error) {}
|
|
103
105
|
|
|
106
|
+
let largeBuffer = Buffer.from([]);
|
|
107
|
+
try {
|
|
108
|
+
largeBuffer = await require("sharp")(parsedData.cover)
|
|
109
|
+
.resize(850)
|
|
110
|
+
.jpeg({ mozjpeg: true })
|
|
111
|
+
.toBuffer();
|
|
112
|
+
} catch (error) {}
|
|
113
|
+
|
|
114
|
+
// Images
|
|
104
115
|
const command = new PutObjectCommand({
|
|
105
116
|
Bucket: "rhthia-avatars",
|
|
106
117
|
Key: imgkey,
|
|
@@ -109,6 +120,17 @@ export async function handler({
|
|
|
109
120
|
});
|
|
110
121
|
|
|
111
122
|
await s3Client.send(command);
|
|
123
|
+
|
|
124
|
+
const command2 = new PutObjectCommand({
|
|
125
|
+
Bucket: "rhthia-avatars",
|
|
126
|
+
Key: imgkey + "large",
|
|
127
|
+
Body: largeBuffer,
|
|
128
|
+
ContentType: "image/jpeg",
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
await s3Client.send(command2);
|
|
132
|
+
// Images End
|
|
133
|
+
|
|
112
134
|
const markers = parsedData.markers.sort((a, b) => a.position - b.position);
|
|
113
135
|
|
|
114
136
|
const upserted = await supabase.from("beatmaps").upsert({
|
|
@@ -120,6 +142,7 @@ export async function handler({
|
|
|
120
142
|
length: markers[markers.length - 1].position,
|
|
121
143
|
beatmapFile: url,
|
|
122
144
|
image: `https://static.rhythia.com/${imgkey}`,
|
|
145
|
+
imageLarge: `https://static.rhythia.com/${imgkey}large`,
|
|
123
146
|
starRating: rateMap(parsedData),
|
|
124
147
|
});
|
|
125
148
|
|
package/api/createBeatmapPage.ts
CHANGED
|
@@ -2,6 +2,8 @@ import { NextResponse } from "next/server";
|
|
|
2
2
|
import z from "zod";
|
|
3
3
|
import { protectedApi, validUser } from "../utils/requestUtils";
|
|
4
4
|
import { supabase } from "../utils/supabase";
|
|
5
|
+
import { getUserBySession } from "../utils/getUserBySession";
|
|
6
|
+
import { User } from "@supabase/supabase-js";
|
|
5
7
|
|
|
6
8
|
export const Schema = {
|
|
7
9
|
input: z.strictObject({
|
|
@@ -27,7 +29,7 @@ export async function handler({
|
|
|
27
29
|
}: (typeof Schema)["input"]["_type"]): Promise<
|
|
28
30
|
NextResponse<(typeof Schema)["output"]["_type"]>
|
|
29
31
|
> {
|
|
30
|
-
const user = (await
|
|
32
|
+
const user = (await getUserBySession(session)) as User;
|
|
31
33
|
let { data: userData, error: userError } = await supabase
|
|
32
34
|
.from("profiles")
|
|
33
35
|
.select("*")
|
|
@@ -49,7 +51,7 @@ export async function handler({
|
|
|
49
51
|
{ status: 404 }
|
|
50
52
|
);
|
|
51
53
|
}
|
|
52
|
-
|
|
54
|
+
|
|
53
55
|
const upserted = await supabase
|
|
54
56
|
.from("beatmapPages")
|
|
55
57
|
.upsert({
|
package/api/deleteBeatmapPage.ts
CHANGED
|
@@ -2,6 +2,8 @@ import { NextResponse } from "next/server";
|
|
|
2
2
|
import z from "zod";
|
|
3
3
|
import { protectedApi, validUser } from "../utils/requestUtils";
|
|
4
4
|
import { supabase } from "../utils/supabase";
|
|
5
|
+
import { getUserBySession } from "../utils/getUserBySession";
|
|
6
|
+
import { User } from "@supabase/supabase-js";
|
|
5
7
|
|
|
6
8
|
export const Schema = {
|
|
7
9
|
input: z.strictObject({
|
|
@@ -28,7 +30,7 @@ export async function handler({
|
|
|
28
30
|
}: (typeof Schema)["input"]["_type"]): Promise<
|
|
29
31
|
NextResponse<(typeof Schema)["output"]["_type"]>
|
|
30
32
|
> {
|
|
31
|
-
const user = (await
|
|
33
|
+
const user = (await getUserBySession(session)) as User;
|
|
32
34
|
let { data: userData, error: userError } = await supabase
|
|
33
35
|
.from("profiles")
|
|
34
36
|
.select("*")
|
package/api/editAboutMe.ts
CHANGED
|
@@ -3,6 +3,8 @@ import z from "zod";
|
|
|
3
3
|
import { Database } from "../types/database";
|
|
4
4
|
import { protectedApi, validUser } from "../utils/requestUtils";
|
|
5
5
|
import { supabase } from "../utils/supabase";
|
|
6
|
+
import { getUserBySession } from "../utils/getUserBySession";
|
|
7
|
+
import { User } from "@supabase/supabase-js";
|
|
6
8
|
export const Schema = {
|
|
7
9
|
input: z.strictObject({
|
|
8
10
|
session: z.string(),
|
|
@@ -45,7 +47,7 @@ export async function handler(
|
|
|
45
47
|
);
|
|
46
48
|
}
|
|
47
49
|
|
|
48
|
-
const user = (await
|
|
50
|
+
const user = (await getUserBySession(data.session)) as User;
|
|
49
51
|
let userData: Database["public"]["Tables"]["profiles"]["Update"];
|
|
50
52
|
|
|
51
53
|
// Find user's entry
|
package/api/editProfile.ts
CHANGED
|
@@ -3,6 +3,8 @@ import z from "zod";
|
|
|
3
3
|
import { Database } from "../types/database";
|
|
4
4
|
import { protectedApi, validUser } from "../utils/requestUtils";
|
|
5
5
|
import { supabase } from "../utils/supabase";
|
|
6
|
+
import { getUserBySession } from "../utils/getUserBySession";
|
|
7
|
+
import { User } from "@supabase/supabase-js";
|
|
6
8
|
export const Schema = {
|
|
7
9
|
input: z.strictObject({
|
|
8
10
|
session: z.string(),
|
|
@@ -46,7 +48,7 @@ export async function handler(
|
|
|
46
48
|
);
|
|
47
49
|
}
|
|
48
50
|
|
|
49
|
-
const user = (await
|
|
51
|
+
const user = (await getUserBySession(data.session)) as User;
|
|
50
52
|
|
|
51
53
|
let userData: Database["public"]["Tables"]["profiles"]["Update"];
|
|
52
54
|
|
|
@@ -10,6 +10,8 @@ import {
|
|
|
10
10
|
} from "@aws-sdk/client-s3";
|
|
11
11
|
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
|
12
12
|
import { validateIntrinsicToken } from "../utils/validateToken";
|
|
13
|
+
import { getUserBySession } from "../utils/getUserBySession";
|
|
14
|
+
import { User } from "@supabase/supabase-js";
|
|
13
15
|
|
|
14
16
|
const s3Client = new S3Client({
|
|
15
17
|
region: "auto",
|
|
@@ -51,7 +53,7 @@ export async function handler({
|
|
|
51
53
|
}: (typeof Schema)["input"]["_type"]): Promise<
|
|
52
54
|
NextResponse<(typeof Schema)["output"]["_type"]>
|
|
53
55
|
> {
|
|
54
|
-
const user = (await
|
|
56
|
+
const user = (await getUserBySession(session)) as User;
|
|
55
57
|
|
|
56
58
|
if (!validateIntrinsicToken(intrinsicToken)) {
|
|
57
59
|
return NextResponse.json({
|
package/api/getBadgedUsers.ts
CHANGED
package/api/getBeatmapPage.ts
CHANGED
|
@@ -23,6 +23,7 @@ export const Schema = {
|
|
|
23
23
|
ranked: z.boolean().nullable().optional(),
|
|
24
24
|
beatmapFile: z.string().nullable().optional(),
|
|
25
25
|
image: z.string().nullable().optional(),
|
|
26
|
+
imageLarge: z.string().nullable().optional(),
|
|
26
27
|
starRating: z.number().nullable().optional(),
|
|
27
28
|
owner: z.number().nullable().optional(),
|
|
28
29
|
ownerUsername: z.string().nullable().optional(),
|
|
@@ -60,6 +61,7 @@ export async function handler(
|
|
|
60
61
|
ranked,
|
|
61
62
|
beatmapFile,
|
|
62
63
|
image,
|
|
64
|
+
imageLarge,
|
|
63
65
|
starRating,
|
|
64
66
|
difficulty,
|
|
65
67
|
noteCount,
|
|
@@ -87,6 +89,7 @@ export async function handler(
|
|
|
87
89
|
ranked: beatmapPage.beatmaps?.ranked,
|
|
88
90
|
beatmapFile: beatmapPage.beatmaps?.beatmapFile,
|
|
89
91
|
image: beatmapPage.beatmaps?.image,
|
|
92
|
+
imageLarge: beatmapPage.beatmaps?.imageLarge,
|
|
90
93
|
starRating: beatmapPage.beatmaps?.starRating,
|
|
91
94
|
owner: beatmapPage.owner,
|
|
92
95
|
ownerUsername: beatmapPage.profiles?.username,
|
package/api/getLeaderboard.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { NextResponse } from "next/server";
|
|
2
2
|
import z from "zod";
|
|
3
|
-
import {
|
|
3
|
+
import { protectedApi } from "../utils/requestUtils";
|
|
4
4
|
import { supabase } from "../utils/supabase";
|
|
5
|
+
import { getUserBySession } from "../utils/getUserBySession";
|
|
6
|
+
import { User } from "@supabase/supabase-js";
|
|
5
7
|
|
|
6
8
|
export const Schema = {
|
|
7
9
|
input: z.strictObject({
|
|
@@ -48,7 +50,7 @@ export async function handler(
|
|
|
48
50
|
const VIEW_PER_PAGE = 50;
|
|
49
51
|
|
|
50
52
|
export async function getLeaderboard(page = 1, session: string) {
|
|
51
|
-
const getUserData = await
|
|
53
|
+
const getUserData = (await getUserBySession(session)) as User;
|
|
52
54
|
|
|
53
55
|
let leaderPosition = 0;
|
|
54
56
|
|
|
@@ -56,7 +58,7 @@ export async function getLeaderboard(page = 1, session: string) {
|
|
|
56
58
|
let { data: queryData, error } = await supabase
|
|
57
59
|
.from("profiles")
|
|
58
60
|
.select("*")
|
|
59
|
-
.eq("uid", getUserData.
|
|
61
|
+
.eq("uid", getUserData.id)
|
|
60
62
|
.single();
|
|
61
63
|
|
|
62
64
|
if (queryData) {
|
package/api/getMapUploadUrl.ts
CHANGED
|
@@ -10,6 +10,8 @@ import {
|
|
|
10
10
|
} from "@aws-sdk/client-s3";
|
|
11
11
|
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
|
12
12
|
import { validateIntrinsicToken } from "../utils/validateToken";
|
|
13
|
+
import { getUserBySession } from "../utils/getUserBySession";
|
|
14
|
+
import { User } from "@supabase/supabase-js";
|
|
13
15
|
|
|
14
16
|
const s3Client = new S3Client({
|
|
15
17
|
region: "auto",
|
|
@@ -53,7 +55,7 @@ export async function handler({
|
|
|
53
55
|
}: (typeof Schema)["input"]["_type"]): Promise<
|
|
54
56
|
NextResponse<(typeof Schema)["output"]["_type"]>
|
|
55
57
|
> {
|
|
56
|
-
const user = (await
|
|
58
|
+
const user = (await getUserBySession(session)) as User;
|
|
57
59
|
|
|
58
60
|
if (!validateIntrinsicToken(intrinsicToken)) {
|
|
59
61
|
return NextResponse.json({
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import z from "zod";
|
|
3
|
+
import { Database } from "../types/database";
|
|
4
|
+
import { protectedApi, validUser } from "../utils/requestUtils";
|
|
5
|
+
import { supabase } from "../utils/supabase";
|
|
6
|
+
import md5 from "md5";
|
|
7
|
+
import { encryptString } from "../utils/security";
|
|
8
|
+
export const Schema = {
|
|
9
|
+
input: z.strictObject({
|
|
10
|
+
data: z.object({
|
|
11
|
+
email: z.string(),
|
|
12
|
+
passkey: z.string(),
|
|
13
|
+
computerName: z.string(),
|
|
14
|
+
}),
|
|
15
|
+
}),
|
|
16
|
+
output: z.object({
|
|
17
|
+
token: z.string().optional(),
|
|
18
|
+
error: z.string().optional(),
|
|
19
|
+
}),
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export async function POST(request: Request): Promise<NextResponse> {
|
|
23
|
+
return protectedApi({
|
|
24
|
+
request,
|
|
25
|
+
schema: Schema,
|
|
26
|
+
authorization: () => {},
|
|
27
|
+
activity: handler,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function handler(
|
|
32
|
+
data: (typeof Schema)["input"]["_type"]
|
|
33
|
+
): Promise<NextResponse<(typeof Schema)["output"]["_type"]>> {
|
|
34
|
+
let { data: queryPasskey, error } = await supabase
|
|
35
|
+
.from("passkeys")
|
|
36
|
+
.select("*")
|
|
37
|
+
.eq("email", data.data.email)
|
|
38
|
+
.eq("passkey", data.data.passkey)
|
|
39
|
+
.single();
|
|
40
|
+
|
|
41
|
+
if (!queryPasskey) {
|
|
42
|
+
return NextResponse.json({
|
|
43
|
+
error: "Wrong combination",
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
return NextResponse.json({
|
|
47
|
+
token: encryptString(
|
|
48
|
+
JSON.stringify({
|
|
49
|
+
userId: queryPasskey.id,
|
|
50
|
+
email: queryPasskey.email,
|
|
51
|
+
passKey: queryPasskey.passkey,
|
|
52
|
+
computerName: data.data.computerName,
|
|
53
|
+
})
|
|
54
|
+
),
|
|
55
|
+
});
|
|
56
|
+
}
|
package/api/nominateMap.ts
CHANGED
|
@@ -2,6 +2,8 @@ import { NextResponse } from "next/server";
|
|
|
2
2
|
import z from "zod";
|
|
3
3
|
import { protectedApi, validUser } from "../utils/requestUtils";
|
|
4
4
|
import { supabase } from "../utils/supabase";
|
|
5
|
+
import { getUserBySession } from "../utils/getUserBySession";
|
|
6
|
+
import { User } from "@supabase/supabase-js";
|
|
5
7
|
|
|
6
8
|
export const Schema = {
|
|
7
9
|
input: z.strictObject({
|
|
@@ -23,7 +25,7 @@ export async function POST(request: Request) {
|
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
export async function handler(data: (typeof Schema)["input"]["_type"]) {
|
|
26
|
-
const user = (await
|
|
28
|
+
const user = (await getUserBySession(data.session)) as User;
|
|
27
29
|
let { data: queryUserData, error: userError } = await supabase
|
|
28
30
|
.from("profiles")
|
|
29
31
|
.select("*")
|
|
@@ -2,6 +2,8 @@ import { NextResponse } from "next/server";
|
|
|
2
2
|
import z from "zod";
|
|
3
3
|
import { protectedApi, validUser } from "../utils/requestUtils";
|
|
4
4
|
import { supabase } from "../utils/supabase";
|
|
5
|
+
import { User } from "@supabase/supabase-js";
|
|
6
|
+
import { getUserBySession } from "../utils/getUserBySession";
|
|
5
7
|
|
|
6
8
|
export const Schema = {
|
|
7
9
|
input: z.strictObject({
|
|
@@ -30,7 +32,7 @@ export async function handler({
|
|
|
30
32
|
}: (typeof Schema)["input"]["_type"]): Promise<
|
|
31
33
|
NextResponse<(typeof Schema)["output"]["_type"]>
|
|
32
34
|
> {
|
|
33
|
-
const user = (await
|
|
35
|
+
const user = (await getUserBySession(session)) as User;
|
|
34
36
|
let { data: userData, error: userError } = await supabase
|
|
35
37
|
.from("profiles")
|
|
36
38
|
.select("*")
|
package/api/rankMapsArchive.ts
CHANGED
|
@@ -2,6 +2,8 @@ import { NextResponse } from "next/server";
|
|
|
2
2
|
import z from "zod";
|
|
3
3
|
import { protectedApi, validUser } from "../utils/requestUtils";
|
|
4
4
|
import { supabase } from "../utils/supabase";
|
|
5
|
+
import { getUserBySession } from "../utils/getUserBySession";
|
|
6
|
+
import { User } from "@supabase/supabase-js";
|
|
5
7
|
|
|
6
8
|
export const Schema = {
|
|
7
9
|
input: z.strictObject({
|
|
@@ -23,7 +25,7 @@ export async function POST(request: Request) {
|
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
export async function handler(data: (typeof Schema)["input"]["_type"]) {
|
|
26
|
-
const user = (await
|
|
28
|
+
const user = (await getUserBySession(data.session)) as User;
|
|
27
29
|
let { data: queryUserData, error: userError } = await supabase
|
|
28
30
|
.from("profiles")
|
|
29
31
|
.select("*")
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import z from "zod";
|
|
3
|
+
import { Database } from "../types/database";
|
|
4
|
+
import { protectedApi, validUser } from "../utils/requestUtils";
|
|
5
|
+
import { supabase } from "../utils/supabase";
|
|
6
|
+
import md5 from "md5";
|
|
7
|
+
import { getUserBySession } from "../utils/getUserBySession";
|
|
8
|
+
import { User } from "@supabase/supabase-js";
|
|
9
|
+
export const Schema = {
|
|
10
|
+
input: z.strictObject({
|
|
11
|
+
session: z.string(),
|
|
12
|
+
data: z.object({
|
|
13
|
+
passkey: z.string(),
|
|
14
|
+
}),
|
|
15
|
+
}),
|
|
16
|
+
output: z.object({
|
|
17
|
+
error: z.string().optional(),
|
|
18
|
+
}),
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export async function POST(request: Request): Promise<NextResponse> {
|
|
22
|
+
return protectedApi({
|
|
23
|
+
request,
|
|
24
|
+
schema: Schema,
|
|
25
|
+
authorization: () => {},
|
|
26
|
+
activity: handler,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function handler(
|
|
31
|
+
data: (typeof Schema)["input"]["_type"]
|
|
32
|
+
): Promise<NextResponse<(typeof Schema)["output"]["_type"]>> {
|
|
33
|
+
const user = (await getUserBySession(data.session)) as User;
|
|
34
|
+
let userData: Database["public"]["Tables"]["profiles"]["Update"];
|
|
35
|
+
// Find user's entry
|
|
36
|
+
{
|
|
37
|
+
let { data: queryUserData, error } = await supabase
|
|
38
|
+
.from("profiles")
|
|
39
|
+
.select("*")
|
|
40
|
+
.eq("uid", user.id);
|
|
41
|
+
|
|
42
|
+
if (!queryUserData?.length) {
|
|
43
|
+
return NextResponse.json(
|
|
44
|
+
{
|
|
45
|
+
error: "User cannot be retrieved from session",
|
|
46
|
+
},
|
|
47
|
+
{ status: 404 }
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
userData = queryUserData[0];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
await supabase.from("passkeys").upsert({
|
|
54
|
+
id: userData.id!,
|
|
55
|
+
email: user.email!,
|
|
56
|
+
passkey: md5(data.data.passkey),
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
return NextResponse.json({});
|
|
60
|
+
}
|
package/api/submitScore.ts
CHANGED
|
@@ -2,6 +2,10 @@ import { NextResponse } from "next/server";
|
|
|
2
2
|
import z from "zod";
|
|
3
3
|
import { protectedApi, validUser } from "../utils/requestUtils";
|
|
4
4
|
import { supabase } from "../utils/supabase";
|
|
5
|
+
import { decryptString } from "../utils/security";
|
|
6
|
+
import { isEqual } from "lodash";
|
|
7
|
+
import { getUserBySession } from "../utils/getUserBySession";
|
|
8
|
+
import { User } from "@supabase/supabase-js";
|
|
5
9
|
|
|
6
10
|
export const Schema = {
|
|
7
11
|
input: z.strictObject({
|
|
@@ -50,13 +54,27 @@ export async function handler({
|
|
|
50
54
|
}: (typeof Schema)["input"]["_type"]): Promise<
|
|
51
55
|
NextResponse<(typeof Schema)["output"]["_type"]>
|
|
52
56
|
> {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
const tokenContents = JSON.parse(decryptString(data.token));
|
|
58
|
+
if (
|
|
59
|
+
!isEqual(tokenContents, {
|
|
60
|
+
relayHwid: data.relayHwid,
|
|
61
|
+
songId: data.songId,
|
|
62
|
+
misses: data.misses,
|
|
63
|
+
hits: data.hits,
|
|
64
|
+
mapHash: data.mapHash,
|
|
65
|
+
mapNoteCount: data.mapNoteCount,
|
|
66
|
+
speed: data.speed,
|
|
67
|
+
})
|
|
68
|
+
) {
|
|
69
|
+
return NextResponse.json(
|
|
70
|
+
{
|
|
71
|
+
error: "Token miscalculation",
|
|
72
|
+
},
|
|
73
|
+
{ status: 500 }
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const user = (await getUserBySession(session)) as User;
|
|
60
78
|
|
|
61
79
|
let { data: userData, error: userError } = await supabase
|
|
62
80
|
.from("profiles")
|
package/api/updateBeatmapPage.ts
CHANGED
|
@@ -2,6 +2,8 @@ import { NextResponse } from "next/server";
|
|
|
2
2
|
import z from "zod";
|
|
3
3
|
import { protectedApi, validUser } from "../utils/requestUtils";
|
|
4
4
|
import { supabase } from "../utils/supabase";
|
|
5
|
+
import { getUserBySession } from "../utils/getUserBySession";
|
|
6
|
+
import { User } from "@supabase/supabase-js";
|
|
5
7
|
|
|
6
8
|
export const Schema = {
|
|
7
9
|
input: z.strictObject({
|
|
@@ -34,7 +36,7 @@ export async function handler({
|
|
|
34
36
|
}: (typeof Schema)["input"]["_type"]): Promise<
|
|
35
37
|
NextResponse<(typeof Schema)["output"]["_type"]>
|
|
36
38
|
> {
|
|
37
|
-
const user = (await
|
|
39
|
+
const user = (await getUserBySession(session)) as User;
|
|
38
40
|
let { data: userData, error: userError } = await supabase
|
|
39
41
|
.from("profiles")
|
|
40
42
|
.select("*")
|
|
@@ -67,17 +69,17 @@ export async function handler({
|
|
|
67
69
|
|
|
68
70
|
const upsertPayload = {
|
|
69
71
|
id,
|
|
70
|
-
latestBeatmapHash: beatmapHash ? beatmapHash : pageData.latestBeatmapHash,
|
|
71
72
|
genre: "",
|
|
72
73
|
status: "UNRANKED",
|
|
73
74
|
owner: userData.id,
|
|
74
75
|
description: description ? description : pageData.description,
|
|
75
76
|
tags: tags ? tags : pageData.tags,
|
|
76
|
-
nominations: [],
|
|
77
77
|
};
|
|
78
78
|
|
|
79
79
|
if (beatmapHash && beatmapData) {
|
|
80
80
|
upsertPayload["title"] = beatmapData.title;
|
|
81
|
+
upsertPayload["latestBeatmapHash"] = beatmapHash;
|
|
82
|
+
upsertPayload["nominations"] = [];
|
|
81
83
|
}
|
|
82
84
|
|
|
83
85
|
const upserted = await supabase
|
package/index.ts
CHANGED
|
@@ -198,6 +198,7 @@ export const Schema = {
|
|
|
198
198
|
ranked: z.boolean().nullable().optional(),
|
|
199
199
|
beatmapFile: z.string().nullable().optional(),
|
|
200
200
|
image: z.string().nullable().optional(),
|
|
201
|
+
imageLarge: z.string().nullable().optional(),
|
|
201
202
|
starRating: z.number().nullable().optional(),
|
|
202
203
|
owner: z.number().nullable().optional(),
|
|
203
204
|
ownerUsername: z.string().nullable().optional(),
|
|
@@ -352,6 +353,26 @@ import { Schema as GetMapUploadUrl } from "./api/getMapUploadUrl"
|
|
|
352
353
|
export { Schema as SchemaGetMapUploadUrl } from "./api/getMapUploadUrl"
|
|
353
354
|
export const getMapUploadUrl = handleApi({url:"/api/getMapUploadUrl",...GetMapUploadUrl})
|
|
354
355
|
|
|
356
|
+
// ./api/getPassToken.ts API
|
|
357
|
+
|
|
358
|
+
/*
|
|
359
|
+
export const Schema = {
|
|
360
|
+
input: z.strictObject({
|
|
361
|
+
data: z.object({
|
|
362
|
+
email: z.string(),
|
|
363
|
+
passkey: z.string(),
|
|
364
|
+
computerName: z.string(),
|
|
365
|
+
}),
|
|
366
|
+
}),
|
|
367
|
+
output: z.object({
|
|
368
|
+
token: z.string().optional(),
|
|
369
|
+
error: z.string().optional(),
|
|
370
|
+
}),
|
|
371
|
+
};*/
|
|
372
|
+
import { Schema as GetPassToken } from "./api/getPassToken"
|
|
373
|
+
export { Schema as SchemaGetPassToken } from "./api/getPassToken"
|
|
374
|
+
export const getPassToken = handleApi({url:"/api/getPassToken",...GetPassToken})
|
|
375
|
+
|
|
355
376
|
// ./api/getProfile.ts API
|
|
356
377
|
|
|
357
378
|
/*
|
|
@@ -561,6 +582,24 @@ import { Schema as SearchUsers } from "./api/searchUsers"
|
|
|
561
582
|
export { Schema as SchemaSearchUsers } from "./api/searchUsers"
|
|
562
583
|
export const searchUsers = handleApi({url:"/api/searchUsers",...SearchUsers})
|
|
563
584
|
|
|
585
|
+
// ./api/setPasskey.ts API
|
|
586
|
+
|
|
587
|
+
/*
|
|
588
|
+
export const Schema = {
|
|
589
|
+
input: z.strictObject({
|
|
590
|
+
session: z.string(),
|
|
591
|
+
data: z.object({
|
|
592
|
+
passkey: z.string(),
|
|
593
|
+
}),
|
|
594
|
+
}),
|
|
595
|
+
output: z.object({
|
|
596
|
+
error: z.string().optional(),
|
|
597
|
+
}),
|
|
598
|
+
};*/
|
|
599
|
+
import { Schema as SetPasskey } from "./api/setPasskey"
|
|
600
|
+
export { Schema as SchemaSetPasskey } from "./api/setPasskey"
|
|
601
|
+
export const setPasskey = handleApi({url:"/api/setPasskey",...SetPasskey})
|
|
602
|
+
|
|
564
603
|
// ./api/submitScore.ts API
|
|
565
604
|
|
|
566
605
|
/*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rhythia-api",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "152.0.0",
|
|
4
4
|
"main": "index.ts",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"update": "bun ./scripts/update.ts",
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"@aws-sdk/client-s3": "^3.637.0",
|
|
17
17
|
"@aws-sdk/s3-request-presigner": "^3.637.0",
|
|
18
18
|
"@netlify/zip-it-and-ship-it": "^9.37.9",
|
|
19
|
+
"@noble/ciphers": "^1.0.0",
|
|
19
20
|
"@supabase/supabase-js": "^2.45.1",
|
|
20
21
|
"@types/aws-lambda": "^8.10.143",
|
|
21
22
|
"@types/bun": "^1.1.6",
|
|
@@ -38,7 +39,7 @@
|
|
|
38
39
|
"osu-standard-stable": "^5.0.0",
|
|
39
40
|
"sharp": "^0.33.5",
|
|
40
41
|
"simple-git": "^3.25.0",
|
|
41
|
-
"supabase": "^1.
|
|
42
|
+
"supabase": "^1.207.9",
|
|
42
43
|
"tsx": "^4.17.0",
|
|
43
44
|
"utf-8-validate": "^6.0.4",
|
|
44
45
|
"zod": "^3.23.8"
|
package/types/database.ts
CHANGED
|
@@ -109,6 +109,7 @@ export type Database = {
|
|
|
109
109
|
created_at: string
|
|
110
110
|
difficulty: number | null
|
|
111
111
|
image: string | null
|
|
112
|
+
imageLarge: string | null
|
|
112
113
|
length: number | null
|
|
113
114
|
noteCount: number | null
|
|
114
115
|
playcount: number | null
|
|
@@ -122,6 +123,7 @@ export type Database = {
|
|
|
122
123
|
created_at?: string
|
|
123
124
|
difficulty?: number | null
|
|
124
125
|
image?: string | null
|
|
126
|
+
imageLarge?: string | null
|
|
125
127
|
length?: number | null
|
|
126
128
|
noteCount?: number | null
|
|
127
129
|
playcount?: number | null
|
|
@@ -135,6 +137,7 @@ export type Database = {
|
|
|
135
137
|
created_at?: string
|
|
136
138
|
difficulty?: number | null
|
|
137
139
|
image?: string | null
|
|
140
|
+
imageLarge?: string | null
|
|
138
141
|
length?: number | null
|
|
139
142
|
noteCount?: number | null
|
|
140
143
|
playcount?: number | null
|
|
@@ -144,6 +147,32 @@ export type Database = {
|
|
|
144
147
|
}
|
|
145
148
|
Relationships: []
|
|
146
149
|
}
|
|
150
|
+
passkeys: {
|
|
151
|
+
Row: {
|
|
152
|
+
email: string
|
|
153
|
+
id: number
|
|
154
|
+
passkey: string
|
|
155
|
+
}
|
|
156
|
+
Insert: {
|
|
157
|
+
email: string
|
|
158
|
+
id: number
|
|
159
|
+
passkey: string
|
|
160
|
+
}
|
|
161
|
+
Update: {
|
|
162
|
+
email?: string
|
|
163
|
+
id?: number
|
|
164
|
+
passkey?: string
|
|
165
|
+
}
|
|
166
|
+
Relationships: [
|
|
167
|
+
{
|
|
168
|
+
foreignKeyName: "passkeys_id_fkey"
|
|
169
|
+
columns: ["id"]
|
|
170
|
+
isOneToOne: true
|
|
171
|
+
referencedRelation: "profiles"
|
|
172
|
+
referencedColumns: ["id"]
|
|
173
|
+
},
|
|
174
|
+
]
|
|
175
|
+
}
|
|
147
176
|
profiles: {
|
|
148
177
|
Row: {
|
|
149
178
|
about_me: string | null
|
|
@@ -202,15 +231,7 @@ export type Database = {
|
|
|
202
231
|
username?: string | null
|
|
203
232
|
verified?: boolean | null
|
|
204
233
|
}
|
|
205
|
-
Relationships: [
|
|
206
|
-
{
|
|
207
|
-
foreignKeyName: "profiles_uid_fkey"
|
|
208
|
-
columns: ["uid"]
|
|
209
|
-
isOneToOne: true
|
|
210
|
-
referencedRelation: "users"
|
|
211
|
-
referencedColumns: ["id"]
|
|
212
|
-
},
|
|
213
|
-
]
|
|
234
|
+
Relationships: []
|
|
214
235
|
}
|
|
215
236
|
scores: {
|
|
216
237
|
Row: {
|
|
@@ -363,3 +384,18 @@ export type Enums<
|
|
|
363
384
|
: PublicEnumNameOrOptions extends keyof PublicSchema["Enums"]
|
|
364
385
|
? PublicSchema["Enums"][PublicEnumNameOrOptions]
|
|
365
386
|
: never
|
|
387
|
+
|
|
388
|
+
export type CompositeTypes<
|
|
389
|
+
PublicCompositeTypeNameOrOptions extends
|
|
390
|
+
| keyof PublicSchema["CompositeTypes"]
|
|
391
|
+
| { schema: keyof Database },
|
|
392
|
+
CompositeTypeName extends PublicCompositeTypeNameOrOptions extends {
|
|
393
|
+
schema: keyof Database
|
|
394
|
+
}
|
|
395
|
+
? keyof Database[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"]
|
|
396
|
+
: never = never,
|
|
397
|
+
> = PublicCompositeTypeNameOrOptions extends { schema: keyof Database }
|
|
398
|
+
? Database[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"][CompositeTypeName]
|
|
399
|
+
: PublicCompositeTypeNameOrOptions extends keyof PublicSchema["CompositeTypes"]
|
|
400
|
+
? PublicSchema["CompositeTypes"][PublicCompositeTypeNameOrOptions]
|
|
401
|
+
: never
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { User } from "@supabase/supabase-js";
|
|
2
|
+
import { decryptString } from "./security";
|
|
3
|
+
import { supabase } from "./supabase";
|
|
4
|
+
|
|
5
|
+
export async function getUserBySession(session: string): Promise<User | null> {
|
|
6
|
+
const user = (await supabase.auth.getUser(session)).data.user;
|
|
7
|
+
|
|
8
|
+
if (user) {
|
|
9
|
+
return user;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
const decryptedToken = JSON.parse(decryptString(session)) as {
|
|
14
|
+
userId: number;
|
|
15
|
+
email: string;
|
|
16
|
+
passKey: string;
|
|
17
|
+
computerName: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
let { data: queryPasskey, error } = await supabase
|
|
21
|
+
.from("passkeys")
|
|
22
|
+
.select("*,profiles(uid)")
|
|
23
|
+
.eq("id", decryptedToken.userId || "nil")
|
|
24
|
+
.eq("email", decryptedToken.email || "nil")
|
|
25
|
+
.eq("passkey", decryptedToken.passKey || "nil")
|
|
26
|
+
.single();
|
|
27
|
+
|
|
28
|
+
if (!queryPasskey) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return (await supabase.auth.admin.getUserById(queryPasskey.profiles?.uid!))
|
|
33
|
+
.data.user;
|
|
34
|
+
} catch (error) {}
|
|
35
|
+
|
|
36
|
+
return null;
|
|
37
|
+
}
|
package/utils/requestUtils.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { NextResponse } from "next/server";
|
|
2
|
-
import { supabase } from "./supabase";
|
|
3
2
|
import { ZodObject } from "zod";
|
|
3
|
+
import { getUserBySession } from "./getUserBySession";
|
|
4
4
|
|
|
5
5
|
interface Props<
|
|
6
6
|
K = (...args: any[]) => Promise<NextResponse<any>>,
|
|
@@ -43,8 +43,8 @@ export async function validUser(data) {
|
|
|
43
43
|
);
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
const user = await
|
|
47
|
-
if (
|
|
46
|
+
const user = await getUserBySession(data.session);
|
|
47
|
+
if (!user) {
|
|
48
48
|
return NextResponse.json(
|
|
49
49
|
{
|
|
50
50
|
error: "Invalid user session",
|
|
@@ -53,15 +53,3 @@ export async function validUser(data) {
|
|
|
53
53
|
);
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
|
-
|
|
57
|
-
export async function getUser(data) {
|
|
58
|
-
if (!data.session) {
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const user = await supabase.auth.getUser(data.session);
|
|
63
|
-
if (user.error || !user.data.user) {
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
return user;
|
|
67
|
-
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { xchacha20poly1305 } from "@noble/ciphers/chacha";
|
|
2
|
+
import { managedNonce } from "@noble/ciphers/webcrypto";
|
|
3
|
+
import {
|
|
4
|
+
bytesToHex,
|
|
5
|
+
bytesToUtf8,
|
|
6
|
+
hexToBytes,
|
|
7
|
+
utf8ToBytes,
|
|
8
|
+
} from "@noble/ciphers/utils";
|
|
9
|
+
const key = hexToBytes(process.env.TOKEN_SECRET || "");
|
|
10
|
+
const chacha = managedNonce(xchacha20poly1305)(key); // manages nonces for you
|
|
11
|
+
|
|
12
|
+
export function encryptString(str: string) {
|
|
13
|
+
const data = utf8ToBytes(str);
|
|
14
|
+
return bytesToHex(chacha.encrypt(data));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function decryptString(str: string) {
|
|
18
|
+
const data = hexToBytes(str);
|
|
19
|
+
return bytesToUtf8(chacha.decrypt(data));
|
|
20
|
+
}
|
package/utils/star-calc/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BeatmapDecoder } from "osu-parsers";
|
|
1
|
+
import { BeatmapDecoder, BeatmapEncoder } from "osu-parsers";
|
|
2
2
|
import { StandardRuleset } from "osu-standard-stable";
|
|
3
3
|
import { sampleMap } from "./osuUtils";
|
|
4
4
|
import { SSPMParsedMap } from "./sspmParser";
|
|
@@ -18,9 +18,6 @@ export function calculatePerformancePoints(
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
export function rateMap(map: SSPMParsedMap) {
|
|
21
|
-
const decoder = new BeatmapDecoder();
|
|
22
|
-
const beatmap1 = decoder.decodeFromString(sampleMap);
|
|
23
|
-
|
|
24
21
|
let notes = map.markers
|
|
25
22
|
.filter((marker) => marker.type === 0)
|
|
26
23
|
.map((marker) => ({
|
|
@@ -29,40 +26,44 @@ export function rateMap(map: SSPMParsedMap) {
|
|
|
29
26
|
y: marker.data["field0"].y,
|
|
30
27
|
}));
|
|
31
28
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
for (const note of notes) {
|
|
35
|
-
const hittable = beatmap1.hitObjects[0].clone();
|
|
36
|
-
hittable.startX = Math.round((note.x / 2) * 100);
|
|
37
|
-
hittable.startY = Math.round((note.y / 2) * 100);
|
|
38
|
-
hittable.startTime = note.time;
|
|
39
|
-
beatmap1.hitObjects.push(hittable);
|
|
40
|
-
}
|
|
41
|
-
const ruleset = new StandardRuleset();
|
|
42
|
-
const mods = ruleset.createModCombination("RX");
|
|
43
|
-
const difficultyCalculator = ruleset.createDifficultyCalculator(beatmap1);
|
|
44
|
-
const difficultyAttributes = difficultyCalculator.calculateWithMods(mods);
|
|
45
|
-
return difficultyAttributes.starRating;
|
|
29
|
+
return rate(notes);
|
|
46
30
|
}
|
|
47
31
|
|
|
48
32
|
export function rateMapOld(map: SSPMMap) {
|
|
49
|
-
const decoder = new BeatmapDecoder();
|
|
50
|
-
const beatmap1 = decoder.decodeFromString(sampleMap);
|
|
51
|
-
|
|
52
33
|
let notes = map.notes.map((marker) => ({
|
|
53
34
|
time: marker.position,
|
|
54
35
|
x: marker.x,
|
|
55
36
|
y: marker.y,
|
|
56
37
|
}));
|
|
57
38
|
|
|
39
|
+
return rate(notes);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function rate(
|
|
43
|
+
notes: {
|
|
44
|
+
time: number;
|
|
45
|
+
x: number;
|
|
46
|
+
y: number;
|
|
47
|
+
}[]
|
|
48
|
+
) {
|
|
58
49
|
notes = notes.sort((a, b) => a.time - b.time);
|
|
50
|
+
const decoder = new BeatmapDecoder();
|
|
51
|
+
const beatmap1 = decoder.decodeFromString(sampleMap);
|
|
52
|
+
let i = 0;
|
|
53
|
+
while (i < notes.length - 1) {
|
|
54
|
+
const note = notes[i];
|
|
55
|
+
const nextNote = notes[i + 1];
|
|
56
|
+
if (distance(note.x, note.y, nextNote.x, nextNote.y) < 1.25) {
|
|
57
|
+
notes.splice(i + 1, 1);
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
59
60
|
|
|
60
|
-
for (const note of notes) {
|
|
61
61
|
const hittable = beatmap1.hitObjects[0].clone();
|
|
62
62
|
hittable.startX = Math.round((note.x / 2) * 100);
|
|
63
63
|
hittable.startY = Math.round((note.y / 2) * 100);
|
|
64
64
|
hittable.startTime = note.time;
|
|
65
65
|
beatmap1.hitObjects.push(hittable);
|
|
66
|
+
i++;
|
|
66
67
|
}
|
|
67
68
|
const ruleset = new StandardRuleset();
|
|
68
69
|
const mods = ruleset.createModCombination("RX");
|
|
@@ -70,3 +71,5 @@ export function rateMapOld(map: SSPMMap) {
|
|
|
70
71
|
const difficultyAttributes = difficultyCalculator.calculateWithMods(mods);
|
|
71
72
|
return difficultyAttributes.starRating;
|
|
72
73
|
}
|
|
74
|
+
|
|
75
|
+
const distance = (x1, y1, x2, y2) => Math.hypot(x2 - x1, y2 - y1);
|