diff --git a/app/lib/constants.ts b/app/lib/constants.ts index 764d47b..9f6a28e 100644 --- a/app/lib/constants.ts +++ b/app/lib/constants.ts @@ -1,10 +1,11 @@ -export const VERSION = '1.4.0' +export const VERSION = '1.5.0' export const RequiredVersions = { controller: VERSION, tts: '2.0.0', piper: '1.3.0', - sounder: '2.1.0' + sounder: '2.1.0', + button: '1.0.0' } export const DOCS_URL = `https://openschoolbell.co.uk` diff --git a/app/lib/hooks/use-live-data.ts b/app/lib/hooks/use-live-data.ts new file mode 100644 index 0000000..680967d --- /dev/null +++ b/app/lib/hooks/use-live-data.ts @@ -0,0 +1,32 @@ +import {useRevalidator} from '@remix-run/react' +import {useEffect, useRef} from 'react' + +function useInterval(callback: () => void, delay: number) { + const savedCallback = useRef<() => void>() + + // Remember the latest callback. + useEffect(() => { + savedCallback.current = callback + }, [callback]) + + // Set up the interval. + useEffect(() => { + function tick() { + savedCallback.current!() + } + if (delay !== null) { + let id = setInterval(tick, delay) + return () => clearInterval(id) + } + }, [delay]) +} + +export const useLivePageData = (interval: number = 5000) => { + const revalidator = useRevalidator() + + useInterval(() => { + if (revalidator.state === 'idle') { + revalidator.revalidate() + } + }, interval) +} diff --git a/app/locales/en.ts b/app/locales/en.ts index 91a8c49..cbca71d 100644 --- a/app/locales/en.ts +++ b/app/locales/en.ts @@ -31,6 +31,7 @@ export const en = { 'Are you sure you want to disable lockdown?', 'dashboard.lockdown.button.enable': 'Enable', 'dashboard.lockdown.button.disable': 'Disable', + 'dashboard.log': 'Log', 'about.title': 'About', 'about.table.component': 'Component', 'about.table.version': 'Version', @@ -343,7 +344,54 @@ export const en = { 'zones.edit.pageTitle': 'Edit zone {{name}}', 'zones.detail.metaFallback': 'Zone', 'zones.detail.soundersTitle': 'Sounders', - 'zones.detail.editButton': 'Edit zone' + 'zones.detail.editButton': 'Edit zone', + 'buttons.titleWithCount': 'Buttons ({{count}})', + 'buttons.table.device': 'Button', + 'buttons.table.action': 'Action', + 'nav.buttons': 'Buttons', + 'buttons.addButton': 'Add Button', + 'buttons.metaTitle': 'Buttons', + 'buttons.add.pageTitle': 'Add Button', + 'buttons.form.name.label': 'Name', + 'buttons.form.name.helper': 'Descriptive name of the button.', + 'buttons.form.ip.label': 'IP address', + 'buttons.form.ip.helper': + 'IP address where the controller can reach the button.', + 'buttons.form.action.label': 'Action', + 'buttons.form.action.helper': 'The action to be triggered by this button', + 'buttons.form.zone.label': 'Zone', + 'buttons.form.zone.helper': 'The zone to use when triggering the action.', + 'buttons.form.ledPin.label': 'LED Pin', + 'buttons.form.ledPin.helper': 'The GPIO output pin connected to the LED.', + 'buttons.form.buttonPin.label': 'Button Pin', + 'buttons.form.buttonPin.helper': + 'The GPIO input pin connected to the Button.', + 'buttons.form.holdDuration.label': 'Hold Duration', + 'buttons.form.holdDuration.helper': + 'How long in seconds should the button be held to trigger the action.', + 'buttons.form.cancelDuration.label': 'Cancel Duration', + 'buttons.form.cancelDuration.helper': + 'How long in seconds does the user have to cancel the trigger.', + 'buttons.add.submit': 'Add Button', + 'buttons.edit.submit': 'Update Button', + 'buttons.deleteConfirmation': + 'Are you sure you want to delete the button {{name}}?', + 'buttons.detail.metaFallback': 'Button', + 'buttons.detail.infoTitle': 'About', + 'buttons.detail.ipLabel': 'IP', + 'buttons.detail.keyLabel': 'Key', + 'buttons.detail.logTitle': 'Log', + 'buttons.detail.editButton': 'Edit button', + 'buttons.detail.logButton': 'View full log', + 'buttons.detail.ledPinLabel': 'LED Pin', + 'buttons.detail.buttonPinLabel': 'Button Pin', + 'buttons.detail.holdLabel': 'Hold Duration', + 'buttons.detail.cancelLabel': 'Cancel Duration', + 'buttons.detail.actionLabel': 'Action', + 'buttons.detail.zoneLabel': 'Zone', + 'buttons.edit.metaTitle': 'Edit {{name}}', + 'buttons.edit.pageTitle': 'Edit button {{name}}', + 'dashboard.buttons': 'Buttons' } as const export type EnMessages = typeof en diff --git a/app/root.tsx b/app/root.tsx index d0f2b97..85d23d9 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -28,6 +28,7 @@ import LockClosedIcon from '@heroicons/react/24/outline/LockClosedIcon' import MusicIcon from '@heroicons/react/24/outline/MusicalNoteIcon' import CodeIcon from '@heroicons/react/24/outline/CodeBracketIcon' import LogIcon from '@heroicons/react/24/outline/ClipboardDocumentCheckIcon' +import ButtonIcon from '@heroicons/react/24/outline/ArrowDownOnSquareIcon' import './tailwind.css' @@ -112,6 +113,9 @@ const AppContent = () => { {t('nav.actions')} + + {t('nav.buttons')} + {t('nav.webhooks')} diff --git a/app/routes/_index.tsx b/app/routes/_index.tsx index d9aed0a..9fcddde 100644 --- a/app/routes/_index.tsx +++ b/app/routes/_index.tsx @@ -4,7 +4,7 @@ import { redirect } from '@remix-run/node' import {Link, useLoaderData} from '@remix-run/react' -import {formatDistance} from 'date-fns' +import {formatDistance, format} from 'date-fns' import {enUS, pl} from 'date-fns/locale' import {getPrisma} from '~/lib/prisma.server' @@ -15,6 +15,8 @@ import {Page} from '~/lib/ui' import {useTranslation} from '~/lib/i18n' import {translate} from '~/lib/i18n.shared' import {getRootI18n} from '~/lib/i18n.meta' +import {useLivePageData} from '~/lib/hooks/use-live-data' +import {translateLogMessage} from './log' export const loader = async ({request}: LoaderFunctionArgs) => { const result = await checkSession(request) @@ -26,10 +28,12 @@ export const loader = async ({request}: LoaderFunctionArgs) => { const prisma = getPrisma() const sounders = await prisma.sounder.findMany({orderBy: {name: 'asc'}}) + const buttons = await prisma.actionButton.findMany({orderBy: {name: 'asc'}}) + const logs = await prisma.log.findMany({orderBy: {time: 'desc'}, take: 10}) const lockdownMode = await getSetting('lockdownMode') - return {sounders, lockdownMode} + return {sounders, lockdownMode, buttons, logs} } export const meta: MetaFunction = ({matches}) => { @@ -38,12 +42,13 @@ export const meta: MetaFunction = ({matches}) => { } export default function Index() { - const {sounders, lockdownMode} = useLoaderData() + const {sounders, lockdownMode, buttons, logs} = useLoaderData() const {t, locale} = useTranslation() const dateLocale = locale === 'pl' ? pl : enUS + useLivePageData() return ( - +

{t('dashboard.devices')}

@@ -82,9 +87,9 @@ export default function Index() {
-

+

{t('dashboard.lockdown.message', { status: t( lockdownMode === '0' @@ -110,7 +115,9 @@ export default function Index() { } }} > -

+
+

{t('dashboard.buttons')}

+ + + + + + + + + + {buttons.map(({id, name, lastCheckIn}) => { + return ( + + + + + + ) + })} + +
{t('dashboard.table.name')}{t('dashboard.table.status')}{t('dashboard.table.lastSeen')}
+ {name} + + {new Date().getTime() / 1000 - + lastCheckIn.getTime() / 1000 < + 65 + ? '🟢' + : '🔴'} + + {formatDistance(lastCheckIn, new Date(), { + addSuffix: true, + locale: dateLocale + })} +
+
+
+

{t('dashboard.log')}

+ + + + + + + + + {logs.map(({id, message, time}) => { + return ( + + + + + ) + })} + +
{t('log.columns.time')}{t('log.columns.message')}
+ {format(time, 'dd/MM/yy HH:mm')} + {translateLogMessage(message, t)}
+
) diff --git a/app/routes/about.tsx b/app/routes/about.tsx index 91728f4..e0a4632 100644 --- a/app/routes/about.tsx +++ b/app/routes/about.tsx @@ -112,6 +112,25 @@ export const loader = async ({request}: LoaderFunctionArgs) => { .catch(() => resolve('error')) }) + const buttonLatest = await new Promise(resolve => { + fetch( + 'https://api.github.com/repos/Open-School-Bell/action-button/releases?per_page=1', + { + headers: { + Accept: 'application/vnd.github+json', + 'X-GitHub-Api-Version': '2022-11-28' + } + } + ) + .then(response => { + response + .json() + .then(data => resolve(data[0].tag_name)) + .catch(() => resolve('error')) + }) + .catch(() => resolve('error')) + }) + const prisma = getPrisma() const redis = getRedis() @@ -128,6 +147,19 @@ export const loader = async ({request}: LoaderFunctionArgs) => { sounderVersions[id] = version ? version : '0.0.0' }) + const buttons = await prisma.actionButton.findMany({ + select: {id: true, name: true}, + orderBy: {name: 'asc'} + }) + + const buttonVersions: {[buttonId: string]: string} = {} + + await asyncForEach(buttons, async ({id}) => { + const version = await redis.get(`osb-button-version-${id}`) + + buttonVersions[id] = version ? version : '0.0.0' + }) + const license = ( await readFile(path.join(process.cwd(), 'LICENSE')) ).toString() @@ -137,6 +169,9 @@ export const loader = async ({request}: LoaderFunctionArgs) => { sounders, sounderVersions, sounderLatest, + buttons, + buttonVersions, + buttonLatest, ttsLatest, controllerLatest, license @@ -154,6 +189,9 @@ const About = () => { sounders, sounderVersions, sounderLatest, + buttons, + buttonVersions, + buttonLatest, ttsLatest, controllerLatest, license @@ -228,6 +266,24 @@ const About = () => { ) })} + {buttons.map(({id, name}) => { + return ( + + {`Button: ${name}`} + {buttonVersions[id]} + + {buttonLatest.replace('v', '')} + + + {RequiredVersions.button} + + + ) + })} diff --git a/app/routes/button-api.enroll.tsx b/app/routes/button-api.enroll.tsx new file mode 100644 index 0000000..0873df2 --- /dev/null +++ b/app/routes/button-api.enroll.tsx @@ -0,0 +1,24 @@ +import {type ActionFunctionArgs} from '@remix-run/node' + +import {getPrisma} from '~/lib/prisma.server' + +export const action = async ({request}: ActionFunctionArgs) => { + const {key} = (await request.json()) as {key?: string} + + if (!key || typeof key !== 'string') { + return Response.json({error: 'missing key'}, {status: 400}) + } + + const prisma = getPrisma() + + const button = await prisma.actionButton.findFirstOrThrow({ + where: {key, enrolled: false} + }) + + await prisma.actionButton.update({ + where: {id: button.id}, + data: {enrolled: true} + }) + + return Response.json({id: button.id, name: button.name}) +} diff --git a/app/routes/button-api.get-config.tsx b/app/routes/button-api.get-config.tsx new file mode 100644 index 0000000..3804764 --- /dev/null +++ b/app/routes/button-api.get-config.tsx @@ -0,0 +1,34 @@ +import {type ActionFunctionArgs} from '@remix-run/node' + +import {getPrisma} from '~/lib/prisma.server' +import {getSettings} from '~/lib/settings.server' + +export const action = async ({request}: ActionFunctionArgs) => { + const {key} = (await request.json()) as {key?: string} + + if (!key || typeof key !== 'string') { + return Response.json({error: 'missing key'}, {status: 400}) + } + + const prisma = getPrisma() + + const button = await prisma.actionButton.findFirst({ + where: {key, enrolled: true} + }) + + if (!button) { + return Response.json({error: 'invalid key'}, {status: 403}) + } + + const {lockdownMode} = await getSettings(['lockdownMode']) + + return Response.json({ + name: button.name, + id: button.id, + ledPin: button.ledPin, + buttonPin: button.buttonPin, + holdDuration: button.holdDuration, + cancelDuration: button.cancelDuration, + lockdown: lockdownMode === '1' + }) +} diff --git a/app/routes/button-api.log.tsx b/app/routes/button-api.log.tsx new file mode 100644 index 0000000..20352db --- /dev/null +++ b/app/routes/button-api.log.tsx @@ -0,0 +1,34 @@ +import {type ActionFunctionArgs} from '@remix-run/node' + +import {getPrisma} from '~/lib/prisma.server' + +export const action = async ({request}: ActionFunctionArgs) => { + const {key, message} = (await request.json()) as { + key?: string + message?: string + } + + if (!key || typeof key !== 'string') { + return Response.json({error: 'missing key'}, {status: 400}) + } + + if (!message || typeof message !== 'string') { + return Response.json({error: 'missing message'}, {status: 400}) + } + + const prisma = getPrisma() + + const button = await prisma.actionButton.findFirst({ + where: {key, enrolled: true} + }) + + if (!button) { + return Response.json({error: 'invalid key'}, {status: 403}) + } + + await prisma.actionButtonLog.create({ + data: {message, actionButtonId: button.id} + }) + + return Response.json({status: 'ok'}) +} diff --git a/app/routes/button-api.ping.tsx b/app/routes/button-api.ping.tsx new file mode 100644 index 0000000..4d244fb --- /dev/null +++ b/app/routes/button-api.ping.tsx @@ -0,0 +1,37 @@ +import {type ActionFunctionArgs} from '@remix-run/node' + +import {getPrisma} from '~/lib/prisma.server' +import {getRedis} from '~/lib/redis.server.mjs' + +export const action = async ({request}: ActionFunctionArgs) => { + const {key, version} = (await request.json()) as { + key?: string + version?: string + } + + if (!key || typeof key !== 'string') { + return Response.json({error: 'missing key'}, {status: 400}) + } + + const prisma = getPrisma() + const redis = getRedis() + + const button = await prisma.actionButton.findFirst({ + where: {key, enrolled: true} + }) + + if (!button) { + return Response.json({error: 'invalid key'}, {status: 403}) + } + + await prisma.actionButton.update({ + where: {id: button.id}, + data: {lastCheckIn: new Date()} + }) + + if (version) { + void redis.set(`osb-button-version-${button.id}`, version) + } + + return Response.json({ping: 'pong'}) +} diff --git a/app/routes/button-api.trigger.tsx b/app/routes/button-api.trigger.tsx new file mode 100644 index 0000000..745fea7 --- /dev/null +++ b/app/routes/button-api.trigger.tsx @@ -0,0 +1,48 @@ +import {type ActionFunctionArgs} from '@remix-run/node' + +import {getPrisma} from '~/lib/prisma.server' +import {broadcast} from '~/lib/broadcast.server' +import {toggleLockdown} from '~/lib/lockdown.server' + +export const action = async ({request}: ActionFunctionArgs) => { + const {key} = (await request.json()) as { + key?: string + } + + if (!key || typeof key !== 'string') { + return Response.json({error: 'missing key'}, {status: 400}) + } + + const prisma = getPrisma() + + const button = await prisma.actionButton.findFirst({ + where: {key, enrolled: true}, + include: {action: true} + }) + + if (!button) { + return Response.json({error: 'sounder not found'}, {status: 401}) + } + + const zone = button.zoneId + + switch (button.action.action) { + case 'broadcast': + if (!zone || typeof zone !== 'string' || zone.trim() === '') { + return Response.json({error: 'missing zone'}, {status: 400}) + } + + if (button.action.audioId) { + const zoneId = zone.trim() + await broadcast(zoneId, JSON.stringify([button.action.audioId])) + } + break + case 'lockdown': + await toggleLockdown() + break + default: + break + } + + return Response.json({ping: 'pong'}) +} diff --git a/app/routes/buttons.$button._index.tsx b/app/routes/buttons.$button._index.tsx new file mode 100644 index 0000000..7978954 --- /dev/null +++ b/app/routes/buttons.$button._index.tsx @@ -0,0 +1,150 @@ +import { + type LoaderFunctionArgs, + type MetaFunction, + redirect +} from '@remix-run/node' +import {useLoaderData, useNavigate, Link} from '@remix-run/react' +import {format} from 'date-fns' + +import {getPrisma} from '~/lib/prisma.server' +import {checkSession} from '~/lib/session' +import {INPUT_CLASSES, pageTitle} from '~/lib/utils' +import {Page, Actions} from '~/lib/ui' +import {getSetting} from '~/lib/settings.server' +import {useTranslation} from '~/lib/i18n' +import {translate} from '~/lib/i18n.shared' +import {getRootI18n} from '~/lib/i18n.meta' +import {useLivePageData} from '~/lib/hooks/use-live-data' + +export const meta: MetaFunction = ({data, matches}) => { + const {messages} = getRootI18n(matches) + const name = data + ? data.button.name + : translate(messages, 'buttons.detail.metaFallback') + + return [{title: pageTitle(translate(messages, 'buttons.metaTitle'), name)}] +} + +export const loader = async ({request, params}: LoaderFunctionArgs) => { + const result = await checkSession(request) + + if (!result) { + return redirect('/login') + } + + const prisma = getPrisma() + + const button = await prisma.actionButton.findFirstOrThrow({ + where: {id: params.sounder}, + include: { + action: true, + zone: true, + logs: {orderBy: {time: 'desc'}, take: 10} + } + }) + + const enrollUrl = await getSetting('enrollUrl') + + return {button, enrollUrl} +} + +const Sounder = () => { + const {button, enrollUrl} = useLoaderData() + const navigate = useNavigate() + const {t} = useTranslation() + useLivePageData() + + return ( + +
+
+

{t('buttons.detail.infoTitle')}

+

+ {t('buttons.detail.ipLabel')}: {button.ip} +

+

+ {t('buttons.detail.ledPinLabel')}: {button.ledPin} +

+

+ {t('buttons.detail.buttonPinLabel')}: {button.buttonPin} +

+

+ {t('buttons.detail.holdLabel')}: {button.holdDuration} +

+

+ {t('buttons.detail.cancelLabel')}: {button.cancelDuration} +

+

+ {t('buttons.detail.actionLabel')}:{' '} + {button.action.name} +

+

+ {t('buttons.detail.zoneLabel')}:{' '} + + {button.zone ? button.zone.name : 'None'} + +

+ {button.enrolled ? ( +
+ +
+ ) : ( + <> +

+ {t('buttons.detail.keyLabel')}: {button.key} +

+
+
+                  button --enroll {button.key} --controller {enrollUrl}
+                
+
+ + )} +
+
+

{t('buttons.detail.logTitle')}

+ + + + + + + + + {button.logs.map(({id, message, time}) => { + return ( + + + + + ) + })} + +
{t('log.columns.time')}{t('log.columns.message')}
+ {format(time, 'dd/MM/yy HH:mm')} + {message}
+
+
+ { + window.location.href = `/buttons/${button.id}/log` + } + }, + { + label: t('buttons.detail.editButton'), + color: 'bg-blue-300', + onClick: () => navigate(`/buttons/${button.id}/edit`) + } + ]} + /> +
+ ) +} + +export default Sounder diff --git a/app/routes/buttons.$button.delete.tsx b/app/routes/buttons.$button.delete.tsx new file mode 100644 index 0000000..f760cbf --- /dev/null +++ b/app/routes/buttons.$button.delete.tsx @@ -0,0 +1,18 @@ +import {type ActionFunctionArgs, redirect} from '@remix-run/node' + +import {getPrisma} from '~/lib/prisma.server' +import {checkSession} from '~/lib/session' + +export const action = async ({request, params}: ActionFunctionArgs) => { + const result = await checkSession(request) + + if (!result) { + return redirect('/login') + } + + const prisma = getPrisma() + + await prisma.actionButton.delete({where: {id: params.button}}) + + return redirect('/buttons') +} diff --git a/app/routes/buttons.$button.edit.tsx b/app/routes/buttons.$button.edit.tsx new file mode 100644 index 0000000..3b31b61 --- /dev/null +++ b/app/routes/buttons.$button.edit.tsx @@ -0,0 +1,220 @@ +import { + redirect, + type ActionFunctionArgs, + type LoaderFunctionArgs, + type MetaFunction +} from '@remix-run/node' +import {useLoaderData, useNavigate} from '@remix-run/react' +import {invariant} from '@arcath/utils' + +import {getPrisma} from '~/lib/prisma.server' +import {INPUT_CLASSES, pageTitle} from '~/lib/utils' +import {checkSession} from '~/lib/session' +import {Page, FormElement, Actions} from '~/lib/ui' +import {useTranslation} from '~/lib/i18n' +import {translate} from '~/lib/i18n.shared' +import {getRootI18n} from '~/lib/i18n.meta' + +export const meta: MetaFunction = ({data, matches}) => { + const {messages} = getRootI18n(matches) + return [ + { + title: pageTitle( + translate(messages, 'buttons.metaTitle'), + data + ? translate(messages, 'buttons.edit.metaTitle', { + name: data.button.name + }) + : translate(messages, 'buttons.edit.metaTitle', {name: ''}) + ) + } + ] +} + +export const loader = async ({request, params}: LoaderFunctionArgs) => { + const result = await checkSession(request) + + if (!result) { + return redirect('/login') + } + + const prisma = getPrisma() + + const actions = await prisma.action.findMany({orderBy: {name: 'asc'}}) + const zones = await prisma.zone.findMany({orderBy: {name: 'asc'}}) + const button = await prisma.actionButton.findFirstOrThrow({ + where: {id: params.button} + }) + + return {actions, button, zones} +} + +export const action = async ({request, params}: ActionFunctionArgs) => { + const result = await checkSession(request) + + if (!result) { + return redirect('/login') + } + + const prisma = getPrisma() + + const formData = await request.formData() + + const name = formData.get('name') as string | undefined + const ip = formData.get('ip') as string | undefined + const action = formData.get('action') as string | undefined + const zone = formData.get('zone') as string | undefined + const ledPin = formData.get('ledpin') as string | undefined + const buttonPin = formData.get('buttonpin') as string | undefined + const holdDuration = formData.get('holdduration') as string | undefined + const cancelDuration = formData.get('cancelduration') as string | undefined + + invariant(name) + invariant(ip) + invariant(action) + invariant(zone) + invariant(ledPin) + invariant(buttonPin) + invariant(holdDuration) + invariant(cancelDuration) + + const button = await prisma.actionButton.update({ + where: {id: params.button}, + data: { + name, + ip, + actionId: action, + zoneId: zone, + ledPin: parseInt(ledPin), + buttonPin: parseInt(buttonPin), + holdDuration: parseInt(holdDuration), + cancelDuration: parseInt(cancelDuration) + } + }) + + return redirect(`/buttons/${button.id}`) +} + +const EditButton = () => { + const {actions, button, zones} = useLoaderData() + const navigate = useNavigate() + const {t} = useTranslation() + + return ( + +
+ + + + + + + + + + + + + + + + + + + + + + + + + { + e.preventDefault() + navigate('/buttons') + } + }, + {label: t('buttons.edit.submit'), color: 'bg-green-300'} + ]} + /> + +
+ ) +} + +export default EditButton diff --git a/app/routes/buttons.$button.log.tsx b/app/routes/buttons.$button.log.tsx new file mode 100644 index 0000000..fa37a89 --- /dev/null +++ b/app/routes/buttons.$button.log.tsx @@ -0,0 +1,29 @@ +import {type LoaderFunctionArgs, redirect} from '@remix-run/node' + +import {getPrisma} from '~/lib/prisma.server' +import {checkSession} from '~/lib/session' + +export const loader = async ({request, params}: LoaderFunctionArgs) => { + const result = await checkSession(request) + + if (!result) { + return redirect('/login') + } + + const prisma = getPrisma() + + const button = await prisma.actionButton.findFirstOrThrow({ + where: {id: params.button}, + include: { + logs: {orderBy: {time: 'desc'}} + } + }) + + return new Response( + button.logs + .map(({time, message}) => { + return `${time}: ${message}` + }) + .join(`\r\n`) + ) +} diff --git a/app/routes/buttons.$button.reset.tsx b/app/routes/buttons.$button.reset.tsx new file mode 100644 index 0000000..dd8a97c --- /dev/null +++ b/app/routes/buttons.$button.reset.tsx @@ -0,0 +1,26 @@ +import {type ActionFunctionArgs, redirect} from '@remix-run/node' + +import {getPrisma} from '~/lib/prisma.server' +import {makeKey} from '~/lib/utils' +import {checkSession} from '~/lib/session' + +export const action = async ({request, params}: ActionFunctionArgs) => { + const result = await checkSession(request) + + if (!result) { + return redirect('/login') + } + + const prisma = getPrisma() + + const button = await prisma.actionButton.findFirstOrThrow({ + where: {id: params.button} + }) + + await prisma.actionButton.update({ + where: {id: button.id}, + data: {enrolled: false, key: makeKey()} + }) + + return redirect(`/buttons/${button.id}`) +} diff --git a/app/routes/buttons._index.tsx b/app/routes/buttons._index.tsx new file mode 100644 index 0000000..bb3296d --- /dev/null +++ b/app/routes/buttons._index.tsx @@ -0,0 +1,99 @@ +import { + type LoaderFunctionArgs, + type MetaFunction, + redirect +} from '@remix-run/node' +import {useLoaderData, Link, useNavigate} from '@remix-run/react' + +import {getPrisma} from '~/lib/prisma.server' +import {checkSession} from '~/lib/session' +import {pageTitle} from '~/lib/utils' +import {Page, Actions} from '~/lib/ui' +import {useTranslation} from '~/lib/i18n' +import {translate} from '~/lib/i18n.shared' +import {getRootI18n} from '~/lib/i18n.meta' + +export const meta: MetaFunction = ({matches}) => { + const {messages} = getRootI18n(matches) + return [{title: pageTitle(translate(messages, 'buttons.metaTitle'))}] +} + +export const loader = async ({request}: LoaderFunctionArgs) => { + const result = await checkSession(request) + + if (!result) { + return redirect('/login') + } + + const prisma = getPrisma() + + const buttons = await prisma.actionButton.findMany({ + orderBy: {name: 'asc'}, + include: {action: true} + }) + + return {buttons} +} + +const Sounders = () => { + const {buttons} = useLoaderData() + const navigate = useNavigate() + const {t} = useTranslation() + + return ( + +
+ + + + + + + + + + {buttons.map(({id, name, action}) => { + return ( + + + + + + ) + })} + +
{t('buttons.table.device')}{t('buttons.table.action')}
+ {name} + + {action.name} + +
{ + if (!confirm(t('buttons.deleteConfirmation', {name}))) { + e.preventDefault() + } + }} + > + +
+
+
+ navigate('/buttons/add') + } + ]} + /> +
+ ) +} + +export default Sounders diff --git a/app/routes/buttons.add.tsx b/app/routes/buttons.add.tsx new file mode 100644 index 0000000..51b719e --- /dev/null +++ b/app/routes/buttons.add.tsx @@ -0,0 +1,124 @@ +import { + redirect, + type ActionFunctionArgs, + type LoaderFunctionArgs, + type MetaFunction +} from '@remix-run/node' +import {useLoaderData, useNavigate} from '@remix-run/react' +import {invariant} from '@arcath/utils' + +import {getPrisma} from '~/lib/prisma.server' +import {makeKey, INPUT_CLASSES, pageTitle} from '~/lib/utils' +import {checkSession} from '~/lib/session' +import {Page, FormElement, Actions} from '~/lib/ui' +import {useTranslation} from '~/lib/i18n' +import {translate} from '~/lib/i18n.shared' +import {getRootI18n} from '~/lib/i18n.meta' + +export const meta: MetaFunction = ({matches}) => { + const {messages} = getRootI18n(matches) + return [ + { + title: pageTitle( + translate(messages, 'buttons.metaTitle'), + translate(messages, 'buttons.add.pageTitle') + ) + } + ] +} + +export const loader = async ({request}: LoaderFunctionArgs) => { + const result = await checkSession(request) + + if (!result) { + return redirect('/login') + } + + const prisma = getPrisma() + + const actions = await prisma.action.findMany({orderBy: {name: 'asc'}}) + + return {actions} +} + +export const action = async ({request}: ActionFunctionArgs) => { + const result = await checkSession(request) + + if (!result) { + return redirect('/login') + } + + const prisma = getPrisma() + + const formData = await request.formData() + + const name = formData.get('name') as string | undefined + const ip = formData.get('ip') as string | undefined + const action = formData.get('action') as string | undefined + + invariant(name) + invariant(ip) + invariant(action) + + const key = makeKey() + + const button = await prisma.actionButton.create({ + data: {name, key, ip, actionId: action} + }) + + return redirect(`/buttons/${button.id}`) +} + +const AddButton = () => { + const {actions} = useLoaderData() + const navigate = useNavigate() + const {t} = useTranslation() + + return ( + +
+ + + + + + + + + + { + e.preventDefault() + navigate('/buttons') + } + }, + {label: t('buttons.add.submit'), color: 'bg-green-300'} + ]} + /> + +
+ ) +} + +export default AddButton diff --git a/app/routes/log.tsx b/app/routes/log.tsx index 879333d..252a166 100644 --- a/app/routes/log.tsx +++ b/app/routes/log.tsx @@ -14,6 +14,7 @@ import {useTranslation} from '~/lib/i18n' import {translate} from '~/lib/i18n.shared' import {getRootI18n} from '~/lib/i18n.meta' import {MessageKey} from '~/locales' +import {useLivePageData} from '~/lib/hooks/use-live-data' export const meta: MetaFunction = ({matches}) => { const {messages} = getRootI18n(matches) @@ -37,6 +38,7 @@ export const loader = async ({request}: LoaderFunctionArgs) => { const Log = () => { const {logs} = useLoaderData() const {t} = useTranslation() + useLivePageData() return ( @@ -66,7 +68,7 @@ const Log = () => { export default Log -const translateLogMessage = ( +export const translateLogMessage = ( message: string, t: ReturnType['t'] ) => { diff --git a/app/routes/sounder-api.get-status.tsx b/app/routes/sounder-api.get-status.tsx index fcbd5b2..2a67fc8 100644 --- a/app/routes/sounder-api.get-status.tsx +++ b/app/routes/sounder-api.get-status.tsx @@ -22,6 +22,7 @@ export const action = async ({request}: ActionFunctionArgs) => { } const sounders = await prisma.sounder.findMany({orderBy: {name: 'asc'}}) + const buttons = await prisma.actionButton.findMany({orderBy: {name: 'asc'}}) return Response.json({ system: 'ok', @@ -38,6 +39,18 @@ export const action = async ({request}: ActionFunctionArgs) => { ? '🟢' : '🔴' } + }), + buttons: buttons.map(button => { + return { + ...button, + lastSeen: formatDistance(new Date(button.lastCheckIn), new Date(), { + addSuffix: true + }), + status: + new Date().getTime() / 1000 - button.lastCheckIn.getTime() / 1000 < 65 + ? '🟢' + : '🔴' + } }) }) } diff --git a/app/routes/sounders.$sounder._index.tsx b/app/routes/sounders.$sounder._index.tsx index 9e76bad..bae7633 100644 --- a/app/routes/sounders.$sounder._index.tsx +++ b/app/routes/sounders.$sounder._index.tsx @@ -14,6 +14,7 @@ import {getSetting} from '~/lib/settings.server' import {useTranslation} from '~/lib/i18n' import {translate} from '~/lib/i18n.shared' import {getRootI18n} from '~/lib/i18n.meta' +import {useLivePageData} from '~/lib/hooks/use-live-data' export const meta: MetaFunction = ({data, matches}) => { const {messages} = getRootI18n(matches) @@ -61,6 +62,8 @@ const Sounder = () => { ? t('sounders.detail.ringerPin.none') : String(sounder.ringerPin) + useLivePageData() + return (
@@ -154,6 +157,13 @@ const Sounder = () => {
{ + window.location.href = `/sounders/${sounder.id}/log` + } + }, { label: t('sounders.detail.editButton'), color: 'bg-blue-300', diff --git a/app/routes/sounders.$sounder.log.tsx b/app/routes/sounders.$sounder.log.tsx new file mode 100644 index 0000000..82fe3a0 --- /dev/null +++ b/app/routes/sounders.$sounder.log.tsx @@ -0,0 +1,29 @@ +import {type LoaderFunctionArgs, redirect} from '@remix-run/node' + +import {getPrisma} from '~/lib/prisma.server' +import {checkSession} from '~/lib/session' + +export const loader = async ({request, params}: LoaderFunctionArgs) => { + const result = await checkSession(request) + + if (!result) { + return redirect('/login') + } + + const prisma = getPrisma() + + const sounder = await prisma.sounder.findFirstOrThrow({ + where: {id: params.sounder}, + include: { + logs: {orderBy: {time: 'desc'}} + } + }) + + return new Response( + sounder.logs + .map(({time, message}) => { + return `${time}: ${message}` + }) + .join(`\r\n`) + ) +} diff --git a/app/routes/sounds.add.tsx b/app/routes/sounds.add.tsx index fd6619a..8a6743e 100644 --- a/app/routes/sounds.add.tsx +++ b/app/routes/sounds.add.tsx @@ -142,7 +142,7 @@ const AddSound = () => { diff --git a/app/routes/status.tsx b/app/routes/status.tsx index 97f56a8..e50467e 100644 --- a/app/routes/status.tsx +++ b/app/routes/status.tsx @@ -13,6 +13,9 @@ export const loader = async ({}: LoaderFunctionArgs) => { select: {id: true, name: true, lastCheckIn: true, enrolled: true, ip: true} }) const zones = await prisma.zone.findMany() + const buttons = await prisma.actionButton.findMany({ + select: {id: true, name: true, lastCheckIn: true, enrolled: true, ip: true} + }) const date = new Date() date.setHours(0, 0, 0, 0) @@ -27,6 +30,7 @@ export const loader = async ({}: LoaderFunctionArgs) => { lockdown: lockdownMode === '1', sounders, version: VERSION, - zones + zones, + buttons }) } diff --git a/package-lock.json b/package-lock.json index 8d213b6..effd8d7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,20 +13,20 @@ "@remix-run/react": "^2.17.0", "@remix-run/serve": "^2.17.0", "adm-zip": "^0.5.16", - "bullmq": "^5.58.5", + "bullmq": "^5.64.1", "date-fns": "^4.1.0", - "isbot": "^5.1.30", + "isbot": "^5.1.32", "jsonwebtoken": "^9.0.2", "mkdirp": "^3.0.1", - "music-metadata": "^11.9.0", + "music-metadata": "^11.10.3", "prisma": "^6.15.0", "react": "^18.2.0", "react-dom": "^18.2.0", - "semver": "^7.7.2" + "semver": "^7.7.3" }, "devDependencies": { - "@remix-run/dev": "^2.17.0", - "@tailwindcss/postcss": "^4.1.13", + "@remix-run/dev": "^2.17.2", + "@tailwindcss/postcss": "^4.1.17", "@types/adm-zip": "^0.5.7", "@types/jsonwebtoken": "^9.0.10", "@types/react": "^18.2.20", @@ -34,22 +34,22 @@ "@types/semver": "^7.7.1", "@typescript-eslint/eslint-plugin": "^8.37.0", "@typescript-eslint/parser": "^8.37.0", - "autoprefixer": "^10.4.21", + "autoprefixer": "^10.4.22", "env-cmd": "^11.0.0", - "eslint": "^9.34.0", + "eslint": "^9.39.1", "eslint-import-resolver-typescript": "^4.4.4", "eslint-plugin-import": "^2.32.0", "eslint-plugin-import-x": "^4.16.1", "eslint-plugin-jsx-a11y": "^6.10.2", "eslint-plugin-react": "^7.37.5", "eslint-plugin-react-hooks": "^5.2.0", - "globals": "^16.3.0", + "globals": "^16.5.0", "npm-run-all": "^4.1.5", "postcss": "^8.5.6", "prettier": "^3.6.2", - "tailwindcss": "^4.1.13", - "typescript": "5.9.2", - "typescript-eslint": "^8.42.0", + "tailwindcss": "^4.1.17", + "typescript": "5.9.3", + "typescript-eslint": "^8.47.0", "vite": "^6.3.5", "vite-tsconfig-paths": "^5.1.4" }, @@ -123,6 +123,7 @@ "integrity": "sha512-lWBYIrF7qK5+GjY5Uy+/hEgp8OJWOD/rpy74GplYRhEauvbHDeFB8t5hPOZxCZ0Oxf4Cc36tK51/l3ymJysrKw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.26.2", @@ -1070,9 +1071,9 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", - "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", "dev": true, "license": "MIT", "dependencies": { @@ -1099,13 +1100,13 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.21.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", - "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/object-schema": "^2.1.6", + "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.2" }, @@ -1138,19 +1139,22 @@ } }, "node_modules/@eslint/config-helpers": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz", - "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", "dev": true, "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/core": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", - "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1222,9 +1226,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.34.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.34.0.tgz", - "integrity": "sha512-EoyvqQnBNsV1CWaEJ559rxXL4c8V92gxirbawSmVUOWXlsRxxQXl6LmCpdUblgxgSkDIqKnhzba2SjRTI/A5Rw==", + "version": "9.39.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.1.tgz", + "integrity": "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==", "dev": true, "license": "MIT", "engines": { @@ -1235,9 +1239,9 @@ } }, "node_modules/@eslint/object-schema": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", - "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1245,13 +1249,13 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", - "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.15.2", + "@eslint/core": "^0.17.0", "levn": "^0.4.1" }, "engines": { @@ -1334,9 +1338,9 @@ } }, "node_modules/@ioredis/commands": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz", - "integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.4.0.tgz", + "integrity": "sha512-aFT2yemJJo+TZCmieA7qnYGQooOS7QfNmYrzGtsYd3g9j5iDP8AimYYAesf79ohjbLG12XxC4nG5DyEnC88AsQ==", "license": "MIT" }, "node_modules/@isaacs/cliui": { @@ -1386,19 +1390,6 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/@isaacs/fs-minipass": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", - "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^7.0.4" - }, - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.8", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", @@ -1591,44 +1582,6 @@ "@tybys/wasm-util": "^0.10.0" } }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/@npmcli/fs": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.1.tgz", @@ -1795,9 +1748,9 @@ } }, "node_modules/@remix-run/dev": { - "version": "2.17.0", - "resolved": "https://registry.npmjs.org/@remix-run/dev/-/dev-2.17.0.tgz", - "integrity": "sha512-L2W8PYH3jUvCKlJeUkFMGyOMzUsM0goZg4n0NU69O85TNlB1jgiqSbSMb69xhviphGpwzAoH+D/p3/cUnw4DbQ==", + "version": "2.17.2", + "resolved": "https://registry.npmjs.org/@remix-run/dev/-/dev-2.17.2.tgz", + "integrity": "sha512-gfc4Hu2Sysr+GyU/F12d2uvEfQwUwWvsOjBtiemhdN1xGOn1+FyYzlLl/aJ7AL9oYko8sDqOPyJCiApWJJGCCw==", "dev": true, "license": "MIT", "dependencies": { @@ -1811,9 +1764,9 @@ "@babel/types": "^7.22.5", "@mdx-js/mdx": "^2.3.0", "@npmcli/package-json": "^4.0.1", - "@remix-run/node": "2.17.0", + "@remix-run/node": "2.17.2", "@remix-run/router": "1.23.0", - "@remix-run/server-runtime": "2.17.0", + "@remix-run/server-runtime": "2.17.2", "@types/mdx": "^2.0.5", "@vanilla-extract/integration": "^6.2.0", "arg": "^5.0.1", @@ -1886,6 +1839,33 @@ } } }, + "node_modules/@remix-run/dev/node_modules/@remix-run/node": { + "version": "2.17.2", + "resolved": "https://registry.npmjs.org/@remix-run/node/-/node-2.17.2.tgz", + "integrity": "sha512-NHBIQI1Fap3ZmyHMPVsMcma6mvi2oUunvTzOcuWHHkkx1LrvWRzQTlaWqEnqCp/ff5PfX5r0eBEPrSkC8zrHRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@remix-run/server-runtime": "2.17.2", + "@remix-run/web-fetch": "^4.4.2", + "@web3-storage/multipart-parser": "^1.0.0", + "cookie-signature": "^1.1.0", + "source-map-support": "^0.5.21", + "stream-slice": "^0.1.2", + "undici": "^6.21.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "typescript": "^5.1.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/@remix-run/dev/node_modules/prettier": { "version": "2.8.8", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", @@ -1949,6 +1929,32 @@ } } }, + "node_modules/@remix-run/node/node_modules/@remix-run/server-runtime": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/@remix-run/server-runtime/-/server-runtime-2.17.0.tgz", + "integrity": "sha512-X0zfGLgvukhuTIL0tdWKnlvHy4xUe7Z17iQ0KMQoITK0SkTZPSud/6cJCsKhPqC8kfdYT1GNFLJKRhHz7Aapmw==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.0", + "@types/cookie": "^0.6.0", + "@web3-storage/multipart-parser": "^1.0.0", + "cookie": "^0.7.2", + "set-cookie-parser": "^2.4.8", + "source-map": "^0.7.3", + "turbo-stream": "2.4.1" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "typescript": "^5.1.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/@remix-run/react": { "version": "2.17.0", "resolved": "https://registry.npmjs.org/@remix-run/react/-/react-2.17.0.tgz", @@ -1975,6 +1981,32 @@ } } }, + "node_modules/@remix-run/react/node_modules/@remix-run/server-runtime": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/@remix-run/server-runtime/-/server-runtime-2.17.0.tgz", + "integrity": "sha512-X0zfGLgvukhuTIL0tdWKnlvHy4xUe7Z17iQ0KMQoITK0SkTZPSud/6cJCsKhPqC8kfdYT1GNFLJKRhHz7Aapmw==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.0", + "@types/cookie": "^0.6.0", + "@web3-storage/multipart-parser": "^1.0.0", + "cookie": "^0.7.2", + "set-cookie-parser": "^2.4.8", + "source-map": "^0.7.3", + "turbo-stream": "2.4.1" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "typescript": "^5.1.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/@remix-run/router": { "version": "1.23.0", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz", @@ -1989,6 +2021,7 @@ "resolved": "https://registry.npmjs.org/@remix-run/serve/-/serve-2.17.0.tgz", "integrity": "sha512-eq0A7A89uqg+rQiGVHoUwb1NUawmPpjAY3RWn4KG4uCp4QwhqJausML63fIxnfdp7zu2OplXhfSXCNiTekQ0rw==", "license": "MIT", + "peer": true, "dependencies": { "@remix-run/express": "2.17.0", "@remix-run/node": "2.17.0", @@ -2007,9 +2040,10 @@ } }, "node_modules/@remix-run/server-runtime": { - "version": "2.17.0", - "resolved": "https://registry.npmjs.org/@remix-run/server-runtime/-/server-runtime-2.17.0.tgz", - "integrity": "sha512-X0zfGLgvukhuTIL0tdWKnlvHy4xUe7Z17iQ0KMQoITK0SkTZPSud/6cJCsKhPqC8kfdYT1GNFLJKRhHz7Aapmw==", + "version": "2.17.2", + "resolved": "https://registry.npmjs.org/@remix-run/server-runtime/-/server-runtime-2.17.2.tgz", + "integrity": "sha512-dTrAG1SgOLgz1DFBDsLHN0V34YqLsHEReVHYOI4UV/p+ALbn/BRQMw1MaUuqGXmX21ZTuCzzPegtTLNEOc8ixA==", + "dev": true, "license": "MIT", "dependencies": { "@remix-run/router": "1.23.0", @@ -2396,54 +2430,49 @@ "license": "MIT" }, "node_modules/@tailwindcss/node": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.13.tgz", - "integrity": "sha512-eq3ouolC1oEFOAvOMOBAmfCIqZBJuvWvvYWh5h5iOYfe1HFC6+GZ6EIL0JdM3/niGRJmnrOc+8gl9/HGUaaptw==", + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.17.tgz", + "integrity": "sha512-csIkHIgLb3JisEFQ0vxr2Y57GUNYh447C8xzwj89U/8fdW8LhProdxvnVH6U8M2Y73QKiTIH+LWbK3V2BBZsAg==", "dev": true, "license": "MIT", "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", - "jiti": "^2.5.1", - "lightningcss": "1.30.1", - "magic-string": "^0.30.18", + "jiti": "^2.6.1", + "lightningcss": "1.30.2", + "magic-string": "^0.30.21", "source-map-js": "^1.2.1", - "tailwindcss": "4.1.13" + "tailwindcss": "4.1.17" } }, "node_modules/@tailwindcss/oxide": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.13.tgz", - "integrity": "sha512-CPgsM1IpGRa880sMbYmG1s4xhAy3xEt1QULgTJGQmZUeNgXFR7s1YxYygmJyBGtou4SyEosGAGEeYqY7R53bIA==", + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.17.tgz", + "integrity": "sha512-F0F7d01fmkQhsTjXezGBLdrl1KresJTcI3DB8EkScCldyKp3Msz4hub4uyYaVnk88BAS1g5DQjjF6F5qczheLA==", "dev": true, - "hasInstallScript": true, "license": "MIT", - "dependencies": { - "detect-libc": "^2.0.4", - "tar": "^7.4.3" - }, "engines": { "node": ">= 10" }, "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.1.13", - "@tailwindcss/oxide-darwin-arm64": "4.1.13", - "@tailwindcss/oxide-darwin-x64": "4.1.13", - "@tailwindcss/oxide-freebsd-x64": "4.1.13", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.13", - "@tailwindcss/oxide-linux-arm64-gnu": "4.1.13", - "@tailwindcss/oxide-linux-arm64-musl": "4.1.13", - "@tailwindcss/oxide-linux-x64-gnu": "4.1.13", - "@tailwindcss/oxide-linux-x64-musl": "4.1.13", - "@tailwindcss/oxide-wasm32-wasi": "4.1.13", - "@tailwindcss/oxide-win32-arm64-msvc": "4.1.13", - "@tailwindcss/oxide-win32-x64-msvc": "4.1.13" + "@tailwindcss/oxide-android-arm64": "4.1.17", + "@tailwindcss/oxide-darwin-arm64": "4.1.17", + "@tailwindcss/oxide-darwin-x64": "4.1.17", + "@tailwindcss/oxide-freebsd-x64": "4.1.17", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.17", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.17", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.17", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.17", + "@tailwindcss/oxide-linux-x64-musl": "4.1.17", + "@tailwindcss/oxide-wasm32-wasi": "4.1.17", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.17", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.17" } }, "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.13.tgz", - "integrity": "sha512-BrpTrVYyejbgGo57yc8ieE+D6VT9GOgnNdmh5Sac6+t0m+v+sKQevpFVpwX3pBrM2qKrQwJ0c5eDbtjouY/+ew==", + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.17.tgz", + "integrity": "sha512-BMqpkJHgOZ5z78qqiGE6ZIRExyaHyuxjgrJ6eBO5+hfrfGkuya0lYfw8fRHG77gdTjWkNWEEm+qeG2cDMxArLQ==", "cpu": [ "arm64" ], @@ -2458,9 +2487,9 @@ } }, "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.13.tgz", - "integrity": "sha512-YP+Jksc4U0KHcu76UhRDHq9bx4qtBftp9ShK/7UGfq0wpaP96YVnnjFnj3ZFrUAjc5iECzODl/Ts0AN7ZPOANQ==", + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.17.tgz", + "integrity": "sha512-EquyumkQweUBNk1zGEU/wfZo2qkp/nQKRZM8bUYO0J+Lums5+wl2CcG1f9BgAjn/u9pJzdYddHWBiFXJTcxmOg==", "cpu": [ "arm64" ], @@ -2475,9 +2504,9 @@ } }, "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.13.tgz", - "integrity": "sha512-aAJ3bbwrn/PQHDxCto9sxwQfT30PzyYJFG0u/BWZGeVXi5Hx6uuUOQEI2Fa43qvmUjTRQNZnGqe9t0Zntexeuw==", + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.17.tgz", + "integrity": "sha512-gdhEPLzke2Pog8s12oADwYu0IAw04Y2tlmgVzIN0+046ytcgx8uZmCzEg4VcQh+AHKiS7xaL8kGo/QTiNEGRog==", "cpu": [ "x64" ], @@ -2492,9 +2521,9 @@ } }, "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.13.tgz", - "integrity": "sha512-Wt8KvASHwSXhKE/dJLCCWcTSVmBj3xhVhp/aF3RpAhGeZ3sVo7+NTfgiN8Vey/Fi8prRClDs6/f0KXPDTZE6nQ==", + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.17.tgz", + "integrity": "sha512-hxGS81KskMxML9DXsaXT1H0DyA+ZBIbyG/sSAjWNe2EDl7TkPOBI42GBV3u38itzGUOmFfCzk1iAjDXds8Oh0g==", "cpu": [ "x64" ], @@ -2509,9 +2538,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.13.tgz", - "integrity": "sha512-mbVbcAsW3Gkm2MGwA93eLtWrwajz91aXZCNSkGTx/R5eb6KpKD5q8Ueckkh9YNboU8RH7jiv+ol/I7ZyQ9H7Bw==", + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.17.tgz", + "integrity": "sha512-k7jWk5E3ldAdw0cNglhjSgv501u7yrMf8oeZ0cElhxU6Y2o7f8yqelOp3fhf7evjIS6ujTI3U8pKUXV2I4iXHQ==", "cpu": [ "arm" ], @@ -2526,9 +2555,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.13.tgz", - "integrity": "sha512-wdtfkmpXiwej/yoAkrCP2DNzRXCALq9NVLgLELgLim1QpSfhQM5+ZxQQF8fkOiEpuNoKLp4nKZ6RC4kmeFH0HQ==", + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.17.tgz", + "integrity": "sha512-HVDOm/mxK6+TbARwdW17WrgDYEGzmoYayrCgmLEw7FxTPLcp/glBisuyWkFz/jb7ZfiAXAXUACfyItn+nTgsdQ==", "cpu": [ "arm64" ], @@ -2543,9 +2572,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.13.tgz", - "integrity": "sha512-hZQrmtLdhyqzXHB7mkXfq0IYbxegaqTmfa1p9MBj72WPoDD3oNOh1Lnxf6xZLY9C3OV6qiCYkO1i/LrzEdW2mg==", + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.17.tgz", + "integrity": "sha512-HvZLfGr42i5anKtIeQzxdkw/wPqIbpeZqe7vd3V9vI3RQxe3xU1fLjss0TjyhxWcBaipk7NYwSrwTwK1hJARMg==", "cpu": [ "arm64" ], @@ -2560,9 +2589,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.13.tgz", - "integrity": "sha512-uaZTYWxSXyMWDJZNY1Ul7XkJTCBRFZ5Fo6wtjrgBKzZLoJNrG+WderJwAjPzuNZOnmdrVg260DKwXCFtJ/hWRQ==", + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.17.tgz", + "integrity": "sha512-M3XZuORCGB7VPOEDH+nzpJ21XPvK5PyjlkSFkFziNHGLc5d6g3di2McAAblmaSUNl8IOmzYwLx9NsE7bplNkwQ==", "cpu": [ "x64" ], @@ -2577,9 +2606,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.13.tgz", - "integrity": "sha512-oXiPj5mi4Hdn50v5RdnuuIms0PVPI/EG4fxAfFiIKQh5TgQgX7oSuDWntHW7WNIi/yVLAiS+CRGW4RkoGSSgVQ==", + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.17.tgz", + "integrity": "sha512-k7f+pf9eXLEey4pBlw+8dgfJHY4PZ5qOUFDyNf7SI6lHjQ9Zt7+NcscjpwdCEbYi6FI5c2KDTDWyf2iHcCSyyQ==", "cpu": [ "x64" ], @@ -2594,9 +2623,9 @@ } }, "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.13.tgz", - "integrity": "sha512-+LC2nNtPovtrDwBc/nqnIKYh/W2+R69FA0hgoeOn64BdCX522u19ryLh3Vf3F8W49XBcMIxSe665kwy21FkhvA==", + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.17.tgz", + "integrity": "sha512-cEytGqSSoy7zK4JRWiTCx43FsKP/zGr0CsuMawhH67ONlH+T79VteQeJQRO/X7L0juEUA8ZyuYikcRBf0vsxhg==", "bundleDependencies": [ "@napi-rs/wasm-runtime", "@emnapi/core", @@ -2612,21 +2641,21 @@ "license": "MIT", "optional": true, "dependencies": { - "@emnapi/core": "^1.4.5", - "@emnapi/runtime": "^1.4.5", - "@emnapi/wasi-threads": "^1.0.4", - "@napi-rs/wasm-runtime": "^0.2.12", - "@tybys/wasm-util": "^0.10.0", - "tslib": "^2.8.0" + "@emnapi/core": "^1.6.0", + "@emnapi/runtime": "^1.6.0", + "@emnapi/wasi-threads": "^1.1.0", + "@napi-rs/wasm-runtime": "^1.0.7", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.4.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.13.tgz", - "integrity": "sha512-dziTNeQXtoQ2KBXmrjCxsuPk3F3CQ/yb7ZNZNA+UkNTeiTGgfeh+gH5Pi7mRncVgcPD2xgHvkFCh/MhZWSgyQg==", + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.17.tgz", + "integrity": "sha512-JU5AHr7gKbZlOGvMdb4722/0aYbU+tN6lv1kONx0JK2cGsh7g148zVWLM0IKR3NeKLv+L90chBVYcJ8uJWbC9A==", "cpu": [ "arm64" ], @@ -2641,9 +2670,9 @@ } }, "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.13.tgz", - "integrity": "sha512-3+LKesjXydTkHk5zXX01b5KMzLV1xl2mcktBJkje7rhFUpUlYJy7IMOLqjIRQncLTa1WZZiFY/foAeB5nmaiTw==", + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.17.tgz", + "integrity": "sha512-SKWM4waLuqx0IH+FMDUw6R66Hu4OuTALFgnleKbqhgGU30DY20NORZMZUKgLRjQXNN2TLzKvh48QXTig4h4bGw==", "cpu": [ "x64" ], @@ -2657,80 +2686,28 @@ "node": ">= 10" } }, - "node_modules/@tailwindcss/oxide/node_modules/chownr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/@tailwindcss/oxide/node_modules/minizlib": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", - "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", - "dev": true, - "license": "MIT", - "dependencies": { - "minipass": "^7.1.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@tailwindcss/oxide/node_modules/tar": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", - "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", - "dev": true, - "license": "ISC", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.0.1", - "mkdirp": "^3.0.1", - "yallist": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@tailwindcss/oxide/node_modules/yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, "node_modules/@tailwindcss/postcss": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.13.tgz", - "integrity": "sha512-HLgx6YSFKJT7rJqh9oJs/TkBFhxuMOfUKSBEPYwV+t78POOBsdQ7crhZLzwcH3T0UyUuOzU/GK5pk5eKr3wCiQ==", + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.17.tgz", + "integrity": "sha512-+nKl9N9mN5uJ+M7dBOOCzINw94MPstNR/GtIhz1fpZysxL/4a+No64jCBD6CPN+bIHWFx3KWuu8XJRrj/572Dw==", "dev": true, "license": "MIT", "dependencies": { "@alloc/quick-lru": "^5.2.0", - "@tailwindcss/node": "4.1.13", - "@tailwindcss/oxide": "4.1.13", + "@tailwindcss/node": "4.1.17", + "@tailwindcss/oxide": "4.1.17", "postcss": "^8.4.41", - "tailwindcss": "4.1.13" + "tailwindcss": "4.1.17" } }, "node_modules/@tokenizer/inflate": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.2.7.tgz", - "integrity": "sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.4.1.tgz", + "integrity": "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==", "license": "MIT", "dependencies": { - "debug": "^4.4.0", - "fflate": "^0.8.2", - "token-types": "^6.0.0" + "debug": "^4.4.3", + "token-types": "^6.1.1" }, "engines": { "node": ">=18" @@ -2898,6 +2875,7 @@ "integrity": "sha512-IPaCZN7PShZK/3t6Q87pfTkRm6oLTd4vztyoj+cbHUF1g3FfVb2tFIL79uCRKEfv16AhqDMBywP2VW3KIZUvcg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -2928,17 +2906,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.42.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.42.0.tgz", - "integrity": "sha512-Aq2dPqsQkxHOLfb2OPv43RnIvfj05nw8v/6n3B2NABIPpHnjQnaLo9QGMTvml+tv4korl/Cjfrb/BYhoL8UUTQ==", + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.48.0.tgz", + "integrity": "sha512-XxXP5tL1txl13YFtrECECQYeZjBZad4fyd3cFV4a19LkAY/bIp9fev3US4S5fDVV2JaYFiKAZ/GRTOLer+mbyQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.42.0", - "@typescript-eslint/type-utils": "8.42.0", - "@typescript-eslint/utils": "8.42.0", - "@typescript-eslint/visitor-keys": "8.42.0", + "@typescript-eslint/scope-manager": "8.48.0", + "@typescript-eslint/type-utils": "8.48.0", + "@typescript-eslint/utils": "8.48.0", + "@typescript-eslint/visitor-keys": "8.48.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", @@ -2952,7 +2930,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.42.0", + "@typescript-eslint/parser": "^8.48.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } @@ -2968,16 +2946,17 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.42.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.42.0.tgz", - "integrity": "sha512-r1XG74QgShUgXph1BYseJ+KZd17bKQib/yF3SR+demvytiRXrwd12Blnz5eYGm8tXaeRdd4x88MlfwldHoudGg==", + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.48.0.tgz", + "integrity": "sha512-jCzKdm/QK0Kg4V4IK/oMlRZlY+QOcdjv89U2NgKHZk1CYTj82/RVSx1mV/0gqCVMJ/DA+Zf/S4NBWNF8GQ+eqQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "@typescript-eslint/scope-manager": "8.42.0", - "@typescript-eslint/types": "8.42.0", - "@typescript-eslint/typescript-estree": "8.42.0", - "@typescript-eslint/visitor-keys": "8.42.0", + "@typescript-eslint/scope-manager": "8.48.0", + "@typescript-eslint/types": "8.48.0", + "@typescript-eslint/typescript-estree": "8.48.0", + "@typescript-eslint/visitor-keys": "8.48.0", "debug": "^4.3.4" }, "engines": { @@ -2993,14 +2972,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.42.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.42.0.tgz", - "integrity": "sha512-vfVpLHAhbPjilrabtOSNcUDmBboQNrJUiNAGoImkZKnMjs2TIcWG33s4Ds0wY3/50aZmTMqJa6PiwkwezaAklg==", + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.48.0.tgz", + "integrity": "sha512-Ne4CTZyRh1BecBf84siv42wv5vQvVmgtk8AuiEffKTUo3DrBaGYZueJSxxBZ8fjk/N3DrgChH4TOdIOwOwiqqw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.42.0", - "@typescript-eslint/types": "^8.42.0", + "@typescript-eslint/tsconfig-utils": "^8.48.0", + "@typescript-eslint/types": "^8.48.0", "debug": "^4.3.4" }, "engines": { @@ -3015,14 +2994,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.42.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.42.0.tgz", - "integrity": "sha512-51+x9o78NBAVgQzOPd17DkNTnIzJ8T/O2dmMBLoK9qbY0Gm52XJcdJcCl18ExBMiHo6jPMErUQWUv5RLE51zJw==", + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.48.0.tgz", + "integrity": "sha512-uGSSsbrtJrLduti0Q1Q9+BF1/iFKaxGoQwjWOIVNJv0o6omrdyR8ct37m4xIl5Zzpkp69Kkmvom7QFTtue89YQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.42.0", - "@typescript-eslint/visitor-keys": "8.42.0" + "@typescript-eslint/types": "8.48.0", + "@typescript-eslint/visitor-keys": "8.48.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3033,9 +3012,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.42.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.42.0.tgz", - "integrity": "sha512-kHeFUOdwAJfUmYKjR3CLgZSglGHjbNTi1H8sTYRYV2xX6eNz4RyJ2LIgsDLKf8Yi0/GL1WZAC/DgZBeBft8QAQ==", + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.48.0.tgz", + "integrity": "sha512-WNebjBdFdyu10sR1M4OXTt2OkMd5KWIL+LLfeH9KhgP+jzfDV/LI3eXzwJ1s9+Yc0Kzo2fQCdY/OpdusCMmh6w==", "dev": true, "license": "MIT", "engines": { @@ -3050,15 +3029,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.42.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.42.0.tgz", - "integrity": "sha512-9KChw92sbPTYVFw3JLRH1ockhyR3zqqn9lQXol3/YbI6jVxzWoGcT3AsAW0mu1MY0gYtsXnUGV/AKpkAj5tVlQ==", + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.48.0.tgz", + "integrity": "sha512-zbeVaVqeXhhab6QNEKfK96Xyc7UQuoFWERhEnj3mLVnUWrQnv15cJNseUni7f3g557gm0e46LZ6IJ4NJVOgOpw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.42.0", - "@typescript-eslint/typescript-estree": "8.42.0", - "@typescript-eslint/utils": "8.42.0", + "@typescript-eslint/types": "8.48.0", + "@typescript-eslint/typescript-estree": "8.48.0", + "@typescript-eslint/utils": "8.48.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, @@ -3075,9 +3054,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.42.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.42.0.tgz", - "integrity": "sha512-LdtAWMiFmbRLNP7JNeY0SqEtJvGMYSzfiWBSmx+VSZ1CH+1zyl8Mmw1TT39OrtsRvIYShjJWzTDMPWZJCpwBlw==", + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.48.0.tgz", + "integrity": "sha512-cQMcGQQH7kwKoVswD1xdOytxQR60MWKM1di26xSUtxehaDs/32Zpqsu5WJlXTtTTqyAVK8R7hvsUnIXRS+bjvA==", "dev": true, "license": "MIT", "engines": { @@ -3089,21 +3068,20 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.42.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.42.0.tgz", - "integrity": "sha512-ku/uYtT4QXY8sl9EDJETD27o3Ewdi72hcXg1ah/kkUgBvAYHLwj2ofswFFNXS+FL5G+AGkxBtvGt8pFBHKlHsQ==", + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.48.0.tgz", + "integrity": "sha512-ljHab1CSO4rGrQIAyizUS6UGHHCiAYhbfcIZ1zVJr5nMryxlXMVWS3duFPSKvSUbFPwkXMFk1k0EMIjub4sRRQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.42.0", - "@typescript-eslint/tsconfig-utils": "8.42.0", - "@typescript-eslint/types": "8.42.0", - "@typescript-eslint/visitor-keys": "8.42.0", + "@typescript-eslint/project-service": "8.48.0", + "@typescript-eslint/tsconfig-utils": "8.48.0", + "@typescript-eslint/types": "8.48.0", + "@typescript-eslint/visitor-keys": "8.48.0", "debug": "^4.3.4", - "fast-glob": "^3.3.2", - "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", + "tinyglobby": "^0.2.15", "ts-api-utils": "^2.1.0" }, "engines": { @@ -3118,16 +3096,17 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.42.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.42.0.tgz", - "integrity": "sha512-JnIzu7H3RH5BrKC4NoZqRfmjqCIS1u3hGZltDYJgkVdqAezl4L9d1ZLw+36huCujtSBSAirGINF/S4UxOcR+/g==", + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.48.0.tgz", + "integrity": "sha512-yTJO1XuGxCsSfIVt1+1UrLHtue8xz16V8apzPYI06W0HbEbEWHxHXgZaAgavIkoh+GeV6hKKd5jm0sS6OYxWXQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.42.0", - "@typescript-eslint/types": "8.42.0", - "@typescript-eslint/typescript-estree": "8.42.0" + "@typescript-eslint/scope-manager": "8.48.0", + "@typescript-eslint/types": "8.48.0", + "@typescript-eslint/typescript-estree": "8.48.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3142,13 +3121,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.42.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.42.0.tgz", - "integrity": "sha512-3WbiuzoEowaEn8RSnhJBrxSwX8ULYE9CXaPepS2C2W3NSA5NNIvBaslpBSBElPq0UGr0xVJlXFWOAKIkyylydQ==", + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.48.0.tgz", + "integrity": "sha512-T0XJMaRPOH3+LBbAfzR2jalckP1MSG/L9eUtY0DEzUyVaXJ/t6zN0nR7co5kz0Jko/nkSYCBRkz1djvjajVTTg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.42.0", + "@typescript-eslint/types": "8.48.0", "eslint-visitor-keys": "^4.2.1" }, "engines": { @@ -4074,6 +4053,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4388,9 +4368,9 @@ } }, "node_modules/autoprefixer": { - "version": "10.4.21", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", - "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", + "version": "10.4.22", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.22.tgz", + "integrity": "sha512-ARe0v/t9gO28Bznv6GgqARmVqcWOV3mfgUPn9becPHMiD3o9BwlRgaeccZnwTpZ7Zwqrm+c1sUSsMxIzQzc8Xg==", "dev": true, "funding": [ { @@ -4408,9 +4388,9 @@ ], "license": "MIT", "dependencies": { - "browserslist": "^4.24.4", - "caniuse-lite": "^1.0.30001702", - "fraction.js": "^4.3.7", + "browserslist": "^4.27.0", + "caniuse-lite": "^1.0.30001754", + "fraction.js": "^5.3.4", "normalize-range": "^0.1.2", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" @@ -4499,6 +4479,16 @@ ], "license": "MIT" }, + "node_modules/baseline-browser-mapping": { + "version": "2.8.31", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.31.tgz", + "integrity": "sha512-a28v2eWrrRWPpJSzxc+mKwm0ZtVx/G8SepdQZDArnXYU/XS+IF6mp8aB/4E+hH1tyGCoDo3KlUCdlSxGDsRkAw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, "node_modules/basic-auth": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", @@ -4613,9 +4603,9 @@ } }, "node_modules/browserslist": { - "version": "4.24.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", - "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz", + "integrity": "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==", "dev": true, "funding": [ { @@ -4632,11 +4622,13 @@ } ], "license": "MIT", + "peer": true, "dependencies": { - "caniuse-lite": "^1.0.30001688", - "electron-to-chromium": "^1.5.73", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.1" + "baseline-browser-mapping": "^2.8.25", + "caniuse-lite": "^1.0.30001754", + "electron-to-chromium": "^1.5.249", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.1.4" }, "bin": { "browserslist": "cli.js" @@ -4683,18 +4675,18 @@ "license": "MIT" }, "node_modules/bullmq": { - "version": "5.58.5", - "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.58.5.tgz", - "integrity": "sha512-0A6Qjxdn8j7aOcxfRZY798vO/aMuwvoZwfE6a9EOXHb1pzpBVAogsc/OfRWeUf+5wMBoYB5nthstnJo/zrQOeQ==", + "version": "5.64.1", + "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.64.1.tgz", + "integrity": "sha512-Tg4ORit8bQ1xLwcQrEfcDpG50pS30Onuz1ZA4rPLbL9QEsOaBbvFQMSEvXSgMnvRZRqDggJoSBGz9tck+1PixQ==", "license": "MIT", "dependencies": { "cron-parser": "^4.9.0", - "ioredis": "^5.4.1", + "ioredis": "^5.8.2", "msgpackr": "^1.11.2", "node-abort-controller": "^3.1.1", "semver": "^7.5.4", "tslib": "^2.0.0", - "uuid": "^9.0.0" + "uuid": "^11.1.0" } }, "node_modules/bytes": { @@ -4887,9 +4879,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001703", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001703.tgz", - "integrity": "sha512-kRlAGTRWgPsOj7oARC9m1okJEXdL/8fekFVcxA8Hl7GH4r/sN4OJn/i6Flde373T50KS7Y37oFbMwlE8+F42kQ==", + "version": "1.0.30001757", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001757.tgz", + "integrity": "sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ==", "dev": true, "funding": [ { @@ -5114,6 +5106,7 @@ "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=18" } @@ -5714,9 +5707,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.109", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.109.tgz", - "integrity": "sha512-AidaH9JETVRr9DIPGfp1kAarm/W6hRJTPuCnkF+2MqhF4KaAgRIcBc8nvjk+YMXZhwfISof/7WG29eS4iGxQLQ==", + "version": "1.5.260", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.260.tgz", + "integrity": "sha512-ov8rBoOBhVawpzdre+Cmz4FB+y66Eqrk6Gwqd8NGxuhv99GQ8XqMAr351KEkOt7gukXWDg6gJWEMKgL2RLMPtA==", "dev": true, "license": "ISC" }, @@ -5991,6 +5984,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -6070,25 +6064,25 @@ } }, "node_modules/eslint": { - "version": "9.34.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.34.0.tgz", - "integrity": "sha512-RNCHRX5EwdrESy3Jc9o8ie8Bog+PeYvvSR8sDGoZxNFTvZ4dlxUB3WzQ3bQMztFrSRODGrLLj8g6OFuGY/aiQg==", + "version": "9.39.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz", + "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.0", - "@eslint/config-helpers": "^0.3.1", - "@eslint/core": "^0.15.2", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.34.0", - "@eslint/plugin-kit": "^0.3.5", + "@eslint/js": "9.39.1", + "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", - "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", @@ -6246,6 +6240,7 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -6280,6 +6275,7 @@ "integrity": "sha512-vPZZsiOKaBAIATpFE2uMI4w5IRwdv/FpQ+qZZMR4E+PeOcM4OeoEbqxRMnywdxP19TyB/3h6QBB0EWon7letSQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/types": "^8.35.0", "comment-parser": "^1.4.1", @@ -6884,6 +6880,7 @@ "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "license": "MIT", + "peer": true, "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -6997,23 +6994,6 @@ "dev": true, "license": "MIT" }, - "node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -7028,16 +7008,6 @@ "dev": true, "license": "MIT" }, - "node_modules/fastq": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, "node_modules/fault": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/fault/-/fault-2.0.1.tgz", @@ -7052,12 +7022,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/fflate": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", - "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", - "license": "MIT" - }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -7072,14 +7036,14 @@ } }, "node_modules/file-type": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.0.0.tgz", - "integrity": "sha512-ek5xNX2YBYlXhiUXui3D/BXa3LdqPmoLJ7rqEx2bKJ7EAUEfmXgW0Das7Dc6Nr9MvqaOnIqiPV0mZk/r/UpNAg==", + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.1.1.tgz", + "integrity": "sha512-ifJXo8zUqbQ/bLbl9sFoqHNTNWbnPY1COImFfM6CCy7z+E+jC1eY9YfOKkx0fckIg+VljAy2/87T61fp0+eEkg==", "license": "MIT", "dependencies": { - "@tokenizer/inflate": "^0.2.7", - "strtok3": "^10.2.2", - "token-types": "^6.0.0", + "@tokenizer/inflate": "^0.4.1", + "strtok3": "^10.3.4", + "token-types": "^6.1.1", "uint8array-extras": "^1.4.0" }, "engines": { @@ -7236,16 +7200,16 @@ } }, "node_modules/fraction.js": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", - "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", "dev": true, "license": "MIT", "engines": { "node": "*" }, "funding": { - "type": "patreon", + "type": "github", "url": "https://github.com/sponsors/rawify" } }, @@ -7517,9 +7481,9 @@ } }, "node_modules/globals": { - "version": "16.3.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-16.3.0.tgz", - "integrity": "sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ==", + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", + "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==", "dev": true, "license": "MIT", "engines": { @@ -7896,12 +7860,12 @@ } }, "node_modules/ioredis": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.6.0.tgz", - "integrity": "sha512-tBZlIIWbndeWBWCXWZiqtOF/yxf6yZX3tAlTJ7nfo5jhd6dctNxF7QnYlZLZ1a0o0pDoen7CgZqO+zjNaFbJAg==", + "version": "5.8.2", + "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.8.2.tgz", + "integrity": "sha512-C6uC+kleiIMmjViJINWk80sOQw5lEzse1ZmvD+S/s8p8CWapftSaC+kocGTx6xrbrJ4WmYQGC08ffHLr6ToR6Q==", "license": "MIT", "dependencies": { - "@ioredis/commands": "^1.1.1", + "@ioredis/commands": "1.4.0", "cluster-key-slot": "^1.1.0", "debug": "^4.3.4", "denque": "^2.1.0", @@ -8521,9 +8485,9 @@ "license": "MIT" }, "node_modules/isbot": { - "version": "5.1.30", - "resolved": "https://registry.npmjs.org/isbot/-/isbot-5.1.30.tgz", - "integrity": "sha512-3wVJEonAns1OETX83uWsk5IAne2S5zfDcntD2hbtU23LelSqNXzXs9zKjMPOLMzroCgIjCfjYAEHrd2D6FOkiA==", + "version": "5.1.32", + "resolved": "https://registry.npmjs.org/isbot/-/isbot-5.1.32.tgz", + "integrity": "sha512-VNfjM73zz2IBZmdShMfAUg10prm6t7HFUQmNAEOAVS4YH92ZrZcvkMcGX6cIgBJAzWDzPent/EeAtYEHNPNPBQ==", "license": "Unlicense", "engines": { "node": ">=18" @@ -8578,9 +8542,9 @@ "license": "MIT" }, "node_modules/jiti": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz", - "integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", "license": "MIT", "bin": { "jiti": "lib/jiti-cli.mjs" @@ -8796,11 +8760,12 @@ } }, "node_modules/lightningcss": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz", - "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==", + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", + "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==", "dev": true, "license": "MPL-2.0", + "peer": true, "dependencies": { "detect-libc": "^2.0.3" }, @@ -8812,22 +8777,44 @@ "url": "https://opencollective.com/parcel" }, "optionalDependencies": { - "lightningcss-darwin-arm64": "1.30.1", - "lightningcss-darwin-x64": "1.30.1", - "lightningcss-freebsd-x64": "1.30.1", - "lightningcss-linux-arm-gnueabihf": "1.30.1", - "lightningcss-linux-arm64-gnu": "1.30.1", - "lightningcss-linux-arm64-musl": "1.30.1", - "lightningcss-linux-x64-gnu": "1.30.1", - "lightningcss-linux-x64-musl": "1.30.1", - "lightningcss-win32-arm64-msvc": "1.30.1", - "lightningcss-win32-x64-msvc": "1.30.1" + "lightningcss-android-arm64": "1.30.2", + "lightningcss-darwin-arm64": "1.30.2", + "lightningcss-darwin-x64": "1.30.2", + "lightningcss-freebsd-x64": "1.30.2", + "lightningcss-linux-arm-gnueabihf": "1.30.2", + "lightningcss-linux-arm64-gnu": "1.30.2", + "lightningcss-linux-arm64-musl": "1.30.2", + "lightningcss-linux-x64-gnu": "1.30.2", + "lightningcss-linux-x64-musl": "1.30.2", + "lightningcss-win32-arm64-msvc": "1.30.2", + "lightningcss-win32-x64-msvc": "1.30.2" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz", + "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, "node_modules/lightningcss-darwin-arm64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz", - "integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==", + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz", + "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==", "cpu": [ "arm64" ], @@ -8846,9 +8833,9 @@ } }, "node_modules/lightningcss-darwin-x64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz", - "integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==", + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz", + "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==", "cpu": [ "x64" ], @@ -8867,9 +8854,9 @@ } }, "node_modules/lightningcss-freebsd-x64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz", - "integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==", + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz", + "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==", "cpu": [ "x64" ], @@ -8888,9 +8875,9 @@ } }, "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz", - "integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==", + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz", + "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==", "cpu": [ "arm" ], @@ -8909,9 +8896,9 @@ } }, "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz", - "integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==", + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz", + "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==", "cpu": [ "arm64" ], @@ -8930,9 +8917,9 @@ } }, "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz", - "integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==", + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz", + "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==", "cpu": [ "arm64" ], @@ -8951,9 +8938,9 @@ } }, "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz", - "integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==", + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz", + "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==", "cpu": [ "x64" ], @@ -8972,9 +8959,9 @@ } }, "node_modules/lightningcss-linux-x64-musl": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz", - "integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==", + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz", + "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==", "cpu": [ "x64" ], @@ -8993,9 +8980,9 @@ } }, "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz", - "integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==", + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz", + "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==", "cpu": [ "arm64" ], @@ -9014,9 +9001,9 @@ } }, "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz", - "integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==", + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz", + "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==", "cpu": [ "x64" ], @@ -9269,9 +9256,9 @@ } }, "node_modules/magic-string": { - "version": "0.30.18", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.18.tgz", - "integrity": "sha512-yi8swmWbO17qHhwIBNeeZxTceJMeBvWJaId6dyvTSOwTipqeHhMhOrz6513r1sOKnpvQ7zkhlG8tPrpilwTxHQ==", + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "dev": true, "license": "MIT", "dependencies": { @@ -9548,16 +9535,6 @@ "dev": true, "license": "MIT" }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, "node_modules/methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -10208,20 +10185,6 @@ ], "license": "MIT" }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, "node_modules/mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -10592,9 +10555,9 @@ } }, "node_modules/music-metadata": { - "version": "11.9.0", - "resolved": "https://registry.npmjs.org/music-metadata/-/music-metadata-11.9.0.tgz", - "integrity": "sha512-J7VqD8FY6KRcm75Fzj86FPsckiD/EdvO5OS3P+JiMf/2krP3TcAseZYfkic6eFeJ0iBhhzcdxgfu8hLW95aXXw==", + "version": "11.10.3", + "resolved": "https://registry.npmjs.org/music-metadata/-/music-metadata-11.10.3.tgz", + "integrity": "sha512-j0g/x4cNNZW6I5gdcPAY+GFkJY9WHTpkFDMBJKQLxJQyvSfQbXm57fTE3haGFFuOzCgtsTd4Plwc49Sn9RacDQ==", "funding": [ { "type": "github", @@ -10611,7 +10574,7 @@ "@tokenizer/token": "^0.3.0", "content-type": "^1.0.5", "debug": "^4.4.3", - "file-type": "^21.0.0", + "file-type": "^21.1.1", "media-typer": "^1.1.0", "strtok3": "^10.3.4", "token-types": "^6.1.1", @@ -10716,9 +10679,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", "dev": true, "license": "MIT" }, @@ -11692,6 +11655,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -11902,6 +11866,7 @@ "integrity": "sha512-E6RCgOt+kUVtjtZgLQDBJ6md2tDItLJNExwI0XJeBc1FKL+Vwb+ovxXxuok9r8oBgsOXBA33fGDuE/0qDdCWqQ==", "hasInstallScript": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "@prisma/config": "6.15.0", "@prisma/engines": "6.15.0" @@ -12076,27 +12041,6 @@ ], "license": "MIT" }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -12136,6 +12080,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -12148,6 +12093,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -12642,17 +12588,6 @@ "node": ">= 4" } }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, "node_modules/rollup": { "version": "4.50.0", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.50.0.tgz", @@ -12694,30 +12629,6 @@ "fsevents": "~2.3.2" } }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, "node_modules/sade": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", @@ -12821,9 +12732,9 @@ } }, "node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -13563,16 +13474,16 @@ } }, "node_modules/tailwindcss": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.13.tgz", - "integrity": "sha512-i+zidfmTqtwquj4hMEwdjshYYgMbOrPzb9a0M3ZgNa0JMoZeFC6bxZvO8yr8ozS6ix2SDz0+mvryPeBs2TFE+w==", + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.17.tgz", + "integrity": "sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q==", "dev": true, "license": "MIT" }, "node_modules/tapable": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.3.tgz", - "integrity": "sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", "dev": true, "license": "MIT", "engines": { @@ -13763,14 +13674,14 @@ "license": "MIT" }, "node_modules/tinyglobby": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", - "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", "dev": true, "license": "MIT", "dependencies": { - "fdir": "^6.4.4", - "picomatch": "^4.0.2" + "fdir": "^6.5.0", + "picomatch": "^4.0.3" }, "engines": { "node": ">=12.0.0" @@ -13780,11 +13691,14 @@ } }, "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.4.4", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", - "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, "peerDependencies": { "picomatch": "^3 || ^4" }, @@ -13795,11 +13709,12 @@ } }, "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -14063,11 +13978,12 @@ } }, "node_modules/typescript": { - "version": "5.9.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", - "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -14077,16 +13993,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.42.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.42.0.tgz", - "integrity": "sha512-ozR/rQn+aQXQxh1YgbCzQWDFrsi9mcg+1PM3l/z5o1+20P7suOIaNg515bpr/OYt6FObz/NHcBstydDLHWeEKg==", + "version": "8.48.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.48.0.tgz", + "integrity": "sha512-fcKOvQD9GUn3Xw63EgiDqhvWJ5jsyZUaekl3KVpGsDJnN46WJTe3jWxtQP9lMZm1LJNkFLlTaWAxK2vUQR+cqw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.42.0", - "@typescript-eslint/parser": "8.42.0", - "@typescript-eslint/typescript-estree": "8.42.0", - "@typescript-eslint/utils": "8.42.0" + "@typescript-eslint/eslint-plugin": "8.48.0", + "@typescript-eslint/parser": "8.48.0", + "@typescript-eslint/typescript-estree": "8.48.0", + "@typescript-eslint/utils": "8.48.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -14381,9 +14297,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", + "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", "dev": true, "funding": [ { @@ -14451,16 +14367,16 @@ } }, "node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" ], "license": "MIT", "bin": { - "uuid": "dist/bin/uuid" + "uuid": "dist/esm/bin/uuid" } }, "node_modules/uvu": { @@ -15122,6 +15038,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, diff --git a/package.json b/package.json index eb263ad..91ffa10 100644 --- a/package.json +++ b/package.json @@ -24,20 +24,20 @@ "@remix-run/react": "^2.17.0", "@remix-run/serve": "^2.17.0", "adm-zip": "^0.5.16", - "bullmq": "^5.58.5", + "bullmq": "^5.64.1", "date-fns": "^4.1.0", - "isbot": "^5.1.30", + "isbot": "^5.1.32", "jsonwebtoken": "^9.0.2", "mkdirp": "^3.0.1", - "music-metadata": "^11.9.0", + "music-metadata": "^11.10.3", "prisma": "^6.15.0", "react": "^18.2.0", "react-dom": "^18.2.0", - "semver": "^7.7.2" + "semver": "^7.7.3" }, "devDependencies": { - "@remix-run/dev": "^2.17.0", - "@tailwindcss/postcss": "^4.1.13", + "@remix-run/dev": "^2.17.2", + "@tailwindcss/postcss": "^4.1.17", "@types/adm-zip": "^0.5.7", "@types/jsonwebtoken": "^9.0.10", "@types/react": "^18.2.20", @@ -45,22 +45,22 @@ "@types/semver": "^7.7.1", "@typescript-eslint/eslint-plugin": "^8.37.0", "@typescript-eslint/parser": "^8.37.0", - "autoprefixer": "^10.4.21", + "autoprefixer": "^10.4.22", "env-cmd": "^11.0.0", - "eslint": "^9.34.0", + "eslint": "^9.39.1", "eslint-import-resolver-typescript": "^4.4.4", "eslint-plugin-import": "^2.32.0", "eslint-plugin-import-x": "^4.16.1", "eslint-plugin-jsx-a11y": "^6.10.2", "eslint-plugin-react": "^7.37.5", "eslint-plugin-react-hooks": "^5.2.0", - "globals": "^16.3.0", + "globals": "^16.5.0", "npm-run-all": "^4.1.5", "postcss": "^8.5.6", "prettier": "^3.6.2", - "tailwindcss": "^4.1.13", - "typescript": "5.9.2", - "typescript-eslint": "^8.42.0", + "tailwindcss": "^4.1.17", + "typescript": "5.9.3", + "typescript-eslint": "^8.47.0", "vite": "^6.3.5", "vite-tsconfig-paths": "^5.1.4" }, diff --git a/prisma/migrations/20251118144444_add_action_buttons/migration.sql b/prisma/migrations/20251118144444_add_action_buttons/migration.sql new file mode 100644 index 0000000..f4f5072 --- /dev/null +++ b/prisma/migrations/20251118144444_add_action_buttons/migration.sql @@ -0,0 +1,8 @@ +-- CreateTable +CREATE TABLE "ActionButton" ( + "id" TEXT NOT NULL PRIMARY KEY, + "name" TEXT NOT NULL, + "key" TEXT NOT NULL, + "actionId" TEXT NOT NULL, + CONSTRAINT "ActionButton_actionId_fkey" FOREIGN KEY ("actionId") REFERENCES "Action" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); diff --git a/prisma/migrations/20251119092757_add_pin_and_duration_configuration_to_the_buttons/migration.sql b/prisma/migrations/20251119092757_add_pin_and_duration_configuration_to_the_buttons/migration.sql new file mode 100644 index 0000000..3d60718 --- /dev/null +++ b/prisma/migrations/20251119092757_add_pin_and_duration_configuration_to_the_buttons/migration.sql @@ -0,0 +1,19 @@ +-- RedefineTables +PRAGMA defer_foreign_keys=ON; +PRAGMA foreign_keys=OFF; +CREATE TABLE "new_ActionButton" ( + "id" TEXT NOT NULL PRIMARY KEY, + "name" TEXT NOT NULL, + "key" TEXT NOT NULL, + "ledPin" INTEGER NOT NULL DEFAULT 0, + "buttonPin" INTEGER NOT NULL DEFAULT 0, + "holdDuration" INTEGER NOT NULL DEFAULT 0, + "cancelDuration" INTEGER NOT NULL DEFAULT 0, + "actionId" TEXT NOT NULL, + CONSTRAINT "ActionButton_actionId_fkey" FOREIGN KEY ("actionId") REFERENCES "Action" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); +INSERT INTO "new_ActionButton" ("actionId", "id", "key", "name") SELECT "actionId", "id", "key", "name" FROM "ActionButton"; +DROP TABLE "ActionButton"; +ALTER TABLE "new_ActionButton" RENAME TO "ActionButton"; +PRAGMA foreign_keys=ON; +PRAGMA defer_foreign_keys=OFF; diff --git a/prisma/migrations/20251119115345_add_ip_to_button/migration.sql b/prisma/migrations/20251119115345_add_ip_to_button/migration.sql new file mode 100644 index 0000000..42908c1 --- /dev/null +++ b/prisma/migrations/20251119115345_add_ip_to_button/migration.sql @@ -0,0 +1,20 @@ +-- RedefineTables +PRAGMA defer_foreign_keys=ON; +PRAGMA foreign_keys=OFF; +CREATE TABLE "new_ActionButton" ( + "id" TEXT NOT NULL PRIMARY KEY, + "name" TEXT NOT NULL, + "key" TEXT NOT NULL, + "ip" TEXT NOT NULL DEFAULT '', + "ledPin" INTEGER NOT NULL DEFAULT 0, + "buttonPin" INTEGER NOT NULL DEFAULT 0, + "holdDuration" INTEGER NOT NULL DEFAULT 0, + "cancelDuration" INTEGER NOT NULL DEFAULT 0, + "actionId" TEXT NOT NULL, + CONSTRAINT "ActionButton_actionId_fkey" FOREIGN KEY ("actionId") REFERENCES "Action" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); +INSERT INTO "new_ActionButton" ("actionId", "buttonPin", "cancelDuration", "holdDuration", "id", "key", "ledPin", "name") SELECT "actionId", "buttonPin", "cancelDuration", "holdDuration", "id", "key", "ledPin", "name" FROM "ActionButton"; +DROP TABLE "ActionButton"; +ALTER TABLE "new_ActionButton" RENAME TO "ActionButton"; +PRAGMA foreign_keys=ON; +PRAGMA defer_foreign_keys=OFF; diff --git a/prisma/migrations/20251119122451_add_action_button_log/migration.sql b/prisma/migrations/20251119122451_add_action_button_log/migration.sql new file mode 100644 index 0000000..d3c727a --- /dev/null +++ b/prisma/migrations/20251119122451_add_action_button_log/migration.sql @@ -0,0 +1,8 @@ +-- CreateTable +CREATE TABLE "ActionButtonLog" ( + "id" TEXT NOT NULL PRIMARY KEY, + "time" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "message" TEXT NOT NULL, + "actionButtonId" TEXT NOT NULL, + CONSTRAINT "ActionButtonLog_actionButtonId_fkey" FOREIGN KEY ("actionButtonId") REFERENCES "ActionButton" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); diff --git a/prisma/migrations/20251119130829_add_enrolled_to_button/migration.sql b/prisma/migrations/20251119130829_add_enrolled_to_button/migration.sql new file mode 100644 index 0000000..d5edd63 --- /dev/null +++ b/prisma/migrations/20251119130829_add_enrolled_to_button/migration.sql @@ -0,0 +1,21 @@ +-- RedefineTables +PRAGMA defer_foreign_keys=ON; +PRAGMA foreign_keys=OFF; +CREATE TABLE "new_ActionButton" ( + "id" TEXT NOT NULL PRIMARY KEY, + "name" TEXT NOT NULL, + "key" TEXT NOT NULL, + "ip" TEXT NOT NULL DEFAULT '', + "enrolled" BOOLEAN NOT NULL DEFAULT false, + "ledPin" INTEGER NOT NULL DEFAULT 0, + "buttonPin" INTEGER NOT NULL DEFAULT 0, + "holdDuration" INTEGER NOT NULL DEFAULT 0, + "cancelDuration" INTEGER NOT NULL DEFAULT 0, + "actionId" TEXT NOT NULL, + CONSTRAINT "ActionButton_actionId_fkey" FOREIGN KEY ("actionId") REFERENCES "Action" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); +INSERT INTO "new_ActionButton" ("actionId", "buttonPin", "cancelDuration", "holdDuration", "id", "ip", "key", "ledPin", "name") SELECT "actionId", "buttonPin", "cancelDuration", "holdDuration", "id", "ip", "key", "ledPin", "name" FROM "ActionButton"; +DROP TABLE "ActionButton"; +ALTER TABLE "new_ActionButton" RENAME TO "ActionButton"; +PRAGMA foreign_keys=ON; +PRAGMA defer_foreign_keys=OFF; diff --git a/prisma/migrations/20251119204729_add_last_seen_to_action_button/migration.sql b/prisma/migrations/20251119204729_add_last_seen_to_action_button/migration.sql new file mode 100644 index 0000000..64f2d49 --- /dev/null +++ b/prisma/migrations/20251119204729_add_last_seen_to_action_button/migration.sql @@ -0,0 +1,22 @@ +-- RedefineTables +PRAGMA defer_foreign_keys=ON; +PRAGMA foreign_keys=OFF; +CREATE TABLE "new_ActionButton" ( + "id" TEXT NOT NULL PRIMARY KEY, + "name" TEXT NOT NULL, + "key" TEXT NOT NULL, + "ip" TEXT NOT NULL DEFAULT '', + "enrolled" BOOLEAN NOT NULL DEFAULT false, + "lastCheckIn" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "ledPin" INTEGER NOT NULL DEFAULT 0, + "buttonPin" INTEGER NOT NULL DEFAULT 0, + "holdDuration" INTEGER NOT NULL DEFAULT 0, + "cancelDuration" INTEGER NOT NULL DEFAULT 0, + "actionId" TEXT NOT NULL, + CONSTRAINT "ActionButton_actionId_fkey" FOREIGN KEY ("actionId") REFERENCES "Action" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); +INSERT INTO "new_ActionButton" ("actionId", "buttonPin", "cancelDuration", "enrolled", "holdDuration", "id", "ip", "key", "ledPin", "name") SELECT "actionId", "buttonPin", "cancelDuration", "enrolled", "holdDuration", "id", "ip", "key", "ledPin", "name" FROM "ActionButton"; +DROP TABLE "ActionButton"; +ALTER TABLE "new_ActionButton" RENAME TO "ActionButton"; +PRAGMA foreign_keys=ON; +PRAGMA defer_foreign_keys=OFF; diff --git a/prisma/migrations/20251123203946_add_zone_to_button/migration.sql b/prisma/migrations/20251123203946_add_zone_to_button/migration.sql new file mode 100644 index 0000000..f10b838 --- /dev/null +++ b/prisma/migrations/20251123203946_add_zone_to_button/migration.sql @@ -0,0 +1,24 @@ +-- RedefineTables +PRAGMA defer_foreign_keys=ON; +PRAGMA foreign_keys=OFF; +CREATE TABLE "new_ActionButton" ( + "id" TEXT NOT NULL PRIMARY KEY, + "name" TEXT NOT NULL, + "key" TEXT NOT NULL, + "ip" TEXT NOT NULL DEFAULT '', + "enrolled" BOOLEAN NOT NULL DEFAULT false, + "lastCheckIn" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "ledPin" INTEGER NOT NULL DEFAULT 0, + "buttonPin" INTEGER NOT NULL DEFAULT 0, + "holdDuration" INTEGER NOT NULL DEFAULT 0, + "cancelDuration" INTEGER NOT NULL DEFAULT 0, + "actionId" TEXT NOT NULL, + "zoneId" TEXT, + CONSTRAINT "ActionButton_actionId_fkey" FOREIGN KEY ("actionId") REFERENCES "Action" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, + CONSTRAINT "ActionButton_zoneId_fkey" FOREIGN KEY ("zoneId") REFERENCES "Zone" ("id") ON DELETE SET NULL ON UPDATE CASCADE +); +INSERT INTO "new_ActionButton" ("actionId", "buttonPin", "cancelDuration", "enrolled", "holdDuration", "id", "ip", "key", "lastCheckIn", "ledPin", "name") SELECT "actionId", "buttonPin", "cancelDuration", "enrolled", "holdDuration", "id", "ip", "key", "lastCheckIn", "ledPin", "name" FROM "ActionButton"; +DROP TABLE "ActionButton"; +ALTER TABLE "new_ActionButton" RENAME TO "ActionButton"; +PRAGMA foreign_keys=ON; +PRAGMA defer_foreign_keys=OFF; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 5475afd..fcfdc5a 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -40,8 +40,9 @@ model Zone { id String @id @default(uuid()) name String - sounders ZoneSounder[] - schedules Schedule[] + sounders ZoneSounder[] + schedules Schedule[] + ActionButton ActionButton[] } model ZoneSounder { @@ -110,6 +111,7 @@ model Action { audioId String? webhooks Webhook[] + buttons ActionButton[] } model Setting { @@ -146,3 +148,33 @@ model OutboundWebhook { event String key String } + +model ActionButton { + id String @id @default(uuid()) + name String + key String + ip String @default("") + enrolled Boolean @default(false) + lastCheckIn DateTime @default(now()) + + ledPin Int @default(0) + buttonPin Int @default(0) + holdDuration Int @default(0) + cancelDuration Int @default(0) + + action Action @relation(fields: [actionId], references: [id]) + actionId String + zone Zone? @relation(fields: [zoneId], references: [id]) + zoneId String? + + logs ActionButtonLog[] +} + +model ActionButtonLog { + id String @id @default(uuid()) + time DateTime @default(now()) + message String + + actionButton ActionButton @relation(fields: [actionButtonId], references: [id]) + actionButtonId String +}