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 { TwilioContext } from "../../../types/TwilioContext";
|
||||
import { UserAgentHeader } from "../../../utils/blockUserAgent";
|
||||
import { clearLockStatusCommand, createDDBClient, ddbItemToJSON, getDoorConfigCommand, getLockStatusCommand, isLockOpen, setLockStatusCommand } from "../../../utils/ddb";
|
||||
import { DoorConfig } from "../../../types/DoorConfig";
|
||||
import { createDynaBridgeClient } from "../../../utils/ddb";
|
||||
import { AuthMethod } from "../../../types/AuthMethod";
|
||||
import { Lock } from "../../../types/Lock";
|
||||
import { DoorStatus } from "../../../types/DoorStatus";
|
||||
import { getMetricFromRegistry, withMetrics } from "../../../common/DoormanHandler";
|
||||
import { AuthMetrics, registerMetrics } from "../../../metrics/AuthMetrics";
|
||||
import { Counter } from "prom-client";
|
||||
import { DoorConfig, getDoorConfigID } from "../../../schema/DoorConfig";
|
||||
import { createLockStatusWithTimeout, getLockStatusID, isLockOpen } from "../../../schema/LockStatus";
|
||||
|
||||
export interface AuthRequest extends ServerlessEventObject<{}, UserAgentHeader> {
|
||||
door?: string;
|
||||
@ -34,10 +34,9 @@ export const handler: ServerlessFunctionSignature<TwilioContext, AuthRequest> =
|
||||
return callback(null, response);
|
||||
}
|
||||
|
||||
const client = createDDBClient(context);
|
||||
const db = createDynaBridgeClient(context);
|
||||
|
||||
const ddbConfig = await client.send(getDoorConfigCommand(door));
|
||||
const config: DoorConfig = ddbItemToJSON<DoorConfig>(ddbConfig);
|
||||
const config: DoorConfig | undefined = await db.entities.doorConfig.findById(getDoorConfigID(door));
|
||||
|
||||
if (!config) {
|
||||
getMetricFromRegistry<Counter>(metricsRegistry, AuthMetrics.DOOR_CONFIG_NOT_FOUND).inc({ door }, 1);
|
||||
@ -76,41 +75,32 @@ export const handler: ServerlessFunctionSignature<TwilioContext, AuthRequest> =
|
||||
const timeout = event.timeout ? parseInt(event.timeout) : config.timeout;
|
||||
|
||||
// check lock status if locked, then unlock. If unlocked then lock
|
||||
await client.send(getLockStatusCommand(door))
|
||||
.then(async (lockDdb) => {
|
||||
const isOpen = isLockOpen(lockDdb);
|
||||
const lock = await db.entities.lockStatus.findById(getLockStatusID(door));
|
||||
const isOpen = isLockOpen(lock);
|
||||
|
||||
if (isOpen) {
|
||||
const lock: Lock = ddbItemToJSON<Lock>(lockDdb);
|
||||
const fingerprint = JSON.parse(lock.fingerprint);
|
||||
if (isOpen && lock) {
|
||||
const fingerprint = JSON.parse(lock.fingerprint);
|
||||
|
||||
response
|
||||
.setStatusCode(200)
|
||||
.appendHeader('Content-Type', 'application/json')
|
||||
.setBody({
|
||||
status: DoorStatus.CLOSED,
|
||||
fingerprint,
|
||||
});
|
||||
response
|
||||
.setStatusCode(200)
|
||||
.appendHeader('Content-Type', 'application/json')
|
||||
.setBody({
|
||||
status: DoorStatus.CLOSED,
|
||||
fingerprint,
|
||||
});
|
||||
|
||||
await client.send(clearLockStatusCommand(lockDdb));
|
||||
return;
|
||||
}
|
||||
await db.entities.lockStatus.deleteById(getLockStatusID(door));
|
||||
} else {
|
||||
await db.entities.lockStatus.save(createLockStatusWithTimeout(door, timeout, fingerprint));
|
||||
response
|
||||
.setStatusCode(200)
|
||||
.appendHeader('Content-Type', 'application/json')
|
||||
.setBody({ msg: `Opened the door "${door}" for ${timeout}s` });
|
||||
}
|
||||
|
||||
await client.send(setLockStatusCommand(door, timeout, fingerprint))
|
||||
.then(async (item) => {
|
||||
response
|
||||
.setStatusCode(200)
|
||||
.appendHeader('Content-Type', 'application/json')
|
||||
.setBody({ msg: `Opened the door "${door}" for ${timeout}s` });
|
||||
}).catch((e) => {
|
||||
console.log(e);
|
||||
response
|
||||
.setStatusCode(500)
|
||||
.appendHeader('Content-Type', 'application/json')
|
||||
.setBody({ err: e });
|
||||
});
|
||||
});
|
||||
// destroy the internal client after
|
||||
// @ts-ignore
|
||||
db.ddbClient.destroy();
|
||||
|
||||
await client.destroy();
|
||||
return callback(null, response);
|
||||
});
|
||||
|
||||
@ -6,9 +6,9 @@
|
||||
import { ServerlessEventObject, ServerlessFunctionSignature } from "@twilio-labs/serverless-runtime-types/types";
|
||||
import { TwilioContext } from "../../../types/TwilioContext";
|
||||
import { shouldBlockRequest, UserAgentHeader } from "../../../utils/blockUserAgent";
|
||||
import { createDDBClient, ddbItemToJSON, getDoorConfigCommand, getDoorConfigUpdateCommand, putDoorUpdateConfigCommand, replaceDoorConfigWithUpdateItem } from "../../../utils/ddb";
|
||||
import { DoorConfig, EditDoorConfig } from "../../../types/DoorConfig";
|
||||
import { createDynaBridgeClient } from "../../../utils/ddb";
|
||||
import { sendMessageToUser } from "../../../utils/discord";
|
||||
import { createEditDoorConfig, EditDoorConfigReq, editDoorToDoorConfig, getDoorConfigID, getEditDoorConfigID } from "../../../schema/DoorConfig";
|
||||
|
||||
export interface EditRequest extends ServerlessEventObject<{}, UserAgentHeader> {
|
||||
door?: string;
|
||||
@ -28,19 +28,18 @@ export const handler: ServerlessFunctionSignature<TwilioContext, EditRequest> =
|
||||
let approvalId = event.approvalId;
|
||||
let newConfigString = event.newConfig;
|
||||
|
||||
const client = createDDBClient(context);
|
||||
const db = createDynaBridgeClient(context);
|
||||
|
||||
// approve path
|
||||
if (door && approvalId) {
|
||||
const newConfigDdb = await client.send(getDoorConfigUpdateCommand(door));
|
||||
const newConfig = ddbItemToJSON<EditDoorConfig>(newConfigDdb);
|
||||
const newConfig = await db.entities.editDoorConfig.findById(getEditDoorConfigID(door));
|
||||
|
||||
if (!newConfig || newConfig.approvalId !== approvalId) {
|
||||
response.setStatusCode(400);
|
||||
return callback(null, response);
|
||||
}
|
||||
|
||||
await client.send(replaceDoorConfigWithUpdateItem(newConfigDdb as any));
|
||||
db.entities.doorConfig.save(editDoorToDoorConfig(newConfig));
|
||||
|
||||
// send update to discord users
|
||||
const updateMessage = `Configuration change \`${approvalId}\` was approved @ Door "${door}"`;
|
||||
@ -64,10 +63,8 @@ export const handler: ServerlessFunctionSignature<TwilioContext, EditRequest> =
|
||||
return callback(null, response);
|
||||
}
|
||||
|
||||
const newConfig: EditDoorConfig = JSON.parse(newConfigString);
|
||||
|
||||
const configDdb = await client.send(getDoorConfigCommand(door));
|
||||
const config = ddbItemToJSON<DoorConfig>(configDdb);
|
||||
const newConfig: EditDoorConfigReq = JSON.parse(newConfigString);
|
||||
const config = await db.entities.doorConfig.findById(getDoorConfigID(door));
|
||||
|
||||
if (!config) {
|
||||
response.setStatusCode(404);
|
||||
@ -79,15 +76,14 @@ export const handler: ServerlessFunctionSignature<TwilioContext, EditRequest> =
|
||||
newConfig.pin = config.pin;
|
||||
}
|
||||
|
||||
const input = putDoorUpdateConfigCommand(door, newConfig);
|
||||
|
||||
const update = await client.send(input);
|
||||
const editDoorConfig = createEditDoorConfig(door, newConfig);
|
||||
await db.entities.editDoorConfig.save(editDoorConfig);
|
||||
|
||||
// newConfig.discordUsers = undefined;
|
||||
// newConfig.fallbackNumbers = 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);
|
||||
|
||||
// send update to discord users
|
||||
@ -106,8 +102,11 @@ export const handler: ServerlessFunctionSignature<TwilioContext, EditRequest> =
|
||||
response
|
||||
.setStatusCode(200)
|
||||
.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);
|
||||
};
|
||||
|
||||
@ -4,10 +4,11 @@
|
||||
|
||||
import { ServerlessEventObject, ServerlessFunctionSignature } from "@twilio-labs/serverless-runtime-types/types";
|
||||
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 { DoorConfig } from "../../../types/DoorConfig";
|
||||
import { DOOR_CONFIG_SK, getDoorConfigID } from "../../../schema/DoorConfig";
|
||||
import { getDoorConfigID } from "../../../schema/DoorConfig";
|
||||
import { getDoorAliasID } from "../../../schema/DoorAlias";
|
||||
import { getLockStatusID, isLockOpen } from "../../../schema/LockStatus";
|
||||
|
||||
export interface InfoRequest extends ServerlessEventObject {
|
||||
door?: string;
|
||||
@ -38,20 +39,19 @@ export const handler: ServerlessFunctionSignature<TwilioContext, InfoRequest> =
|
||||
return callback(null, response);
|
||||
}
|
||||
|
||||
const client = createDDBClient(context);
|
||||
const db = createDynaBridgeClient(context);
|
||||
|
||||
if (buzzer) {
|
||||
door = await client.send(getDoorAliasCommand(buzzer))
|
||||
door = await db.entities.doorAlias.findById(getDoorAliasID(buzzer))
|
||||
.then(async (alias) => {
|
||||
if (!alias.Item) {
|
||||
if (!alias) {
|
||||
response
|
||||
.setStatusCode(404)
|
||||
.appendHeader('Content-Type', 'application/json')
|
||||
.setBody({ err: "This buzzer is not registered" });
|
||||
return undefined;
|
||||
}
|
||||
return alias.Item.name.S;
|
||||
return alias.name;
|
||||
});
|
||||
}
|
||||
|
||||
@ -74,39 +74,34 @@ export const handler: ServerlessFunctionSignature<TwilioContext, InfoRequest> =
|
||||
door,
|
||||
fallbackNumbers: config.fallbackNumbers,
|
||||
pressKey: config.pressKey,
|
||||
discordUsers: config.discordUsers || [],
|
||||
discordUsers: config.discordUsers,
|
||||
});
|
||||
} else {
|
||||
await client.send(getLockStatusCommand(door))
|
||||
.then(async (lock) => {
|
||||
const status = isLockOpen(lock) ? DoorStatus.OPEN: DoorStatus.CLOSED;
|
||||
const lock = await db.entities.lockStatus.findById(getLockStatusID(door));
|
||||
const status = isLockOpen(lock) ? DoorStatus.OPEN: DoorStatus.CLOSED;
|
||||
|
||||
// respond to UI
|
||||
response
|
||||
.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 || "",
|
||||
});
|
||||
|
||||
}).catch((e) => {
|
||||
console.log(e);
|
||||
response
|
||||
.setStatusCode(500)
|
||||
.appendHeader('Content-Type', 'application/json')
|
||||
.setBody({ err: e });
|
||||
// respond to UI
|
||||
response
|
||||
.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 || "",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
await client.destroy();
|
||||
|
||||
// destroy the internal client after
|
||||
// @ts-ignore
|
||||
db.ddbClient.destroy();
|
||||
|
||||
return callback(null, response);
|
||||
};
|
||||
@ -5,8 +5,9 @@
|
||||
import { ServerlessFunctionSignature, ServerlessEventObject } from "@twilio-labs/serverless-runtime-types/types";
|
||||
import { TwilioContext } from "../../../types/TwilioContext";
|
||||
import { shouldBlockRequest } from "../../../utils/blockUserAgent";
|
||||
import { clearLockStatusCommand, createDDBClient, getLockStatusCommand, isLockOpen } from "../../../utils/ddb";
|
||||
import { createDynaBridgeClient } from "../../../utils/ddb";
|
||||
import { DoorStatus } from "../../../types/DoorStatus";
|
||||
import { getLockStatusID, isLockOpen } from "../../../schema/LockStatus";
|
||||
|
||||
export interface StatusRequest extends ServerlessEventObject {
|
||||
door: string;
|
||||
@ -32,42 +33,34 @@ export const handler: ServerlessFunctionSignature<TwilioContext, StatusRequest>
|
||||
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) {
|
||||
const fingerprint = JSON.parse(lock?.Item?.fingerprint.S as string);
|
||||
if (isOpen && lock) {
|
||||
const fingerprint = JSON.parse(lock.fingerprint);
|
||||
|
||||
response
|
||||
.setStatusCode(200)
|
||||
.appendHeader('Content-Type', 'application/json')
|
||||
.setBody({
|
||||
status: DoorStatus.OPEN,
|
||||
fingerprint,
|
||||
});
|
||||
response
|
||||
.setStatusCode(200)
|
||||
.appendHeader('Content-Type', 'application/json')
|
||||
.setBody({
|
||||
status: DoorStatus.OPEN,
|
||||
fingerprint,
|
||||
});
|
||||
|
||||
await client.send(clearLockStatusCommand(lock));
|
||||
return;
|
||||
}
|
||||
|
||||
response
|
||||
.setStatusCode(200)
|
||||
.appendHeader('Content-Type', 'application/json')
|
||||
.setBody({
|
||||
status: DoorStatus.CLOSED,
|
||||
});
|
||||
|
||||
}).catch((e) => {
|
||||
console.log(e);
|
||||
response
|
||||
.setStatusCode(500)
|
||||
.appendHeader('Content-Type', 'application/json')
|
||||
.setBody({ err: e });
|
||||
await db.entities.lockStatus.deleteById(getLockStatusID(door));
|
||||
} else {
|
||||
response
|
||||
.setStatusCode(200)
|
||||
.appendHeader('Content-Type', 'application/json')
|
||||
.setBody({
|
||||
status: DoorStatus.CLOSED,
|
||||
});
|
||||
}
|
||||
|
||||
await client.destroy();
|
||||
// destroy the internal client after
|
||||
// @ts-ignore
|
||||
db.ddbClient.destroy();
|
||||
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 { DynaBridge, DynaBridgeEntity } from 'dynabridge';
|
||||
import { DynaBridgeEntity } from 'dynabridge';
|
||||
import { randomUUID } from "crypto";
|
||||
|
||||
export const DOOR_CONFIG_SK = "config";
|
||||
export const EDIT_DOOR_CONFIG_SK = "config-update";
|
||||
@ -15,7 +16,7 @@ export const DoorConfigSchema = z.object({
|
||||
fallbackNumbers: z.array(z.string()),
|
||||
pin: z.string(),
|
||||
pressKey: z.string(),
|
||||
greeting: z.string(),
|
||||
greeting: z.string().optional(),
|
||||
timeout: z.number(),
|
||||
});
|
||||
|
||||
@ -36,6 +37,8 @@ export const getEditDoorConfigID = (doorName: string): string[] => {
|
||||
export type DoorConfig = z.infer<typeof DoorConfigSchema>;
|
||||
export type EditDoorConfig = z.infer<typeof EditDoorConfigSchema>;
|
||||
|
||||
export type EditDoorConfigReq = Omit<EditDoorConfig, "PK" | "SK" | "approvalId">;
|
||||
|
||||
export const DoorConfigEntity: DynaBridgeEntity<DoorConfig> = {
|
||||
tableName: "doorman",
|
||||
id: ["PK", "SK"],
|
||||
@ -45,3 +48,23 @@ export const EditDoorConfigEntity: DynaBridgeEntity<EditDoorConfig> = {
|
||||
tableName: "doorman",
|
||||
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 { DoorConfig } from "../types/DoorConfig";
|
||||
import { DynaBridge } from "dynabridge";
|
||||
import { DoorConfigEntity, EditDoorConfigEntity } from "../schema/DoorConfig";
|
||||
|
||||
export const createDDBClient = (context: TwilioContext) => {
|
||||
return new DynamoDBClient({
|
||||
region: "us-east-1" ,
|
||||
credentials: {
|
||||
accessKeyId: context.AWS_ACCESS_KEY,
|
||||
secretAccessKey: context.AWS_SECRET_ACCESS_KEY,
|
||||
},
|
||||
});
|
||||
};
|
||||
import { DoorAliasEntity } from "../schema/DoorAlias";
|
||||
import { LockStatusEntity } from "../schema/LockStatus";
|
||||
|
||||
export const createDynaBridgeClient = (context: TwilioContext) => {
|
||||
// register all entities here
|
||||
return new DynaBridge({
|
||||
doorConfig: DoorConfigEntity,
|
||||
editDoorConfig: EditDoorConfigEntity,
|
||||
doorAlias: DoorAliasEntity,
|
||||
lockStatus: LockStatusEntity,
|
||||
}, {
|
||||
serialize: (entity) => 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",
|
||||
"node-fetch": "^2.7.0",
|
||||
"prom-client": "^15.1.3",
|
||||
"prometheus-remote-write": "^0.5.0",
|
||||
"prometheus-remote-write": "^0.5.1",
|
||||
"promise.timeout": "^1.2.0",
|
||||
"twilio": "^3.84.1",
|
||||
"winston": "^3.17.0",
|
||||
@ -23,7 +23,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest",
|
||||
"concurrently": "^9.1.0",
|
||||
"concurrently": "^9.1.2",
|
||||
"twilio-run": "^3.5.4"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
import { serve } from "bun";
|
||||
import { Hono } from "hono";
|
||||
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 { getLockStatusID, isLockOpen } from "../../doorman-api/src/schema/LockStatus";
|
||||
import { getDoorConfigID } from "../../doorman-api/src/schema/DoorConfig";
|
||||
|
||||
const app = new Hono();
|
||||
|
||||
@ -15,28 +17,23 @@ app.get("/api/door/info", async (c) => {
|
||||
err: "Must specify a door",
|
||||
}, 400);
|
||||
}
|
||||
|
||||
const db = createDynaBridgeClient(Bun.env as any);
|
||||
|
||||
const client = createDDBClient(Bun.env as any);
|
||||
const config = await client.send(getDoorConfigCommand(door));
|
||||
const config = await db.entities.doorConfig.findById(getDoorConfigID(door));
|
||||
|
||||
if (!config.Item) {
|
||||
if (!config) {
|
||||
return c.json({
|
||||
err: "This buzzer is not registered properly",
|
||||
}, 404);
|
||||
}
|
||||
return await client.send(getLockStatusCommand(door))
|
||||
.then(async (lock) => {
|
||||
const status = isLockOpen(lock) ? DoorStatus.OPEN: "CLOSED";
|
||||
return c.json({
|
||||
id: door,
|
||||
status,
|
||||
});
|
||||
}).catch((e) => {
|
||||
console.log(e);
|
||||
return c.json({
|
||||
err: e,
|
||||
}, 500);
|
||||
});
|
||||
|
||||
const lock = await db.entities.lockStatus.findById(getLockStatusID(door));
|
||||
const status = isLockOpen(lock) ? DoorStatus.OPEN: "CLOSED";
|
||||
return c.json({
|
||||
id: door,
|
||||
status,
|
||||
});
|
||||
});
|
||||
|
||||
serve({
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user