rhythia-api 233.0.0 → 235.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.
Files changed (90) hide show
  1. package/.codex +0 -0
  2. package/.env +1 -12
  3. package/README.md +4 -4
  4. package/api/acceptInvite.ts +1 -1
  5. package/api/addCollectionMap.ts +1 -1
  6. package/api/chartPublicStats.ts +1 -1
  7. package/api/checkQualified.ts +93 -93
  8. package/api/createBeatmap.ts +53 -62
  9. package/api/createBeatmapPage.ts +1 -1
  10. package/api/createClan.ts +1 -1
  11. package/api/createCollection.ts +1 -1
  12. package/api/createInvite.ts +1 -1
  13. package/api/createSupporter.ts +1 -1
  14. package/api/deleteBeatmapPage.ts +2 -5
  15. package/api/deleteCollection.ts +1 -1
  16. package/api/deleteCollectionMap.ts +1 -1
  17. package/api/editAboutMe.ts +1 -1
  18. package/api/editClan.ts +1 -1
  19. package/api/editCollection.ts +1 -2
  20. package/api/editProfile.ts +1 -1
  21. package/api/enhancedSearch.ts +113 -113
  22. package/api/executeAdminOperation.ts +1 -22
  23. package/api/getAvatarUploadUrl.ts +1 -1
  24. package/api/getBadgeLeaders.ts +1 -1
  25. package/api/getBadgedUsers.ts +1 -1
  26. package/api/getBeatmapComments.ts +1 -1
  27. package/api/getBeatmapPage.ts +74 -106
  28. package/api/getBeatmapPageById.ts +70 -109
  29. package/api/getBeatmapStarRating.ts +1 -1
  30. package/api/getBeatmaps.ts +123 -93
  31. package/api/getClan.ts +1 -1
  32. package/api/getClans.ts +1 -1
  33. package/api/getCollection.ts +1 -1
  34. package/api/getCollections.ts +1 -1
  35. package/api/getInventory.ts +1 -1
  36. package/api/getLeaderboard.ts +1 -1
  37. package/api/getMapUploadUrl.ts +2 -2
  38. package/api/getOnlinePlayers.ts +1 -1
  39. package/api/getPassToken.ts +1 -1
  40. package/api/getProfile.ts +51 -31
  41. package/api/getPublicStats.ts +5 -5
  42. package/api/getRawStarRating.ts +1 -1
  43. package/api/getScore.ts +1 -1
  44. package/api/getStoryBeatmaps.ts +1 -1
  45. package/api/getTimestamp.ts +1 -1
  46. package/api/getUserScores.ts +19 -19
  47. package/api/getVerified.ts +1 -1
  48. package/api/getVideoUploadUrl.ts +1 -1
  49. package/api/postBeatmapComment.ts +1 -1
  50. package/api/qualifyMap.ts +97 -92
  51. package/api/rankMapsArchive.ts +20 -20
  52. package/api/searchUsers.ts +1 -1
  53. package/api/setPasskey.ts +1 -1
  54. package/api/submitScore.ts +1 -6
  55. package/api/submitScoreInternal.ts +461 -449
  56. package/api/updateBeatmapPage.ts +1 -1
  57. package/api/vetoMap.ts +101 -101
  58. package/index.ts +180 -167
  59. package/package.json +7 -12
  60. package/queries/admin_delete_user.sql +39 -39
  61. package/queries/admin_exclude_user.sql +21 -21
  62. package/queries/admin_invalidate_ranked_scores.sql +18 -18
  63. package/queries/admin_log_action.sql +10 -10
  64. package/queries/admin_profanity_clear.sql +29 -29
  65. package/queries/admin_remove_all_scores.sql +29 -29
  66. package/queries/admin_restrict_user.sql +21 -21
  67. package/queries/admin_search_users.sql +24 -24
  68. package/queries/admin_silence_user.sql +21 -21
  69. package/queries/admin_unban_user.sql +21 -21
  70. package/queries/enhanced_search.sql +217 -217
  71. package/queries/get_badge_leaderboard.sql +50 -50
  72. package/queries/get_clan_leaderboard.sql +68 -68
  73. package/queries/get_collections_v4.sql +109 -109
  74. package/queries/get_top_scores_for_beatmap.sql +44 -44
  75. package/queries/get_top_scores_for_beatmap3.sql +38 -0
  76. package/queries/get_user_by_email.sql +32 -32
  77. package/queries/get_user_scores_lastday.sql +47 -47
  78. package/queries/get_user_scores_reign.sql +31 -31
  79. package/queries/get_user_scores_top_and_stats.sql +84 -84
  80. package/queries/grant_special_badges.sql +69 -69
  81. package/types/database.ts +1288 -1248
  82. package/utils/beatmapTopScores.ts +84 -0
  83. package/utils/mapLifecycleWebhook.ts +287 -277
  84. package/utils/requestGeo.ts +13 -0
  85. package/utils/requestUtils.ts +127 -127
  86. package/utils/response.ts +11 -0
  87. package/worker.ts +189 -0
  88. package/wrangler.jsonc +10 -0
  89. package/index.html +0 -3
  90. package/vercel.json +0 -13
