metamigo-api: work on making it build
This commit is contained in:
parent
38e68852d9
commit
ef216f7b1c
35 changed files with 407 additions and 322 deletions
|
|
@ -2,25 +2,11 @@ require("eslint-config-link/patch/modern-module-resolution");
|
|||
module.exports = {
|
||||
extends: [
|
||||
"eslint-config-link/profile/node",
|
||||
"eslint-config-link/eslint-config-amigo/profile/typescript",
|
||||
"eslint-config-link/eslint-config-amigo/profile/jest",
|
||||
"eslint-config-link/profile/typescript",
|
||||
"eslint-config-link/profile/jest",
|
||||
],
|
||||
parserOptions: { tsconfigRootDir: __dirname },
|
||||
rules: {
|
||||
"new-cap": "off",
|
||||
"import/no-extraneous-dependencies": [
|
||||
// enable this when this is fixed
|
||||
// https://github.com/benmosher/eslint-plugin-import/pull/1696
|
||||
"off",
|
||||
{
|
||||
packageDir: [
|
||||
".",
|
||||
"node_modules/@digiresilience/amigo",
|
||||
"node_modules/@digiresilience/amigo-dev",
|
||||
],
|
||||
},
|
||||
],
|
||||
// TODO: enable this after jest fixes this issue https://github.com/nodejs/node/issues/38343
|
||||
"unicorn/prefer-node-protocol": "off"
|
||||
"new-cap": "off"
|
||||
},
|
||||
};
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
import type * as Hapi from "@hapi/hapi";
|
||||
import Schmervice from "@hapipal/schmervice";
|
||||
import PgPromisePlugin from "@digiresilience/hapi-pg-promise";
|
||||
|
||||
import type { IAppConfig } from "../../config";
|
||||
import { dbInitOptions } from "@digiresilience/metamigo-db";
|
||||
import { registerNextAuth } from "./hapi-nextauth";
|
||||
import { registerSwagger } from "./swagger";
|
||||
import { registerNextAuthJwt } from "./nextauth-jwt";
|
||||
import { registerCloudflareAccessJwt } from "./cloudflare-jwt";
|
||||
|
||||
export const register = async (
|
||||
server: Hapi.Server,
|
||||
config: IAppConfig
|
||||
): Promise<void> => {
|
||||
await server.register(Schmervice);
|
||||
|
||||
await server.register({
|
||||
plugin: PgPromisePlugin,
|
||||
options: {
|
||||
// the only required parameter is the connection string
|
||||
connection: config.db.connection,
|
||||
// ... and the pg-promise initialization options
|
||||
pgpInit: dbInitOptions(config),
|
||||
},
|
||||
});
|
||||
|
||||
await registerNextAuth(server, config);
|
||||
await registerSwagger(server);
|
||||
await registerNextAuthJwt(server, config);
|
||||
await registerCloudflareAccessJwt(server, config);
|
||||
};
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
import config, {
|
||||
loadConfig,
|
||||
loadConfigRaw,
|
||||
IAppConfig,
|
||||
IAppConvict,
|
||||
} from "config";
|
||||
|
||||
export { IAppConvict, IAppConfig, loadConfig, loadConfigRaw };
|
||||
|
||||
export default config;
|
||||
|
|
@ -37,7 +37,8 @@
|
|||
"pg-promise": "^11.0.2",
|
||||
"postgraphile-plugin-connection-filter": "^2.3.0",
|
||||
"remeda": "^1.6.0",
|
||||
"twilio": "^3.84.1"
|
||||
"twilio": "^3.84.1",
|
||||
"expiry-map": "^2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"pino-pretty": "^9.1.1",
|
||||
|
|
|
|||
|
|
@ -7,10 +7,10 @@ import * as Plugins from "./plugins";
|
|||
|
||||
const AppPlugin = {
|
||||
name: "App",
|
||||
register: async (
|
||||
async register(
|
||||
server: Hapi.Server,
|
||||
options: { config: IAppConfig }
|
||||
): Promise<void> => {
|
||||
): Promise<void> {
|
||||
// declare our **run-time** plugin dependencies
|
||||
// these are runtime only deps, not registration time
|
||||
// ref: https://hapipal.com/best-practices/handling-plugin-dependencies
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { Boom } from "@hapi/boom";
|
||||
import { Server } from "@hapi/hapi";
|
||||
import { randomBytes } from "crypto";
|
||||
import { randomBytes } from "node:crypto";
|
||||
import type { Logger } from "pino";
|
||||
import {
|
||||
proto,
|
||||
|
|
@ -45,43 +45,38 @@ export const addTransactionCapability = (
|
|||
? ids.filter((item) => !(item in dict))
|
||||
: ids;
|
||||
// only fetch if there are any items to fetch
|
||||
if (idsRequiringFetch.length) {
|
||||
if (idsRequiringFetch.length > 0) {
|
||||
const result = await state.get(type, idsRequiringFetch);
|
||||
|
||||
transactionCache[type] = transactionCache[type] || {};
|
||||
// @ts-expect-error
|
||||
Object.assign(transactionCache[type], result);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
get: async (type, ids) => {
|
||||
async get(type, ids) {
|
||||
if (inTransaction) {
|
||||
await prefetch(type, ids);
|
||||
return ids.reduce((dict, id) => {
|
||||
const value = transactionCache[type]?.[id];
|
||||
if (value) {
|
||||
// @ts-expect-error
|
||||
dict[id] = value;
|
||||
}
|
||||
|
||||
return dict;
|
||||
}, {});
|
||||
} else {
|
||||
return state.get(type, ids);
|
||||
}
|
||||
|
||||
return state.get(type, ids);
|
||||
|
||||
},
|
||||
set: (data) => {
|
||||
set(data) {
|
||||
if (inTransaction) {
|
||||
logger.trace({ types: Object.keys(data) }, "caching in transaction");
|
||||
for (const key in data) {
|
||||
// @ts-expect-error
|
||||
transactionCache[key] = transactionCache[key] || {};
|
||||
// @ts-expect-error
|
||||
Object.assign(transactionCache[key], data[key]);
|
||||
// @ts-expect-error
|
||||
mutations[key] = mutations[key] || {};
|
||||
// @ts-expect-error
|
||||
Object.assign(mutations[key], data[key]);
|
||||
}
|
||||
} else {
|
||||
|
|
@ -90,11 +85,11 @@ export const addTransactionCapability = (
|
|||
},
|
||||
isInTransaction: () => inTransaction,
|
||||
// @ts-expect-error
|
||||
prefetch: (type, ids) => {
|
||||
prefetch(type, ids) {
|
||||
logger.trace({ type, ids }, "prefetching");
|
||||
return prefetch(type, ids);
|
||||
},
|
||||
transaction: async (work) => {
|
||||
async transaction(work) {
|
||||
if (inTransaction) {
|
||||
await work();
|
||||
} else {
|
||||
|
|
@ -102,7 +97,7 @@ export const addTransactionCapability = (
|
|||
inTransaction = true;
|
||||
try {
|
||||
await work();
|
||||
if (Object.keys(mutations).length) {
|
||||
if (Object.keys(mutations).length > 0) {
|
||||
logger.debug("committing transaction");
|
||||
await state.set(mutations);
|
||||
} else {
|
||||
|
|
@ -140,7 +135,7 @@ export const useDatabaseAuthState = (
|
|||
bot: Bot,
|
||||
server: Server
|
||||
): { state: AuthenticationState; saveState: () => void } => {
|
||||
let { logger }: any = server;
|
||||
const { logger }: any = server;
|
||||
let creds: AuthenticationCreds;
|
||||
let keys: any = {};
|
||||
|
||||
|
|
@ -165,7 +160,7 @@ export const useDatabaseAuthState = (
|
|||
state: {
|
||||
creds,
|
||||
keys: {
|
||||
get: (type, ids) => {
|
||||
get(type, ids) {
|
||||
const key = KEY_MAP[type];
|
||||
return ids.reduce((dict, id) => {
|
||||
let value = keys[key]?.[id];
|
||||
|
|
@ -174,18 +169,17 @@ export const useDatabaseAuthState = (
|
|||
// @ts-expect-error
|
||||
value = proto.AppStateSyncKeyData.fromObject(value);
|
||||
}
|
||||
// @ts-expect-error
|
||||
|
||||
dict[id] = value;
|
||||
}
|
||||
|
||||
return dict;
|
||||
}, {});
|
||||
},
|
||||
set: (data) => {
|
||||
set(data) {
|
||||
for (const _key in data) {
|
||||
const key = KEY_MAP[_key as keyof SignalDataTypeMap];
|
||||
keys[key] = keys[key] || {};
|
||||
// @ts-expect-error
|
||||
Object.assign(keys[key], data[_key]);
|
||||
}
|
||||
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import * as Boom from "@hapi/boom";
|
||||
import * as Hoek from "@hapi/hoek";
|
||||
import * as Hapi from "@hapi/hapi";
|
||||
import { promisify } from "util";
|
||||
import { promisify } from "node:util";
|
||||
import jwt from "jsonwebtoken";
|
||||
import jwksClient, { hapiJwt2KeyAsync } from "jwks-rsa";
|
||||
import type { IAppConfig } from "../../config";
|
||||
|
|
@ -36,22 +36,20 @@ const verifyToken = (settings: any) => {
|
|||
};
|
||||
};
|
||||
|
||||
const handleCfJwt = (verify: any) => async (
|
||||
request: Hapi.Request,
|
||||
h: Hapi.ResponseToolkit
|
||||
) => {
|
||||
const token = request.headers[CF_JWT_HEADER_NAME];
|
||||
if (token) {
|
||||
try {
|
||||
await verify(token);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return Boom.unauthorized("invalid cloudflare access token");
|
||||
const handleCfJwt =
|
||||
(verify: any) => async (request: Hapi.Request, h: Hapi.ResponseToolkit) => {
|
||||
const token = request.headers[CF_JWT_HEADER_NAME];
|
||||
if (token) {
|
||||
try {
|
||||
await verify(token);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return Boom.unauthorized("invalid cloudflare access token");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return h.continue;
|
||||
};
|
||||
return h.continue;
|
||||
};
|
||||
|
||||
const defaultOpts = {
|
||||
issuer: undefined,
|
||||
|
|
@ -60,7 +58,10 @@ const defaultOpts = {
|
|||
validate: undefined,
|
||||
};
|
||||
|
||||
const cfJwtRegister = async (server: Hapi.Server, options: any): Promise<void> => {
|
||||
const cfJwtRegister = async (
|
||||
server: Hapi.Server,
|
||||
options: any
|
||||
): Promise<void> => {
|
||||
server.dependency(["hapi-auth-jwt2"]);
|
||||
const settings = Hoek.applyToDefaults(defaultOpts, options);
|
||||
const verify = verifyToken(settings);
|
||||
|
|
@ -101,7 +102,7 @@ export const registerCloudflareAccessJwt = async (
|
|||
options: {
|
||||
issuer: `https://${domain}`,
|
||||
audience,
|
||||
validate: (decoded: any, _request: any) => {
|
||||
validate(decoded: any, _request: any) {
|
||||
const { email, name } = decoded;
|
||||
return {
|
||||
isValid: true,
|
||||
|
|
@ -1,8 +1,12 @@
|
|||
import type * as Hapi from "@hapi/hapi";
|
||||
import NextAuthPlugin, { AdapterFactory } from "@digiresilience/hapi-nextauth";
|
||||
import { NextAuthAdapter } from "@digiresilience/metamigo-common";
|
||||
import type { SavedUser, UnsavedUser, SavedSession } from "@digiresilience/metamigo-common";
|
||||
import { IAppConfig } from "config";
|
||||
import type {
|
||||
SavedUser,
|
||||
UnsavedUser,
|
||||
SavedSession,
|
||||
} from "@digiresilience/metamigo-common";
|
||||
import { IAppConfig } from "@digiresilience/metamigo-config";
|
||||
|
||||
export const registerNextAuth = async (
|
||||
server: Hapi.Server,
|
||||
40
apps/metamigo-api/src/app/plugins/index.ts
Normal file
40
apps/metamigo-api/src/app/plugins/index.ts
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
import type * as Hapi from "@hapi/hapi";
|
||||
import { ServerRegisterPluginObjectDirect } from "@hapi/hapi/lib/types/plugin";
|
||||
import type { IInitOptions, IDatabase } from "pg-promise";
|
||||
import Schmervice from "@hapipal/schmervice";
|
||||
import { makePlugin } from "@digiresilience/hapi-pg-promise";
|
||||
|
||||
import type { IAppConfig } from "../../config";
|
||||
import { dbInitOptions, IRepositories } from "@digiresilience/metamigo-db";
|
||||
import { registerNextAuth } from "./hapi-nextauth";
|
||||
import { registerSwagger } from "./swagger";
|
||||
import { registerNextAuthJwt } from "./nextauth-jwt";
|
||||
import { registerCloudflareAccessJwt } from "./cloudflare-jwt";
|
||||
import pg from "pg-promise/typescript/pg-subset";
|
||||
|
||||
export const register = async (
|
||||
server: Hapi.Server,
|
||||
config: IAppConfig
|
||||
): Promise<void> => {
|
||||
await server.register(Schmervice);
|
||||
|
||||
const pgpInit = dbInitOptions(config);
|
||||
const options = {
|
||||
// the only required parameter is the connection string
|
||||
connection: config.db.connection,
|
||||
// ... and the pg-promise initialization options
|
||||
pgpInit,
|
||||
};
|
||||
|
||||
await server.register([
|
||||
{
|
||||
plugin: makePlugin<IInitOptions<IRepositories, pg.IClient>>(),
|
||||
options,
|
||||
},
|
||||
]);
|
||||
|
||||
await registerNextAuth(server, config);
|
||||
await registerSwagger(server);
|
||||
await registerNextAuthJwt(server, config);
|
||||
await registerCloudflareAccessJwt(server, config);
|
||||
};
|
||||
|
|
@ -46,7 +46,7 @@ export const registerNextAuthJwt = async (
|
|||
},
|
||||
options: {
|
||||
jwkeysB64: [config.nextAuth.signingKey],
|
||||
validate: async (decoded, request: Hapi.Request) => {
|
||||
async validate(decoded, request: Hapi.Request) {
|
||||
const { email, name, role } = decoded;
|
||||
const user = await request.db().users.findBy({ email });
|
||||
if (!config.isProd) {
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
import isFunction from "lodash/isFunction";
|
||||
import type * as Hapi from "@hapi/hapi";
|
||||
import * as RandomRoutes from "./random";
|
||||
import * as UserRoutes from "./users";
|
||||
import * as VoiceRoutes from "./voice";
|
||||
import * as WhatsappRoutes from "./whatsapp";
|
||||
|
|
@ -25,7 +24,6 @@ export const register = async (server: Hapi.Server): Promise<void> => {
|
|||
// Load your routes here.
|
||||
// routes are loaded from the list of exported vars
|
||||
// a route file should export routes directly or an async function that returns the routes.
|
||||
loadRouteIndex(server, RandomRoutes);
|
||||
loadRouteIndex(server, UserRoutes);
|
||||
loadRouteIndex(server, VoiceRoutes);
|
||||
loadRouteIndex(server, WhatsappRoutes);
|
||||
|
|
@ -3,16 +3,14 @@ import * as Joi from "joi";
|
|||
import * as Helpers from "../helpers";
|
||||
import Boom from "boom";
|
||||
|
||||
const getSignalService = (request) => {
|
||||
return request.services().signaldService;
|
||||
};
|
||||
const getSignalService = (request) => request.services("app").signaldService;
|
||||
|
||||
export const GetAllSignalBotsRoute = Helpers.withDefaults({
|
||||
method: "get",
|
||||
path: "/api/signal/bots",
|
||||
options: {
|
||||
description: "Get all bots",
|
||||
handler: async (request: Hapi.Request, _h: Hapi.ResponseToolkit) => {
|
||||
async handler(request: Hapi.Request, _h: Hapi.ResponseToolkit) {
|
||||
const signalService = getSignalService(request);
|
||||
const bots = await signalService.findAll();
|
||||
|
||||
|
|
@ -35,7 +33,7 @@ export const GetBotsRoute = Helpers.noAuth({
|
|||
path: "/api/signal/bots/{token}",
|
||||
options: {
|
||||
description: "Get one bot",
|
||||
handler: async (request: Hapi.Request, _h: Hapi.ResponseToolkit) => {
|
||||
async handler(request: Hapi.Request, _h: Hapi.ResponseToolkit) {
|
||||
const { token } = request.params;
|
||||
const signalService = getSignalService(request);
|
||||
|
||||
|
|
@ -65,7 +63,7 @@ export const SendBotRoute = Helpers.noAuth({
|
|||
path: "/api/signal/bots/{token}/send",
|
||||
options: {
|
||||
description: "Send a message",
|
||||
handler: async (request: Hapi.Request, _h: Hapi.ResponseToolkit) => {
|
||||
async handler(request: Hapi.Request, _h: Hapi.ResponseToolkit) {
|
||||
const { token } = request.params;
|
||||
const { phoneNumber, message } = request.payload as MessageRequest;
|
||||
const signalService = getSignalService(request);
|
||||
|
|
@ -101,7 +99,7 @@ export const ResetSessionBotRoute = Helpers.noAuth({
|
|||
path: "/api/signal/bots/{token}/resetSession",
|
||||
options: {
|
||||
description: "Reset a session with another user",
|
||||
handler: async (request: Hapi.Request, _h: Hapi.ResponseToolkit) => {
|
||||
async handler(request: Hapi.Request, _h: Hapi.ResponseToolkit) {
|
||||
const { token } = request.params;
|
||||
const { phoneNumber } = request.payload as ResetSessionRequest;
|
||||
const signalService = getSignalService(request);
|
||||
|
|
@ -131,7 +129,7 @@ export const ReceiveBotRoute = Helpers.withDefaults({
|
|||
path: "/api/signal/bots/{token}/receive",
|
||||
options: {
|
||||
description: "Receive messages",
|
||||
handler: async (request: Hapi.Request, _h: Hapi.ResponseToolkit) => {
|
||||
async handler(request: Hapi.Request, _h: Hapi.ResponseToolkit) {
|
||||
const { token } = request.params;
|
||||
const signalService = getSignalService(request);
|
||||
|
||||
|
|
@ -153,7 +151,7 @@ export const RegisterBotRoute = Helpers.withDefaults({
|
|||
path: "/api/signal/bots/{id}/register",
|
||||
options: {
|
||||
description: "Register a bot",
|
||||
handler: async (request: Hapi.Request, h: Hapi.ResponseToolkit) => {
|
||||
async handler(request: Hapi.Request, h: Hapi.ResponseToolkit) {
|
||||
const { id } = request.params;
|
||||
const signalService = getSignalService(request);
|
||||
const { code } = request.query;
|
||||
|
|
@ -182,7 +180,7 @@ export const CreateBotRoute = Helpers.withDefaults({
|
|||
path: "/api/signal/bots",
|
||||
options: {
|
||||
description: "Register a bot",
|
||||
handler: async (request: Hapi.Request, _h: Hapi.ResponseToolkit) => {
|
||||
async handler(request: Hapi.Request, _h: Hapi.ResponseToolkit) {
|
||||
const { phoneNumber, description } = request.payload as BotRequest;
|
||||
const signalService = getSignalService(request);
|
||||
console.log("request.auth.credentials:", request.auth.credentials);
|
||||
|
|
@ -216,7 +214,7 @@ export const RequestCodeRoute = Helpers.withDefaults({
|
|||
captcha: Joi.string(),
|
||||
}),
|
||||
},
|
||||
handler: async (request: Hapi.Request, h: Hapi.ResponseToolkit) => {
|
||||
async handler(request: Hapi.Request, h: Hapi.ResponseToolkit) {
|
||||
const { id } = request.params;
|
||||
const { mode, captcha } = request.query;
|
||||
const signalService = getSignalService(request);
|
||||
|
|
@ -233,16 +231,20 @@ export const RequestCodeRoute = Helpers.withDefaults({
|
|||
} else if (mode === "voice") {
|
||||
await signalService.requestVoiceVerification(bot, captcha);
|
||||
}
|
||||
|
||||
return h.response().code(200);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
if (error.name === "CaptchaRequiredException") {
|
||||
return h.response().code(402);
|
||||
} else if (error.code) {
|
||||
return h.response().code(error.code);
|
||||
} else {
|
||||
return h.response().code(500);
|
||||
}
|
||||
|
||||
if (error.code) {
|
||||
return h.response().code(error.code);
|
||||
}
|
||||
|
||||
return h.response().code(500);
|
||||
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
@ -1,9 +1,13 @@
|
|||
import * as Joi from "joi";
|
||||
import * as Hapi from "@hapi/hapi";
|
||||
import { UserRecord, crudRoutesFor, CrudControllerBase } from "@digiresilience/metamigo-common";
|
||||
import {
|
||||
UserRecord,
|
||||
crudRoutesFor,
|
||||
CrudControllerBase,
|
||||
} from "@digiresilience/metamigo-common";
|
||||
import * as RouteHelpers from "../helpers";
|
||||
|
||||
class UserRecordController extends CrudControllerBase(UserRecord) { }
|
||||
class UserRecordController extends CrudControllerBase(UserRecord) {}
|
||||
|
||||
const validator = (): Record<string, Hapi.RouteOptionsValidate> => ({
|
||||
create: {
|
||||
|
|
@ -49,11 +53,12 @@ const validator = (): Record<string, Hapi.RouteOptionsValidate> => ({
|
|||
},
|
||||
});
|
||||
|
||||
export const UserRoutes = async (
|
||||
_server: Hapi.Server
|
||||
): Promise<Hapi.ServerRoute[]> => {
|
||||
const controller = new UserRecordController("users", "userId");
|
||||
return RouteHelpers.withDefaults(
|
||||
crudRoutesFor("user", "/api/users", controller, "userId", validator())
|
||||
);
|
||||
};
|
||||
export const UserRoutes = RouteHelpers.withDefaults(
|
||||
crudRoutesFor(
|
||||
"user",
|
||||
"/api/users",
|
||||
new UserRecordController("users", "userId"),
|
||||
"userId",
|
||||
validator()
|
||||
)
|
||||
);
|
||||
|
|
@ -4,11 +4,14 @@ import * as Boom from "@hapi/boom";
|
|||
import * as R from "remeda";
|
||||
import * as Helpers from "../helpers";
|
||||
import Twilio from "twilio";
|
||||
import { crudRoutesFor, CrudControllerBase } from "@digiresilience/metamigo-common";
|
||||
import {
|
||||
crudRoutesFor,
|
||||
CrudControllerBase,
|
||||
} from "@digiresilience/metamigo-common";
|
||||
import { VoiceLineRecord, SavedVoiceLine } from "@digiresilience/metamigo-db";
|
||||
|
||||
const TwilioHandlers = {
|
||||
freeNumbers: async (provider, request: Hapi.Request) => {
|
||||
async freeNumbers(provider, request: Hapi.Request) {
|
||||
const { accountSid, apiKeySid, apiKeySecret } = provider.credentials;
|
||||
const client = Twilio(apiKeySid, apiKeySecret, {
|
||||
accountSid,
|
||||
|
|
@ -44,23 +47,26 @@ export const VoiceProviderRoutes = Helpers.withDefaults([
|
|||
providerId: Joi.string().uuid().required(),
|
||||
},
|
||||
},
|
||||
handler: async (request: Hapi.Request, _h: Hapi.ResponseToolkit) => {
|
||||
async handler(request: Hapi.Request, _h: Hapi.ResponseToolkit) {
|
||||
const { providerId } = request.params;
|
||||
const voiceProvidersRepo = request.db().voiceProviders;
|
||||
const provider = await voiceProvidersRepo.findById(providerId);
|
||||
if (!provider) return Boom.notFound();
|
||||
switch (provider.kind) {
|
||||
case "TWILIO":
|
||||
case "TWILIO": {
|
||||
return TwilioHandlers.freeNumbers(provider, request);
|
||||
default:
|
||||
}
|
||||
|
||||
default: {
|
||||
return Boom.badImplementation();
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
class VoiceLineRecordController extends CrudControllerBase(VoiceLineRecord) { }
|
||||
class VoiceLineRecordController extends CrudControllerBase(VoiceLineRecord) {}
|
||||
|
||||
const validator = (): Record<string, Hapi.RouteOptionsValidate> => ({
|
||||
create: {
|
||||
|
|
@ -106,19 +112,14 @@ const validator = (): Record<string, Hapi.RouteOptionsValidate> => ({
|
|||
},
|
||||
});
|
||||
|
||||
export const VoiceLineRoutes = async (
|
||||
_server: Hapi.Server
|
||||
): Promise<Hapi.ServerRoute[]> => {
|
||||
const controller = new VoiceLineRecordController("voiceLines", "id");
|
||||
return Helpers.withDefaults(
|
||||
crudRoutesFor(
|
||||
"voice-line",
|
||||
"/api/voice/voice-line",
|
||||
controller,
|
||||
"id",
|
||||
validator()
|
||||
)
|
||||
);
|
||||
};
|
||||
export const VoiceLineRoutes = Helpers.withDefaults(
|
||||
crudRoutesFor(
|
||||
"voice-line",
|
||||
"/api/voice/voice-line",
|
||||
new VoiceLineRecordController("voiceLines", "id"),
|
||||
"id",
|
||||
validator()
|
||||
)
|
||||
);
|
||||
|
||||
export * from "./twilio";
|
||||
|
|
@ -4,14 +4,13 @@ import * as Boom from "@hapi/boom";
|
|||
import Twilio from "twilio";
|
||||
import { SavedVoiceProvider } from "@digiresilience/metamigo-db";
|
||||
import pMemoize from "p-memoize";
|
||||
import ExpiryMap from "expiry-map";
|
||||
import ms from "ms";
|
||||
import * as Helpers from "../../helpers";
|
||||
import workerUtils from "../../../../worker-utils";
|
||||
import { SayLanguage, SayVoice } from "twilio/lib/twiml/VoiceResponse";
|
||||
|
||||
const queueRecording = async (meta) => {
|
||||
return workerUtils.addJob("twilio-recording", meta, { jobKey: meta.callSid });
|
||||
};
|
||||
const queueRecording = async (meta) => workerUtils.addJob("twilio-recording", meta, { jobKey: meta.callSid });
|
||||
|
||||
const twilioClientFor = (provider: SavedVoiceProvider): Twilio.Twilio => {
|
||||
const { accountSid, apiKeySid, apiKeySecret } = provider.credentials;
|
||||
|
|
@ -43,8 +42,9 @@ const _getOrCreateTTSTestApplication = async (
|
|||
});
|
||||
};
|
||||
|
||||
const cache = new ExpiryMap(ms("1h"));
|
||||
const getOrCreateTTSTestApplication = pMemoize(_getOrCreateTTSTestApplication, {
|
||||
maxAge: ms("1h"),
|
||||
cache,
|
||||
});
|
||||
|
||||
export const TwilioRoutes = Helpers.noAuth([
|
||||
|
|
@ -58,7 +58,7 @@ export const TwilioRoutes = Helpers.noAuth([
|
|||
voiceLineId: Joi.string().uuid().required(),
|
||||
},
|
||||
},
|
||||
handler: async (request: Hapi.Request, h: Hapi.ResponseToolkit) => {
|
||||
async handler(request: Hapi.Request, h: Hapi.ResponseToolkit) {
|
||||
const { voiceLineId } = request.params;
|
||||
const voiceLine = await request
|
||||
.db()
|
||||
|
|
@ -89,7 +89,7 @@ export const TwilioRoutes = Helpers.noAuth([
|
|||
voiceLineId: Joi.string().uuid().required(),
|
||||
},
|
||||
},
|
||||
handler: async (request: Hapi.Request, _h: Hapi.ResponseToolkit) => {
|
||||
async handler(request: Hapi.Request, _h: Hapi.ResponseToolkit) {
|
||||
const { voiceLineId } = request.params;
|
||||
const { To } = request.payload as { To: string };
|
||||
const voiceLine = await request.db().voiceLines.findBy({ number: To });
|
||||
|
|
@ -136,7 +136,7 @@ export const TwilioRoutes = Helpers.noAuth([
|
|||
voiceLineId: Joi.string().uuid().required(),
|
||||
},
|
||||
},
|
||||
handler: async (request: Hapi.Request, h: Hapi.ResponseToolkit) => {
|
||||
async handler(request: Hapi.Request, h: Hapi.ResponseToolkit) {
|
||||
const { voiceLineId } = request.params;
|
||||
const voiceLine = await request
|
||||
.db()
|
||||
|
|
@ -169,7 +169,7 @@ export const TwilioRoutes = Helpers.noAuth([
|
|||
providerId: Joi.string().uuid().required(),
|
||||
},
|
||||
},
|
||||
handler: async (request: Hapi.Request, _h: Hapi.ResponseToolkit) => {
|
||||
async handler(request: Hapi.Request, _h: Hapi.ResponseToolkit) {
|
||||
const { language, voice, prompt } = request.payload as {
|
||||
language: SayLanguage;
|
||||
voice: SayVoice;
|
||||
|
|
@ -192,7 +192,7 @@ export const TwilioRoutes = Helpers.noAuth([
|
|||
providerId: Joi.string().uuid().required(),
|
||||
},
|
||||
},
|
||||
handler: async (request: Hapi.Request, h: Hapi.ResponseToolkit) => {
|
||||
async handler(request: Hapi.Request, h: Hapi.ResponseToolkit) {
|
||||
const { providerId } = request.params as { providerId: string };
|
||||
const provider: SavedVoiceProvider = await request
|
||||
.db()
|
||||
|
|
@ -7,8 +7,8 @@ export const GetAllWhatsappBotsRoute = Helpers.withDefaults({
|
|||
path: "/api/whatsapp/bots",
|
||||
options: {
|
||||
description: "Get all bots",
|
||||
handler: async (request: Hapi.Request, _h: Hapi.ResponseToolkit) => {
|
||||
const { whatsappService } = request.services();
|
||||
async handler(request: Hapi.Request, _h: Hapi.ResponseToolkit) {
|
||||
const { whatsappService } = request.services("app");
|
||||
|
||||
const bots = await whatsappService.findAll();
|
||||
|
||||
|
|
@ -31,9 +31,9 @@ export const GetBotsRoute = Helpers.noAuth({
|
|||
path: "/api/whatsapp/bots/{token}",
|
||||
options: {
|
||||
description: "Get one bot",
|
||||
handler: async (request: Hapi.Request, _h: Hapi.ResponseToolkit) => {
|
||||
async handler(request: Hapi.Request, _h: Hapi.ResponseToolkit) {
|
||||
const { token } = request.params;
|
||||
const { whatsappService } = request.services();
|
||||
const { whatsappService } = request.services("app");
|
||||
|
||||
const bot = await whatsappService.findByToken(token);
|
||||
|
||||
|
|
@ -61,10 +61,10 @@ export const SendBotRoute = Helpers.noAuth({
|
|||
path: "/api/whatsapp/bots/{token}/send",
|
||||
options: {
|
||||
description: "Send a message",
|
||||
handler: async (request: Hapi.Request, _h: Hapi.ResponseToolkit) => {
|
||||
async handler(request: Hapi.Request, _h: Hapi.ResponseToolkit) {
|
||||
const { token } = request.params;
|
||||
const { phoneNumber, message } = request.payload as MessageRequest;
|
||||
const { whatsappService } = request.services();
|
||||
const { whatsappService } = request.services("app");
|
||||
|
||||
const bot = await whatsappService.findByToken(token);
|
||||
|
||||
|
|
@ -93,9 +93,9 @@ export const ReceiveBotRoute = Helpers.withDefaults({
|
|||
path: "/api/whatsapp/bots/{token}/receive",
|
||||
options: {
|
||||
description: "Receive messages",
|
||||
handler: async (request: Hapi.Request, _h: Hapi.ResponseToolkit) => {
|
||||
async handler(request: Hapi.Request, _h: Hapi.ResponseToolkit) {
|
||||
const { token } = request.params;
|
||||
const { whatsappService } = request.services();
|
||||
const { whatsappService } = request.services("app");
|
||||
|
||||
const bot = await whatsappService.findByToken(token);
|
||||
|
||||
|
|
@ -119,9 +119,9 @@ export const RegisterBotRoute = Helpers.withDefaults({
|
|||
path: "/api/whatsapp/bots/{id}/register",
|
||||
options: {
|
||||
description: "Register a bot",
|
||||
handler: async (request: Hapi.Request, _h: Hapi.ResponseToolkit) => {
|
||||
async handler(request: Hapi.Request, _h: Hapi.ResponseToolkit) {
|
||||
const { id } = request.params;
|
||||
const { whatsappService } = request.services();
|
||||
const { whatsappService } = request.services("app");
|
||||
|
||||
const bot = await whatsappService.findById(id);
|
||||
|
||||
|
|
@ -146,9 +146,9 @@ export const RefreshBotRoute = Helpers.withDefaults({
|
|||
path: "/api/whatsapp/bots/{id}/refresh",
|
||||
options: {
|
||||
description: "Refresh messages",
|
||||
handler: async (request: Hapi.Request, _h: Hapi.ResponseToolkit) => {
|
||||
async handler(request: Hapi.Request, _h: Hapi.ResponseToolkit) {
|
||||
const { id } = request.params;
|
||||
const { whatsappService } = request.services();
|
||||
const { whatsappService } = request.services("app");
|
||||
|
||||
const bot = await whatsappService.findById(id);
|
||||
|
||||
|
|
@ -174,9 +174,9 @@ export const CreateBotRoute = Helpers.withDefaults({
|
|||
path: "/api/whatsapp/bots",
|
||||
options: {
|
||||
description: "Register a bot",
|
||||
handler: async (request: Hapi.Request, _h: Hapi.ResponseToolkit) => {
|
||||
async handler(request: Hapi.Request, _h: Hapi.ResponseToolkit) {
|
||||
const { phoneNumber, description } = request.payload as BotRequest;
|
||||
const { whatsappService } = request.services();
|
||||
const { whatsappService } = request.services("app");
|
||||
console.log("request.auth.credentials:", request.auth.credentials);
|
||||
|
||||
const bot = await whatsappService.create(
|
||||
|
|
@ -1,13 +1,11 @@
|
|||
import type * as Hapi from "@hapi/hapi";
|
||||
import SettingsService from "./settings";
|
||||
import RandomService from "./random";
|
||||
import WhatsappService from "./whatsapp";
|
||||
import SignaldService from "./signald";
|
||||
|
||||
export const register = async (server: Hapi.Server): Promise<void> => {
|
||||
// register your services here
|
||||
// don't forget to add them to the AppServices interface in ../types/index.ts
|
||||
server.registerService(RandomService);
|
||||
server.registerService(SettingsService);
|
||||
server.registerService(WhatsappService);
|
||||
server.registerService(SignaldService);
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
|
||||
import { Server } from "@hapi/hapi";
|
||||
import { Service } from "@hapipal/schmervice";
|
||||
import {
|
||||
SignaldAPI,
|
||||
IncomingMessagev1,
|
||||
ClientMessageWrapperv1
|
||||
ClientMessageWrapperv1,
|
||||
} from "@digiresilience/node-signald";
|
||||
import { SavedSignalBot as Bot } from "@digiresilience/metamigo-db";
|
||||
import workerUtils from "../../worker-utils";
|
||||
|
|
@ -54,12 +54,15 @@ export default class SignaldService extends Service {
|
|||
this.signald.on("transport_connected", async () => {
|
||||
this.onConnected();
|
||||
});
|
||||
this.signald.on("transport_received_payload", async (payload: ClientMessageWrapperv1) => {
|
||||
this.server.logger.debug({ payload }, "signald payload received");
|
||||
if (payload.type === "IncomingMessage") {
|
||||
this.receiveMessage(payload.data)
|
||||
this.signald.on(
|
||||
"transport_received_payload",
|
||||
async (payload: ClientMessageWrapperv1) => {
|
||||
this.server.logger.debug({ payload }, "signald payload received");
|
||||
if (payload.type === "IncomingMessage") {
|
||||
this.receiveMessage(payload.data);
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
this.signald.on("transport_sent_payload", async (payload) => {
|
||||
this.server.logger.debug({ payload }, "signald payload sent");
|
||||
});
|
||||
|
|
@ -163,7 +166,9 @@ export default class SignaldService extends Service {
|
|||
this.server.logger.error("invalid message received");
|
||||
}
|
||||
|
||||
const bot = await this.server.db().signalBots.findBy({ phoneNumber: account });
|
||||
const bot = await this.server
|
||||
.db()
|
||||
.signalBots.findBy({ phoneNumber: account });
|
||||
if (!bot) {
|
||||
this.server.logger.info("message received for unknown bot", {
|
||||
account,
|
||||
|
|
@ -1,16 +1,31 @@
|
|||
import { Server } from "@hapi/hapi";
|
||||
import { Service } from "@hapipal/schmervice";
|
||||
import { SavedWhatsappBot as Bot } from "@digiresilience/metamigo-db";
|
||||
import makeWASocket, { DisconnectReason, proto, downloadContentFromMessage, MediaType } from "@adiwajshing/baileys";
|
||||
import makeWASocket, {
|
||||
DisconnectReason,
|
||||
proto,
|
||||
downloadContentFromMessage,
|
||||
MediaType,
|
||||
AnyMessageContent,
|
||||
WAProto,
|
||||
MiscMessageGenerationOptions,
|
||||
} from "@adiwajshing/baileys";
|
||||
import workerUtils from "../../worker-utils";
|
||||
import { useDatabaseAuthState } from "../lib/whatsapp-key-store";
|
||||
import { connect } from "pg-monitor";
|
||||
|
||||
export type AuthCompleteCallback = (error?: string) => void;
|
||||
|
||||
export type Connection = {
|
||||
end: (error: Error | undefined) => void;
|
||||
sendMessage: (
|
||||
jid: string,
|
||||
content: AnyMessageContent,
|
||||
options?: MiscMessageGenerationOptions
|
||||
) => Promise<WAProto.WebMessageInfo | undefined>;
|
||||
};
|
||||
|
||||
export default class WhatsappService extends Service {
|
||||
connections: { [key: string]: any } = {};
|
||||
loginConnections: { [key: string]: any } = {};
|
||||
connections: { [key: string]: Connection } = {};
|
||||
|
||||
static browserDescription: [string, string, string] = [
|
||||
"Metamigo",
|
||||
|
|
@ -31,68 +46,87 @@ export default class WhatsappService extends Service {
|
|||
}
|
||||
|
||||
private async sleep(ms: number): Promise<void> {
|
||||
console.log(`pausing ${ms}`)
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
console.log(`pausing ${ms}`);
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
private async resetConnections() {
|
||||
for (const connection of Object.values(this.connections)) {
|
||||
try {
|
||||
connection.end(null)
|
||||
connection.end(null);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
this.connections = {};
|
||||
}
|
||||
|
||||
private createConnection(bot: Bot, server: Server, options: any, authCompleteCallback?: any) {
|
||||
const { state, saveState } = useDatabaseAuthState(bot, server)
|
||||
private createConnection(
|
||||
bot: Bot,
|
||||
server: Server,
|
||||
options: any,
|
||||
authCompleteCallback?: any
|
||||
) {
|
||||
const { state, saveState } = useDatabaseAuthState(bot, server);
|
||||
const connection = makeWASocket({ ...options, auth: state });
|
||||
let pause = 5000;
|
||||
connection.ev.on('connection.update', async (update) => {
|
||||
console.log(`Connection updated ${JSON.stringify(update, null, 2)}`)
|
||||
const { connection: connectionState, lastDisconnect, qr, isNewLogin } = update
|
||||
connection.ev.on("connection.update", async (update) => {
|
||||
console.log(`Connection updated ${JSON.stringify(update, null, 2)}`);
|
||||
const {
|
||||
connection: connectionState,
|
||||
lastDisconnect,
|
||||
qr,
|
||||
isNewLogin,
|
||||
} = update;
|
||||
if (qr) {
|
||||
console.log('got qr code')
|
||||
console.log("got qr code");
|
||||
await this.server.db().whatsappBots.updateQR(bot, qr);
|
||||
} else if (isNewLogin) {
|
||||
console.log("got new login")
|
||||
} else if (connectionState === 'open') {
|
||||
console.log('opened connection')
|
||||
console.log("got new login");
|
||||
} else if (connectionState === "open") {
|
||||
console.log("opened connection");
|
||||
} else if (connectionState === "close") {
|
||||
console.log('connection closed due to ', lastDisconnect.error)
|
||||
const disconnectStatusCode = (lastDisconnect?.error as any)?.output?.statusCode
|
||||
console.log("connection closed due to", lastDisconnect.error);
|
||||
const disconnectStatusCode = (lastDisconnect?.error as any)?.output
|
||||
?.statusCode;
|
||||
if (disconnectStatusCode === DisconnectReason.restartRequired) {
|
||||
console.log('reconnecting after got new login')
|
||||
console.log("reconnecting after got new login");
|
||||
const updatedBot = await this.findById(bot.id);
|
||||
this.createConnection(updatedBot, server, options)
|
||||
authCompleteCallback()
|
||||
this.createConnection(updatedBot, server, options);
|
||||
authCompleteCallback();
|
||||
} else if (disconnectStatusCode !== DisconnectReason.loggedOut) {
|
||||
console.log('reconnecting')
|
||||
await this.sleep(pause)
|
||||
pause = pause * 2
|
||||
this.createConnection(bot, server, options)
|
||||
console.log("reconnecting");
|
||||
await this.sleep(pause);
|
||||
pause *= 2;
|
||||
this.createConnection(bot, server, options);
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
connection.ev.on('chats.set', item => console.log(`recv ${item.chats.length} chats (is latest: ${item.isLatest})`))
|
||||
connection.ev.on('messages.set', item => console.log(`recv ${item.messages.length} messages (is latest: ${item.isLatest})`))
|
||||
connection.ev.on('contacts.set', item => console.log(`recv ${item.contacts.length} contacts`))
|
||||
connection.ev.on('messages.upsert', async m => {
|
||||
console.log("messages upsert")
|
||||
connection.ev.process(async (events) => {
|
||||
if (events["messaging-history.set"]) {
|
||||
const { chats, contacts, messages, isLatest } =
|
||||
events["messaging-history.set"];
|
||||
console.log(
|
||||
`recv ${chats.length} chats, ${contacts.length} contacts, ${messages.length} msgs (is latest: ${isLatest})`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
connection.ev.on("messages.upsert", async (m) => {
|
||||
console.log("messages upsert");
|
||||
const { messages } = m;
|
||||
if (messages) {
|
||||
await this.queueUnreadMessages(bot, messages);
|
||||
}
|
||||
})
|
||||
connection.ev.on('messages.update', m => console.log(m))
|
||||
connection.ev.on('message-receipt.update', m => console.log(m))
|
||||
connection.ev.on('presence.update', m => console.log(m))
|
||||
connection.ev.on('chats.update', m => console.log(m))
|
||||
connection.ev.on('contacts.upsert', m => console.log(m))
|
||||
connection.ev.on('creds.update', saveState)
|
||||
});
|
||||
connection.ev.on("messages.update", (m) => console.log(m));
|
||||
connection.ev.on("message-receipt.update", (m) => console.log(m));
|
||||
connection.ev.on("presence.update", (m) => console.log(m));
|
||||
connection.ev.on("chats.update", (m) => console.log(m));
|
||||
connection.ev.on("contacts.upsert", (m) => console.log(m));
|
||||
connection.ev.on("creds.update", saveState);
|
||||
|
||||
this.connections[bot.id] = connection;
|
||||
}
|
||||
|
|
@ -103,14 +137,11 @@ export default class WhatsappService extends Service {
|
|||
const bots = await this.server.db().whatsappBots.findAll();
|
||||
for await (const bot of bots) {
|
||||
if (bot.isVerified) {
|
||||
this.createConnection(
|
||||
bot,
|
||||
this.server,
|
||||
{
|
||||
browser: WhatsappService.browserDescription,
|
||||
printQRInTerminal: false,
|
||||
version: [2, 2204, 13],
|
||||
})
|
||||
this.createConnection(bot, this.server, {
|
||||
browser: WhatsappService.browserDescription,
|
||||
printQRInTerminal: false,
|
||||
version: [2, 2204, 13],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -126,7 +157,7 @@ export default class WhatsappService extends Service {
|
|||
message.imageMessage ||
|
||||
message.videoMessage;
|
||||
|
||||
let messageContent = Object.values(message)[0]
|
||||
const messageContent = Object.values(message)[0];
|
||||
let messageType: MediaType;
|
||||
let attachment: string;
|
||||
let filename: string;
|
||||
|
|
@ -147,17 +178,21 @@ export default class WhatsappService extends Service {
|
|||
key.id + "." + message.imageMessage.mimetype.split("/").pop();
|
||||
mimetype = message.imageMessage.mimetype;
|
||||
} else if (message.videoMessage) {
|
||||
messageType = "video"
|
||||
messageType = "video";
|
||||
filename =
|
||||
key.id + "." + message.videoMessage.mimetype.split("/").pop();
|
||||
mimetype = message.videoMessage.mimetype;
|
||||
}
|
||||
|
||||
const stream = await downloadContentFromMessage(messageContent, messageType)
|
||||
let buffer = Buffer.from([])
|
||||
const stream = await downloadContentFromMessage(
|
||||
messageContent,
|
||||
messageType
|
||||
);
|
||||
let buffer = Buffer.from([]);
|
||||
for await (const chunk of stream) {
|
||||
buffer = Buffer.concat([buffer, chunk])
|
||||
buffer = Buffer.concat([buffer, chunk]);
|
||||
}
|
||||
|
||||
attachment = buffer.toString("base64");
|
||||
}
|
||||
|
||||
|
|
@ -214,7 +249,12 @@ export default class WhatsappService extends Service {
|
|||
}
|
||||
|
||||
async register(bot: Bot, callback: AuthCompleteCallback): Promise<void> {
|
||||
await this.createConnection(bot, this.server, { version: [2, 2204, 13] }, callback);
|
||||
await this.createConnection(
|
||||
bot,
|
||||
this.server,
|
||||
{ version: [2, 2204, 13] },
|
||||
callback
|
||||
);
|
||||
}
|
||||
|
||||
async send(bot: Bot, phoneNumber: string, message: string): Promise<void> {
|
||||
|
|
@ -6,16 +6,10 @@ import type { IAppConfig } from "../../config";
|
|||
import type { AppDatabase } from "@digiresilience/metamigo-db";
|
||||
|
||||
// add your service interfaces here
|
||||
interface AppServices {
|
||||
settingsService: ISettingsService;
|
||||
whatsappService: WhatsappService;
|
||||
signaldService: SignaldService;
|
||||
}
|
||||
|
||||
// extend the hapi types with our services and config
|
||||
declare module "@hapi/hapi" {
|
||||
export interface Request {
|
||||
services(): AppServices;
|
||||
db(): AppDatabase;
|
||||
pgp: IMain;
|
||||
}
|
||||
|
|
@ -25,3 +19,15 @@ declare module "@hapi/hapi" {
|
|||
pgp: IMain;
|
||||
}
|
||||
}
|
||||
|
||||
declare module "@hapipal/schmervice" {
|
||||
interface AppServices {
|
||||
settingsService: ISettingsService;
|
||||
whatsappService: WhatsappService;
|
||||
signaldService: SignaldService;
|
||||
}
|
||||
|
||||
interface SchmerviceDecorator {
|
||||
(namespace: "app"): AppServices;
|
||||
}
|
||||
}
|
||||
7
apps/metamigo-api/src/config.ts
Normal file
7
apps/metamigo-api/src/config.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export {default, loadConfig, loadConfigRaw, IAppConfig, IAppConvict} from "@digiresilience/metamigo-config";
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { defState } from "@digiresilience/montar";
|
||||
import { configureLogger } from "@digiresilience/metamigo-common";
|
||||
import config from "config";
|
||||
import config from "@digiresilience/metamigo-config";
|
||||
|
||||
export const logger = defState("apiLogger", {
|
||||
start: async () => configureLogger(config),
|
||||
|
|
@ -16,9 +16,9 @@ export const deployment = async (
|
|||
return server;
|
||||
};
|
||||
|
||||
export const stopDeployment = async (server: Metamigo.Server): Promise<void> => {
|
||||
return Metamigo.stopDeployment(server);
|
||||
};
|
||||
export const stopDeployment = async (
|
||||
server: Metamigo.Server
|
||||
): Promise<void> => Metamigo.stopDeployment(server);
|
||||
|
||||
const server = defState("server", {
|
||||
start: () => deployment(config, true),
|
||||
|
|
@ -9,9 +9,7 @@ const startWorkerUtils = async (): Promise<Worker.WorkerUtils> => {
|
|||
return workerUtils;
|
||||
};
|
||||
|
||||
const stopWorkerUtils = async (): Promise<void> => {
|
||||
return workerUtils.release();
|
||||
};
|
||||
const stopWorkerUtils = async (): Promise<void> => workerUtils.release();
|
||||
|
||||
const workerUtils = defState("apiWorkerUtils", {
|
||||
start: startWorkerUtils,
|
||||
|
|
@ -1,10 +1,11 @@
|
|||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"extends": "tsconfig-link",
|
||||
"compilerOptions": {
|
||||
"outDir": "build/main",
|
||||
"types": ["long", "jest", "node"],
|
||||
"rootDir": "src",
|
||||
"types": ["jest", "node", "long"],
|
||||
"lib": ["es2020", "DOM"]
|
||||
},
|
||||
"include": ["**/*.ts", "**/.*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
"include": ["src/**/*.ts", "src/**/.*.ts"],
|
||||
"exclude": ["node_modules/**"]
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue