257 lines
7.0 KiB
TypeScript
257 lines
7.0 KiB
TypeScript
import * as mongodb from "mongodb";
|
|
import * as crypto from "crypto";
|
|
export namespace numbert {
|
|
interface calleesFilter {
|
|
caller: string;
|
|
}
|
|
namespace utils {
|
|
export function now(): number {
|
|
return Math.floor(Date.now() / 1000);
|
|
}
|
|
export function hashPwd(pwd: string): string {
|
|
return crypto
|
|
.createHash("sha512")
|
|
.setEncoding("ascii")
|
|
.update(pwd)
|
|
.digest("base64")
|
|
.toString();
|
|
}
|
|
export function generateStaticID(): string {
|
|
return Buffer.concat([
|
|
Buffer.from(process.hrtime()[1].toString(16), "hex"),
|
|
require("crypto").randomBytes(8),
|
|
]).toString("hex");
|
|
}
|
|
}
|
|
export class API {
|
|
callersCollection: mongodb.Collection;
|
|
calleesCollection: mongodb.Collection;
|
|
constructor(
|
|
callersCollection: mongodb.Collection,
|
|
calleesCollection: mongodb.Collection
|
|
) {
|
|
this.callersCollection = callersCollection;
|
|
this.calleesCollection = calleesCollection;
|
|
}
|
|
async init() {
|
|
await this.calleesCollection.createIndex(
|
|
{ caller: 1, code: -1 },
|
|
{
|
|
unique: true,
|
|
}
|
|
);
|
|
await this.calleesCollection.createIndex(
|
|
{ caller: 1, callAt: -1 },
|
|
{
|
|
unique: true,
|
|
}
|
|
);
|
|
await this.callersCollection.createIndex(
|
|
{ dynamicID: 1 },
|
|
{
|
|
unique: true,
|
|
partialFilterExpression: { dynamicID: { $exists: true } },
|
|
}
|
|
);
|
|
await this.callersCollection.createIndex(
|
|
{ staticID: 1 },
|
|
{
|
|
unique: true,
|
|
partialFilterExpression: { staticID: { $exists: true } },
|
|
}
|
|
);
|
|
await this.callersCollection.createIndex(
|
|
{ token: 1 },
|
|
{
|
|
unique: true,
|
|
partialFilterExpression: { staticID: { $exists: true } },
|
|
}
|
|
);
|
|
}
|
|
async registerCaller(
|
|
pwd: string,
|
|
dynamicID: string
|
|
): Promise<{ staticID: string }> {
|
|
const staticID = utils.generateStaticID();
|
|
pwd = utils.hashPwd(pwd);
|
|
let { insertedCount } = await this.callersCollection.insertOne({
|
|
pwd,
|
|
dynamicID,
|
|
staticID,
|
|
registerAt: utils.now(),
|
|
});
|
|
if (insertedCount === 0) {
|
|
throw new Error("insertaion failed");
|
|
}
|
|
return { staticID };
|
|
}
|
|
|
|
async updateCallerToken(staticID: string, pwd: string, token: string) {
|
|
let tokenAsBinary = new mongodb.Binary(Buffer.from(token, "base64"));
|
|
pwd = utils.hashPwd(pwd);
|
|
let { matchedCount } = await this.callersCollection.updateOne(
|
|
{ staticID, pwd },
|
|
{ $set: { token: tokenAsBinary, lastTokenAt: utils.now() } }
|
|
);
|
|
if (matchedCount === 0) {
|
|
throw new Error("auth failed");
|
|
}
|
|
}
|
|
async dropCallerToken(token: string) {
|
|
let tokenAsBinary = new mongodb.Binary(Buffer.from(token, "base64"));
|
|
let { matchedCount } = await this.callersCollection.updateOne(
|
|
{ token: tokenAsBinary },
|
|
{ $unset: { token: "" } }
|
|
);
|
|
if (matchedCount === 0) {
|
|
throw new Error("auth failed");
|
|
}
|
|
}
|
|
async updateCallerPwd(token: string, currentPwd: string, newPwd: string) {
|
|
currentPwd = utils.hashPwd(currentPwd);
|
|
newPwd = utils.hashPwd(newPwd);
|
|
|
|
let tokenAsBinary = new mongodb.Binary(Buffer.from(token, "base64"));
|
|
let { matchedCount } = await this.callersCollection.updateOne(
|
|
{ token: tokenAsBinary, pwd: currentPwd },
|
|
{ $set: { pwd: newPwd } }
|
|
);
|
|
if (matchedCount === 0) {
|
|
throw new Error("auth failed");
|
|
}
|
|
}
|
|
async patchCaller(
|
|
token: string,
|
|
patch: {
|
|
dynamicID?: string;
|
|
title?: string;
|
|
desc?: string;
|
|
menu?: {
|
|
title: string;
|
|
items: {
|
|
title: string;
|
|
desc: string;
|
|
price: number;
|
|
}[];
|
|
}[];
|
|
}
|
|
) {
|
|
let tokenAsBinary = new mongodb.Binary(Buffer.from(token, "base64"));
|
|
let { matchedCount } = await this.callersCollection.updateOne(
|
|
{ token: tokenAsBinary },
|
|
{ $set: patch }
|
|
);
|
|
if (matchedCount === 0) {
|
|
throw new Error("auth failed");
|
|
}
|
|
}
|
|
async resolveCallerDynamicID(
|
|
dynamicID: string
|
|
): Promise<{ staticID: String }> {
|
|
let foundCaller = await this.callersCollection.findOne(
|
|
{ dynamicID },
|
|
{ projection: { _id: 0, staticID: 1 } }
|
|
);
|
|
if (!foundCaller) throw new Error("not found");
|
|
return { staticID: foundCaller.staticID.toString() };
|
|
}
|
|
async resolveCallerToken(
|
|
token: string
|
|
): Promise<{
|
|
staticID: string;
|
|
}> {
|
|
let tokenAsBinary = new mongodb.Binary(Buffer.from(token, "base64"));
|
|
|
|
let foundCaller = await this.callersCollection.findOne(
|
|
{ token: tokenAsBinary },
|
|
{ projection: { _id: 0, staticID: 1 } }
|
|
);
|
|
if (!foundCaller) throw new Error("not found");
|
|
|
|
return foundCaller;
|
|
}
|
|
async getCaller(
|
|
staticID: string
|
|
): Promise<{
|
|
dynamicID?: string;
|
|
title?: string;
|
|
desc?: string;
|
|
menu?: {
|
|
title: string;
|
|
items: {
|
|
title: string;
|
|
desc: string;
|
|
price: number;
|
|
}[];
|
|
}[];
|
|
}> {
|
|
let foundCaller = await this.callersCollection.findOne(
|
|
{ staticID },
|
|
{ projection: { _id: 0, dynamicID: 1, title: 1, desc: 1, menu: 1 } }
|
|
);
|
|
if (!foundCaller) throw new Error("not found");
|
|
|
|
return foundCaller;
|
|
}
|
|
async upsertCall(
|
|
token: string,
|
|
call: {
|
|
msg: string;
|
|
code: string;
|
|
}
|
|
) {
|
|
let { staticID: caller } = await this.resolveCallerToken(token);
|
|
await this.calleesCollection.updateOne(
|
|
{ caller, code: call.code },
|
|
{
|
|
$set: { msg: call.msg, callAt: utils.now() },
|
|
$setOnInsert: { caller, code: call.code },
|
|
},
|
|
{ upsert: true }
|
|
);
|
|
}
|
|
async listCallees(
|
|
filter: calleesFilter,
|
|
sort: [string, number][],
|
|
limit: number,
|
|
skip: number
|
|
): Promise<{ msg: string; code: string; callAt: number }[]> {
|
|
const cursor = this.calleesCollection.find(filter, {
|
|
skip,
|
|
limit,
|
|
sort,
|
|
maxTimeMS: 5 * 1000,
|
|
projection: {
|
|
_id: 0,
|
|
msg: 1,
|
|
code: 1,
|
|
callAt: 1,
|
|
},
|
|
});
|
|
const results = await cursor.toArray();
|
|
await cursor.close();
|
|
return results;
|
|
}
|
|
async purgeCallees(
|
|
filter: calleesFilter
|
|
): Promise<{ deletedCount: number }> {
|
|
let { deletedCount } = await this.calleesCollection.deleteMany(filter);
|
|
deletedCount = deletedCount || 0;
|
|
return { deletedCount };
|
|
}
|
|
async getCall(
|
|
callerStaticID: string,
|
|
code: string
|
|
): Promise<{ msg: string; callAt: number }> {
|
|
let foundCallee = await this.calleesCollection.findOne(
|
|
{ caller: callerStaticID, code },
|
|
{ projection: { _id: 0, msg: 1, callAt: 1 } }
|
|
);
|
|
if (!foundCallee) {
|
|
throw new Error("not found");
|
|
}
|
|
return foundCallee;
|
|
}
|
|
}
|
|
}
|