Compare commits

..

No commits in common. "2f45005a8a37eefad643b7f6fd2eaf92c14125a3" and "73e98c0508b26ee6c0ebc9432d6b88351a1ff884" have entirely different histories.

13 changed files with 337 additions and 203 deletions

BIN
bun.lockb

Binary file not shown.

View File

@ -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 { createDynaBridgeClient } from "../../../utils/ddb"; import { clearLockStatusCommand, createDDBClient, ddbItemToJSON, getDoorConfigCommand, getLockStatusCommand, isLockOpen, setLockStatusCommand } 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,9 +34,10 @@ export const handler: ServerlessFunctionSignature<TwilioContext, AuthRequest> =
return callback(null, response); return callback(null, response);
} }
const db = createDynaBridgeClient(context); const client = createDDBClient(context);
const config: DoorConfig | undefined = await db.entities.doorConfig.findById(getDoorConfigID(door)); const ddbConfig = await client.send(getDoorConfigCommand(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);
@ -75,10 +76,12 @@ 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
const lock = await db.entities.lockStatus.findById(getLockStatusID(door)); await client.send(getLockStatusCommand(door))
const isOpen = isLockOpen(lock); .then(async (lockDdb) => {
const isOpen = isLockOpen(lockDdb);
if (isOpen && lock) { if (isOpen) {
const lock: Lock = ddbItemToJSON<Lock>(lockDdb);
const fingerprint = JSON.parse(lock.fingerprint); const fingerprint = JSON.parse(lock.fingerprint);
response response
@ -89,18 +92,25 @@ export const handler: ServerlessFunctionSignature<TwilioContext, AuthRequest> =
fingerprint, fingerprint,
}); });
await db.entities.lockStatus.deleteById(getLockStatusID(door)); await client.send(clearLockStatusCommand(lockDdb));
} else { return;
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);
// destroy the internal client after response
// @ts-ignore .setStatusCode(500)
db.ddbClient.destroy(); .appendHeader('Content-Type', 'application/json')
.setBody({ err: e });
});
});
await client.destroy();
return callback(null, response); return callback(null, response);
}); });

View File

@ -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 { createDynaBridgeClient } from "../../../utils/ddb"; import { createDDBClient, ddbItemToJSON, getDoorConfigCommand, getDoorConfigUpdateCommand, putDoorUpdateConfigCommand, replaceDoorConfigWithUpdateItem } 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,18 +28,19 @@ export const handler: ServerlessFunctionSignature<TwilioContext, EditRequest> =
let approvalId = event.approvalId; let approvalId = event.approvalId;
let newConfigString = event.newConfig; let newConfigString = event.newConfig;
const db = createDynaBridgeClient(context); const client = createDDBClient(context);
// approve path // approve path
if (door && approvalId) { if (door && approvalId) {
const newConfig = await db.entities.editDoorConfig.findById(getEditDoorConfigID(door)); const newConfigDdb = await client.send(getDoorConfigUpdateCommand(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);
} }
db.entities.doorConfig.save(editDoorToDoorConfig(newConfig)); await client.send(replaceDoorConfigWithUpdateItem(newConfigDdb as any));
// 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}"`;
@ -63,8 +64,10 @@ export const handler: ServerlessFunctionSignature<TwilioContext, EditRequest> =
return callback(null, response); return callback(null, response);
} }
const newConfig: EditDoorConfigReq = JSON.parse(newConfigString); const newConfig: EditDoorConfig = 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);
@ -76,14 +79,15 @@ export const handler: ServerlessFunctionSignature<TwilioContext, EditRequest> =
newConfig.pin = config.pin; newConfig.pin = config.pin;
} }
const editDoorConfig = createEditDoorConfig(door, newConfig); const input = putDoorUpdateConfigCommand(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=${editDoorConfig.approvalId as string}`; const approvalUrl = `https://doorman.chromart.cc/api/door/edit?door=${door}&approvalId=${input?.input?.Item?.approvalId.S as string}`;
console.log(approvalUrl); console.log(approvalUrl);
// send update to discord users // send update to discord users
@ -102,11 +106,8 @@ export const handler: ServerlessFunctionSignature<TwilioContext, EditRequest> =
response response
.setStatusCode(200) .setStatusCode(200)
.appendHeader('Content-Type', 'application/json') .appendHeader('Content-Type', 'application/json')
.setBody({ msg: "Created Configuration change" }); .setBody({ msg: update });
// destroy the internal client after
// @ts-ignore
db.ddbClient.destroy();
await client.destroy();
return callback(null, response); return callback(null, response);
}; };

View File

