Refactoring 2

This commit is contained in:
Darren Clarke 2024-04-30 11:39:16 +02:00
parent dd14dfe72e
commit e4b78ceec2
76 changed files with 870 additions and 734 deletions

View file

@ -1,74 +0,0 @@
import { Database } from "./database";
import { FieldDescription, Entity } from "./service";
import {
createAction,
updateAction,
deleteAction,
selectAllAction,
} from "@/app/_actions/service";
type GenerateCreateActionArgs = {
entity: Entity;
table: keyof Database;
fields: FieldDescription[];
};
export function generateCreateAction({
entity,
table,
fields,
}: GenerateCreateActionArgs) {
return async (currentState: any, formData: FormData) => {
console.log({ entity, table, fields });
console.log({ currentState, formData });
return createAction({
entity,
table,
fields,
currentState,
formData,
});
};
}
type GenerateUpdateActionArgs = {
entity: Entity;
table: keyof Database;
fields: FieldDescription[];
};
export function generateUpdateAction({
entity,
table,
fields,
}: GenerateUpdateActionArgs) {
return async (currentState: any, formData: FormData) => {
return updateAction({
entity,
table,
fields,
currentState,
formData,
});
};
}
type GenerateDeleteActionArgs = {
entity: Entity;
table: keyof Database;
};
export function generateDeleteAction({
entity,
table,
}: GenerateDeleteActionArgs) {
return async (id: string) => {
return deleteAction({ entity, table, id });
};
}
export function generateSelectAllAction(table: keyof Database) {
return async () => {
return selectAllAction(table);
};
}

View file

