Compare commits
7 Commits
73e98c0508
...
2f45005a8a
| Author | SHA1 | Date | |
|---|---|---|---|
| 2f45005a8a | |||
| aeefd6f5d3 | |||
| ca2cd5286f | |||
| 154ca3aec0 | |||
| 50c4e9ae2c | |||
| ff2b5f9b9e | |||
| 9f1a05acf8 |
@ -5,14 +5,14 @@
|
|||||||
import { ServerlessEventObject, ServerlessFunctionSignature } from "@twilio-labs/serverless-runtime-types/types";
|
import { ServerlessEventObject, ServerlessFunctionSignature } from "@twilio-labs/serverless-runtime-types/types";
|
||||||
import { TwilioContext } from "../../../types/TwilioContext";
|
import { TwilioContext } from "../../../types/TwilioContext";
|
||||||
import { UserAgentHeader } from "../../../utils/blockUserAgent";
|
import { UserAgentHeader } from "../../../utils/blockUserAgent";
|
||||||
import { clearLockStatusCommand, createDDBClient, ddbItemToJSON, getDoorConfigCommand, getLockStatusCommand, isLockOpen, setLockStatusCommand } from "../../../utils/ddb";
|
import { createDynaBridgeClient } from "../../../utils/ddb";
|
||||||
import { DoorConfig } from "../../../types/DoorConfig";
|
|
||||||
import { AuthMethod } from "../../../types/AuthMethod";
|
import { AuthMethod } from "../../../types/AuthMethod";
|
||||||
import { Lock } from "../../../types/Lock";
|
|
||||||
import { DoorStatus } from "../../../types/DoorStatus";
|
import { DoorStatus } from "../../../types/DoorStatus";
|
||||||
import { getMetricFromRegistry, withMetrics } from "../../../common/DoormanHandler";
|
import { getMetricFromRegistry, withMetrics } from "../../../common/DoormanHandler";
|
||||||
import { AuthMetrics, registerMetrics } from "../../../metrics/AuthMetrics";
|
import { AuthMetrics, registerMetrics } from "../../../metrics/AuthMetrics";
|
||||||
import { Counter } from "prom-client";
|
import { Counter } from "prom-client";
|
||||||
|
import { DoorConfig, getDoorConfigID } from "../../../schema/DoorConfig";
|
||||||
|
import { createLockStatusWithTimeout, getLockStatusID, isLockOpen } from "../../../schema/LockStatus";
|
||||||
|
|
||||||
export interface AuthRequest extends ServerlessEventObject<{}, UserAgentHeader> {
|
export interface AuthRequest extends ServerlessEventObject<{}, UserAgentHeader> {
|
||||||
door?: string;
|
door?: string;
|
||||||
@ -34,10 +34,9 @@ export const handler: ServerlessFunctionSignature<TwilioContext, AuthRequest> =
|
|||||||
return callback(null, response);
|
return callback(null, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
const client = createDDBClient(context);
|
const db = createDynaBridgeClient(context);
|
||||||
|
|
||||||
const ddbConfig = await client.send(getDoorConfigCommand(door));
|
const config: DoorConfig | undefined = await db.entities.doorConfig.findById(getDoorConfigID(door));
|
||||||
const config: DoorConfig = ddbItemToJSON<DoorConfig>(ddbConfig);
|
|
||||||
|
|
||||||
if (!config) {
|
if (!config) {
|
||||||
getMetricFromRegistry<Counter>(metricsRegistry, AuthMetrics.DOOR_CONFIG_NOT_FOUND).inc({ door }, 1);
|
getMetricFromRegistry<Counter>(metricsRegistry, AuthMetrics.DOOR_CONFIG_NOT_FOUND).inc({ door }, 1);
|
||||||
@ -76,12 +75,10 @@ export const handler: ServerlessFunctionSignature<TwilioContext, AuthRequest> =
|
|||||||
const timeout = event.timeout ? parseInt(event.timeout) : config.timeout;
|
const timeout = event.timeout ? parseInt(event.timeout) : config.timeout;
|
||||||
|
|
||||||
// check lock status if locked, then unlock. If unlocked then lock
|
// check lock status if locked, then unlock. If unlocked then lock
|
||||||
await client.send(getLockStatusCommand(door))
|
const lock = await db.entities.lockStatus.findById(getLockStatusID(door));
|
||||||
.then(async (lockDdb) => {
|
const isOpen = isLockOpen(lock);
|
||||||
const isOpen = isLockOpen(lockDdb);
|
|
||||||
|
|
||||||
if (isOpen) {
|
if (isOpen && lock) {
|
||||||
const lock: Lock = ddbItemToJSON<Lock>(lockDdb);
|
|
||||||
const fingerprint = JSON.parse(lock.fingerprint);
|
const fingerprint = JSON.parse(lock.fingerprint);
|
||||||
|
|
||||||
response
|
response
|
||||||
@ -92,25 +89,18 @@ export const handler: ServerlessFunctionSignature<TwilioContext, AuthRequest> =
|
|||||||
fingerprint,
|
fingerprint,
|
||||||
});
|
});
|
||||||
|
|
||||||
await client.send(clearLockStatusCommand(lockDdb));
|
await db.entities.lockStatus.deleteById(getLockStatusID(door));
|
||||||
return;
|
} else {
|
||||||
}
|
await db.entities.lockStatus.save(createLockStatusWithTimeout(door, timeout, fingerprint));
|
||||||
|
|
||||||
await client.send(setLockStatusCommand(door, timeout, fingerprint))
|
|
||||||
.then(async (item) => {
|
|
||||||
response
|
response
|
||||||
.setStatusCode(200)
|
.setStatusCode(200)
|
||||||
.appendHeader('Content-Type', 'application/json')
|
.appendHeader('Content-Type', 'application/json')
|
||||||
.setBody({ msg: `Opened the door "${door}" for ${timeout}s` });
|
.setBody({ msg: `Opened the door "${door}" for ${timeout}s` });
|
||||||
}).catch((e) => {
|
}
|
||||||
console.log(e);
|
|
||||||
response
|
// destroy the internal client after
|
||||||
.setStatusCode(500)
|
// @ts-ignore
|
||||||
.appendHeader('Content-Type', 'application/json')
|
db.ddbClient.destroy();
|
||||||
.setBody({ err: e });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
await client.destroy();
|
|
||||||
return callback(null, response);
|
return callback(null, response);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -6,9 +6,9 @@
|
|||||||
import { ServerlessEventObject, ServerlessFunctionSignature } from "@twilio-labs/serverless-runtime-types/types";
|
import { ServerlessEventObject, ServerlessFunctionSignature } from "@twilio-labs/serverless-runtime-types/types";
|
||||||
import { TwilioContext } from "../../../types/TwilioContext";
|
import { TwilioContext } from "../../../types/TwilioContext";
|
||||||
import { shouldBlockRequest, UserAgentHeader } from "../../../utils/blockUserAgent";
|
import { shouldBlockRequest, UserAgentHeader } from "../../../utils/blockUserAgent";
|
||||||
import { createDDBClient, ddbItemToJSON, getDoorConfigCommand, getDoorConfigUpdateCommand, putDoorUpdateConfigCommand, replaceDoorConfigWithUpdateItem } from "../../../utils/ddb";
|
import { createDynaBridgeClient } from "../../../utils/ddb";
|
||||||
import { DoorConfig, EditDoorConfig } from "../../../types/DoorConfig";
|
|
||||||
import { sendMessageToUser } from "../../../utils/discord";
|
import { sendMessageToUser } from "../../../utils/discord";
|
||||||
|
import { createEditDoorConfig, EditDoorConfigReq, editDoorToDoorConfig, getDoorConfigID, getEditDoorConfigID } from "../../../schema/DoorConfig";
|
||||||
|
|
||||||
export interface EditRequest extends ServerlessEventObject<{}, UserAgentHeader> {
|
export interface EditRequest extends ServerlessEventObject<{}, UserAgentHeader> {
|
||||||
door?: string;
|
door?: string;
|
||||||
@ -28,19 +28,18 @@ export const handler: ServerlessFunctionSignature<TwilioContext, EditRequest> =
|
|||||||
let approvalId = event.approvalId;
|
let approvalId = event.approvalId;
|
||||||
let newConfigString = event.newConfig;
|
let newConfigString = event.newConfig;
|
||||||
|
|
||||||
const client = createDDBClient(context);
|
const db = createDynaBridgeClient(context);
|
||||||
|
|
||||||
// approve path
|
// approve path
|
||||||
if (door && approvalId) {
|
if (door && approvalId) {
|
||||||
const newConfigDdb = await client.send(getDoorConfigUpdateCommand(door));
|
const newConfig = await db.entities.editDoorConfig.findById(getEditDoorConfigID(door));
|
||||||
const newConfig = ddbItemToJSON<EditDoorConfig>(newConfigDdb);
|
|
||||||
|
|
||||||
if (!newConfig || newConfig.approvalId !== approvalId) {
|
if (!newConfig || newConfig.approvalId !== approvalId) {
|
||||||
response.setStatusCode(400);
|
response.setStatusCode(400);
|
||||||
return callback(null, response);
|
return callback(null, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
await client.send(replaceDoorConfigWithUpdateItem(newConfigDdb as any));
|
db.entities.doorConfig.save(editDoorToDoorConfig(newConfig));
|
||||||
|
|
||||||
// send update to discord users
|
// send update to discord users
|
||||||
const updateMessage = `Configuration change \`${approvalId}\` was approved @ Door "${door}"`;
|
const updateMessage = `Configuration change \`${approvalId}\` was approved @ Door "${door}"`;
|
||||||
@ -64,10 +63,8 @@ export const handler: ServerlessFunctionSignature<TwilioContext, EditRequest> =
|
|||||||
return callback(null, response);
|
return callback(null, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
const newConfig: EditDoorConfig = JSON.parse(newConfigString);
|
const newConfig: EditDoorConfigReq = JSON.parse(newConfigString);
|
||||||
|
const config = await db.entities.doorConfig.findById(getDoorConfigID(door));
|
||||||
const configDdb = await client.send(getDoorConfigCommand(door));
|
|
||||||
const config = ddbItemToJSON<DoorConfig>(configDdb);
|
|
||||||
|
|
||||||
if (!config) {
|
if (!config) {
|
||||||
response.setStatusCode(404);
|
response.setStatusCode(404);
|
||||||
@ -79,15 +76,14 @@ export const handler: ServerlessFunctionSignature<TwilioContext, EditRequest> =
|
|||||||
newConfig.pin = config.pin;
|
newConfig.pin = config.pin;
|
||||||
}
|
}
|
||||||
|
|
||||||
const input = putDoorUpdateConfigCommand(door, newConfig);
|
const editDoorConfig = createEditDoorConfig(door, newConfig);
|
||||||
|
await db.entities.editDoorConfig.save(editDoorConfig);
|
||||||
const update = await client.send(input);
|
|
||||||
|
|
||||||
// newConfig.discordUsers = undefined;
|
// newConfig.discordUsers = undefined;
|
||||||
// newConfig.fallbackNumbers = undefined;
|
// newConfig.fallbackNumbers = undefined;
|
||||||
// newConfig.status = undefined;
|
// newConfig.status = undefined;
|
||||||
|
|
||||||
const approvalUrl = `https://doorman.chromart.cc/api/door/edit?door=${door}&approvalId=${input?.input?.Item?.approvalId.S as string}`;
|
const approvalUrl = `https://doorman.chromart.cc/api/door/edit?door=${door}&approvalId=${editDoorConfig.approvalId as string}`;
|
||||||
console.log(approvalUrl);
|
console.log(approvalUrl);
|
||||||
|
|
||||||
// send update to discord users
|
// send update to discord users
|
||||||
@ -106,8 +102,11 @@ export const handler: ServerlessFunctionSignature<TwilioContext, EditRequest> =
|
|||||||
response
|
response
|
||||||
.setStatusCode(200)
|
.setStatusCode(200)
|
||||||
.appendHeader('Content-Type', 'application/json')
|
.appendHeader('Content-Type', 'application/json')
|
||||||
.setBody({ msg: update });
|
.setBody({ msg: "Created Configuration change" });
|
||||||
|
|
||||||
|
// destroy the internal client after
|
||||||
|
// @ts-ignore
|
||||||
|
db.ddbClient.destroy();
|
||||||
|
|
||||||
await client.destroy();
|
|
||||||
return callback(null, response);
|
return callback(null, response);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -4,10 +4,11 @@
|
|||||||
|
|
||||||
import { ServerlessEventObject, ServerlessFunctionSignature } from "@twilio-labs/serverless-runtime-types/types";
|
import { ServerlessEventObject, ServerlessFunctionSignature } from "@twilio-labs/serverless-runtime-types/types";
|
||||||
import { TwilioContext } from "../../../types/TwilioContext";
|
import { TwilioContext } from "../../../types/TwilioContext";
|
||||||
import { createDDBClient, createDynaBridgeClient, ddbItemToJSON, getDoorAliasCommand, getDoorConfigCommand, getLockStatusCommand, isLockOpen } from "../../../utils/ddb";
|
import { createDynaBridgeClient } from "../../../utils/ddb";
|
||||||
import { DoorStatus } from "../../../types/DoorStatus";
|
import { DoorStatus } from "../../../types/DoorStatus";
|
||||||
import { DoorConfig } from "../../../types/DoorConfig";
|
import { getDoorConfigID } from "../../../schema/DoorConfig";
|
||||||
import { DOOR_CONFIG_SK, getDoorConfigID } from "../../../schema/DoorConfig";
|
import { getDoorAliasID } from "../../../schema/DoorAlias";
|
||||||
|
import { getLockStatusID, isLockOpen } from "../../../schema/LockStatus";
|
||||||
|
|
||||||
export interface InfoRequest extends ServerlessEventObject {
|
export interface InfoRequest extends ServerlessEventObject {
|
||||||
door?: string;
|
door?: string;
|
||||||
@ -38,20 +39,19 @@ export const handler: ServerlessFunctionSignature<TwilioContext, InfoRequest> =
|
|||||||
return callback(null, response);
|
return callback(null, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
const client = createDDBClient(context);
|
|
||||||
const db = createDynaBridgeClient(context);
|
const db = createDynaBridgeClient(context);
|
||||||
|
|
||||||
if (buzzer) {
|
if (buzzer) {
|
||||||
door = await client.send(getDoorAliasCommand(buzzer))
|
door = await db.entities.doorAlias.findById(getDoorAliasID(buzzer))
|
||||||
.then(async (alias) => {
|
.then(async (alias) => {
|
||||||
if (!alias.Item) {
|
if (!alias) {
|
||||||
response
|
response
|
||||||
.setStatusCode(404)
|
.setStatusCode(404)
|
||||||
.appendHeader('Content-Type', 'application/json')
|
.appendHeader('Content-Type', 'application/json')
|
||||||
.setBody({ err: "This buzzer is not registered" });
|
.setBody({ err: "This buzzer is not registered" });
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
return alias.Item.name.S;
|
return alias.name;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,11 +74,10 @@ export const handler: ServerlessFunctionSignature<TwilioContext, InfoRequest> =
|
|||||||
door,
|
door,
|
||||||
fallbackNumbers: config.fallbackNumbers,
|
fallbackNumbers: config.fallbackNumbers,
|
||||||
pressKey: config.pressKey,
|
pressKey: config.pressKey,
|
||||||
discordUsers: config.discordUsers || [],
|
discordUsers: config.discordUsers,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await client.send(getLockStatusCommand(door))
|
const lock = await db.entities.lockStatus.findById(getLockStatusID(door));
|
||||||
.then(async (lock) => {
|
|
||||||
const status = isLockOpen(lock) ? DoorStatus.OPEN: DoorStatus.CLOSED;
|
const status = isLockOpen(lock) ? DoorStatus.OPEN: DoorStatus.CLOSED;
|
||||||
|
|
||||||
// respond to UI
|
// respond to UI
|
||||||
@ -93,20 +92,16 @@ export const handler: ServerlessFunctionSignature<TwilioContext, InfoRequest> =
|
|||||||
buzzerCode: config.buzzerCode,
|
buzzerCode: config.buzzerCode,
|
||||||
fallbackNumbers: config.fallbackNumbers,
|
fallbackNumbers: config.fallbackNumbers,
|
||||||
pressKey: config.pressKey,
|
pressKey: config.pressKey,
|
||||||
discordUsers: config.discordUsers || [],
|
discordUsers: config.discordUsers,
|
||||||
greeting: config.greeting || "",
|
greeting: config.greeting || "",
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// destroy the internal client after
|
||||||
|
// @ts-ignore
|
||||||
|
db.ddbClient.destroy();
|
||||||
|
|
||||||
}).catch((e) => {
|
|
||||||
console.log(e);
|
|
||||||
response
|
|
||||||
.setStatusCode(500)
|
|
||||||
.appendHeader('Content-Type', 'application/json')
|
|
||||||
.setBody({ err: e });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await client.destroy();
|
|
||||||
return callback(null, response);
|
return callback(null, response);
|
||||||
};
|
};
|
||||||
@ -5,8 +5,9 @@
|
|||||||
import { ServerlessFunctionSignature, ServerlessEventObject } from "@twilio-labs/serverless-runtime-types/types";
|
import { ServerlessFunctionSignature, ServerlessEventObject } from "@twilio-labs/serverless-runtime-types/types";
|
||||||
import { TwilioContext } from "../../../types/TwilioContext";
|
import { TwilioContext } from "../../../types/TwilioContext";
|
||||||
import { shouldBlockRequest } from "../../../utils/blockUserAgent";
|
import { shouldBlockRequest } from "../../../utils/blockUserAgent";
|
||||||
import { clearLockStatusCommand, createDDBClient, getLockStatusCommand, isLockOpen } from "../../../utils/ddb";
|
import { createDynaBridgeClient } from "../../../utils/ddb";
|
||||||
import { DoorStatus } from "../../../types/DoorStatus";
|
import { DoorStatus } from "../../../types/DoorStatus";
|
||||||
|
import { getLockStatusID, isLockOpen } from "../../../schema/LockStatus";
|
||||||
|
|
||||||
export interface StatusRequest extends ServerlessEventObject {
|
export interface StatusRequest extends ServerlessEventObject {
|
||||||
door: string;
|
door: string;
|
||||||
@ -32,14 +33,13 @@ export const handler: ServerlessFunctionSignature<TwilioContext, StatusRequest>
|
|||||||
return callback(null, response);
|
return callback(null, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
const client = createDDBClient(context);
|
const db = createDynaBridgeClient(context);
|
||||||
|
const lock = await db.entities.lockStatus.findById(getLockStatusID(door));
|
||||||
|
|
||||||
await client.send(getLockStatusCommand(door))
|
|
||||||
.then(async (lock) => {
|
|
||||||
const isOpen = isLockOpen(lock);
|
const isOpen = isLockOpen(lock);
|
||||||
|
|
||||||
if (isOpen) {
|
if (isOpen && lock) {
|
||||||
const fingerprint = JSON.parse(lock?.Item?.fingerprint.S as string);
|
const fingerprint = JSON.parse(lock.fingerprint);
|
||||||
|
|
||||||
response
|
response
|
||||||
.setStatusCode(200)
|
.setStatusCode(200)
|
||||||
@ -49,25 +49,18 @@ export const handler: ServerlessFunctionSignature<TwilioContext, StatusRequest>
|
|||||||
fingerprint,
|
fingerprint,
|
||||||
});
|
});
|
||||||
|
|
||||||
await client.send(clearLockStatusCommand(lock));
|
await db.entities.lockStatus.deleteById(getLockStatusID(door));
|
||||||
return;
|
} else {
|
||||||
}
|
|
||||||
|
|
||||||
response
|
response
|
||||||
.setStatusCode(200)
|
.setStatusCode(200)
|
||||||
.appendHeader('Content-Type', 'application/json')
|
.appendHeader('Content-Type', 'application/json')
|
||||||
.setBody({
|
.setBody({
|
||||||
status: DoorStatus.CLOSED,
|
status: DoorStatus.CLOSED,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}).catch((e) => {
|
// destroy the internal client after
|
||||||
console.log(e);
|
// @ts-ignore
|
||||||
response
|
db.ddbClient.destroy();
|
||||||
.setStatusCode(500)
|
|
||||||
.appendHeader('Content-Type', 'application/json')
|
|
||||||
.setBody({ err: e });
|
|
||||||
});
|
|
||||||
|
|
||||||
await client.destroy();
|
|
||||||
return callback(null, response);
|
return callback(null, response);
|
||||||
};
|
};
|
||||||
|
|||||||
22
packages/doorman-api/src/schema/DoorAlias.ts
Normal file
22
packages/doorman-api/src/schema/DoorAlias.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
import { DynaBridgeEntity } from 'dynabridge';
|
||||||
|
|
||||||
|
export const DOOR_ALIAS_SK = "alias";
|
||||||
|
|
||||||
|
export const DoorAliasSchema = z.object({
|
||||||
|
// keys
|
||||||
|
PK: z.string(), // phone number (buzzer number)
|
||||||
|
SK: z.literal(DOOR_ALIAS_SK).default(DOOR_ALIAS_SK),
|
||||||
|
name: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const getDoorAliasID = (buzzer: string): string[] => {
|
||||||
|
return [buzzer, DOOR_ALIAS_SK];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DoorAlias = z.infer<typeof DoorAliasSchema>;
|
||||||
|
|
||||||
|
export const DoorAliasEntity: DynaBridgeEntity<DoorAlias> = {
|
||||||
|
tableName: "doorman",
|
||||||
|
id: ["PK", "SK"],
|
||||||
|
};
|
||||||
@ -1,5 +1,6 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { DynaBridge, DynaBridgeEntity } from 'dynabridge';
|
import { DynaBridgeEntity } from 'dynabridge';
|
||||||
|
import { randomUUID } from "crypto";
|
||||||
|
|
||||||
export const DOOR_CONFIG_SK = "config";
|
export const DOOR_CONFIG_SK = "config";
|
||||||
export const EDIT_DOOR_CONFIG_SK = "config-update";
|
export const EDIT_DOOR_CONFIG_SK = "config-update";
|
||||||
@ -15,7 +16,7 @@ export const DoorConfigSchema = z.object({
|
|||||||
fallbackNumbers: z.array(z.string()),
|
fallbackNumbers: z.array(z.string()),
|
||||||
pin: z.string(),
|
pin: z.string(),
|
||||||
pressKey: z.string(),
|
pressKey: z.string(),
|
||||||
greeting: z.string(),
|
greeting: z.string().optional(),
|
||||||
timeout: z.number(),
|
timeout: z.number(),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -36,6 +37,8 @@ export const getEditDoorConfigID = (doorName: string): string[] => {
|
|||||||
export type DoorConfig = z.infer<typeof DoorConfigSchema>;
|
export type DoorConfig = z.infer<typeof DoorConfigSchema>;
|
||||||
export type EditDoorConfig = z.infer<typeof EditDoorConfigSchema>;
|
export type EditDoorConfig = z.infer<typeof EditDoorConfigSchema>;
|
||||||
|
|
||||||
|
export type EditDoorConfigReq = Omit<EditDoorConfig, "PK" | "SK" | "approvalId">;
|
||||||
|
|
||||||
export const DoorConfigEntity: DynaBridgeEntity<DoorConfig> = {
|
export const DoorConfigEntity: DynaBridgeEntity<DoorConfig> = {
|
||||||
tableName: "doorman",
|
tableName: "doorman",
|
||||||
id: ["PK", "SK"],
|
id: ["PK", "SK"],
|
||||||
@ -45,3 +48,23 @@ export const EditDoorConfigEntity: DynaBridgeEntity<EditDoorConfig> = {
|
|||||||
tableName: "doorman",
|
tableName: "doorman",
|
||||||
id: ["PK", "SK"],
|
id: ["PK", "SK"],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const editDoorToDoorConfig = (updateDoor: EditDoorConfig): DoorConfig => {
|
||||||
|
const newItem: any = {
|
||||||
|
...updateDoor,
|
||||||
|
SK: DOOR_CONFIG_SK,
|
||||||
|
};
|
||||||
|
|
||||||
|
delete newItem.approvalId;
|
||||||
|
|
||||||
|
return newItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createEditDoorConfig = (door: string, newConfig: EditDoorConfigReq): EditDoorConfig => {
|
||||||
|
return {
|
||||||
|
...newConfig,
|
||||||
|
PK: "door-" + door,
|
||||||
|
SK: EDIT_DOOR_CONFIG_SK,
|
||||||
|
approvalId: randomUUID().toString(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
40
packages/doorman-api/src/schema/LockStatus.ts
Normal file
40
packages/doorman-api/src/schema/LockStatus.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
import { DynaBridgeEntity } from 'dynabridge';
|
||||||
|
|
||||||
|
export const LOCK_STATUS_SK = "lock";
|
||||||
|
|
||||||
|
export const LockStatusSchema = z.object({
|
||||||
|
// keys
|
||||||
|
PK: z.string(), // door name
|
||||||
|
SK: z.literal(LOCK_STATUS_SK).default(LOCK_STATUS_SK),
|
||||||
|
|
||||||
|
TTL: z.number(),
|
||||||
|
fingerprint: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const getLockStatusID = (door: string): string[] => {
|
||||||
|
return [door, LOCK_STATUS_SK];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LockStatus = z.infer<typeof LockStatusSchema>;
|
||||||
|
|
||||||
|
export const LockStatusEntity: DynaBridgeEntity<LockStatus> = {
|
||||||
|
tableName: "doorman",
|
||||||
|
id: ["PK", "SK"],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isLockOpen = (lock?: LockStatus) => {
|
||||||
|
// ttl is a UTC ms time for how long it is unlocked
|
||||||
|
const ttl = lock?.TTL || 0;
|
||||||
|
|
||||||
|
return parseInt("" + ttl) > Date.now();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createLockStatusWithTimeout = (door: string, timeoutSeconds: number, fingerprint: any): LockStatus => {
|
||||||
|
return {
|
||||||
|
PK: door,
|
||||||
|
SK: LOCK_STATUS_SK,
|
||||||
|
TTL: Date.now() + timeoutSeconds * 1000,
|
||||||
|
fingerprint: JSON.stringify(fingerprint),
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -1,14 +0,0 @@
|
|||||||
export interface DoorConfig {
|
|
||||||
buzzer: string;
|
|
||||||
buzzerCode: string;
|
|
||||||
discordUsers: string[];
|
|
||||||
fallbackNumbers: string[];
|
|
||||||
pin: string;
|
|
||||||
pressKey: string;
|
|
||||||
timeout: number;
|
|
||||||
greeting: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface EditDoorConfig extends DoorConfig {
|
|
||||||
approvalId: string;
|
|
||||||
}
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
export interface Lock {
|
|
||||||
fingerprint: any;
|
|
||||||
}
|
|
||||||
@ -1,25 +1,16 @@
|
|||||||
import { randomUUID } from "crypto";
|
|
||||||
import { DynamoDBClient, GetItemCommand, DeleteItemCommand, PutItemCommand, UpdateItemCommand, GetItemOutput } from "@aws-sdk/client-dynamodb";
|
|
||||||
import { TwilioContext } from "../types/TwilioContext";
|
import { TwilioContext } from "../types/TwilioContext";
|
||||||
import { DoorConfig } from "../types/DoorConfig";
|
|
||||||
import { DynaBridge } from "dynabridge";
|
import { DynaBridge } from "dynabridge";
|
||||||
import { DoorConfigEntity, EditDoorConfigEntity } from "../schema/DoorConfig";
|
import { DoorConfigEntity, EditDoorConfigEntity } from "../schema/DoorConfig";
|
||||||
|
import { DoorAliasEntity } from "../schema/DoorAlias";
|
||||||
export const createDDBClient = (context: TwilioContext) => {
|
import { LockStatusEntity } from "../schema/LockStatus";
|
||||||
return new DynamoDBClient({
|
|
||||||
region: "us-east-1" ,
|
|
||||||
credentials: {
|
|
||||||
accessKeyId: context.AWS_ACCESS_KEY,
|
|
||||||
secretAccessKey: context.AWS_SECRET_ACCESS_KEY,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const createDynaBridgeClient = (context: TwilioContext) => {
|
export const createDynaBridgeClient = (context: TwilioContext) => {
|
||||||
// register all entities here
|
// register all entities here
|
||||||
return new DynaBridge({
|
return new DynaBridge({
|
||||||
doorConfig: DoorConfigEntity,
|
doorConfig: DoorConfigEntity,
|
||||||
editDoorConfig: EditDoorConfigEntity,
|
editDoorConfig: EditDoorConfigEntity,
|
||||||
|
doorAlias: DoorAliasEntity,
|
||||||
|
lockStatus: LockStatusEntity,
|
||||||
}, {
|
}, {
|
||||||
serialize: (entity) => entity,
|
serialize: (entity) => entity,
|
||||||
deserialize: (entity) => {
|
deserialize: (entity) => {
|
||||||
@ -42,170 +33,3 @@ export const createDynaBridgeClient = (context: TwilioContext) => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getLockStatusCommand = (door: string) => {
|
|
||||||
return new GetItemCommand({
|
|
||||||
TableName: "doorman",
|
|
||||||
Key: {
|
|
||||||
"PK": {
|
|
||||||
S: door,
|
|
||||||
},
|
|
||||||
"SK": {
|
|
||||||
S: "lock",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getDoorAliasCommand = (buzzerNumber: string) => {
|
|
||||||
return new GetItemCommand({
|
|
||||||
TableName: "doorman",
|
|
||||||
Key: {
|
|
||||||
"PK": {
|
|
||||||
S: buzzerNumber,
|
|
||||||
},
|
|
||||||
"SK": {
|
|
||||||
S: "alias",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getDoorConfigCommand = (door: string) => {
|
|
||||||
return new GetItemCommand({
|
|
||||||
TableName: "doorman",
|
|
||||||
Key: {
|
|
||||||
"PK": {
|
|
||||||
S: `door-${door}`,
|
|
||||||
},
|
|
||||||
"SK": {
|
|
||||||
S: "config",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const isLockOpen = (lock: GetItemOutput) => {
|
|
||||||
// ttl is a UTC ms time for how long it is unlocked
|
|
||||||
const ttl = lock.Item?.TTL?.N || 0;
|
|
||||||
|
|
||||||
return parseInt("" + ttl) > Date.now();
|
|
||||||
};
|
|
||||||
|
|
||||||
export const clearLockStatusCommand = (lock: GetItemOutput) => {
|
|
||||||
return new DeleteItemCommand({
|
|
||||||
TableName: "doorman",
|
|
||||||
Key: {
|
|
||||||
"PK": {
|
|
||||||
S: lock?.Item?.PK.S as string,
|
|
||||||
},
|
|
||||||
"SK": {
|
|
||||||
S: "lock",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const setLockStatusCommand = (door: string, timeoutSeconds: number, fingerprintObj: any) => {
|
|
||||||
return new PutItemCommand({
|
|
||||||
TableName: "doorman",
|
|
||||||
Item: {
|
|
||||||
"PK": {
|
|
||||||
S: door,
|
|
||||||
},
|
|
||||||
"SK": {
|
|
||||||
S: "lock",
|
|
||||||
},
|
|
||||||
"TTL": {
|
|
||||||
N: `${Date.now() + timeoutSeconds * 1000}`,
|
|
||||||
},
|
|
||||||
"fingerprint": {
|
|
||||||
S: JSON.stringify(fingerprintObj),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const putDoorUpdateConfigCommand = (door: string, config: DoorConfig) => {
|
|
||||||
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(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export function ddbItemToJSON<T extends Record<string, any>>(ddbItem: GetItemOutput): T {
|
|
||||||
let obj: any = {};
|
|
||||||
|
|
||||||
if (!ddbItem.Item) {
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Item = ddbItem.Item;
|
|
||||||
|
|
||||||
Object.keys(Item).forEach(key => {
|
|
||||||
obj[key] = Object.values(Item[key])[0];
|
|
||||||
});
|
|
||||||
return obj;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getDoorConfigUpdateCommand = (door: string) => {
|
|
||||||
return new GetItemCommand({
|
|
||||||
TableName: "doorman",
|
|
||||||
Key: {
|
|
||||||
"PK": {
|
|
||||||
S: `door-${door}`,
|
|
||||||
},
|
|
||||||
"SK": {
|
|
||||||
S: "config-update",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const replaceDoorConfigWithUpdateItem = (newConfigItem: GetItemOutput & { Item: { approvalId?: { S: string } }}) => {
|
|
||||||
const newItem = {
|
|
||||||
...newConfigItem.Item,
|
|
||||||
SK: { S: "config" },
|
|
||||||
};
|
|
||||||
|
|
||||||
delete newItem.approvalId;
|
|
||||||
|
|
||||||
return new PutItemCommand({
|
|
||||||
TableName: "doorman",
|
|
||||||
Item: newItem,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|||||||
@ -15,7 +15,7 @@
|
|||||||
"@twilio/runtime-handler": "1.3.0",
|
"@twilio/runtime-handler": "1.3.0",
|
||||||
"node-fetch": "^2.7.0",
|
"node-fetch": "^2.7.0",
|
||||||
"prom-client": "^15.1.3",
|
"prom-client": "^15.1.3",
|
||||||
"prometheus-remote-write": "^0.5.0",
|
"prometheus-remote-write": "^0.5.1",
|
||||||
"promise.timeout": "^1.2.0",
|
"promise.timeout": "^1.2.0",
|
||||||
"twilio": "^3.84.1",
|
"twilio": "^3.84.1",
|
||||||
"winston": "^3.17.0",
|
"winston": "^3.17.0",
|
||||||
@ -23,7 +23,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bun": "latest",
|
"@types/bun": "latest",
|
||||||
"concurrently": "^9.1.0",
|
"concurrently": "^9.1.2",
|
||||||
"twilio-run": "^3.5.4"
|
"twilio-run": "^3.5.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
import { serve } from "bun";
|
import { serve } from "bun";
|
||||||
import { Hono } from "hono";
|
import { Hono } from "hono";
|
||||||
import { prettyJSON } from "hono/pretty-json";
|
import { prettyJSON } from "hono/pretty-json";
|
||||||
import { createDDBClient, getDoorConfigCommand, getLockStatusCommand, isLockOpen } from "../../doorman-api/src/utils/ddb";
|
import { createDynaBridgeClient } from "../../doorman-api/src/utils/ddb";
|
||||||
import { DoorStatus } from "../../doorman-api/src/types/DoorStatus";
|
import { DoorStatus } from "../../doorman-api/src/types/DoorStatus";
|
||||||
|
import { getLockStatusID, isLockOpen } from "../../doorman-api/src/schema/LockStatus";
|
||||||
|
import { getDoorConfigID } from "../../doorman-api/src/schema/DoorConfig";
|
||||||
|
|
||||||
const app = new Hono();
|
const app = new Hono();
|
||||||
|
|
||||||
@ -16,27 +18,22 @@ app.get("/api/door/info", async (c) => {
|
|||||||
}, 400);
|
}, 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
const client = createDDBClient(Bun.env as any);
|
const db = createDynaBridgeClient(Bun.env as any);
|
||||||
const config = await client.send(getDoorConfigCommand(door));
|
|
||||||
|
|
||||||
if (!config.Item) {
|
const config = await db.entities.doorConfig.findById(getDoorConfigID(door));
|
||||||
|
|
||||||
|
if (!config) {
|
||||||
return c.json({
|
return c.json({
|
||||||
err: "This buzzer is not registered properly",
|
err: "This buzzer is not registered properly",
|
||||||
}, 404);
|
}, 404);
|
||||||
}
|
}
|
||||||
return await client.send(getLockStatusCommand(door))
|
|
||||||
.then(async (lock) => {
|
const lock = await db.entities.lockStatus.findById(getLockStatusID(door));
|
||||||
const status = isLockOpen(lock) ? DoorStatus.OPEN: "CLOSED";
|
const status = isLockOpen(lock) ? DoorStatus.OPEN: "CLOSED";
|
||||||
return c.json({
|
return c.json({
|
||||||
id: door,
|
id: door,
|
||||||
status,
|
status,
|
||||||
});
|
});
|
||||||
}).catch((e) => {
|
|
||||||
console.log(e);
|
|
||||||
return c.json({
|
|
||||||
err: e,
|
|
||||||
}, 500);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
serve({
|
serve({
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user