From 25438f79013da8f4f2ffd55dc7790c69f2d1da1e Mon Sep 17 00:00:00 2001 From: Martin Dimitrov Date: Tue, 12 Nov 2024 21:46:45 -0800 Subject: [PATCH] launch edit api --- bun.lockb | Bin 405768 -> 406520 bytes .../doorman-api/functions/api/door/edit.js | 103 +++++++++++++ .../doorman-api/functions/api/door/info.js | 13 +- .../functions/common/ddb.private.js | 86 ++++++++++- .../src/components/InputTokenGroup.tsx | 27 ++++ packages/doorman-ui/src/index.tsx | 12 +- packages/doorman-ui/src/pages/DoorPage.tsx | 24 ++- packages/doorman-ui/src/pages/EditPage.tsx | 145 ++++++++++++++++++ .../doorman-ui/src/routers/QueryRouter.tsx | 24 +++ packages/doorman-ui/src/types/Action.ts | 5 - packages/doorman-ui/src/types/DoorResponse.ts | 4 + 11 files changed, 418 insertions(+), 25 deletions(-) create mode 100644 packages/doorman-api/functions/api/door/edit.js create mode 100644 packages/doorman-ui/src/components/InputTokenGroup.tsx create mode 100644 packages/doorman-ui/src/pages/EditPage.tsx create mode 100644 packages/doorman-ui/src/routers/QueryRouter.tsx delete mode 100644 packages/doorman-ui/src/types/Action.ts diff --git a/bun.lockb b/bun.lockb index 2d74fb82ef6f77c8b9c77e94dd3de17dfa30d655..267862f01ceaf435847c37a2977efa07e785f9b3 100755 GIT binary patch delta 7036 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 G^!RV^6f-se delta 7925 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 diff --git a/packages/doorman-api/functions/api/door/edit.js b/packages/doorman-api/functions/api/door/edit.js new file mode 100644 index 0000000..2ed179b --- /dev/null +++ b/packages/doorman-api/functions/api/door/edit.js @@ -0,0 +1,103 @@ + +/** + * Edit API for doors + */ + +exports.handler = async function(context, event, callback) { + const response = new Twilio.Response(); + + let door = event.door; + let approvalId = event.approvalId; + let newConfig = event.newConfig; + + const ddbPath = Runtime.getFunctions()['common/ddb'].path; + const discordPath = Runtime.getFunctions()['common/discord'].path; + const ddb = require(ddbPath); + const discord = require(discordPath); + const client = ddb.createDDBClient(context); + + // approve path + if (door && approvalId) { + const newConfig = await client.send(ddb.getDoorConfigUpdateCommand(door)); + + if (!newConfig || newConfig.Item.approvalId.S !== approvalId) { + response.setStatusCode(400); + return callback(null, response); + } + + await client.send(ddb.replaceDoorConfigWithUpdateItem(newConfig)); + + const hostUrl = event.request.headers.host; + const scheme = hostUrl.startsWith("localhost") ? "http://": "https://"; + + // send update to discord users + const updateMessage = `Configuration change \`${approvalId}\` was approved @ Door "${door}"`; + + const discordPromises = newConfig.Item.discordUsers.SS.map((user) => { + return discord.sendMessageToUser( + context, + user, + updateMessage, + ).catch(e => console.error(e)) + }); + + await Promise.all(discordPromises); + + response.setStatusCode(200); + return callback(null, response); + } + + if (!door || !newConfig) { + response.setStatusCode(400); + return callback(null, response); + } + + newConfig = JSON.parse(event.newConfig); + + const config = await client.send(ddb.getDoorConfigCommand(door)); + + if (!config.Item) { + response.setStatusCode(404); + return callback(null, response); + } + + // set to old PIN if it is missing + if (newConfig.pin === "") { + newConfig.pin = config.Item.pin.S; + } + + const input = ddb.putDoorUpdateConfigCommand(door, newConfig); + + const update = await client.send(input); + + newConfig.discordUser = undefined; + newConfig.fallbackNumber = undefined; + newConfig.status = undefined; + + const hostUrl = event.request.headers.host; + const scheme = hostUrl.startsWith("localhost") ? "http://": "https://"; + + const approvalUrl = `${scheme + hostUrl}/api/door/edit?door=${door}&approvalId=${input.input.Item.approvalId.S}`; + console.log(approvalUrl); + + // send update to discord users + const approvalMessage = `Configuration change requested @ Door "${door}" [click to approve it](${approvalUrl})\`\`\`${JSON.stringify(newConfig, null, 4)}\`\`\``; + + const discordPromises = config.Item.discordUsers.SS.map((user) => { + return discord.sendMessageToUser( + context, + user, + approvalMessage, + ).catch(e => console.error(e)) + }); + + await Promise.all(discordPromises); + + response + .setStatusCode(200) + .appendHeader('Content-Type', 'application/json') + .setBody({ msg: update }); + + await client.destroy(); + return callback(null, response); +}; diff --git a/packages/doorman-api/functions/api/door/info.js b/packages/doorman-api/functions/api/door/info.js index 694d92c..3580dd1 100644 --- a/packages/doorman-api/functions/api/door/info.js +++ b/packages/doorman-api/functions/api/door/info.js @@ -41,6 +41,7 @@ exports.handler = async function(context, event, callback) { .setBody({ err: "This buzzer is not registered properly" }); } else { if (buzzer) { + // respond to twilio CLIENT response .setStatusCode(200) .appendHeader('Content-Type', 'application/json') @@ -56,10 +57,20 @@ exports.handler = async function(context, event, callback) { .then(async (lock) => { const status = ddb.isLockOpen(lock) ? "OPEN": "CLOSED"; + // respond to UI response .setStatusCode(200) .appendHeader('Content-Type', 'application/json') - .setBody({ id: door, timeout: config.Item.timeout.N, status, buzzerCode: config.Item.buzzerCode.S }); + .setBody({ + id: door, + timeout: config.Item.timeout.N, + buzzer: config.Item.buzzer.S, + status, + buzzerCode: config.Item.buzzerCode.S, + fallbackNumbers: config.Item.fallbackNumbers.SS, + pressKey: config.Item.pressKey.S, + discordUsers: config.Item?.discordUsers?.SS || [], + }); }).catch((e) => { console.log(e); diff --git a/packages/doorman-api/functions/common/ddb.private.js b/packages/doorman-api/functions/common/ddb.private.js index 62bf239..632fa5d 100644 --- a/packages/doorman-api/functions/common/ddb.private.js +++ b/packages/doorman-api/functions/common/ddb.private.js @@ -1,4 +1,5 @@ -const { DynamoDBClient, GetItemCommand, DeleteItemCommand, PutItemCommand } = require("@aws-sdk/client-dynamodb"); +const { randomUUID } = require("crypto"); +const { DynamoDBClient, GetItemCommand, DeleteItemCommand, PutItemCommand, UpdateItemCommand } = require("@aws-sdk/client-dynamodb"); exports.createDDBClient = (context) => { return new DynamoDBClient({ @@ -91,3 +92,86 @@ exports.setLockStatusCommand = (door, timeoutSeconds, fingerprintObj) => { } }); }; + +exports.putDoorUpdateConfigCommand = (door, config) => { + return new PutItemCommand({ + TableName: "doorman", + Item: { + "PK": { + S: "door-" + door, + }, + "SK": { + S: "config-update", + }, + "buzzer": { + S: config.buzzer, + }, + "buzzerCode": { + S: config.buzzerCode, + }, + "discordUsers": { + SS: config.discordUsers, + }, + "fallbackNumbers": { + SS: config.fallbackNumbers, + }, + "pin": { + S: config.pin, + }, + "pressKey": { + S: config.pressKey, + }, + "timeout": { + N: `${config.timeout}`, + }, + "greeting": { + S: config.greeting, + }, + "approvalId": { + S: randomUUID().toString(), + } + } + }); +}; + +exports.getDoorConfigCommand = (door) => { + return new GetItemCommand({ + TableName: "doorman", + Key: { + "PK": { + S: `door-${door}`, + }, + "SK": { + S: "config-update", + }, + }, + }); +}; + +exports.getDoorConfigUpdateCommand = (door) => { + return new GetItemCommand({ + TableName: "doorman", + Key: { + "PK": { + S: `door-${door}`, + }, + "SK": { + S: "config-update", + }, + }, + }); +}; + +exports.replaceDoorConfigWithUpdateItem = (newConfigItem) => { + const newItem = { + ...newConfigItem.Item, + SK: { S: "config" }, + }; + + delete newItem.approvalId; + + return new PutItemCommand({ + TableName: "doorman", + Item: newItem, + }); +}; diff --git a/packages/doorman-ui/src/components/InputTokenGroup.tsx b/packages/doorman-ui/src/components/InputTokenGroup.tsx new file mode 100644 index 0000000..5440005 --- /dev/null +++ b/packages/doorman-ui/src/components/InputTokenGroup.tsx @@ -0,0 +1,27 @@ +import { Button, ColumnLayout, Input, SpaceBetween, TokenGroup, TokenGroupProps } from "@cloudscape-design/components"; + +export type InputTokenGroupProps = { + type?: React.HTMLInputTypeAttribute; + registerInput: any, + tokenGroupProps: TokenGroupProps, + onAdd: () => void, + onDismiss: (i: number) => void, +}; + +export const InputTokenGroup = (props: InputTokenGroupProps) => { + return ( + + + + + + props.onDismiss(e.detail.itemIndex)} + {...props.tokenGroupProps} + /> + + ); +}; diff --git a/packages/doorman-ui/src/index.tsx b/packages/doorman-ui/src/index.tsx index 69df590..4695693 100644 --- a/packages/doorman-ui/src/index.tsx +++ b/packages/doorman-ui/src/index.tsx @@ -4,7 +4,8 @@ import App from './App'; import { RouterProvider, createBrowserRouter } from 'react-router-dom'; import { DoorPage, loader as doorpageloader } from './pages/DoorPage'; import { ErrorPage } from './pages/ErrorPage'; - +import { QueryRouter } from './routers/QueryRouter'; +import { EditPage } from './pages/EditPage'; const root = ReactDOM.createRoot( document.getElementById('root') as HTMLElement @@ -19,7 +20,13 @@ const router = createBrowserRouter([ { path: "", loader: doorpageloader, - element: + element: + , + door: + }} + /> } ] } @@ -30,4 +37,3 @@ root.render( ); - diff --git a/packages/doorman-ui/src/pages/DoorPage.tsx b/packages/doorman-ui/src/pages/DoorPage.tsx index 18af4b1..13d69a7 100644 --- a/packages/doorman-ui/src/pages/DoorPage.tsx +++ b/packages/doorman-ui/src/pages/DoorPage.tsx @@ -1,6 +1,6 @@ -import React, { ReactElement, ReactNode, useEffect, useState } from "react"; +import { ReactNode, useEffect, useState } from "react"; import { useLoaderData, useNavigate, useSearchParams } from "react-router-dom"; -import { AppLayout, BreadcrumbGroup, Container, Flashbar, FlashbarProps, Header, SideNavigation, SpaceBetween, TextContent, Wizard } from "@cloudscape-design/components"; +import { AppLayout, BreadcrumbGroup, Container, Flashbar, FlashbarProps, Header, SpaceBetween, TextContent, Wizard } from "@cloudscape-design/components"; import { AuthComponent, IAuthMode } from "../components/AuthComponent"; import OtpInput from 'react-otp-input'; import { CountdownBar } from "../components/CountdownBar"; @@ -9,27 +9,21 @@ import { DoorResponse } from "../types/DoorResponse"; export async function loader({ params, request }: any) { const door = new URL(request.url).searchParams.get('door'); + if (!door) { + return {}; + } + const response = await fetch(`/api/door/info?door=${door}`).then(res => res.json()); console.log(response); - if (response.msg) { + if (response.err) { throw new Error("Not a valid door"); } return response as DoorResponse; } -interface SelectOption { - label?: string; - value?: string; -} - -const selectOptions: SelectOption[] = [ - { label: "backup", value: "backup" }, - { label: "key", value: "key" }, -]; - export function DoorPage() { const doorResponse = useLoaderData() as DoorResponse; const navigate = useNavigate(); @@ -106,7 +100,7 @@ export function DoorPage() { @@ -272,4 +266,4 @@ export function DoorPage() { } /> ); -} +}; diff --git a/packages/doorman-ui/src/pages/EditPage.tsx b/packages/doorman-ui/src/pages/EditPage.tsx new file mode 100644 index 0000000..00b7592 --- /dev/null +++ b/packages/doorman-ui/src/pages/EditPage.tsx @@ -0,0 +1,145 @@ +import { AppLayout, BreadcrumbGroup, Button, Container, Form, FormField, Header, Link, SpaceBetween } from "@cloudscape-design/components"; +import { useLoaderData, useSearchParams } from "react-router-dom"; +import { DoorResponse } from "../types/DoorResponse"; +import { useForm } from "react-hook-form"; +import { InputTokenGroup } from "../components/InputTokenGroup"; + +export type DoorEditForm = DoorResponse & { pin: string, fallbackNumber: string, discordUser: string, greeting: string }; + +export const EditPage = () => { + const doorResponse = useLoaderData() as DoorResponse; + const door = doorResponse.id; + const { register, setValue, setError, getValues, watch, formState, clearErrors } = useForm({ + defaultValues: doorResponse, + mode: "all", + }); + + const fallbackNumbers = watch("fallbackNumbers"); + const discordUsers = watch("discordUsers"); + + const fallbackNumbersError = formState.errors.fallbackNumbers?.message; + const discordUsersError = formState.errors.discordUsers?.message; + + return ( + + } + content={ +
+ + + + } + header={
Edit Door
} + > + + Door - {door} + + } + > + + + + + + + + + + + + + + + ({ label: n }))}} + onAdd={() => { + clearErrors("fallbackNumbers"); + setValue("fallbackNumbers", [...fallbackNumbers, getValues().fallbackNumber]) + setValue("fallbackNumber", "") + }} + onDismiss={(i) => { + clearErrors("fallbackNumbers"); + + if (fallbackNumbers.length === 1) { + setError("fallbackNumbers", { message: "Can't remove last entry", type: "value" }) + return; + } + fallbackNumbers.splice(i, 1); + setValue("fallbackNumbers", [...fallbackNumbers]); + }} + /> + + + Discord users to receive notifications + + } + > + ({ label: n }))}} + onAdd={() => { + clearErrors("discordUsers"); + setValue("discordUsers", [...discordUsers, getValues().discordUser]) + setValue("discordUser", "") + }} + onDismiss={(i) => { + clearErrors("discordUsers"); + + if (discordUsers.length === 1) { + setError("discordUsers", { message: "Can't remove last entry" }) + return; + } + discordUsers.splice(i, 1); + setValue("discordUsers", [...discordUsers]); + }} + /> + + +