migrate api to typescript
This commit is contained in:
parent
804d5b678d
commit
e2da767c86
3
packages/doorman-api/.gitignore
vendored
3
packages/doorman-api/.gitignore
vendored
@ -133,3 +133,6 @@ dist
|
|||||||
.yarn/build-state.yml
|
.yarn/build-state.yml
|
||||||
.yarn/install-state.gz
|
.yarn/install-state.gz
|
||||||
.pnp.*
|
.pnp.*
|
||||||
|
|
||||||
|
functions/*
|
||||||
|
deprecated-functions
|
||||||
@ -2,6 +2,7 @@
|
|||||||
"commands": {},
|
"commands": {},
|
||||||
"environments": {},
|
"environments": {},
|
||||||
"projects": {},
|
"projects": {},
|
||||||
|
"functionsFolder": "./build/functions",
|
||||||
// "assets": true /* Upload assets. Can be turned off with --no-assets */,
|
// "assets": true /* Upload assets. Can be turned off with --no-assets */,
|
||||||
// "assetsFolder": null /* Specific folder name to be used for static assets */,
|
// "assetsFolder": null /* Specific folder name to be used for static assets */,
|
||||||
// "buildSid": null /* An existing Build SID to deploy to the new environment */,
|
// "buildSid": null /* An existing Build SID to deploy to the new environment */,
|
||||||
|
|||||||
@ -1,99 +0,0 @@
|
|||||||
/**
|
|
||||||
* Try to unlock the door with auth mode
|
|
||||||
*/
|
|
||||||
|
|
||||||
exports.handler = async function(context, event, callback) {
|
|
||||||
const response = new Twilio.Response();
|
|
||||||
|
|
||||||
const blockPath = Runtime.getFunctions()['common/blockUserAgent'].path;
|
|
||||||
const block = require(blockPath);
|
|
||||||
|
|
||||||
if (block.shouldBlockRequest(event)) {
|
|
||||||
response.setStatusCode(200);
|
|
||||||
return callback(null, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
let door = event.door;
|
|
||||||
let pin = event.key;
|
|
||||||
|
|
||||||
if (!door || !pin) {
|
|
||||||
response.setStatusCode(400);
|
|
||||||
return callback(null, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
const ddbPath = Runtime.getFunctions()['common/ddb'].path;
|
|
||||||
const ddb = require(ddbPath);
|
|
||||||
const client = ddb.createDDBClient(context);
|
|
||||||
|
|
||||||
const config = await client.send(ddb.getDoorConfigCommand(door));
|
|
||||||
|
|
||||||
if (!config.Item) {
|
|
||||||
response.setStatusCode(404);
|
|
||||||
return callback(null, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
let correctPin = config.Item.pin.S;
|
|
||||||
let discordUsers = config.Item.discordUsers.SS;
|
|
||||||
|
|
||||||
let method;
|
|
||||||
|
|
||||||
if (correctPin === pin) {
|
|
||||||
method = "PIN";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (discordUsers.includes(pin)) {
|
|
||||||
method = "DISCORD";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!method) {
|
|
||||||
response.setStatusCode(401);
|
|
||||||
return callback(null, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
const fingerprint = {
|
|
||||||
method,
|
|
||||||
userAgent: event.request.headers['user-agent'],
|
|
||||||
ip: event.ip,
|
|
||||||
};
|
|
||||||
|
|
||||||
// take timeout from the query string
|
|
||||||
const timeout = event.timeout ? parseInt(event.timeout) : config.Item.timeout.N;
|
|
||||||
|
|
||||||
// check lock status if locked, then unlock. If unlocked then lock
|
|
||||||
await client.send(ddb.getLockStatusCommand(door))
|
|
||||||
.then(async (lock) => {
|
|
||||||
const isOpen = ddb.isLockOpen(lock);
|
|
||||||
|
|
||||||
if (isOpen) {
|
|
||||||
const fingerprint = JSON.parse(lock.Item.fingerprint.S);
|
|
||||||
|
|
||||||
response
|
|
||||||
.setStatusCode(200)
|
|
||||||
.appendHeader('Content-Type', 'application/json')
|
|
||||||
.setBody({
|
|
||||||
status: "CLOSED",
|
|
||||||
fingerprint,
|
|
||||||
});
|
|
||||||
|
|
||||||
await client.send(ddb.clearLockStatusCommand(lock));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await client.send(ddb.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 });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
await client.destroy();
|
|
||||||
return callback(null, response);
|
|
||||||
};
|
|
||||||
@ -1,105 +0,0 @@
|
|||||||
|
|
||||||
/**
|
|
||||||
* Edit API for doors
|
|
||||||
*/
|
|
||||||
|
|
||||||
exports.handler = async function(context, event, callback) {
|
|
||||||
const response = new Twilio.Response();
|
|
||||||
|
|
||||||
const blockPath = Runtime.getFunctions()['common/blockUserAgent'].path;
|
|
||||||
const block = require(blockPath);
|
|
||||||
|
|
||||||
if (block.shouldBlockRequest(event)) {
|
|
||||||
response.setStatusCode(200);
|
|
||||||
return callback(null, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
let door = event.door;
|
|
||||||
let approvalId = event.approvalId;
|
|
||||||
let newConfig = event.newConfig;
|
|
||||||
|
|
||||||
const ddbPath = Runtime.getFunctions()['common/ddb'].path;
|
|
||||||
const discordPath = Runtime.getFunctions()['common/discord'].path;
|
|
||||||
const ddb = require(ddbPath);
|
|
||||||
const discord = require(discordPath);
|
|
||||||
const client = ddb.createDDBClient(context);
|
|
||||||
|
|
||||||
// approve path
|
|
||||||
if (door && approvalId) {
|
|
||||||
const newConfig = await client.send(ddb.getDoorConfigUpdateCommand(door));
|
|
||||||
|
|
||||||
if (!newConfig || newConfig.Item.approvalId.S !== approvalId) {
|
|
||||||
response.setStatusCode(400);
|
|
||||||
return callback(null, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
await client.send(ddb.replaceDoorConfigWithUpdateItem(newConfig));
|
|
||||||
|
|
||||||
// send update to discord users
|
|
||||||
const updateMessage = `Configuration change \`${approvalId}\` was approved @ Door "${door}"`;
|
|
||||||
|
|
||||||
const discordPromises = newConfig.Item.discordUsers.SS.map((user) => {
|
|
||||||
return discord.sendMessageToUser(
|
|
||||||
context,
|
|
||||||
user,
|
|
||||||
updateMessage,
|
|
||||||
).catch(e => console.error(e))
|
|
||||||
});
|
|
||||||
|
|
||||||
await Promise.all(discordPromises);
|
|
||||||
|
|
||||||
response.setStatusCode(200);
|
|
||||||
return callback(null, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!door || !newConfig) {
|
|
||||||
response.setStatusCode(400);
|
|
||||||
return callback(null, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
newConfig = JSON.parse(event.newConfig);
|
|
||||||
|
|
||||||
const config = await client.send(ddb.getDoorConfigCommand(door));
|
|
||||||
|
|
||||||
if (!config.Item) {
|
|
||||||
response.setStatusCode(404);
|
|
||||||
return callback(null, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
// set to old PIN if it is missing
|
|
||||||
if (newConfig.pin === "") {
|
|
||||||
newConfig.pin = config.Item.pin.S;
|
|
||||||
}
|
|
||||||
|
|
||||||
const input = ddb.putDoorUpdateConfigCommand(door, newConfig);
|
|
||||||
|
|
||||||
const update = await client.send(input);
|
|
||||||
|
|
||||||
newConfig.discordUser = undefined;
|
|
||||||
newConfig.fallbackNumber = undefined;
|
|
||||||
newConfig.status = undefined;
|
|
||||||
|
|
||||||
const approvalUrl = `https://doorman.chromart.cc/api/door/edit?door=${door}&approvalId=${input.input.Item.approvalId.S}`;
|
|
||||||
console.log(approvalUrl);
|
|
||||||
|
|
||||||
// send update to discord users
|
|
||||||
const approvalMessage = `Configuration change requested @ Door "${door}" [click to approve it](${approvalUrl})\`\`\`${JSON.stringify(newConfig, null, 4)}\`\`\``;
|
|
||||||
|
|
||||||
const discordPromises = config.Item.discordUsers.SS.map((user) => {
|
|
||||||
return discord.sendMessageToUser(
|
|
||||||
context,
|
|
||||||
user,
|
|
||||||
approvalMessage,
|
|
||||||
).catch(e => console.error(e))
|
|
||||||
});
|
|
||||||
|
|
||||||
await Promise.all(discordPromises);
|
|
||||||
|
|
||||||
response
|
|
||||||
.setStatusCode(200)
|
|
||||||
.appendHeader('Content-Type', 'application/json')
|
|
||||||
.setBody({ msg: update });
|
|
||||||
|
|
||||||
await client.destroy();
|
|
||||||
return callback(null, response);
|
|
||||||
};
|
|
||||||
@ -1,54 +0,0 @@
|
|||||||
function jsonMsgSuffix(jsonString) {
|
|
||||||
if (!jsonString) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const fingerprint = JSON.parse(jsonString);
|
|
||||||
return `\`\`\`# Unlocked by\n${JSON.stringify(fingerprint, null, 4)}\`\`\``;
|
|
||||||
} catch (e) {
|
|
||||||
return `\`\`\`# Unlocked by\n# WARN: Unknown or corrupt raw fingerprint:\n ${jsonString}\`\`\``;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.handler = async function(context, event, callback) {
|
|
||||||
const response = new Twilio.Response();
|
|
||||||
const discordPath = Runtime.getFunctions()['common/discord'].path;
|
|
||||||
const discord = require(discordPath);
|
|
||||||
|
|
||||||
let users = event.discordUser;
|
|
||||||
let msgs = event.msg;
|
|
||||||
let jsons = event.json;
|
|
||||||
let promises = [];
|
|
||||||
|
|
||||||
try {
|
|
||||||
users = JSON.parse(users);
|
|
||||||
msgs = JSON.parse(msgs);
|
|
||||||
console.log("before parsing", jsons);
|
|
||||||
jsons = JSON.parse(jsons);
|
|
||||||
console.log("after parsing", jsons);
|
|
||||||
|
|
||||||
promises = msgs.map((msg, i) =>
|
|
||||||
discord.sendMessageToUser(
|
|
||||||
context,
|
|
||||||
users[i],
|
|
||||||
msg + jsonMsgSuffix(jsons[i])
|
|
||||||
).catch(e => console.error(e))
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
response
|
|
||||||
.setStatusCode(500)
|
|
||||||
.appendHeader('Content-Type', 'application/json')
|
|
||||||
.setBody({ err: e, event });
|
|
||||||
return callback(null, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
let timer = setTimeout(() => {
|
|
||||||
console.log("Ungraceful finish: running out of time");
|
|
||||||
callback(null, response);
|
|
||||||
}, 9500);
|
|
||||||
|
|
||||||
await Promise.all(promises);
|
|
||||||
clearTimeout(timer);
|
|
||||||
return callback(null, response);
|
|
||||||
};
|
|
||||||
@ -1,64 +0,0 @@
|
|||||||
/**
|
|
||||||
* Try to update door status
|
|
||||||
*/
|
|
||||||
|
|
||||||
exports.handler = async function(context, event, callback) {
|
|
||||||
const response = new Twilio.Response();
|
|
||||||
|
|
||||||
const blockPath = Runtime.getFunctions()['common/blockUserAgent'].path;
|
|
||||||
const block = require(blockPath);
|
|
||||||
|
|
||||||
if (block.shouldBlockRequest(event)) {
|
|
||||||
response.setStatusCode(200);
|
|
||||||
return callback(null, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
const door = event.door;
|
|
||||||
|
|
||||||
if (!door) {
|
|
||||||
response.setStatusCode(400);
|
|
||||||
return callback(null, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
const ddbPath = Runtime.getFunctions()['common/ddb'].path;
|
|
||||||
const ddb = require(ddbPath);
|
|
||||||
|
|
||||||
const client = ddb.createDDBClient(context);
|
|
||||||
|
|
||||||
await client.send(ddb.getLockStatusCommand(door))
|
|
||||||
.then(async (lock) => {
|
|
||||||
const isOpen = ddb.isLockOpen(lock);
|
|
||||||
|
|
||||||
if (isOpen) {
|
|
||||||
const fingerprint = JSON.parse(lock.Item.fingerprint.S);
|
|
||||||
|
|
||||||
response
|
|
||||||
.setStatusCode(200)
|
|
||||||
.appendHeader('Content-Type', 'application/json')
|
|
||||||
.setBody({
|
|
||||||
status: "OPEN",
|
|
||||||
fingerprint,
|
|
||||||
});
|
|
||||||
|
|
||||||
await client.send(ddb.clearLockStatusCommand(lock));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
response
|
|
||||||
.setStatusCode(200)
|
|
||||||
.appendHeader('Content-Type', 'application/json')
|
|
||||||
.setBody({
|
|
||||||
status: "CLOSED",
|
|
||||||
});
|
|
||||||
|
|
||||||
}).catch((e) => {
|
|
||||||
console.log(e);
|
|
||||||
response
|
|
||||||
.setStatusCode(500)
|
|
||||||
.appendHeader('Content-Type', 'application/json')
|
|
||||||
.setBody({ err: e });
|
|
||||||
});
|
|
||||||
|
|
||||||
await client.destroy();
|
|
||||||
return callback(null, response);
|
|
||||||
};
|
|
||||||
@ -4,17 +4,22 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
"start": "twilio-run --live --port 8080",
|
"start-twilio": "twilio-run --live --port 8080",
|
||||||
|
"watch-build": "bun run --watch src/index.ts",
|
||||||
|
"start": "concurrently \"bun run watch-build\" \"bun run start-twilio\"",
|
||||||
|
"build": "bun run src/index.ts",
|
||||||
"deploy": "twilio-run deploy --load-system-env --env .env.example --service-name doorman --environment=prod --override-existing-project"
|
"deploy": "twilio-run deploy --load-system-env --env .env.example --service-name doorman --environment=prod --override-existing-project"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-dynamodb": "^3.609.0",
|
"@aws-sdk/client-dynamodb": "^3.609.0",
|
||||||
|
"@twilio-labs/serverless-runtime-types": "^4.0.1",
|
||||||
"@twilio/runtime-handler": "1.3.0",
|
"@twilio/runtime-handler": "1.3.0",
|
||||||
"discord.js": "^14.16.3",
|
"discord.js": "^14.16.3",
|
||||||
"twilio": "^3.56"
|
"twilio": "^3.56"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"twilio-run": "^3.5.4"
|
"twilio-run": "^3.5.4",
|
||||||
|
"concurrently": "^9.1.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "18"
|
"node": "18"
|
||||||
|
|||||||
112
packages/doorman-api/src/functions/api/door/auth.ts
Normal file
112
packages/doorman-api/src/functions/api/door/auth.ts
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
/**
|
||||||
|
* Try to unlock the door with auth mode
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { ServerlessEventObject, ServerlessFunctionSignature } from "@twilio-labs/serverless-runtime-types/types";
|
||||||
|
import { TwilioContext } from "../../../types/TwilioContext";
|
||||||
|
import { shouldBlockRequest, UserAgentHeader } from "../../../utils/blockUserAgent";
|
||||||
|
import { clearLockStatusCommand, createDDBClient, ddbItemToJSON, getDoorConfigCommand, getLockStatusCommand, isLockOpen, setLockStatusCommand } from "../../../utils/ddb";
|
||||||
|
import { DoorConfig } from "../../../types/DoorConfig";
|
||||||
|
import { AuthMethod } from "../../../types/AuthMethod";
|
||||||
|
import { Lock } from "../../../types/Lock";
|
||||||
|
import { DoorStatus } from "../../../types/DoorStatus";
|
||||||
|
|
||||||
|
export interface AuthRequest extends ServerlessEventObject<{}, UserAgentHeader> {
|
||||||
|
door?: string;
|
||||||
|
key?: string;
|
||||||
|
ip: string;
|
||||||
|
timeout?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const handler: ServerlessFunctionSignature<TwilioContext, AuthRequest> = async function(context, event, callback) {
|
||||||
|
const response = new Twilio.Response();
|
||||||
|
|
||||||
|
if (shouldBlockRequest(event)) {
|
||||||
|
response.setStatusCode(200);
|
||||||
|
return callback(null, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
let door = event.door;
|
||||||
|
let pin = event.key;
|
||||||
|
|
||||||
|
if (!door || !pin) {
|
||||||
|
response.setStatusCode(400);
|
||||||
|
return callback(null, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
const client = createDDBClient(context);
|
||||||
|
|
||||||
|
const ddbConfig = await client.send(getDoorConfigCommand(door));
|
||||||
|
const config: DoorConfig = ddbItemToJSON<DoorConfig>(ddbConfig);
|
||||||
|
|
||||||
|
if (!config) {
|
||||||
|
response.setStatusCode(404);
|
||||||
|
return callback(null, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
let correctPin = config.pin;
|
||||||
|
let discordUsers = config.discordUsers;
|
||||||
|
|
||||||
|
let method: AuthMethod | undefined;
|
||||||
|
|
||||||
|
if (correctPin === pin) {
|
||||||
|
method = AuthMethod.PIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (discordUsers.includes(pin)) {
|
||||||
|
method = AuthMethod.DISCORD;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!method) {
|
||||||
|
response.setStatusCode(401);
|
||||||
|
return callback(null, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
const fingerprint = {
|
||||||
|
method,
|
||||||
|
userAgent: event.request.headers['user-agent'],
|
||||||
|
ip: event.ip,
|
||||||
|
};
|
||||||
|
|
||||||
|
// take timeout from the query string
|
||||||
|
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);
|
||||||
|
|
||||||
|
if (isOpen) {
|
||||||
|
const lock: Lock = ddbItemToJSON<Lock>(lockDdb);
|
||||||
|
const fingerprint = JSON.parse(lock.fingerprint);
|
||||||
|
|
||||||
|
response
|
||||||
|
.setStatusCode(200)
|
||||||
|
.appendHeader('Content-Type', 'application/json')
|
||||||
|
.setBody({
|
||||||
|
status: DoorStatus.CLOSED,
|
||||||
|
fingerprint,
|
||||||
|
});
|
||||||
|
|
||||||
|
await client.send(clearLockStatusCommand(lockDdb));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await client.destroy();
|
||||||
|
return callback(null, response);
|
||||||
|
};
|
||||||
113
packages/doorman-api/src/functions/api/door/edit.ts
Normal file
113
packages/doorman-api/src/functions/api/door/edit.ts
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
|
||||||
|
/**
|
||||||
|
* Edit API for doors
|
||||||
|
*/
|
||||||
|
|
||||||
|
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 { sendMessageToUser } from "../../../utils/discord";
|
||||||
|
|
||||||
|
export interface EditRequest extends ServerlessEventObject<{}, UserAgentHeader> {
|
||||||
|
door?: string;
|
||||||
|
approvalId?: string;
|
||||||
|
newConfig?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const handler: ServerlessFunctionSignature<TwilioContext, EditRequest> = async function(context, event, callback) {
|
||||||
|
const response = new Twilio.Response();
|
||||||
|
|
||||||
|
if (shouldBlockRequest(event)) {
|
||||||
|
response.setStatusCode(200);
|
||||||
|
return callback(null, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
let door = event.door;
|
||||||
|
let approvalId = event.approvalId;
|
||||||
|
let newConfigString = event.newConfig;
|
||||||
|
|
||||||
|
const client = createDDBClient(context);
|
||||||
|
|
||||||
|
// approve path
|
||||||
|
if (door && approvalId) {
|
||||||
|
const newConfigDdb = await client.send(getDoorConfigUpdateCommand(door));
|
||||||
|
const newConfig = ddbItemToJSON<EditDoorConfig>(newConfigDdb);
|
||||||
|
|
||||||
|
if (!newConfig || newConfig.approvalId !== approvalId) {
|
||||||
|
response.setStatusCode(400);
|
||||||
|
return callback(null, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
await client.send(replaceDoorConfigWithUpdateItem(newConfigDdb as any));
|
||||||
|
|
||||||
|
// send update to discord users
|
||||||
|
const updateMessage = `Configuration change \`${approvalId}\` was approved @ Door "${door}"`;
|
||||||
|
|
||||||
|
const discordPromises = newConfig.discordUsers.map((user) => {
|
||||||
|
return sendMessageToUser(
|
||||||
|
context,
|
||||||
|
user,
|
||||||
|
updateMessage,
|
||||||
|
).catch(e => console.error(e))
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all(discordPromises);
|
||||||
|
|
||||||
|
response.setStatusCode(200);
|
||||||
|
return callback(null, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!door || !newConfigString) {
|
||||||
|
response.setStatusCode(400);
|
||||||
|
return callback(null, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
const newConfig: EditDoorConfig = JSON.parse(newConfigString);
|
||||||
|
|
||||||
|
const configDdb = await client.send(getDoorConfigCommand(door));
|
||||||
|
const config = ddbItemToJSON<DoorConfig>(configDdb);
|
||||||
|
|
||||||
|
if (!config) {
|
||||||
|
response.setStatusCode(404);
|
||||||
|
return callback(null, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
// set to old PIN if it is missing
|
||||||
|
if (newConfig.pin === "") {
|
||||||
|
newConfig.pin = config.pin;
|
||||||
|
}
|
||||||
|
|
||||||
|
const input = putDoorUpdateConfigCommand(door, newConfig);
|
||||||
|
|
||||||
|
const update = await client.send(input);
|
||||||
|
|
||||||
|
// 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}`;
|
||||||
|
console.log(approvalUrl);
|
||||||
|
|
||||||
|
// send update to discord users
|
||||||
|
const approvalMessage = `Configuration change requested @ Door "${door}" [click to approve it](${approvalUrl})\`\`\`${JSON.stringify(newConfig, null, 4)}\`\`\``;
|
||||||
|
|
||||||
|
const discordPromises = config.discordUsers.map((user) => {
|
||||||
|
return sendMessageToUser(
|
||||||
|
context,
|
||||||
|
user,
|
||||||
|
approvalMessage,
|
||||||
|
).catch(e => console.error(e))
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all(discordPromises);
|
||||||
|
|
||||||
|
response
|
||||||
|
.setStatusCode(200)
|
||||||
|
.appendHeader('Content-Type', 'application/json')
|
||||||
|
.setBody({ msg: update });
|
||||||
|
|
||||||
|
await client.destroy();
|
||||||
|
return callback(null, response);
|
||||||
|
};
|
||||||
@ -2,7 +2,23 @@
|
|||||||
* Try to get door info
|
* Try to get door info
|
||||||
*/
|
*/
|
||||||
|
|
||||||
exports.handler = async function(context, event, callback) {
|
import { ServerlessEventObject, ServerlessFunctionSignature } from "@twilio-labs/serverless-runtime-types/types";
|
||||||
|
import { TwilioContext } from "../../../types/TwilioContext";
|
||||||
|
import { createDDBClient, ddbItemToJSON, getDoorAliasCommand, getDoorConfigCommand, getLockStatusCommand, isLockOpen } from "../../../utils/ddb";
|
||||||
|
import { DoorStatus } from "../../../types/DoorStatus";
|
||||||
|
import { DoorConfig } from "../../../types/DoorConfig";
|
||||||
|
|
||||||
|
export interface InfoRequest extends ServerlessEventObject {
|
||||||
|
door?: string;
|
||||||
|
buzzer?: string;
|
||||||
|
|
||||||
|
// TODO: change these to be multiple
|
||||||
|
discordUser: string;
|
||||||
|
msg: string;
|
||||||
|
json: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const handler: ServerlessFunctionSignature<TwilioContext, InfoRequest> = async function(context, event, callback) {
|
||||||
const response = new Twilio.Response();
|
const response = new Twilio.Response();
|
||||||
|
|
||||||
let door = event.door;
|
let door = event.door;
|
||||||
@ -13,12 +29,10 @@ exports.handler = async function(context, event, callback) {
|
|||||||
return callback(null, response);
|
return callback(null, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
const ddbPath = Runtime.getFunctions()['common/ddb'].path;
|
const client = createDDBClient(context);
|
||||||
const ddb = require(ddbPath);
|
|
||||||
const client = ddb.createDDBClient(context);
|
|
||||||
|
|
||||||
if (buzzer) {
|
if (buzzer) {
|
||||||
door = await client.send(ddb.getDoorAliasCommand(buzzer))
|
door = await client.send(getDoorAliasCommand(buzzer))
|
||||||
.then(async (alias) => {
|
.then(async (alias) => {
|
||||||
if (!alias.Item) {
|
if (!alias.Item) {
|
||||||
response
|
response
|
||||||
@ -32,7 +46,7 @@ exports.handler = async function(context, event, callback) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (door) {
|
if (door) {
|
||||||
const config = await client.send(ddb.getDoorConfigCommand(door));
|
const config = await client.send(getDoorConfigCommand(door));
|
||||||
|
|
||||||
if (!config.Item) {
|
if (!config.Item) {
|
||||||
response
|
response
|
||||||
@ -53,9 +67,10 @@ exports.handler = async function(context, event, callback) {
|
|||||||
discordUsers: config.Item?.discordUsers?.SS || [],
|
discordUsers: config.Item?.discordUsers?.SS || [],
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await client.send(ddb.getLockStatusCommand(door))
|
await client.send(getLockStatusCommand(door))
|
||||||
.then(async (lock) => {
|
.then(async (lock) => {
|
||||||
const status = ddb.isLockOpen(lock) ? "OPEN": "CLOSED";
|
const status = isLockOpen(lock) ? DoorStatus.OPEN: DoorStatus.CLOSED;
|
||||||
|
const doorConfig: DoorConfig = ddbItemToJSON<DoorConfig>(lock);
|
||||||
|
|
||||||
// respond to UI
|
// respond to UI
|
||||||
response
|
response
|
||||||
@ -63,14 +78,14 @@ exports.handler = async function(context, event, callback) {
|
|||||||
.appendHeader('Content-Type', 'application/json')
|
.appendHeader('Content-Type', 'application/json')
|
||||||
.setBody({
|
.setBody({
|
||||||
id: door,
|
id: door,
|
||||||
timeout: config.Item.timeout.N,
|
timeout: doorConfig.timeout,
|
||||||
buzzer: config.Item.buzzer.S,
|
buzzer: doorConfig.buzzer,
|
||||||
status,
|
status,
|
||||||
buzzerCode: config.Item.buzzerCode.S,
|
buzzerCode: doorConfig.buzzerCode,
|
||||||
fallbackNumbers: config.Item.fallbackNumbers.SS,
|
fallbackNumbers: doorConfig.fallbackNumbers,
|
||||||
pressKey: config.Item.pressKey.S,
|
pressKey: doorConfig.pressKey,
|
||||||
discordUsers: config.Item?.discordUsers?.SS || [],
|
discordUsers: doorConfig.discordUsers || [],
|
||||||
greeting: config.Item?.greeting?.S || "",
|
greeting: doorConfig.greeting || "",
|
||||||
});
|
});
|
||||||
|
|
||||||
}).catch((e) => {
|
}).catch((e) => {
|
||||||
54
packages/doorman-api/src/functions/api/door/notify.ts
Normal file
54
packages/doorman-api/src/functions/api/door/notify.ts
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import { ServerlessEventObject, ServerlessFunctionSignature } from "@twilio-labs/serverless-runtime-types/types";
|
||||||
|
import { TwilioContext } from "../../../types/TwilioContext";
|
||||||
|
import { jsonMsgSuffix, sendMessageToUser } from "../../../utils/discord";
|
||||||
|
|
||||||
|
|
||||||
|
export interface NotifyRequest extends ServerlessEventObject {
|
||||||
|
door: string;
|
||||||
|
|
||||||
|
// TODO: change these to be multiple
|
||||||
|
discordUser: string;
|
||||||
|
msg: string;
|
||||||
|
json: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const handler: ServerlessFunctionSignature<TwilioContext, NotifyRequest> = async function(context, event, callback) {
|
||||||
|
const response = new Twilio.Response();
|
||||||
|
|
||||||
|
let users: string[];
|
||||||
|
let msgs: string[];
|
||||||
|
let jsons: string[];
|
||||||
|
let promises = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
users = JSON.parse(event.discordUser);
|
||||||
|
msgs = JSON.parse(event.msg);
|
||||||
|
console.log("before parsing", event.json);
|
||||||
|
jsons = JSON.parse(event.json);
|
||||||
|
console.log("after parsing", event.json);
|
||||||
|
|
||||||
|
promises = msgs.map((msg, i) =>
|
||||||
|
sendMessageToUser(
|
||||||
|
context,
|
||||||
|
users[i],
|
||||||
|
msg + jsonMsgSuffix(jsons[i])
|
||||||
|
).catch((e: Error) => console.error(e))
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
response
|
||||||
|
.setStatusCode(500)
|
||||||
|
.appendHeader('Content-Type', 'application/json')
|
||||||
|
.setBody({ err: e, event });
|
||||||
|
return callback(null, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
let timer = setTimeout(() => {
|
||||||
|
console.log("Ungraceful finish: running out of time");
|
||||||
|
callback(null, response);
|
||||||
|
}, 9500);
|
||||||
|
|
||||||
|
await Promise.all(promises);
|
||||||
|
clearTimeout(timer);
|
||||||
|
return callback(null, response);
|
||||||
|
};
|
||||||
68
packages/doorman-api/src/functions/api/door/status.ts
Normal file
68
packages/doorman-api/src/functions/api/door/status.ts
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
/**
|
||||||
|
* Try to update door status
|
||||||
|
*/
|
||||||
|
|
||||||
|
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 { DoorStatus } from "../../../types/DoorStatus";
|
||||||
|
|
||||||
|
export interface StatusRequest extends ServerlessEventObject {
|
||||||
|
door: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const handler: ServerlessFunctionSignature<TwilioContext, StatusRequest> = async function(context, event, callback) {
|
||||||
|
const response = new Twilio.Response();
|
||||||
|
|
||||||
|
if (shouldBlockRequest(event)) {
|
||||||
|
response.setStatusCode(200);
|
||||||
|
return callback(null, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
const door = event.door;
|
||||||
|
|
||||||
|
if (!door) {
|
||||||
|
response.setStatusCode(400);
|
||||||
|
return callback(null, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
const client = createDDBClient(context);
|
||||||
|
|
||||||
|
await client.send(getLockStatusCommand(door))
|
||||||
|
.then(async (lock) => {
|
||||||
|
const isOpen = isLockOpen(lock);
|
||||||
|
|
||||||
|
if (isOpen) {
|
||||||
|
const fingerprint = JSON.parse(lock?.Item?.fingerprint.S as string);
|
||||||
|
|
||||||
|
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 client.destroy();
|
||||||
|
return callback(null, response);
|
||||||
|
};
|
||||||
23
packages/doorman-api/src/index.ts
Normal file
23
packages/doorman-api/src/index.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { readdirSync } from "node:fs";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
|
const paths = [
|
||||||
|
'./src/functions/api/door'
|
||||||
|
];
|
||||||
|
|
||||||
|
const functionFiles = paths.map(path => readdirSync(path).map(file => path + "/" + file)).flat();
|
||||||
|
|
||||||
|
// for hot reload to work, we import all the files we want to build
|
||||||
|
const imports = functionFiles.forEach(file => require('./' + path.relative('src', file)));
|
||||||
|
|
||||||
|
console.log("functions to build:", functionFiles);
|
||||||
|
|
||||||
|
console.log("Building functions...");
|
||||||
|
await Bun.build({
|
||||||
|
entrypoints: functionFiles,
|
||||||
|
outdir: './build/functions',
|
||||||
|
packages: 'external',
|
||||||
|
target: 'node',
|
||||||
|
root: './src/functions',
|
||||||
|
format: 'cjs',
|
||||||
|
});
|
||||||
4
packages/doorman-api/src/types/AuthMethod.ts
Normal file
4
packages/doorman-api/src/types/AuthMethod.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export enum AuthMethod {
|
||||||
|
PIN = "PIN",
|
||||||
|
DISCORD = "DISCORD",
|
||||||
|
}
|
||||||
14
packages/doorman-api/src/types/DoorConfig.ts
Normal file
14
packages/doorman-api/src/types/DoorConfig.ts
Normal 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;
|
||||||
|
}
|
||||||
4
packages/doorman-api/src/types/DoorStatus.ts
Normal file
4
packages/doorman-api/src/types/DoorStatus.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export enum DoorStatus {
|
||||||
|
OPEN = "OPEN",
|
||||||
|
CLOSED = "CLOSED",
|
||||||
|
}
|
||||||
3
packages/doorman-api/src/types/Lock.ts
Normal file
3
packages/doorman-api/src/types/Lock.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export interface Lock {
|
||||||
|
fingerprint: any;
|
||||||
|
}
|
||||||
7
packages/doorman-api/src/types/TwilioContext.ts
Normal file
7
packages/doorman-api/src/types/TwilioContext.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { EnvironmentVariables } from "@twilio-labs/serverless-runtime-types/types";
|
||||||
|
|
||||||
|
export interface TwilioContext extends EnvironmentVariables {
|
||||||
|
AWS_ACCESS_KEY: string;
|
||||||
|
AWS_SECRET_ACCESS_KEY: string;
|
||||||
|
DISCORD_BOT_TOKEN: string;
|
||||||
|
}
|
||||||
@ -1,8 +1,12 @@
|
|||||||
|
import { ServerlessEventObject } from "@twilio-labs/serverless-runtime-types/types";
|
||||||
|
|
||||||
|
export type UserAgentHeader = { "user-agent"?: string };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper method to BLOCK discordbot from scraping API links
|
* Helper method to BLOCK discordbot from scraping API links
|
||||||
* This is a bit of a hack until we process event links from UI instead of raw API
|
* This is a bit of a hack until we process event links from UI instead of raw API
|
||||||
*/
|
*/
|
||||||
exports.shouldBlockRequest = (event) => {
|
export function shouldBlockRequest(event: ServerlessEventObject<{}, UserAgentHeader>): boolean {
|
||||||
let headers = event?.request?.headers;
|
let headers = event?.request?.headers;
|
||||||
let userAgentString = "";
|
let userAgentString = "";
|
||||||
|
|
||||||
@ -1,7 +1,9 @@
|
|||||||
const { randomUUID } = require("crypto");
|
import { randomUUID } from "crypto";
|
||||||
const { DynamoDBClient, GetItemCommand, DeleteItemCommand, PutItemCommand, UpdateItemCommand } = require("@aws-sdk/client-dynamodb");
|
import { DynamoDBClient, GetItemCommand, DeleteItemCommand, PutItemCommand, UpdateItemCommand, GetItemOutput } from "@aws-sdk/client-dynamodb";
|
||||||
|
import { TwilioContext } from "../types/TwilioContext";
|
||||||
|
import { DoorConfig } from "../types/DoorConfig";
|
||||||
|
|
||||||
exports.createDDBClient = (context) => {
|
export const createDDBClient = (context: TwilioContext) => {
|
||||||
return new DynamoDBClient({
|
return new DynamoDBClient({
|
||||||
region: "us-east-1" ,
|
region: "us-east-1" ,
|
||||||
credentials: {
|
credentials: {
|
||||||
@ -11,7 +13,7 @@ exports.createDDBClient = (context) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.getLockStatusCommand = (door) => {
|
export const getLockStatusCommand = (door: string) => {
|
||||||
return new GetItemCommand({
|
return new GetItemCommand({
|
||||||
TableName: "doorman",
|
TableName: "doorman",
|
||||||
Key: {
|
Key: {
|
||||||
@ -25,7 +27,7 @@ exports.getLockStatusCommand = (door) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.getDoorAliasCommand = (buzzerNumber) => {
|
export const getDoorAliasCommand = (buzzerNumber: string) => {
|
||||||
return new GetItemCommand({
|
return new GetItemCommand({
|
||||||
TableName: "doorman",
|
TableName: "doorman",
|
||||||
Key: {
|
Key: {
|
||||||
@ -39,7 +41,7 @@ exports.getDoorAliasCommand = (buzzerNumber) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.getDoorConfigCommand = (door) => {
|
export const getDoorConfigCommand = (door: string) => {
|
||||||
return new GetItemCommand({
|
return new GetItemCommand({
|
||||||
TableName: "doorman",
|
TableName: "doorman",
|
||||||
Key: {
|
Key: {
|
||||||
@ -53,18 +55,19 @@ exports.getDoorConfigCommand = (door) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.isLockOpen = (lock) => {
|
export const isLockOpen = (lock: GetItemOutput) => {
|
||||||
// ttl is a UTC ms time for how long it is unlocked
|
// ttl is a UTC ms time for how long it is unlocked
|
||||||
const ttl = lock.Item?.TTL?.N || 0;
|
const ttl = lock.Item?.TTL?.N || 0;
|
||||||
return ttl > Date.now();
|
|
||||||
|
return parseInt("" + ttl) > Date.now();
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.clearLockStatusCommand = (lock) => {
|
export const clearLockStatusCommand = (lock: GetItemOutput) => {
|
||||||
return new DeleteItemCommand({
|
return new DeleteItemCommand({
|
||||||
TableName: "doorman",
|
TableName: "doorman",
|
||||||
Key: {
|
Key: {
|
||||||
"PK": {
|
"PK": {
|
||||||
S: lock.Item.PK.S,
|
S: lock?.Item?.PK.S as string,
|
||||||
},
|
},
|
||||||
"SK": {
|
"SK": {
|
||||||
S: "lock",
|
S: "lock",
|
||||||
@ -73,7 +76,7 @@ exports.clearLockStatusCommand = (lock) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.setLockStatusCommand = (door, timeoutSeconds, fingerprintObj) => {
|
export const setLockStatusCommand = (door: string, timeoutSeconds: number, fingerprintObj: any) => {
|
||||||
return new PutItemCommand({
|
return new PutItemCommand({
|
||||||
TableName: "doorman",
|
TableName: "doorman",
|
||||||
Item: {
|
Item: {
|
||||||
@ -93,7 +96,7 @@ exports.setLockStatusCommand = (door, timeoutSeconds, fingerprintObj) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.putDoorUpdateConfigCommand = (door, config) => {
|
export const putDoorUpdateConfigCommand = (door: string, config: DoorConfig) => {
|
||||||
return new PutItemCommand({
|
return new PutItemCommand({
|
||||||
TableName: "doorman",
|
TableName: "doorman",
|
||||||
Item: {
|
Item: {
|
||||||
@ -134,21 +137,22 @@ exports.putDoorUpdateConfigCommand = (door, config) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.getDoorConfigCommand = (door) => {
|
export function ddbItemToJSON<T extends Record<string, any>>(ddbItem: GetItemOutput): T {
|
||||||
return new GetItemCommand({
|
let obj: any = {};
|
||||||
TableName: "doorman",
|
|
||||||
Key: {
|
if (!ddbItem.Item) {
|
||||||
"PK": {
|
return obj;
|
||||||
S: `door-${door}`,
|
}
|
||||||
},
|
|
||||||
"SK": {
|
const Item = ddbItem.Item;
|
||||||
S: "config",
|
|
||||||
},
|
Object.keys(Item).forEach(key => {
|
||||||
},
|
obj[key] = Object.values(Item[key])[0];
|
||||||
});
|
});
|
||||||
|
return obj;
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.getDoorConfigUpdateCommand = (door) => {
|
export const getDoorConfigUpdateCommand = (door: string) => {
|
||||||
return new GetItemCommand({
|
return new GetItemCommand({
|
||||||
TableName: "doorman",
|
TableName: "doorman",
|
||||||
Key: {
|
Key: {
|
||||||
@ -162,7 +166,7 @@ exports.getDoorConfigUpdateCommand = (door) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.replaceDoorConfigWithUpdateItem = (newConfigItem) => {
|
export const replaceDoorConfigWithUpdateItem = (newConfigItem: GetItemOutput & { Item: { approvalId?: { S: string } }}) => {
|
||||||
const newItem = {
|
const newItem = {
|
||||||
...newConfigItem.Item,
|
...newConfigItem.Item,
|
||||||
SK: { S: "config" },
|
SK: { S: "config" },
|
||||||
@ -2,13 +2,13 @@
|
|||||||
// https://gitea.chromart.cc/martin/refbounty/src/commit/1f40d7870a530fe7e7acbc3b901024092c9fb90a/packages/server/src/connectors/DiscordConnector.ts
|
// https://gitea.chromart.cc/martin/refbounty/src/commit/1f40d7870a530fe7e7acbc3b901024092c9fb90a/packages/server/src/connectors/DiscordConnector.ts
|
||||||
// https://gitea.chromart.cc/martin/refbounty/src/commit/1f40d7870a530fe7e7acbc3b901024092c9fb90a/packages/server/src/utils/DiscordUtils.ts
|
// https://gitea.chromart.cc/martin/refbounty/src/commit/1f40d7870a530fe7e7acbc3b901024092c9fb90a/packages/server/src/utils/DiscordUtils.ts
|
||||||
|
|
||||||
const { Client, GatewayIntentBits } = require("discord.js");
|
import { Client, GatewayIntentBits, User } from "discord.js";
|
||||||
|
import { TwilioContext } from "../types/TwilioContext";
|
||||||
|
|
||||||
// TODO: cache these at top level in handler code
|
let conn: Client;
|
||||||
let conn;
|
let userCache: Record<string, User> = {};
|
||||||
let userCache = {};
|
|
||||||
|
|
||||||
exports.getDiscordClient = async (context) => {
|
export const getDiscordClient = async (context: TwilioContext) => {
|
||||||
if (!conn) {
|
if (!conn) {
|
||||||
console.log("[DiscordClientCache] cache miss for discord");
|
console.log("[DiscordClientCache] cache miss for discord");
|
||||||
const client = new Client({
|
const client = new Client({
|
||||||
@ -28,13 +28,13 @@ exports.getDiscordClient = async (context) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
exports.sendMessageToUser = async (
|
export const sendMessageToUser = async (
|
||||||
context,
|
context: TwilioContext,
|
||||||
userId,
|
userId: string,
|
||||||
msg,
|
msg: string,
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
const client = await exports.getDiscordClient(context);
|
const client = await getDiscordClient(context);
|
||||||
if (userCache[userId] === undefined) {
|
if (userCache[userId] === undefined) {
|
||||||
console.log("[UserCache] cache miss for", userId);
|
console.log("[UserCache] cache miss for", userId);
|
||||||
userCache[userId] = await client.users.fetch(userId);
|
userCache[userId] = await client.users.fetch(userId);
|
||||||
@ -47,4 +47,16 @@ exports.sendMessageToUser = async (
|
|||||||
console.log(e);
|
console.log(e);
|
||||||
console.log(`Failed to send msg to ${userId}`);
|
console.log(`Failed to send msg to ${userId}`);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const jsonMsgSuffix = (jsonString: string = "") => {
|
||||||
|
if (!jsonString) {
|
||||||
|
return "";
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
|
const fingerprint = JSON.parse(jsonString);
|
||||||
|
return `\`\`\`# Unlocked by\n${JSON.stringify(fingerprint, null, 4)}\`\`\``;
|
||||||
|
} catch (e) {
|
||||||
|
return `\`\`\`# Unlocked by\n# WARN: Unknown or corrupt raw fingerprint:\n ${jsonString}\`\`\``;
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -2,7 +2,7 @@ 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 * as ddb from "../../doorman-api/functions/common/ddb.private";
|
import * as ddb from "../../doorman-api/deprecated-functions/common/ddb.private";
|
||||||
|
|
||||||
const app = new Hono();
|
const app = new Hono();
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user