rhythia-api 242.0.0 → 243.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/handleApi.ts CHANGED
@@ -1,21 +1,24 @@
1
- import { z } from "zod";
2
- let env = "development";
3
- import { profanity, CensorType } from "@2toad/profanity";
4
- export function setEnvironment(
5
- stage: "development" | "testing" | "production"
6
- ) {
7
- env = stage;
8
- }
9
- export function handleApi<
10
- T extends { url: string; input: z.ZodObject<any>; output: z.ZodObject<any> }
11
- >(apiSchema: T) {
12
- profanity.whitelist.addWords(["willy"]);
13
- return async (input: T["input"]["_type"]): Promise<T["output"]["_type"]> => {
14
- const response = await fetch(`https://${env}.rhythia.com${apiSchema.url}`, {
15
- method: "POST",
16
- body: JSON.stringify(input),
17
- });
18
- const output = await response.text();
19
- return JSON.parse(profanity.censor(output));
20
- };
21
- }
1
+ import { z } from "zod";
2
+ let endpoint = "https://development.rhythia.com";
3
+ import { profanity, CensorType } from "@2toad/profanity";
4
+ export function setEnvironment(
5
+ stage: "development" | "testing" | "production"
6
+ ) {
7
+ endpoint = `https://${stage}.rhythia.com`;
8
+ }
9
+ export function setEndpoint(value: string) {
10
+ endpoint = value;
11
+ }
12
+ export function handleApi<
13
+ T extends { url: string; input: z.ZodObject<any>; output: z.ZodObject<any> }
14
+ >(apiSchema: T) {
15
+ profanity.whitelist.addWords(["willy"]);
16
+ return async (input: T["input"]["_type"]): Promise<T["output"]["_type"]> => {
17
+ const response = await fetch(`${endpoint}${apiSchema.url}`, {
18
+ method: "POST",
19
+ body: JSON.stringify(input),
20
+ });
21
+ const output = await response.text();
22
+ return JSON.parse(profanity.censor(output));
23
+ };
24
+ }
package/index.ts CHANGED
@@ -392,18 +392,18 @@ export const executeAdminOperation = handleApi({url:"/api/executeAdminOperation"
392
392
  // ./api/getAvatarUploadUrl.ts API
393
393
 
394
394
  /*
395
- export const Schema = {
396
- input: z.strictObject({
397
- session: z.string(),
398
- contentLength: z.number(),
399
- contentType: z.string(),
400
- intrinsicToken: z.string(),
401
- }),
402
- output: z.strictObject({
403
- error: z.string().optional(),
404
- url: z.string().optional(),
405
- objectKey: z.string().optional(),
406
- }),
395
+ export const Schema = {
396
+ input: z.strictObject({
397
+ session: z.string(),
398
+ contentLength: z.number(),
399
+ contentType: z.string(),
400
+ intrinsicToken: z.string(),
401
+ }),
402
+ output: z.strictObject({
403
+ error: z.string().optional(),
404
+ url: z.string().optional(),
405
+ objectKey: z.string().optional(),
406
+ }),
407
407
  };*/
408
408
  import { Schema as GetAvatarUploadUrl } from "./api/getAvatarUploadUrl"
409
409
  export { Schema as SchemaGetAvatarUploadUrl } from "./api/getAvatarUploadUrl"
@@ -541,6 +541,7 @@ export const Schema = {
541
541
  description: z.string().nullable().optional(),
542
542
  tags: z.string().nullable().optional(),
543
543
  videoUrl: z.string().nullable().optional(),
544
+ mapHash: z.string().nullable().optional(),
544
545
  vetos: z
545
546
  .array(
546
547
  z.object({
@@ -613,6 +614,7 @@ export const Schema = {
613
614
  qualified: z.boolean().nullable().optional(),
614
615
  qualifiedAt: z.string().nullable().optional(),
615
616
  videoUrl: z.string().nullable(),
617
+ mapHash: z.string().nullable().optional(),
616
618
  vetos: z
617
619
  .array(
618
620
  z.object({
@@ -636,50 +638,51 @@ export const getBeatmapPageById = handleApi({url:"/api/getBeatmapPageById",...Ge
636
638
  // ./api/getBeatmaps.ts API
637
639
 
638
640
  /*
639
- export const Schema = {
640
- input: z.strictObject({
641
- session: z.string(),
642
- textFilter: z.string().optional(),
643
- authorFilter: z.string().optional(),
644
- tagsFilter: z.string().optional(),
645
- page: z.number().default(1),
646
- maxStars: z.number().optional(),
647
- minLength: z.number().optional(),
648
- maxLength: z.number().optional(),
649
- minStars: z.number().optional(),
650
- creator: z.number().optional(),
651
- status: z.string().optional(),
652
- }),
653
- output: z.object({
654
- error: z.string().optional(),
655
- total: z.number(),
656
- viewPerPage: z.number(),
657
- currentPage: z.number(),
658
- beatmaps: z
659
- .array(
660
- z.object({
661
- id: z.number(),
662
- playcount: z.number().nullable().optional(),
663
- created_at: z.string().nullable().optional(),
664
- difficulty: z.number().nullable().optional(),
665
- noteCount: z.number().nullable().optional(),
666
- length: z.number().nullable().optional(),
667
- title: z.string().nullable().optional(),
668
- ranked: z.boolean().nullable().optional(),
669
- beatmapFile: z.string().nullable().optional(),
670
- image: z.string().nullable().optional(),
671
- starRating: z.number().nullable().optional(),
672
- owner: z.number().nullable().optional(),
673
- ownerUsername: z.string().nullable().optional(),
674
- ownerAvatar: z.string().nullable().optional(),
675
- status: z.string().nullable().optional(),
676
- qualified: z.boolean().nullable().optional(),
677
- tags: z.string().nullable().optional(),
678
- videoUrl: z.string().nullable().optional(),
679
- })
680
- )
681
- .optional(),
682
- }),
641
+ export const Schema = {
642
+ input: z.strictObject({
643
+ session: z.string(),
644
+ textFilter: z.string().optional(),
645
+ authorFilter: z.string().optional(),
646
+ tagsFilter: z.string().optional(),
647
+ page: z.number().default(1),
648
+ maxStars: z.number().optional(),
649
+ minLength: z.number().optional(),
650
+ maxLength: z.number().optional(),
651
+ minStars: z.number().optional(),
652
+ creator: z.number().optional(),
653
+ status: z.string().optional(),
654
+ }),
655
+ output: z.object({
656
+ error: z.string().optional(),
657
+ total: z.number(),
658
+ viewPerPage: z.number(),
659
+ currentPage: z.number(),
660
+ beatmaps: z
661
+ .array(
662
+ z.object({
663
+ id: z.number(),
664
+ playcount: z.number().nullable().optional(),
665
+ created_at: z.string().nullable().optional(),
666
+ difficulty: z.number().nullable().optional(),
667
+ noteCount: z.number().nullable().optional(),
668
+ length: z.number().nullable().optional(),
669
+ title: z.string().nullable().optional(),
670
+ ranked: z.boolean().nullable().optional(),
671
+ beatmapFile: z.string().nullable().optional(),
672
+ image: z.string().nullable().optional(),
673
+ starRating: z.number().nullable().optional(),
674
+ owner: z.number().nullable().optional(),
675
+ ownerUsername: z.string().nullable().optional(),
676
+ ownerAvatar: z.string().nullable().optional(),
677
+ status: z.string().nullable().optional(),
678
+ qualified: z.boolean().nullable().optional(),
679
+ tags: z.string().nullable().optional(),
680
+ videoUrl: z.string().nullable().optional(),
681
+ mapHash: z.string().nullable().optional(),
682
+ })
683
+ )
684
+ .optional(),
685
+ }),
683
686
  };*/
684
687
  import { Schema as GetBeatmaps } from "./api/getBeatmaps"
685
688
  export { Schema as SchemaGetBeatmaps } from "./api/getBeatmaps"
@@ -783,19 +786,22 @@ export const Schema = {
783
786
  input: z.strictObject({
784
787
  session: z.string(),
785
788
  collection: z.number(),
789
+ page: z.number().optional().default(1),
790
+ itemsPerPage: z.number().optional().default(30),
786
791
  }),
787
792
  output: z.object({
788
- collection: z.object({
789
- title: z.string(),
790
- description: z.string(),
791
- owner: z.object({
792
- id: z.number(),
793
- username: z.string(),
794
- avatar_url: z.string().nullable(),
795
- }),
796
- isList: z.boolean(),
797
- beatmaps: z.array(
798
- z.object({
793
+ collection: z.object({
794
+ title: z.string(),
795
+ description: z.string(),
796
+ owner: z.object({
797
+ id: z.number(),
798
+ username: z.string(),
799
+ avatar_url: z.string().nullable(),
800
+ }),
801
+ isList: z.boolean(),
802
+ beatmapCount: z.number(),
803
+ beatmaps: z.array(
804
+ z.object({
799
805
  id: z.number(),
800
806
  playcount: z.number().nullable().optional(),
801
807
  created_at: z.string().nullable().optional(),
@@ -813,6 +819,7 @@ export const Schema = {
813
819
  })
814
820
  ),
815
821
  }),
822
+ totalPages: z.number(),
816
823
  error: z.string().optional(),
817
824
  }),
818
825
  };*/
@@ -939,19 +946,19 @@ export const getLeaderboard = handleApi({url:"/api/getLeaderboard",...GetLeaderb
939
946
  // ./api/getMapUploadUrl.ts API
940
947
 
941
948
  /*
942
- export const Schema = {
943
- input: z.strictObject({
944
- mapName: z.string().optional(),
945
- session: z.string(),
946
- contentLength: z.number(),
947
- contentType: z.string(),
948
- intrinsicToken: z.string(),
949
- }),
950
- output: z.strictObject({
951
- error: z.string().optional(),
952
- url: z.string().optional(),
953
- objectKey: z.string().optional(),
954
- }),
949
+ export const Schema = {
950
+ input: z.strictObject({
951
+ mapName: z.string().optional(),
952
+ session: z.string(),
953
+ contentLength: z.number(),
954
+ contentType: z.string(),
955
+ intrinsicToken: z.string(),
956
+ }),
957
+ output: z.strictObject({
958
+ error: z.string().optional(),
959
+ url: z.string().optional(),
960
+ objectKey: z.string().optional(),
961
+ }),
955
962
  };*/
956
963
  import { Schema as GetMapUploadUrl } from "./api/getMapUploadUrl"
957
964
  export { Schema as SchemaGetMapUploadUrl } from "./api/getMapUploadUrl"
@@ -1161,6 +1168,7 @@ export const Schema = {
1161
1168
  username: z.string().optional().nullable(),
1162
1169
  speed: z.number().optional().nullable(),
1163
1170
  spin: z.boolean().optional().nullable(),
1171
+ replay_url: z.string().optional().nullable(),
1164
1172
  })
1165
1173
  .optional(),
1166
1174
  }),
@@ -1321,18 +1329,18 @@ export const getVerified = handleApi({url:"/api/getVerified",...GetVerified})
1321
1329
  // ./api/getVideoUploadUrl.ts API
1322
1330
 
1323
1331
  /*
1324
- export const Schema = {
1325
- input: z.strictObject({
1326
- session: z.string(),
1327
- contentLength: z.number(),
1328
- contentType: z.string(),
1329
- intrinsicToken: z.string(),
1330
- }),
1331
- output: z.strictObject({
1332
- error: z.string().optional(),
1333
- url: z.string().optional(),
1334
- objectKey: z.string().optional(),
1335
- }),
1332
+ export const Schema = {
1333
+ input: z.strictObject({
1334
+ session: z.string(),
1335
+ contentLength: z.number(),
1336
+ contentType: z.string(),
1337
+ intrinsicToken: z.string(),
1338
+ }),
1339
+ output: z.strictObject({
1340
+ error: z.string().optional(),
1341
+ url: z.string().optional(),
1342
+ objectKey: z.string().optional(),
1343
+ }),
1336
1344
  };*/
1337
1345
  import { Schema as GetVideoUploadUrl } from "./api/getVideoUploadUrl"
1338
1346
  export { Schema as SchemaGetVideoUploadUrl } from "./api/getVideoUploadUrl"
@@ -1479,26 +1487,27 @@ export const submitScore = handleApi({url:"/api/submitScore",...SubmitScore})
1479
1487
  // ./api/submitScoreInternal.ts API
1480
1488
 
1481
1489
  /*
1482
- export const Schema = {
1483
- input: z.strictObject({
1484
- session: z.string(),
1485
- secret: z.string(),
1486
- token: z.string(),
1487
- data: z.strictObject({
1488
- onlineMapId: z.number(),
1489
- misses: z.number(),
1490
- hits: z.number(),
1491
- speed: z.number(),
1492
- mods: z.array(z.string()),
1493
- spin: z.boolean(),
1494
- replayBytes: z.string().nullable().optional(),
1495
- pauses: z.number().nullable().optional(),
1496
- failTime: z.number().nullable().optional(),
1497
- }),
1498
- }),
1499
- output: z.object({
1500
- error: z.string().optional(),
1501
- }),
1490
+ export const Schema = {
1491
+ input: z.strictObject({
1492
+ session: z.string(),
1493
+ secret: z.string(),
1494
+ token: z.string(),
1495
+ data: z.strictObject({
1496
+ beatmapHash: z.string().optional().nullable(),
1497
+ onlineMapId: z.number(),
1498
+ misses: z.number(),
1499
+ hits: z.number(),
1500
+ speed: z.number(),
1501
+ mods: z.array(z.string()),
1502
+ spin: z.boolean(),
1503
+ replayBytes: z.string().nullable().optional(),
1504
+ pauses: z.number().nullable().optional(),
1505
+ failTime: z.number().nullable().optional(),
1506
+ }),
1507
+ }),
1508
+ output: z.object({
1509
+ error: z.string().optional(),
1510
+ }),
1502
1511
  };*/
1503
1512
  import { Schema as SubmitScoreInternal } from "./api/submitScoreInternal"
1504
1513
  export { Schema as SchemaSubmitScoreInternal } from "./api/submitScoreInternal"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rhythia-api",
3
- "version": "242.0.0",
3
+ "version": "243.0.0",
4
4
  "main": "index.ts",
5
5
  "author": "online-contributors-cunev",
6
6
  "scripts": {
@@ -10,6 +10,8 @@
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:add-beatmap-page-map-hash": "node --experimental-transform-types scripts/add-beatmap-page-map-hash.ts",
14
+ "db:backfill-beatmap-page-map-hashes": "node --experimental-transform-types scripts/backfill-beatmap-page-map-hashes.ts",
13
15
  "db:create-profile-flags": "node scripts/create-profile-flags-table.ts",
14
16
  "db:create-profile-friends": "node scripts/create-profile-friends-table.ts",
15
17
  "db:create-profile-reports": "node scripts/create-profile-reports-table.ts",
@@ -28,7 +30,7 @@
28
30
  "@supabase/supabase-js": "^2.45.1",
29
31
  "@types/bun": "^1.1.6",
30
32
  "@types/lodash": "^4.17.7",
31
- "@types/node": "^22.2.0",
33
+ "@types/node": "^25.6.0",
32
34
  "@types/pg": "^8.15.5",
33
35
  "@types/validator": "^13.12.2",
34
36
  "bad-words": "^4.0.0",
@@ -1,39 +1,42 @@
1
- CREATE OR REPLACE FUNCTION public.admin_delete_user(user_id integer)
2
- RETURNS boolean
3
- LANGUAGE plpgsql
4
- SECURITY DEFINER
5
- AS $function$
6
- DECLARE
7
- success BOOLEAN;
8
- BEGIN
9
- -- Delete scores first due to foreign key constraints
10
- DELETE FROM public.scores WHERE "userId" = user_id;
11
-
12
- -- Delete beatmapPageComments
13
- DELETE FROM public."beatmapPageComments" WHERE owner = user_id;
14
-
15
- -- Delete beatmapPages owned by user
16
- DELETE FROM public."beatmapPages" WHERE owner = user_id;
17
-
18
- -- Delete collections owned by user
19
- DELETE FROM public."beatmapCollections" WHERE owner = user_id;
20
-
21
- -- Delete collection relations related to user's collections
22
- -- (this is handled by cascade if you have it set up)
23
-
24
- -- Delete passkeys
25
- DELETE FROM public.passkeys WHERE id = user_id;
26
-
27
- -- Delete profile activity
28
- DELETE FROM public."profileActivities"
29
- WHERE uid = (SELECT uid FROM public.profiles WHERE id = user_id);
30
-
31
- -- Finally, delete the profile
32
- DELETE FROM public.profiles WHERE id = user_id;
33
-
34
- -- Check if deletion was successful
35
- success := NOT EXISTS (SELECT 1 FROM public.profiles WHERE id = user_id);
36
-
37
- RETURN success;
38
- END;
39
- $function$
1
+ CREATE OR REPLACE FUNCTION public.admin_delete_user(user_id integer)
2
+ RETURNS boolean
3
+ LANGUAGE plpgsql
4
+ SECURITY DEFINER
5
+ AS $function$
6
+ DECLARE
7
+ success BOOLEAN;
8
+ BEGIN
9
+ -- Delete scores first due to foreign key constraints
10
+ DELETE FROM public.scores WHERE "userId" = user_id;
11
+
12
+ -- Delete beatmapPageComments
13
+ DELETE FROM public."beatmapPageComments" WHERE owner = user_id;
14
+
15
+ -- Delete beatmapPages owned by user
16
+ DELETE FROM public."beatmapPages" WHERE owner = user_id;
17
+
18
+ -- Delete collections owned by user
19
+ DELETE FROM public."beatmapCollections" WHERE owner = user_id;
20
+
21
+ -- Delete collection relations related to user's collections
22
+ -- (this is handled by cascade if you have it set up)
23
+
24
+ -- Delete passkeys
25
+ DELETE FROM public.passkeys WHERE id = user_id;
26
+
27
+ -- Delete user HWID records
28
+ DELETE FROM public.user_hwids WHERE id = user_id;
29
+
30
+ -- Delete profile activity
31
+ DELETE FROM public."profileActivities"
32
+ WHERE uid = (SELECT uid FROM public.profiles WHERE id = user_id);
33
+
34
+ -- Finally, delete the profile
35
+ DELETE FROM public.profiles WHERE id = user_id;
36
+
37
+ -- Check if deletion was successful
38
+ success := NOT EXISTS (SELECT 1 FROM public.profiles WHERE id = user_id);
39
+
40
+ RETURN success;
41
+ END;
42
+ $function$
@@ -5,9 +5,12 @@ CREATE OR REPLACE FUNCTION public.admin_remove_all_scores(user_id integer)
5
5
  AS $function$
6
6
  DECLARE
7
7
  deleted_count INTEGER;
8
- BEGIN
9
- -- Delete all scores for this user and count them
10
- WITH deleted AS (
8
+ BEGIN
9
+ DELETE FROM public.leaderboard_map_user
10
+ WHERE "userId" = user_id;
11
+
12
+ -- Delete all scores for this user and count them
13
+ WITH deleted AS (
11
14
  DELETE FROM public.scores
12
15
  WHERE "userId" = user_id
13
16
  RETURNING *
@@ -0,0 +1,107 @@
1
+ CREATE OR REPLACE FUNCTION public.admin_remove_score(user_id integer, score_id bigint)
2
+ RETURNS boolean
3
+ LANGUAGE plpgsql
4
+ SECURITY DEFINER
5
+ AS $function$
6
+ DECLARE
7
+ removed_score public.scores%ROWTYPE;
8
+ best_score RECORD;
9
+ total_sp numeric;
10
+ spin_total_sp numeric;
11
+ BEGIN
12
+ DELETE FROM public.scores
13
+ WHERE id = score_id
14
+ AND "userId" = user_id
15
+ RETURNING * INTO removed_score;
16
+
17
+ IF removed_score.id IS NULL THEN
18
+ RETURN false;
19
+ END IF;
20
+
21
+ SELECT id, awarded_sp
22
+ INTO best_score
23
+ FROM public.scores
24
+ WHERE "userId" = user_id
25
+ AND "beatmapHash" = removed_score."beatmapHash"
26
+ AND passed = true
27
+ ORDER BY awarded_sp DESC NULLS LAST
28
+ LIMIT 1;
29
+
30
+ IF best_score.id IS NULL THEN
31
+ DELETE FROM public.leaderboard_map_user
32
+ WHERE "userId" = user_id
33
+ AND "beatmapHash" = removed_score."beatmapHash";
34
+ ELSE
35
+ INSERT INTO public.leaderboard_map_user (
36
+ "beatmapHash",
37
+ "userId",
38
+ best_score_id,
39
+ best_sp
40
+ )
41
+ VALUES (
42
+ removed_score."beatmapHash",
43
+ user_id,
44
+ best_score.id,
45
+ best_score.awarded_sp
46
+ )
47
+ ON CONFLICT ("beatmapHash", "userId")
48
+ DO UPDATE SET
49
+ best_score_id = excluded.best_score_id,
50
+ best_sp = excluded.best_sp;
51
+ END IF;
52
+
53
+ WITH best_scores AS (
54
+ SELECT DISTINCT ON ("beatmapHash")
55
+ "beatmapHash",
56
+ awarded_sp
57
+ FROM public.scores
58
+ WHERE "userId" = user_id
59
+ AND passed = true
60
+ AND awarded_sp <> 0
61
+ ORDER BY "beatmapHash", awarded_sp DESC
62
+ ),
63
+ weighted AS (
64
+ SELECT
65
+ awarded_sp,
66
+ power(0.97, row_number() over (ORDER BY awarded_sp DESC) - 1) AS weight
67
+ FROM best_scores
68
+ )
69
+ SELECT COALESCE(round(sum(awarded_sp * weight) FILTER (WHERE weight >= 0.05), 2), 0)
70
+ INTO total_sp
71
+ FROM weighted;
72
+
73
+ WITH best_scores AS (
74
+ SELECT DISTINCT ON ("beatmapHash")
75
+ "beatmapHash",
76
+ awarded_sp
77
+ FROM public.scores
78
+ WHERE "userId" = user_id
79
+ AND passed = true
80
+ AND spin = true
81
+ AND awarded_sp <> 0
82
+ ORDER BY "beatmapHash", awarded_sp DESC
83
+ ),
84
+ weighted AS (
85
+ SELECT
86
+ awarded_sp,
87
+ power(0.97, row_number() over (ORDER BY awarded_sp DESC) - 1) AS weight
88
+ FROM best_scores
89
+ )
90
+ SELECT COALESCE(round(sum(awarded_sp * weight) FILTER (WHERE weight >= 0.05), 2), 0)
91
+ INTO spin_total_sp
92
+ FROM weighted;
93
+
94
+ UPDATE public.profiles
95
+ SET
96
+ play_count = (
97
+ SELECT count(*)
98
+ FROM public.scores
99
+ WHERE "userId" = user_id
100
+ ),
101
+ skill_points = total_sp,
102
+ spin_skill_points = spin_total_sp
103
+ WHERE id = user_id;
104
+
105
+ RETURN true;
106
+ END;
107
+ $function$
@@ -0,0 +1,22 @@
1
+ CREATE OR REPLACE FUNCTION public.admin_update_profile(user_id integer, profile_data jsonb)
2
+ RETURNS SETOF public.profiles
3
+ LANGUAGE plpgsql
4
+ SECURITY DEFINER
5
+ AS $function$
6
+ BEGIN
7
+ PERFORM set_config('app.bypass_profile_limits', 'on', true);
8
+
9
+ RETURN QUERY
10
+ UPDATE public.profiles
11
+ SET
12
+ username = CASE WHEN profile_data ? 'username' THEN profile_data->>'username' ELSE profiles.username END,
13
+ "computedUsername" = CASE WHEN profile_data ? 'username' THEN lower(profile_data->>'username') ELSE profiles."computedUsername" END,
14
+ avatar_url = CASE WHEN profile_data ? 'avatar_url' THEN profile_data->>'avatar_url' ELSE profiles.avatar_url END,
15
+ flag = CASE WHEN profile_data ? 'flag' THEN profile_data->>'flag' ELSE profiles.flag END,
16
+ profile_image = CASE WHEN profile_data ? 'profile_image' THEN profile_data->>'profile_image' ELSE profiles.profile_image END,
17
+ verified = CASE WHEN profile_data ? 'verified' THEN (profile_data->>'verified')::boolean ELSE profiles.verified END,
18
+ "verificationDeadline" = CASE WHEN profile_data ? 'verified' THEN 2524608000000 ELSE profiles."verificationDeadline" END
19
+ WHERE id = user_id
20
+ RETURNING profiles.*;
21
+ END;
22
+ $function$
@@ -0,0 +1,48 @@
1
+ DROP FUNCTION IF EXISTS public.get_beatmaps_v2(integer, integer, text, text, text, double precision, double precision, double precision, double precision, bigint, text);
2
+
3
+ CREATE OR REPLACE FUNCTION public.get_beatmaps_v2(page_number integer DEFAULT 1, items_per_page integer DEFAULT 50, text_filter text DEFAULT NULL::text, author_filter text DEFAULT NULL::text, tags_filter text DEFAULT NULL::text, max_stars double precision DEFAULT NULL::double precision, min_length double precision DEFAULT NULL::double precision, max_length double precision DEFAULT NULL::double precision, min_stars double precision DEFAULT NULL::double precision, creator_filter bigint DEFAULT NULL::bigint, status_filter text DEFAULT NULL::text)
4
+ RETURNS TABLE(total_count bigint, owner bigint, created_at timestamp with time zone, id bigint, status text, qualified boolean, tags text, video_url text, map_hash text, playcount bigint, ranked boolean, beatmap_file text, image text, star_rating double precision, difficulty bigint, length double precision, title text, owner_username text)
5
+ LANGUAGE sql
6
+ STABLE
7
+ AS $function$
8
+ SELECT
9
+ COUNT(*) OVER () AS total_count,
10
+ bp.owner::bigint,
11
+ bp.created_at,
12
+ bp.id::bigint,
13
+ bp.status,
14
+ bp.qualified,
15
+ bp.tags,
16
+ bp.video_url,
17
+ bp."mapHash",
18
+ b.playcount::bigint,
19
+ b.ranked,
20
+ b."beatmapFile",
21
+ b.image,
22
+ b."starRating"::double precision,
23
+ b.difficulty::bigint,
24
+ b.length::double precision,
25
+ b.title,
26
+ p.username
27
+ FROM public."beatmapPages" bp
28
+ INNER JOIN public.beatmaps b ON b."beatmapHash" = bp."latestBeatmapHash"
29
+ INNER JOIN public.profiles p ON p.id = bp.owner
30
+ WHERE (text_filter IS NULL OR b.title ILIKE '%' || text_filter || '%' OR p.username ILIKE '%' || text_filter || '%')
31
+ AND (author_filter IS NULL OR p.username ILIKE '%' || author_filter || '%')
32
+ AND (tags_filter IS NULL OR bp.tags ILIKE '%' || tags_filter || '%')
33
+ AND (min_stars IS NULL OR b."starRating" > min_stars)
34
+ AND (max_stars IS NULL OR b."starRating" < max_stars)
35
+ AND (min_length IS NULL OR b.length > min_length)
36
+ AND (max_length IS NULL OR b.length < max_length)
37
+ AND (creator_filter IS NULL OR bp.owner = creator_filter)
38
+ AND (
39
+ status_filter IS NULL
40
+ OR (UPPER(status_filter) = 'QUALIFIED' AND bp.qualified = true)
41
+ OR (UPPER(status_filter) <> 'QUALIFIED' AND bp.status = status_filter)
42
+ )
43
+ ORDER BY
44
+ CASE WHEN UPPER(status_filter) = 'RANKED' THEN bp.ranked_at END DESC NULLS LAST,
45
+ CASE WHEN UPPER(status_filter) = 'RANKED' THEN NULL ELSE bp.created_at END DESC NULLS LAST
46
+ LIMIT items_per_page
47
+ OFFSET ((page_number - 1) * items_per_page);
48
+ $function$