Compare commits

...

45 Commits

Author SHA1 Message Date
73b6c85171 ow my formatting
All checks were successful
Test, Build, Deploy / Test, Build, and Deploy (push) Successful in 1m35s
Test, Build, Deploy / Create Sentry Release (push) Successful in 6s
2026-05-11 00:52:14 -04:00
037eb7fcac Possibly fix appeals? 2026-05-11 00:51:51 -04:00
abc1389dbb Bump dependencies 2026-05-11 00:51:40 -04:00
cd566248fd Bump node version 2026-05-11 00:51:09 -04:00
3d7e499ec1 Create data requests table 2026-05-06 03:18:57 -04:00
40dd0b5a5c Remove data transfer code
All checks were successful
Test, Build, Deploy / Test, Build, and Deploy (push) Successful in 1m25s
Test, Build, Deploy / Create Sentry Release (push) Successful in 7s
2026-04-29 02:05:08 -04:00
229398e401 Disable button while event is submitting
All checks were successful
Test, Build, Deploy / Test, Build, and Deploy (push) Successful in 58s
Test, Build, Deploy / Create Sentry Release (push) Successful in 6s
2026-04-17 01:47:42 -04:00
a9863f5680 Remove unneeded unix time param
All checks were successful
Test, Build, Deploy / Test, Build, and Deploy (push) Successful in 55s
Test, Build, Deploy / Create Sentry Release (push) Successful in 9s
2026-04-16 04:08:20 -04:00
2e76bd9f28 Make buttons hide again when performed_at is not null
All checks were successful
Test, Build, Deploy / Test, Build, and Deploy (push) Successful in 1m7s
Test, Build, Deploy / Create Sentry Release (push) Successful in 7s
2026-04-16 03:52:40 -04:00
171240bc7d Add db setup migration
All checks were successful
Test, Build, Deploy / Test, Build, and Deploy (push) Successful in 1m1s
Test, Build, Deploy / Create Sentry Release (push) Successful in 7s
2026-04-15 02:45:39 -04:00
ffce17d7aa Add more stuff to gitignore 2026-04-15 02:43:38 -04:00
06fdfe9d10 Log appeal denials
All checks were successful
Test, Build, Deploy / Test, Build, and Deploy (push) Successful in 1m8s
Test, Build, Deploy / Create Sentry Release (push) Successful in 7s
2026-04-14 22:09:43 -04:00
ba643bf986 Apparently these are causing problems
All checks were successful
Test, Build, Deploy / Test, Build, and Deploy (push) Successful in 1m1s
Test, Build, Deploy / Create Sentry Release (push) Successful in 6s
2026-04-14 04:16:45 -04:00
12f91dca7d Move input button styles hook to top level
All checks were successful
Test, Build, Deploy / Test, Build, and Deploy (push) Successful in 1m2s
Test, Build, Deploy / Create Sentry Release (push) Successful in 5s
2026-04-14 03:49:06 -04:00
b6de1aa462 Change order of state changes
All checks were successful
Test, Build, Deploy / Test, Build, and Deploy (push) Successful in 1m2s
Test, Build, Deploy / Create Sentry Release (push) Successful in 5s
2026-04-14 03:44:06 -04:00
b65b62dac5 Upload functions sourcemaps from CI 2026-04-14 03:36:42 -04:00
0ec5399726 Accept multiple input files again
All checks were successful
Test, Build, Deploy / Test, Build, and Deploy (push) Successful in 1m1s
Test, Build, Deploy / Create Sentry Release (push) Successful in 5s
2026-04-14 03:25:09 -04:00
f0c4e178aa Actually hide border this time
All checks were successful
Test, Build, Deploy / Test, Build, and Deploy (push) Successful in 1m0s
Test, Build, Deploy / Create Sentry Release (push) Successful in 6s
2026-04-14 03:21:11 -04:00
6ad4fa0514 Remove outline from input
All checks were successful
Test, Build, Deploy / Test, Build, and Deploy (push) Successful in 1m1s
Test, Build, Deploy / Create Sentry Release (push) Successful in 6s
2026-04-14 03:16:16 -04:00
42275fcb0f Remove old button 2026-04-14 03:15:08 -04:00
0854d72449 Try new way of fixing this nonsense
All checks were successful
Test, Build, Deploy / Test, Build, and Deploy (push) Successful in 1m3s
Test, Build, Deploy / Create Sentry Release (push) Successful in 6s
2026-04-14 03:11:32 -04:00
cb0be09c0d Use react state to keep track of file names
All checks were successful
Test, Build, Deploy / Test, Build, and Deploy (push) Successful in 1m2s
Test, Build, Deploy / Create Sentry Release (push) Successful in 6s
2026-04-14 02:07:15 -04:00
16ecab6881 Fix safari annoyingness
All checks were successful
Test, Build, Deploy / Test, Build, and Deploy (push) Successful in 1m2s
Test, Build, Deploy / Create Sentry Release (push) Successful in 6s
2026-04-14 01:48:37 -04:00
7d5ec1183c Bump node version
All checks were successful
Test, Build, Deploy / Test, Build, and Deploy (push) Successful in 1m20s
Test, Build, Deploy / Create Sentry Release (push) Successful in 5s
2026-04-14 00:23:24 -04:00
96d221be2a Fix this one too 2026-04-14 00:20:57 -04:00
f5e3e3cca6 Add messaging service publish method
All checks were successful
Test, Build, Deploy / Test, Build, and Deploy (push) Successful in 59s
Test, Build, Deploy / Create Sentry Release (push) Successful in 5s
2026-04-13 03:04:02 -04:00
2d9f03c394 Fix weird token element null issue
All checks were successful
Test, Build, Deploy / Test, Build, and Deploy (push) Successful in 1m0s
Test, Build, Deploy / Create Sentry Release (push) Successful in 6s
2026-04-13 02:46:17 -04:00
c51b29ce57 Upload sourcemaps to pages 2026-04-13 02:45:58 -04:00
546842c4dd These need to still be parsed
All checks were successful
Test, Build, Deploy / Test, Build, and Deploy (push) Successful in 58s
Test, Build, Deploy / Create Sentry Release (push) Successful in 6s
2026-04-12 01:34:32 -04:00
1f2a8770a1 Set keys on list elements 2026-04-12 01:30:18 -04:00
4b15c65092 Spit out as text in error console 2026-04-12 01:13:59 -04:00
b671aefd6e Possibly fix reports queue not working
All checks were successful
Test, Build, Deploy / Test, Build, and Deploy (push) Successful in 1m2s
Test, Build, Deploy / Create Sentry Release (push) Successful in 7s
2026-04-12 01:03:39 -04:00
f32a7912b4 Fix media signing
All checks were successful
Test, Build, Deploy / Test, Build, and Deploy (push) Successful in 56s
Test, Build, Deploy / Create Sentry Release (push) Successful in 6s
2026-04-11 04:59:05 -04:00
da0ce2b188 Rest of it
All checks were successful
Test, Build, Deploy / Test, Build, and Deploy (push) Successful in 55s
Test, Build, Deploy / Create Sentry Release (push) Successful in 5s
2026-04-11 04:54:39 -04:00
6da49d191a Fix missing attachments 2026-04-11 04:53:58 -04:00
5457898ff9 Get almost everything else
All checks were successful
Test, Build, Deploy / Test, Build, and Deploy (push) Successful in 55s
Test, Build, Deploy / Create Sentry Release (push) Successful in 6s
2026-04-11 04:49:02 -04:00
fe206e2fbd Commit the right files this time...
All checks were successful
Test, Build, Deploy / Test, Build, and Deploy (push) Successful in 53s
Test, Build, Deploy / Create Sentry Release (push) Successful in 5s
2026-04-11 04:45:01 -04:00
02cac814da Use internally generated types for jsonobject and raw
Some checks failed
Test, Build, Deploy / Test, Build, and Deploy (push) Failing after 50s
Test, Build, Deploy / Create Sentry Release (push) Has been skipped
2026-04-11 04:42:39 -04:00
f184389ffd Migrate mod queue list endpoint
Some checks failed
Test, Build, Deploy / Test, Build, and Deploy (push) Failing after 50s
Test, Build, Deploy / Create Sentry Release (push) Has been skipped
2026-04-11 04:36:25 -04:00
5c17f87f89 Migrate the rest of the easy stuff
All checks were successful
Test, Build, Deploy / Test, Build, and Deploy (push) Successful in 58s
Test, Build, Deploy / Create Sentry Release (push) Successful in 6s
2026-04-11 04:32:09 -04:00
48631e32be Finish rest of game appeal stuff 2026-04-11 04:29:25 -04:00
b60f211d7b Migrate report recall endpoint 2026-04-11 04:29:15 -04:00
1a891e5898 Even more migrations 2026-04-11 04:29:04 -04:00
7b72f815b0 More stuff to migrate 2026-04-11 04:28:45 -04:00
4860288d11 Oops need schema update 2026-04-11 04:28:11 -04:00
53 changed files with 1094 additions and 1234 deletions

View File

@@ -56,7 +56,7 @@ jobs:
}' }'
- name: Deploy - name: Deploy
run: wrangler pages deploy public --project-name $CLOUDFLARE_PROJECT_NAME run: wrangler pages deploy public --upload-source-maps --project-name $CLOUDFLARE_PROJECT_NAME
Sentry-Release: Sentry-Release:
name: Create Sentry Release name: Create Sentry Release

2
.gitignore vendored
View File

@@ -37,5 +37,7 @@ public/build
# Wrangler data # Wrangler data
.wrangler .wrangler
wrangler.jsonc
wrangler.toml
/generated/prisma /generated/prisma

View File

@@ -1 +1 @@
v24.14.0 v24.15.0

View File

