Refactoring 3

This commit is contained in:
Darren Clarke 2024-04-30 13:13:49 +02:00
parent e4b78ceec2
commit d1fb9b4d06
20 changed files with 201 additions and 123 deletions

View file

@ -6,16 +6,16 @@
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"scripts": { "scripts": {
"build": "tsc -p tsconfig.json", "build": "tsc -p tsconfig.json",
"dev": "NODE_OPTIONS=\"--loader ts-node/esm\" graphile-worker" "dev": " NODE_OPTIONS=\"--loader ts-node/esm\" graphile-worker"
}, },
"dependencies": { "dependencies": {
"@hapi/wreck": "^18.1.0", "@hapi/wreck": "^18.1.0",
"bridge-common": "*",
"fluent-ffmpeg": "^2.1.2", "fluent-ffmpeg": "^2.1.2",
"graphile-worker": "^0.16.5", "graphile-worker": "^0.16.5",
"html-to-text": "^9.0.5", "html-to-text": "^9.0.5",
"jest": "^29.7.0", "jest": "^29.7.0",
"kysely": "^0.27.3", "kysely": "^0.27.3",
"bridge-common": "*",
"pg": "^8.11.5", "pg": "^8.11.5",
"remeda": "^1.61.0", "remeda": "^1.61.0",
"twilio": "^5.0.4" "twilio": "^5.0.4"
@ -25,6 +25,7 @@
"@babel/preset-env": "7.24.4", "@babel/preset-env": "7.24.4",
"@babel/preset-typescript": "7.24.1", "@babel/preset-typescript": "7.24.1",
"@types/fluent-ffmpeg": "^2.1.24", "@types/fluent-ffmpeg": "^2.1.24",
"dotenv": "^16.4.5",
"eslint": "^9.0.0", "eslint": "^9.0.0",
"prettier": "^3.2.5", "prettier": "^3.2.5",
"ts-config": "*", "ts-config": "*",

View file

@ -1,57 +0,0 @@
import Wreck from "@hapi/wreck";
import * as R from "remeda";
import { withDb, AppDatabase } from "../../lib/db";
// import logger from "../logger";
export interface WebhookOptions {
webhookId: string;
payload: any;
}
const notifyWebhooksTask = async (options: WebhookOptions): Promise<void> =>
withDb(async (db: AppDatabase) => {
const { webhookId, payload } = options;
const webhook = await db.webhooks.findById({ id: webhookId });
if (!webhook) {
// logger.debug(
// { webhookId },
// "notify-webhook: no webhook registered with id",
// );
return;
}
const { endpointUrl, httpMethod, headers } = webhook;
const headersFormatted = R.reduce(
headers || [],
(acc: any, h: any) => {
acc[h.header] = h.value;
return acc;
},
{},
);
const wreck = Wreck.defaults({
json: true,
headers: headersFormatted,
});
// http errors will bubble up and cause the job to fail and be retried
try {
// logger.debug(
// { webhookId, endpointUrl, httpMethod },
// "notify-webhook: notifying",
// );
await (httpMethod === "post"
? wreck.post(endpointUrl, { payload })
: wreck.put(endpointUrl, { payload }));
} catch (error: any) {
// logger.error(
// { webhookId, error: error.output },
// "notify-webhook failed with this error",
// );
throw new Error(`webhook failed webhookId=${webhookId}`);
}
});
export default notifyWebhooksTask;

View file

@ -0,0 +1,31 @@
import { db } from "bridge-common";
export interface NotifyWebhooksOptions {
backendId: string;
payload: any;
}
const notifyWebhooksTask = async (
options: NotifyWebhooksOptions,
): Promise<void> => {
const { backendId, payload } = options;
const webhooks = await db
.selectFrom("Webhook")
.selectAll()
.where("backendId", "=", backendId)
.execute();
for (const webhook of webhooks) {
const { endpointUrl, httpMethod, headers } = webhook;
const finalHeaders = { "Content-Type": "application/json", ...headers };
await fetch(endpointUrl, {
method: httpMethod,
headers: finalHeaders,
body: JSON.stringify(payload),
});
}
};
export default notifyWebhooksTask;

View file

@ -17,9 +17,13 @@ const receiveFacebookMessageTask = async ({
.selectAll() .selectAll()
.where("pageId", "=", pageId) .where("pageId", "=", pageId)
.executeTakeFirstOrThrow(); .executeTakeFirstOrThrow();
const backendId = row.id;
const payload = {
text: messaging.message.text,
recipient: messaging.sender.id,
};
console.log({ row }); await worker.addJob("common/notify-webhooks", { backendId, payload });
await worker.addJob("notify_webhooks", messaging);
} }
} }
}; };

