numbert/numbert.ts
2020-09-30 21:04:35 +03:30

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;
}
}
}