rhythia-api 217.0.0 → 225.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/.env +12 -0
- package/.yarnrc +1 -0
- package/README.md +2 -0
- package/api/createBeatmap.ts +1 -1
- package/api/deleteBeatmapPage.ts +20 -20
- package/api/executeAdminOperation.ts +113 -113
- package/api/getBadgeLeaders.ts +25 -3
- package/api/getBadgedUsers.ts +2 -1
- package/api/getBeatmapComments.ts +32 -32
- package/api/getBeatmapPage.ts +67 -67
- package/api/getBeatmapPageById.ts +72 -63
- package/api/getLeaderboard.ts +173 -44
- package/api/getOnlinePlayers.ts +78 -78
- package/api/getProfile.ts +21 -7
- package/api/getPublicStats.ts +5 -2
- package/api/getUserScores.ts +48 -48
- package/api/postBeatmapComment.ts +23 -23
- package/api/submitScore.ts +26 -26
- package/api/submitScoreInternal.ts +426 -0
- package/handleApi.ts +7 -3
- package/index.ts +64 -32
- package/package.json +4 -3
- package/types/database.ts +1179 -1145
- package/utils/activityStatus.ts +36 -0
- package/utils/cache.ts +60 -60
- package/utils/leaderboardCache.ts +8 -0
- package/utils/requestUtils.ts +2 -1
package/.env
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
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
|
package/.yarnrc
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
registry: https://registry.npmjs.org/
|
package/README.md
CHANGED
|
@@ -7,3 +7,5 @@ Install dependencies with Bun, then reach for the scripts in `package.json`; `bu
|
|
|
7
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
8
|
|
|
9
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.
|
|
10
|
+
|
|
11
|
+
//
|
package/api/createBeatmap.ts
CHANGED
package/api/deleteBeatmapPage.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { NextResponse } from "next/server";
|
|
2
2
|
import z from "zod";
|
|
3
|
-
import { protectedApi, validUser } from "../utils/requestUtils";
|
|
4
|
-
import { supabase } from "../utils/supabase";
|
|
5
|
-
import { getUserBySession } from "../utils/getUserBySession";
|
|
6
|
-
import { User } from "@supabase/supabase-js";
|
|
7
|
-
import { invalidateCache, invalidateCachePrefix } from "../utils/cache";
|
|
3
|
+
import { protectedApi, validUser } from "../utils/requestUtils";
|
|
4
|
+
import { supabase } from "../utils/supabase";
|
|
5
|
+
import { getUserBySession } from "../utils/getUserBySession";
|
|
6
|
+
import { User } from "@supabase/supabase-js";
|
|
7
|
+
import { invalidateCache, invalidateCachePrefix } from "../utils/cache";
|
|
8
8
|
|
|
9
9
|
export const Schema = {
|
|
10
10
|
input: z.strictObject({
|
|
@@ -67,18 +67,18 @@ export async function handler({
|
|
|
67
67
|
if (pageData.status !== "UNRANKED")
|
|
68
68
|
return NextResponse.json({ error: "Only unranked maps can be updated" });
|
|
69
69
|
|
|
70
|
-
await supabase.from("beatmapPageComments").delete().eq("beatmapPage", id);
|
|
71
|
-
await supabase.from("collectionRelations").delete().eq("beatmapPage", id);
|
|
72
|
-
await supabase.from("beatmapPages").delete().eq("id", id);
|
|
73
|
-
await supabase
|
|
74
|
-
.from("beatmaps")
|
|
75
|
-
.delete()
|
|
76
|
-
.eq("beatmapHash", beatmapData.beatmapHash);
|
|
77
|
-
|
|
78
|
-
await invalidateCache(`beatmap-comments:${id}`);
|
|
79
|
-
if (pageData.latestBeatmapHash) {
|
|
80
|
-
await invalidateCachePrefix(`beatmap-scores:${pageData.latestBeatmapHash}`);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
return NextResponse.json({});
|
|
84
|
-
}
|
|
70
|
+
await supabase.from("beatmapPageComments").delete().eq("beatmapPage", id);
|
|
71
|
+
await supabase.from("collectionRelations").delete().eq("beatmapPage", id);
|
|
72
|
+
await supabase.from("beatmapPages").delete().eq("id", id);
|
|
73
|
+
await supabase
|
|
74
|
+
.from("beatmaps")
|
|
75
|
+
.delete()
|
|
76
|
+
.eq("beatmapHash", beatmapData.beatmapHash);
|
|
77
|
+
|
|
78
|
+
await invalidateCache(`beatmap-comments:${id}`);
|
|
79
|
+
if (pageData.latestBeatmapHash) {
|
|
80
|
+
await invalidateCachePrefix(`beatmap-scores:${pageData.latestBeatmapHash}`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return NextResponse.json({});
|
|
84
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { NextResponse } from "next/server";
|
|
2
|
-
import z from "zod";
|
|
3
|
-
import { Database } from "../types/database";
|
|
4
|
-
import { protectedApi, validUser } from "../utils/requestUtils";
|
|
5
|
-
import { supabase } from "../utils/supabase";
|
|
6
|
-
import { getUserBySession } from "../utils/getUserBySession";
|
|
7
|
-
import { User } from "@supabase/supabase-js";
|
|
8
|
-
import { invalidateCache, invalidateCachePrefix } from "../utils/cache";
|
|
2
|
+
import z from "zod";
|
|
3
|
+
import { Database } from "../types/database";
|
|
4
|
+
import { protectedApi, validUser } from "../utils/requestUtils";
|
|
5
|
+
import { supabase } from "../utils/supabase";
|
|
6
|
+
import { getUserBySession } from "../utils/getUserBySession";
|
|
7
|
+
import { User } from "@supabase/supabase-js";
|
|
8
|
+
import { invalidateCache, invalidateCachePrefix } from "../utils/cache";
|
|
9
9
|
|
|
10
10
|
// Define supported admin operations and their parameter types
|
|
11
11
|
const adminOperations = {
|
|
@@ -146,21 +146,21 @@ export async function handler(
|
|
|
146
146
|
{ status: 403 }
|
|
147
147
|
);
|
|
148
148
|
}
|
|
149
|
-
|
|
150
|
-
// Execute the requested admin operation
|
|
151
|
-
try {
|
|
152
|
-
let result: { data?: any; error?: any } | null = null;
|
|
153
|
-
const operation = data.data.operation;
|
|
154
|
-
const params = data.data.params as any;
|
|
155
|
-
const targetUserId =
|
|
156
|
-
"userId" in params && typeof params.userId === "number"
|
|
157
|
-
? params.userId
|
|
158
|
-
: null;
|
|
159
|
-
|
|
160
|
-
switch (operation) {
|
|
161
|
-
case "deleteUser":
|
|
162
|
-
result = await supabase.rpc("admin_delete_user", {
|
|
163
|
-
user_id: params.userId,
|
|
149
|
+
|
|
150
|
+
// Execute the requested admin operation
|
|
151
|
+
try {
|
|
152
|
+
let result: { data?: any; error?: any } | null = null;
|
|
153
|
+
const operation = data.data.operation;
|
|
154
|
+
const params = data.data.params as any;
|
|
155
|
+
const targetUserId =
|
|
156
|
+
"userId" in params && typeof params.userId === "number"
|
|
157
|
+
? params.userId
|
|
158
|
+
: null;
|
|
159
|
+
|
|
160
|
+
switch (operation) {
|
|
161
|
+
case "deleteUser":
|
|
162
|
+
result = await supabase.rpc("admin_delete_user", {
|
|
163
|
+
user_id: params.userId,
|
|
164
164
|
});
|
|
165
165
|
break;
|
|
166
166
|
|
|
@@ -221,23 +221,23 @@ export async function handler(
|
|
|
221
221
|
})
|
|
222
222
|
.select();
|
|
223
223
|
break;
|
|
224
|
-
case "changeBadges":
|
|
225
|
-
// Allow only developers to modify badges.
|
|
226
|
-
if ((queryUserData.badges as string[]).includes("Developer")) {
|
|
227
|
-
result = await supabase
|
|
228
|
-
.from("profiles")
|
|
229
|
-
.upsert({
|
|
230
|
-
id: params.userId,
|
|
231
|
-
badges: JSON.parse(params.badges),
|
|
232
|
-
})
|
|
233
|
-
.select();
|
|
234
|
-
} else {
|
|
235
|
-
result = { data: null, error: { message: "Unauthorized" } };
|
|
236
|
-
}
|
|
237
|
-
break;
|
|
238
|
-
|
|
239
|
-
case "addBadge":
|
|
240
|
-
// Allow only developers to modify badges.
|
|
224
|
+
case "changeBadges":
|
|
225
|
+
// Allow only developers to modify badges.
|
|
226
|
+
if ((queryUserData.badges as string[]).includes("Developer")) {
|
|
227
|
+
result = await supabase
|
|
228
|
+
.from("profiles")
|
|
229
|
+
.upsert({
|
|
230
|
+
id: params.userId,
|
|
231
|
+
badges: JSON.parse(params.badges),
|
|
232
|
+
})
|
|
233
|
+
.select();
|
|
234
|
+
} else {
|
|
235
|
+
result = { data: null, error: { message: "Unauthorized" } };
|
|
236
|
+
}
|
|
237
|
+
break;
|
|
238
|
+
|
|
239
|
+
case "addBadge":
|
|
240
|
+
// Allow only developers to modify badges.
|
|
241
241
|
if ((queryUserData.badges as string[]).includes("Developer")) {
|
|
242
242
|
// Get current badges
|
|
243
243
|
const { data: targetUser } = await supabase
|
|
@@ -249,23 +249,23 @@ export async function handler(
|
|
|
249
249
|
const currentBadges = (targetUser?.badges || []) as string[];
|
|
250
250
|
if (!currentBadges.includes(params.badge)) {
|
|
251
251
|
currentBadges.push(params.badge);
|
|
252
|
-
result = await supabase
|
|
253
|
-
.from("profiles")
|
|
254
|
-
.upsert({
|
|
255
|
-
id: params.userId,
|
|
256
|
-
badges: currentBadges,
|
|
257
|
-
})
|
|
258
|
-
.select();
|
|
259
|
-
} else {
|
|
260
|
-
result = { data: targetUser, error: null };
|
|
261
|
-
}
|
|
262
|
-
} else {
|
|
263
|
-
result = { data: null, error: { message: "Unauthorized" } };
|
|
264
|
-
}
|
|
265
|
-
break;
|
|
266
|
-
|
|
267
|
-
case "removeBadge":
|
|
268
|
-
// Allow only developers to modify badges.
|
|
252
|
+
result = await supabase
|
|
253
|
+
.from("profiles")
|
|
254
|
+
.upsert({
|
|
255
|
+
id: params.userId,
|
|
256
|
+
badges: currentBadges,
|
|
257
|
+
})
|
|
258
|
+
.select();
|
|
259
|
+
} else {
|
|
260
|
+
result = { data: targetUser, error: null };
|
|
261
|
+
}
|
|
262
|
+
} else {
|
|
263
|
+
result = { data: null, error: { message: "Unauthorized" } };
|
|
264
|
+
}
|
|
265
|
+
break;
|
|
266
|
+
|
|
267
|
+
case "removeBadge":
|
|
268
|
+
// Allow only developers to modify badges.
|
|
269
269
|
if ((queryUserData.badges as string[]).includes("Developer")) {
|
|
270
270
|
// Get current badges
|
|
271
271
|
const { data: targetUser } = await supabase
|
|
@@ -277,17 +277,17 @@ export async function handler(
|
|
|
277
277
|
const currentBadges = (targetUser?.badges || []) as string[];
|
|
278
278
|
const updatedBadges = currentBadges.filter(b => b !== params.badge);
|
|
279
279
|
|
|
280
|
-
result = await supabase
|
|
281
|
-
.from("profiles")
|
|
282
|
-
.upsert({
|
|
283
|
-
id: params.userId,
|
|
284
|
-
badges: updatedBadges,
|
|
285
|
-
})
|
|
286
|
-
.select();
|
|
287
|
-
} else {
|
|
288
|
-
result = { data: null, error: { message: "Unauthorized" } };
|
|
289
|
-
}
|
|
290
|
-
break;
|
|
280
|
+
result = await supabase
|
|
281
|
+
.from("profiles")
|
|
282
|
+
.upsert({
|
|
283
|
+
id: params.userId,
|
|
284
|
+
badges: updatedBadges,
|
|
285
|
+
})
|
|
286
|
+
.select();
|
|
287
|
+
} else {
|
|
288
|
+
result = { data: null, error: { message: "Unauthorized" } };
|
|
289
|
+
}
|
|
290
|
+
break;
|
|
291
291
|
|
|
292
292
|
case "getScoresPaginated":
|
|
293
293
|
const offset = (params.page - 1) * params.limit;
|
|
@@ -349,52 +349,52 @@ export async function handler(
|
|
|
349
349
|
}
|
|
350
350
|
|
|
351
351
|
// Log the admin action
|
|
352
|
-
await supabase.rpc("admin_log_action", {
|
|
353
|
-
admin_id: queryUserData.id,
|
|
354
|
-
action_type: operation,
|
|
355
|
-
target_id: "userId" in params ? params.userId : null,
|
|
356
|
-
details: { params },
|
|
357
|
-
});
|
|
358
|
-
|
|
359
|
-
if (result?.error) {
|
|
360
|
-
return NextResponse.json(
|
|
361
|
-
{
|
|
362
|
-
success: false,
|
|
363
|
-
error: result.error.message,
|
|
364
|
-
},
|
|
365
|
-
{ status: 500 }
|
|
366
|
-
);
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
if (targetUserId !== null && !result?.error) {
|
|
370
|
-
await invalidateCachePrefix(`userscore:${targetUserId}`);
|
|
371
|
-
|
|
372
|
-
if (
|
|
373
|
-
operation === "removeAllScores" ||
|
|
374
|
-
operation === "invalidateRankedScores" ||
|
|
375
|
-
operation === "deleteUser"
|
|
376
|
-
) {
|
|
377
|
-
const { data: beatmapHashes } = await supabase
|
|
378
|
-
.from("scores")
|
|
379
|
-
.select("beatmapHash")
|
|
380
|
-
.eq("userId", targetUserId);
|
|
381
|
-
|
|
382
|
-
const uniqueHashes = new Set(
|
|
383
|
-
(beatmapHashes || [])
|
|
384
|
-
.map((row) => row.beatmapHash)
|
|
385
|
-
.filter((hash): hash is string => Boolean(hash))
|
|
386
|
-
);
|
|
387
|
-
|
|
388
|
-
for (const hash of uniqueHashes) {
|
|
389
|
-
await invalidateCachePrefix(`beatmap-scores:${hash}`);
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
return NextResponse.json({
|
|
395
|
-
success: true,
|
|
396
|
-
result: result?.data,
|
|
397
|
-
});
|
|
352
|
+
await supabase.rpc("admin_log_action", {
|
|
353
|
+
admin_id: queryUserData.id,
|
|
354
|
+
action_type: operation,
|
|
355
|
+
target_id: "userId" in params ? params.userId : null,
|
|
356
|
+
details: { params },
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
if (result?.error) {
|
|
360
|
+
return NextResponse.json(
|
|
361
|
+
{
|
|
362
|
+
success: false,
|
|
363
|
+
error: result.error.message,
|
|
364
|
+
},
|
|
365
|
+
{ status: 500 }
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (targetUserId !== null && !result?.error) {
|
|
370
|
+
await invalidateCachePrefix(`userscore:${targetUserId}`);
|
|
371
|
+
|
|
372
|
+
if (
|
|
373
|
+
operation === "removeAllScores" ||
|
|
374
|
+
operation === "invalidateRankedScores" ||
|
|
375
|
+
operation === "deleteUser"
|
|
376
|
+
) {
|
|
377
|
+
const { data: beatmapHashes } = await supabase
|
|
378
|
+
.from("scores")
|
|
379
|
+
.select("beatmapHash")
|
|
380
|
+
.eq("userId", targetUserId);
|
|
381
|
+
|
|
382
|
+
const uniqueHashes = new Set(
|
|
383
|
+
(beatmapHashes || [])
|
|
384
|
+
.map((row) => row.beatmapHash)
|
|
385
|
+
.filter((hash): hash is string => Boolean(hash))
|
|
386
|
+
);
|
|
387
|
+
|
|
388
|
+
for (const hash of uniqueHashes) {
|
|
389
|
+
await invalidateCachePrefix(`beatmap-scores:${hash}`);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
return NextResponse.json({
|
|
395
|
+
success: true,
|
|
396
|
+
result: result?.data,
|
|
397
|
+
});
|
|
398
398
|
} catch (err: any) {
|
|
399
399
|
return NextResponse.json(
|
|
400
400
|
{
|
package/api/getBadgeLeaders.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { NextResponse } from "next/server";
|
|
2
2
|
import z from "zod";
|
|
3
3
|
import { supabase } from "../utils/supabase";
|
|
4
|
+
import { getActiveProfileIdSet } from "../utils/activityStatus";
|
|
4
5
|
|
|
5
6
|
export const Schema = {
|
|
6
7
|
input: z.strictObject({
|
|
7
8
|
limit: z.number().min(1).max(100).optional().default(100),
|
|
9
|
+
include_inactive: z.boolean().optional().default(false),
|
|
8
10
|
}),
|
|
9
11
|
output: z.object({
|
|
10
12
|
leaderboard: z.array(
|
|
@@ -21,13 +23,20 @@ export const Schema = {
|
|
|
21
23
|
};
|
|
22
24
|
|
|
23
25
|
export async function POST(request: Request): Promise<NextResponse> {
|
|
24
|
-
|
|
26
|
+
let body: unknown = {};
|
|
27
|
+
try {
|
|
28
|
+
body = await request.json();
|
|
29
|
+
} catch {}
|
|
30
|
+
|
|
31
|
+
return handler(Schema.input.parse(body));
|
|
25
32
|
}
|
|
26
33
|
|
|
27
34
|
export async function handler({
|
|
28
35
|
limit = 100,
|
|
36
|
+
include_inactive = false,
|
|
29
37
|
}: {
|
|
30
38
|
limit?: number;
|
|
39
|
+
include_inactive?: boolean;
|
|
31
40
|
}): Promise<NextResponse<(typeof Schema)["output"]["_type"]>> {
|
|
32
41
|
try {
|
|
33
42
|
const { data: leaderboard, error } = await supabase.rpc(
|
|
@@ -49,9 +58,22 @@ export async function handler({
|
|
|
49
58
|
);
|
|
50
59
|
}
|
|
51
60
|
|
|
61
|
+
const entries = leaderboard || [];
|
|
62
|
+
|
|
63
|
+
if (include_inactive) {
|
|
64
|
+
return NextResponse.json({
|
|
65
|
+
leaderboard: entries,
|
|
66
|
+
total_count: entries.length,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const activeIds = await getActiveProfileIdSet(entries.map((entry) => entry.id));
|
|
71
|
+
|
|
72
|
+
const filteredLeaderboard = entries.filter((entry) => activeIds.has(entry.id));
|
|
73
|
+
|
|
52
74
|
return NextResponse.json({
|
|
53
|
-
leaderboard:
|
|
54
|
-
total_count:
|
|
75
|
+
leaderboard: filteredLeaderboard,
|
|
76
|
+
total_count: filteredLeaderboard.length,
|
|
55
77
|
});
|
|
56
78
|
} catch (error) {
|
|
57
79
|
console.error("Badge leaderboard exception:", error);
|
package/api/getBadgedUsers.ts
CHANGED
|
@@ -40,7 +40,8 @@ export async function handler(
|
|
|
40
40
|
export async function getLeaderboard(badge: string) {
|
|
41
41
|
let { data: queryData, error } = await supabase
|
|
42
42
|
.from("profiles")
|
|
43
|
-
.select("flag,id,username,badges")
|
|
43
|
+
.select("flag,id,username,badges,scores!inner(id)")
|
|
44
|
+
.limit(1, { foreignTable: "scores" });
|
|
44
45
|
|
|
45
46
|
const users = queryData?.filter((e) =>
|
|
46
47
|
((e.badges || []) as string[]).includes(badge)
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { NextResponse } from "next/server";
|
|
2
|
-
import z from "zod";
|
|
3
|
-
import { protectedApi, validUser } from "../utils/requestUtils";
|
|
4
|
-
import { getCacheValue, setCacheValue } from "../utils/cache";
|
|
5
|
-
import { supabase } from "../utils/supabase";
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import z from "zod";
|
|
3
|
+
import { protectedApi, validUser } from "../utils/requestUtils";
|
|
4
|
+
import { getCacheValue, setCacheValue } from "../utils/cache";
|
|
5
|
+
import { supabase } from "../utils/supabase";
|
|
6
6
|
|
|
7
7
|
export const Schema = {
|
|
8
8
|
input: z.strictObject({
|
|
@@ -35,25 +35,25 @@ export async function POST(request: Request): Promise<NextResponse> {
|
|
|
35
35
|
});
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
export async function handler({
|
|
39
|
-
page,
|
|
40
|
-
}: (typeof Schema)["input"]["_type"]): Promise<
|
|
41
|
-
NextResponse<(typeof Schema)["output"]["_type"]>
|
|
42
|
-
> {
|
|
43
|
-
const cacheKey = `beatmap-comments:${page}`;
|
|
44
|
-
const cachedComments = await getCacheValue<
|
|
45
|
-
(typeof Schema)["output"]["_type"]["comments"]
|
|
46
|
-
>(cacheKey);
|
|
47
|
-
|
|
48
|
-
if (cachedComments) {
|
|
49
|
-
return NextResponse.json({ comments: cachedComments });
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
let { data: userData, error: userError } = await supabase
|
|
53
|
-
.from("beatmapPageComments")
|
|
54
|
-
.select(
|
|
55
|
-
`
|
|
56
|
-
*,
|
|
38
|
+
export async function handler({
|
|
39
|
+
page,
|
|
40
|
+
}: (typeof Schema)["input"]["_type"]): Promise<
|
|
41
|
+
NextResponse<(typeof Schema)["output"]["_type"]>
|
|
42
|
+
> {
|
|
43
|
+
const cacheKey = `beatmap-comments:${page}`;
|
|
44
|
+
const cachedComments = await getCacheValue<
|
|
45
|
+
(typeof Schema)["output"]["_type"]["comments"]
|
|
46
|
+
>(cacheKey);
|
|
47
|
+
|
|
48
|
+
if (cachedComments) {
|
|
49
|
+
return NextResponse.json({ comments: cachedComments });
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
let { data: userData, error: userError } = await supabase
|
|
53
|
+
.from("beatmapPageComments")
|
|
54
|
+
.select(
|
|
55
|
+
`
|
|
56
|
+
*,
|
|
57
57
|
profiles!inner(
|
|
58
58
|
username,
|
|
59
59
|
avatar_url,
|
|
@@ -61,11 +61,11 @@ export async function handler({
|
|
|
61
61
|
)
|
|
62
62
|
`
|
|
63
63
|
)
|
|
64
|
-
.eq("beatmapPage", page);
|
|
65
|
-
|
|
66
|
-
if (userData) {
|
|
67
|
-
await setCacheValue(cacheKey, userData);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
return NextResponse.json({ comments: userData! });
|
|
71
|
-
}
|
|
64
|
+
.eq("beatmapPage", page);
|
|
65
|
+
|
|
66
|
+
if (userData) {
|
|
67
|
+
await setCacheValue(cacheKey, userData);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return NextResponse.json({ comments: userData! });
|
|
71
|
+
}
|
package/api/getBeatmapPage.ts
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
import { NextResponse } from "next/server";
|
|
2
|
-
import z from "zod";
|
|
3
|
-
import { protectedApi } from "../utils/requestUtils";
|
|
4
|
-
import { getCacheValue, setCacheValue } from "../utils/cache";
|
|
5
|
-
import { supabase } from "../utils/supabase";
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import z from "zod";
|
|
3
|
+
import { protectedApi } from "../utils/requestUtils";
|
|
4
|
+
import { getCacheValue, setCacheValue } from "../utils/cache";
|
|
5
|
+
import { supabase } from "../utils/supabase";
|
|
6
6
|
|
|
7
|
-
export const Schema = {
|
|
8
|
-
input: z.strictObject({
|
|
9
|
-
session: z.string(),
|
|
10
|
-
id: z.number(),
|
|
11
|
-
limit: z.number().min(1).max(200).default(50),
|
|
12
|
-
}),
|
|
13
|
-
output: z.object({
|
|
14
|
-
error: z.string().optional(),
|
|
15
|
-
scores: z
|
|
16
|
-
.array(
|
|
7
|
+
export const Schema = {
|
|
8
|
+
input: z.strictObject({
|
|
9
|
+
session: z.string(),
|
|
10
|
+
id: z.number(),
|
|
11
|
+
limit: z.number().min(1).max(200).default(50),
|
|
12
|
+
}),
|
|
13
|
+
output: z.object({
|
|
14
|
+
error: z.string().optional(),
|
|
15
|
+
scores: z
|
|
16
|
+
.array(
|
|
17
17
|
z.object({
|
|
18
18
|
id: z.number(),
|
|
19
19
|
awarded_sp: z.number().nullable(),
|
|
@@ -68,17 +68,17 @@ export async function POST(request: Request): Promise<NextResponse> {
|
|
|
68
68
|
});
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
export async function handler(
|
|
72
|
-
data: (typeof Schema)["input"]["_type"],
|
|
73
|
-
req: Request
|
|
74
|
-
): Promise<NextResponse<(typeof Schema)["output"]["_type"]>> {
|
|
75
|
-
const limit = data.limit ?? 50;
|
|
76
|
-
|
|
77
|
-
let { data: beatmapPage, error: errorlast } = await supabase
|
|
78
|
-
.from("beatmapPages")
|
|
79
|
-
.select(
|
|
80
|
-
`
|
|
81
|
-
*,
|
|
71
|
+
export async function handler(
|
|
72
|
+
data: (typeof Schema)["input"]["_type"],
|
|
73
|
+
req: Request
|
|
74
|
+
): Promise<NextResponse<(typeof Schema)["output"]["_type"]>> {
|
|
75
|
+
const limit = data.limit ?? 50;
|
|
76
|
+
|
|
77
|
+
let { data: beatmapPage, error: errorlast } = await supabase
|
|
78
|
+
.from("beatmapPages")
|
|
79
|
+
.select(
|
|
80
|
+
`
|
|
81
|
+
*,
|
|
82
82
|
beatmaps (
|
|
83
83
|
created_at,
|
|
84
84
|
playcount,
|
|
@@ -97,47 +97,47 @@ export async function handler(
|
|
|
97
97
|
avatar_url
|
|
98
98
|
)
|
|
99
99
|
`
|
|
100
|
-
)
|
|
101
|
-
.eq("id", data.id)
|
|
102
|
-
.single();
|
|
103
|
-
|
|
104
|
-
if (!beatmapPage) return NextResponse.json({});
|
|
105
|
-
|
|
106
|
-
const beatmapHash = beatmapPage?.latestBeatmapHash || "";
|
|
107
|
-
const isCacheable =
|
|
108
|
-
beatmapPage?.status === "RANKED" || beatmapPage?.status === "APPROVED";
|
|
109
|
-
const cacheKey = `beatmap-scores:${beatmapHash}:limit=${limit}`;
|
|
110
|
-
|
|
111
|
-
let scoreData: any[] | null = null;
|
|
112
|
-
|
|
113
|
-
if (isCacheable && beatmapHash) {
|
|
114
|
-
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
if (!scoreData) {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
return NextResponse.json({
|
|
135
|
-
scores:
|
|
136
|
-
id: score.id,
|
|
137
|
-
awarded_sp: score.awarded_sp,
|
|
138
|
-
created_at: score.created_at,
|
|
139
|
-
misses: score.misses,
|
|
140
|
-
mods: score.mods,
|
|
100
|
+
)
|
|
101
|
+
.eq("id", data.id)
|
|
102
|
+
.single();
|
|
103
|
+
|
|
104
|
+
if (!beatmapPage) return NextResponse.json({});
|
|
105
|
+
|
|
106
|
+
const beatmapHash = beatmapPage?.latestBeatmapHash || "";
|
|
107
|
+
const isCacheable =
|
|
108
|
+
beatmapPage?.status === "RANKED" || beatmapPage?.status === "APPROVED";
|
|
109
|
+
const cacheKey = `beatmap-scores:${beatmapHash}:limit=${limit}`;
|
|
110
|
+
|
|
111
|
+
// let scoreData: any[] | null = null;
|
|
112
|
+
|
|
113
|
+
// if (isCacheable && beatmapHash) {
|
|
114
|
+
// scoreData = await getCacheValue<any[]>(cacheKey);
|
|
115
|
+
// }
|
|
116
|
+
|
|
117
|
+
// if (!scoreData) {
|
|
118
|
+
// const { data: rpcScores, error } = await supabase.rpc(
|
|
119
|
+
// "get_top_scores_for_beatmap",
|
|
120
|
+
// { beatmap_hash: beatmapHash }
|
|
121
|
+
// );
|
|
122
|
+
|
|
123
|
+
// if (error) {
|
|
124
|
+
// return NextResponse.json({ error: JSON.stringify(error) });
|
|
125
|
+
// }
|
|
126
|
+
|
|
127
|
+
// scoreData = (rpcScores || []).slice(0, limit);
|
|
128
|
+
|
|
129
|
+
// if (isCacheable && beatmapHash) {
|
|
130
|
+
// await setCacheValue(cacheKey, scoreData);
|
|
131
|
+
// }
|
|
132
|
+
// }
|
|
133
|
+
|
|
134
|
+
return NextResponse.json({
|
|
135
|
+
scores: [].map((score: any) => ({
|
|
136
|
+
id: score.id,
|
|
137
|
+
awarded_sp: score.awarded_sp,
|
|
138
|
+
created_at: score.created_at,
|
|
139
|
+
misses: score.misses,
|
|
140
|
+
mods: score.mods,
|
|
141
141
|
passed: score.passed,
|
|
142
142
|
songId: score.songid,
|
|
143
143
|
speed: score.speed,
|