rhythia-api 212.0.0 → 215.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/README.md +9 -0
- package/api/getBeatmapPage.ts +2 -0
- package/api/getBeatmapPageById.ts +4 -2
- package/api/getVideoUploadUrl.ts +85 -0
- package/api/updateBeatmapPage.ts +4 -1
- package/index.ts +25 -2
- package/package.json +2 -2
- package/types/database.ts +4 -3
package/README.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Rhythia Online API
|
|
2
|
+
|
|
3
|
+
This repo is a typed wrapper around Rhythia's backend endpoints. The helpers under `api/` are wired through `handleApi.ts` so consumers can call functions like `acceptInvite` or `createCollection` and let the shared client post to the hosted service while censoring stray profanity in responses.
|
|
4
|
+
|
|
5
|
+
Install dependencies with Bun, then reach for the scripts in `package.json`; `bun run dev` starts the local handler, `bun run test` exercises the contract tests, and `bun run pipeline:build-api` prepares the bundle that powers deployments. The production build is pushed to Vercel from the `main` branch, so anything merged there lands at the hosted Rhythia Online API a few moments later.
|
|
6
|
+
|
|
7
|
+
Runtime secrets live in environment variables. Upload endpoints expect `ACCESS_BUCKET` and `SECRET_BUCKET` for S3, the purchase flow checks `BUY_SECRET`, auth helpers derive tokens from `TOKEN_SECRET`, and the Supabase admin calls need `ADMIN_KEY`. The deploy script also looks for `GIT_USER`, `GIT_KEY`, `SOURCE_BRANCH`, and `TARGET_BRANCH` when the CI job mirrors changes upstream.
|
|
8
|
+
|
|
9
|
+
If you need to point at a different stack, call `setEnvironment` with `development`, `testing`, or `production` before making requests so the helper talks to the right Rhythia host.
|
package/api/getBeatmapPage.ts
CHANGED
|
@@ -51,6 +51,7 @@ export const Schema = {
|
|
|
51
51
|
status: z.string().nullable().optional(),
|
|
52
52
|
description: z.string().nullable().optional(),
|
|
53
53
|
tags: z.string().nullable().optional(),
|
|
54
|
+
videoUrl: z.string().nullable().optional(),
|
|
54
55
|
})
|
|
55
56
|
.optional(),
|
|
56
57
|
}),
|
|
@@ -144,6 +145,7 @@ export async function handler(
|
|
|
144
145
|
nominations: beatmapPage.nominations as number[],
|
|
145
146
|
description: beatmapPage.description,
|
|
146
147
|
tags: beatmapPage.tags,
|
|
148
|
+
videoUrl: beatmapPage.video_url,
|
|
147
149
|
},
|
|
148
150
|
});
|
|
149
151
|
}
|
|
@@ -15,9 +15,9 @@ export const Schema = {
|
|
|
15
15
|
z.object({
|
|
16
16
|
id: z.number(),
|
|
17
17
|
awarded_sp: z.number().nullable(),
|
|
18
|
-
created_at: z.string(),
|
|
18
|
+
created_at: z.string(),
|
|
19
19
|
misses: z.number().nullable(),
|
|
20
|
-
mods: z.record(z.unknown()),
|
|
20
|
+
mods: z.record(z.unknown()),
|
|
21
21
|
passed: z.boolean().nullable(),
|
|
22
22
|
songId: z.string().nullable(),
|
|
23
23
|
speed: z.number().nullable(),
|
|
@@ -47,6 +47,7 @@ export const Schema = {
|
|
|
47
47
|
ownerUsername: z.string().nullable().optional(),
|
|
48
48
|
ownerAvatar: z.string().nullable().optional(),
|
|
49
49
|
status: z.string().nullable().optional(),
|
|
50
|
+
videoUrl: z.string().nullable(),
|
|
50
51
|
})
|
|
51
52
|
.optional(),
|
|
52
53
|
}),
|
|
@@ -135,6 +136,7 @@ export async function handler(
|
|
|
135
136
|
id: beatmapPage.id,
|
|
136
137
|
status: beatmapPage.status,
|
|
137
138
|
nominations: beatmapPage.nominations as number[],
|
|
139
|
+
videoUrl: beatmapPage.video_url,
|
|
138
140
|
},
|
|
139
141
|
});
|
|
140
142
|
}
|
|
@@ -0,0 +1,85 @@
|
|
|
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
|
+
import {
|
|
7
|
+
PutBucketCorsCommand,
|
|
8
|
+
PutObjectCommand,
|
|
9
|
+
S3Client,
|
|
10
|
+
} from "@aws-sdk/client-s3";
|
|
11
|
+
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
|
12
|
+
import { validateIntrinsicToken } from "../utils/validateToken";
|
|
13
|
+
import { getUserBySession } from "../utils/getUserBySession";
|
|
14
|
+
import { User } from "@supabase/supabase-js";
|
|
15
|
+
|
|
16
|
+
const s3Client = new S3Client({
|
|
17
|
+
region: "auto",
|
|
18
|
+
endpoint: "https://s3.eu-central-003.backblazeb2.com",
|
|
19
|
+
credentials: {
|
|
20
|
+
secretAccessKey: process.env.SECRET_BUCKET || "",
|
|
21
|
+
accessKeyId: process.env.ACCESS_BUCKET || "",
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export const Schema = {
|
|
26
|
+
input: z.strictObject({
|
|
27
|
+
session: z.string(),
|
|
28
|
+
contentLength: z.number(),
|
|
29
|
+
contentType: z.string(),
|
|
30
|
+
intrinsicToken: z.string(),
|
|
31
|
+
}),
|
|
32
|
+
output: z.strictObject({
|
|
33
|
+
error: z.string().optional(),
|
|
34
|
+
url: z.string().optional(),
|
|
35
|
+
objectKey: z.string().optional(),
|
|
36
|
+
}),
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export async function POST(request: Request): Promise<NextResponse> {
|
|
40
|
+
return protectedApi({
|
|
41
|
+
request,
|
|
42
|
+
schema: Schema,
|
|
43
|
+
authorization: validUser,
|
|
44
|
+
activity: handler,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function handler({
|
|
49
|
+
session,
|
|
50
|
+
contentLength,
|
|
51
|
+
contentType,
|
|
52
|
+
intrinsicToken,
|
|
53
|
+
}: (typeof Schema)["input"]["_type"]): Promise<
|
|
54
|
+
NextResponse<(typeof Schema)["output"]["_type"]>
|
|
55
|
+
> {
|
|
56
|
+
const user = (await getUserBySession(session)) as User;
|
|
57
|
+
|
|
58
|
+
if (!validateIntrinsicToken(intrinsicToken)) {
|
|
59
|
+
return NextResponse.json({
|
|
60
|
+
error: "Invalid intrinsic token",
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (contentLength > 25000000) {
|
|
65
|
+
return NextResponse.json({
|
|
66
|
+
error: "Max content length exceeded.",
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const key = `beatmap-video-${Date.now()}-${user.id}`;
|
|
71
|
+
const command = new PutObjectCommand({
|
|
72
|
+
Bucket: "rhthia-avatars",
|
|
73
|
+
Key: key,
|
|
74
|
+
ContentLength: contentLength,
|
|
75
|
+
ContentType: contentType,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const presigned = await getSignedUrl(s3Client, command, {
|
|
79
|
+
expiresIn: 3600,
|
|
80
|
+
});
|
|
81
|
+
return NextResponse.json({
|
|
82
|
+
url: presigned,
|
|
83
|
+
objectKey: key,
|
|
84
|
+
});
|
|
85
|
+
}
|
package/api/updateBeatmapPage.ts
CHANGED
|
@@ -13,6 +13,7 @@ export const Schema = {
|
|
|
13
13
|
beatmapHash: z.string().optional(),
|
|
14
14
|
tags: z.string().optional(),
|
|
15
15
|
description: z.string().optional(),
|
|
16
|
+
videoUrl: z.string().optional(),
|
|
16
17
|
}),
|
|
17
18
|
output: z.strictObject({
|
|
18
19
|
error: z.string().optional(),
|
|
@@ -41,6 +42,7 @@ export async function handler({
|
|
|
41
42
|
id,
|
|
42
43
|
description,
|
|
43
44
|
tags,
|
|
45
|
+
videoUrl,
|
|
44
46
|
}: (typeof Schema)["input"]["_type"]): Promise<
|
|
45
47
|
NextResponse<(typeof Schema)["output"]["_type"]>
|
|
46
48
|
> {
|
|
@@ -82,8 +84,9 @@ export async function handler({
|
|
|
82
84
|
owner: userData.id,
|
|
83
85
|
description: description ? description : pageData.description,
|
|
84
86
|
tags: tags ? tags : pageData.tags,
|
|
87
|
+
video_url: videoUrl ? videoUrl : pageData.video_url,
|
|
85
88
|
updated_at: Date.now(),
|
|
86
|
-
};
|
|
89
|
+
} as any;
|
|
87
90
|
|
|
88
91
|
if (beatmapHash && beatmapData) {
|
|
89
92
|
upsertPayload["title"] = beatmapData.title;
|
package/index.ts
CHANGED
|
@@ -473,6 +473,7 @@ export const Schema = {
|
|
|
473
473
|
status: z.string().nullable().optional(),
|
|
474
474
|
description: z.string().nullable().optional(),
|
|
475
475
|
tags: z.string().nullable().optional(),
|
|
476
|
+
videoUrl: z.string().nullable().optional(),
|
|
476
477
|
})
|
|
477
478
|
.optional(),
|
|
478
479
|
}),
|
|
@@ -496,9 +497,9 @@ export const Schema = {
|
|
|
496
497
|
z.object({
|
|
497
498
|
id: z.number(),
|
|
498
499
|
awarded_sp: z.number().nullable(),
|
|
499
|
-
created_at: z.string(),
|
|
500
|
+
created_at: z.string(),
|
|
500
501
|
misses: z.number().nullable(),
|
|
501
|
-
mods: z.record(z.unknown()),
|
|
502
|
+
mods: z.record(z.unknown()),
|
|
502
503
|
passed: z.boolean().nullable(),
|
|
503
504
|
songId: z.string().nullable(),
|
|
504
505
|
speed: z.number().nullable(),
|
|
@@ -528,6 +529,7 @@ export const Schema = {
|
|
|
528
529
|
ownerUsername: z.string().nullable().optional(),
|
|
529
530
|
ownerAvatar: z.string().nullable().optional(),
|
|
530
531
|
status: z.string().nullable().optional(),
|
|
532
|
+
videoUrl: z.string().nullable(),
|
|
531
533
|
})
|
|
532
534
|
.optional(),
|
|
533
535
|
}),
|
|
@@ -1164,6 +1166,26 @@ import { Schema as GetVerified } from "./api/getVerified"
|
|
|
1164
1166
|
export { Schema as SchemaGetVerified } from "./api/getVerified"
|
|
1165
1167
|
export const getVerified = handleApi({url:"/api/getVerified",...GetVerified})
|
|
1166
1168
|
|
|
1169
|
+
// ./api/getVideoUploadUrl.ts API
|
|
1170
|
+
|
|
1171
|
+
/*
|
|
1172
|
+
export const Schema = {
|
|
1173
|
+
input: z.strictObject({
|
|
1174
|
+
session: z.string(),
|
|
1175
|
+
contentLength: z.number(),
|
|
1176
|
+
contentType: z.string(),
|
|
1177
|
+
intrinsicToken: z.string(),
|
|
1178
|
+
}),
|
|
1179
|
+
output: z.strictObject({
|
|
1180
|
+
error: z.string().optional(),
|
|
1181
|
+
url: z.string().optional(),
|
|
1182
|
+
objectKey: z.string().optional(),
|
|
1183
|
+
}),
|
|
1184
|
+
};*/
|
|
1185
|
+
import { Schema as GetVideoUploadUrl } from "./api/getVideoUploadUrl"
|
|
1186
|
+
export { Schema as SchemaGetVideoUploadUrl } from "./api/getVideoUploadUrl"
|
|
1187
|
+
export const getVideoUploadUrl = handleApi({url:"/api/getVideoUploadUrl",...GetVideoUploadUrl})
|
|
1188
|
+
|
|
1167
1189
|
// ./api/nominateMap.ts API
|
|
1168
1190
|
|
|
1169
1191
|
/*
|
|
@@ -1293,6 +1315,7 @@ export const Schema = {
|
|
|
1293
1315
|
beatmapHash: z.string().optional(),
|
|
1294
1316
|
tags: z.string().optional(),
|
|
1295
1317
|
description: z.string().optional(),
|
|
1318
|
+
videoUrl: z.string().optional(),
|
|
1296
1319
|
}),
|
|
1297
1320
|
output: z.strictObject({
|
|
1298
1321
|
error: z.string().optional(),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rhythia-api",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "215.0.0",
|
|
4
4
|
"main": "index.ts",
|
|
5
5
|
"author": "online-contributors-cunev",
|
|
6
6
|
"scripts": {
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"sharp": "^0.33.5",
|
|
44
44
|
"short-uuid": "^5.2.0",
|
|
45
45
|
"simple-git": "^3.25.0",
|
|
46
|
-
"supabase": "^2.
|
|
46
|
+
"supabase": "^2.53.6",
|
|
47
47
|
"tsx": "^4.17.0",
|
|
48
48
|
"utf-8-validate": "^6.0.4",
|
|
49
49
|
"uuid": "^11.1.0",
|
package/types/database.ts
CHANGED
|
@@ -10,7 +10,7 @@ export type Database = {
|
|
|
10
10
|
// Allows to automatically instantiate createClient with right options
|
|
11
11
|
// instead of createClient<Database, { PostgrestVersion: 'XX' }>(URL, KEY)
|
|
12
12
|
__InternalSupabase: {
|
|
13
|
-
PostgrestVersion: "
|
|
13
|
+
PostgrestVersion: "13.0.4"
|
|
14
14
|
}
|
|
15
15
|
public: {
|
|
16
16
|
Tables: {
|
|
@@ -158,6 +158,7 @@ export type Database = {
|
|
|
158
158
|
tags: string
|
|
159
159
|
title: string | null
|
|
160
160
|
updated_at: number | null
|
|
161
|
+
video_url: string | null
|
|
161
162
|
}
|
|
162
163
|
Insert: {
|
|
163
164
|
created_at?: string
|
|
@@ -172,6 +173,7 @@ export type Database = {
|
|
|
172
173
|
tags?: string
|
|
173
174
|
title?: string | null
|
|
174
175
|
updated_at?: number | null
|
|
176
|
+
video_url?: string | null
|
|
175
177
|
}
|
|
176
178
|
Update: {
|
|
177
179
|
created_at?: string
|
|
@@ -186,6 +188,7 @@ export type Database = {
|
|
|
186
188
|
tags?: string
|
|
187
189
|
title?: string | null
|
|
188
190
|
updated_at?: number | null
|
|
191
|
+
video_url?: string | null
|
|
189
192
|
}
|
|
190
193
|
Relationships: [
|
|
191
194
|
{
|
|
@@ -703,10 +706,8 @@ export type Database = {
|
|
|
703
706
|
get_badge_leaderboard: {
|
|
704
707
|
Args: { p_limit?: number }
|
|
705
708
|
Returns: {
|
|
706
|
-
all_badges: Json
|
|
707
709
|
avatar_url: string
|
|
708
710
|
display_name: string
|
|
709
|
-
earned_badges: string[]
|
|
710
711
|
id: number
|
|
711
712
|
special_badge_count: number
|
|
712
713
|
}[]
|