@@ -40,11 +40,18 @@ export async function loader({ context }: { context: RequestContext }) {
!Boolean(disabled) && !Boolean(disabled) &&
!Boolean(await dataKV.get(`blockedappeal_${currentUser.id}`)) && !Boolean(await dataKV.get(`blockedappeal_${currentUser.id}`)) &&
!Boolean( !Boolean(
await context.env.D1.prepare( await context.data.prisma.appeal.findFirst({
"SELECT * FROM appeals WHERE approved IS NULL AND json_extract(user, '$.id') = ? LIMIT 1;", select: {
) id: true,
.bind(currentUser.id) },
.first(), where: {
approved: null,
user: {
path: "id",
equals: currentUser.id,
},
},
}),
), ),
can_toggle: can_toggle:
currentUser.permissions & (1 << 0) || currentUser.permissions & (1 << 11), currentUser.permissions & (1 << 0) || currentUser.permissions & (1 << 11),

View File

@@ -44,6 +44,7 @@ export default function () {
const [eventType, setEventType] = useState(""); const [eventType, setEventType] = useState("");
const [riddleAnswer, setRiddleAnswer] = useState(""); const [riddleAnswer, setRiddleAnswer] = useState("");
const [submitSuccess, setSubmitSuccess] = useState(false); const [submitSuccess, setSubmitSuccess] = useState(false);
const [disableSubmit, setDisableSubmit] = useState(false);
useEffect(() => { useEffect(() => {
setDatePickerMin(`${new Date().toISOString().split("T").at(0)}`); setDatePickerMin(`${new Date().toISOString().split("T").at(0)}`);
@@ -53,6 +54,7 @@ export default function () {
}, []); }, []);
async function submit() { async function submit() {
setDisableSubmit(true);
let eventResp: Response; let eventResp: Response;
try { try {
@@ -69,6 +71,7 @@ export default function () {
method: "POST", method: "POST",
}); });
} catch { } catch {
setDisableSubmit(false);
toast({ toast({
description: "Please check your internet and try again", description: "Please check your internet and try again",
isClosable: true, isClosable: true,
@@ -86,6 +89,7 @@ export default function () {
errorMessage = ((await eventResp.json()) as { error: string }).error; errorMessage = ((await eventResp.json()) as { error: string }).error;
} catch {} } catch {}
setDisableSubmit(false);
toast({ toast({
description: errorMessage, description: errorMessage,
isClosable: true, isClosable: true,
@@ -150,7 +154,11 @@ export default function () {
onChange={(e) => setRiddleAnswer(e.target.value)} onChange={(e) => setRiddleAnswer(e.target.value)}
placeholder="Riddle answer" placeholder="Riddle answer"
/> />
<Button mt="16px" onClick={async () => await submit()}> <Button
disabled={disableSubmit}
mt="16px"
onClick={async () => await submit()}
>
Book Book
</Button> </Button>
</Container> </Container>

View File

@@ -1,42 +0,0 @@
import { Button, Container, Heading, Text } from "@chakra-ui/react";
export default function () {
return (
<Container maxW="container.md">
<Heading size="lg">Transfer your Save Data</Heading>
<br />
<br />
<Text>Lost your account? Want to shake off a stalker?</Text>
<br />
<br />
<Text size="lg">We can help!</Text>
<br />
<br />
<Text>Some information you should know:</Text>
<br />
<Text>
We might require your .ROBLOSECURITY cookie, depending on your
circumstances. This is because Roblox does not allow terminated accounts
to utilize OAuth. Normally this would be a very bad idea, and we don't
like doing this either, but it may be the only option. Security is also
less of a concern for terminated accounts as they are blocked from
accessing almost all Roblox API endpoints (the exceptions being login,
logout, and creating support tickets - and only the logout endpoint
doesn't require completing a captcha). If you are concerned about your
account's security, we suggest logging in to your terminated account in
a private/incognito window, copying the .ROBLOSECURITY cookie from
there, and logging out once we have verified your old account (which
normally only takes a few seconds). The ultra paranoid may also consider
resetting their password. If you are not convinced or still have
questions, join our Discord server (link is on the about page) and open
a ticket with ModMail for us to verify you manually (no cookie
required).
</Text>
<br />
<br />
<Button as="a" href="/data-transfer/start" colorScheme="blue">
Start my Transfer
</Button>
</Container>
);
}

View File

@@ -1,10 +0,0 @@
import Success from "../../components/Success.js";
export default function () {
return (
<Success
heading="Data Transfer Submitted"
message="Your request is now being processed; this normally takes 1-2 weeks."
/>
);
}

View File

@@ -1,34 +0,0 @@
import { Button, Card, Container, Heading, VStack } from "@chakra-ui/react";
import { useLoaderData } from "@remix-run/react";
export async function loader({ context }: { context: RequestContext }) {
const { host, protocol } = new URL(context.request.url);
return { client_id: context.env.ROBLOX_OAUTH_CLIENT_ID, host, protocol };
}
export default function () {
const loaderData = useLoaderData<typeof loader>();
return (
<Container pt="16vh">
<Card borderRadius="32px" p="4vh">
<VStack alignContent="center" gap="2vh">
<Heading>Verify your new Roblox account</Heading>
<br />
<Button
as="a"
borderRadius="24px"
colorScheme="blue"
href={`https://apis.roblox.com/oauth/v1/authorize?client_id=${
loaderData.client_id
}&redirect_uri=${encodeURIComponent(
`${loaderData.protocol}//${loaderData.host}/api/data-transfers/verify`,
)}&response_type=code&scope=openid%20profile`}
>
Verify
</Button>
</VStack>
</Card>
</Container>
);
}

View File

@@ -1,81 +0,0 @@
import {
Button,
Container,
Heading,
HStack,
Radio,
RadioGroup,
Text,
Textarea,
useToast,
} from "@chakra-ui/react";
import { useState } from "react";
export default function () {
const [showCookieBox, setShowCookieBox] = useState(false);
const [loading, setLoading] = useState(false);
return (
<Container maxW="container.md">
<Heading pt="36px">Let's get started</Heading>
<Text pt="128px">Is your old Roblox account banned?</Text>
<RadioGroup onChange={(val) => setShowCookieBox(JSON.parse(val))}>
<HStack>
<Radio value="false">No</Radio>
<Radio value="true">Yes</Radio>
</HStack>
</RadioGroup>
<Textarea
id="cookie-box"
placeholder="Paste your .ROBLOSECURITY cookie here"
mt="16px"
style={{ display: showCookieBox ? "initial" : "none" }}
/>
<Button
colorScheme="blue"
isLoading={loading}
loadingText="Processing..."
mt="16px"
onClick={async () => {
setLoading(true);
const createTransferReq = await fetch("/api/data-transfers/create", {
body: JSON.stringify({
can_access: !showCookieBox,
cookie: (
document.getElementById("cookie-box") as HTMLInputElement
).value,
}),
headers: {
"content-type": "application/json",
},
method: "POST",
});
if (!createTransferReq.ok) {
setLoading(false);
useToast()({
description: (
(await createTransferReq.json()) as { error: string }
).error,
isClosable: true,
status: "error",
title: "Failed to create transfer request",
});
return;
}
location.assign(
((await createTransferReq.json()) as { url: string }).url,
);
}}
>
Continue
</Button>
<br />
<Text pt="16px">
If you cannot login at all, please visit the support page and join our
server.
</Text>
</Container>
);
}

View File

@@ -271,7 +271,7 @@ export default function () {
// Technically this won't be the same as the time in the db, but that doesn't matter since this is just to hide the button // Technically this won't be the same as the time in the db, but that doesn't matter since this is just to hide the button
newEventData[eventData.findIndex((e) => e.id === eventId)].performed_at = newEventData[eventData.findIndex((e) => e.id === eventId)].performed_at =
Date.now(); new Date().toISOString();
setEventData([...newEventData]); setEventData([...newEventData]);
setSelectedEvent(""); setSelectedEvent("");
@@ -313,7 +313,8 @@ export default function () {
const newEventData = eventData; const newEventData = eventData;
newEventData[eventData.findIndex((e) => e.id === eventId)].performed_at = 0; newEventData[eventData.findIndex((e) => e.id === eventId)].performed_at =
new Date(0).toISOString();
setEventData([...newEventData]); setEventData([...newEventData]);
setSelectedEvent(""); setSelectedEvent("");
setDisableClicks(false); setDisableClicks(false);
@@ -664,9 +665,7 @@ export default function () {
</Button> </Button>
</> </>
) : null} ) : null}
{can_approve && {can_approve && !event.pending && !event.performed_at ? (
!event.pending &&
typeof event.performed_at !== "number" ? (
<> <>
<Button <Button
colorScheme="blue" colorScheme="blue"

View File

@@ -268,6 +268,7 @@ export default function () {
element: ( element: (
<AppealCard <AppealCard
{...(entry as AppealCardProps & { port?: MessagePort })} {...(entry as AppealCardProps & { port?: MessagePort })}
key={`appeal_${entry.id}`}
port={messageChannel.current?.port2} port={messageChannel.current?.port2}
/> />
), ),
@@ -281,6 +282,7 @@ export default function () {
element: ( element: (
<GameAppealCard <GameAppealCard
{...(entry as GameAppealProps & { port?: MessagePort })} {...(entry as GameAppealProps & { port?: MessagePort })}
key={`gma_${entry.id}`}
port={messageChannel.current?.port2} port={messageChannel.current?.port2}
/> />
), ),
@@ -294,6 +296,7 @@ export default function () {
element: ( element: (
<InactivityNoticeCard <InactivityNoticeCard
{...(entry as InactivityNoticeProps & { port?: MessagePort })} {...(entry as InactivityNoticeProps & { port?: MessagePort })}
key={`inactivity_${entry.id}`}
port={messageChannel.current?.port2} port={messageChannel.current?.port2}
/> />
), ),
@@ -307,6 +310,7 @@ export default function () {
element: ( element: (
<ReportCard <ReportCard
{...(entry as ReportCardProps & { port?: MessagePort })} {...(entry as ReportCardProps & { port?: MessagePort })}
key={`report_${entry.id}`}
port={messageChannel.current?.port2} port={messageChannel.current?.port2}
/> />
), ),

View File

@@ -14,6 +14,7 @@ import {
Stack, Stack,
Text, Text,
Textarea, Textarea,
useMultiStyleConfig,
useToast, useToast,
} from "@chakra-ui/react"; } from "@chakra-ui/react";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
@@ -50,6 +51,9 @@ export default function () {
const toast = useToast(); const toast = useToast();
const [uploading, setUploading] = useState(false); const [uploading, setUploading] = useState(false);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const inputSelectorProps = useMultiStyleConfig("Button", {
colorScheme: "blue",
});
const fileTypes: { [k: string]: string } = { const fileTypes: { [k: string]: string } = {
avif: "image/avif", avif: "image/avif",
gif: "image/gif", gif: "image/gif",
@@ -112,9 +116,9 @@ export default function () {
if (!logged_in) { if (!logged_in) {
const tokenElem = document const tokenElem = document
.getElementsByName("cf-turnstile-response") .getElementsByName("cf-turnstile-response")
.item(0) as HTMLInputElement; .item(0) as HTMLInputElement | null;
if (!tokenElem.value) { if (!tokenElem?.value) {
setLoading(false); setLoading(false);
return toast({ return toast({
description: "Please complete the captcha and try again", description: "Please complete the captcha and try again",
@@ -250,8 +254,8 @@ export default function () {
method: "POST", method: "POST",
}); });
setShowSuccess(true);
setLoading(false); setLoading(false);
setShowSuccess(true);
} }
useEffect(() => { useEffect(() => {
@@ -304,14 +308,19 @@ export default function () {
<br /> <br />
<FormControl isRequired> <FormControl isRequired>
<FormLabel>Your Evidence (Max size per file: 512MB)</FormLabel> <FormLabel>Your Evidence (Max size per file: 512MB)</FormLabel>
<Button <Input
colorScheme="blue" border="none"
mr="8px" id="evidence"
onClick={() => document.getElementById("evidence")?.click()} sx={{
> "::file-selector-button": {
Select File border: "none",
</Button> outline: "none",
<input id="evidence" multiple type="file" /> ...inputSelectorProps,
},
}}
multiple={true}
type="file"
/>
</FormControl> </FormControl>
<br /> <br />
<FormControl> <FormControl>

View File

@@ -40,13 +40,15 @@ export async function loader({ context }: { context: RequestContext }) {
status: 403, status: 403,
}); });
const { results } = await context.env.D1.prepare( return await context.data.prisma.shortLink.findMany({
"SELECT destination, path FROM short_links WHERE user = ?;", select: {
) destination: true,
.bind(userId) path: true,
.all(); },
where: {
return results as Record<string, string>[]; user: userId,
},
});
} }
export default function () { export default function () {

View File

@@ -91,7 +91,7 @@ export default function (props: { isOpen: boolean; onClose: () => void }) {
</Thead> </Thead>
<Tbody> <Tbody>
{entries.map((entry) => ( {entries.map((entry) => (
<Tr> <Tr key={`appealban_${entry.user}`}>
<Td>{entry.user}</Td> <Td>{entry.user}</Td>
<Td>{entry.created_by}</Td> <Td>{entry.created_by}</Td>
<Td>{new Date(entry.created_at).toUTCString()}</Td> <Td>{new Date(entry.created_at).toUTCString()}</Td>

View File

@@ -173,7 +173,6 @@ export default function (props: {
)} )}
</div> </div>
<svg <svg
xmlns="http://www.w3.org/2000/svg"
width="24" width="24"
height="24" height="24"
fill="currentColor" fill="currentColor"
@@ -223,7 +222,6 @@ export default function (props: {
{data.id ? data.username : ""} {data.id ? data.username : ""}
</Text> </Text>
<svg <svg
xmlns="http://www.w3.org/2000/svg"
width="24" width="24"
height="24" height="24"
fill="currentColor" fill="currentColor"

View File

@@ -17,13 +17,7 @@ export default function ({
> >
<Flex> <Flex>
<Spacer /> <Spacer />
<svg <svg width="128" height="128" fill="currentColor" viewBox="0 0 16 16">
xmlns="http://www.w3.org/2000/svg"
width="128"
height="128"
fill="currentColor"
viewBox="0 0 16 16"
>
<path d="M2.5 8a5.5 5.5 0 0 1 8.25-4.764.5.5 0 0 0 .5-.866A6.5 6.5 0 1 0 14.5 8a.5.5 0 0 0-1 0 5.5 5.5 0 1 1-11 0z" /> <path d="M2.5 8a5.5 5.5 0 0 1 8.25-4.764.5.5 0 0 0 .5-.866A6.5 6.5 0 1 0 14.5 8a.5.5 0 0 0-1 0 5.5 5.5 0 1 1-11 0z" />
<path d="M15.354 3.354a.5.5 0 0 0-.708-.708L8 9.293 5.354 6.646a.5.5 0 1 0-.708.708l3 3a.5.5 0 0 0 .708 0l7-7z" /> <path d="M15.354 3.354a.5.5 0 0 0-.708-.708L8 9.293 5.354 6.646a.5.5 0 1 0-.708.708l3 3a.5.5 0 0 0 .708 0l7-7z" />
</svg> </svg>

View File

@@ -1,4 +1,8 @@
import { jsonError } from "../../../common.js"; import { jsonError } from "../../../common.js";
import {
Appeal,
PushNotification,
} from "../../../../generated/prisma/client.js";
export async function onRequestPost(context: RequestContext) { export async function onRequestPost(context: RequestContext) {
const { pathname } = new URL(context.request.url); const { pathname } = new URL(context.request.url);
@@ -20,22 +24,23 @@ export async function onRequestPost(context: RequestContext) {
context.data.targetId = id; context.data.targetId = id;
if (!pathname.endsWith("/ban")) { if (!pathname.endsWith("/ban")) {
const appeal: Record<string, any> | null = await context.env.D1.prepare( const appeal: Appeal | null = await context.data.prisma.appeal.findUnique({
"SELECT * FROM appeals WHERE id = ?;", where: {
) id: id,
.bind(id) },
.first(); });
if (!appeal) return jsonError("No appeal with that ID exists", 404); if (!appeal) return jsonError("No appeal with that ID exists", 404);
appeal.user = JSON.parse(appeal.user);
context.data.appeal = appeal; context.data.appeal = appeal;
const pushNotificationData = await context.env.D1.prepare( const pushNotificationData: PushNotification | null =
"SELECT token FROM push_notifications WHERE event_id = ? AND event_type = 'appeal';", await context.data.prisma.pushNotification.findUnique({
) where: {
.bind(id) event_id: id,
.first(); event_type: "appeal",
},
});
if (pushNotificationData) if (pushNotificationData)
context.data.fcm_token = pushNotificationData.token; context.data.fcm_token = pushNotificationData.token;

View File

@@ -13,11 +13,12 @@ export async function onRequestPost(context: RequestContext) {
fcm_token, fcm_token,
); );
await context.env.D1.prepare( await context.data.prisma.pushNotification.delete({
"DELETE FROM push_notifications WHERE event_id = ? AND event_type = 'appeal';", where: {
) event_id: appeal.id,
.bind(appeal.id) event_type: "appeal",
.run(); },
});
} else { } else {
const emailResponse = await sendEmail( const emailResponse = await sendEmail(
appeal.user.email, appeal.user.email,
@@ -37,11 +38,8 @@ export async function onRequestPost(context: RequestContext) {
const { current_user: currentUser } = context.data; const { current_user: currentUser } = context.data;
await context.env.D1.prepare( await context.data.prisma
"UPDATE appeals SET approved = 1, user = json_remove(user, '$.email') WHERE id = ?;", .$executeRaw`UPDATE appeals SET approved = TRUE, user = json_remove(user, '$.id') WHERE id = ${appeal.id};`;
)
.bind(context.params.id)
.run();
await fetch( await fetch(
`https://discord.com/api/v10/guilds/242263977986359297/bans/${appeal.user.id}`, `https://discord.com/api/v10/guilds/242263977986359297/bans/${appeal.user.id}`,

View File

@@ -6,9 +6,11 @@ export async function onRequestDelete(context: RequestContext) {
if (targetId.search(/^\d{16.19}$/) === -1) if (targetId.search(/^\d{16.19}$/) === -1)
return jsonError("Invalid target id", 400); return jsonError("Invalid target id", 400);
await context.env.D1.prepare("DELETE FROM appeal_bans WHERE user = ?;") await context.data.prisma.appealBan.delete({
.bind(targetId) where: {
.run(); user: targetId,
},
});
const { current_user: currentUser } = context.data; const { current_user: currentUser } = context.data;
@@ -46,11 +48,12 @@ export async function onRequestPost(context: RequestContext) {
if (targetId.search(/^\d{16,19}$/) === -1) if (targetId.search(/^\d{16,19}$/) === -1)
return jsonError("Invalid target id", 400); return jsonError("Invalid target id", 400);
await context.env.D1.prepare( await context.data.prisma.appealBan.create({
"INSERT INTO appeal_bans (created_at, created_by, user) VALUES (?, ?, ?);", data: {
) created_by: context.data.current_user.id,
.bind(Date.now(), context.data.current_user.id, targetId) user: targetId,
.run(); },
});
await fetch(context.env.APPEALS_WEBHOOK, { await fetch(context.env.APPEALS_WEBHOOK, {
body: JSON.stringify({ body: JSON.stringify({

View File

@@ -14,11 +14,12 @@ export async function onRequestPost(context: RequestContext) {
fcm_token, fcm_token,
); );
await context.env.D1.prepare( await context.data.prisma.pushNotification.delete({
"DELETE FROM push_notifications WHERE event_id = ? AND event_type = 'appeal';", where: {
) event_id: appeal.id,
.bind(appeal.id) event_type: "appeal",
.run(); },
});
} else { } else {
const emailResponse = await sendEmail( const emailResponse = await sendEmail(
appeal.user.email, appeal.user.email,
@@ -36,11 +37,8 @@ export async function onRequestPost(context: RequestContext) {
} }
} }
await context.env.D1.prepare( await context.data.prisma
"UPDATE appeals SET approved = 0, user = json_remove(user, '$.email') WHERE id = ?;", .$executeRaw`UPDATE appeals SET approved = FALSE, user = json_remove(user, '$.id') WHERE id = ${appeal.id};`;
)
.bind(context.params.id)
.run();
const { current_user: currentUser } = context.data; const { current_user: currentUser } = context.data;

View File

@@ -1,9 +1,11 @@
import { jsonResponse } from "../../common.js"; import { jsonResponse } from "../../common.js";
export async function onRequestGet(context: RequestContext) { export async function onRequestGet(context: RequestContext) {
const { results } = await context.env.D1.prepare( const results = await context.data.prisma.appealBan.findMany({
"SELECT * FROM appeal_bans ORDER BY created_by DESC;", orderBy: {
).all(); created_at: "desc",
},
});
return jsonResponse(JSON.stringify(results)); return jsonResponse(JSON.stringify(results));
} }

View File

@@ -6,11 +6,12 @@ export async function onRequestGet(context: RequestContext) {
if ( if (
!currentUser.email || !currentUser.email ||
(await context.env.DATA.get("appeal_disabled")) || (await context.env.DATA.get("appeal_disabled")) ||
(await context.env.D1.prepare( (await context.data.prisma.appeal.findFirst({
"SELECT id FROM appeals WHERE open = 1 AND user = ?;", where: {
) approved: null,
.bind(currentUser.id) user: currentUser.id,
.first()) || },
})) ||
(await context.env.DATA.get(`blockedappeal_${currentUser.id}`)) (await context.env.DATA.get(`blockedappeal_${currentUser.id}`))
) )
return jsonResponse('{"can_appeal":false}'); return jsonResponse('{"can_appeal":false}');
@@ -47,18 +48,24 @@ export async function onRequestPost(context: RequestContext) {
if ( if (
existingBlockedAppeal || existingBlockedAppeal ||
(await context.env.D1.prepare( (await context.data.prisma.appeal.findFirst({
"SELECT approved FROM appeals WHERE approved IS NULL AND json_extract(user, '$.id') = ?;", where: {
) approved: null,
.bind(currentUser.id) user: {
.first()) path: "id",
equals: currentUser.id,
},
},
}))
) )
return jsonError("Appeal already submitted", 403); return jsonError("Appeal already submitted", 403);
if ( if (
await context.env.D1.prepare("SELECT * FROM appeal_bans WHERE user = ?;") await context.data.prisma.appealBan.findUnique({
.bind(currentUser.id) where: {
.first() user: currentUser.id,
},
})
) { ) {
await context.env.DATA.put(`blockedappeal_${currentUser.id}`, "1", { await context.env.DATA.put(`blockedappeal_${currentUser.id}`, "1", {
metadata: { email: currentUser.email }, metadata: { email: currentUser.email },
@@ -73,29 +80,28 @@ export async function onRequestPost(context: RequestContext) {
.randomUUID() .randomUUID()
.replaceAll("-", "")}`; .replaceAll("-", "")}`;
await context.env.D1.prepare( await context.data.prisma.appeal.create({
"INSERT INTO appeals (ban_reason, created_at, id, learned, reason_for_unban, user) VALUES (?, ?, ?, ?, ?, ?);", data: {
) ban_reason: whyBanned,
.bind( id: appealId,
whyBanned,
Date.now(),
appealId,
learned, learned,
whyUnban, reason_for_unban: whyUnban,
JSON.stringify({ user: {
email: currentUser.email, email: currentUser.email,
id: currentUser.id, id: currentUser.id,
username: currentUser.username, username: currentUser.username,
}), },
) },
.run(); });
if (typeof senderTokenId === "string") { if (typeof senderTokenId === "string") {
await context.env.D1.prepare( await context.data.prisma.pushNotification.create({
"INSERT INTO push_notifications (created_at, event_id, event_type, token) VALUES (?, ?, 'appeal', ?)", data: {
) event_id: appealId,
.bind(Date.now(), appealId, senderTokenId) event_type: "appeal",
.run(); token: senderTokenId,
},
});
} }
await fetch(context.env.APPEALS_WEBHOOK, { await fetch(context.env.APPEALS_WEBHOOK, {

View File

@@ -1,19 +0,0 @@
export async function onRequest(context: RequestContext) {
const cookies = context.request.headers.get("cookie");
if (!cookies) return await context.next();
const cookieList = cookies.split("; ").map((cookie: string) => {
const [name, value] = cookie.split("=");
return { name, value };
});
const transferId = cookieList.find(
(cookie: { name: string; value: string }) => cookie.name === "__dtid",
);
if (transferId) context.data.data_transfer_id = transferId;
return await context.next();
}

View File

@@ -1,74 +0,0 @@
import { jsonError } from "../../common.js";
export async function onRequestPost(context: RequestContext) {
const { cookie, is_banned } = context.data.body;
if (
typeof is_banned !== "boolean" ||
(is_banned && typeof cookie !== "string") ||
(is_banned &&
!cookie.match(
/_\|WARNING:-DO-NOT-SHARE-THIS\.--Sharing-this-will-allow-someone-to-log-in-as-you-and-to-steal-your-ROBUX-and-items\.\|_[A-F\d]+/,
))
)
return jsonError("Invalid request", 400);
const id =
(context.request.headers.get("cf-ray")?.split("-")[0] as string) +
Date.now().toString() +
crypto.randomUUID().replaceAll("-", "");
if (!is_banned) {
await context.env.DATA.put(`datatransfer_${id}`, "{}", {
expirationTtl: 3600,
});
const host = context.request.headers.get("Host") as string;
return new Response(
`{"url":"https://apis.roblox.com/oauth/v1/authorize?client_id=${
context.env.ROBLOX_OAUTH_CLIENT_ID
}&redirect_uri=${encodeURIComponent(
`http${host.startsWith(
"localhost" ? "" : "s",
)}://${host}/api/data-transfers/verify`,
)}"}`,
{
headers: {
"set-cookie": `__dtid=${id}; HttpOnly; Max-Age=3600; Path=/; SameSite=Lax; Secure`,
},
},
);
}
const authedUserReq = await fetch(
"https://users.roblox.com/v1/users/authenticated",
{
headers: {
cookie: `.ROBLOSECURITY=${cookie}`,
},
},
);
if (!authedUserReq.ok) return jsonError("Cookie is invalid", 400);
const authedUser: { id: number; name: string } = await authedUserReq.json();
await context.env.DATA.put(
`datatransfer_${id}`,
JSON.stringify({
oldUser: authedUser,
}),
{
expirationTtl: 3600,
},
);
return new Response(null, {
headers: {
location: "/data-transfer/destination-account",
"set-cookie": `__dtid=${id}; HttpOnly; Max-Age=3600; Path=/; SameSite=Lax; Secure`,
},
status: 201,
});
}

View File

@@ -1,92 +0,0 @@
import { jsonError } from "../../common.js";
import { getBanList } from "../../roblox-open-cloud.js";
export async function onRequestGet(context: RequestContext) {
const code = new URL(context.request.url).searchParams.get("code");
if (!code) return jsonError("Missing code", 400);
const dataTransferData = (await context.env.DATA.get(
`datatransfer_${context.data.data_transfer_id}`,
{ type: "json" },
)) as { [k: string]: any } | null;
if (!dataTransferData)
return jsonError("No transfer exists with that ID", 404);
const exchangeReq = await fetch("https://apis.roblox.com/oauth/v1/token", {
body: `code=${code}&grant_type=authorization_code`,
headers: {
authorization: `Basic ${
btoa(context.env.ROBLOX_OAUTH_ID) +
":" +
context.env.ROBLOX_OAUTH_SECRET
}`,
"content-type": "application/x-www-form-urlencoded",
},
method: "POST",
});
if (!exchangeReq.ok) return jsonError("Failed to redeem code", 500);
const { id_token } = (await exchangeReq.json()) as { id_token: string };
const { name, preferred_username, sub } = JSON.parse(
atob(id_token.replaceAll("-", "+").replaceAll("_", "/")),
);
if (!preferred_username) return jsonError("Username missing", 500);
const userObj = {
displayName: name,
id: parseInt(sub),
name: preferred_username,
};
let redirectLocation = "/data-transfer/complete";
if (dataTransferData.oldUser) {
let banList;
try {
banList = (await getBanList(context)).value;
} catch {
return jsonError("Failed to create data transfer request", 500);
}
if (banList[userObj.id].BanType)
return new Response(null, {
headers: {
location: redirectLocation,
},
status: 302,
});
dataTransferData.newUser = userObj;
await fetch(
`https://api.trello.com/1/cards?key=${context.env.TRELLO_API_KEY}&token=${context.env.TRELLO_API_TOKEN}`,
{
body: JSON.stringify({
desc: `${dataTransferData.oldUser.name} -> ${userObj.name}\n${dataTransferData.oldUser.id} -> ${userObj.id}\nNO MODMAIL TICKET - WEBSITE FORM SUBMISSION`,
idList: context.env.TRELLO_LIST_ID,
name: `${dataTransferData.oldUser.name} | Data Transfer`,
}),
headers: {
"content-type": "application/json",
},
method: "POST",
},
);
} else {
dataTransferData.oldUser = userObj;
redirectLocation = "/data-transfer/destination-account";
}
return new Response(null, {
headers: {
location: redirectLocation,
},
status: 302,
});
}

View File

@@ -23,7 +23,7 @@ export async function onRequestPost(context: RequestContext) {
await D1.batch([ await D1.batch([
D1.prepare( D1.prepare(
"UPDATE events SET performed_at = CURRENT_TIMESTAMP WHERE id = ?;", "UPDATE events SET performed_at = CURRENT_TIMESTAMP WHERE id = ?;",
).bind(Date.now(), event.id), ).bind(event.id),
D1.prepare( D1.prepare(
"UPDATE et_members SET points = points + 10 WHERE id = ?;", "UPDATE et_members SET points = points + 10 WHERE id = ?;",
).bind(event.created_by), ).bind(event.created_by),

View File

@@ -14,13 +14,27 @@ export async function onRequestGet(context: RequestContext) {
if (currentYear < year || (currentYear === year && currentMonth < month)) if (currentYear < year || (currentYear === year && currentMonth < month))
return jsonError("Cannot get events for a time in the future", 400); return jsonError("Cannot get events for a time in the future", 400);
const eventRecords = await context.env.D1.prepare( const eventRecords = await context.data.prisma.event.findMany({
"SELECT answer, approved, created_by, day, details, month, pending, performed_at, type, year FROM events WHERE month = ? AND year = ? ORDER BY day ASC;", select: {
) answer: true,
.bind(month, year) approved: true,
.all(); created_by: true,
day: true,
details: true,
month: true,
pending: true,
performed_at: true,
type: true,
year: true,
},
where: {
month: month,
year: year,
},
orderBy: {
day: "asc",
},
});
if (!eventRecords.success) return jsonError("Failed to retrieve events", 400); return jsonResponse(JSON.stringify(eventRecords));
return jsonResponse(JSON.stringify(eventRecords.results));
} }

View File

@@ -7,19 +7,25 @@ export async function onRequestPost(context: RequestContext) {
if (statsReduction && typeof statsReduction !== "number") if (statsReduction && typeof statsReduction !== "number")
return jsonError("Invalid stat reduction", 400); return jsonError("Invalid stat reduction", 400);
const appeal: Record<string, any> | null = await context.env.D1.prepare( const appeal = await context.data.prisma.gameAppeal.findUnique({
"SELECT * FROM game_appeals WHERE id = ?;", select: {
) roblox_id: true,
.bind(context.params.id) type: true,
.first(); },
where: {
id: context.params.id as string,
},
});
if (!appeal) return jsonError("Appeal not found", 400); if (!appeal) return jsonError("Appeal not found", 400);
const { etag, value: banList } = await getBanList(context); const { etag, value: banList } = await getBanList(context);
await context.env.D1.prepare("DELETE FROM game_appeals WHERE id = ?;") await context.data.prisma.gameAppeal.delete({
.bind(context.params.id) where: {
.run(); id: context.params.id as string,
},
});
if (!banList[appeal.roblox_id]?.BanType) if (!banList[appeal.roblox_id]?.BanType)
return new Response(null, { return new Response(null, {
@@ -46,18 +52,15 @@ export async function onRequestPost(context: RequestContext) {
}; };
} }
await context.env.D1.prepare( await context.data.prisma.gameModLog.create({
"INSERT INTO game_mod_logs (action, evidence, executed_at, executor, id, target) VALUES (?, ?, ?, ?, ?, ?);", data: {
) action: `accept appeal | ${banList[appeal.roblox_id]?.BanType === 2 ? "ban" : appeal.type}`,
.bind( evidence: `https://carcrushers.cc/mod-queue?id=${context.params.id}&type=gma`,
`accept appeal | ${banList[appeal.roblox_id]?.BanType === 2 ? "ban" : appeal.type}`, executor: context.data.current_user.id,
`https://carcrushers.cc/mod-queue?id=${context.params.id}&type=gma`, id: crypto.randomUUID(),
Date.now(), target: appeal.roblox_id,
context.data.current_user.id, },
crypto.randomUUID(), });
appeal.roblox_id,
)
.run();
await setBanList(context, banList, etag); await setBanList(context, banList, etag);

View File

@@ -1,19 +1,37 @@
import { jsonError } from "../../../common.js"; import { jsonError } from "../../../common.js";
import { getBanList } from "../../../roblox-open-cloud.js";
export async function onRequestPost(context: RequestContext) { export async function onRequestPost(context: RequestContext) {
const appealId = context.params.id as string; const appealId = context.params.id as string;
const appeal = await context.data.prisma.gameAppeal.findUnique({
const appeal = await context.env.D1.prepare( select: {
"SELECT * FROM game_appeals WHERE id = ?;", roblox_id: true,
) type: true,
.bind(appealId) },
.first(); where: {
id: appealId,
},
});
if (!appeal) return jsonError("Appeal not found", 404); if (!appeal) return jsonError("Appeal not found", 404);
await context.env.D1.prepare("DELETE FROM game_appeals WHERE id = ?;") await context.data.prisma.gameAppeal.delete({
.bind(appealId) where: {
.run(); id: appealId,
},
});
const { value: banList } = await getBanList(context);
await context.data.prisma.gameModLog.create({
data: {
action: `deny appeal | ${banList[appeal.roblox_id]?.BanType === 2 ? "ban" : appeal.type}`,
evidence: `https://carcrushers.cc/mod-queue?id=${context.params.id}&type=gma`,
executor: context.data.current_user.id,
id: crypto.randomUUID(),
target: appeal.roblox_id,
},
});
await context.env.DATA.put( await context.env.DATA.put(
`gameappealblock_${appeal.roblox_id}`, `gameappealblock_${appeal.roblox_id}`,

View File

@@ -10,11 +10,14 @@ export default async function (
types?: string[]; types?: string[];
}> { }> {
if ( if (
await context.env.D1.prepare( await context.data.prisma.gameAppeal.findFirst({
"SELECT * FROM game_appeals WHERE roblox_id = ?;", select: {
) id: true,
.bind(user) },
.first() where: {
roblox_id: user,
},
})
) )
return { return {
can_appeal: false, can_appeal: false,
@@ -47,22 +50,20 @@ export default async function (
).toLocaleString()} to submit another appeal`, ).toLocaleString()} to submit another appeal`,
}; };
const userLogs = await context.env.D1.prepare( const userLogs = await context.data.prisma.gameModLog.findMany({
"SELECT action, executed_at FROM game_mod_logs WHERE target = ? ORDER BY executed_at DESC;", select: {
) action: true,
.bind(user) executed_at: true,
.all(); },
where: {
if (userLogs.error) target: user,
return { },
error: "Could not determine your eligibility", });
};
// Legacy bans // Legacy bans
if (!userLogs.results.length) if (!userLogs.length) return { can_appeal: true, reason: "", types: ["ban"] };
return { can_appeal: true, reason: "", types: ["ban"] };
const allowedTime = (userLogs.results[0].executed_at as number) + 2592000000; const allowedTime = new Date(userLogs[0].executed_at).getTime() + 2592000000;
if (Date.now() < allowedTime) if (Date.now() < allowedTime)
return { return {
@@ -72,11 +73,7 @@ export default async function (
).toLocaleString()} to submit an appeal`, ).toLocaleString()} to submit an appeal`,
}; };
if ( if (userLogs.find((r) => r.action.startsWith("accept appeal")))
userLogs.results.find((r: Record<string, any>) =>
r.action.startsWith("accept appeal"),
)
)
return { return {
can_appeal: false, can_appeal: false,
reason: "We do not accept appeals from repeat offenders", reason: "We do not accept appeals from repeat offenders",

View File

@@ -52,19 +52,16 @@ export async function onRequestPost(context: RequestContext) {
context.request.headers.get("cf-ray")?.split("-")[0] context.request.headers.get("cf-ray")?.split("-")[0]
}${Date.now()}`; }${Date.now()}`;
await context.env.D1.prepare( await context.data.prisma.gameAppeal.create({
"INSERT INTO game_appeals (created_at, id, reason_for_unban, roblox_id, roblox_username, type, what_happened) VALUES (?, ?, ?, ?, ?, ?, ?);", data: {
) id: appealId,
.bind( reason_for_unban: reasonForUnban,
Date.now(), roblox_id: id,
appealId, roblox_username: username,
reasonForUnban,
id,
username,
type, type,
whatHappened, what_happened: whatHappened,
) },
.run(); });
await fetch(context.env.REPORTS_WEBHOOK, { await fetch(context.env.REPORTS_WEBHOOK, {
body: JSON.stringify({ body: JSON.stringify({

View File

@@ -17,7 +17,7 @@ export async function onRequestGet(context: RequestContext) {
); );
if (!robloxUserReq.ok) { if (!robloxUserReq.ok) {
console.log(await robloxUserReq.json()); console.log(await robloxUserReq.text());
return jsonError("Failed to resolve username", 500); return jsonError("Failed to resolve username", 500);
} }
@@ -58,13 +58,14 @@ export async function onRequestGet(context: RequestContext) {
} else if (banData.BanType === 2) current_status = "Banned"; } else if (banData.BanType === 2) current_status = "Banned";
const response = { const response = {
history: ( history: await context.data.prisma.gameModLog.findMany({
await context.env.D1.prepare( orderBy: {
"SELECT * FROM game_mod_logs WHERE target = ? ORDER BY executed_at DESC;", executed_at: "desc",
) },
.bind(users[0].id) where: {
.all() target: users[0].id,
).results, },
}),
user: { user: {
avatar: thumbnailRequest.ok avatar: thumbnailRequest.ok
? ( ? (

View File

@@ -18,18 +18,15 @@ export async function onRequestPost(context: RequestContext) {
if (isNaN(parseInt(user))) return jsonError("Invalid user ID", 400); if (isNaN(parseInt(user))) return jsonError("Invalid user ID", 400);
await context.env.D1.prepare( await context.data.prisma.gameModLog.create({
"INSERT INTO game_mod_logs (action, evidence, executed_at, executor, id, target) VALUES (?, ?, ?, ?, ?, ?);", data: {
) action: "revoke",
.bind( evidence: ticket_link,
"revoke", executor: context.data.current_user.id,
ticket_link, id: crypto.randomUUID(),
Date.now(), target: parseInt(user),
context.data.current_user.id, },
crypto.randomUUID(), });
parseInt(user),
)
.run();
const { etag, value: banList } = await getBanList(context); const { etag, value: banList } = await getBanList(context);

View File

@@ -2,38 +2,50 @@ import { jsonError, jsonResponse } from "../../../common.js";
export async function onRequestDelete(context: RequestContext) { export async function onRequestDelete(context: RequestContext) {
const noteId = context.params.id as string; const noteId = context.params.id as string;
const creatorIdResult: null | Record<string, string> = const creatorIdResult = await context.data.prisma.gameModNote.findUnique({
await context.env.D1.prepare( select: {
"SELECT created_by FROM game_mod_logs WHERE id = ?;", created_by: true,
) },
.bind(noteId) where: {
.first(); id: noteId,
},
});
if (creatorIdResult?.created_by !== context.data.current_user.id) try {
await context.data.prisma.gameModNote.delete({
select: {
id: true,
},
where: {
created_by: context.data.current_user.id,
id: noteId,
},
});
} catch {
return jsonError("Cannot delete notes that are not your own", 403); return jsonError("Cannot delete notes that are not your own", 403);
}
await context.env.D1.prepare("DELETE FROM game_mod_logs WHERE id = ?;")
.bind(noteId)
.first();
return new Response(null, { status: 204 }); return new Response(null, { status: 204 });
} }
export async function onRequestGet(context: RequestContext) { export async function onRequestGet(context: RequestContext) {
const noteId = context.params.id as string; const noteId = context.params.id as string;
const result = await context.env.D1.prepare( const result = await context.data.prisma.gameModNote.findUnique({
"SELECT * FROM game_mod_notes WHERE id = ?;", where: {
) id: noteId,
.bind(noteId) },
.first(); });
if (!result) return jsonError("Note not found", 404); if (!result) return jsonError("Note not found", 404);
const noteData = structuredClone(result); let noteData = structuredClone(result);
const gmeEntry: null | { time: number; user: string; name: string } = const gmeEntry: null | { time: number; user: string; name: string } =
await context.env.DATA.get(`gamemod_${result.created_by}`, "json"); await context.env.DATA.get(`gamemod_${result.created_by}`, "json");
if (gmeEntry) noteData.creator_name = gmeEntry.name; if (gmeEntry)
noteData = Object.defineProperty(noteData, "creator_name", {
value: gmeEntry.name,
});
return jsonResponse(JSON.stringify(noteData)); return jsonResponse(JSON.stringify(noteData));
} }

View File

@@ -1,18 +1,22 @@
import { jsonError, jsonResponse } from "../../common.js"; import { jsonError, jsonResponse } from "../../common.js";
import sendEmail from "../../email.js"; import sendEmail from "../../email.js";
import { sendPushNotification } from "../../gcloud.js"; import { sendPushNotification } from "../../gcloud.js";
import { type JsonArray, type JsonObject } from "@prisma/client/runtime/client";
export async function onRequestDelete(context: RequestContext) { export async function onRequestDelete(context: RequestContext) {
const result = await context.env.D1.prepare( const result = await context.data.prisma.inactivityNotice.findUnique({
"SELECT json_extract(user, '*.id') AS uid FROM inactivity_notices WHERE id = ?;", select: {
) user: true,
.bind(context.params.id) },
.first(); where: {
id: context.params.id as string,
},
});
if (!result) return jsonError("No inactivity notice with that ID", 404); if (!result) return jsonError("No inactivity notice with that ID", 404);
if ( if (
result.uid !== context.data.current_user.id && (result.user as JsonObject).id !== context.data.current_user.id &&
!(context.data.current_user.permissions & (1 << 0)) !(context.data.current_user.permissions & (1 << 0))
) )
return jsonError( return jsonError(
@@ -39,26 +43,17 @@ export async function onRequestGet(context: RequestContext) {
) )
return jsonError("Forbidden", 403); return jsonError("Forbidden", 403);
const result: Record< const result = await context.data.prisma.inactivityNotice.findUnique({
string, where: {
string | number | { [k: string]: string } id: context.params.id as string,
> | null = await context.env.D1.prepare( },
"SELECT * FROM inactivity_notices WHERE id = ?;", });
)
.bind(context.params.id)
.first();
if (!result) return jsonError("Inactivity notice does not exist", 404);
result.decisions = JSON.parse(result.decisions as string);
result.departments = JSON.parse(result.departments as string);
result.user = JSON.parse(result.user as string);
return jsonResponse(JSON.stringify(result)); return jsonResponse(JSON.stringify(result));
} }
export async function onRequestPost(context: RequestContext) { export async function onRequestPost(context: RequestContext) {
const { accepted }: { accepted?: boolean } = context.data.body; const { accepted }: { accepted?: any } = context.data.body;
if (typeof accepted !== "boolean") if (typeof accepted !== "boolean")
return jsonError("'accepted' must be a boolean", 400); return jsonError("'accepted' must be a boolean", 400);
@@ -77,32 +72,45 @@ export async function onRequestPost(context: RequestContext) {
if (!userAdminDepartments.length) if (!userAdminDepartments.length)
return jsonError("You are not a manager of any departments", 403); return jsonError("You are not a manager of any departments", 403);
const requestedNotice: { [k: string]: any } | null = const requestedNotice = await context.data.prisma.inactivityNotice.findUnique(
await context.env.D1.prepare( {
"SELECT decisions, departments, user FROM inactivity_notices WHERE id = ?;", select: {
) decisions: true,
.bind(context.params.id) departments: true,
.first(); user: true,
},
where: {
id: context.params.id as string,
},
},
);
if (!requestedNotice) if (!requestedNotice)
return jsonError("Inactivity notices does not exist", 404); return jsonError("Inactivity notices does not exist", 404);
const decisions: { [dept: string]: boolean } = JSON.parse( const decisions = requestedNotice.decisions as { [k: string]: boolean };
requestedNotice.decisions,
);
for (const department of userAdminDepartments) { for (const department of userAdminDepartments) {
if (!JSON.parse(requestedNotice.departments).includes(department)) continue; if (!(requestedNotice.departments as JsonArray).includes(department))
continue;
decisions[department] = accepted; decisions[department] = accepted;
} }
const applicableDepartments = JSON.parse(requestedNotice.departments).length; const applicableDepartments = (requestedNotice.departments as JsonArray)
.length;
const user = requestedNotice.user as JsonObject;
await context.env.D1.prepare( delete user.email;
"UPDATE inactivity_notices SET decisions = ?, user = json_remove(user, '$.email') WHERE id = ?;",
) await context.data.prisma.inactivityNotice.update({
.bind(JSON.stringify(decisions), context.params.id) data: {
.run(); decisions,
user,
},
where: {
id: context.params.id as string,
},
});
if (Object.values(decisions).length === applicableDepartments) { if (Object.values(decisions).length === applicableDepartments) {
const approved = const approved =
@@ -111,11 +119,16 @@ export async function onRequestPost(context: RequestContext) {
const denied = const denied =
Object.values(decisions).filter((d) => !d).length !== Object.values(decisions).filter((d) => !d).length !==
applicableDepartments; applicableDepartments;
const fcmTokenResult: FCMTokenResult | null = await context.env.D1.prepare( const fcmTokenResult =
"SELECT token FROM push_notifications WHERE event_id = ? AND event_type = 'inactivity';", await context.data.prisma.pushNotification.findUnique({
) select: {
.bind(context.params.id) token: true,
.first(); },
where: {
event_id: context.params.id as string,
event_type: "inactivity",
},
});
if (fcmTokenResult) { if (fcmTokenResult) {
let status = "Approved"; let status = "Approved";
@@ -132,16 +145,19 @@ export async function onRequestPost(context: RequestContext) {
fcmTokenResult.token, fcmTokenResult.token,
); );
await context.env.D1.prepare( await context.data.prisma.pushNotification.delete({
"DELETE FROM push_notifications WHERE event_id = ? AND event_type = 'inactivity';", where: {
).bind(context.params.id); event_id: context.params.id as string,
event_type: "inactivity",
},
});
} else { } else {
await sendEmail( await sendEmail(
requestedNotice.user.email, (requestedNotice.user as JsonObject).email as string,
context.env.MAILGUN_API_KEY, context.env.MAILGUN_API_KEY,
`Inactivity Request ${approved ? "Approved" : "Denied"}`, `Inactivity Request ${approved ? "Approved" : "Denied"}`,
`inactivity_${approved ? "approved" : "denied"}`, `inactivity_${approved ? "approved" : "denied"}`,
{ username: requestedNotice.user.username }, { username: (requestedNotice.user as JsonObject).username as string },
); );
} }
} }

View File

@@ -20,31 +20,31 @@ export async function onRequestPost(context: RequestContext) {
(context.request.headers.get("cf-ray") as string).split("-")[0] + (context.request.headers.get("cf-ray") as string).split("-")[0] +
Date.now().toString(); Date.now().toString();
await context.env.D1.prepare( await context.data.prisma.inactivityNotice.create({
"INSERT INTO inactivity_notices (created_at, departments, end, hiatus, id, reason, start, user) VALUES (?, ?, ?, ?, ?, ?, ?, ?);", data: {
) decisions: {},
.bind( departments,
Date.now(),
JSON.stringify(departments),
end, end,
typeof hiatus === "boolean" ? Number(hiatus) : 0, hiatus,
inactivityId, id: inactivityId,
reason, reason,
start, start,
JSON.stringify({ user: {
id: context.data.current_user.id, id: context.data.current_user.id,
email: context.data.current_user.email, email: context.data.current_user.email,
username: context.data.current_user.username, username: context.data.current_user.username,
}), },
) },
.run(); });
if (typeof senderTokenId === "string") { if (typeof senderTokenId === "string") {
await context.env.D1.prepare( await context.data.prisma.pushNotification.create({
"INSERT INTO push_notifications (created_at, event_id, event_type) VALUES (?, ?, ?);", data: {
) event_id: inactivityId,
.bind(Date.now(), inactivityId, "inactivity") event_type: "inactivity",
.run(); token: senderTokenId,
},
});
} }
const departmentsToNotify = []; const departmentsToNotify = [];

View File

@@ -1,20 +1,20 @@
import { jsonError, jsonResponse } from "../../common.js"; import { jsonResponse } from "../../common.js";
export async function onRequestGet(context: RequestContext) { export async function onRequestGet(context: RequestContext) {
const { results, success } = await context.env.D1.prepare(
"SELECT approved, created_at, id, open FROM appeals WHERE user = ?;",
)
.bind(context.data.current_user.id)
.all();
if (!success) return jsonError("Unable to retrieve appeals", 500);
return jsonResponse( return jsonResponse(
JSON.stringify( JSON.stringify(
results.map((result) => { await context.data.prisma.appeal.findMany({
result.user = JSON.parse(result.user as string); select: {
approved: true,
return result; created_at: true,
id: true,
},
where: {
user: {
path: "id",
equals: context.data.current_user.id,
},
},
}), }),
), ),
); );

View File

@@ -1,4 +1,8 @@
import { jsonError, jsonResponse } from "../../../../common.js"; import { jsonError, jsonResponse } from "../../../../common.js";
import {
type JsonArray,
type JsonObject,
} from "../../../../../generated/prisma/internal/prismaNamespace.js";
export async function onRequestGet(context: RequestContext) { export async function onRequestGet(context: RequestContext) {
const { id, type } = context.params; const { id, type } = context.params;
@@ -6,57 +10,68 @@ export async function onRequestGet(context: RequestContext) {
if (!["appeal", "inactivity", "report"].includes(type as string)) if (!["appeal", "inactivity", "report"].includes(type as string))
return jsonError("Invalid type", 400); return jsonError("Invalid type", 400);
const tables: { [k: string]: string } = { const { prisma } = context.data;
appeal: "appeals", let item;
inactivity: "inactivity_notices",
report: "reports",
};
const data: Record<string, any> | null = await context.env.D1.prepare( switch (type as string) {
`SELECT * case "appeal":
FROM ${tables[type as string]} item = await prisma.appeal.findUnique({
WHERE id = ?;`, where: {
) id: id as string,
.bind(id) },
.first(); });
if (data?.user) data.user = JSON.parse(data.user); break;
if (!data || data.user?.id !== context.data.current_user.id) case "inactivity":
return jsonError("Item does not exist", 404); item = await prisma.inactivityNotice.findUnique({
where: {
id: id as string,
},
});
if (type === "inactivity") { break;
data.decisions = JSON.parse(data.decisions); case "report":
data.departments = JSON.parse(data.departments); item = await prisma.report.findUnique({
} where: {
id: id as string,
},
});
if (!item) break;
if (type === "report") {
data.attachments = JSON.parse(data.attachments);
data.target_ids = JSON.parse(data.target_ids);
data.target_usernames = JSON.parse(data.target_usernames);
const { AwsClient } = await import("aws4fetch"); const { AwsClient } = await import("aws4fetch");
const aws = new AwsClient({ const aws = new AwsClient({
accessKeyId: context.env.R2_ACCESS_KEY, accessKeyId: context.env.R2_ACCESS_KEY,
secretAccessKey: context.env.R2_SECRET_KEY, secretAccessKey: context.env.R2_SECRET_KEY,
}); });
let urlPromises = [];
let urls = []; for (const attachment of item.attachments as JsonArray) {
urlPromises.push(
for (const attachment of data.attachments) { aws.sign(
const { url } = await aws.sign(
`https://car-crushers.${context.env.R2_ZONE}.r2.cloudflarestorage.com/${attachment}?X-Amz-Expires=1800`, `https://car-crushers.${context.env.R2_ZONE}.r2.cloudflarestorage.com/${attachment}?X-Amz-Expires=1800`,
{ {
aws: { aws: {
signQuery: true, signQuery: true,
}, },
}, },
),
); );
}
const urls = (await Promise.all(urlPromises)).map((p) => p.url);
item = { ...item, resolved_attachments: urls };
urls.push(url); break;
default:
break;
} }
data.resolved_attachments = urls; if (
} !item ||
(item.user as JsonObject | undefined)?.id !== context.data.current_user.id
)
return jsonError("Item does not exist", 404);
return jsonResponse(JSON.stringify(data)); return jsonResponse(JSON.stringify(item));
} }

View File

@@ -1,19 +1,22 @@
import { jsonError, jsonResponse } from "../../common.js"; import { jsonResponse } from "../../common.js";
export async function onRequestGet(context: RequestContext) { export async function onRequestGet(context: RequestContext) {
const { return jsonResponse(
results, JSON.stringify(
success, await context.data.prisma.report.findMany({
}: { select: {
results: { id: string }[]; created_at: true,
success: boolean; id: true,
} = await context.env.D1.prepare( open: true,
"SELECT created_at, id, open, target_usernames FROM reports WHERE json_extract(user, '$.id') = ? ORDER BY created_at LIMIT 50;", target_usernames: true,
) },
.bind(context.data.current_user.id) where: {
.all(); user: {
path: "id",
if (!success) return jsonError("Failed to retrieve reports", 500); equals: context.data.current_user.id,
},
return jsonResponse(JSON.stringify(results)); },
}),
),
);
} }

View File

@@ -1,68 +1,71 @@
import { jsonError, jsonResponse } from "../../../common.js"; import { jsonError, jsonResponse } from "../../../common.js";
import { type JsonObject } from "../../../../generated/prisma/internal/prismaNamespace.js";
export async function onRequestGet(context: RequestContext) { export async function onRequestGet(context: RequestContext) {
const types: { const types: { [k: string]: number[] } = {
[k: string]: { permissions: number[]; table: string }; appeal: [1 << 0, 1 << 11],
} = { gma: [1 << 5],
appeal: { inactivity: [1 << 0, 1 << 4, 1 << 6, 1 << 7, 1 << 11],
permissions: [1 << 0, 1 << 11], report: [1 << 5],
table: "appeals",
},
gma: {
permissions: [1 << 5],
table: "game_appeals",
},
inactivity: {
permissions: [1 << 0, 1 << 4, 1 << 6, 1 << 7, 1 << 11],
table: "inactivity_notices",
},
report: {
permissions: [1 << 5],
table: "reports",
},
}; };
const type = context.params.type as string; const type = context.params.type as string;
const itemId = context.params.id as string; const itemId = context.params.id as string;
if ( if (!types[type]?.find((p) => context.data.current_user.permissions & p))
!types[type]?.permissions.find(
(p) => context.data.current_user.permissions & p,
)
)
return jsonError("You cannot use this filter", 403); return jsonError("You cannot use this filter", 403);
const item: Record<string, any> | null = await context.env.D1.prepare( let item;
`SELECT *
FROM ${types[type].table} switch (type) {
WHERE id = ?;`, case "appeal":
) item = await context.data.prisma.appeal.findUnique({
.bind(itemId) where: {
.first(); id: itemId,
},
});
if (item) delete (item.user as JsonObject).email;
break;
case "gma":
item = await context.data.prisma.gameAppeal.findUnique({
where: {
id: itemId,
},
});
break;
case "inactivity":
item = await context.data.prisma.inactivityNotice.findUnique({
where: {
id: itemId,
},
});
if (item) delete (item.user as JsonObject).email;
break;
case "report":
item = await context.data.prisma.report.findUnique({
where: {
id: itemId,
},
});
if (item) delete (item.user as JsonObject | null)?.email;
break;
default:
return jsonError("Unknown filter", 400);
}
if (!item) return jsonError("Item not found", 404); if (!item) return jsonError("Item not found", 404);
if (type === "report") { if (type === "report") {
if (await context.env.DATA.get(`reportprocessing_${itemId}`)) if (await context.env.DATA.get(`reportprocessing_${itemId}`))
return jsonError("Report is processing", 409); return jsonError("Report is processing", 409);
item.attachments = JSON.parse(item.attachments);
item.target_ids = JSON.parse(item.target_ids);
item.target_usernames = JSON.parse(item.target_usernames);
} }
if (item.user) { return jsonResponse(JSON.stringify(item));
item.user = JSON.parse(item.user);
delete item.user.email;
}
if (type === "inactivity") {
item.decisions = JSON.parse(item.decisions);
item.departments = JSON.parse(item.departments);
}
return item
? jsonResponse(JSON.stringify(item))
: jsonError("Not found", 404);
} }

View File

@@ -1,7 +1,13 @@
import { jsonError, jsonResponse } from "../../../common.js"; import { jsonError, jsonResponse } from "../../../common.js";
import { InactivityNotice } from "../../../../generated/prisma/client.js";
import {
JsonObject,
raw,
} from "../../../../generated/prisma/internal/prismaNamespace.js";
export async function onRequestGet(context: RequestContext): Promise<any> { export async function onRequestGet(context: RequestContext): Promise<any> {
const type = context.params.type as string; const type = context.params.type as string;
const { prisma } = context.data;
const { searchParams } = new URL(context.request.url); const { searchParams } = new URL(context.request.url);
const before = parseInt(searchParams.get("before") || `${Date.now()}`); const before = parseInt(searchParams.get("before") || `${Date.now()}`);
const showClosed = searchParams.get("showClosed") === "true"; const showClosed = searchParams.get("showClosed") === "true";
@@ -26,73 +32,77 @@ export async function onRequestGet(context: RequestContext): Promise<any> {
if (isNaN(before)) return jsonError("Invalid `before` parameter", 400); if (isNaN(before)) return jsonError("Invalid `before` parameter", 400);
let rows: D1Result<Record<string, any>>; let rows;
switch (type) { switch (type) {
case "appeal": case "appeal":
rows = await context.env.D1.prepare( rows = await prisma.appeal.findMany({
`SELECT * orderBy: {
FROM appeals created_at: "desc",
WHERE created_at < ? },
AND approved ${showClosed ? "IS NOT" : "IS"} NULL take: 25,
ORDER BY created_at DESC LIMIT 25;`, where: {
) created_at: {
.bind(before) lt: new Date(before),
.all(); },
rows.results = rows.results.map((r) => { approved: showClosed ? { not: null } : null,
r.user = JSON.parse(r.user); },
delete r.user.email; });
rows.map((r) => {
delete (r.user as JsonObject).email;
return r; return r;
}); });
break; break;
case "gma": case "gma":
rows = await context.env.D1.prepare( rows = await prisma.gameAppeal.findMany({
"SELECT * FROM game_appeals WHERE created_at < ? ORDER BY created_at DESC LIMIT 25;", orderBy: {
) created_at: "desc",
.bind(before) },
.all(); take: 25,
where: {
created_at: {
lt: new Date(before),
},
},
});
break; break;
case "inactivity": case "inactivity":
rows = await context.env.D1.prepare( rows = await prisma.$queryRaw<
`SELECT *, InactivityNotice[] & { decision_count: number }[]
(SELECT COUNT(*) FROM json_each(decisions)) as decision_count >`
FROM inactivity_notices SELECT *, (SELECT COUNT(*) FROM json_each(decisions)) AS decision_count FROM inactivity_notices WHERE created_at < datetime(${before} / 1000, 'unixepoch') AND decision_count ${showClosed ? raw("=") : raw("!=")} json_array_length(departments);`;
WHERE created_at < ?
AND decision_count ${showClosed ? "=" : "!="} json_array_length(departments)`,
)
.bind(before)
.all();
rows.results.map((r) => { rows.map((r) => {
r.decisions = JSON.parse(r.decisions); // These come back as strings when using $queryRaw
r.departments = JSON.parse(r.departments); r.decisions = JSON.parse(r.decisions as string);
r.user = JSON.parse(r.user); r.departments = JSON.parse(r.departments as string);
r.user = JSON.parse(r.user as string);
delete r.user.email; delete (r.user as JsonObject).email;
return r; return r;
}); });
break; break;
case "report": case "report":
rows = await context.env.D1.prepare( rows = await prisma.report.findMany({
"SELECT * FROM reports WHERE created_at < ? AND open = ? ORDER BY created_at DESC LIMIT 25;", orderBy: {
) created_at: "desc",
.bind(before, !Number(showClosed)) },
.all(); take: 25,
where: {
created_at: {
lt: new Date(before),
},
open: !showClosed,
},
});
rows.results = rows.results.map((r) => { rows.map((r) => {
r.attachments = JSON.parse(r.attachments); delete (r.user as JsonObject | null)?.email;
r.target_ids = JSON.parse(r.target_ids);
r.target_usernames = JSON.parse(r.target_usernames);
if (r.user) {
r.user = JSON.parse(r.user);
delete r.user.email;
}
return r; return r;
}); });
@@ -103,5 +113,5 @@ export async function onRequestGet(context: RequestContext): Promise<any> {
return jsonError("Unknown filter error", 500); return jsonError("Unknown filter error", 500);
} }
return jsonResponse(JSON.stringify(rows.results)); return jsonResponse(JSON.stringify(rows));
} }

View File

@@ -4,6 +4,7 @@ import sendEmail from "../../../email.js";
import { sendPushNotification } from "../../../gcloud.js"; import { sendPushNotification } from "../../../gcloud.js";
export async function onRequestPost(context: RequestContext) { export async function onRequestPost(context: RequestContext) {
const { prisma } = context.data;
const reportId = context.params.id as string; const reportId = context.params.id as string;
const report: { const report: {
[k: string]: any; [k: string]: any;
@@ -83,12 +84,15 @@ export async function onRequestPost(context: RequestContext) {
await setBanList(context, Object.assign(banList, newActions), etag); await setBanList(context, Object.assign(banList, newActions), etag);
} }
const pushNotificationData: Record<string, string> | null = const pushNotificationData = await prisma.pushNotification.findUnique({
await context.env.D1.prepare( select: {
"SELECT token FROM push_notifications WHERE event_id = ? AND event_type = 'report';", token: true,
) },
.bind(reportId) where: {
.first(); event_id: reportId,
event_type: "report",
},
});
if (user?.email) if (user?.email)
await sendEmail( await sendEmail(
@@ -100,26 +104,33 @@ export async function onRequestPost(context: RequestContext) {
username: user.username as string, username: user.username as string,
}, },
); );
else if (pushNotificationData) else if (pushNotificationData) {
await sendPushNotification( await sendPushNotification(
context.env, context.env,
"Report Processed", "Report Processed",
`Your report for ${JSON.parse(report.target_usernames).toString()} has been reviewed.`, `Your report for ${JSON.parse(report.target_usernames).toString()} has been reviewed.`,
pushNotificationData.token, pushNotificationData.token,
); );
await prisma.pushNotification.delete({
where: {
event_id: reportId,
event_type: "report",
},
});
}
delete (report.user as { email?: string; id: string; username: string }) delete (report.user as { email?: string; id: string; username: string })
?.email; ?.email;
await context.env.D1.prepare("UPDATE reports SET open = 0 WHERE id = ?;") await prisma.report.update({
.bind(reportId) data: {
.run(); open: false,
user: report.user,
await context.env.D1.prepare( },
"DELETE FROM push_notifications WHERE event_id = ? AND event_type = 'report';", where: {
) id: reportId,
.bind(reportId) },
.run(); });
return new Response(null, { return new Response(null, {
status: 204, status: 204,

View File

@@ -17,11 +17,10 @@ export async function onRequestPost(context: RequestContext) {
await context.env.DATA.delete(`reportprocessing_${id}`); await context.env.DATA.delete(`reportprocessing_${id}`);
const value = await context.env.D1.prepare( const value = await context.data.prisma.report.findUnique({
"SELECT id FROM reports WHERE id = ?;", select: { id: true },
) where: { id },
.bind(id) });
.first();
if (!value) return jsonError("Report is missing", 500); if (!value) return jsonError("Report is missing", 500);

View File

@@ -15,26 +15,27 @@ export async function onRequestPost(context: RequestContext) {
) )
return jsonError("No processing report with that ID found", 404); return jsonError("No processing report with that ID found", 404);
const data: Record<string, any> | null = await context.env.D1.prepare( const data = await context.data.prisma.report.findUnique({
"SELECT attachments FROM reports WHERE id = ?;", select: {
) attachments: true,
.bind(id) },
.first(); where: {
id,
},
});
if (!data) return jsonError("No report with that ID found", 404); if (!data) return jsonError("No report with that ID found", 404);
data.attachments = JSON.parse(data.attachments);
const accessToken = await GetAccessToken(context.env); const accessToken = await GetAccessToken(context.env);
const attachmentDeletePromises = []; const attachmentDeletePromises = [];
const existingAttachments = [...data.attachments]; const existingAttachments = [...(data.attachments as string[])];
for (const attachment of existingAttachments) { for (let i = 0; i < existingAttachments.length; i++) {
if (!attachment.startsWith("t/")) data.attachments.push(`t/${attachment}`); if (!existingAttachments[i].startsWith("t/"))
else data.attachments.push(attachment.replace("t/", "")); existingAttachments[i] = existingAttachments[i].replace("t/", "");
} }
for (const attachment of data.attachments) for (const attachment of existingAttachments)
attachmentDeletePromises.push( attachmentDeletePromises.push(
fetch( fetch(
`https://storage.googleapis.com/storage/v1/b/portal-carcrushers-cc/o/${encodeURIComponent( `https://storage.googleapis.com/storage/v1/b/portal-carcrushers-cc/o/${encodeURIComponent(
@@ -50,9 +51,11 @@ export async function onRequestPost(context: RequestContext) {
); );
await Promise.allSettled(attachmentDeletePromises); await Promise.allSettled(attachmentDeletePromises);
await context.env.D1.prepare("DELETE FROM reports WHERE id = ?;") await context.data.prisma.report.delete({
.bind(id) where: {
.run(); id,
},
});
return new Response(null, { return new Response(null, {
status: 204, status: 204,

View File

@@ -1,11 +1,18 @@
import { jsonResponse } from "../../common.js"; import { jsonResponse } from "../../common.js";
export async function onRequestGet(context: RequestContext) { export async function onRequestGet(context: RequestContext) {
const { results } = await context.env.D1.prepare( return jsonResponse(
"SELECT created_at, destination, path FROM short_links WHERE user = ?;", JSON.stringify(
) await context.data.prisma.shortLink.findMany({
.bind(context.data.current_user.id) select: {
.all(); created_at: true,
destination: true,
return jsonResponse(JSON.stringify(results)); path: true,
},
where: {
user: context.data.current_user.id,
},
}),
),
);
} }

View File

@@ -6,11 +6,14 @@ export async function onRequestPost(context: RequestContext) {
if (typeof path !== "string" || path.length > 256) if (typeof path !== "string" || path.length > 256)
return jsonError("Invalid path", 400); return jsonError("Invalid path", 400);
const result = await context.env.D1.prepare( const result = await context.data.prisma.shortLink.findUnique({
"SELECT path FROM short_links WHERE path = ?;", select: {
) path: true,
.bind(path) },
.first(); where: {
path,
},
});
if (result) if (result)
return jsonError( return jsonError(
@@ -78,11 +81,13 @@ export async function onRequestPost(context: RequestContext) {
400, 400,
); );
await context.env.D1.prepare( await context.data.prisma.shortLink.create({
"INSERT INTO short_links (created_at, destination, path, user) VALUES (?, ?, ?, ?);", data: {
) destination,
.bind(Date.now(), destination, path, context.data.current_user.id) path,
.run(); user: context.data.current_user.id,
},
});
return new Response(null, { return new Response(null, {
status: 204, status: 204,

View File

@@ -1,6 +1,9 @@
const DATASTORE_URL = const DATASTORE_URL =
"https://apis.roblox.com/cloud/v2/universes/274816972/data-stores/BanData/entries/CloudBanList"; "https://apis.roblox.com/cloud/v2/universes/274816972/data-stores/BanData/entries/CloudBanList";
const MESSAGING_SERVICE_URL =
"https://apis.roblox.com/cloud/v2/universes/274816972:publishMessage";
const SAVE_DATA_URL = const SAVE_DATA_URL =
"https://apis.roblox.com/cloud/v2/universes/274816972/data-stores/RealData/entries"; "https://apis.roblox.com/cloud/v2/universes/274816972/data-stores/RealData/entries";
@@ -31,6 +34,28 @@ export async function getBanList(context: RequestContext) {
}; };
} }
export async function publishMessage(
context: RequestContext,
topic: string,
message: string,
) {
const response = await fetch(MESSAGING_SERVICE_URL, {
body: JSON.stringify({
message,
topic,
}),
headers: {
"Content-Type": "application/json",
"x-api-key": context.env.ROBLOX_OPENCLOUD_KEY,
},
method: "POST",
});
if (!response.ok) {
throw new Error("Failed to publish message\n" + (await response.text()));
}
}
export async function getSaveData( export async function getSaveData(
context: RequestContext, context: RequestContext,
user: number, user: number,

View File

@@ -70,10 +70,6 @@ button:focus-visible {
padding: 2em; padding: 2em;
} }
::file-selector-button {
display: none;
}
.desktop-nav { .desktop-nav {
visibility: visible; visibility: visible;
} }

View File

@@ -0,0 +1,186 @@
-- CreateTable
CREATE TABLE "appeals" (
"approved" BOOLEAN,
"ban_reason" TEXT NOT NULL,
"created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"id" TEXT NOT NULL PRIMARY KEY,
"learned" TEXT NOT NULL,
"reason_for_unban" TEXT NOT NULL,
"user" JSONB NOT NULL
);
-- CreateTable
CREATE TABLE "appeal_bans" (
"created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"created_by" TEXT NOT NULL,
"user" TEXT NOT NULL PRIMARY KEY
);
-- CreateTable
CREATE TABLE "et_members" (
"created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"created_by" TEXT NOT NULL,
"id" TEXT NOT NULL PRIMARY KEY,
"is_management" BOOLEAN NOT NULL DEFAULT false,
"name" TEXT NOT NULL,
"points" INTEGER NOT NULL DEFAULT 0,
"roblox_id" INTEGER
);
-- CreateTable
CREATE TABLE "et_strikes" (
"created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"created_by" TEXT NOT NULL,
"id" TEXT NOT NULL PRIMARY KEY,
"reason" TEXT NOT NULL,
"user" TEXT NOT NULL
);
-- CreateTable
CREATE TABLE "events" (
"answer" TEXT,
"answered_at" DATETIME,
"approved" BOOLEAN NOT NULL DEFAULT false,
"created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"created_by" TEXT NOT NULL,
"day" INTEGER NOT NULL,
"details" TEXT NOT NULL,
"id" TEXT NOT NULL PRIMARY KEY,
"month" INTEGER NOT NULL,
"pending" BOOLEAN NOT NULL DEFAULT true,
"performed_at" DATETIME,
"reached_minimum_player_count" BOOLEAN NOT NULL DEFAULT false,
"type" TEXT NOT NULL,
"year" INTEGER NOT NULL
);
-- CreateTable
CREATE TABLE "game_appeals" (
"created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"id" TEXT NOT NULL PRIMARY KEY,
"reason_for_unban" TEXT NOT NULL,
"roblox_id" INTEGER NOT NULL,
"roblox_username" TEXT NOT NULL,
"type" TEXT NOT NULL,
"what_happened" TEXT NOT NULL
);
-- CreateTable
CREATE TABLE "game_mod_logs" (
"action" TEXT NOT NULL,
"evidence" TEXT NOT NULL,
"executed_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"executor" TEXT NOT NULL,
"id" TEXT NOT NULL PRIMARY KEY,
"target" INTEGER NOT NULL
);
-- CreateTable
CREATE TABLE "game_mod_notes" (
"content" TEXT NOT NULL,
"created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"created_by" TEXT NOT NULL,
"id" TEXT NOT NULL PRIMARY KEY,
"target" INTEGER NOT NULL
);
-- CreateTable
CREATE TABLE "inactivity_notices" (
"created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"decisions" JSONB NOT NULL,
"departments" JSONB NOT NULL DEFAULT [],
"end" TEXT NOT NULL,
"hiatus" BOOLEAN NOT NULL DEFAULT false,
"id" TEXT NOT NULL PRIMARY KEY,
"reason" TEXT NOT NULL,
"start" TEXT NOT NULL,
"user" JSONB NOT NULL
);
-- CreateTable
CREATE TABLE "push_notifications" (
"created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"event_id" TEXT NOT NULL,
"event_type" TEXT NOT NULL,
"token" TEXT NOT NULL
);
-- CreateTable
CREATE TABLE "reports" (
"attachments" JSONB NOT NULL DEFAULT [],
"created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"id" TEXT NOT NULL PRIMARY KEY,
"open" BOOLEAN NOT NULL DEFAULT true,
"target_ids" JSONB NOT NULL DEFAULT [],
"target_usernames" JSONB NOT NULL DEFAULT [],
"type" TEXT NOT NULL DEFAULT 'exploit',
"user" JSONB
);
-- CreateTable
CREATE TABLE "short_links" (
"created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"destination" TEXT NOT NULL,
"path" TEXT NOT NULL PRIMARY KEY,
"user" TEXT NOT NULL
);
-- CreateIndex
CREATE UNIQUE INDEX "appeals_id_key" ON "appeals"("id");
-- CreateIndex
CREATE INDEX "idx_appeals_approved_created_at" ON "appeals"("approved", "created_at");
-- CreateIndex
CREATE UNIQUE INDEX "appeal_bans_user_key" ON "appeal_bans"("user");
-- CreateIndex
CREATE UNIQUE INDEX "et_members_id_key" ON "et_members"("id");
-- CreateIndex
CREATE INDEX "idx_et_members_id_name" ON "et_members"("id", "name");
-- CreateIndex
CREATE UNIQUE INDEX "et_strikes_id_key" ON "et_strikes"("id");
-- CreateIndex
CREATE UNIQUE INDEX "events_id_key" ON "events"("id");
-- CreateIndex
CREATE INDEX "idx_events_month_year" ON "events"("month", "year");
-- CreateIndex
CREATE UNIQUE INDEX "game_appeals_id_key" ON "game_appeals"("id");
-- CreateIndex
CREATE INDEX "idx_game_appeals_created_at" ON "game_appeals"("created_at");
-- CreateIndex
CREATE UNIQUE INDEX "game_mod_logs_id_key" ON "game_mod_logs"("id");
-- CreateIndex
CREATE INDEX "idx_game_mod_logs_target" ON "game_mod_logs"("target");
-- CreateIndex
CREATE UNIQUE INDEX "game_mod_notes_id_key" ON "game_mod_notes"("id");
-- CreateIndex
CREATE INDEX "idx_game_mod_notes_target" ON "game_mod_notes"("target");
-- CreateIndex
CREATE UNIQUE INDEX "inactivity_notices_id_key" ON "inactivity_notices"("id");
-- CreateIndex
CREATE INDEX "idx_inactivity_notices_end_start" ON "inactivity_notices"("end", "start");
-- CreateIndex
CREATE UNIQUE INDEX "push_notifications_event_id_key" ON "push_notifications"("event_id");
-- CreateIndex
CREATE UNIQUE INDEX "reports_id_key" ON "reports"("id");
-- CreateIndex
CREATE INDEX "idx_reports_created_at_open" ON "reports"("created_at", "open");
-- CreateIndex
CREATE UNIQUE INDEX "short_links_path_key" ON "short_links"("path");

View File

@@ -0,0 +1,15 @@
-- CreateTable
CREATE TABLE "data_requests" (
"created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"id" TEXT NOT NULL PRIMARY KEY,
"originating_user" INTEGER,
"status" TEXT NOT NULL,
"target_user" INTEGER NOT NULL,
"type" TEXT NOT NULL
);
-- CreateIndex
CREATE UNIQUE INDEX "data_requests_id_key" ON "data_requests"("id");
-- CreateIndex
CREATE INDEX "idx_data_requests_created_at" ON "data_requests"("created_at");

538
package-lock.json generated
View File

@@ -12,13 +12,13 @@
"@emotion/react": "^11.14.0", "@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1", "@emotion/styled": "^11.14.1",
"@fontsource-variable/plus-jakarta-sans": "^5.2.8", "@fontsource-variable/plus-jakarta-sans": "^5.2.8",
"@prisma/adapter-d1": "^7.7.0", "@prisma/adapter-d1": "^7.8.0",
"@prisma/client": "^7.7.0", "@prisma/client": "^7.8.0",
"@remix-run/cloudflare": "^2.17.4", "@remix-run/cloudflare": "^2.17.4",
"@remix-run/cloudflare-pages": "^2.17.4", "@remix-run/cloudflare-pages": "^2.17.4",
"@remix-run/react": "^2.17.4", "@remix-run/react": "^2.17.4",
"@sentry/cloudflare": "^10.48.0", "@sentry/cloudflare": "^10.52.0",
"@sentry/remix": "^10.48.0", "@sentry/remix": "^10.52.0",
"aws4fetch": "^1.0.20", "aws4fetch": "^1.0.20",
"dayjs": "^1.11.20", "dayjs": "^1.11.20",
"framer-motion": "^12.38.0", "framer-motion": "^12.38.0",
@@ -28,13 +28,13 @@
}, },
"devDependencies": { "devDependencies": {
"@remix-run/dev": "^2.17.4", "@remix-run/dev": "^2.17.4",
"@types/node": "^24.12.2", "@types/node": "^24.12.3",
"@types/react": "^18.3.28", "@types/react": "^18.3.28",
"@types/react-big-calendar": "^1.16.3", "@types/react-big-calendar": "^1.16.3",
"@types/react-dom": "^18.3.7", "@types/react-dom": "^18.3.7",
"dotenv": "^17.4.1", "dotenv": "^17.4.1",
"prettier": "^3.8.2", "prettier": "^3.8.3",
"prisma": "^7.7.0", "prisma": "^7.8.0",
"typescript": "^5.9.3" "typescript": "^5.9.3"
} }
}, },
@@ -569,9 +569,9 @@
} }
}, },
"node_modules/@cloudflare/workers-types": { "node_modules/@cloudflare/workers-types": {
"version": "4.20260329.1", "version": "4.20260511.1",
"resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20260329.1.tgz", "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20260511.1.tgz",
"integrity": "sha512-LxBHrYYI/AZ6OCbUzRqRgg6Rt1qev2KxN2NNd3saye41AO2g52cYvHV+ohts5oPnrIUD7YRjbgN/J3NU7e7m5A==", "integrity": "sha512-FA+si7cOq9i/gtCHhIc0XJL0l1F/ApF+m00752Aj7WZFJrj3ZulT2T8/+rT3BabMT0QEnqFEGIqCgrmqhgEfMg==",
"license": "MIT OR Apache-2.0" "license": "MIT OR Apache-2.0"
}, },
"node_modules/@electric-sql/pglite": { "node_modules/@electric-sql/pglite": {
@@ -1177,9 +1177,9 @@
} }
}, },
"node_modules/@fastify/otel/node_modules/brace-expansion": { "node_modules/@fastify/otel/node_modules/brace-expansion": {
"version": "5.0.5", "version": "5.0.6",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
"integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"balanced-match": "^4.0.2" "balanced-match": "^4.0.2"
@@ -1413,22 +1413,10 @@
"node": ">=8.0.0" "node": ">=8.0.0"
} }
}, },
"node_modules/@opentelemetry/context-async-hooks": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.6.1.tgz",
"integrity": "sha512-XHzhwRNkBpeP8Fs/qjGrAf9r9PRv67wkJQ/7ZPaBQQ68DYlTBBx5MF9LvPx7mhuXcDessKK2b+DcxqwpgkcivQ==",
"license": "Apache-2.0",
"engines": {
"node": "^18.19.0 || >=20.6.0"
},
"peerDependencies": {
"@opentelemetry/api": ">=1.0.0 <1.10.0"
}
},
"node_modules/@opentelemetry/core": { "node_modules/@opentelemetry/core": {
"version": "2.6.1", "version": "2.7.1",
"resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.1.tgz", "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.7.1.tgz",
"integrity": "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g==", "integrity": "sha512-QAqIj32AtK6+pEVNG7EOVxHdE06RP+FM5qpiEJ4RtDcFIqKUZHYhl7/7UY5efhwmwNAg7j8QbJVBLxMerc0+gw==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@opentelemetry/semantic-conventions": "^1.29.0" "@opentelemetry/semantic-conventions": "^1.29.0"
@@ -1588,21 +1576,19 @@
"@opentelemetry/api": "^1.3.0" "@opentelemetry/api": "^1.3.0"
} }
}, },
"node_modules/@opentelemetry/instrumentation-ioredis": { "node_modules/@opentelemetry/instrumentation-http/node_modules/@opentelemetry/core": {
"version": "0.62.0", "version": "2.6.1",
"resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-ioredis/-/instrumentation-ioredis-0.62.0.tgz", "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.6.1.tgz",
"integrity": "sha512-ZYt//zcPve8qklaZX+5Z4MkU7UpEkFRrxsf2cnaKYBitqDnsCN69CPAuuMOX6NYdW2rG9sFy7V/QWtBlP5XiNQ==", "integrity": "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@opentelemetry/instrumentation": "^0.214.0", "@opentelemetry/semantic-conventions": "^1.29.0"
"@opentelemetry/redis-common": "^0.38.2",
"@opentelemetry/semantic-conventions": "^1.33.0"
}, },
"engines": { "engines": {
"node": "^18.19.0 || >=20.6.0" "node": "^18.19.0 || >=20.6.0"
}, },
"peerDependencies": { "peerDependencies": {
"@opentelemetry/api": "^1.3.0" "@opentelemetry/api": ">=1.0.0 <1.10.0"
} }
}, },
"node_modules/@opentelemetry/instrumentation-kafkajs": { "node_modules/@opentelemetry/instrumentation-kafkajs": {
@@ -1756,23 +1742,6 @@
"@opentelemetry/api": "^1.3.0" "@opentelemetry/api": "^1.3.0"
} }
}, },
"node_modules/@opentelemetry/instrumentation-redis": {
"version": "0.62.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-redis/-/instrumentation-redis-0.62.0.tgz",
"integrity": "sha512-y3pPpot7WzR/8JtHcYlTYsyY8g+pbFhAqbwAuG5bLPnR6v6pt1rQc0DpH0OlGP/9CZbWBP+Zhwp9yFoygf/ZXQ==",
"license": "Apache-2.0",
"dependencies": {
"@opentelemetry/instrumentation": "^0.214.0",
"@opentelemetry/redis-common": "^0.38.2",
"@opentelemetry/semantic-conventions": "^1.27.0"
},
"engines": {
"node": "^18.19.0 || >=20.6.0"
},
"peerDependencies": {
"@opentelemetry/api": "^1.3.0"
}
},
"node_modules/@opentelemetry/instrumentation-tedious": { "node_modules/@opentelemetry/instrumentation-tedious": {
"version": "0.33.0", "version": "0.33.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-tedious/-/instrumentation-tedious-0.33.0.tgz", "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-tedious/-/instrumentation-tedious-0.33.0.tgz",
@@ -1790,39 +1759,13 @@
"@opentelemetry/api": "^1.3.0" "@opentelemetry/api": "^1.3.0"
} }
}, },
"node_modules/@opentelemetry/instrumentation-undici": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-undici/-/instrumentation-undici-0.24.0.tgz",
"integrity": "sha512-oKzZ3uvqP17sV0EsoQcJgjEfIp0kiZRbYu/eD8p13Cbahumf8lb/xpYeNr/hfAJ4owzEtIDcGIjprfLcYbIKBQ==",
"license": "Apache-2.0",
"dependencies": {
"@opentelemetry/core": "^2.0.0",
"@opentelemetry/instrumentation": "^0.214.0",
"@opentelemetry/semantic-conventions": "^1.24.0"
},
"engines": {
"node": "^18.19.0 || >=20.6.0"
},
"peerDependencies": {
"@opentelemetry/api": "^1.7.0"
}
},
"node_modules/@opentelemetry/redis-common": {
"version": "0.38.2",
"resolved": "https://registry.npmjs.org/@opentelemetry/redis-common/-/redis-common-0.38.2.tgz",
"integrity": "sha512-1BCcU93iwSRZvDAgwUxC/DV4T/406SkMfxGqu5ojc3AvNI+I9GhV7v0J1HljsczuuhcnFLYqD5VmwVXfCGHzxA==",
"license": "Apache-2.0",
"engines": {
"node": "^18.19.0 || >=20.6.0"
}
},
"node_modules/@opentelemetry/resources": { "node_modules/@opentelemetry/resources": {
"version": "2.6.1", "version": "2.7.1",
"resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.6.1.tgz", "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.7.1.tgz",
"integrity": "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA==", "integrity": "sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@opentelemetry/core": "2.6.1", "@opentelemetry/core": "2.7.1",
"@opentelemetry/semantic-conventions": "^1.29.0" "@opentelemetry/semantic-conventions": "^1.29.0"
}, },
"engines": { "engines": {
@@ -1833,13 +1776,13 @@
} }
}, },
"node_modules/@opentelemetry/sdk-trace-base": { "node_modules/@opentelemetry/sdk-trace-base": {
"version": "2.6.1", "version": "2.7.1",
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.6.1.tgz", "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.7.1.tgz",
"integrity": "sha512-r86ut4T1e8vNwB35CqCcKd45yzqH6/6Wzvpk2/cZB8PsPLlZFTvrh8yfOS3CYZYcUmAx4hHTZJ8AO8Dj8nrdhw==", "integrity": "sha512-NAYIlsF8MPUsKqJMiDQJTMPOmlbawC1Iz/omMLygZ1C9am8fTKYjTaI+OZM+WTY3t3Glo0wnOg/6/pac6RGPPw==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@opentelemetry/core": "2.6.1", "@opentelemetry/core": "2.7.1",
"@opentelemetry/resources": "2.6.1", "@opentelemetry/resources": "2.7.1",
"@opentelemetry/semantic-conventions": "^1.29.0" "@opentelemetry/semantic-conventions": "^1.29.0"
}, },
"engines": { "engines": {
@@ -1891,23 +1834,23 @@
} }
}, },
"node_modules/@prisma/adapter-d1": { "node_modules/@prisma/adapter-d1": {
"version": "7.7.0", "version": "7.8.0",
"resolved": "https://registry.npmjs.org/@prisma/adapter-d1/-/adapter-d1-7.7.0.tgz", "resolved": "https://registry.npmjs.org/@prisma/adapter-d1/-/adapter-d1-7.8.0.tgz",
"integrity": "sha512-0+lJsHqm+TXxXdGCCMFouNcJMVE029g6td/vRM7EhWoJYtvakwCzdzJqCQEaGPjCAPi6lzar4DAtEi7z04qjIA==", "integrity": "sha512-JLmmuDKnPltHicr8+W0xOt4ozbTlAiaPEDe8YGAJIEgHmflOF7sr4Dsd8d+saRBKOARnODz/yxx+y+QMRm/e/g==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@cloudflare/workers-types": "^4.20251014.0", "@cloudflare/workers-types": "^4.20251014.0",
"@prisma/driver-adapter-utils": "7.7.0", "@prisma/driver-adapter-utils": "7.8.0",
"ky": "1.7.5" "ky": "1.7.5"
} }
}, },
"node_modules/@prisma/client": { "node_modules/@prisma/client": {
"version": "7.7.0", "version": "7.8.0",
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-7.7.0.tgz", "resolved": "https://registry.npmjs.org/@prisma/client/-/client-7.8.0.tgz",
"integrity": "sha512-5Ar4OsZpJ54s21sy5oDNNW9gQtd4NuxCaiM7+JDTOU07D6VvlpLjYzAVCMB1+JzokN+08dAVomlx+b7bhJd3ww==", "integrity": "sha512-HFp3Dawv/3sU3JtlPha90IB+48lS7zHiH4LKZPjmcE8YH5P9DOXGPvo8dqOtO7MqLDd1p2hOWMcFlRT1DMblHw==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@prisma/client-runtime-utils": "7.7.0" "@prisma/client-runtime-utils": "7.8.0"
}, },
"engines": { "engines": {
"node": "^20.19 || ^22.12 || >=24.0" "node": "^20.19 || ^22.12 || >=24.0"
@@ -1926,28 +1869,28 @@
} }
}, },
"node_modules/@prisma/client-runtime-utils": { "node_modules/@prisma/client-runtime-utils": {
"version": "7.7.0", "version": "7.8.0",
"resolved": "https://registry.npmjs.org/@prisma/client-runtime-utils/-/client-runtime-utils-7.7.0.tgz", "resolved": "https://registry.npmjs.org/@prisma/client-runtime-utils/-/client-runtime-utils-7.8.0.tgz",
"integrity": "sha512-BLyd0UpFYOtyJFTHm7jS9vesHW7P83abibodQMiIofqjBKzDHQ1VAsQkdfvXyYDkPlONPfOTz7/rv3x/+CQqvQ==", "integrity": "sha512-5NQZztQ0oY/ADFkmd9gPuweH5A1/CCY8YQPorLLO0Mu6a87mY5gsnDkzmFmIHs9NFaLnZojzgddFVN4RpKYrdw==",
"license": "Apache-2.0" "license": "Apache-2.0"
}, },
"node_modules/@prisma/config": { "node_modules/@prisma/config": {
"version": "7.7.0", "version": "7.8.0",
"resolved": "https://registry.npmjs.org/@prisma/config/-/config-7.7.0.tgz", "resolved": "https://registry.npmjs.org/@prisma/config/-/config-7.8.0.tgz",
"integrity": "sha512-hmPI3tKLO2aP0Y5vugbjcnA9qqlfJndiT6ds4tw28U5hNHLWg+mHJEWAhjsSPgxjtmxhJ/EDIeIlyh+3Us0OPg==", "integrity": "sha512-HFESzd9rx2ZQxlK+TL7tu1HPvCqrHiL6LCxYykI2c34mvaUuIVVl3lYuicJD/MNnzgPnyeBEMlK4WTomJCV5jw==",
"devOptional": true, "devOptional": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"c12": "3.1.0", "c12": "3.3.4",
"deepmerge-ts": "7.1.5", "deepmerge-ts": "7.1.5",
"effect": "3.20.0", "effect": "3.20.0",
"empathic": "2.0.0" "empathic": "2.0.0"
} }
}, },
"node_modules/@prisma/debug": { "node_modules/@prisma/debug": {
"version": "7.7.0", "version": "7.8.0",
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.7.0.tgz", "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.8.0.tgz",
"integrity": "sha512-12J62XdqCmpiwJHhHdQxZeY3ckVCWIFmcJP8hg5dPTceeiQ0wiojXGFYTluKqFQfu46fRLgb/rLALZMAx3+dTA==", "integrity": "sha512-p+QZReysDUqXC+mk17q9a+Y/qzh4c2KYliDK30buYUyfrGeTGSyfmc0AIrJRhZJrLHhRiJa9Au/J72h3C+szvA==",
"license": "Apache-2.0" "license": "Apache-2.0"
}, },
"node_modules/@prisma/dev": { "node_modules/@prisma/dev": {
@@ -1984,65 +1927,65 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@prisma/driver-adapter-utils": { "node_modules/@prisma/driver-adapter-utils": {
"version": "7.7.0", "version": "7.8.0",
"resolved": "https://registry.npmjs.org/@prisma/driver-adapter-utils/-/driver-adapter-utils-7.7.0.tgz", "resolved": "https://registry.npmjs.org/@prisma/driver-adapter-utils/-/driver-adapter-utils-7.8.0.tgz",
"integrity": "sha512-gZXREeu6mOk7zXfGFJgh86p7Vhj0sXNKp+4Cg1tWYo7V2dfncP2qxS2BiTmbIIha8xPqItkl0WSw38RuSq1HoQ==", "integrity": "sha512-/Q13o0ZT0rjc1Xk0Q9KhZYwuq2EW/vSbWUBKfgEKkaCuB/Sg6bqnjmTZqC5cD4d6y1vfFAEwBRzfzoSMIVJ55A==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@prisma/debug": "7.7.0" "@prisma/debug": "7.8.0"
} }
}, },
"node_modules/@prisma/engines": { "node_modules/@prisma/engines": {
"version": "7.7.0", "version": "7.8.0",
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-7.7.0.tgz", "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-7.8.0.tgz",
"integrity": "sha512-7fmcbT7HHXBq/b+3h/dO1JI3fd8l8q7erf7xP7pRprh58hmSSnG8mg9K3yjW3h9WaHWUwngVFpSxxxivaitQ2w==", "integrity": "sha512-jx3rCnNNrt5uzbkKlegtQ2GZHxSlihMCzutgT/BP6UIDF1r9tDI39hV/0T/cHZgzJ3ELbuQPXlVZy+Y1n0pcgw==",
"devOptional": true, "devOptional": true,
"hasInstallScript": true, "hasInstallScript": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@prisma/debug": "7.7.0", "@prisma/debug": "7.8.0",
"@prisma/engines-version": "7.6.0-1.75cbdc1eb7150937890ad5465d861175c6624711", "@prisma/engines-version": "7.8.0-6.3c6e192761c0362d496ed980de936e2f3cebcd3a",
"@prisma/fetch-engine": "7.7.0", "@prisma/fetch-engine": "7.8.0",
"@prisma/get-platform": "7.7.0" "@prisma/get-platform": "7.8.0"
} }
}, },
"node_modules/@prisma/engines-version": { "node_modules/@prisma/engines-version": {
"version": "7.6.0-1.75cbdc1eb7150937890ad5465d861175c6624711", "version": "7.8.0-6.3c6e192761c0362d496ed980de936e2f3cebcd3a",
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-7.6.0-1.75cbdc1eb7150937890ad5465d861175c6624711.tgz", "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-7.8.0-6.3c6e192761c0362d496ed980de936e2f3cebcd3a.tgz",
"integrity": "sha512-r51DLcJ8bDRSrBEJF3J4cinoWyGA7rfP2mG6lD90VqIbGNOkbfcLcXalSVjq5Y6brQS3vcjrq4GbyUb1Cb7vkw==", "integrity": "sha512-fJPQxCkLgA5EayWaW8eArgCvjJ+N+Kz3VyeNKMEeYiQC4alNkxRKFVAGxv/ZUzuJISKqdw+zGeDbS6mn6RCPOA==",
"devOptional": true, "devOptional": true,
"license": "Apache-2.0" "license": "Apache-2.0"
}, },
"node_modules/@prisma/engines/node_modules/@prisma/get-platform": { "node_modules/@prisma/engines/node_modules/@prisma/get-platform": {
"version": "7.7.0", "version": "7.8.0",
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.7.0.tgz", "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.8.0.tgz",
"integrity": "sha512-MEUNzvKxvYnJ7kgvd6oNRnMmmiGNS9TYLB2weMeIXplnHdL/UWEGnvavYGnN7KLJ2n0iI4dDAyzSkHI3c7AscQ==", "integrity": "sha512-WlxgRGnolL8VH2EmkH1R/DkKNr/mVdS3G2h42IZFFZ3eUrH9OT6t73kIOSlkkrv50wG123Iq8d96ufv5LlZktw==",
"devOptional": true, "devOptional": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@prisma/debug": "7.7.0" "@prisma/debug": "7.8.0"
} }
}, },
"node_modules/@prisma/fetch-engine": { "node_modules/@prisma/fetch-engine": {
"version": "7.7.0", "version": "7.8.0",
"resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-7.7.0.tgz", "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-7.8.0.tgz",
"integrity": "sha512-TfyzveBQoK4xALzsTpVhB/0KG1N8zOK0ap+RnBMkzGUu3f98fnQ4QtXa2wlKPhsO2X8a3N5ugFQgcKNoHGmDfw==", "integrity": "sha512-gwB0Euiz/DDRyxFRpLXYlK3RfaZUj1c5dAYMuhZYfApg7arknJlcb9bIsOHDppJmbqYaVA+yBIiFMDBfprsNPQ==",
"devOptional": true, "devOptional": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@prisma/debug": "7.7.0", "@prisma/debug": "7.8.0",
"@prisma/engines-version": "7.6.0-1.75cbdc1eb7150937890ad5465d861175c6624711", "@prisma/engines-version": "7.8.0-6.3c6e192761c0362d496ed980de936e2f3cebcd3a",
"@prisma/get-platform": "7.7.0" "@prisma/get-platform": "7.8.0"
} }
}, },
"node_modules/@prisma/fetch-engine/node_modules/@prisma/get-platform": { "node_modules/@prisma/fetch-engine/node_modules/@prisma/get-platform": {
"version": "7.7.0", "version": "7.8.0",
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.7.0.tgz", "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.8.0.tgz",
"integrity": "sha512-MEUNzvKxvYnJ7kgvd6oNRnMmmiGNS9TYLB2weMeIXplnHdL/UWEGnvavYGnN7KLJ2n0iI4dDAyzSkHI3c7AscQ==", "integrity": "sha512-WlxgRGnolL8VH2EmkH1R/DkKNr/mVdS3G2h42IZFFZ3eUrH9OT6t73kIOSlkkrv50wG123Iq8d96ufv5LlZktw==",
"devOptional": true, "devOptional": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@prisma/debug": "7.7.0" "@prisma/debug": "7.8.0"
} }
}, },
"node_modules/@prisma/get-platform": { "node_modules/@prisma/get-platform": {
@@ -2960,66 +2903,66 @@
] ]
}, },
"node_modules/@sentry-internal/browser-utils": { "node_modules/@sentry-internal/browser-utils": {
"version": "10.48.0", "version": "10.52.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-10.48.0.tgz", "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-10.52.0.tgz",
"integrity": "sha512-SCiTLBXzugFKxev6NoKYBIhQoDk0gUh0AVVVepCBqfCJiWBG01Zvv0R5tCVohr4cWRllkQ8mlBdNQd/I7s9tdA==", "integrity": "sha512-x/yEPZdpH6NGQeoeQnV9tj8reAH8twNttiltGZl2o8Rk7sQeUfe7E8yuYP2XbJ2RqyZK5qRS3COrNyMPzf6KFA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@sentry/core": "10.48.0" "@sentry/core": "10.52.0"
}, },
"engines": { "engines": {
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/@sentry-internal/feedback": { "node_modules/@sentry-internal/feedback": {
"version": "10.48.0", "version": "10.52.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-10.48.0.tgz", "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-10.52.0.tgz",
"integrity": "sha512-tGkEyOM1HDS9qebDphUMEnyk3qq/50AnuTBiFmMJyjNzowylVGmRRk0sr3xkmbVHCDXQCiYnDmSVlJ2x4SDMrQ==", "integrity": "sha512-5kAn1W8ZvCuHtEHXpq6iRkUMdNCilwww+YxaN2yofVrCivAbB3Ha5JJUMqmWOPW0pC27zGYmoJMIDvG+PczUxA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@sentry/core": "10.48.0" "@sentry/core": "10.52.0"
}, },
"engines": { "engines": {
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/@sentry-internal/replay": { "node_modules/@sentry-internal/replay": {
"version": "10.48.0", "version": "10.52.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-10.48.0.tgz", "resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-10.52.0.tgz",
"integrity": "sha512-sevRTePfuk4PNuz9KAKpmTZEomAU0aLXyIhOwA0OnUDdxPhkY8kq5lwDbuxTHv6DQUjUX3YgFbY45VH1JEqHKA==", "integrity": "sha512-diywyuc/H7VTUR+W5ryVmLF+0X4UP1OskMqb6V8RSAvJHcj2JmIm7uP+Fc6ACTno+b6AUShwT/L4xVXzO6X9Cw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@sentry-internal/browser-utils": "10.48.0", "@sentry-internal/browser-utils": "10.52.0",
"@sentry/core": "10.48.0" "@sentry/core": "10.52.0"
}, },
"engines": { "engines": {
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/@sentry-internal/replay-canvas": { "node_modules/@sentry-internal/replay-canvas": {
"version": "10.48.0", "version": "10.52.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-10.48.0.tgz", "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-10.52.0.tgz",
"integrity": "sha512-9nWuN2z4O+iwbTfuYV5ZmngBgJU/ZxfOo47A5RJP3Nu/kl59aJ1lUhILYOKyeNOIC/JyeERmpIcTxnlPXQzZ3Q==", "integrity": "sha512-BI5ie4dxPuUJ344CXVSnAxY1xZCbghglPSCIlTOYODpR9so9yo5IZh+Mwspt0oWsUMaxWJiQSNYlbPWi7WDavg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@sentry-internal/replay": "10.48.0", "@sentry-internal/replay": "10.52.0",
"@sentry/core": "10.48.0" "@sentry/core": "10.52.0"
}, },
"engines": { "engines": {
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/@sentry/browser": { "node_modules/@sentry/browser": {
"version": "10.48.0", "version": "10.52.0",
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-10.48.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-10.52.0.tgz",
"integrity": "sha512-4jt2zX2ExgFcNe2x+W+/k81fmDUsOrquGtt028CiGuDuma6kEsWBI4JbooT1jhj2T+eeUxe3YGbM23Zhh7Ghhw==", "integrity": "sha512-ijL9jN86oXwXQWbwhPlEb70ODJSEmjxQEQdnZkC4gDWbjswcwvRsVJPYk+1xl2ir2iZixRIHipVxDcLwian35g==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@sentry-internal/browser-utils": "10.48.0", "@sentry-internal/browser-utils": "10.52.0",
"@sentry-internal/feedback": "10.48.0", "@sentry-internal/feedback": "10.52.0",
"@sentry-internal/replay": "10.48.0", "@sentry-internal/replay": "10.52.0",
"@sentry-internal/replay-canvas": "10.48.0", "@sentry-internal/replay-canvas": "10.52.0",
"@sentry/core": "10.48.0" "@sentry/core": "10.52.0"
}, },
"engines": { "engines": {
"node": ">=18" "node": ">=18"
@@ -3206,13 +3149,13 @@
} }
}, },
"node_modules/@sentry/cloudflare": { "node_modules/@sentry/cloudflare": {
"version": "10.48.0", "version": "10.52.0",
"resolved": "https://registry.npmjs.org/@sentry/cloudflare/-/cloudflare-10.48.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/cloudflare/-/cloudflare-10.52.0.tgz",
"integrity": "sha512-i02Ps4/cJjFpbcHLMhNEFXTeVqLB9XpB3+/OFQ9aMFV3yDcxlvHwe0oo7WZf41iroArvpysotLG8Y8NBOU9omA==", "integrity": "sha512-NhFpR0OKd7gZDuJWJd8C0jC6WhA+R+m1OGp0ul2ItwIASTzGMw6gGLo0XuQjC6GfuodNAGbCF1bQN3Zi3nV1Lg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@opentelemetry/api": "^1.9.1", "@opentelemetry/api": "^1.9.1",
"@sentry/core": "10.48.0" "@sentry/core": "10.52.0"
}, },
"engines": { "engines": {
"node": ">=18" "node": ">=18"
@@ -3227,23 +3170,22 @@
} }
}, },
"node_modules/@sentry/core": { "node_modules/@sentry/core": {
"version": "10.48.0", "version": "10.52.0",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.48.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.52.0.tgz",
"integrity": "sha512-h8F+fXVwYC9ro5ZaO8V+v3vqc0awlXHGblEAuVxSGgh4IV/oFX+QVzXeDTTrFOFS6v/Vn5vAyu240eJrJAS6/g==", "integrity": "sha512-VA/kAqLhkMnRWY2RXdBLyTemR9D4m7MVRy/gyapoq9yvllVPx9WXbvKgnMP2LQp7mFgT/oLFvw58aQKaYTGn3A==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/@sentry/node": { "node_modules/@sentry/node": {
"version": "10.48.0", "version": "10.52.0",
"resolved": "https://registry.npmjs.org/@sentry/node/-/node-10.48.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/node/-/node-10.52.0.tgz",
"integrity": "sha512-MzyLJyYmr0Qg60K6NJ2EdwJUX1OuAYXs9tyYxnqVO3nJ8MyYwIcuN4FCYEnXkG6Jiy/4q7OuZgXWnfdQJVcaqw==", "integrity": "sha512-9+p3KJUk3rHO1HOEZuSknP2RgKCJZONDm4HWgkVDtVBtocb66KLtVlMjc59d2/bWP7tM3wc877tpG30quFfU9g==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@fastify/otel": "0.18.0", "@fastify/otel": "0.18.0",
"@opentelemetry/api": "^1.9.1", "@opentelemetry/api": "^1.9.1",
"@opentelemetry/context-async-hooks": "^2.6.1",
"@opentelemetry/core": "^2.6.1", "@opentelemetry/core": "^2.6.1",
"@opentelemetry/instrumentation": "^0.214.0", "@opentelemetry/instrumentation": "^0.214.0",
"@opentelemetry/instrumentation-amqplib": "0.61.0", "@opentelemetry/instrumentation-amqplib": "0.61.0",
@@ -3254,7 +3196,6 @@
"@opentelemetry/instrumentation-graphql": "0.62.0", "@opentelemetry/instrumentation-graphql": "0.62.0",
"@opentelemetry/instrumentation-hapi": "0.60.0", "@opentelemetry/instrumentation-hapi": "0.60.0",
"@opentelemetry/instrumentation-http": "0.214.0", "@opentelemetry/instrumentation-http": "0.214.0",
"@opentelemetry/instrumentation-ioredis": "0.62.0",
"@opentelemetry/instrumentation-kafkajs": "0.23.0", "@opentelemetry/instrumentation-kafkajs": "0.23.0",
"@opentelemetry/instrumentation-knex": "0.58.0", "@opentelemetry/instrumentation-knex": "0.58.0",
"@opentelemetry/instrumentation-koa": "0.62.0", "@opentelemetry/instrumentation-koa": "0.62.0",
@@ -3264,16 +3205,13 @@
"@opentelemetry/instrumentation-mysql": "0.60.0", "@opentelemetry/instrumentation-mysql": "0.60.0",
"@opentelemetry/instrumentation-mysql2": "0.60.0", "@opentelemetry/instrumentation-mysql2": "0.60.0",
"@opentelemetry/instrumentation-pg": "0.66.0", "@opentelemetry/instrumentation-pg": "0.66.0",
"@opentelemetry/instrumentation-redis": "0.62.0",
"@opentelemetry/instrumentation-tedious": "0.33.0", "@opentelemetry/instrumentation-tedious": "0.33.0",
"@opentelemetry/instrumentation-undici": "0.24.0",
"@opentelemetry/resources": "^2.6.1",
"@opentelemetry/sdk-trace-base": "^2.6.1", "@opentelemetry/sdk-trace-base": "^2.6.1",
"@opentelemetry/semantic-conventions": "^1.40.0", "@opentelemetry/semantic-conventions": "^1.40.0",
"@prisma/instrumentation": "7.6.0", "@prisma/instrumentation": "7.6.0",
"@sentry/core": "10.48.0", "@sentry/core": "10.52.0",
"@sentry/node-core": "10.48.0", "@sentry/node-core": "10.52.0",
"@sentry/opentelemetry": "10.48.0", "@sentry/opentelemetry": "10.52.0",
"import-in-the-middle": "^3.0.0" "import-in-the-middle": "^3.0.0"
}, },
"engines": { "engines": {
@@ -3281,13 +3219,13 @@
} }
}, },
"node_modules/@sentry/node-core": { "node_modules/@sentry/node-core": {
"version": "10.48.0", "version": "10.52.0",
"resolved": "https://registry.npmjs.org/@sentry/node-core/-/node-core-10.48.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/node-core/-/node-core-10.52.0.tgz",
"integrity": "sha512-D1TnPhN6vhrRqJ+bN+rdXDM+INibI6lNBm0eGx45zz7DBx9ouq2e9gm/DPx+y/hAkYYq0qTd6x84cGxtVZbKLw==", "integrity": "sha512-IG7MBtLRPQ2LuU+kbD14AFZroZgAeUmJQTP1FI/F8n56O31+p+9R703LuBTpvZr6sm+eRYDMWcGYYkfLHRVjwg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@sentry/core": "10.48.0", "@sentry/core": "10.52.0",
"@sentry/opentelemetry": "10.48.0", "@sentry/opentelemetry": "10.52.0",
"import-in-the-middle": "^3.0.0" "import-in-the-middle": "^3.0.0"
}, },
"engines": { "engines": {
@@ -3295,11 +3233,9 @@
}, },
"peerDependencies": { "peerDependencies": {
"@opentelemetry/api": "^1.9.0", "@opentelemetry/api": "^1.9.0",
"@opentelemetry/context-async-hooks": "^1.30.1 || ^2.1.0",
"@opentelemetry/core": "^1.30.1 || ^2.1.0", "@opentelemetry/core": "^1.30.1 || ^2.1.0",
"@opentelemetry/exporter-trace-otlp-http": ">=0.57.0 <1", "@opentelemetry/exporter-trace-otlp-http": ">=0.57.0 <1",
"@opentelemetry/instrumentation": ">=0.57.1 <1", "@opentelemetry/instrumentation": ">=0.57.1 <1",
"@opentelemetry/resources": "^1.30.1 || ^2.1.0",
"@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.1.0", "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.1.0",
"@opentelemetry/semantic-conventions": "^1.39.0" "@opentelemetry/semantic-conventions": "^1.39.0"
}, },
@@ -3307,9 +3243,6 @@
"@opentelemetry/api": { "@opentelemetry/api": {
"optional": true "optional": true
}, },
"@opentelemetry/context-async-hooks": {
"optional": true
},
"@opentelemetry/core": { "@opentelemetry/core": {
"optional": true "optional": true
}, },
@@ -3319,9 +3252,6 @@
"@opentelemetry/instrumentation": { "@opentelemetry/instrumentation": {
"optional": true "optional": true
}, },
"@opentelemetry/resources": {
"optional": true
},
"@opentelemetry/sdk-trace-base": { "@opentelemetry/sdk-trace-base": {
"optional": true "optional": true
}, },
@@ -3331,32 +3261,31 @@
} }
}, },
"node_modules/@sentry/opentelemetry": { "node_modules/@sentry/opentelemetry": {
"version": "10.48.0", "version": "10.52.0",
"resolved": "https://registry.npmjs.org/@sentry/opentelemetry/-/opentelemetry-10.48.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/opentelemetry/-/opentelemetry-10.52.0.tgz",
"integrity": "sha512-Tn6Y0PZjRJ7OW8loK1ntK7wnJnIINnCfSpnwuqow0FMblaDmu5jDVOYq0U1SJBoBcMD5j9aSqrwyj6zqKwjc0A==", "integrity": "sha512-Sc7StsvC0bwhMcgDfTRWUIexO5cNzzKUurvUwtpgQUnxO7AzexU3lkY3yHYDsCbWYAEQMXAgQYQtbcqoh+Ie7g==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@sentry/core": "10.48.0" "@sentry/core": "10.52.0"
}, },
"engines": { "engines": {
"node": ">=18" "node": ">=18"
}, },
"peerDependencies": { "peerDependencies": {
"@opentelemetry/api": "^1.9.0", "@opentelemetry/api": "^1.9.0",
"@opentelemetry/context-async-hooks": "^1.30.1 || ^2.1.0",
"@opentelemetry/core": "^1.30.1 || ^2.1.0", "@opentelemetry/core": "^1.30.1 || ^2.1.0",
"@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.1.0", "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.1.0",
"@opentelemetry/semantic-conventions": "^1.39.0" "@opentelemetry/semantic-conventions": "^1.39.0"
} }
}, },
"node_modules/@sentry/react": { "node_modules/@sentry/react": {
"version": "10.48.0", "version": "10.52.0",
"resolved": "https://registry.npmjs.org/@sentry/react/-/react-10.48.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/react/-/react-10.52.0.tgz",
"integrity": "sha512-uc93vKjmu6gNns+JAX4qquuxWpAMit0uGPA1TYlMjct9NG1uX3TkDPJAr9Pgd1lOXx8mKqCmj5fK33QeExMpPw==", "integrity": "sha512-2m72QCsja2cJJHD0ALxRnVt0qMEC2FV4LSi6AAiEdEG4lTb6mgcxavx5pJrW90jE+6dMGPbUz4q8c9vi4jh1qQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@sentry/browser": "10.48.0", "@sentry/browser": "10.52.0",
"@sentry/core": "10.48.0" "@sentry/core": "10.52.0"
}, },
"engines": { "engines": {
"node": ">=18" "node": ">=18"
@@ -3366,9 +3295,9 @@
} }
}, },
"node_modules/@sentry/remix": { "node_modules/@sentry/remix": {
"version": "10.48.0", "version": "10.52.0",
"resolved": "https://registry.npmjs.org/@sentry/remix/-/remix-10.48.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/remix/-/remix-10.52.0.tgz",
"integrity": "sha512-ToJ1j9x0tHNM78UrdbjMDNL31nvbRUDdE2rTfeUEt7cQUo7vio68mmqrdnrIo0j6g5rwewWPXkNP+ENBeuzLOQ==", "integrity": "sha512-k6WguxtHupGKDlYW49kJAi7gWUt7uEWP3EpJ2hzdpIZwARcU4Mv5aCZm0tgtQs5O9c+Gq9EJsr+ASoDgM39Lng==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@opentelemetry/api": "^1.9.1", "@opentelemetry/api": "^1.9.1",
@@ -3376,9 +3305,9 @@
"@opentelemetry/semantic-conventions": "^1.40.0", "@opentelemetry/semantic-conventions": "^1.40.0",
"@remix-run/router": "^1.23.2", "@remix-run/router": "^1.23.2",
"@sentry/cli": "^2.58.5", "@sentry/cli": "^2.58.5",
"@sentry/core": "10.48.0", "@sentry/core": "10.52.0",
"@sentry/node": "10.48.0", "@sentry/node": "10.52.0",
"@sentry/react": "10.48.0", "@sentry/react": "10.52.0",
"yargs": "^17.6.0" "yargs": "^17.6.0"
}, },
"bin": { "bin": {
@@ -3495,9 +3424,9 @@
} }
}, },
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "24.12.2", "version": "24.12.3",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.2.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.3.tgz",
"integrity": "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==", "integrity": "sha512-8oljBDGun9cIsZRJR6fkihn0TSXJI0UDOOhncYaERq6M0JMDoPLxyscwruJcb4GKS6dvK/d8xebYBg27h/duaQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"undici-types": "~7.16.0" "undici-types": "~7.16.0"
@@ -4082,27 +4011,27 @@
} }
}, },
"node_modules/c12": { "node_modules/c12": {
"version": "3.1.0", "version": "3.3.4",
"resolved": "https://registry.npmjs.org/c12/-/c12-3.1.0.tgz", "resolved": "https://registry.npmjs.org/c12/-/c12-3.3.4.tgz",
"integrity": "sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==", "integrity": "sha512-cM0ApFQSBXuourJejzwv/AuPRvAxordTyParRVcHjjtXirtkzM0uK2L9TTn9s0cXZbG7E55jCivRQzoxYmRAlA==",
"devOptional": true, "devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"chokidar": "^4.0.3", "chokidar": "^5.0.0",
"confbox": "^0.2.2", "confbox": "^0.2.4",
"defu": "^6.1.4", "defu": "^6.1.6",
"dotenv": "^16.6.1", "dotenv": "^17.3.1",
"exsolve": "^1.0.7", "exsolve": "^1.0.8",
"giget": "^2.0.0", "giget": "^3.2.0",
"jiti": "^2.4.2", "jiti": "^2.6.1",
"ohash": "^2.0.11", "ohash": "^2.0.11",
"pathe": "^2.0.3", "pathe": "^2.0.3",
"perfect-debounce": "^1.0.0", "perfect-debounce": "^2.1.0",
"pkg-types": "^2.2.0", "pkg-types": "^2.3.0",
"rc9": "^2.1.2" "rc9": "^3.0.1"
}, },
"peerDependencies": { "peerDependencies": {
"magicast": "^0.3.5" "magicast": "*"
}, },
"peerDependenciesMeta": { "peerDependenciesMeta": {
"magicast": { "magicast": {
@@ -4111,34 +4040,21 @@
} }
}, },
"node_modules/c12/node_modules/chokidar": { "node_modules/c12/node_modules/chokidar": {
"version": "4.0.3", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz",
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==",
"devOptional": true, "devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"readdirp": "^4.0.1" "readdirp": "^5.0.0"
}, },
"engines": { "engines": {
"node": ">= 14.16.0" "node": ">= 20.19.0"
}, },
"funding": { "funding": {
"url": "https://paulmillr.com/funding/" "url": "https://paulmillr.com/funding/"
} }
}, },
"node_modules/c12/node_modules/dotenv": {
"version": "16.6.1",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
"integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==",
"devOptional": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://dotenvx.com"
}
},
"node_modules/c12/node_modules/pathe": { "node_modules/c12/node_modules/pathe": {
"version": "2.0.3", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
@@ -4147,13 +4063,13 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/c12/node_modules/readdirp": { "node_modules/c12/node_modules/readdirp": {
"version": "4.1.2", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz",
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==",
"devOptional": true, "devOptional": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 14.18.0" "node": ">= 20.19.0"
}, },
"funding": { "funding": {
"type": "individual", "type": "individual",
@@ -4369,16 +4285,6 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/citty": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz",
"integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"consola": "^3.2.3"
}
},
"node_modules/cjs-module-lexer": { "node_modules/cjs-module-lexer": {
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz",
@@ -4534,16 +4440,6 @@
"devOptional": true, "devOptional": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/consola": {
"version": "3.4.2",
"resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz",
"integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==",
"devOptional": true,
"license": "MIT",
"engines": {
"node": "^14.18.0 || >=16.10.0"
}
},
"node_modules/content-disposition": { "node_modules/content-disposition": {
"version": "0.5.4", "version": "0.5.4",
"dev": true, "dev": true,
@@ -4838,7 +4734,7 @@
"version": "17.4.1", "version": "17.4.1",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.1.tgz", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.1.tgz",
"integrity": "sha512-k8DaKGP6r1G30Lx8V4+pCsLzKr8vLmV2paqEj1Y55GdAgJuIqpRp5FfajGF8KtwMxCz9qJc6wUIJnm053d/WCw==", "integrity": "sha512-k8DaKGP6r1G30Lx8V4+pCsLzKr8vLmV2paqEj1Y55GdAgJuIqpRp5FfajGF8KtwMxCz9qJc6wUIJnm053d/WCw==",
"dev": true, "devOptional": true,
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"engines": { "engines": {
"node": ">=12" "node": ">=12"
@@ -5701,30 +5597,15 @@
} }
}, },
"node_modules/giget": { "node_modules/giget": {
"version": "2.0.0", "version": "3.2.0",
"resolved": "https://registry.npmjs.org/giget/-/giget-2.0.0.tgz", "resolved": "https://registry.npmjs.org/giget/-/giget-3.2.0.tgz",
"integrity": "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==", "integrity": "sha512-GvHTWcykIR/fP8cj8dMpuMMkvaeJfPvYnhq0oW+chSeIr+ldX21ifU2Ms6KBoyKZQZmVaUAAhQ2EZ68KJF8a7A==",
"devOptional": true, "devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": {
"citty": "^0.1.6",
"consola": "^3.4.0",
"defu": "^6.1.4",
"node-fetch-native": "^1.6.6",
"nypm": "^0.6.0",
"pathe": "^2.0.3"
},
"bin": { "bin": {
"giget": "dist/cli.mjs" "giget": "dist/cli.mjs"
} }
}, },
"node_modules/giget/node_modules/pathe": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
"integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
"devOptional": true,
"license": "MIT"
},
"node_modules/glob": { "node_modules/glob": {
"version": "10.5.0", "version": "10.5.0",
"dev": true, "dev": true,
@@ -6374,9 +6255,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/jiti": { "node_modules/jiti": {
"version": "2.6.1", "version": "2.7.0",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz",
"integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==",
"devOptional": true, "devOptional": true,
"license": "MIT", "license": "MIT",
"bin": { "bin": {
@@ -7819,13 +7700,6 @@
} }
} }
}, },
"node_modules/node-fetch-native": {
"version": "1.6.7",
"resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz",
"integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==",
"devOptional": true,
"license": "MIT"
},
"node_modules/node-releases": { "node_modules/node-releases": {
"version": "2.0.27", "version": "2.0.27",
"dev": true, "dev": true,
@@ -7911,38 +7785,6 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/nypm": {
"version": "0.6.5",
"resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.5.tgz",
"integrity": "sha512-K6AJy1GMVyfyMXRVB88700BJqNUkByijGJM8kEHpLdcAt+vSQAVfkWWHYzuRXHSY6xA2sNc5RjTj0p9rE2izVQ==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"citty": "^0.2.0",
"pathe": "^2.0.3",
"tinyexec": "^1.0.2"
},
"bin": {
"nypm": "dist/cli.mjs"
},
"engines": {
"node": ">=18"
}
},
"node_modules/nypm/node_modules/citty": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/citty/-/citty-0.2.2.tgz",
"integrity": "sha512-+6vJA3L98yv+IdfKGZHBNiGW5KHn22e/JwID0Strsz8h4S/csAu/OuICwxrg44k5MRiZHWIo8XXuJgQTriRP4w==",
"devOptional": true,
"license": "MIT"
},
"node_modules/nypm/node_modules/pathe": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
"integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
"devOptional": true,
"license": "MIT"
},
"node_modules/object-assign": { "node_modules/object-assign": {
"version": "4.1.1", "version": "4.1.1",
"license": "MIT", "license": "MIT",
@@ -8231,9 +8073,9 @@
} }
}, },
"node_modules/perfect-debounce": { "node_modules/perfect-debounce": {
"version": "1.0.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.1.0.tgz",
"integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==",
"devOptional": true, "devOptional": true,
"license": "MIT" "license": "MIT"
}, },
@@ -8556,9 +8398,9 @@
} }
}, },
"node_modules/prettier": { "node_modules/prettier": {
"version": "3.8.2", "version": "3.8.3",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.2.tgz", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz",
"integrity": "sha512-8c3mgTe0ASwWAJK+78dpviD+A8EqhndQPUBpNUIPt6+xWlIigCwfN01lWr9MAede4uqXGTEKeQWTvzb3vjia0Q==", "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"bin": { "bin": {
@@ -8586,16 +8428,16 @@
} }
}, },
"node_modules/prisma": { "node_modules/prisma": {
"version": "7.7.0", "version": "7.8.0",
"resolved": "https://registry.npmjs.org/prisma/-/prisma-7.7.0.tgz", "resolved": "https://registry.npmjs.org/prisma/-/prisma-7.8.0.tgz",
"integrity": "sha512-HlgwRBt1uEFB9LStHL4HLYDvoi4BNu1rYA0hPG0zCAEyK9SaZBqp7E5Rjpc3Qh8Lex/ye/svoHZ0OWoFNhWxuQ==", "integrity": "sha512-yfN4yrw7HV9kEJhoy1+jgah0jafEIQsf7uWouSsM8MvJtlubsk+kM7AIBWZ8+GJl74Yj3c+nbYqBkMOxtsZ3Lw==",
"devOptional": true, "devOptional": true,
"hasInstallScript": true, "hasInstallScript": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@prisma/config": "7.7.0", "@prisma/config": "7.8.0",
"@prisma/dev": "0.24.3", "@prisma/dev": "0.24.3",
"@prisma/engines": "7.7.0", "@prisma/engines": "7.8.0",
"@prisma/studio-core": "0.27.3", "@prisma/studio-core": "0.27.3",
"mysql2": "3.15.3", "mysql2": "3.15.3",
"postgres": "3.4.7" "postgres": "3.4.7"
@@ -8794,14 +8636,14 @@
} }
}, },
"node_modules/rc9": { "node_modules/rc9": {
"version": "2.1.2", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz", "resolved": "https://registry.npmjs.org/rc9/-/rc9-3.0.1.tgz",
"integrity": "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==", "integrity": "sha512-gMDyleLWVE+i6Sgtc0QbbY6pEKqYs97NGi6isHQPqYlLemPoO8dxQ3uGi0f4NiP98c+jMW6cG1Kx9dDwfvqARQ==",
"devOptional": true, "devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"defu": "^6.1.4", "defu": "^6.1.6",
"destr": "^2.0.3" "destr": "^2.0.5"
} }
}, },
"node_modules/react": { "node_modules/react": {
@@ -9920,16 +9762,6 @@
"safe-buffer": "~5.1.0" "safe-buffer": "~5.1.0"
} }
}, },
"node_modules/tinyexec": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.1.tgz",
"integrity": "sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==",
"devOptional": true,
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/to-regex-range": { "node_modules/to-regex-range": {
"version": "5.0.1", "version": "5.0.1",
"dev": true, "dev": true,

