rhythia-api 106.0.0 → 108.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/createBeatmap.ts +47 -0
- package/api/createBeatmapPage.ts +47 -0
- package/api/getLeaderboard.ts +3 -1
- package/api/getMapUploadUrl.ts +1 -3
- package/api/getProfile.ts +1 -0
- package/api/searchUsers.ts +1 -0
- package/api/updateBeatmapPage.ts +64 -0
- package/index.ts +15 -0
- package/package.json +2 -1
- package/scripts/test.ts +31 -0
- package/types/database.ts +36 -0
|
@@ -0,0 +1,47 @@
|
|
|
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 {
|
|
6
|
+
SSPMParsedMap,
|
|
7
|
+
SSPMParser,
|
|
8
|
+
} from "rhythia-star-calculator/src/sspmParser";
|
|
9
|
+
|
|
10
|
+
export const Schema = {
|
|
11
|
+
input: z.strictObject({
|
|
12
|
+
url: z.string(),
|
|
13
|
+
}),
|
|
14
|
+
output: z.strictObject({
|
|
15
|
+
hash: z.string().optional(),
|
|
16
|
+
error: z.string().optional(),
|
|
17
|
+
}),
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export async function POST(request: Request): Promise<NextResponse> {
|
|
21
|
+
return protectedApi({
|
|
22
|
+
request,
|
|
23
|
+
schema: Schema,
|
|
24
|
+
authorization: validUser,
|
|
25
|
+
activity: handler,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function handler({
|
|
30
|
+
url,
|
|
31
|
+
}: (typeof Schema)["input"]["_type"]): Promise<
|
|
32
|
+
NextResponse<(typeof Schema)["output"]["_type"]>
|
|
33
|
+
> {
|
|
34
|
+
if (
|
|
35
|
+
!url.startsWith(`https://rhthia-avatars.s3.eu-central-003.backblazeb2.com/`)
|
|
36
|
+
)
|
|
37
|
+
return NextResponse.json({ error: "Invalid url" });
|
|
38
|
+
|
|
39
|
+
const request = await fetch(url);
|
|
40
|
+
const bytes = await request.arrayBuffer();
|
|
41
|
+
const parser = new SSPMParser(Buffer.from(bytes));
|
|
42
|
+
|
|
43
|
+
const parsedData = parser.parse();
|
|
44
|
+
|
|
45
|
+
console.log(parsedData);
|
|
46
|
+
return NextResponse.json({});
|
|
47
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
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
|
+
|
|
6
|
+
export const Schema = {
|
|
7
|
+
input: z.strictObject({
|
|
8
|
+
session: z.string(),
|
|
9
|
+
}),
|
|
10
|
+
output: z.strictObject({
|
|
11
|
+
error: z.string().optional(),
|
|
12
|
+
id: z.number().optional(),
|
|
13
|
+
}),
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export async function POST(request: Request): Promise<NextResponse> {
|
|
17
|
+
return protectedApi({
|
|
18
|
+
request,
|
|
19
|
+
schema: Schema,
|
|
20
|
+
authorization: validUser,
|
|
21
|
+
activity: handler,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function handler({
|
|
26
|
+
session,
|
|
27
|
+
}: (typeof Schema)["input"]["_type"]): Promise<
|
|
28
|
+
NextResponse<(typeof Schema)["output"]["_type"]>
|
|
29
|
+
> {
|
|
30
|
+
const user = (await supabase.auth.getUser(session)).data.user!;
|
|
31
|
+
let { data: userData, error: userError } = await supabase
|
|
32
|
+
.from("profiles")
|
|
33
|
+
.select("*")
|
|
34
|
+
.eq("uid", user.id)
|
|
35
|
+
.single();
|
|
36
|
+
|
|
37
|
+
if (!userData) return NextResponse.json({ error: "No user." });
|
|
38
|
+
|
|
39
|
+
const upserted = await supabase
|
|
40
|
+
.from("beatmapPages")
|
|
41
|
+
.upsert({
|
|
42
|
+
owner: userData.id,
|
|
43
|
+
})
|
|
44
|
+
.select("*")
|
|
45
|
+
.single();
|
|
46
|
+
return NextResponse.json({ id: upserted.data?.id });
|
|
47
|
+
}
|
package/api/getLeaderboard.ts
CHANGED
|
@@ -74,11 +74,13 @@ export async function getLeaderboard(page = 1, session: string) {
|
|
|
74
74
|
console.log(startPage, endPage);
|
|
75
75
|
const countQuery = await supabase
|
|
76
76
|
.from("profiles")
|
|
77
|
-
.select("*", { count: "exact", head: true })
|
|
77
|
+
.select("*", { count: "exact", head: true })
|
|
78
|
+
.neq("ban", "excluded");
|
|
78
79
|
|
|
79
80
|
let { data: queryData, error } = await supabase
|
|
80
81
|
.from("profiles")
|
|
81
82
|
.select("*")
|
|
83
|
+
.neq("ban", "excluded")
|
|
82
84
|
.order("skill_points", { ascending: false })
|
|
83
85
|
.range(startPage, endPage);
|
|
84
86
|
|
package/api/getMapUploadUrl.ts
CHANGED
|
@@ -22,7 +22,6 @@ const s3Client = new S3Client({
|
|
|
22
22
|
export const Schema = {
|
|
23
23
|
input: z.strictObject({
|
|
24
24
|
session: z.string(),
|
|
25
|
-
beatmapHash: z.string(),
|
|
26
25
|
contentLength: z.number(),
|
|
27
26
|
contentType: z.string(),
|
|
28
27
|
}),
|
|
@@ -44,7 +43,6 @@ export async function POST(request: Request): Promise<NextResponse> {
|
|
|
44
43
|
|
|
45
44
|
export async function handler({
|
|
46
45
|
session,
|
|
47
|
-
beatmapHash,
|
|
48
46
|
contentLength,
|
|
49
47
|
contentType,
|
|
50
48
|
}: (typeof Schema)["input"]["_type"]): Promise<
|
|
@@ -58,7 +56,7 @@ export async function handler({
|
|
|
58
56
|
});
|
|
59
57
|
}
|
|
60
58
|
|
|
61
|
-
const key = `beatmap-${
|
|
59
|
+
const key = `beatmap-${Date.now()}-${user.id}`;
|
|
62
60
|
const command = new PutObjectCommand({
|
|
63
61
|
Bucket: "rhthia-avatars",
|
|
64
62
|
Key: key,
|
package/api/getProfile.ts
CHANGED
|
@@ -113,6 +113,7 @@ export async function handler(
|
|
|
113
113
|
const { count: playersWithMorePoints, error: rankError } = await supabase
|
|
114
114
|
.from("profiles")
|
|
115
115
|
.select("*", { count: "exact", head: true })
|
|
116
|
+
.neq("ban", "excluded")
|
|
116
117
|
.gt("skill_points", user.skill_points);
|
|
117
118
|
|
|
118
119
|
return NextResponse.json({
|
package/api/searchUsers.ts
CHANGED
|
@@ -33,6 +33,7 @@ export async function handler(data: (typeof Schema)["input"]["_type"]) {
|
|
|
33
33
|
const { data: searchData, error } = await supabase
|
|
34
34
|
.from("profiles")
|
|
35
35
|
.select("id,username")
|
|
36
|
+
.neq("ban", "excluded")
|
|
36
37
|
.ilike("username", `%${data.text}%`)
|
|
37
38
|
.limit(10);
|
|
38
39
|
return NextResponse.json({
|
|
@@ -0,0 +1,64 @@
|
|
|
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
|
+
|
|
6
|
+
export const Schema = {
|
|
7
|
+
input: z.strictObject({
|
|
8
|
+
session: z.string(),
|
|
9
|
+
id: z.number(),
|
|
10
|
+
beatmapHash: z.string(),
|
|
11
|
+
}),
|
|
12
|
+
output: z.strictObject({
|
|
13
|
+
error: z.string().optional(),
|
|
14
|
+
}),
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export async function POST(request: Request): Promise<NextResponse> {
|
|
18
|
+
return protectedApi({
|
|
19
|
+
request,
|
|
20
|
+
schema: Schema,
|
|
21
|
+
authorization: validUser,
|
|
22
|
+
activity: handler,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function handler({
|
|
27
|
+
session,
|
|
28
|
+
beatmapHash,
|
|
29
|
+
id,
|
|
30
|
+
}: (typeof Schema)["input"]["_type"]): Promise<
|
|
31
|
+
NextResponse<(typeof Schema)["output"]["_type"]>
|
|
32
|
+
> {
|
|
33
|
+
const user = (await supabase.auth.getUser(session)).data.user!;
|
|
34
|
+
let { data: userData, error: userError } = await supabase
|
|
35
|
+
.from("profiles")
|
|
36
|
+
.select("*")
|
|
37
|
+
.eq("uid", user.id)
|
|
38
|
+
.single();
|
|
39
|
+
|
|
40
|
+
let { data: pageData, error: pageError } = await supabase
|
|
41
|
+
.from("beatmapPages")
|
|
42
|
+
.select("*")
|
|
43
|
+
.eq("id", id)
|
|
44
|
+
.single();
|
|
45
|
+
|
|
46
|
+
if (!userData) return NextResponse.json({ error: "No user." });
|
|
47
|
+
if (userData.id !== pageData?.owner)
|
|
48
|
+
return NextResponse.json({ error: "Non-authz user." });
|
|
49
|
+
|
|
50
|
+
const upserted = await supabase
|
|
51
|
+
.from("beatmapPages")
|
|
52
|
+
.upsert({
|
|
53
|
+
id,
|
|
54
|
+
latestBeatmapHash: beatmapHash,
|
|
55
|
+
owner: userData.id,
|
|
56
|
+
})
|
|
57
|
+
.select("*")
|
|
58
|
+
.single();
|
|
59
|
+
|
|
60
|
+
if (upserted.error) {
|
|
61
|
+
return NextResponse.json({ error: upserted.error.message });
|
|
62
|
+
}
|
|
63
|
+
return NextResponse.json({});
|
|
64
|
+
}
|
package/index.ts
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
import { handleApi } from "./handleApi"
|
|
2
2
|
|
|
3
|
+
// ./api/createBeatmap.ts API
|
|
4
|
+
import { Schema as CreateBeatmap } from "./api/createBeatmap"
|
|
5
|
+
export { Schema as SchemaCreateBeatmap } from "./api/createBeatmap"
|
|
6
|
+
export const createBeatmap = handleApi({url:"/api/createBeatmap",...CreateBeatmap})
|
|
7
|
+
|
|
8
|
+
// ./api/createBeatmapPage.ts API
|
|
9
|
+
import { Schema as CreateBeatmapPage } from "./api/createBeatmapPage"
|
|
10
|
+
export { Schema as SchemaCreateBeatmapPage } from "./api/createBeatmapPage"
|
|
11
|
+
export const createBeatmapPage = handleApi({url:"/api/createBeatmapPage",...CreateBeatmapPage})
|
|
12
|
+
|
|
3
13
|
// ./api/editAboutMe.ts API
|
|
4
14
|
import { Schema as EditAboutMe } from "./api/editAboutMe"
|
|
5
15
|
export { Schema as SchemaEditAboutMe } from "./api/editAboutMe"
|
|
@@ -54,4 +64,9 @@ export const searchUsers = handleApi({url:"/api/searchUsers",...SearchUsers})
|
|
|
54
64
|
import { Schema as SubmitScore } from "./api/submitScore"
|
|
55
65
|
export { Schema as SchemaSubmitScore } from "./api/submitScore"
|
|
56
66
|
export const submitScore = handleApi({url:"/api/submitScore",...SubmitScore})
|
|
67
|
+
|
|
68
|
+
// ./api/updateBeatmapPage.ts API
|
|
69
|
+
import { Schema as UpdateBeatmapPage } from "./api/updateBeatmapPage"
|
|
70
|
+
export { Schema as SchemaUpdateBeatmapPage } from "./api/updateBeatmapPage"
|
|
71
|
+
export const updateBeatmapPage = handleApi({url:"/api/updateBeatmapPage",...UpdateBeatmapPage})
|
|
57
72
|
export { handleApi } from "./handleApi"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rhythia-api",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "108.0.0",
|
|
4
4
|
"main": "index.ts",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"update": "bun ./scripts/update.ts",
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"isomorphic-git": "^1.27.1",
|
|
31
31
|
"lodash": "^4.17.21",
|
|
32
32
|
"next": "^14.2.5",
|
|
33
|
+
"rhythia-star-calculator": "^1.0.4",
|
|
33
34
|
"simple-git": "^3.25.0",
|
|
34
35
|
"supabase": "^1.192.5",
|
|
35
36
|
"tsx": "^4.17.0",
|
package/scripts/test.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { createClient } from "@supabase/supabase-js";
|
|
2
|
+
import { Database } from "../types/database";
|
|
3
|
+
|
|
4
|
+
export const supabase = createClient<Database>(
|
|
5
|
+
`https://pfkajngbllcbdzoylrvp.supabase.co`,
|
|
6
|
+
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InBma2FqbmdibGxjYmR6b3lscnZwIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImlhdCI6MTcxODU3NjA3MCwiZXhwIjoyMDM0MTUyMDcwfQ.XKUlQWvzmcYyirM-Zi4nwhiEKcpx1xLS97QUyuR3MoY",
|
|
7
|
+
{
|
|
8
|
+
auth: {
|
|
9
|
+
autoRefreshToken: false,
|
|
10
|
+
persistSession: false,
|
|
11
|
+
},
|
|
12
|
+
}
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
async function main() {
|
|
16
|
+
const countQuery = await supabase
|
|
17
|
+
.from("profiles")
|
|
18
|
+
.select("*", { count: "exact", head: true })
|
|
19
|
+
.neq("ban", "excluded");
|
|
20
|
+
|
|
21
|
+
let { data: queryData, error } = await supabase
|
|
22
|
+
.from("profiles")
|
|
23
|
+
.select("*")
|
|
24
|
+
.neq("ban", "excluded")
|
|
25
|
+
.order("skill_points", { ascending: false })
|
|
26
|
+
.range(0, 50);
|
|
27
|
+
|
|
28
|
+
console.log(queryData);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
main();
|
package/types/database.ts
CHANGED
|
@@ -9,6 +9,42 @@ export type Json =
|
|
|
9
9
|
export type Database = {
|
|
10
10
|
public: {
|
|
11
11
|
Tables: {
|
|
12
|
+
beatmapPages: {
|
|
13
|
+
Row: {
|
|
14
|
+
created_at: string
|
|
15
|
+
id: number
|
|
16
|
+
latestBeatmapHash: string | null
|
|
17
|
+
owner: number | null
|
|
18
|
+
}
|
|
19
|
+
Insert: {
|
|
20
|
+
created_at?: string
|
|
21
|
+
id?: number
|
|
22
|
+
latestBeatmapHash?: string | null
|
|
23
|
+
owner?: number | null
|
|
24
|
+
}
|
|
25
|
+
Update: {
|
|
26
|
+
created_at?: string
|
|
27
|
+
id?: number
|
|
28
|
+
latestBeatmapHash?: string | null
|
|
29
|
+
owner?: number | null
|
|
30
|
+
}
|
|
31
|
+
Relationships: [
|
|
32
|
+
{
|
|
33
|
+
foreignKeyName: "beatmapPages_latestBeatmapHash_fkey"
|
|
34
|
+
columns: ["latestBeatmapHash"]
|
|
35
|
+
isOneToOne: false
|
|
36
|
+
referencedRelation: "beatmaps"
|
|
37
|
+
referencedColumns: ["beatmapHash"]
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
foreignKeyName: "beatmapPages_owner_fkey"
|
|
41
|
+
columns: ["owner"]
|
|
42
|
+
isOneToOne: false
|
|
43
|
+
referencedRelation: "profiles"
|
|
44
|
+
referencedColumns: ["id"]
|
|
45
|
+
},
|
|
46
|
+
]
|
|
47
|
+
}
|
|
12
48
|
beatmaps: {
|
|
13
49
|
Row: {
|
|
14
50
|
beatmapFile: string | null
|