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 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.
@@ -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(), // Assuming Supabase returns timestamps as strings
18
+ created_at: z.string(),
19
19
  misses: z.number().nullable(),
20
- mods: z.record(z.unknown()), // JSONB data, can be any object
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
+ }
@@ -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(), // Assuming Supabase returns timestamps as strings
500
+ created_at: z.string(),
500
501
  misses: z.number().nullable(),
501
- mods: z.record(z.unknown()), // JSONB data, can be any object
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": "212.0.0",
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.19.7",
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: "12.2.3 (519615d)"
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
  }[]