package/.codex ADDED
File without changes
package/.env CHANGED
@@ -1,12 +1 @@
1
- REDIS_PORT=18304
2
- REDIS_USERNAME=default
3
- REDIS_PASSWORD=yg7hYF9LPhGJRmORVIjMB8FWP2axPGto
4
- REDIS_HOST=redis-18304.c55.eu-central-1-1.ec2.cloud.redislabs.com
5
- CF_NAMESPACE_ID=571cf88650514eeba649517a75ede74a
6
- CF_API_TOKEN=Ldsh-UKJZw0feHmZOvhSCxaurwJBVW12LgnV_v38
7
- CF_ACCOUNT_ID=571cf88650514eeba649517a75ede74a
8
- TOKEN_SECRET=fa686bfdffd3758f6377abbc23bf3d9bdc1a0dda4a6e7f8dbdd579fa1ff6d7e2
9
- ACCESS_BUCKET=003c245e893e8060000000003
10
- SECRET_BUCKET=K003LpAu8X+2lJ09EB8NtPL/OZXV8ts
11
- ADMIN_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InBma2FqbmdibGxjYmR6b3lscnZwIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImlhdCI6MTcyOTAxNTMwNSwiZXhwIjoyMDQ0NTkxMzA1fQ.dKg8Wq3zZEBAH63V7q1D8a8N-d6qkB5Inm524Vmso3k
12
- PROD_DO_NOT_WRITE_PG=postgresql://postgres:huaUpTz3d3p7w6VU@db.pfkajngbllcbdzoylrvp.supabase.co:5432/postgres
1
+ PROD_DO_NOT_WRITE_PG=postgresql://postgres:pMCnLpLl6UmdcStP@db.pfkajngbllcbdzoylrvp.supabase.co:5432/postgres
package/README.md CHANGED
@@ -2,10 +2,10 @@
2
2
 