@ -1,6 +1,6 @@
import GoogleProvider from "next-auth/providers/google";
import { KyselyAdapter } from "@auth/kysely-adapter";
import { db } from "./database";
import { db } from "bridge-common";
export const authOptions = {
// @ts-ignore

View file

@ -1,147 +0,0 @@
import { PostgresDialect, CamelCasePlugin } from "kysely";
import type {
GeneratedAlways,
Generated,
ColumnType,
Selectable,
Insertable,
Updateable,
} from "kysely";
import { Pool, types } from "pg";
import { KyselyAuth } from "@auth/kysely-adapter";
type Timestamp = ColumnType<Date, Date | string>;
types.setTypeParser(types.builtins.TIMESTAMPTZ, (val) =>
new Date(val).toISOString(),
);
type GraphileJob = {
taskIdentifier: string;
payload: Record<string, any>;
priority: number;
maxAttempts: number;
key: string;
queueName: string;
};
export const addGraphileJob = async (jobInfo: GraphileJob) => {
// await db.insertInto("graphile_worker.jobs").values(jobInfo).execute();
};
export interface Database {
User: {
id: string;
name: string | null;
email: string;
emailVerified: Date | null;
image: string | null;
};
Account: {
id: GeneratedAlways<string>;
userId: string;
type: "oidc" | "oauth" | "email" | "webauthn";
provider: string;
providerAccountId: string;
refresh_token: string | undefined;
access_token: string | undefined;
expires_at: number | undefined;
token_type: Lowercase<string> | undefined;
scope: string | undefined;
id_token: string | undefined;
session_state: string | undefined;
};
Session: {
id: GeneratedAlways<string>;
userId: string;
sessionToken: string;
expires: Date;
};
VerificationToken: {
identifier: string;
token: string;
expires: Date;
};
WhatsappBot: {
id: GeneratedAlways<string>;
name: string;
description: string;
phoneNumber: string;
createdBy: string;
createdAt: Date;
updatedAt: Date;
};
FacebookBot: {
id: GeneratedAlways<string>;
name: string | null;
description: string | null;
token: string | null;
pageAccessToken: string | null;
appSecret: string | null;
verifyToken: string | null;
pageId: string | null;
appId: string | null;
userId: string | null;
isVerified: Generated<boolean>;
createdAt: GeneratedAlways<Timestamp>;
updatedAt: GeneratedAlways<Timestamp>;
};
VoiceLine: {
id: GeneratedAlways<string>;
name: string;
description: string;
createdBy: string;
createdAt: Date;
updatedAt: Date;
};
SignalBot: {
id: GeneratedAlways<string>;
name: string;
description: string;
phoneNumber: string;
createdBy: string;
createdAt: Date;
updatedAt: Date;
};
Webhook: {
id: GeneratedAlways<string>;
name: string;
description: string;
backendType: string;
backendId: string;
endpointUrl: string;
httpMethod: "post" | "put";
headers: Record<string, any>;
createdBy: string;
createdAt: Date;
updatedAt: Date;
};
}
export type FacebookBot = Selectable<Database["FacebookBot"]>;
export type SignalBot = Selectable<Database["SignalBot"]>;
export type WhatsappBot = Selectable<Database["WhatsappBot"]>;
export type VoiceLine = Selectable<Database["VoiceLine"]>;
export type Webhook = Selectable<Database["Webhook"]>;
export type User = Selectable<Database["User"]>;
export const db = new KyselyAuth<Database>({
dialect: new PostgresDialect({
pool: new Pool({
host: process.env.DATABASE_HOST,
database: process.env.DATABASE_NAME,
port: parseInt(process.env.DATABASE_PORT!),
user: process.env.DATABASE_USER,
password: process.env.DATABASE_PASSWORD,
}),
}),
plugins: [new CamelCasePlugin()],
});

View file

@ -1,101 +0,0 @@
import { NextRequest, NextResponse } from "next/server";
import { makeWorkerUtils, WorkerUtils } from "graphile-worker";
import { Service } from "./service";
import { db } from "./database";
let workerUtils: WorkerUtils;
const getWorkerUtils = async () => {
if (!workerUtils) {
workerUtils = await makeWorkerUtils({
connectionString: process.env.DATABASE_URL,
});
}
return workerUtils;
};
const sendMessage = async (req: NextRequest) => {
console.log({ req });
return NextResponse.json({ response: "ok" });
};
const receiveMessages = async (req: NextRequest) => {
console.log({ req });
return NextResponse.json({ response: "ok" });
};
const handleWebhook = async (req: NextRequest) => {
const { searchParams } = req.nextUrl;
const submittedToken = searchParams.get("hub.verify_token");
if (submittedToken) {
console.log({ submittedToken });
const row = await db
.selectFrom("FacebookBot")
.selectAll()
.where("verifyToken", "=", submittedToken)
.executeTakeFirst();
console.log({ row });
if (!row) {
return NextResponse.error();
}
if (searchParams.get("hub.mode") === "subscribe") {
const challenge = searchParams.get("hub.challenge");
console.log(submittedToken);
console.log(challenge);
return NextResponse.json(challenge) as any;
}
}
const message = await req.json();
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")
.selectAll()
.where("pageId", "=", pageId)
.executeTakeFirst();
console.log({ row });
const endpoint = `https://graph.facebook.com/v19.0/${pageId}/messages`;
const inMessage = messaging?.message?.text;
const outgoingMessage = {
recipient: { id: messaging?.sender?.id },
message: { text: `"${inMessage}", right back at you!` },
messaging_type: "RESPONSE",
access_token: row?.pageAccessToken,
};
console.log({ outgoingMessage });
const response = await fetch(endpoint, {
method: "POST",
headers: { "Content-Type": "application/json" },
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" });
};
export const Facebook: Service = {
sendMessage,
receiveMessages,
handleWebhook,
};

View file

@ -1,17 +0,0 @@
import { NextRequest, NextResponse } from "next/server";
import { getService } from "./utils";
const notFound = () => new NextResponse(null, { status: 404 });
export const getOneBot = async (req: NextRequest): Promise<NextResponse> =>
notFound();
export const sendMessage = async (req: NextRequest): Promise<NextResponse> =>
getService(req)?.sendMessage(req) ?? notFound();
export const receiveMessages = async (
req: NextRequest,
): Promise<NextResponse> => getService(req)?.receiveMessages(req) ?? notFound();
export const handleWebhook = async (req: NextRequest): Promise<NextResponse> =>
getService(req)?.handleWebhook(req) ?? notFound();

View file

@ -1,63 +0,0 @@
import { NextRequest, NextResponse } from "next/server";
import { GridColDef } from "@mui/x-data-grid-pro";
import { Database } from "./database";
const entities = [
"facebook",
"whatsapp",
"signal",
"voice",
"webhooks",
"users",
] as const;
export type Entity = (typeof entities)[number];
export type SelectOption = {
value: string;
label: string;
};
export type FieldDescription = {
name: string;
label: string;
kind?: "text" | "phone" | "select" | "multi";
getOptions?: (formState: any) => Promise<SelectOption[]>;
autogenerated?: "token";
hidden?: boolean;
type?: string;
lines?: number;
copyable?: boolean;
refreshable?: boolean;
defaultValue?: string;
required?: boolean;
disabled?: boolean;
size?: number;
helperText?: string;
};
export type ServiceConfig = {
entity: Entity;
table: keyof Database;
displayName: string;
createFields: FieldDescription[];
updateFields: FieldDescription[];
displayFields: FieldDescription[];
listColumns: GridColDef[];
};
export class Service {
sendMessage: (req: NextRequest) => Promise<NextResponse> = async (req) => {
return NextResponse.json({ ok: "nice" });
};
receiveMessages: (req: NextRequest) => Promise<NextResponse> = async (
req,
) => {
return NextResponse.json({ ok: "nice" });
};
handleWebhook: (req: NextRequest) => Promise<NextResponse> = async (req) => {
return NextResponse.json({ ok: "nice" });
};
}

View file

@ -1,26 +0,0 @@
import { NextRequest, NextResponse } from "next/server";
import { Service } from "./service";
const sendMessage = async (req: NextRequest) => {
console.log({ req });
return NextResponse.json({ response: "ok" });
};
const receiveMessages = async (req: NextRequest) => {
console.log({ req });
return NextResponse.json({ response: "ok" });
};
const handleWebhook = async (req: NextRequest) => {
console.log({ req });
return NextResponse.json({ response: "ok" });
};
export const Signal: Service = {
sendMessage,
receiveMessages,
handleWebhook,
};

View file

@ -1,13 +0,0 @@
import { NextRequest } from "next/server";
import { Service } from "./service";
import { Facebook } from "./facebook";
const services: Record<string, Service> = {
facebook: Facebook,
};
export const getService = (req: NextRequest): Service => {
const service = req.nextUrl.pathname.split("/")?.[2] ?? "none";
return services[service];
};

View file

@ -1,26 +0,0 @@
import { NextRequest, NextResponse } from "next/server";
import { Service } from "./service";
const sendMessage = async (req: NextRequest) => {
console.log({ req });
return NextResponse.json({ response: "ok" });
};
const receiveMessages = async (req: NextRequest) => {
console.log({ req });
return NextResponse.json({ response: "ok" });
};
const handleWebhook = async (req: NextRequest) => {
console.log({ req });
return NextResponse.json({ response: "ok" });
};
export const Voice: Service = {
sendMessage,
receiveMessages,
handleWebhook,
};

View file

@ -1,26 +0,0 @@
import { NextRequest, NextResponse } from "next/server";
import { Service } from "./service";
const sendMessage = async (req: NextRequest) => {
console.log({ req });
return NextResponse.json({ response: "ok" });
};
const receiveMessages = async (req: NextRequest) => {
console.log({ req });
return NextResponse.json({ response: "ok" });
};
const handleWebhook = async (req: NextRequest) => {
console.log({ req });
return NextResponse.json({ response: "ok" });
};
export const Whatsapp: Service = {
sendMessage,
receiveMessages,
handleWebhook,
};