rhythia-api 211.0.0 → 214.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/getBadgeLeaders.ts +0 -2
- package/api/getVideoUploadUrl.ts +85 -0
- package/api/updateBeatmapPage.ts +4 -1
- package/index.ts +21 -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/getBadgeLeaders.ts
CHANGED
|
@@ -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
|
@@ -384,8 +384,6 @@ export const Schema = {
|
|
|
384
384
|
display_name: z.string(),
|
|
385
385
|
avatar_url: z.string().nullable(),
|
|
386
386
|
special_badge_count: z.number(),
|
|
387
|
-
earned_badges: z.array(z.string()),
|
|
388
|
-
all_badges: z.any(), // JSON type
|
|
389
387
|
})
|
|
390
388
|
),
|
|
391
389
|
total_count: z.number(),
|
|
@@ -1166,6 +1164,26 @@ import { Schema as GetVerified } from "./api/getVerified"
|
|
|
1166
1164
|
export { Schema as SchemaGetVerified } from "./api/getVerified"
|
|
1167
1165
|
export const getVerified = handleApi({url:"/api/getVerified",...GetVerified})
|
|
1168
1166
|
|
|
1167
|
+
// ./api/getVideoUploadUrl.ts API
|
|
1168
|
+
|
|
1169
|
+
/*
|
|
1170
|
+
export const Schema = {
|
|
1171
|
+
input: z.strictObject({
|
|
1172
|
+
session: z.string(),
|
|
1173
|
+
contentLength: z.number(),
|
|
1174
|
+
contentType: z.string(),
|
|
1175
|
+
intrinsicToken: z.string(),
|
|
1176
|
+
}),
|
|
1177
|
+
output: z.strictObject({
|
|
1178
|
+
error: z.string().optional(),
|
|
1179
|
+
url: z.string().optional(),
|
|
1180
|
+
objectKey: z.string().optional(),
|
|
1181
|
+
}),
|
|
1182
|
+
};*/
|
|
1183
|
+
import { Schema as GetVideoUploadUrl } from "./api/getVideoUploadUrl"
|
|
1184
|
+
export { Schema as SchemaGetVideoUploadUrl } from "./api/getVideoUploadUrl"
|
|
1185
|
+
export const getVideoUploadUrl = handleApi({url:"/api/getVideoUploadUrl",...GetVideoUploadUrl})
|
|
1186
|
+
|
|
1169
1187
|
// ./api/nominateMap.ts API
|
|
1170
1188
|
|
|
1171
1189
|
/*
|
|
@@ -1295,6 +1313,7 @@ export const Schema = {
|
|
|
1295
1313
|
beatmapHash: z.string().optional(),
|
|
1296
1314
|
tags: z.string().optional(),
|
|
1297
1315
|
description: z.string().optional(),
|
|
1316
|
+
videoUrl: z.string().optional(),
|
|
1298
1317
|
}),
|
|
1299
1318
|
output: z.strictObject({
|
|
1300
1319
|
error: z.string().optional(),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rhythia-api",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "214.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
|
}[]
|