3
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
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.
5
+ The deploy target is Cloudflare Workers. `worker.ts` dispatches the existing `api/*.ts` handlers, keeps the old `/api/*` paths, and applies the same CORS policy the old deployment used.
6
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.
7
+ Local development uses Wrangler and reads secrets from the existing `.env` file. Install dependencies, then run `yarn dev`. Deploy with `yarn deploy`. For production, mirror the same keys in the Cloudflare dashboard or with Wrangler secrets.
8
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.
9
+ 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`. Score ingestion also still expects the Redis credentials already used by `submitScoreInternal`.
10
10
 
11
- //
11
+ 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.
@@ -1,4 +1,4 @@
1
- import { NextResponse } from "next/server";
1
+ import { NextResponse } from "../utils/response";
2
2
  import z from "zod";
3
3
  import { protectedApi, validUser } from "../utils/requestUtils";
4
4
  import { supabase } from "../utils/supabase";
@@ -1,4 +1,4 @@
1
- import { NextResponse } from "next/server";
1
+ import { NextResponse } from "../utils/response";
2
2
  import z from "zod";
3
3
  import { protectedApi, validUser } from "../utils/requestUtils";
4
4
  import { supabase } from "../utils/supabase";
@@ -1,4 +1,4 @@
1
- import { NextResponse } from "next/server";
1
+ import { NextResponse } from "../utils/response";
2
2
  import z from "zod";
3
3
  import { protectedApi } from "../utils/requestUtils";
4
4
  import { supabase } from "../utils/supabase";
@@ -1,93 +1,93 @@
1
- import { NextResponse } from "next/server";
2
- import z from "zod";
3
- import { protectedApi } from "../utils/requestUtils";
4
- import { supabase } from "../utils/supabase";
5
- import { postMapLifecycleWebhook } from "../utils/mapLifecycleWebhook";
6
-
7
- const INTERNAL_SECRET = "testing-1";
8
- const THREE_DAYS_MS = 3 * 24 * 60 * 60 * 1000;
9
-
10
- export const Schema = {
11
- input: z.strictObject({
12
- secret: z.string(),
13
- }),
14
- output: z.object({
15
- error: z.string().optional(),
16
- updated: z.number(),
17
- }),
18
- };
19
-
20
- export async function POST(request: Request): Promise<NextResponse> {
21
- return protectedApi({
22
- request,
23
- schema: Schema,
24
- authorization: () => {},
25
- activity: handler,
26
- });
27
- }
28
-
29
- export async function handler({
30
- secret,
31
- }: (typeof Schema)["input"]["_type"]): Promise<
32
- NextResponse<(typeof Schema)["output"]["_type"]>
33
- > {
34
- if (secret !== INTERNAL_SECRET) {
35
- return NextResponse.json({ error: "Internal usage only", updated: 0 });
36
- }
37
-
38
- const { data: qualifiedMaps, error: qualifiedMapsError } = await supabase
39
- .from("beatmapPages")
40
- .select("id,status,qualifiedAt")
41
- .eq("qualified", true)
42
- .not("qualifiedAt", "is", null);
43
-
44
- if (qualifiedMapsError) {
45
- return NextResponse.json({ error: qualifiedMapsError.message, updated: 0 });
46
- }
47
-
48
- const cutoffTimestamp = Date.now() - THREE_DAYS_MS;
49
- const mapsToRank = (qualifiedMaps || []).filter((mapData) => {
50
- if (mapData.status === "RANKED") {
51
- return false;
52
- }
53
-
54
- const qualifiedAt = Date.parse(mapData.qualifiedAt || "");
55
- if (!Number.isFinite(qualifiedAt)) {
56
- return false;
57
- }
58
-
59
- return qualifiedAt <= cutoffTimestamp;
60
- });
61
-
62
- if (mapsToRank.length === 0) {
63
- return NextResponse.json({ updated: 0 });
64
- }
65
-
66
- const { error: updateError } = await supabase
67
- .from("beatmapPages")
68
- .update({
69
- status: "RANKED",
70
- ranked_at: Date.now(),
71
- qualified: false,
72
- qualifiedAt: null,
73
- })
74
- .in(
75
- "id",
76
- mapsToRank.map((mapData) => mapData.id)
77
- );
78
-
79
- if (updateError) {
80
- return NextResponse.json({ error: updateError.message, updated: 0 });
81
- }
82
-
83
- await Promise.allSettled(
84
- mapsToRank.map((mapData) =>
85
- postMapLifecycleWebhook({
86
- mapId: mapData.id,
87
- event: "ranked",
88
- })
89
- )
90
- );
91
-
92
- return NextResponse.json({ updated: mapsToRank.length });
93
- }
1
+ import { NextResponse } from "../utils/response";
2
+ import z from "zod";
3
+ import { protectedApi } from "../utils/requestUtils";
4
+ import { supabase } from "../utils/supabase";
5
+ import { postMapLifecycleWebhook } from "../utils/mapLifecycleWebhook";
6
+
7
+ const INTERNAL_SECRET = "testing-1";
8
+ const THREE_DAYS_MS = 3 * 24 * 60 * 60 * 1000;
9
+
10
+ export const Schema = {
11
+ input: z.strictObject({
12
+ secret: z.string(),
13
+ }),
14
+ output: z.object({
15
+ error: z.string().optional(),
16
+ updated: z.number(),
17
+ }),
18
+ };
19
+
20
+ export async function POST(request: Request): Promise<NextResponse> {
21
+ return protectedApi({
22
+ request,
23
+ schema: Schema,
24
+ authorization: () => {},
25
+ activity: handler,
26
+ });
27
+ }
28
+
29
+ export async function handler({
30
+ secret,
31
+ }: (typeof Schema)["input"]["_type"]): Promise<
32
+ NextResponse<(typeof Schema)["output"]["_type"]>
33
+ > {
34
+ if (secret !== INTERNAL_SECRET) {
35
+ return NextResponse.json({ error: "Internal usage only", updated: 0 });
36
+ }
37
+
38
+ const { data: qualifiedMaps, error: qualifiedMapsError } = await supabase
39
+ .from("beatmapPages")
40
+ .select("id,status,qualifiedAt")
41
+ .eq("qualified", true)
42
+ .not("qualifiedAt", "is", null);
43
+
44
+ if (qualifiedMapsError) {
45
+ return NextResponse.json({ error: qualifiedMapsError.message, updated: 0 });
46
+ }
47
+
48
+ const cutoffTimestamp = Date.now() - THREE_DAYS_MS;
49
+ const mapsToRank = (qualifiedMaps || []).filter((mapData) => {
50
+ if (mapData.status === "RANKED") {
51
+ return false;
52
+ }
53
+
54
+ const qualifiedAt = Date.parse(mapData.qualifiedAt || "");
55
+ if (!Number.isFinite(qualifiedAt)) {
56
+ return false;
57
+ }
58
+
59
+ return qualifiedAt <= cutoffTimestamp;
60
+ });
61
+
62
+ if (mapsToRank.length === 0) {
63
+ return NextResponse.json({ updated: 0 });
64
+ }
65
+
66
+ const { error: updateError } = await supabase
67
+ .from("beatmapPages")
68
+ .update({
69
+ status: "RANKED",
70
+ ranked_at: Date.now(),
71
+ qualified: false,
72
+ qualifiedAt: null,
73
+ })
74
+ .in(
75
+ "id",
76
+ mapsToRank.map((mapData) => mapData.id)
77
+ );
78
+
79
+ if (updateError) {
80
+ return NextResponse.json({ error: updateError.message, updated: 0 });
81
+ }
82
+
83
+ await Promise.allSettled(
84
+ mapsToRank.map((mapData) =>
85
+ postMapLifecycleWebhook({
86
+ mapId: mapData.id,
87
+ event: "ranked",
88
+ })
89
+ )
90
+ );
91
+
92
+ return NextResponse.json({ updated: mapsToRank.length });
93
+ }
@@ -1,4 +1,4 @@
1
- import { NextResponse } from "next/server";
1
+ import { NextResponse } from "../utils/response";
2
2
  import z from "zod";
3
3
  import { protectedApi, validUser } from "../utils/requestUtils";
4
4
  import { SSPMParser } from "../utils/star-calc/sspmParser";
@@ -7,7 +7,6 @@ import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
7
7
  import { rateMap } from "../utils/star-calc";
8
8
  import { getUserBySession } from "../utils/getUserBySession";
9
9
  import { User } from "@supabase/supabase-js";
10
- import { NodeHttpHandler } from "@aws-sdk/node-http-handler";
11
10
 
12
11
  const s3Client = new S3Client({
13
12
  region: "auto",
@@ -19,21 +18,10 @@ const s3Client = new S3Client({
19
18
  requestChecksumCalculation: "WHEN_REQUIRED",
20
19
  });
21
20
 
22
- // Remove ALL validation and checksum middleware
23
- const middlewareToRemove = [
24
- "build:checksum",
25
- "build:content-checksum",
26
- "build:content-md5",
27
- "validate",
28
- "validateChecksum",
29
- ];
30
-
31
21
  s3Client.middlewareStack.add(
32
22
  (next) =>
33
23
  async (args): Promise<any> => {
34
24
  const request = args.request as RequestInit;
35
-
36
- // Remove checksum headers
37
25
  const headers = request.headers as Record<string, string>;
38
26
  delete headers["x-amz-checksum-crc32"];
39
27
  delete headers["x-amz-checksum-crc32c"];
@@ -41,21 +29,51 @@ s3Client.middlewareStack.add(
41
29
  delete headers["x-amz-checksum-sha256"];
42
30
  request.headers = headers;
43
31
 
44
- Object.entries(request.headers).forEach(
45
- // @ts-ignore
46
- ([key, value]: [string, string]): void => {
47
- if (!request.headers) {
48
- request.headers = {};
49
- }
50
- (request.headers as Record<string, string>)[key] = value;
51
- }
52
- );
53
-
54
32
  return next(args);
55
33
  },
56
34
  { step: "build", name: "customHeaders" }
57
35
  );
58
36
 
37
+ function getCoverContentType(cover: Uint8Array) {
38
+ if (
39
+ cover.length >= 8 &&
40
+ cover[0] === 0x89 &&
41
+ cover[1] === 0x50 &&
42
+ cover[2] === 0x4e &&
43
+ cover[3] === 0x47
44
+ ) {
45
+ return "image/png";
46
+ }
47
+
48
+ if (cover.length >= 3 && cover[0] === 0xff && cover[1] === 0xd8) {
49
+ return "image/jpeg";
50
+ }
51
+
52
+ if (
53
+ cover.length >= 12 &&
54
+ cover[0] === 0x52 &&
55
+ cover[1] === 0x49 &&
56
+ cover[2] === 0x46 &&
57
+ cover[3] === 0x46 &&
58
+ cover[8] === 0x57 &&
59
+ cover[9] === 0x45 &&
60
+ cover[10] === 0x42 &&
61
+ cover[11] === 0x50
62
+ ) {
63
+ return "image/webp";
64
+ }
65
+
66
+ if (
67
+ cover.length >= 6 &&
68
+ cover[0] === 0x47 &&
69
+ cover[1] === 0x49 &&
70
+ cover[2] === 0x46
71
+ ) {
72
+ return "image/gif";
73
+ }
74
+
75
+ return "image/jpeg";
76
+ }
59
77
  export const Schema = {
60
78
  input: z.strictObject({
61
79
  url: z.string(),
@@ -147,44 +165,18 @@ export async function handler({
147
165
  return NextResponse.json({ error: "Already Exists" });
148
166
  }
149
167
  }
150
-
151
168
  const imgkey = `beatmap-img-${Date.now()}-${digested}`;
152
-
153
- let buffer = Buffer.from([]);
154
- try {
155
- buffer = await require("sharp")(parsedData.cover)
156
- .resize(250)
157
- .jpeg({ mozjpeg: true })
158
- .toBuffer();
159
- } catch (error) {}
160
-
161
- let largeBuffer = Buffer.from([]);
162
- try {
163
- largeBuffer = await require("sharp")(parsedData.cover)
164
- .resize(850)
165
- .jpeg({ mozjpeg: true })
166
- .toBuffer();
167
- } catch (error) {}
168
-
169
- // Images
170
- const command = new PutObjectCommand({
171
- Bucket: "rhthia-avatars",
172
- Key: imgkey,
173
- Body: buffer,
174
- ContentType: "image/jpeg",
175
- });
176
-
177
- await s3Client.send(command);
178
-
179
- const command2 = new PutObjectCommand({
180
- Bucket: "rhthia-avatars",
181
- Key: imgkey + "large",
182
- Body: largeBuffer,
183
- ContentType: "image/jpeg",
184
- });
185
-
186
- await s3Client.send(command2);
187
- // Images End
169
+ const cover = parsedData.cover || Buffer.from([]);
170
+ const coverContentType = getCoverContentType(cover);
171
+
172
+ await s3Client.send(
173
+ new PutObjectCommand({
174
+ Bucket: "rhthia-avatars",
175
+ Key: imgkey,
176
+ Body: cover,
177
+ ContentType: coverContentType,
178
+ })
179
+ );
188
180
 
189
181
  const markers = parsedData.markers.sort((a, b) => a.position - b.position);
190
182
 
@@ -197,10 +189,9 @@ export async function handler({
197
189
  length: markers[markers.length - 1].position,
198
190
  beatmapFile: url,
199
191
  image: `https://static.rhythia.com/${imgkey}`,