View file

@ -1,64 +1,34 @@
interface SendFacebookMessageTaskOptions {} import { db } from "bridge-common";
interface SendFacebookMessageTaskOptions {
token: string;
message: any;
}
const sendFacebookMessageTask = async ( const sendFacebookMessageTask = async (
options: SendFacebookMessageTaskOptions, options: SendFacebookMessageTaskOptions,
): Promise<void> => { ): Promise<void> => {
console.log(options); const { token, message } = options;
const { pageId, pageAccessToken } = await db
const message = await req.json();
const message = await req.json();
for (const entry of message.entry) {
for (const messaging of entry.messaging) {
const pageId = messaging.recipient.id;
const row = await db
.selectFrom("FacebookBot")
.selectAll()
.where("pageId", "=", pageId)
.executeTakeFirst();
console.log({ message });
const entry = message.entry[0];
console.log({ entry });
const messaging = entry?.messaging[0];
const pageId = messaging?.recipient?.id;
console.log({ pageId });
const row = await db
.selectFrom("FacebookBot") .selectFrom("FacebookBot")
.selectAll() .selectAll()
.where("pageId", "=", pageId) .where("token", "=", token)
.executeTakeFirst(); .executeTakeFirstOrThrow();
console.log({ row });
const endpoint = `https://graph.facebook.com/v19.0/${pageId}/messages`; const endpoint = `https://graph.facebook.com/v19.0/${pageId}/messages`;
const inMessage = messaging?.message?.text;
const outgoingMessage = { const outgoingMessage = {
recipient: { id: messaging?.sender?.id }, recipient: { id: message.recipient },
message: { text: `"${inMessage}", right back at you!` }, message: { text: `"${message.text}", right back at you!` },
messaging_type: "RESPONSE", messaging_type: "RESPONSE",
access_token: row?.pageAccessToken, access_token: pageAccessToken,
}; };
console.log({ outgoingMessage });
const response = await fetch(endpoint, { await fetch(endpoint, {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify(outgoingMessage), body: JSON.stringify(outgoingMessage),
}); });
console.log({ response });
console.log(message);
const wu = await getWorkerUtils();
await wu.addJob("receive_facebook_message", message);
return NextResponse.json({ response: "ok" });
// withDb(async (db: AppDatabase) => {
// await notifyWebhooks(db, options);
// });
}; };
export default sendFacebookMessageTask; export default sendFacebookMessageTask;

View file

@ -0,0 +1,11 @@
import { db, getWorkerUtils } from "bridge-common";
interface ReceiveSignalMessageTaskOptions {
message: any;
}
const receiveSignalMessageTask = async ({
message,
}: ReceiveSignalMessageTaskOptions): Promise<void> => {};
export default receiveSignalMessageTask;

View file

@ -0,0 +1,11 @@
import { db, getWorkerUtils } from "bridge-common";
interface SendSignalMessageTaskOptions {
message: any;
}
const sendSignalMessageTask = async ({
message,
}: SendSignalMessageTaskOptions): Promise<void> => {};
export default sendSignalMessageTask;

View file

@ -0,0 +1,11 @@
import { db, getWorkerUtils } from "bridge-common";
interface ReceiveVoiceMessageTaskOptions {
message: any;
}
const receiveVoiceMessageTask = async ({
message,
}: ReceiveVoiceMessageTaskOptions): Promise<void> => {};
export default receiveVoiceMessageTask;

View file

@ -0,0 +1,11 @@
import { db, getWorkerUtils } from "bridge-common";
interface SendVoiceMessageTaskOptions {
message: any;
}
const sendVoiceMessageTask = async ({
message,
}: SendVoiceMessageTaskOptions): Promise<void> => {};
export default sendVoiceMessageTask;

View file

@ -1,4 +1,17 @@
import { db, getWorkerUtils } from "bridge-common";
interface ReceiveWhatsappMessageTaskOptions {
message: any;
}
const receiveWhatsappMessageTask = async ({
message,
}: ReceiveWhatsappMessageTaskOptions): Promise<void> => {};
export default receiveWhatsappMessageTask;
/* eslint-disable camelcase */ /* eslint-disable camelcase */
/*
import { withDb, AppDatabase } from "../../lib/db"; import { withDb, AppDatabase } from "../../lib/db";
import workerUtils from "../../lib/utils"; import workerUtils from "../../lib/utils";
@ -93,3 +106,4 @@ const whatsappMessageTask = async (
}; };
export default whatsappMessageTask; export default whatsappMessageTask;
*/

View file

@ -0,0 +1,11 @@
import { db, getWorkerUtils } from "bridge-common";
interface SendWhatsappMessageTaskOptions {
message: any;
}
const sendWhatsappMessageTask = async ({
message,
}: SendWhatsappMessageTaskOptions): Promise<void> => {};
export default sendWhatsappMessageTask;

57
package-lock.json generated
View file

@ -36,6 +36,8 @@
"@mui/x-data-grid-pro": "^7.3.1", "@mui/x-data-grid-pro": "^7.3.1",
"@mui/x-date-pickers-pro": "^7.3.1", "@mui/x-date-pickers-pro": "^7.3.1",
"@mui/x-license": "^7.2.0", "@mui/x-license": "^7.2.0",
"bridge-common": "*",
"bridge-ui": "*",
"date-fns": "^3.6.0", "date-fns": "^3.6.0",
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"graphile-worker": "^0.16.6", "graphile-worker": "^0.16.6",
@ -152,6 +154,7 @@
"@babel/preset-env": "7.24.4", "@babel/preset-env": "7.24.4",
"@babel/preset-typescript": "7.24.1", "@babel/preset-typescript": "7.24.1",
"@types/fluent-ffmpeg": "^2.1.24", "@types/fluent-ffmpeg": "^2.1.24",
"dotenv": "^16.4.5",
"eslint": "^9.0.0", "eslint": "^9.0.0",
"prettier": "^3.2.5", "prettier": "^3.2.5",
"ts-config": "*", "ts-config": "*",
@ -16430,8 +16433,58 @@
} }
}, },
"packages/bridge-ui": { "packages/bridge-ui": {
"version": "1.0.0", "version": "0.2.0",
"license": "ISC" "dependencies": {
"@emotion/cache": "^11.11.0",
"@emotion/react": "^11.11.4",
"@emotion/server": "^11.11.0",
"@emotion/styled": "^11.11.5",
"@mui/icons-material": "^5",
"@mui/lab": "^5.0.0-alpha.170",
"@mui/material": "^5",
"@mui/x-data-grid-pro": "^7.3.1",
"@mui/x-date-pickers-pro": "^7.3.1",
"date-fns": "^3.6.0",
"material-ui-popup-state": "^5.1.0",
"next": "14.2.3",
"react": "18.3.0",
"react-cookie": "^7.1.4",
"react-cookie-consent": "^9.0.0",
"react-dom": "18.3.0",
"react-iframe": "^1.8.5",
"react-markdown": "^9.0.1",
"react-polyglot": "^0.7.2",
"tss-react": "^4.9.7",
"uuid": "^9.0.1"
},
"devDependencies": {
"@babel/core": "^7.24.4",
"@types/node": "^20.12.7",
"@types/react": "18.3.0",
"@types/uuid": "^9.0.8",
"babel-loader": "^9.1.3",
"eslint": "^8.0.0",
"eslint-config-next": "^14.2.3",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-jsx-a11y": "^6.8.0",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-react": "^7.34.1",
"file-loader": "^6.2.0",
"typescript": "5.4.5"
}
},
"packages/bridge-ui/node_modules/uuid": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
"integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
"funding": [
"https://github.com/sponsors/broofa",
"https://github.com/sponsors/ctavan"
],
"bin": {
"uuid": "dist/bin/uuid"
}
}, },
"packages/eslint-config": { "packages/eslint-config": {
"version": "0.3.10", "version": "0.3.10",

View file

@ -7,8 +7,9 @@ import type {
Insertable, Insertable,
Updateable, Updateable,
} from "kysely"; } from "kysely";
import { Pool, types } from "pg"; import pg from "pg";
import { KyselyAuth } from "@auth/kysely-adapter"; import { KyselyAuth } from "@auth/kysely-adapter";
const { Pool, types } = pg;
type Timestamp = ColumnType<Date, Date | string>; type Timestamp = ColumnType<Date, Date | string>;

View file

@ -6,8 +6,7 @@
"author": "Darren Clarke <darren@redaranj.com>", "author": "Darren Clarke <darren@redaranj.com>",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"scripts": { "scripts": {
"build": "tsc -p tsconfig.json", "build": "tsc -p tsconfig.json"
"dev": "NODE_OPTIONS=\"--loader ts-node/esm\" graphile-worker"
}, },
"dependencies": { "dependencies": {
"@auth/kysely-adapter": "^1.0.0", "@auth/kysely-adapter": "^1.0.0",

View file

@ -1,7 +1,7 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "es5", "target": "esnext",
"lib": ["dom", "dom.iterable", "esnext"], "lib": ["esnext"],
"allowJs": true, "allowJs": true,
"skipLibCheck": true, "skipLibCheck": true,
"strict": true, "strict": true,

File diff suppressed because one or more lines are too long

View file

@ -25,7 +25,7 @@ export class Facebook extends Service {
const message = await req.json(); const message = await req.json();
const worker = await getWorkerUtils(); const worker = await getWorkerUtils();
await worker.addJob("receive_facebook_message", message); await worker.addJob("facebook/receive-facebook-message", { message });
return NextResponse.json({ response: "ok" }); return NextResponse.json({ response: "ok" });
} }

View file

@ -48,12 +48,16 @@ export type ServiceConfig = {
}; };
export type ServiceParams = { export type ServiceParams = {
service: string; params: {
token?: string; service: string;
token?: string;
};
}; };
export class Service { export class Service {
async getBot({ service, token }: ServiceParams): Promise<NextResponse> { async getBot({
params: { service, token },
}: ServiceParams): Promise<NextResponse> {
const table = getServiceTable(service); const table = getServiceTable(service);
const row = await db const row = await db
.selectFrom(table) .selectFrom(table)
@ -66,11 +70,14 @@ export class Service {
async sendMessage( async sendMessage(
req: NextRequest, req: NextRequest,
{ service, token }: ServiceParams, { params: { service, token } }: ServiceParams,
): Promise<NextResponse> { ): Promise<NextResponse> {
const message = await req.json(); const message = await req.json();
const worker = await getWorkerUtils(); const worker = await getWorkerUtils();
await worker.addJob(`send_${service}_message`, { token, message }); await worker.addJob(`${service}/send-${service}-message`, {
token,
message,
});
return NextResponse.json({ response: "ok" }); return NextResponse.json({ response: "ok" });
} }

View file

@ -3,7 +3,7 @@ import { Facebook } from "./facebook";
import { Signal } from "./signal"; import { Signal } from "./signal";
import { Whatsapp } from "./whatsapp"; import { Whatsapp } from "./whatsapp";
export const getService = ({ service }: ServiceParams): Service => { export const getService = ({ params: { service } }: ServiceParams): Service => {
if (service === "facebook") { if (service === "facebook") {
return new Facebook(); return new Facebook();
} else if (service === "signal") { } else if (service === "signal") {

File diff suppressed because one or more lines are too long