rhythia-api 243.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.
- package/api/createBeatmap.ts +13 -7
- package/api/executeAdminOperation.ts +1291 -1030
- package/api/getChangelog.ts +46 -0
- package/api/getProfile.ts +297 -297
- package/handleApi.ts +24 -24
- package/index.ts +72 -50
- package/package.json +4 -2
- package/supabase/.temp/cli-latest +1 -0
- package/supabase/.temp/linked-project.json +1 -0
- package/types/database.ts +178 -1
- package/utils/beatmapHash.ts +239 -336
- package/utils/moderation.ts +101 -0
- package/utils/requestUtils.ts +2 -2
- package/utils/star-calc/formatSingle.ts +107 -0
- package/utils/star-calc/rhmParser.ts +214 -0
- package/worker.ts +197 -195
- package/.env +0 -1
package/utils/beatmapHash.ts
CHANGED
|
@@ -1,336 +1,239 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
|
-
import { SSPMParser, type SSPMParsedMap } from "./star-calc/sspmParser.ts";
|
|
3
|
-
import { V1SSPMParser, type SSPMMap } from "./star-calc/sspmv1Parser.ts";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
const
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
return
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
return;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
insertionSort(items, start, partitionSize);
|
|
244
|
-
return;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
if (depthLimit === 0) {
|
|
248
|
-
heapSort(items, start, partitionSize);
|
|
249
|
-
return;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
depthLimit--;
|
|
253
|
-
const pivot = pickPivotAndPartition(items, start, partitionSize);
|
|
254
|
-
introSort(
|
|
255
|
-
items,
|
|
256
|
-
pivot + 1,
|
|
257
|
-
start + partitionSize - (pivot + 1),
|
|
258
|
-
depthLimit
|
|
259
|
-
);
|
|
260
|
-
partitionSize = pivot - start;
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
function sortByPositionDotNet(markers: Marker[]) {
|
|
265
|
-
const result = [...markers];
|
|
266
|
-
|
|
267
|
-
if (result.length > 1) {
|
|
268
|
-
const depthLimit = 2 * (Math.floor(Math.log2(result.length)) + 1);
|
|
269
|
-
introSort(result, 0, result.length, depthLimit);
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
return result;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
export function computeBeatmapHash(map: SSPMParsedMap) {
|
|
276
|
-
const noteType = Math.max(
|
|
277
|
-
map.markerDefinitions.findIndex((x) => x.id === "ssp_note"),
|
|
278
|
-
0
|
|
279
|
-
);
|
|
280
|
-
const customDifficultyName =
|
|
281
|
-
map.customData.fields.find((x) => x.id === "difficulty_name")?.value ?? "";
|
|
282
|
-
|
|
283
|
-
const hashString =
|
|
284
|
-
map.strings.mapName +
|
|
285
|
-
map.strings.mapName +
|
|
286
|
-
map.strings.mappers.join(",") +
|
|
287
|
-
map.metadata.lastMarkerPos +
|
|
288
|
-
sortByPositionDotNet(map.markers.filter((x) => x.type === noteType))
|
|
289
|
-
.map(
|
|
290
|
-
(x) =>
|
|
291
|
-
x.position +
|
|
292
|
-
formatSingle(x.data.position.x) +
|
|
293
|
-
formatSingle(x.data.position.y)
|
|
294
|
-
)
|
|
295
|
-
.join(",") +
|
|
296
|
-
map.metadata.difficulty +
|
|
297
|
-
customDifficultyName;
|
|
298
|
-
|
|
299
|
-
return createHash("sha256").update(hashString, "utf16le").digest("hex");
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
export function computeV1BeatmapHash(map: SSPMMap) {
|
|
303
|
-
const hashString =
|
|
304
|
-
map.name +
|
|
305
|
-
map.name +
|
|
306
|
-
map.creator
|
|
307
|
-
.split(/[&,]/)
|
|
308
|
-
.map((x) => x.trim())
|
|
309
|
-
.join(",") +
|
|
310
|
-
map.lastNotePosition +
|
|
311
|
-
map.notes
|
|
312
|
-
.map(
|
|
313
|
-
(x) =>
|
|
314
|
-
x.position +
|
|
315
|
-
formatSingle(x.x) +
|
|
316
|
-
formatSingle(x.y)
|
|
317
|
-
)
|
|
318
|
-
.join(",") +
|
|
319
|
-
map.difficulty;
|
|
320
|
-
|
|
321
|
-
return createHash("sha256").update(hashString, "utf16le").digest("hex");
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
export function computeBeatmapHashFromBytes(bytes: Buffer) {
|
|
325
|
-
const version = bytes.readUInt16LE(4);
|
|
326
|
-
|
|
327
|
-
if (version === 1) {
|
|
328
|
-
return computeV1BeatmapHash(new V1SSPMParser(bytes).parse());
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
if (version === 2) {
|
|
332
|
-
return computeBeatmapHash(new SSPMParser(bytes).parse());
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
throw new Error(`Invalid SSPM version: ${version}`);
|
|
336
|
-
}
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { SSPMParser, type SSPMParsedMap } from "./star-calc/sspmParser.ts";
|
|
3
|
+
import { V1SSPMParser, type SSPMMap } from "./star-calc/sspmv1Parser.ts";
|
|
4
|
+
import { formatSingle } from "./star-calc/formatSingle.ts";
|
|
5
|
+
import {
|
|
6
|
+
computeRhythiaMapHash,
|
|
7
|
+
isRHM,
|
|
8
|
+
parseRHM,
|
|
9
|
+
} from "./star-calc/rhmParser.ts";
|
|
10
|
+
|
|
11
|
+
const IntrosortSizeThreshold = 16;
|
|
12
|
+
|
|
13
|
+
type Marker = SSPMParsedMap["markers"][number];
|
|
14
|
+
|
|
15
|
+
function compareMarkerPosition(a: Marker, b: Marker) {
|
|
16
|
+
return a.position - b.position;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function swap(items: Marker[], left: number, right: number) {
|
|
20
|
+
const temp = items[left]!;
|
|
21
|
+
items[left] = items[right]!;
|
|
22
|
+
items[right] = temp;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function swapIfGreater(items: Marker[], left: number, right: number) {
|
|
26
|
+
if (
|
|
27
|
+
left !== right &&
|
|
28
|
+
compareMarkerPosition(items[left]!, items[right]!) > 0
|
|
29
|
+
) {
|
|
30
|
+
swap(items, left, right);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function insertionSort(items: Marker[], start: number, length: number) {
|
|
35
|
+
for (let i = start; i < start + length - 1; i++) {
|
|
36
|
+
const value = items[i + 1]!;
|
|
37
|
+
let j = i;
|
|
38
|
+
|
|
39
|
+
while (j >= start && compareMarkerPosition(value, items[j]!) < 0) {
|
|
40
|
+
items[j + 1] = items[j]!;
|
|
41
|
+
j--;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
items[j + 1] = value;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function downHeap(
|
|
49
|
+
items: Marker[],
|
|
50
|
+
start: number,
|
|
51
|
+
index: number,
|
|
52
|
+
heapSize: number
|
|
53
|
+
) {
|
|
54
|
+
const value = items[start + index - 1]!;
|
|
55
|
+
let child: number;
|
|
56
|
+
|
|
57
|
+
while ((child = 2 * index) <= heapSize) {
|
|
58
|
+
if (
|
|
59
|
+
child < heapSize &&
|
|
60
|
+
compareMarkerPosition(items[start + child - 1]!, items[start + child]!) <
|
|
61
|
+
0
|
|
62
|
+
) {
|
|
63
|
+
child++;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (compareMarkerPosition(value, items[start + child - 1]!) >= 0) {
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
items[start + index - 1] = items[start + child - 1]!;
|
|
71
|
+
index = child;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
items[start + index - 1] = value;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function heapSort(items: Marker[], start: number, length: number) {
|
|
78
|
+
for (let i = Math.floor(length / 2); i >= 1; i--) {
|
|
79
|
+
downHeap(items, start, i, length);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
for (let i = length; i > 1; i--) {
|
|
83
|
+
swap(items, start, start + i - 1);
|
|
84
|
+
downHeap(items, start, 1, i - 1);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function pickPivotAndPartition(items: Marker[], start: number, length: number) {
|
|
89
|
+
const high = start + length - 1;
|
|
90
|
+
const middle = start + ((high - start) >> 1);
|
|
91
|
+
|
|
92
|
+
swapIfGreater(items, start, middle);
|
|
93
|
+
swapIfGreater(items, start, high);
|
|
94
|
+
swapIfGreater(items, middle, high);
|
|
95
|
+
|
|
96
|
+
const pivot = items[middle]!;
|
|
97
|
+
swap(items, middle, high - 1);
|
|
98
|
+
|
|
99
|
+
let left = start;
|
|
100
|
+
let right = high - 1;
|
|
101
|
+
|
|
102
|
+
while (left < right) {
|
|
103
|
+
while (compareMarkerPosition(items[++left]!, pivot) < 0) {}
|
|
104
|
+
while (compareMarkerPosition(pivot, items[--right]!) < 0) {}
|
|
105
|
+
|
|
106
|
+
if (left >= right) {
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
swap(items, left, right);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (left !== high - 1) {
|
|
114
|
+
swap(items, left, high - 1);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return left;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function introSort(
|
|
121
|
+
items: Marker[],
|
|
122
|
+
start: number,
|
|
123
|
+
length: number,
|
|
124
|
+
depthLimit: number
|
|
125
|
+
) {
|
|
126
|
+
let partitionSize = length;
|
|
127
|
+
|
|
128
|
+
while (partitionSize > 1) {
|
|
129
|
+
if (partitionSize <= IntrosortSizeThreshold) {
|
|
130
|
+
if (partitionSize === 2) {
|
|
131
|
+
swapIfGreater(items, start, start + 1);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (partitionSize === 3) {
|
|
136
|
+
swapIfGreater(items, start, start + 1);
|
|
137
|
+
swapIfGreater(items, start, start + 2);
|
|
138
|
+
swapIfGreater(items, start + 1, start + 2);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
insertionSort(items, start, partitionSize);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (depthLimit === 0) {
|
|
147
|
+
heapSort(items, start, partitionSize);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
depthLimit--;
|
|
152
|
+
const pivot = pickPivotAndPartition(items, start, partitionSize);
|
|
153
|
+
introSort(
|
|
154
|
+
items,
|
|
155
|
+
pivot + 1,
|
|
156
|
+
start + partitionSize - (pivot + 1),
|
|
157
|
+
depthLimit
|
|
158
|
+
);
|
|
159
|
+
partitionSize = pivot - start;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function sortByPositionDotNet(markers: Marker[]) {
|
|
164
|
+
const result = [...markers];
|
|
165
|
+
|
|
166
|
+
if (result.length > 1) {
|
|
167
|
+
const depthLimit = 2 * (Math.floor(Math.log2(result.length)) + 1);
|
|
168
|
+
introSort(result, 0, result.length, depthLimit);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return result;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function computeBeatmapHash(map: SSPMParsedMap) {
|
|
175
|
+
const noteType = Math.max(
|
|
176
|
+
map.markerDefinitions.findIndex((x) => x.id === "ssp_note"),
|
|
177
|
+
0
|
|
178
|
+
);
|
|
179
|
+
const customDifficultyName =
|
|
180
|
+
map.customData.fields.find((x) => x.id === "difficulty_name")?.value ?? "";
|
|
181
|
+
|
|
182
|
+
const hashString =
|
|
183
|
+
map.strings.mapName +
|
|
184
|
+
map.strings.mapName +
|
|
185
|
+
map.strings.mappers.join(",") +
|
|
186
|
+
map.metadata.lastMarkerPos +
|
|
187
|
+
sortByPositionDotNet(map.markers.filter((x) => x.type === noteType))
|
|
188
|
+
.map(
|
|
189
|
+
(x) =>
|
|
190
|
+
x.position +
|
|
191
|
+
formatSingle(x.data.position.x) +
|
|
192
|
+
formatSingle(x.data.position.y)
|
|
193
|
+
)
|
|
194
|
+
.join(",") +
|
|
195
|
+
map.metadata.difficulty +
|
|
196
|
+
customDifficultyName;
|
|
197
|
+
|
|
198
|
+
return createHash("sha256").update(hashString, "utf16le").digest("hex");
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export function computeV1BeatmapHash(map: SSPMMap) {
|
|
202
|
+
const hashString =
|
|
203
|
+
map.name +
|
|
204
|
+
map.name +
|
|
205
|
+
map.creator
|
|
206
|
+
.split(/[&,]/)
|
|
207
|
+
.map((x) => x.trim())
|
|
208
|
+
.join(",") +
|
|
209
|
+
map.lastNotePosition +
|
|
210
|
+
map.notes
|
|
211
|
+
.map(
|
|
212
|
+
(x) =>
|
|
213
|
+
x.position +
|
|
214
|
+
formatSingle(x.x) +
|
|
215
|
+
formatSingle(x.y)
|
|
216
|
+
)
|
|
217
|
+
.join(",") +
|
|
218
|
+
map.difficulty;
|
|
219
|
+
|
|
220
|
+
return createHash("sha256").update(hashString, "utf16le").digest("hex");
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export function computeBeatmapHashFromBytes(bytes: Buffer) {
|
|
224
|
+
if (isRHM(bytes)) {
|
|
225
|
+
return computeRhythiaMapHash(parseRHM(bytes).map);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const version = bytes.readUInt16LE(4);
|
|
229
|
+
|
|
230
|
+
if (version === 1) {
|
|
231
|
+
return computeV1BeatmapHash(new V1SSPMParser(bytes).parse());
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (version === 2) {
|
|
235
|
+
return computeBeatmapHash(new SSPMParser(bytes).parse());
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
throw new Error(`Invalid SSPM version: ${version}`);
|
|
239
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type { Json } from "../types/database";
|
|
2
|
+
import { supabase } from "./supabase";
|
|
3
|
+
|
|
4
|
+
export type ViolationType = "excluded" | "silenced" | "restricted";
|
|
5
|
+
|
|
6
|
+
export type AddViolationInput = {
|
|
7
|
+
profileId: number;
|
|
8
|
+
type: ViolationType;
|
|
9
|
+
reason: string;
|
|
10
|
+
expiresAt?: string | null;
|
|
11
|
+
moderatedBy: number;
|
|
12
|
+
metadata?: Record<string, unknown>;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type RevokeViolationInput = {
|
|
16
|
+
profileId: number;
|
|
17
|
+
type: ViolationType;
|
|
18
|
+
revokedBy: number;
|
|
19
|
+
reason?: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type ModerationState = {
|
|
23
|
+
excluded: boolean;
|
|
24
|
+
silenced: boolean;
|
|
25
|
+
restricted: boolean;
|
|
26
|
+
activeViolations: Array<{
|
|
27
|
+
id: number;
|
|
28
|
+
type: ViolationType;
|
|
29
|
+
reason: string;
|
|
30
|
+
createdAt: string;
|
|
31
|
+
expiresAt: string | null;
|
|
32
|
+
moderatedBy: number | null;
|
|
33
|
+
}>;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
type RpcActiveViolation = ModerationState["activeViolations"][number] & {
|
|
37
|
+
violation_type?: ViolationType;
|
|
38
|
+
created_at?: string;
|
|
39
|
+
expires_at?: string | null;
|
|
40
|
+
moderated_by?: number | null;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export async function addViolation(input: AddViolationInput) {
|
|
44
|
+
return supabase.rpc("add_user_violation", {
|
|
45
|
+
_profile_id: input.profileId,
|
|
46
|
+
_violation_type: input.type,
|
|
47
|
+
_reason: input.reason,
|
|
48
|
+
_expires_at: input.expiresAt ?? undefined,
|
|
49
|
+
_moderated_by: input.moderatedBy,
|
|
50
|
+
_metadata: (input.metadata ?? {}) as Json,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function revokeViolation(input: RevokeViolationInput) {
|
|
55
|
+
return supabase.rpc("revoke_user_violation", {
|
|
56
|
+
_profile_id: input.profileId,
|
|
57
|
+
_violation_type: input.type,
|
|
58
|
+
_revoked_by: input.revokedBy,
|
|
59
|
+
_revoke_reason: input.reason,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function getUserViolationState(profileId: number) {
|
|
64
|
+
return supabase.rpc("get_user_violation_state", {
|
|
65
|
+
_profile_id: profileId,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export async function getModerationState(profileId: number) {
|
|
70
|
+
const { data, error } = await getUserViolationState(profileId);
|
|
71
|
+
|
|
72
|
+
if (error) {
|
|
73
|
+
return { data: null, error };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const state = data?.[0];
|
|
77
|
+
|
|
78
|
+
if (!state) {
|
|
79
|
+
return { data: null, error: { message: "Moderation state not found" } };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const activeViolations = (state.active_violations ??
|
|
83
|
+
[]) as RpcActiveViolation[];
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
data: {
|
|
87
|
+
excluded: state.excluded,
|
|
88
|
+
silenced: state.silenced,
|
|
89
|
+
restricted: state.restricted,
|
|
90
|
+
activeViolations: activeViolations.map((violation) => ({
|
|
91
|
+
id: violation.id,
|
|
92
|
+
type: violation.type ?? violation.violation_type!,
|
|
93
|
+
reason: violation.reason,
|
|
94
|
+
createdAt: violation.createdAt ?? violation.created_at!,
|
|
95
|
+
expiresAt: violation.expiresAt ?? violation.expires_at ?? null,
|
|
96
|
+
moderatedBy: violation.moderatedBy ?? violation.moderated_by ?? null,
|
|
97
|
+
})),
|
|
98
|
+
} satisfies ModerationState,
|
|
99
|
+
error: null,
|
|
100
|
+
};
|
|
101
|
+
}
|