View File

@@ -7,20 +7,20 @@
"build": "remix build --sourcemap", "build": "remix build --sourcemap",
"check-format": "prettier -c .", "check-format": "prettier -c .",
"format": "prettier -wc .", "format": "prettier -wc .",
"publish": "remix build --sourcemap && wrangler pages deploy public" "publish": "remix build --sourcemap && wrangler pages deploy --upload-source-maps public"
}, },
"dependencies": { "dependencies": {
"@chakra-ui/react": "^2.10.9", "@chakra-ui/react": "^2.10.9",
"@emotion/react": "^11.14.0", "@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1", "@emotion/styled": "^11.14.1",
"@fontsource-variable/plus-jakarta-sans": "^5.2.8", "@fontsource-variable/plus-jakarta-sans": "^5.2.8",
"@prisma/adapter-d1": "^7.7.0", "@prisma/adapter-d1": "^7.8.0",
"@prisma/client": "^7.7.0", "@prisma/client": "^7.8.0",
"@remix-run/cloudflare": "^2.17.4", "@remix-run/cloudflare": "^2.17.4",
"@remix-run/cloudflare-pages": "^2.17.4", "@remix-run/cloudflare-pages": "^2.17.4",
"@remix-run/react": "^2.17.4", "@remix-run/react": "^2.17.4",
"@sentry/cloudflare": "^10.48.0", "@sentry/cloudflare": "^10.52.0",
"@sentry/remix": "^10.48.0", "@sentry/remix": "^10.52.0",
"aws4fetch": "^1.0.20", "aws4fetch": "^1.0.20",
"dayjs": "^1.11.20", "dayjs": "^1.11.20",
"framer-motion": "^12.38.0", "framer-motion": "^12.38.0",
@@ -30,17 +30,17 @@
}, },
"devDependencies": { "devDependencies": {
"@remix-run/dev": "^2.17.4", "@remix-run/dev": "^2.17.4",
"@types/node": "^24.12.2", "@types/node": "^24.12.3",
"@types/react": "^18.3.28", "@types/react": "^18.3.28",
"@types/react-big-calendar": "^1.16.3", "@types/react-big-calendar": "^1.16.3",
"@types/react-dom": "^18.3.7", "@types/react-dom": "^18.3.7",
"dotenv": "^17.4.1", "dotenv": "^17.4.1",
"prettier": "^3.8.2", "prettier": "^3.8.3",
"prisma": "^7.7.0", "prisma": "^7.8.0",
"typescript": "^5.9.3" "typescript": "^5.9.3"
}, },
"overrides": { "overrides": {
"@cloudflare/workers-types": "^4.20260329.1" "@cloudflare/workers-types": "^4.20260511.1"
}, },
"prettier": { "prettier": {
"endOfLine": "auto" "endOfLine": "auto"

View File

@@ -29,6 +29,18 @@ model AppealBan {
@@map("appeal_bans") @@map("appeal_bans")
} }
model DataRequest {
created_at DateTime @default(now())
id String @id @unique
originating_user Int?
status String
target_user Int
type String
@@index([created_at], name: "idx_data_requests_created_at")
@@map("data_requests")
}
model EtMember { model EtMember {
created_at DateTime @default(now()) created_at DateTime @default(now())
created_by String created_by String
@@ -149,7 +161,7 @@ model Report {
model ShortLink { model ShortLink {
created_at DateTime @default(now()) created_at DateTime @default(now())
destination String destination String
path String @unique path String @id @unique
user String user String
@@map("short_links") @@map("short_links")