add hardcoded door "onboardingflag" for automated onboarding process
All checks were successful
Build and push image for doorman-homeassistant / docker (push) Successful in 1m49s
Build and push Doorman UI / API / docker (push) Successful in 2m19s
Build and push image for doorman-homeassistant / deploy-gitainer (push) Successful in 5s

This commit is contained in:
Martin Dimitrov 2025-10-04 12:18:44 -07:00
parent 512feb0268
commit 33d7764ca2
8 changed files with 85 additions and 13 deletions

View File

@ -0,0 +1,10 @@
export function isTTLInFuture(item?: { TTL: number }) {
if (!item) {
return false;
}
// ttl is a UTC ms time
const ttl = item.TTL || 0;
return parseInt("" + ttl) > Date.now();
}

View File

@ -18,8 +18,8 @@ export const InfoRequestSchema = z.object({
door: z.string().optional(),
buzzer: z.string().optional(),
})
.partial()
.refine(data => data.buzzer || data.door, 'Buzzer or door must be provided');
.partial()
.refine(data => data.buzzer || data.door, 'Buzzer or door must be provided');
export type InfoRequest = z.infer<typeof InfoRequestSchema>;
export interface InfoRequestTwilio extends ServerlessEventObject<InfoRequest, UserAgentHeader> { };
@ -75,7 +75,7 @@ export const handler: ServerlessFunctionSignature<TwilioContext, InfoRequestTwil
setResponseJson(response, 200, body);
} else {
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,

View File

@ -12,6 +12,7 @@ import { withMetrics } from "../../../common/DoormanHandler";
import { z } from "zod";
import { setResponseJson } from "../../../utils/responseUtils";
import zu from "zod_utilz";
import { ONBOARDING_DOOR_NAME } from "../../../schema/DoorConfig";
export const StatusRequestSchema = z.object({
door: z.string(),
@ -49,7 +50,10 @@ export const handler: ServerlessFunctionSignature<TwilioContext, StatusRequestTw
setResponseJson(response, 200, body);
await db.entities.lockStatus.deleteById(getLockStatusID(door));
// this hardcoded door, keep open for timeout
if (door !== ONBOARDING_DOOR_NAME) {
await db.entities.lockStatus.deleteById(getLockStatusID(door));
}
} else {
const body = StatusResponseSchema.parse({
status: DoorStatus.CLOSED,

View File

@ -2,7 +2,7 @@ import { readdirSync } from "node:fs";
import path from "path";
const paths = [
'./src/functions/api/door'
'./src/functions/api/door',
];
const functionFiles = paths.map(path => readdirSync(path).map(file => path + "/" + file)).flat();
@ -15,7 +15,7 @@ console.log("functions to build:", functionFiles);
const bundledModules = ['dynabridge', 'zod_utilz'];
const externalModules = Object.keys(require('../package.json').dependencies)
.filter(dep => !bundledModules.includes(dep));
.filter(dep => !bundledModules.includes(dep));
console.log("Explicitly bundling dependencies", bundledModules);

View File

@ -1,7 +1,7 @@
import { CreateTableCommand, DynamoDBClient, KeyType, PutItemCommand, ScalarAttributeType } from "@aws-sdk/client-dynamodb";
import { DynamoDBDocument } from "@aws-sdk/lib-dynamodb";
import { DoorAlias, DoorAliasEntity, DoorAliasSchema, getDoorAliasID } from "../schema/DoorAlias";
import { DoorConfig } from "../schema/DoorConfig";
import { DoorConfig, getDoorConfigID, ONBOARDING_DOOR_NAME, ONBOARDING_DOOR_PIN } from "../schema/DoorConfig";
import DynamoDbLocal from "dynamodb-local";
import { sleep } from "bun";
@ -76,6 +76,20 @@ const doorConfig: DoorConfig = {
greeting: "test door",
};
// for onboarding mode
const onboardingDoorConfig: DoorConfig = {
PK: "door-" + ONBOARDING_DOOR_NAME,
SK: "config",
buzzer: "nil",
pressKey: "nil",
discordUsers: [],
fallbackNumbers: [],
pin: ONBOARDING_DOOR_PIN,
buzzerCode: "nil",
timeout: 15 * 60,
greeting: "INTERNAL USE - for onboarding new door",
};
try {
await document.put({
TableName: tableName,
@ -86,6 +100,12 @@ try {
TableName: tableName,
Item: doorAlias,
});
await document.put({
TableName: tableName,
Item: onboardingDoorConfig,
});
} catch (e) {
console.error("failed seeding table", e);
}

View File

@ -5,6 +5,9 @@ import { randomUUID } from "crypto";
export const DOOR_CONFIG_SK = "config";
export const EDIT_DOOR_CONFIG_SK = "config-update";
export const ONBOARDING_DOOR_NAME = "onboardingflag";
export const ONBOARDING_DOOR_PIN = "1234";
export const DoorConfigSchema = z.object({
// keys
PK: z.string().startsWith("door-", "Invalid door key"),

View File

@ -1,5 +1,6 @@
import { z } from "zod";
import { DynaBridgeEntity } from 'dynabridge';
import { isTTLInFuture } from "../common/TTLHelper";
export const LOCK_STATUS_SK = "lock";
@ -24,10 +25,7 @@ export const LockStatusEntity: DynaBridgeEntity<LockStatus> = {
};
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();
return isTTLInFuture(lock);
};
export const createLockStatusWithTimeout = (door: string, timeoutSeconds: number, fingerprint: any): LockStatus => {

View File

@ -2,6 +2,8 @@ import { describe, test, expect } from "bun:test";
import { buzzerUrl, buzzerNumber, baseUrl, doorName, key } from "./testCommon";
import { DoorStatus } from "../src/types/DoorStatus";
import { InfoResponseUI } from "../src/functions/api/door/info";
import { ONBOARDING_DOOR_NAME, ONBOARDING_DOOR_PIN } from "../src/schema/DoorConfig";
import { StatusResponse } from "../src/functions/api/door/status";
if (process.env.STAGE !== 'staging') {
process.exit(0);
@ -33,3 +35,38 @@ describe("buzzer client works", () => {
expect(infoResp.status).toBe(DoorStatus.CLOSED);
});
});
// CAVEAT, this door is currently manually created, for the purposes of automated onboarding flow
describe("onboardingflag door should exist", () => {
test("onboarding door exists", async () => {
// door info
const authResp = await fetch(baseUrl + `/api/door/info?door=${ONBOARDING_DOOR_NAME}`);
expect(authResp.status).toBe(200);
const door = (await authResp.json()) as InfoResponseUI;
expect(door.timeout).toBe(15 * 60);
// this may be flaky, it would fail when a user is onboarding and we are deploying
expect(door.status).toBe(DoorStatus.CLOSED);
});
test("onboarding door does not close after status check", async () => {
let authResp = await fetch(baseUrl + `/api/door/auth?door=${ONBOARDING_DOOR_NAME}&key=${ONBOARDING_DOOR_PIN}`);
expect(authResp.status).toBe(200);
let statusResp = await fetch(baseUrl + `/api/door/status?door=${ONBOARDING_DOOR_NAME}`).then(res => res.json()) as StatusResponse;
expect(statusResp.status).toBe(DoorStatus.OPEN);
// open again
statusResp = await fetch(baseUrl + `/api/door/status?door=${ONBOARDING_DOOR_NAME}`).then(res => res.json()) as StatusResponse;
expect(statusResp.status).toBe(DoorStatus.OPEN);
// close it
authResp = await fetch(baseUrl + `/api/door/auth?door=${ONBOARDING_DOOR_NAME}&key=${ONBOARDING_DOOR_PIN}`);
expect(authResp.status).toBe(200);
// door should now be closed
const infoResp = await fetch(baseUrl + `/api/door/info?door=${ONBOARDING_DOOR_NAME}`).then(res => res.json()) as InfoResponseUI;
expect(infoResp.status).toBe(DoorStatus.CLOSED);
})
});