update info route

This commit is contained in:
Martin Dimitrov 2025-06-07 16:15:33 -07:00
parent 231ea8149d
commit e17bb267fe
2 changed files with 55 additions and 59 deletions

View File

@ -6,38 +6,40 @@ import { ServerlessEventObject, ServerlessFunctionSignature } from "@twilio-labs
import { TwilioContext } from "../../../types/TwilioContext"; import { TwilioContext } from "../../../types/TwilioContext";
import { createDynaBridgeClient } from "../../../utils/ddb"; import { createDynaBridgeClient } from "../../../utils/ddb";
import { DoorStatus } from "../../../types/DoorStatus"; import { DoorStatus } from "../../../types/DoorStatus";
import { getDoorConfigID } from "../../../schema/DoorConfig"; import { DoorConfigSchema, getDoorConfigID } from "../../../schema/DoorConfig";
import { getDoorAliasID } from "../../../schema/DoorAlias"; import { getDoorAliasID } from "../../../schema/DoorAlias";
import { getLockStatusID, isLockOpen } from "../../../schema/LockStatus"; import { getLockStatusID, isLockOpen } from "../../../schema/LockStatus";
import { withMetrics } from "../../../common/DoormanHandler";
import { z } from "zod";
import { UserAgentHeader } from "../../../utils/blockUserAgent";
import { setResponseJson } from "../../../utils/responseUtils";
export interface InfoRequest extends ServerlessEventObject { export const InfoRequestSchema = z.object({
door?: string; door: z.string().optional(),
buzzer?: string; buzzer: z.string().optional(),
})
.partial()
.refine(data => data.buzzer || data.door, 'Buzzer or door must be provided');
// TODO: change these to be multiple export type InfoRequest = z.infer<typeof InfoRequestSchema>;
discordUser: string; export interface InfoRequestTwilio extends ServerlessEventObject<InfoRequest, UserAgentHeader> { };
msg: string;
json: string;
}
export interface InfoResponseClient { export const InfoResponseClientSchema = DoorConfigSchema
buzzer: string; .omit({ PK: true, SK: true, pin: true })
door: string; .extend({ door: z.string() });
pressKey: string;
fallbackNumbers: string[];
discordUsers: string[];
}
export const handler: ServerlessFunctionSignature<TwilioContext, InfoRequest> = async function(context, event, callback) { export type InfoResponseClient = z.infer<typeof InfoResponseClientSchema>;
export const InfoResponseUISchema = InfoResponseClientSchema.extend({ id: z.string(), status: z.nativeEnum(DoorStatus) });
export type InfoResponseUI = z.infer<typeof InfoResponseUISchema>;
export const handler: ServerlessFunctionSignature<TwilioContext, InfoRequestTwilio> = withMetrics("info", async (context, event, callback, metricsRegistry) => {
const response = new Twilio.Response(); const response = new Twilio.Response();
const req = InfoRequestSchema.parse(event);
let door = event.door; let door = req.door;
const buzzer = event.buzzer?.slice(-10); const buzzer = req.buzzer?.slice(-10);
if (!door && !buzzer) {
response.setStatusCode(400);
return callback(null, response);
}
const db = createDynaBridgeClient(context); const db = createDynaBridgeClient(context);
@ -45,10 +47,9 @@ export const handler: ServerlessFunctionSignature<TwilioContext, InfoRequest> =
door = await db.entities.doorAlias.findById(getDoorAliasID(buzzer)) door = await db.entities.doorAlias.findById(getDoorAliasID(buzzer))
.then(async (alias) => { .then(async (alias) => {
if (!alias) { if (!alias) {
response setResponseJson(response, 404, {
.setStatusCode(404) err: "This buzzer is not registered",
.appendHeader('Content-Type', 'application/json') });
.setBody({ err: "This buzzer is not registered" });
return undefined; return undefined;
} }
return alias.name; return alias.name;
@ -59,42 +60,32 @@ export const handler: ServerlessFunctionSignature<TwilioContext, InfoRequest> =
const config = await db.entities.doorConfig.findById(getDoorConfigID(door)); const config = await db.entities.doorConfig.findById(getDoorConfigID(door));
if (!config) { if (!config) {
response setResponseJson(response, 404, {
.setStatusCode(404) err: "This buzzer is not registered",
.appendHeader('Content-Type', 'application/json') });
.setBody({ err: "This buzzer is not registered properly" });
} else { } else {
if (buzzer) { if (buzzer) {
// respond to twilio CLIENT // respond to twilio CLIENT
response const body: InfoResponseClient = InfoResponseClientSchema.parse({
.setStatusCode(200) ...config,
.appendHeader('Content-Type', 'application/json') buzzer,
.setBody({ door,
buzzer, });
door,
fallbackNumbers: config.fallbackNumbers, setResponseJson(response, 200, body);
pressKey: config.pressKey,
discordUsers: config.discordUsers,
});
} else { } else {
const lock = await db.entities.lockStatus.findById(getLockStatusID(door)); const lock = await db.entities.lockStatus.findById(getLockStatusID(door));
const status = isLockOpen(lock) ? DoorStatus.OPEN: DoorStatus.CLOSED; const status = isLockOpen(lock) ? DoorStatus.OPEN: DoorStatus.CLOSED;
const body: InfoResponseUI = InfoResponseUISchema.parse({
...config,
id: door,
door,
status,
})
// respond to UI // respond to UI
response setResponseJson(response, 200, body);
.setStatusCode(200)
.appendHeader('Content-Type', 'application/json')
.setBody({
id: door,
timeout: config.timeout,
buzzer: config.buzzer,
status,
buzzerCode: config.buzzerCode,
fallbackNumbers: config.fallbackNumbers,
pressKey: config.pressKey,
discordUsers: config.discordUsers,
greeting: config.greeting || "",
});
} }
} }
} }
@ -104,4 +95,4 @@ export const handler: ServerlessFunctionSignature<TwilioContext, InfoRequest> =
db.ddbClient.destroy(); db.ddbClient.destroy();
return callback(null, response); return callback(null, response);
}; });

View File

@ -5,6 +5,7 @@ import { AuthComponent, IAuthMode } from "../components/AuthComponent";
import OtpInput from 'react-otp-input'; import OtpInput from 'react-otp-input';
import { CountdownBar } from "../components/CountdownBar"; import { CountdownBar } from "../components/CountdownBar";
import { DoorResponse } from "../types/DoorResponse"; import { DoorResponse } from "../types/DoorResponse";
import { fetchUrlEncoded } from "../helpers/FetchHelper";
export async function loader({ params, request }: any) { export async function loader({ params, request }: any) {
const door = new URL(request.url).searchParams.get('door'); const door = new URL(request.url).searchParams.get('door');
@ -13,7 +14,9 @@ export async function loader({ params, request }: any) {
return {}; return {};
} }
const response = await fetch(`/api/door/info?door=${door}`).then(res => res.json()); const response = await fetchUrlEncoded('/api/door/info', {
door,
}).then(res => res.json());
console.log(response); console.log(response);
@ -70,7 +73,9 @@ export function DoorPage() {
} }
const timer = setInterval(async () => { const timer = setInterval(async () => {
const response = await fetch(`/api/door/info?door=${door}`).then(res => res.json()); const response = await fetchUrlEncoded('/api/door/info', {
door,
}).then(res => res.json());
// polling assumes that the door was opened and whatever closed it was the buzzer system... // polling assumes that the door was opened and whatever closed it was the buzzer system...
// ie. state transition from OPEN to CLOSED before timeout means that twilio opened the door // ie. state transition from OPEN to CLOSED before timeout means that twilio opened the door