rhythia-api 242.0.0 → 244.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 (43) hide show
  1. package/api/createBeatmap.ts +64 -40
  2. package/api/editProfile.ts +4 -67
  3. package/api/executeAdminOperation.ts +637 -27
  4. package/api/getAvatarUploadUrl.ts +90 -85
  5. package/api/getBeatmapPage.ts +2 -0
  6. package/api/getBeatmapPageById.ts +2 -0
  7. package/api/getBeatmaps.ts +110 -197
  8. package/api/getChangelog.ts +46 -0
  9. package/api/getCollection.ts +44 -31
  10. package/api/getMapUploadUrl.ts +90 -93
  11. package/api/getProfile.ts +297 -297
  12. package/api/getScore.ts +2 -0
  13. package/api/getVideoUploadUrl.ts +90 -85
  14. package/api/submitScoreInternal.ts +506 -461
  15. package/api/updateBeatmapPage.ts +6 -0
  16. package/beatmap-file-urls.json +29398 -0
  17. package/handleApi.ts +7 -4
  18. package/index.ts +193 -162
  19. package/package.json +7 -3
  20. package/queries/admin_delete_user.sql +42 -39
  21. package/queries/admin_remove_all_scores.sql +6 -3
  22. package/queries/admin_remove_score.sql +107 -0
  23. package/queries/admin_update_profile.sql +22 -0
  24. package/queries/get_beatmaps_v2.sql +48 -0
  25. package/queries/get_top_scores_for_beatmap3.sql +47 -38
  26. package/queries/profile_update_guards.sql +66 -0
  27. package/supabase/.temp/cli-latest +1 -0
  28. package/supabase/.temp/linked-project.json +1 -0
  29. package/types/database.ts +1702 -1450
  30. package/utils/beatmapFiles.ts +102 -0
  31. package/utils/beatmapHash.ts +239 -0
  32. package/utils/beatmapTopScores.ts +68 -84
  33. package/utils/getUserBySession.ts +3 -1
  34. package/utils/moderation.ts +101 -0
  35. package/utils/profileUpdateValidation.ts +51 -0
  36. package/utils/redis.ts +24 -0
  37. package/utils/requestUtils.ts +2 -2
  38. package/utils/rhrReplay.ts +122 -0
  39. package/utils/star-calc/formatSingle.ts +107 -0
  40. package/utils/star-calc/rhmParser.ts +214 -0
  41. package/utils/star-calc/sspmParser.ts +294 -160
  42. package/worker.ts +197 -195
  43. package/.env +0 -1
package/handleApi.ts CHANGED
@@ -1,17 +1,20 @@
1
1
  import { z } from "zod";
2
- let env = "development";
2
+ let endpoint = "https://development.rhythia.com";
3
3
  import { profanity, CensorType } from "@2toad/profanity";
4
4
  export function setEnvironment(
5
5
  stage: "development" | "testing" | "production"
6
6
  ) {
7
- env = stage;
7
+ endpoint = `https://${stage}.rhythia.com`;
8
+ }
9
+ export function setEndpoint(value: string) {
10
+ endpoint = value;
8
11
  }
9
12
  export function handleApi<
