Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion app/lib/settings.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ type SettingKey =
| 'password'
| 'ttsSpeed'
| 'enrollUrl'
| 'controlPointKey'

export const DEFAULT_SETTINGS: {[setting in SettingKey]: string} = {
lockdownEntrySound: '',
Expand All @@ -23,7 +24,8 @@ export const DEFAULT_SETTINGS: {[setting in SettingKey]: string} = {
lockdownRepetitions: '4',
password: 'bell',
ttsSpeed: '1',
enrollUrl: 'http://controller:3000'
enrollUrl: 'http://controller:3000',
controlPointKey: ''
}

export const getSetting = async (setting: SettingKey) => {
Expand Down
21 changes: 21 additions & 0 deletions app/lib/trigger-action.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import {type Action} from '@prisma/client'

import {broadcast} from './broadcast.server'
import {toggleLockdown} from './lockdown.server'

export const triggerAction = async (action: Action, zone: string) => {
switch (action.action) {
case 'broadcast':
if (!zone || typeof zone !== 'string' || zone.trim() === '') {
return Response.json({error: 'missing zone'}, {status: 400})
}

await broadcast(zone, action.data)
break
case 'lockdown':
await toggleLockdown()
break
default:
break
}
}
7 changes: 7 additions & 0 deletions app/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ export const en = {
'actions.form.sequence.helper': 'Build your broadcast sequence.',
'actions.form.sound.label': 'Sound',
'actions.form.sound.helper': 'Which sound should be used when broadcasting?',
'actions.form.pin.label': 'Control Point Pin',
'actions.form.pin.helper':
'The PIN used to trigger this action from the control point interface.',
'button.cancel': 'Cancel',
'button.add': 'Add',
'button.save': 'Save',
Expand All @@ -66,6 +69,7 @@ export const en = {
'actions.detail.icon': 'Icon',
'actions.detail.type': 'Type',
'actions.detail.sound': 'Sound:',
'actions.detail.pin': 'Control Pin',
'actions.edit.metaTitle': 'Edit {{name}}',
'actions.edit.pageTitle': 'Edit {{name}}',
'actions.detail.sequence.label': 'Sequence',
Expand Down Expand Up @@ -206,6 +210,9 @@ export const en = {
'settings.ttsSpeed.label': 'Text-to-speech speed',
'settings.ttsSpeed.helper':
'Speed factor for text-to-speech generation. Default is 1; lower is faster.',
'settings.controlPointKey.label': 'Control Point Key',
'settings.controlPointKey.helper':
'The key used by the Control Point App to communicate with the controller.',
'settings.password.label': 'Change password',
'settings.password.helper':
'Leave fields empty to keep the current password.',
Expand Down
3 changes: 3 additions & 0 deletions app/routes/actions.$action._index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
type MetaFunction,
redirect
} from '@remix-run/node'
import {useLoaderData, Link, useNavigate} from '@remix-run/react'

Check warning on line 6 in app/routes/actions.$action._index.tsx

View workflow job for this annotation

GitHub Actions / ⬣ ESLint

'Link' is defined but never used. Allowed unused vars must match /^ignored/u

import {getPrisma} from '~/lib/prisma.server'
import {checkSession} from '~/lib/session'
Expand Down Expand Up @@ -56,6 +56,9 @@
{t('actions.detail.type')}:{' '}
{typeLabels[action.action] ?? action.action}
</p>
<p>
{t('actions.detail.pin')}: {action.controlPin}
</p>
</div>
<SequenceViewer
sounds={sounds}
Expand Down
16 changes: 15 additions & 1 deletion app/routes/actions.$action.edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,19 @@ export const action = async ({params, request}: ActionFunctionArgs) => {
const icon = formData.get('icon') as string | undefined
const action = formData.get('action') as string | undefined
const data = formData.get('data') as string | undefined
const controlPin = (formData.get('controlPin') as string | undefined)
? (formData.get('controlPin') as string | undefined)
: ''

invariant(name)
invariant(icon)
invariant(action)
invariant(data)
invariant(controlPin)

await prisma.action.update({
where: {id: params.action},
data: {name, icon, action, data}
data: {name, icon, action, data, controlPin}
})

return redirect(`/actions/${params.action}`)
Expand Down Expand Up @@ -105,6 +109,16 @@ const AddAction = () => {
defaultValue={action.icon}
/>
</FormElement>
<FormElement
label={t('actions.form.pin.label')}
helperText={t('actions.form.pin.helper')}
>
<input
name="controlPin"
className={INPUT_CLASSES}
defaultValue={action.controlPin}
/>
</FormElement>
<FormElement
label={t('actions.form.type.label')}
helperText={t('actions.form.type.helper')}
Expand Down
35 changes: 35 additions & 0 deletions app/routes/control-point.config.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import {type LoaderFunctionArgs} from '@remix-run/node'

import {getSetting} from '~/lib/settings.server'
import {getPrisma} from '~/lib/prisma.server'

export const loader = async ({request}: LoaderFunctionArgs) => {
const controlPointKey = await getSetting('controlPointKey')

if (controlPointKey === '') {
return Response.json({
result: 'error',
error: 'Control Point Key has not been set in the settings.'
})
}

const authHeader = request.headers.get('Auth')

if (!authHeader) {
return Response.json({result: 'error', error: 'No key provided.'})
}

if (authHeader !== controlPointKey) {
return Response.json({result: 'error', error: 'Invalid Ket provided.'})
}

const prisma = getPrisma()

const zones = await prisma.zone.findMany({orderBy: {name: 'asc'}})

return Response.json({
zones: zones.map(({id, name}) => {
return {id, name}
})
})
}
44 changes: 44 additions & 0 deletions app/routes/control-point.trigger.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import {type ActionFunctionArgs} from '@remix-run/node'

import {getSetting} from '~/lib/settings.server'
import {getPrisma} from '~/lib/prisma.server'

import {triggerAction} from '~/lib/trigger-action.server'

export const action = async ({request}: ActionFunctionArgs) => {
const controlPointKey = await getSetting('controlPointKey')

if (controlPointKey === '') {
return Response.json({
result: 'error',
error: 'Control Point Key has not been set in the settings.'
})
}

const authHeader = request.headers.get('Auth')

if (!authHeader) {
return Response.json({result: 'error', error: 'No key provided.'})
}

if (authHeader !== controlPointKey) {
return Response.json({result: 'error', error: 'Invalid Ket provided.'})
}

const data = (await request.json()) as {pin: string; zone: string}

const prisma = getPrisma()

const action = await prisma.action.findFirst({where: {controlPin: data.pin}})

if (!action) {
return Response.json({result: 'error', error: 'No action for this pin'})
}

await triggerAction(action, data.zone)

return Response.json({
result: 'success',
message: `${action.icon} ${action.name}`
})
}
22 changes: 5 additions & 17 deletions app/routes/hook.$hook.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
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'
import {triggerAction} from '~/lib/trigger-action.server'

export const action = async ({request, params}: ActionFunctionArgs) => {
const prisma = getPrisma()
Expand All @@ -22,22 +21,11 @@ export const action = async ({request, params}: ActionFunctionArgs) => {
return {error: 'Bad Key Provided'}
}

switch (webhook.action.action) {
case 'broadcast':
if (!zone) {
return {error: 'No zone provided.'}
}

if (webhook.action.audioId) {
await broadcast(zone, webhook.action.data)
}
break
case 'lockdown':
await toggleLockdown()
break
default:
break
if (!zone) {
return {error: 'No zone provided.'}
}

await triggerAction(webhook.action, zone)

return {status: 'ok'}
}
29 changes: 26 additions & 3 deletions app/routes/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,16 @@ export const loader = async ({request}: LoaderFunctionArgs) => {
return redirect('/login')
}

const {ttsSpeed, enrollUrl} = await getSettings(['ttsSpeed', 'enrollUrl'])
const {ttsSpeed, enrollUrl, controlPointKey} = await getSettings([
'ttsSpeed',
'enrollUrl',
'controlPointKey'
])

return {
ttsSpeed,
enrollUrl
enrollUrl,
controlPointKey
}
}

Expand All @@ -42,12 +47,19 @@ export const action = async ({request}: ActionFunctionArgs) => {
const ttsSpeed = formData.get('ttsSpeed') as string | undefined
const password = formData.get('password') as string | undefined
const checkPassword = formData.get('confirmPassword') as string | undefined
const controlPointKey = (formData.get('controlPointKey') as
| string
| undefined)
? (formData.get('controlPointKey') as string | undefined)
: ''

invariant(enrollUrl)
invariant(ttsSpeed)
invariant(controlPointKey)

await setSetting('enrollUrl', enrollUrl)
await setSetting('ttsSpeed', ttsSpeed)
await setSetting('controlPointKey', controlPointKey)

if (password && checkPassword && password === checkPassword) {
await setSetting('password', password)
Expand All @@ -57,7 +69,7 @@ export const action = async ({request}: ActionFunctionArgs) => {
}

const Settings = () => {
const {ttsSpeed, enrollUrl} = useLoaderData<typeof loader>()
const {ttsSpeed, enrollUrl, controlPointKey} = useLoaderData<typeof loader>()
const {t} = useTranslation()

return (
Expand Down Expand Up @@ -85,6 +97,17 @@ const Settings = () => {
defaultValue={ttsSpeed}
/>
</FormElement>
<FormElement
label={t('settings.controlPointKey.label')}
helperText={t('settings.controlPointKey.helper')}
>
<input
type="text"
name="controlPointKey"
className={INPUT_CLASSES}
defaultValue={controlPointKey}
/>
</FormElement>
<FormElement
label={t('settings.password.label')}
helperText={t('settings.password.helper')}
Expand Down
23 changes: 5 additions & 18 deletions app/routes/sounder-api.trigger-action.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
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'
import {triggerAction} from '~/lib/trigger-action.server'

export const action = async ({request}: ActionFunctionArgs) => {
const {key, action, zone} = (await request.json()) as {
Expand Down Expand Up @@ -37,23 +36,11 @@ export const action = async ({request}: ActionFunctionArgs) => {
return Response.json({error: 'action not found'}, {status: 404})
}

switch (dbAction.action) {
case 'broadcast':
if (!zone || typeof zone !== 'string' || zone.trim() === '') {
return Response.json({error: 'missing zone'}, {status: 400})
}

if (dbAction.audioId) {
const zoneId = zone.trim()
await broadcast(zoneId, JSON.stringify([dbAction.audioId]))
}
break
case 'lockdown':
await toggleLockdown()
break
default:
break
if (!zone || typeof zone !== 'string' || zone.trim() === '') {
return Response.json({error: 'missing zone'}, {status: 400})
}

await triggerAction(dbAction, zone)

return Response.json({ping: 'pong'})
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
-- RedefineTables
PRAGMA defer_foreign_keys=ON;
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_Action" (
"id" TEXT NOT NULL PRIMARY KEY,
"name" TEXT NOT NULL,
"icon" TEXT NOT NULL,
"action" TEXT NOT NULL,
"data" TEXT NOT NULL DEFAULT '',
"controlPin" TEXT NOT NULL DEFAULT '',
"audioId" TEXT,
CONSTRAINT "Action_audioId_fkey" FOREIGN KEY ("audioId") REFERENCES "Audio" ("id") ON DELETE SET NULL ON UPDATE CASCADE
);
INSERT INTO "new_Action" ("action", "audioId", "data", "icon", "id", "name") SELECT "action", "audioId", "data", "icon", "id", "name" FROM "Action";
DROP TABLE "Action";
ALTER TABLE "new_Action" RENAME TO "Action";
PRAGMA foreign_keys=ON;
PRAGMA defer_foreign_keys=OFF;
11 changes: 6 additions & 5 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -102,11 +102,12 @@ model Audio {
}

model Action {
id String @id @default(uuid())
name String
icon String
action String
data String @default("")
id String @id @default(uuid())
name String
icon String
action String
data String @default("")
controlPin String @default("")

// BREAKING CHANGE When we make the jump to 2.x this can be removed. Not Worth a full V2 bump just to take it out.
audio Audio? @relation(fields: [audioId], references: [id])
Expand Down
Loading