200
- imageLarge: `https://static.rhythia.com/${imgkey}large`,
192
+ imageLarge: `https://static.rhythia.com/${imgkey}`,
201
193
  starRating: rateMap(parsedData),
202
194
  });
203
-
204
195
  if (upserted.error?.message.length) {
205
196
  return NextResponse.json({ error: upserted.error.message });
206
197
  }
@@ -1,4 +1,4 @@
1
- import { NextResponse } from "next/server";
1
+ import { NextResponse } from "../utils/response";
2
2
  import z from "zod";
3
3
  import { protectedApi, validUser } from "../utils/requestUtils";
4
4
  import { supabase } from "../utils/supabase";
package/api/createClan.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { NextResponse } from "next/server";
1
+ import { NextResponse } from "../utils/response";
2
2
  import z from "zod";
3
3
  import { protectedApi, validUser } from "../utils/requestUtils";
4
4
  import { supabase } from "../utils/supabase";
@@ -1,4 +1,4 @@
1
- import { NextResponse } from "next/server";
1
+ import { NextResponse } from "../utils/response";
2
2
  import z from "zod";
3
3
  import { protectedApi, validUser } from "../utils/requestUtils";
4
4
  import { supabase } from "../utils/supabase";
@@ -1,4 +1,4 @@
1
- import { NextResponse } from "next/server";
1
+ import { NextResponse } from "../utils/response";
2
2
  import z from "zod";