10
- T extends { url: string; input: z.ZodObject<any>; output: z.ZodObject<any> }
13
+ T extends { url: string; input: z.ZodObject<any>; output: z.ZodTypeAny }
11
14
  >(apiSchema: T) {
12
15
  profanity.whitelist.addWords(["willy"]);
13
16
  return async (input: T["input"]["_type"]): Promise<T["output"]["_type"]> => {
14
- const response = await fetch(`https://${env}.rhythia.com${apiSchema.url}`, {
17
+ const response = await fetch(`${endpoint}${apiSchema.url}`, {
15
18
  method: "POST",
16
19
  body: JSON.stringify(input),
17
20
  });
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"
@@ -706,6 +709,28 @@ import { Schema as GetBeatmapStarRating } from "./api/getBeatmapStarRating"
706
709
  export { Schema as SchemaGetBeatmapStarRating } from "./api/getBeatmapStarRating"
707
710
  export const getBeatmapStarRating = handleApi({url:"/api/getBeatmapStarRating",...GetBeatmapStarRating})
708
711
 
712
+ // ./api/getChangelog.ts API
713
+
714
+ /*
715
+ export const Schema = {
716
+ input: z.strictObject({
717
+ type: changelogType,
718
+ skip: z.number().default(0),
719
+ limit: z.number().default(10),
720
+ }),
721
+ output: z.array(
722
+ z.object({
723
+ id: z.number(),
724
+ type: z.string(),
725
+ date: z.string(),
726
+ markdown: z.string(),
727
+ })
728
+ ),
729
+ };*/
730
+ import { Schema as GetChangelog } from "./api/getChangelog"
731
+ export { Schema as SchemaGetChangelog } from "./api/getChangelog"
732
+ export const getChangelog = handleApi({url:"/api/getChangelog",...GetChangelog})
733
+
709
734
  // ./api/getClan.ts API
710
735
 
711
736
  /*
@@ -783,19 +808,22 @@ export const Schema = {
783
808
  input: z.strictObject({
784
809
  session: z.string(),
785
810
  collection: z.number(),
811
+ page: z.number().optional().default(1),
812
+ itemsPerPage: z.number().optional().default(30),
786
813
  }),
787
814
  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({
815
+ collection: z.object({
816
+ title: z.string(),
817
+ description: z.string(),
818
+ owner: z.object({
819
+ id: z.number(),
820
+ username: z.string(),
821
+ avatar_url: z.string().nullable(),
822
+ }),
823
+ isList: z.boolean(),
824
+ beatmapCount: z.number(),
825
+ beatmaps: z.array(
826
+ z.object({
799
827
  id: z.number(),
800
828
  playcount: z.number().nullable().optional(),
801
829
  created_at: z.string().nullable().optional(),
@@ -813,6 +841,7 @@ export const Schema = {
813
841
  })
814
842
  ),
815
843
  }),
844
+ totalPages: z.number(),
816
845
  error: z.string().optional(),
817
846
  }),
818
847
  };*/
@@ -939,19 +968,19 @@ export const getLeaderboard = handleApi({url:"/api/getLeaderboard",...GetLeaderb
939
968
  // ./api/getMapUploadUrl.ts API
940
969
 
941
970
  /*
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
- }),
971
+ export const Schema = {
972
+ input: z.strictObject({
973
+ mapName: z.string().optional(),
974
+ session: z.string(),
975
+ contentLength: z.number(),
976
+ contentType: z.string(),
977
+ intrinsicToken: z.string(),
978
+ }),
979
+ output: z.strictObject({
980
+ error: z.string().optional(),
981
+ url: z.string().optional(),
982
+ objectKey: z.string().optional(),
983
+ }),
955
984
  };*/
956
985
  import { Schema as GetMapUploadUrl } from "./api/getMapUploadUrl"
957
986
  export { Schema as SchemaGetMapUploadUrl } from "./api/getMapUploadUrl"
@@ -1000,56 +1029,56 @@ export const getPassToken = handleApi({url:"/api/getPassToken",...GetPassToken})
1000
1029
  // ./api/getProfile.ts API
1001
1030
 
1002
1031
  /*
1003
- export const Schema = {
1004
- input: z.strictObject({
1005
- session: z.string(),
1006
- id: z.number().nullable().optional(),
1007
- }),
1008
- output: z.object({
1009
- error: z.string().optional(),
1010
- user: z
1011
- .object({
1012
- about_me: z.string().nullable(),
1013
- avatar_url: z.string().nullable(),
1014
- profile_image: z.string().nullable(),
1015
- badges: z.any().nullable(),
1016
- created_at: z.number().nullable(),
1017
- flag: z.string().nullable(),
1018
- id: z.number(),
1019
- uid: z.string().nullable(),
1020
- ban: z.string().nullable(),
1021
- username: z.string().nullable(),
1022
- verified: z.boolean().nullable(),
1023
- verificationDeadline: z.number().nullable(),
1024
- play_count: z.number().nullable(),
1025
- skill_points: z.number().nullable(),
1026
- squares_hit: z.number().nullable(),
1027
- total_score: z.number().nullable(),
1028
- position: z.number().nullable(),
1029
- country_position: z.number().nullable(),
1030
- activity_status: z.enum(["active", "inactive"]),
1031
- is_online: z.boolean(),
1032
- last_active_timestamp: z.number().nullable(),
1033
- friend_count: z.number(),
1034
- friend_state: friendStateSchema,
1035
- can_change_flag: z.boolean(),
1036
- next_username_change_at: z.string().nullable(),
1037
- previous_usernames: z.array(
1038
- z.object({
1039
- username: z.string(),
1040
- changed_at: z.string(),
1041
- })
1042
- ),
1043
- clans: z
1044
- .object({
1045
- id: z.number(),
1046
- acronym: z.string(),
1047
- })
1048
- .optional()
1049
- .nullable(),
1050
- })
1051
- .optional(),
1052
- }),
1032
+ export const Schema = {
1033
+ input: z.strictObject({
1034
+ session: z.string(),
1035
+ id: z.number().nullable().optional(),
1036
+ }),
1037
+ output: z.object({
1038
+ error: z.string().optional(),
1039
+ user: z
1040
+ .object({
1041
+ about_me: z.string().nullable(),
1042
+ avatar_url: z.string().nullable(),
1043
+ profile_image: z.string().nullable(),
1044
+ badges: z.any().nullable(),
1045
+ created_at: z.number().nullable(),
1046
+ flag: z.string().nullable(),
1047
+ id: z.number(),
1048
+ uid: z.string().nullable(),
1049
+ ban: z.string().nullable(),
1050
+ username: z.string().nullable(),
1051
+ verified: z.boolean().nullable(),
1052
+ verificationDeadline: z.number().nullable(),
1053
+ play_count: z.number().nullable(),
1054
+ skill_points: z.number().nullable(),
1055
+ squares_hit: z.number().nullable(),
1056
+ total_score: z.number().nullable(),
1057
+ position: z.number().nullable(),
1058
+ country_position: z.number().nullable(),
1059
+ activity_status: z.enum(["active", "inactive"]),
1060
+ is_online: z.boolean(),
1061
+ last_active_timestamp: z.number().nullable(),
1062
+ friend_count: z.number(),
1063
+ friend_state: friendStateSchema,
1064
+ can_change_flag: z.boolean(),
1065
+ next_username_change_at: z.string().nullable(),
1066
+ previous_usernames: z.array(
1067
+ z.object({
1068
+ username: z.string(),
1069
+ changed_at: z.string(),
1070
+ })
1071
+ ),
1072
+ clans: z
1073
+ .object({
1074
+ id: z.number(),
1075
+ acronym: z.string(),
1076
+ })
1077
+ .optional()
1078
+ .nullable(),
1079
+ })
1080
+ .optional(),
1081
+ }),
1053
1082
  };*/
1054
1083
  import { Schema as GetProfile } from "./api/getProfile"
1055
1084
  export { Schema as SchemaGetProfile } from "./api/getProfile"
@@ -1161,6 +1190,7 @@ export const Schema = {
1161
1190
  username: z.string().optional().nullable(),
1162
1191
  speed: z.number().optional().nullable(),
1163
1192
  spin: z.boolean().optional().nullable(),
1193
+ replay_url: z.string().optional().nullable(),
1164
1194
  })
1165
1195
  .optional(),
1166
1196
  }),
@@ -1321,18 +1351,18 @@ export const getVerified = handleApi({url:"/api/getVerified",...GetVerified})
1321
1351
  // ./api/getVideoUploadUrl.ts API
1322
1352
 
1323
1353
  /*
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
- }),
1354
+ export const Schema = {
1355
+ input: z.strictObject({
1356
+ session: z.string(),
1357
+ contentLength: z.number(),
1358
+ contentType: z.string(),
1359
+ intrinsicToken: z.string(),
1360
+ }),
1361
+ output: z.strictObject({
1362
+ error: z.string().optional(),
1363
+ url: z.string().optional(),
1364
+ objectKey: z.string().optional(),
1365
+ }),
1336
1366
  };*/
1337
1367
  import { Schema as GetVideoUploadUrl } from "./api/getVideoUploadUrl"
1338
1368
  export { Schema as SchemaGetVideoUploadUrl } from "./api/getVideoUploadUrl"
@@ -1479,26 +1509,27 @@ export const submitScore = handleApi({url:"/api/submitScore",...SubmitScore})
1479
1509
  // ./api/submitScoreInternal.ts API
1480
1510
 
1481
1511
  /*
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
- }),
1512
+ export const Schema = {
1513
+ input: z.strictObject({
1514
+ session: z.string(),
1515
+ secret: z.string(),
1516
+ token: z.string(),
1517
+ data: z.strictObject({
1518
+ beatmapHash: z.string().optional().nullable(),
1519
+ onlineMapId: z.number(),
1520
+ misses: z.number(),
1521
+ hits: z.number(),
1522
+ speed: z.number(),
1523
+ mods: z.array(z.string()),
1524
+ spin: z.boolean(),
1525
+ replayBytes: z.string().nullable().optional(),
1526
+ pauses: z.number().nullable().optional(),
1527
+ failTime: z.number().nullable().optional(),
1528
+ }),
1529
+ }),
1530
+ output: z.object({
1531
+ error: z.string().optional(),
1532
+ }),
1502
1533
  };*/
1503
1534
  import { Schema as SubmitScoreInternal } from "./api/submitScoreInternal"
1504
1535
  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": "244.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",
@@ -17,7 +19,8 @@
17
19
  "query-push": "bun run scripts/deploy-queries.ts",
18
20
  "queries:pull": "bun run scripts/pull-queries.ts",
19
21
  "queries:deploy": "bun run scripts/deploy-queries.ts",
20
- "sync": "npx supabase gen types typescript --project-id \"pfkajngbllcbdzoylrvp\" --schema public > types/database.ts"
22
+ "sync": "npx supabase gen types typescript --project-id \"pfkajngbllcbdzoylrvp\" --schema public > types/database.ts",
23
+ "sync-dev": "npx supabase gen types typescript --project-id \"gcxlgiajvbpbimrfqbwp\" --schema public > types/database.ts"
21
24
  },
22
25
  "license": "MIT",
23
26
  "dependencies": {
@@ -28,7 +31,7 @@
28
31
  "@supabase/supabase-js": "^2.45.1",
29
32
  "@types/bun": "^1.1.6",
30
33
  "@types/lodash": "^4.17.7",
31
- "@types/node": "^22.2.0",
34
+ "@types/node": "^25.6.0",
32
35
  "@types/pg": "^8.15.5",
33
36
  "@types/validator": "^13.12.2",
34
37
  "bad-words": "^4.0.0",
@@ -42,6 +45,7 @@
42
45
  "osu-classes": "^3.1.0",
43
46
  "osu-parsers": "^4.1.7",
44
47
  "osu-standard-stable": "^5.0.0",
48
+ "pako": "^2.1.0",
45
49
  "pg": "^8.18.0",
46
50
  "redis": "^5.10.0",
47
51
  "remote-cloudflare-kv": "^1.0.1",
@@ -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 *