@ -4,11 +4,10 @@
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 { createDynaBridgeClient } from "../../../utils/ddb"; import { createDDBClient, createDynaBridgeClient, ddbItemToJSON, getDoorAliasCommand, getDoorConfigCommand, getLockStatusCommand, isLockOpen } from "../../../utils/ddb";
import { DoorStatus } from "../../../types/DoorStatus"; import { DoorStatus } from "../../../types/DoorStatus";
import { getDoorConfigID } from "../../../schema/DoorConfig"; import { DoorConfig } from "../../../types/DoorConfig";
import { getDoorAliasID } from "../../../schema/DoorAlias"; import { DOOR_CONFIG_SK, getDoorConfigID } from "../../../schema/DoorConfig";
import { getLockStatusID, isLockOpen } from "../../../schema/LockStatus";
export interface InfoRequest extends ServerlessEventObject { export interface InfoRequest extends ServerlessEventObject {
door?: string; door?: string;
@ -39,19 +38,20 @@ 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 db.entities.doorAlias.findById(getDoorAliasID(buzzer)) door = await client.send(getDoorAliasCommand(buzzer))
.then(async (alias) => { .then(async (alias) => {
if (!alias) { if (!alias.Item) {
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.name; return alias.Item.name.S;
}); });
} }
@ -74,10 +74,11 @@ 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 {
const lock = await db.entities.lockStatus.findById(getLockStatusID(door)); await client.send(getLockStatusCommand(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
@ -92,16 +93,20 @@ 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 || "",
}); });
}).catch((e) => {
console.log(e);
response
.setStatusCode(500)
.appendHeader('Content-Type', 'application/json')
.setBody({ err: e });
});
} }
} }
} }
await client.destroy();
// destroy the internal client after
// @ts-ignore
db.ddbClient.destroy();
return callback(null, response); return callback(null, response);
}; };

View File

@ -5,9 +5,8 @@
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 { createDynaBridgeClient } from "../../../utils/ddb"; import { clearLockStatusCommand, createDDBClient, getLockStatusCommand, isLockOpen } 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;
@ -33,13 +32,14 @@ export const handler: ServerlessFunctionSignature<TwilioContext, StatusRequest>
return callback(null, response); return callback(null, response);
} }
const db = createDynaBridgeClient(context); const client = createDDBClient(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 && lock) { if (isOpen) {
const fingerprint = JSON.parse(lock.fingerprint); const fingerprint = JSON.parse(lock?.Item?.fingerprint.S as string);
response response
.setStatusCode(200) .setStatusCode(200)
@ -49,18 +49,25 @@ export const handler: ServerlessFunctionSignature<TwilioContext, StatusRequest>
fingerprint, fingerprint,
}); });
await db.entities.lockStatus.deleteById(getLockStatusID(door)); await client.send(clearLockStatusCommand(lock));
} else { return;
}
response response
.setStatusCode(200) .setStatusCode(200)
.appendHeader('Content-Type', 'application/json') .appendHeader('Content-Type', 'application/json')
.setBody({ .setBody({
status: DoorStatus.CLOSED, status: DoorStatus.CLOSED,
}); });
}
// destroy the internal client after }).catch((e) => {
// @ts-ignore console.log(e);
db.ddbClient.destroy(); response
.setStatusCode(500)
.appendHeader('Content-Type', 'application/json')
.setBody({ err: e });
});
await client.destroy();
return callback(null, response); return callback(null, response);
}; };

View File

@ -1,22 +0,0 @@
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"],
};

View File

@ -1,6 +1,5 @@
import { z } from "zod"; import { z } from "zod";
import { DynaBridgeEntity } from 'dynabridge'; import { DynaBridge, 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";
@ -16,7 +15,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().optional(), greeting: z.string(),
timeout: z.number(), timeout: z.number(),
}); });
@ -37,8 +36,6 @@ 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"],
@ -48,23 +45,3 @@ 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(),
};
}

View File

@ -1,40 +0,0 @@
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),
};
};

View File

@ -0,0 +1,14 @@
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;
}

View File

@ -0,0 +1,3 @@
export interface Lock {
fingerprint: any;
}

View File

@ -1,16 +1,25 @@
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";
import { LockStatusEntity } from "../schema/LockStatus"; export const createDDBClient = (context: TwilioContext) => {
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) => {
@ -33,3 +42,170 @@ 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,
});
};

View File

@ -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.1", "prometheus-remote-write": "^0.5.0",
"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.2", "concurrently": "^9.1.0",
"twilio-run": "^3.5.4" "twilio-run": "^3.5.4"
}, },
"engines": { "engines": {

View File

@ -1,10 +1,8 @@
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 { createDynaBridgeClient } from "../../doorman-api/src/utils/ddb"; import { createDDBClient, getDoorConfigCommand, getLockStatusCommand, isLockOpen } 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();
@ -18,22 +16,27 @@ app.get("/api/door/info", async (c) => {
}, 400); }, 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({ 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))
const lock = await db.entities.lockStatus.findById(getLockStatusID(door)); .then(async (lock) => {
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({