rhythia-api 236.0.0 → 238.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/api/executeAdminOperation.ts +296 -1
- package/api/getCollection.ts +26 -23
- package/api/getProfile.ts +9 -7
- package/api/reportProfile.ts +12 -3
- package/index.ts +15 -23
- package/package.json +1 -1
- package/types/database.ts +1356 -1327
- package/utils/profileReportWebhook.ts +180 -0
|
@@ -7,6 +7,56 @@ import { getUserBySession } from "../utils/getUserBySession";
|
|
|
7
7
|
import { User } from "@supabase/supabase-js";
|
|
8
8
|
import { invalidateCache, invalidateCachePrefix } from "../utils/cache";
|
|
9
9
|
|
|
10
|
+
type AdminProfileSummary = Pick<
|
|
11
|
+
Database["public"]["Tables"]["profiles"]["Row"],
|
|
12
|
+
"id" | "username" | "avatar_url" | "flag" | "badges" | "ban"
|
|
13
|
+
>;
|
|
14
|
+
|
|
15
|
+
type InvestigationDateRange = {
|
|
16
|
+
firstSeenAt: string | null;
|
|
17
|
+
lastSeenAt: string | null;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
type LinkedAccountAggregate = AdminProfileSummary &
|
|
21
|
+
InvestigationDateRange & {
|
|
22
|
+
occurrences: number;
|
|
23
|
+
sharedHwids: Set<string>;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
type LinkedProfileAggregate = AdminProfileSummary &
|
|
27
|
+
InvestigationDateRange & {
|
|
28
|
+
occurrences: number;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const MULTIACCOUNT_PROFILE_SELECT =
|
|
32
|
+
"id, username, avatar_url, flag, badges, ban";
|
|
33
|
+
|
|
34
|
+
const SCORE_CACHE_READ_OPERATIONS = new Set([
|
|
35
|
+
"getScoresPaginated",
|
|
36
|
+
"getMultiaccountInvestigation",
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
function timestampOrZero(value: string | null) {
|
|
40
|
+
return value ? new Date(value).getTime() : 0;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function updateDateRange(
|
|
44
|
+
range: InvestigationDateRange,
|
|
45
|
+
createdAt: string | null
|
|
46
|
+
) {
|
|
47
|
+
if (!createdAt) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!range.firstSeenAt || createdAt < range.firstSeenAt) {
|
|
52
|
+
range.firstSeenAt = createdAt;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (!range.lastSeenAt || createdAt > range.lastSeenAt) {
|
|
56
|
+
range.lastSeenAt = createdAt;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
10
60
|
// Define supported admin operations and their parameter types
|
|
11
61
|
const adminOperations = {
|
|
12
62
|
deleteUser: z.object({ userId: z.number() }),
|
|
@@ -28,6 +78,7 @@ const adminOperations = {
|
|
|
28
78
|
userId: z.number().optional(),
|
|
29
79
|
includeAdditionalData: z.boolean().default(true),
|
|
30
80
|
}),
|
|
81
|
+
getMultiaccountInvestigation: z.object({ userId: z.number() }),
|
|
31
82
|
} as const;
|
|
32
83
|
|
|
33
84
|
// Create a discriminated union type for operation parameters
|
|
@@ -88,6 +139,10 @@ const OperationParam = z.discriminatedUnion("operation", [
|
|
|
88
139
|
operation: z.literal("getScoresPaginated"),
|
|
89
140
|
params: adminOperations.getScoresPaginated,
|
|
90
141
|
}),
|
|
142
|
+
z.object({
|
|
143
|
+
operation: z.literal("getMultiaccountInvestigation"),
|
|
144
|
+
params: adminOperations.getMultiaccountInvestigation,
|
|
145
|
+
}),
|
|
91
146
|
]);
|
|
92
147
|
|
|
93
148
|
export const Schema = {
|
|
@@ -346,6 +401,242 @@ export async function handler(
|
|
|
346
401
|
};
|
|
347
402
|
}
|
|
348
403
|
break;
|
|
404
|
+
|
|
405
|
+
case "getMultiaccountInvestigation":
|
|
406
|
+
const { data: investigatedUser, error: investigatedUserError } =
|
|
407
|
+
await supabase
|
|
408
|
+
.from("profiles")
|
|
409
|
+
.select(MULTIACCOUNT_PROFILE_SELECT)
|
|
410
|
+
.eq("id", params.userId)
|
|
411
|
+
.maybeSingle();
|
|
412
|
+
|
|
413
|
+
if (investigatedUserError) {
|
|
414
|
+
result = { error: investigatedUserError, data: null };
|
|
415
|
+
break;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
if (!investigatedUser) {
|
|
419
|
+
result = {
|
|
420
|
+
error: { message: "User not found" },
|
|
421
|
+
data: null,
|
|
422
|
+
};
|
|
423
|
+
break;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const { data: investigatedUserHwids, error: investigatedUserHwidsError } =
|
|
427
|
+
await supabase
|
|
428
|
+
.from("user_hwids")
|
|
429
|
+
.select("id, hwid, created_at")
|
|
430
|
+
.eq("id", params.userId)
|
|
431
|
+
.order("created_at", { ascending: false });
|
|
432
|
+
|
|
433
|
+
if (investigatedUserHwidsError) {
|
|
434
|
+
result = { error: investigatedUserHwidsError, data: null };
|
|
435
|
+
break;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
const targetRows = investigatedUserHwids || [];
|
|
439
|
+
const uniqueHwids = Array.from(
|
|
440
|
+
new Set(targetRows.map((row) => row.hwid).filter(Boolean))
|
|
441
|
+
);
|
|
442
|
+
|
|
443
|
+
const dateRange: InvestigationDateRange = {
|
|
444
|
+
firstSeenAt: null,
|
|
445
|
+
lastSeenAt: null,
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
const hwidAggregates = new Map<
|
|
449
|
+
string,
|
|
450
|
+
InvestigationDateRange & {
|
|
451
|
+
hwid: string;
|
|
452
|
+
occurrences: number;
|
|
453
|
+
linkedProfiles: Map<number, LinkedProfileAggregate>;
|
|
454
|
+
}
|
|
455
|
+
>();
|
|
456
|
+
|
|
457
|
+
for (const row of targetRows) {
|
|
458
|
+
updateDateRange(dateRange, row.created_at);
|
|
459
|
+
|
|
460
|
+
const existing = hwidAggregates.get(row.hwid) || {
|
|
461
|
+
hwid: row.hwid,
|
|
462
|
+
occurrences: 0,
|
|
463
|
+
firstSeenAt: null,
|
|
464
|
+
lastSeenAt: null,
|
|
465
|
+
linkedProfiles: new Map<number, LinkedProfileAggregate>(),
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
existing.occurrences += 1;
|
|
469
|
+
updateDateRange(existing, row.created_at);
|
|
470
|
+
hwidAggregates.set(row.hwid, existing);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
if (uniqueHwids.length === 0) {
|
|
474
|
+
result = {
|
|
475
|
+
data: {
|
|
476
|
+
user: investigatedUser,
|
|
477
|
+
summary: {
|
|
478
|
+
totalRows: 0,
|
|
479
|
+
uniqueHwids: 0,
|
|
480
|
+
sharedHwids: 0,
|
|
481
|
+
linkedAccounts: 0,
|
|
482
|
+
firstSeenAt: null,
|
|
483
|
+
lastSeenAt: null,
|
|
484
|
+
},
|
|
485
|
+
accounts: [],
|
|
486
|
+
hwids: [],
|
|
487
|
+
},
|
|
488
|
+
error: null,
|
|
489
|
+
};
|
|
490
|
+
break;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
const { data: relatedRows, error: relatedRowsError } = await supabase
|
|
494
|
+
.from("user_hwids")
|
|
495
|
+
.select("id, hwid, created_at")
|
|
496
|
+
.in("hwid", uniqueHwids)
|
|
497
|
+
.order("created_at", { ascending: false });
|
|
498
|
+
|
|
499
|
+
if (relatedRowsError) {
|
|
500
|
+
result = { error: relatedRowsError, data: null };
|
|
501
|
+
break;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
const relatedProfileIds = Array.from(
|
|
505
|
+
new Set(
|
|
506
|
+
(relatedRows || [])
|
|
507
|
+
.map((row) => row.id)
|
|
508
|
+
.filter((id) => id !== params.userId)
|
|
509
|
+
)
|
|
510
|
+
);
|
|
511
|
+
|
|
512
|
+
const { data: relatedProfiles, error: relatedProfilesError } =
|
|
513
|
+
relatedProfileIds.length > 0
|
|
514
|
+
? await supabase
|
|
515
|
+
.from("profiles")
|
|
516
|
+
.select(MULTIACCOUNT_PROFILE_SELECT)
|
|
517
|
+
.in("id", relatedProfileIds)
|
|
518
|
+
: { data: [], error: null };
|
|
519
|
+
|
|
520
|
+
if (relatedProfilesError) {
|
|
521
|
+
result = { error: relatedProfilesError, data: null };
|
|
522
|
+
break;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
const profileMap = new Map<number, AdminProfileSummary>([
|
|
526
|
+
[investigatedUser.id, investigatedUser],
|
|
527
|
+
...((relatedProfiles || []) as AdminProfileSummary[]).map((profile) => [
|
|
528
|
+
profile.id,
|
|
529
|
+
profile,
|
|
530
|
+
]),
|
|
531
|
+
]);
|
|
532
|
+
|
|
533
|
+
const linkedAccountAggregates = new Map<number, LinkedAccountAggregate>();
|
|
534
|
+
|
|
535
|
+
for (const row of relatedRows || []) {
|
|
536
|
+
if (row.id === params.userId) {
|
|
537
|
+
continue;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
const hwidAggregate = hwidAggregates.get(row.hwid);
|
|
541
|
+
const relatedProfile = profileMap.get(row.id);
|
|
542
|
+
|
|
543
|
+
if (!hwidAggregate || !relatedProfile) {
|
|
544
|
+
continue;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
const linkedProfile =
|
|
548
|
+
hwidAggregate.linkedProfiles.get(row.id) || {
|
|
549
|
+
...relatedProfile,
|
|
550
|
+
occurrences: 0,
|
|
551
|
+
firstSeenAt: null,
|
|
552
|
+
lastSeenAt: null,
|
|
553
|
+
};
|
|
554
|
+
|
|
555
|
+
linkedProfile.occurrences += 1;
|
|
556
|
+
updateDateRange(linkedProfile, row.created_at);
|
|
557
|
+
hwidAggregate.linkedProfiles.set(row.id, linkedProfile);
|
|
558
|
+
|
|
559
|
+
const linkedAccount = linkedAccountAggregates.get(row.id) || {
|
|
560
|
+
...relatedProfile,
|
|
561
|
+
occurrences: 0,
|
|
562
|
+
sharedHwids: new Set<string>(),
|
|
563
|
+
firstSeenAt: null,
|
|
564
|
+
lastSeenAt: null,
|
|
565
|
+
};
|
|
566
|
+
|
|
567
|
+
linkedAccount.occurrences += 1;
|
|
568
|
+
linkedAccount.sharedHwids.add(row.hwid);
|
|
569
|
+
updateDateRange(linkedAccount, row.created_at);
|
|
570
|
+
linkedAccountAggregates.set(row.id, linkedAccount);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
const accounts = Array.from(linkedAccountAggregates.values())
|
|
574
|
+
.map((account) => ({
|
|
575
|
+
...account,
|
|
576
|
+
sharedHwids: Array.from(account.sharedHwids).sort(),
|
|
577
|
+
sharedHwidCount: account.sharedHwids.size,
|
|
578
|
+
}))
|
|
579
|
+
.sort((a, b) => {
|
|
580
|
+
if (b.sharedHwidCount !== a.sharedHwidCount) {
|
|
581
|
+
return b.sharedHwidCount - a.sharedHwidCount;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
if (b.occurrences !== a.occurrences) {
|
|
585
|
+
return b.occurrences - a.occurrences;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
return timestampOrZero(b.lastSeenAt) - timestampOrZero(a.lastSeenAt);
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
const hwids = Array.from(hwidAggregates.values())
|
|
592
|
+
.map((aggregate) => ({
|
|
593
|
+
hwid: aggregate.hwid,
|
|
594
|
+
occurrences: aggregate.occurrences,
|
|
595
|
+
firstSeenAt: aggregate.firstSeenAt,
|
|
596
|
+
lastSeenAt: aggregate.lastSeenAt,
|
|
597
|
+
linkedAccountCount: aggregate.linkedProfiles.size,
|
|
598
|
+
linkedProfiles: Array.from(aggregate.linkedProfiles.values()).sort(
|
|
599
|
+
(a, b) => {
|
|
600
|
+
if (b.occurrences !== a.occurrences) {
|
|
601
|
+
return b.occurrences - a.occurrences;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
return (
|
|
605
|
+
timestampOrZero(b.lastSeenAt) - timestampOrZero(a.lastSeenAt)
|
|
606
|
+
);
|
|
607
|
+
}
|
|
608
|
+
),
|
|
609
|
+
}))
|
|
610
|
+
.sort((a, b) => {
|
|
611
|
+
if (b.linkedAccountCount !== a.linkedAccountCount) {
|
|
612
|
+
return b.linkedAccountCount - a.linkedAccountCount;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
if (b.occurrences !== a.occurrences) {
|
|
616
|
+
return b.occurrences - a.occurrences;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
return timestampOrZero(b.lastSeenAt) - timestampOrZero(a.lastSeenAt);
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
result = {
|
|
623
|
+
data: {
|
|
624
|
+
user: investigatedUser,
|
|
625
|
+
summary: {
|
|
626
|
+
totalRows: targetRows.length,
|
|
627
|
+
uniqueHwids: uniqueHwids.length,
|
|
628
|
+
sharedHwids: hwids.filter((hwid) => hwid.linkedAccountCount > 0)
|
|
629
|
+
.length,
|
|
630
|
+
linkedAccounts: accounts.length,
|
|
631
|
+
firstSeenAt: dateRange.firstSeenAt,
|
|
632
|
+
lastSeenAt: dateRange.lastSeenAt,
|
|
633
|
+
},
|
|
634
|
+
accounts,
|
|
635
|
+
hwids,
|
|
636
|
+
},
|
|
637
|
+
error: null,
|
|
638
|
+
};
|
|
639
|
+
break;
|
|
349
640
|
}
|
|
350
641
|
|
|
351
642
|
// Log the admin action
|
|
@@ -366,7 +657,11 @@ export async function handler(
|
|
|
366
657
|
);
|
|
367
658
|
}
|
|
368
659
|
|
|
369
|
-
if (
|
|
660
|
+
if (
|
|
661
|
+
targetUserId !== null &&
|
|
662
|
+
!result?.error &&
|
|
663
|
+
!SCORE_CACHE_READ_OPERATIONS.has(operation)
|
|
664
|
+
) {
|
|
370
665
|
await invalidateCachePrefix(`userscore:${targetUserId}`);
|
|
371
666
|
}
|
|
372
667
|
|
package/api/getCollection.ts
CHANGED
|
@@ -9,16 +9,17 @@ export const Schema = {
|
|
|
9
9
|
collection: z.number(),
|
|
10
10
|
}),
|
|
11
11
|
output: z.object({
|
|
12
|
-
collection: z.object({
|
|
13
|
-
title: z.string(),
|
|
14
|
-
description: z.string(),
|
|
15
|
-
owner: z.object({
|
|
16
|
-
id: z.number(),
|
|
17
|
-
username: z.string(),
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
12
|
+
collection: z.object({
|
|
13
|
+
title: z.string(),
|
|
14
|
+
description: z.string(),
|
|
15
|
+
owner: z.object({
|
|
16
|
+
id: z.number(),
|
|
17
|
+
username: z.string(),
|
|
18
|
+
avatar_url: z.string().nullable(),
|
|
19
|
+
}),
|
|
20
|
+
isList: z.boolean(),
|
|
21
|
+
beatmaps: z.array(
|
|
22
|
+
z.object({
|
|
22
23
|
id: z.number(),
|
|
23
24
|
playcount: z.number().nullable().optional(),
|
|
24
25
|
created_at: z.string().nullable().optional(),
|
|
@@ -54,13 +55,14 @@ export async function handler(data: (typeof Schema)["input"]["_type"]) {
|
|
|
54
55
|
.from("beatmapCollections")
|
|
55
56
|
.select(
|
|
56
57
|
`
|
|
57
|
-
*,
|
|
58
|
-
profiles!inner(
|
|
59
|
-
id,
|
|
60
|
-
username
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
58
|
+
*,
|
|
59
|
+
profiles!inner(
|
|
60
|
+
id,
|
|
61
|
+
username,
|
|
62
|
+
avatar_url
|
|
63
|
+
)
|
|
64
|
+
`
|
|
65
|
+
)
|
|
64
66
|
.eq("id", data.collection)
|
|
65
67
|
.single();
|
|
66
68
|
|
|
@@ -117,12 +119,13 @@ export async function handler(data: (typeof Schema)["input"]["_type"]) {
|
|
|
117
119
|
|
|
118
120
|
return NextResponse.json({
|
|
119
121
|
collection: {
|
|
120
|
-
owner: {
|
|
121
|
-
username: queryCollectionData.profiles.username,
|
|
122
|
-
id: queryCollectionData.profiles.id,
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
122
|
+
owner: {
|
|
123
|
+
username: queryCollectionData.profiles.username,
|
|
124
|
+
id: queryCollectionData.profiles.id,
|
|
125
|
+
avatar_url: queryCollectionData.profiles.avatar_url,
|
|
126
|
+
},
|
|
127
|
+
isList: queryCollectionData.is_list,
|
|
128
|
+
title: queryCollectionData.title,
|
|
126
129
|
description: queryCollectionData.description,
|
|
127
130
|
beatmaps: formattedBeatmaps,
|
|
128
131
|
},
|
package/api/getProfile.ts
CHANGED
|
@@ -40,11 +40,12 @@ export const Schema = {
|
|
|
40
40
|
country_position: z.number().nullable(),
|
|
41
41
|
activity_status: z.enum(["active", "inactive"]),
|
|
42
42
|
is_online: z.boolean(),
|
|
43
|
+
last_active_timestamp: z.number().nullable(),
|
|
43
44
|
clans: z
|
|
44
45
|
.object({
|
|
45
46
|
id: z.number(),
|
|
46
|
-
acronym: z.string(),
|
|
47
|
-
})
|
|
47
|
+
acronym: z.string(),
|
|
48
|
+
})
|
|
48
49
|
.optional()
|
|
49
50
|
.nullable(),
|
|
50
51
|
})
|
|
@@ -122,11 +123,11 @@ export async function handler(
|
|
|
122
123
|
|
|
123
124
|
const activityStatus = await getActivityStatusForUserId(user.id);
|
|
124
125
|
|
|
125
|
-
const { data: activityData } = await supabase
|
|
126
|
-
.from("profileActivities")
|
|
127
|
-
.select("
|
|
128
|
-
.eq("uid", user.uid || "")
|
|
129
|
-
.single();
|
|
126
|
+
const { data: activityData } = await supabase
|
|
127
|
+
.from("profileActivities")
|
|
128
|
+
.select("last_activity")
|
|
129
|
+
.eq("uid", user.uid || "")
|
|
130
|
+
.single();
|
|
130
131
|
|
|
131
132
|
//last 30 minutes
|
|
132
133
|
if (activityData && activityData.last_activity) {
|
|
@@ -181,6 +182,7 @@ export async function handler(
|
|
|
181
182
|
country_position: countryPosition,
|
|
182
183
|
activity_status: activityStatus,
|
|
183
184
|
is_online: isOnline,
|
|
185
|
+
last_active_timestamp: activityData?.last_activity ?? null,
|
|
184
186
|
},
|
|
185
187
|
});
|
|
186
188
|
}
|
package/api/reportProfile.ts
CHANGED
|
@@ -4,6 +4,7 @@ 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 { postProfileReportWebhook } from "../utils/profileReportWebhook";
|
|
7
8
|
|
|
8
9
|
const MAX_DESCRIPTION_LENGTH = 1000;
|
|
9
10
|
|
|
@@ -50,7 +51,7 @@ export async function handler({
|
|
|
50
51
|
const user = (await getUserBySession(session)) as User;
|
|
51
52
|
const { data: reporterProfile } = await supabase
|
|
52
53
|
.from("profiles")
|
|
53
|
-
.select("id,ban")
|
|
54
|
+
.select("id,ban,username,computedUsername,avatar_url")
|
|
54
55
|
.eq("uid", user.id)
|
|
55
56
|
.single();
|
|
56
57
|
|
|
@@ -68,7 +69,7 @@ export async function handler({
|
|
|
68
69
|
|
|
69
70
|
const { data: reportedProfile } = await supabase
|
|
70
71
|
.from("profiles")
|
|
71
|
-
.select("id")
|
|
72
|
+
.select("id,username,computedUsername,avatar_url")
|
|
72
73
|
.eq("id", profileId)
|
|
73
74
|
.single();
|
|
74
75
|
|
|
@@ -83,12 +84,20 @@ export async function handler({
|
|
|
83
84
|
reported: reportedProfile.id,
|
|
84
85
|
description: trimmedDescription,
|
|
85
86
|
})
|
|
86
|
-
.select("id")
|
|
87
|
+
.select("id,created_at")
|
|
87
88
|
.single();
|
|
88
89
|
|
|
89
90
|
if (insertResult.error) {
|
|
90
91
|
return NextResponse.json({ error: insertResult.error.message });
|
|
91
92
|
}
|
|
92
93
|
|
|
94
|
+
await postProfileReportWebhook({
|
|
95
|
+
reportId: insertResult.data.id,
|
|
96
|
+
reporter: reporterProfile,
|
|
97
|
+
reported: reportedProfile,
|
|
98
|
+
description: trimmedDescription,
|
|
99
|
+
createdAt: insertResult.data.created_at,
|
|
100
|
+
});
|
|
101
|
+
|
|
93
102
|
return NextResponse.json({ id: insertResult.data?.id });
|
|
94
103
|
}
|
package/index.ts
CHANGED
|
@@ -362,17 +362,7 @@ export const enhancedSearch = handleApi({url:"/api/enhancedSearch",...EnhancedSe
|
|
|
362
362
|
// ./api/executeAdminOperation.ts API
|
|
363
363
|
|
|
364
364
|
/*
|
|
365
|
-
|
|
366
|
-
input: z.strictObject({
|
|
367
|
-
session: z.string(),
|
|
368
|
-
data: OperationParam,
|
|
369
|
-
}),
|
|
370
|
-
output: z.object({
|
|
371
|
-
success: z.boolean(),
|
|
372
|
-
result: z.any().optional(),
|
|
373
|
-
error: z.string().optional(),
|
|
374
|
-
}),
|
|
375
|
-
};*/
|
|
365
|
+
*/
|
|
376
366
|
import { Schema as ExecuteAdminOperation } from "./api/executeAdminOperation"
|
|
377
367
|
export { Schema as SchemaExecuteAdminOperation } from "./api/executeAdminOperation"
|
|
378
368
|
export const executeAdminOperation = handleApi({url:"/api/executeAdminOperation",...ExecuteAdminOperation})
|
|
@@ -773,16 +763,17 @@ export const Schema = {
|
|
|
773
763
|
collection: z.number(),
|
|
774
764
|
}),
|
|
775
765
|
output: z.object({
|
|
776
|
-
collection: z.object({
|
|
777
|
-
title: z.string(),
|
|
778
|
-
description: z.string(),
|
|
779
|
-
owner: z.object({
|
|
780
|
-
id: z.number(),
|
|
781
|
-
username: z.string(),
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
766
|
+
collection: z.object({
|
|
767
|
+
title: z.string(),
|
|
768
|
+
description: z.string(),
|
|
769
|
+
owner: z.object({
|
|
770
|
+
id: z.number(),
|
|
771
|
+
username: z.string(),
|
|
772
|
+
avatar_url: z.string().nullable(),
|
|
773
|
+
}),
|
|
774
|
+
isList: z.boolean(),
|
|
775
|
+
beatmaps: z.array(
|
|
776
|
+
z.object({
|
|
786
777
|
id: z.number(),
|
|
787
778
|
playcount: z.number().nullable().optional(),
|
|
788
779
|
created_at: z.string().nullable().optional(),
|
|
@@ -1000,11 +991,12 @@ export const Schema = {
|
|
|
1000
991
|
country_position: z.number().nullable(),
|
|
1001
992
|
activity_status: z.enum(["active", "inactive"]),
|
|
1002
993
|
is_online: z.boolean(),
|
|
994
|
+
last_active_timestamp: z.number().nullable(),
|
|
1003
995
|
clans: z
|
|
1004
996
|
.object({
|
|
1005
997
|
id: z.number(),
|
|
1006
|
-
acronym: z.string(),
|
|
1007
|
-
})
|
|
998
|
+
acronym: z.string(),
|
|
999
|
+
})
|
|
1008
1000
|
.optional()
|
|
1009
1001
|
.nullable(),
|
|
1010
1002
|
})
|