rhythia-api 235.0.0 → 236.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.
@@ -1,41 +1,41 @@
1
- import { NextResponse } from "../utils/response";
2
- import z from "zod";
3
- import { protectedApi } from "../utils/requestUtils";
4
- import { supabase } from "../utils/supabase";
5
-
6
- export const Schema = {
7
- input: z.strictObject({
8
- session: z.string(),
9
- textFilter: z.string().optional(),
10
- authorFilter: z.string().optional(),
11
- tagsFilter: z.string().optional(),
12
- page: z.number().default(1),
13
- maxStars: z.number().optional(),
14
- minLength: z.number().optional(),
15
- maxLength: z.number().optional(),
16
- minStars: z.number().optional(),
17
- creator: z.number().optional(),
18
- status: z.string().optional(),
19
- }),
20
- output: z.object({
21
- error: z.string().optional(),
22
- total: z.number(),
23
- viewPerPage: z.number(),
24
- currentPage: z.number(),
25
- beatmaps: z
1
+ import { NextResponse } from "../utils/response";
2
+ import z from "zod";
3
+ import { protectedApi } from "../utils/requestUtils";
4
+ import { supabase } from "../utils/supabase";
5
+
6
+ export const Schema = {
7
+ input: z.strictObject({
8
+ session: z.string(),
9
+ textFilter: z.string().optional(),
10
+ authorFilter: z.string().optional(),
11
+ tagsFilter: z.string().optional(),
12
+ page: z.number().default(1),
13
+ maxStars: z.number().optional(),
14
+ minLength: z.number().optional(),
15
+ maxLength: z.number().optional(),
16
+ minStars: z.number().optional(),
17
+ creator: z.number().optional(),
18
+ status: z.string().optional(),
19
+ }),
20
+ output: z.object({
21
+ error: z.string().optional(),
22
+ total: z.number(),
23
+ viewPerPage: z.number(),
24
+ currentPage: z.number(),
25
+ beatmaps: z
26
26
  .array(
27
27
  z.object({
28
28
  id: z.number(),
29
29
  playcount: z.number().nullable().optional(),
30
30
  created_at: z.string().nullable().optional(),
31
- difficulty: z.number().nullable().optional(),
32
- noteCount: z.number().nullable().optional(),
33
- length: z.number().nullable().optional(),
34
- title: z.string().nullable().optional(),
35
- ranked: z.boolean().nullable().optional(),
36
- beatmapFile: z.string().nullable().optional(),
37
- image: z.string().nullable().optional(),
38
- starRating: z.number().nullable().optional(),
31
+ difficulty: z.number().nullable().optional(),
32
+ noteCount: z.number().nullable().optional(),
33
+ length: z.number().nullable().optional(),
34
+ title: z.string().nullable().optional(),
35
+ ranked: z.boolean().nullable().optional(),
36
+ beatmapFile: z.string().nullable().optional(),
37
+ image: z.string().nullable().optional(),
38
+ starRating: z.number().nullable().optional(),
39
39
  owner: z.number().nullable().optional(),
40
40
  ownerUsername: z.string().nullable().optional(),
41
41
  ownerAvatar: z.string().nullable().optional(),
@@ -46,30 +46,31 @@ export const Schema = {
46
46
  })
47
47
  )
48
48
  .optional(),
49
- }),
50
- };
51
-
52
- export async function POST(request: Request): Promise<NextResponse> {
53
- return protectedApi({
54
- request,
55
- schema: Schema,
56
- authorization: () => {},
57
- activity: handler,
58
- });
59
- }
60
-
61
- export async function handler(
62
- data: (typeof Schema)["input"]["_type"]
63
- ): Promise<NextResponse<(typeof Schema)["output"]["_type"]>> {
64
- const result = await getBeatmaps(data);
65
- return NextResponse.json(result);
66
- }
67
-
68
- const VIEW_PER_PAGE = 50;
69
-
49
+ }),
50
+ };
51
+
52
+ export async function POST(request: Request): Promise<NextResponse> {
53
+ return protectedApi({
54
+ request,
55
+ schema: Schema,
56
+ authorization: () => {},
57
+ activity: handler,
58
+ });
59
+ }
60
+
61
+ export async function handler(
62
+ data: (typeof Schema)["input"]["_type"]
63
+ ): Promise<NextResponse<(typeof Schema)["output"]["_type"]>> {
64
+ const result = await getBeatmaps(data);
65
+ return NextResponse.json(result);
66
+ }
67
+
68
+ const VIEW_PER_PAGE = 50;
69
+
70
70
  export async function getBeatmaps(data: (typeof Schema)["input"]["_type"]) {
71
71
  const startPage = (data.page - 1) * VIEW_PER_PAGE;
72
72
  const endPage = startPage + VIEW_PER_PAGE - 1;
73
+ // status filter is a bit uncanny, we store qualified as a field, but it acts as a status in the UI.
73
74
  const statusFilter = data.status?.toUpperCase();
74
75
  let countQry = supabase
75
76
  .from("beatmapPages")
@@ -100,14 +101,14 @@ export async function getBeatmaps(data: (typeof Schema)["input"]["_type"]) {
100
101
  video_url,
101
102
  beatmaps!inner(
102
103
  playcount,
103
- ranked,
104
- beatmapFile,
105
- image,
106
- starRating,
107
- difficulty,
108
- length,
109
- title
110
- ),
104
+ ranked,
105
+ beatmapFile,
106
+ image,
107
+ starRating,
108
+ difficulty,
109
+ length,
110
+ title
111
+ ),
111
112
  profiles!inner(
112
113
  username
113
114
  )`
@@ -173,18 +174,18 @@ export async function getBeatmaps(data: (typeof Schema)["input"]["_type"]) {
173
174
  return {
174
175
  total: countQuery.count || 0,
175
176
  viewPerPage: VIEW_PER_PAGE,
176
- currentPage: data.page,
177
- beatmaps: queryData.data?.map((beatmapPage) => ({
178
- id: beatmapPage.id,
179
- tags: beatmapPage.tags,
180
- playcount: beatmapPage.beatmaps?.playcount,
181
- created_at: beatmapPage.created_at,
182
- difficulty: beatmapPage.beatmaps?.difficulty,
183
- title: beatmapPage.beatmaps?.title,
184
- ranked: beatmapPage.beatmaps?.ranked,
185
- length: beatmapPage.beatmaps?.length,
186
- beatmapFile: beatmapPage.beatmaps?.beatmapFile,
187
- image: beatmapPage.beatmaps?.image,
177
+ currentPage: data.page,
178
+ beatmaps: queryData.data?.map((beatmapPage) => ({
179
+ id: beatmapPage.id,
180
+ tags: beatmapPage.tags,
181
+ playcount: beatmapPage.beatmaps?.playcount,
182
+ created_at: beatmapPage.created_at,
183
+ difficulty: beatmapPage.beatmaps?.difficulty,
184
+ title: beatmapPage.beatmaps?.title,
185
+ ranked: beatmapPage.beatmaps?.ranked,
186
+ length: beatmapPage.beatmaps?.length,
187
+ beatmapFile: beatmapPage.beatmaps?.beatmapFile,
188
+ image: beatmapPage.beatmaps?.image,
188
189
  starRating: beatmapPage.beatmaps?.starRating,
189
190
  owner: beatmapPage.owner,
190
191
  status: beatmapPage.status,
@@ -0,0 +1,94 @@
1
+ import { NextResponse } from "../utils/response";
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
+
8
+ const MAX_DESCRIPTION_LENGTH = 1000;
9
+
10
+ export const Schema = {
11
+ input: z.strictObject({
12
+ session: z.string(),
13
+ profileId: z.number(),
14
+ description: z.string(),
15
+ }),
16
+ output: z.strictObject({
17
+ error: z.string().optional(),
18
+ id: z.number().optional(),
19
+ }),
20
+ };
21
+
22
+ export async function POST(request: Request): Promise<NextResponse> {
23
+ return protectedApi({
24
+ request,
25
+ schema: Schema,
26
+ authorization: validUser,
27
+ activity: handler,
28
+ });
29
+ }
30
+
31
+ export async function handler({
32
+ session,
33
+ profileId,
34
+ description,
35
+ }: (typeof Schema)["input"]["_type"]): Promise<
36
+ NextResponse<(typeof Schema)["output"]["_type"]>
37
+ > {
38
+ const trimmedDescription = description.trim();
39
+
40
+ if (!trimmedDescription.length) {
41
+ return NextResponse.json({ error: "Report description is required." });
42
+ }
43
+
44
+ if (trimmedDescription.length > MAX_DESCRIPTION_LENGTH) {
45
+ return NextResponse.json({
46
+ error: `Report description exceeds ${MAX_DESCRIPTION_LENGTH} characters.`,
47
+ });
48
+ }
49
+
50
+ const user = (await getUserBySession(session)) as User;
51
+ const { data: reporterProfile } = await supabase
52
+ .from("profiles")
53
+ .select("id,ban")
54
+ .eq("uid", user.id)
55
+ .single();
56
+
57
+ if (!reporterProfile) {
58
+ return NextResponse.json({ error: "Can't find user" });
59
+ }
60
+
61
+ if (reporterProfile.ban !== "cool") {
62
+ return NextResponse.json({ error: "Error" });
63
+ }
64
+
65
+ if (reporterProfile.id === profileId) {
66
+ return NextResponse.json({ error: "You can't report yourself." });
67
+ }
68
+
69
+ const { data: reportedProfile } = await supabase
70
+ .from("profiles")
71
+ .select("id")
72
+ .eq("id", profileId)
73
+ .single();
74
+
75
+ if (!reportedProfile) {
76
+ return NextResponse.json({ error: "Player not found." });
77
+ }
78
+
79
+ const insertResult = await supabase
80
+ .from("profileReports")
81
+ .insert({
82
+ reporter: reporterProfile.id,
83
+ reported: reportedProfile.id,
84
+ description: trimmedDescription,
85
+ })
86
+ .select("id")
87
+ .single();
88
+
89
+ if (insertResult.error) {
90
+ return NextResponse.json({ error: insertResult.error.message });
91
+ }
92
+
93
+ return NextResponse.json({ id: insertResult.data?.id });
94
+ }
package/index.ts CHANGED
@@ -624,39 +624,39 @@ export const getBeatmapPageById = handleApi({url:"/api/getBeatmapPageById",...Ge
624
624
  // ./api/getBeatmaps.ts API
625
625
 
626
626
  /*
627
- export const Schema = {
628
- input: z.strictObject({
629
- session: z.string(),
630
- textFilter: z.string().optional(),
631
- authorFilter: z.string().optional(),
632
- tagsFilter: z.string().optional(),
633
- page: z.number().default(1),
634
- maxStars: z.number().optional(),
635
- minLength: z.number().optional(),
636
- maxLength: z.number().optional(),
637
- minStars: z.number().optional(),
638
- creator: z.number().optional(),
639
- status: z.string().optional(),
640
- }),
641
- output: z.object({
642
- error: z.string().optional(),
643
- total: z.number(),
644
- viewPerPage: z.number(),
645
- currentPage: z.number(),
646
- beatmaps: z
627
+ export const Schema = {
628
+ input: z.strictObject({
629
+ session: z.string(),
630
+ textFilter: z.string().optional(),
631
+ authorFilter: z.string().optional(),
632
+ tagsFilter: z.string().optional(),
633
+ page: z.number().default(1),
634
+ maxStars: z.number().optional(),
635
+ minLength: z.number().optional(),
636
+ maxLength: z.number().optional(),
637
+ minStars: z.number().optional(),
638
+ creator: z.number().optional(),
639
+ status: z.string().optional(),
640
+ }),
641
+ output: z.object({
642
+ error: z.string().optional(),
643
+ total: z.number(),
644
+ viewPerPage: z.number(),
645
+ currentPage: z.number(),
646
+ beatmaps: z
647
647
  .array(
648
648
  z.object({
649
649
  id: z.number(),
650
650
  playcount: z.number().nullable().optional(),
651
651
  created_at: z.string().nullable().optional(),
652
- difficulty: z.number().nullable().optional(),
653
- noteCount: z.number().nullable().optional(),
654
- length: z.number().nullable().optional(),
655
- title: z.string().nullable().optional(),
656
- ranked: z.boolean().nullable().optional(),
657
- beatmapFile: z.string().nullable().optional(),
658
- image: z.string().nullable().optional(),
659
- starRating: z.number().nullable().optional(),
652
+ difficulty: z.number().nullable().optional(),
653
+ noteCount: z.number().nullable().optional(),
654
+ length: z.number().nullable().optional(),
655
+ title: z.string().nullable().optional(),
656
+ ranked: z.boolean().nullable().optional(),
657
+ beatmapFile: z.string().nullable().optional(),
658
+ image: z.string().nullable().optional(),
659
+ starRating: z.number().nullable().optional(),
660
660
  owner: z.number().nullable().optional(),
661
661
  ownerUsername: z.string().nullable().optional(),
662
662
  ownerAvatar: z.string().nullable().optional(),
@@ -667,7 +667,7 @@ export const Schema = {
667
667
  })
668
668
  )
669
669
  .optional(),
670
- }),
670
+ }),
671
671
  };*/
672
672
  import { Schema as GetBeatmaps } from "./api/getBeatmaps"
673
673
  export { Schema as SchemaGetBeatmaps } from "./api/getBeatmaps"
@@ -1348,6 +1348,24 @@ import { Schema as RankMapsArchive } from "./api/rankMapsArchive"
1348
1348
  export { Schema as SchemaRankMapsArchive } from "./api/rankMapsArchive"
1349
1349
  export const rankMapsArchive = handleApi({url:"/api/rankMapsArchive",...RankMapsArchive})
1350
1350
 
1351
+ // ./api/reportProfile.ts API
1352
+
1353
+ /*
1354
+ export const Schema = {
1355
+ input: z.strictObject({
1356
+ session: z.string(),
1357
+ profileId: z.number(),
1358
+ description: z.string(),
1359
+ }),
1360
+ output: z.strictObject({
1361
+ error: z.string().optional(),
1362
+ id: z.number().optional(),
1363
+ }),
1364
+ };*/
1365
+ import { Schema as ReportProfile } from "./api/reportProfile"
1366
+ export { Schema as SchemaReportProfile } from "./api/reportProfile"
1367
+ export const reportProfile = handleApi({url:"/api/reportProfile",...ReportProfile})
1368
+
1351
1369
  // ./api/searchUsers.ts API
1352
1370
 
1353
1371
  /*
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rhythia-api",
3
- "version": "235.0.0",
3
+ "version": "236.0.0",
4
4
  "main": "index.ts",
5
5
  "author": "online-contributors-cunev",
6
6
  "scripts": {
@@ -10,6 +10,7 @@
10
10
  "test": "tsx ./scripts/test.ts",
11
11
  "cache:clear-user-scores": "bun run scripts/clear-user-score-cache.ts",
12
12
  "db:optimize-user-scores": "bun run scripts/optimize-user-scores-indexes.ts",
13
+ "db:create-profile-reports": "node scripts/create-profile-reports-table.ts",
13
14
  "query-pull": "bun run scripts/pull-queries.ts",
14
15
  "query-push": "bun run scripts/deploy-queries.ts",
15
16
  "queries:pull": "bun run scripts/pull-queries.ts",
package/types/database.ts CHANGED
@@ -519,6 +519,45 @@ export type Database = {
519
519
  }
520
520
  Relationships: []
521
521
  }
522
+ profileReports: {
523
+ Row: {
524
+ created_at: string
525
+ description: string
526
+ id: number
527
+ reported: number
528
+ reporter: number
529
+ }
530
+ Insert: {
531
+ created_at?: string
532
+ description: string
533
+ id?: number
534
+ reported: number
535
+ reporter: number
536
+ }
537
+ Update: {
538
+ created_at?: string
539
+ description?: string
540
+ id?: number
541
+ reported?: number
542
+ reporter?: number
543
+ }
544
+ Relationships: [
545
+ {
546
+ foreignKeyName: "profileReports_reported_fkey"
547
+ columns: ["reported"]
548
+ isOneToOne: false
549
+ referencedRelation: "profiles"
550
+ referencedColumns: ["id"]
551
+ },
552
+ {
553
+ foreignKeyName: "profileReports_reporter_fkey"
554
+ columns: ["reporter"]
555
+ isOneToOne: false
556
+ referencedRelation: "profiles"
557
+ referencedColumns: ["id"]
558
+ },
559
+ ]
560
+ }
522
561
  profiles: {
523
562
  Row: {
524
563
  about_me: string | null
package/worker.ts CHANGED
@@ -46,6 +46,7 @@ import { POST as getVideoUploadUrl } from "./api/getVideoUploadUrl";
46
46
  import { POST as postBeatmapComment } from "./api/postBeatmapComment";
47
47
  import { POST as qualifyMap } from "./api/qualifyMap";
48
48
  import { POST as rankMapsArchive } from "./api/rankMapsArchive";
49
+ import { POST as reportProfile } from "./api/reportProfile";
49
50
  import { POST as searchUsers } from "./api/searchUsers";
50
51
  import { POST as setPasskey } from "./api/setPasskey";
51
52
  import { POST as submitScore } from "./api/submitScore";
@@ -113,6 +114,7 @@ const apiRoutes: Record<string, RouteHandler> = {
113
114
  "/api/postBeatmapComment": postBeatmapComment,
114
115
  "/api/qualifyMap": qualifyMap,
115
116
  "/api/rankMapsArchive": rankMapsArchive,
117
+ "/api/reportProfile": reportProfile,
116
118
  "/api/searchUsers": searchUsers,
117
119
  "/api/setPasskey": setPasskey,
118
120
  "/api/submitScore": submitScore,