3
3
  import { protectedApi, validUser } from "../utils/requestUtils";
4
4
  import { supabase } from "../utils/supabase";
@@ -1,4 +1,4 @@
1
- import { NextResponse } from "next/server";
1
+ import { NextResponse } from "../utils/response";
2
2
  import z from "zod";
3
3
  import { protectedApi } from "../utils/requestUtils";
4
4
  import { supabase } from "../utils/supabase";
@@ -1,10 +1,10 @@
1
- import { NextResponse } from "next/server";
1
+ import { NextResponse } from "../utils/response";
2
2
  import z from "zod";
3
3
  import { protectedApi, validUser } from "../utils/requestUtils";
4
4
  import { supabase } from "../utils/supabase";
5
5
  import { getUserBySession } from "../utils/getUserBySession";
6
6
  import { User } from "@supabase/supabase-js";
7
- import { invalidateCache, invalidateCachePrefix } from "../utils/cache";
7
+ import { invalidateCache } from "../utils/cache";
8
8
 
9
9
  export const Schema = {
10
10
  input: z.strictObject({
@@ -76,9 +76,6 @@ export async function handler({
76
76
  .eq("beatmapHash", beatmapData.beatmapHash);
77
77
 
78
78
  await invalidateCache(`beatmap-comments:${id}`);
79
- if (pageData.latestBeatmapHash) {
80
- await invalidateCachePrefix(`beatmap-scores:${pageData.latestBeatmapHash}`);
81
- }
82
79
 
83
80
  return NextResponse.json({});
84
81
  }
@@ -1,4 +1,4 @@
1
- import { NextResponse } from "next/server";
1
+ import { NextResponse } from "../utils/response";
2
2
  import z from "zod";
3
3
  import { protectedApi, validUser } from "../utils/requestUtils";
4
4
  import { supabase } from "../utils/supabase";
@@ -1,4 +1,4 @@
1
- import { NextResponse } from "next/server";
1
+ import { NextResponse } from "../utils/response";
2
2
  import z from "zod";
3
3
  import { protectedApi, validUser } from "../utils/requestUtils";
4
4
  import { supabase } from "../utils/supabase";
@@ -1,4 +1,4 @@
1
- import { NextResponse } from "next/server";
1
+ import { NextResponse } from "../utils/response";
2
2
  import z from "zod";
3
3
  import { Database } from "../types/database";
4
4
  import { protectedApi, validUser } from "../utils/requestUtils";
package/api/editClan.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { NextResponse } from "next/server";
1
+ import { NextResponse } from "../utils/response";
2
2
  import z from "zod";
3
3
  import { protectedApi, validUser } from "../utils/requestUtils";
4
4
  import { supabase } from "../utils/supabase";
@@ -1,10 +1,9 @@
1
- import { NextResponse } from "next/server";
1
+ import { NextResponse } from "../utils/response";
2
2
  import z from "zod";
3
3
  import { protectedApi, validUser } from "../utils/requestUtils";
4
4
  import { supabase } from "../utils/supabase";
5
5
  import { getUserBySession } from "../utils/getUserBySession";
6
6
  import { User } from "@supabase/supabase-js";
7
- import { describe } from "node:test";
8
7
 
9
8
  export const Schema = {
10
9
  input: z.strictObject({
@@ -1,4 +1,4 @@
1
- import { NextResponse } from "next/server";
1
+ import { NextResponse } from "../utils/response";
2
2
  import z from "zod";
3
3
  import { Database } from "../types/database";
4
4
  import { protectedApi, validUser } from "../utils/requestUtils";