Compare commits
21 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
21cc160f8f | ||
|
|
dd0265f3f5 | ||
|
|
810a333429 | ||
|
|
def602c05e | ||
|
|
9e5ea2fc41 | ||
|
|
07ee819520 | ||
|
|
ba0f1adcc4 | ||
|
|
5af67ef6ee | ||
|
|
25a5f0bc68 | ||
|
|
ab67245b91 | ||
|
|
857d877efa | ||
|
|
414502a33d | ||
|
|
0525f58324 | ||
|
|
9fb3665ced | ||
|
|
f552f8024f | ||
|
|
589010493d | ||
|
|
a8dd53507d | ||
|
|
130554d86b | ||
|
|
84731c9e9a | ||
|
|
7ad25e8a95 | ||
|
|
48aa89f7cf |
130 changed files with 4393 additions and 3350 deletions
|
|
@ -12,7 +12,7 @@ build-all:
|
||||||
TURBO_TEAM: ${TURBO_TEAM}
|
TURBO_TEAM: ${TURBO_TEAM}
|
||||||
ZAMMAD_URL: ${ZAMMAD_URL}
|
ZAMMAD_URL: ${ZAMMAD_URL}
|
||||||
script:
|
script:
|
||||||
- npm install npm@latest -g
|
- npm install npm@10 -g
|
||||||
- npm install -g turbo
|
- npm install -g turbo
|
||||||
- npm ci
|
- npm ci
|
||||||
- turbo build
|
- turbo build
|
||||||
|
|
@ -204,7 +204,7 @@ zammad-docker-build:
|
||||||
before_script:
|
before_script:
|
||||||
- apk --update add nodejs npm
|
- apk --update add nodejs npm
|
||||||
script:
|
script:
|
||||||
- npm install npm@latest -g
|
- npm install npm@10 -g
|
||||||
- npm install -g turbo
|
- npm install -g turbo
|
||||||
- npm ci
|
- npm ci
|
||||||
- turbo build --force --filter @link-stack/zammad-addon-*
|
- turbo build --force --filter @link-stack/zammad-addon-*
|
||||||
|
|
@ -226,7 +226,7 @@ zammad-standalone-docker-build:
|
||||||
before_script:
|
before_script:
|
||||||
- apk --update add nodejs npm
|
- apk --update add nodejs npm
|
||||||
script:
|
script:
|
||||||
- npm install npm@latest -g
|
- npm install npm@10 -g
|
||||||
- npm install -g turbo
|
- npm install -g turbo
|
||||||
- npm ci
|
- npm ci
|
||||||
- turbo build --force --filter @link-stack/zammad-addon-*
|
- turbo build --force --filter @link-stack/zammad-addon-*
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
import { Create } from "@link-stack/bridge-ui";
|
import { Create } from "@link-stack/bridge-ui";
|
||||||
|
|
||||||
type PageProps = {
|
type PageProps = {
|
||||||
params: { segment: string[] };
|
params: Promise<{ segment: string[] }>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function Page({ params: { segment } }: PageProps) {
|
export default async function Page({ params }: PageProps) {
|
||||||
|
const { segment } = await params;
|
||||||
const service = segment[0];
|
const service = segment[0];
|
||||||
|
|
||||||
return <Create service={service} />;
|
return <Create service={service} />;
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
import { db } from "@link-stack/bridge-common";
|
import { db } from "@link-stack/bridge-common";
|
||||||
import { serviceConfig, Detail } from "@link-stack/bridge-ui";
|
import { serviceConfig, Detail } from "@link-stack/bridge-ui";
|
||||||
|
|
||||||
type Props = {
|
type PageProps = {
|
||||||
params: { segment: string[] };
|
params: Promise<{ segment: string[] }>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default async function Page({ params: { segment } }: Props) {
|
export default async function Page({ params }: PageProps) {
|
||||||
|
const { segment } = await params;
|
||||||
const service = segment[0];
|
const service = segment[0];
|
||||||
const id = segment?.[1];
|
const id = segment?.[1];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,11 @@ import { db } from "@link-stack/bridge-common";
|
||||||
import { serviceConfig, Edit } from "@link-stack/bridge-ui";
|
import { serviceConfig, Edit } from "@link-stack/bridge-ui";
|
||||||
|
|
||||||
type PageProps = {
|
type PageProps = {
|
||||||
params: { segment: string[] };
|
params: Promise<{ segment: string[] }>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default async function Page({ params: { segment } }: PageProps) {
|
export default async function Page({ params }: PageProps) {
|
||||||
|
const { segment } = await params;
|
||||||
const service = segment[0];
|
const service = segment[0];
|
||||||
const id = segment?.[1];
|
const id = segment?.[1];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,13 @@ import { db } from "@link-stack/bridge-common";
|
||||||
import { serviceConfig, List } from "@link-stack/bridge-ui";
|
import { serviceConfig, List } from "@link-stack/bridge-ui";
|
||||||
|
|
||||||
type PageProps = {
|
type PageProps = {
|
||||||
params: {
|
params: Promise<{
|
||||||
segment: string[];
|
segment: string[];
|
||||||
};
|
}>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default async function Page({ params: { segment } }: PageProps) {
|
export default async function Page({ params }: PageProps) {
|
||||||
|
const { segment } = await params;
|
||||||
const service = segment[0];
|
const service = segment[0];
|
||||||
|
|
||||||
if (!service) return null;
|
if (!service) return null;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@link-stack/bridge-frontend",
|
"name": "@link-stack/bridge-frontend",
|
||||||
"version": "2.2.0",
|
"version": "2.4.0b1",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
|
|
@ -13,26 +13,26 @@
|
||||||
"migrate:down:one": "tsx database/migrate.ts down:one"
|
"migrate:down:one": "tsx database/migrate.ts down:one"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@auth/kysely-adapter": "^1.5.2",
|
"@auth/kysely-adapter": "^1.7.4",
|
||||||
"@mui/icons-material": "^5",
|
"@mui/icons-material": "^6",
|
||||||
"@mui/material": "^5",
|
"@mui/material": "^6",
|
||||||
"@mui/material-nextjs": "^5",
|
"@mui/material-nextjs": "^6",
|
||||||
"@mui/x-license": "^7.18.0",
|
"@mui/x-license": "^7.24.1",
|
||||||
"@link-stack/bridge-common": "*",
|
"@link-stack/bridge-common": "*",
|
||||||
"@link-stack/bridge-ui": "*",
|
"@link-stack/bridge-ui": "*",
|
||||||
"next": "14.2.13",
|
"next": "15.1.6",
|
||||||
"next-auth": "^4.24.8",
|
"next-auth": "^4.24.11",
|
||||||
"react": "18.3.1",
|
"react": "19.0.0",
|
||||||
"react-dom": "18.3.1",
|
"react-dom": "19.0.0",
|
||||||
"sharp": "^0.33.5",
|
"sharp": "^0.33.5",
|
||||||
"tsx": "^4.19.1",
|
"tsx": "^4.19.2",
|
||||||
"@link-stack/ui": "*"
|
"@link-stack/ui": "*"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^22",
|
"@types/node": "^22",
|
||||||
"@types/pg": "^8.11.10",
|
"@types/pg": "^8.11.11",
|
||||||
"@types/react": "^18",
|
"@types/react": "^19",
|
||||||
"@types/react-dom": "^18",
|
"@types/react-dom": "^19",
|
||||||
"@link-stack/eslint-config": "*",
|
"@link-stack/eslint-config": "*",
|
||||||
"@link-stack/typescript-config": "*",
|
"@link-stack/typescript-config": "*",
|
||||||
"typescript": "^5"
|
"typescript": "^5"
|
||||||
|
|
|
||||||
2
apps/bridge-frontend/public/robots.txt
Normal file
2
apps/bridge-frontend/public/robots.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
User-agent: *
|
||||||
|
Disallow: /
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"lib": ["dom", "dom.iterable", "esnext"],
|
"lib": [
|
||||||
|
"dom",
|
||||||
|
"dom.iterable",
|
||||||
|
"esnext"
|
||||||
|
],
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
|
|
@ -14,14 +18,24 @@
|
||||||
"jsx": "preserve",
|
"jsx": "preserve",
|
||||||
"incremental": true,
|
"incremental": true,
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./*"]
|
"@/*": [
|
||||||
|
"./*"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"plugins": [
|
"plugins": [
|
||||||
{
|
{
|
||||||
"name": "next"
|
"name": "next"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"target": "ES2017"
|
||||||
},
|
},
|
||||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
"include": [
|
||||||
"exclude": ["node_modules"]
|
"next-env.d.ts",
|
||||||
|
"**/*.ts",
|
||||||
|
"**/*.tsx",
|
||||||
|
".next/types/**/*.ts"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ export const migrate = async (arg: string) => {
|
||||||
|
|
||||||
results?.forEach((it) => {
|
results?.forEach((it) => {
|
||||||
if (it.status === "Success") {
|
if (it.status === "Success") {
|
||||||
console.log(
|
console.info(
|
||||||
`Migration "${it.migrationName} ${it.direction.toLowerCase()}" was executed successfully`,
|
`Migration "${it.migrationName} ${it.direction.toLowerCase()}" was executed successfully`,
|
||||||
);
|
);
|
||||||
} else if (it.status === "Error") {
|
} else if (it.status === "Error") {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@link-stack/bridge-migrations",
|
"name": "@link-stack/bridge-migrations",
|
||||||
"version": "2.2.0",
|
"version": "2.4.0b1",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"migrate:up:all": "tsx migrate.ts up:all",
|
"migrate:up:all": "tsx migrate.ts up:all",
|
||||||
|
|
@ -9,14 +9,14 @@
|
||||||
"migrate:down:one": "tsx migrate.ts down:one"
|
"migrate:down:one": "tsx migrate.ts down:one"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.7",
|
||||||
"kysely": "0.26.1",
|
"kysely": "0.26.1",
|
||||||
"pg": "^8.13.0",
|
"pg": "^8.13.1",
|
||||||
"tsx": "^4.19.1"
|
"tsx": "^4.19.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^22",
|
"@types/node": "^22",
|
||||||
"@types/pg": "^8.11.10",
|
"@types/pg": "^8.11.11",
|
||||||
"@link-stack/eslint-config": "*",
|
"@link-stack/eslint-config": "*",
|
||||||
"@link-stack/typescript-config": "*",
|
"@link-stack/typescript-config": "*",
|
||||||
"typescript": "^5"
|
"typescript": "^5"
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,26 @@
|
||||||
{
|
{
|
||||||
"name": "@link-stack/bridge-whatsapp",
|
"name": "@link-stack/bridge-whatsapp",
|
||||||
"version": "2.2.0",
|
"version": "2.4.0b1",
|
||||||
"main": "build/main/index.js",
|
"main": "build/main/index.js",
|
||||||
"author": "Darren Clarke <darren@redaranj.com>",
|
"author": "Darren Clarke <darren@redaranj.com>",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@adiwajshing/keyed-db": "0.2.4",
|
"@adiwajshing/keyed-db": "0.2.4",
|
||||||
"@hapi/hapi": "^21.3.10",
|
"@hapi/hapi": "^21.3.12",
|
||||||
"@hapipal/schmervice": "^3.0.0",
|
"@hapipal/schmervice": "^3.0.0",
|
||||||
"@hapipal/toys": "^4.0.0",
|
"@hapipal/toys": "^4.0.0",
|
||||||
"@whiskeysockets/baileys": "^6.7.8",
|
"@whiskeysockets/baileys": "^6.7.9",
|
||||||
"hapi-pino": "^12.1.0",
|
"hapi-pino": "^12.1.0",
|
||||||
"link-preview-js": "^3.0.5"
|
"link-preview-js": "^3.0.13"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@link-stack/eslint-config": "*",
|
"@link-stack/eslint-config": "*",
|
||||||
"@link-stack/jest-config": "*",
|
"@link-stack/jest-config": "*",
|
||||||
"@link-stack/typescript-config": "*",
|
"@link-stack/typescript-config": "*",
|
||||||
"@types/node": "*",
|
"@types/node": "*",
|
||||||
"dotenv-cli": "^7.4.2",
|
"dotenv-cli": "^8.0.0",
|
||||||
"tsx": "^4.19.1",
|
"tsx": "^4.19.2",
|
||||||
"typescript": "^5.6.2"
|
"typescript": "^5.7.3"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc -p tsconfig.json",
|
"build": "tsc -p tsconfig.json",
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,6 @@ export const SendMessageRoute = withDefaults({
|
||||||
description: "Send a message",
|
description: "Send a message",
|
||||||
async handler(request: Hapi.Request, _h: Hapi.ResponseToolkit) {
|
async handler(request: Hapi.Request, _h: Hapi.ResponseToolkit) {
|
||||||
const { id } = request.params;
|
const { id } = request.params;
|
||||||
console.log({ payload: request.payload });
|
|
||||||
const { phoneNumber, message } = request.payload as MessageRequest;
|
const { phoneNumber, message } = request.payload as MessageRequest;
|
||||||
const whatsappService = getService(request);
|
const whatsappService = getService(request);
|
||||||
await whatsappService.send(id, phoneNumber, message as string);
|
await whatsappService.send(id, phoneNumber, message as string);
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ export default class WhatsappService extends Service {
|
||||||
try {
|
try {
|
||||||
connection.end(null);
|
connection.end(null);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.connections = {};
|
this.connections = {};
|
||||||
|
|
@ -92,27 +92,27 @@ export default class WhatsappService extends Service {
|
||||||
isNewLogin,
|
isNewLogin,
|
||||||
} = update;
|
} = update;
|
||||||
if (qr) {
|
if (qr) {
|
||||||
console.log("got qr code");
|
console.info("got qr code");
|
||||||
const botDirectory = this.getBotDirectory(botID);
|
const botDirectory = this.getBotDirectory(botID);
|
||||||
const qrPath = `${botDirectory}/qr.txt`;
|
const qrPath = `${botDirectory}/qr.txt`;
|
||||||
fs.writeFileSync(qrPath, qr, "utf8");
|
fs.writeFileSync(qrPath, qr, "utf8");
|
||||||
} else if (isNewLogin) {
|
} else if (isNewLogin) {
|
||||||
console.log("got new login");
|
console.info("got new login");
|
||||||
const botDirectory = this.getBotDirectory(botID);
|
const botDirectory = this.getBotDirectory(botID);
|
||||||
const verifiedFile = `${botDirectory}/verified`;
|
const verifiedFile = `${botDirectory}/verified`;
|
||||||
fs.writeFileSync(verifiedFile, "");
|
fs.writeFileSync(verifiedFile, "");
|
||||||
} else if (connectionState === "open") {
|
} else if (connectionState === "open") {
|
||||||
console.log("opened connection");
|
console.info("opened connection");
|
||||||
} else if (connectionState === "close") {
|
} else if (connectionState === "close") {
|
||||||
console.log("connection closed due to ", lastDisconnect?.error);
|
console.info("connection closed due to ", lastDisconnect?.error);
|
||||||
const disconnectStatusCode = (lastDisconnect?.error as any)?.output
|
const disconnectStatusCode = (lastDisconnect?.error as any)?.output
|
||||||
?.statusCode;
|
?.statusCode;
|
||||||
if (disconnectStatusCode === DisconnectReason.restartRequired) {
|
if (disconnectStatusCode === DisconnectReason.restartRequired) {
|
||||||
console.log("reconnecting after got new login");
|
console.info("reconnecting after got new login");
|
||||||
await this.createConnection(botID, server, options);
|
await this.createConnection(botID, server, options);
|
||||||
authCompleteCallback?.();
|
authCompleteCallback?.();
|
||||||
} else if (disconnectStatusCode !== DisconnectReason.loggedOut) {
|
} else if (disconnectStatusCode !== DisconnectReason.loggedOut) {
|
||||||
console.log("reconnecting");
|
console.info("reconnecting");
|
||||||
await this.sleep(pause);
|
await this.sleep(pause);
|
||||||
pause *= 2;
|
pause *= 2;
|
||||||
this.createConnection(botID, server, options);
|
this.createConnection(botID, server, options);
|
||||||
|
|
@ -121,12 +121,12 @@ export default class WhatsappService extends Service {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (events["creds.update"]) {
|
if (events["creds.update"]) {
|
||||||
console.log("creds update");
|
console.info("creds update");
|
||||||
await saveCreds();
|
await saveCreds();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (events["messages.upsert"]) {
|
if (events["messages.upsert"]) {
|
||||||
console.log("messages upsert");
|
console.info("messages upsert");
|
||||||
const upsert = events["messages.upsert"];
|
const upsert = events["messages.upsert"];
|
||||||
const { messages } = upsert;
|
const { messages } = upsert;
|
||||||
if (messages) {
|
if (messages) {
|
||||||
|
|
@ -143,13 +143,13 @@ export default class WhatsappService extends Service {
|
||||||
|
|
||||||
const baseDirectory = this.getBaseDirectory();
|
const baseDirectory = this.getBaseDirectory();
|
||||||
const botIDs = fs.readdirSync(baseDirectory);
|
const botIDs = fs.readdirSync(baseDirectory);
|
||||||
console.log({ botIDs });
|
|
||||||
for await (const botID of botIDs) {
|
for await (const botID of botIDs) {
|
||||||
const directory = this.getBotDirectory(botID);
|
const directory = this.getBotDirectory(botID);
|
||||||
const verifiedFile = `${directory}/verified`;
|
const verifiedFile = `${directory}/verified`;
|
||||||
if (fs.existsSync(verifiedFile)) {
|
if (fs.existsSync(verifiedFile)) {
|
||||||
const { version, isLatest } = await fetchLatestBaileysVersion();
|
const { version, isLatest } = await fetchLatestBaileysVersion();
|
||||||
console.log(`using WA v${version.join(".")}, isLatest: ${isLatest}`);
|
console.info(`using WA v${version.join(".")}, isLatest: ${isLatest}`);
|
||||||
|
|
||||||
await this.createConnection(botID, this.server, {
|
await this.createConnection(botID, this.server, {
|
||||||
browser: WhatsappService.browserDescription,
|
browser: WhatsappService.browserDescription,
|
||||||
|
|
@ -169,7 +169,10 @@ export default class WhatsappService extends Service {
|
||||||
message,
|
message,
|
||||||
messageTimestamp,
|
messageTimestamp,
|
||||||
} = webMessageInfo;
|
} = webMessageInfo;
|
||||||
console.log(webMessageInfo);
|
console.info("Message type debug");
|
||||||
|
for (const key in message) {
|
||||||
|
console.info(key, !!message[key as keyof proto.IMessage]);
|
||||||
|
}
|
||||||
const isValidMessage =
|
const isValidMessage =
|
||||||
message && remoteJid !== "status@broadcast" && !fromMe;
|
message && remoteJid !== "status@broadcast" && !fromMe;
|
||||||
if (isValidMessage) {
|
if (isValidMessage) {
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,12 @@ import type {} from "graphile-worker";
|
||||||
const preset: GraphileConfig.Preset = {
|
const preset: GraphileConfig.Preset = {
|
||||||
worker: {
|
worker: {
|
||||||
connectionString: process.env.DATABASE_URL,
|
connectionString: process.env.DATABASE_URL,
|
||||||
maxPoolSize: 10,
|
maxPoolSize: process.env.BRIDGE_WORKER_POOL_SIZE
|
||||||
pollInterval: 2000,
|
? parseInt(process.env.BRIDGE_WORKER_POOL_SIZE, 10)
|
||||||
|
: 10,
|
||||||
|
pollInterval: process.env.BRIDGE_WORKER_POLL_INTERVAL
|
||||||
|
? parseInt(process.env.BRIDGE_WORKER_POLL_INTERVAL, 10)
|
||||||
|
: 2000,
|
||||||
fileExtensions: [".ts"],
|
fileExtensions: [".ts"],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,20 @@ const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = path.dirname(__filename);
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
const startWorker = async () => {
|
const startWorker = async () => {
|
||||||
console.log("Starting worker...");
|
console.info("Starting worker...");
|
||||||
console.log(process.env);
|
|
||||||
await run({
|
await run({
|
||||||
connectionString: process.env.DATABASE_URL,
|
connectionString: process.env.DATABASE_URL,
|
||||||
concurrency: 10,
|
|
||||||
noHandleSignals: false,
|
noHandleSignals: false,
|
||||||
pollInterval: 1000,
|
concurrency: process.env.BRIDGE_WORKER_CONCURRENCY
|
||||||
|
? parseInt(process.env.BRIDGE_WORKER_CONCURRENCY, 10)
|
||||||
|
: 10,
|
||||||
|
maxPoolSize: process.env.BRIDGE_WORKER_POOL_SIZE
|
||||||
|
? parseInt(process.env.BRIDGE_WORKER_POOL_SIZE, 10)
|
||||||
|
: 10,
|
||||||
|
pollInterval: process.env.BRIDGE_WORKER_POLL_INTERVAL
|
||||||
|
? parseInt(process.env.BRIDGE_WORKER_POLL_INTERVAL, 10)
|
||||||
|
: 1000,
|
||||||
taskDirectory: `${__dirname}/tasks`,
|
taskDirectory: `${__dirname}/tasks`,
|
||||||
crontabFile: `${__dirname}/crontab`,
|
crontabFile: `${__dirname}/crontab`,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -62,9 +62,8 @@ export const createZammadTicket = async (
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.log(Object.keys(error));
|
|
||||||
if (error.isBoom) {
|
if (error.isBoom) {
|
||||||
console.log(error.output);
|
console.error(error.output);
|
||||||
throw new Error("Failed to create zamamd ticket");
|
throw new Error("Failed to create zamamd ticket");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ const defaultAudioConvertOpts = {
|
||||||
**/
|
**/
|
||||||
export const convert = (
|
export const convert = (
|
||||||
input: Buffer,
|
input: Buffer,
|
||||||
opts?: AudioConvertOpts
|
opts?: AudioConvertOpts,
|
||||||
): Promise<Buffer> => {
|
): Promise<Buffer> => {
|
||||||
const settings = { ...defaultAudioConvertOpts, ...opts };
|
const settings = { ...defaultAudioConvertOpts, ...opts };
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
|
@ -35,12 +35,8 @@ export const convert = (
|
||||||
.audioCodec(settings.audioCodec)
|
.audioCodec(settings.audioCodec)
|
||||||
.audioBitrate(settings.bitrate)
|
.audioBitrate(settings.bitrate)
|
||||||
.toFormat(settings.format)
|
.toFormat(settings.format)
|
||||||
.on("error", (err, stdout, stderr) => {
|
.on("error", (err, _stdout, _stderr) => {
|
||||||
console.error(err.message);
|
console.error(err.message);
|
||||||
console.log("FFMPEG OUTPUT");
|
|
||||||
console.log(stdout);
|
|
||||||
console.log("FFMPEG ERROR");
|
|
||||||
console.log(stderr);
|
|
||||||
reject(err);
|
reject(err);
|
||||||
})
|
})
|
||||||
.on("end", () => {
|
.on("end", () => {
|
||||||
|
|
@ -66,8 +62,12 @@ export const selfCheck = (): Promise<boolean> => {
|
||||||
resolve(false);
|
resolve(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
const preds = R.map(requiredCodecs, (codec) => (available: any) =>
|
const preds = R.map(
|
||||||
available[codec] && available[codec].canDemux && available[codec].canMux
|
requiredCodecs,
|
||||||
|
(codec) => (available: any) =>
|
||||||
|
available[codec] &&
|
||||||
|
available[codec].canDemux &&
|
||||||
|
available[codec].canMux,
|
||||||
);
|
);
|
||||||
|
|
||||||
resolve(R.allPass(codecs, preds));
|
resolve(R.allPass(codecs, preds));
|
||||||
|
|
@ -79,6 +79,6 @@ export const assertFfmpegAvailable = async (): Promise<void> => {
|
||||||
const r = await selfCheck();
|
const r = await selfCheck();
|
||||||
if (!r)
|
if (!r)
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`ffmpeg is not installed, could not be located, or does not support the required codecs: ${requiredCodecs}`
|
`ffmpeg is not installed, could not be located, or does not support the required codecs: ${requiredCodecs}`,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@link-stack/bridge-worker",
|
"name": "@link-stack/bridge-worker",
|
||||||
"version": "2.2.0",
|
"version": "2.4.0b1",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "build/main/index.js",
|
"main": "build/main/index.js",
|
||||||
"author": "Darren Clarke <darren@redaranj.com>",
|
"author": "Darren Clarke <darren@redaranj.com>",
|
||||||
|
|
@ -16,14 +16,14 @@
|
||||||
"@link-stack/signal-api": "*",
|
"@link-stack/signal-api": "*",
|
||||||
"fluent-ffmpeg": "^2.1.3",
|
"fluent-ffmpeg": "^2.1.3",
|
||||||
"graphile-worker": "^0.16.6",
|
"graphile-worker": "^0.16.6",
|
||||||
"remeda": "^2.14.0",
|
"remeda": "^2.20.0",
|
||||||
"twilio": "^5.3.2"
|
"twilio": "^5.4.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/fluent-ffmpeg": "^2.1.26",
|
"@types/fluent-ffmpeg": "^2.1.27",
|
||||||
"dotenv-cli": "^7.4.2",
|
"dotenv-cli": "^8.0.0",
|
||||||
"@link-stack/eslint-config": "*",
|
"@link-stack/eslint-config": "*",
|
||||||
"@link-stack/typescript-config": "*",
|
"@link-stack/typescript-config": "*",
|
||||||
"typescript": "^5.6.2"
|
"typescript": "^5.7.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,13 +19,11 @@ const notifyWebhooksTask = async (
|
||||||
for (const webhook of webhooks) {
|
for (const webhook of webhooks) {
|
||||||
const { endpointUrl, httpMethod, headers } = webhook;
|
const { endpointUrl, httpMethod, headers } = webhook;
|
||||||
const finalHeaders = { "Content-Type": "application/json", ...headers };
|
const finalHeaders = { "Content-Type": "application/json", ...headers };
|
||||||
console.log({ endpointUrl, httpMethod, headers, finalHeaders });
|
|
||||||
const result = await fetch(endpointUrl, {
|
const result = await fetch(endpointUrl, {
|
||||||
method: httpMethod,
|
method: httpMethod,
|
||||||
headers: finalHeaders,
|
headers: finalHeaders,
|
||||||
body: JSON.stringify(payload),
|
body: JSON.stringify(payload),
|
||||||
});
|
});
|
||||||
console.log(result);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,6 @@ const sendFacebookMessageTask = async (
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify(outgoingMessage),
|
body: JSON.stringify(outgoingMessage),
|
||||||
});
|
});
|
||||||
console.log({ response });
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error({ error });
|
console.error({ error });
|
||||||
throw error;
|
throw error;
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,6 @@ const processMessage = async ({
|
||||||
message: msg,
|
message: msg,
|
||||||
}: ProcessMessageArgs): Promise<Record<string, any>[]> => {
|
}: ProcessMessageArgs): Promise<Record<string, any>[]> => {
|
||||||
const { envelope } = msg;
|
const { envelope } = msg;
|
||||||
console.log(envelope);
|
|
||||||
const { source, sourceUuid, dataMessage } = envelope;
|
const { source, sourceUuid, dataMessage } = envelope;
|
||||||
|
|
||||||
if (!dataMessage) return [];
|
if (!dataMessage) return [];
|
||||||
|
|
@ -125,7 +124,6 @@ const fetchSignalMessagesTask = async ({
|
||||||
phoneNumber,
|
phoneNumber,
|
||||||
message,
|
message,
|
||||||
});
|
});
|
||||||
console.log({ formattedMessages });
|
|
||||||
for (const formattedMessage of formattedMessages) {
|
for (const formattedMessage of formattedMessages) {
|
||||||
if (formattedMessage.to !== formattedMessage.from) {
|
if (formattedMessage.to !== formattedMessage.from) {
|
||||||
await worker.addJob(
|
await worker.addJob(
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,6 @@ const getZammadTickets = async (
|
||||||
{ headers },
|
{ headers },
|
||||||
);
|
);
|
||||||
const tickets: any = await rawTickets.json();
|
const tickets: any = await rawTickets.json();
|
||||||
console.log({ tickets });
|
|
||||||
if (!tickets || tickets.length === 0) {
|
if (!tickets || tickets.length === 0) {
|
||||||
return [shouldContinue, docs];
|
return [shouldContinue, docs];
|
||||||
}
|
}
|
||||||
|
|
@ -49,22 +48,8 @@ const getZammadTickets = async (
|
||||||
shouldContinue = true;
|
shouldContinue = true;
|
||||||
|
|
||||||
if (source_closed_at <= minUpdatedTimestamp) {
|
if (source_closed_at <= minUpdatedTimestamp) {
|
||||||
console.log(`Skipping ticket`, {
|
|
||||||
source_id,
|
|
||||||
source_updated_at,
|
|
||||||
source_closed_at,
|
|
||||||
minUpdatedTimestamp,
|
|
||||||
});
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`Processing ticket`, {
|
|
||||||
source_id,
|
|
||||||
source_updated_at,
|
|
||||||
source_closed_at,
|
|
||||||
minUpdatedTimestamp,
|
|
||||||
});
|
|
||||||
|
|
||||||
const rawArticles = await fetch(
|
const rawArticles = await fetch(
|
||||||
`${zammadApiUrl}/ticket_articles/by_ticket/${source_id}`,
|
`${zammadApiUrl}/ticket_articles/by_ticket/${source_id}`,
|
||||||
{ headers },
|
{ headers },
|
||||||
|
|
@ -178,8 +163,6 @@ const sendToLabelStudio = async (tickets: FormattedZammadTicket[]) => {
|
||||||
body: JSON.stringify([ticket]),
|
body: JSON.stringify([ticket]),
|
||||||
});
|
});
|
||||||
const importResult = await res.json();
|
const importResult = await res.json();
|
||||||
|
|
||||||
console.log(JSON.stringify(importResult, undefined, 2));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
*/
|
*/
|
||||||
|
|
@ -201,7 +184,6 @@ const importLabelStudioTask = async (): Promise<void> => {
|
||||||
await sendToLabelStudio(tickets);
|
await sendToLabelStudio(tickets);
|
||||||
const lastTicket = tickets.pop();
|
const lastTicket = tickets.pop();
|
||||||
const newLastTimestamp = lastTicket.data.source_closed_at;
|
const newLastTimestamp = lastTicket.data.source_closed_at;
|
||||||
console.log({ newLastTimestamp });
|
|
||||||
await db.settings.upsert(settingName, {
|
await db.settings.upsert(settingName, {
|
||||||
minUpdatedTimestamp: newLastTimestamp,
|
minUpdatedTimestamp: newLastTimestamp,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -43,14 +43,11 @@ const getLabelStudioTickets = async (
|
||||||
page_size: "50",
|
page_size: "50",
|
||||||
page: `${page}`,
|
page: `${page}`,
|
||||||
});
|
});
|
||||||
console.log({ url: `${labelStudioApiUrl}/projects/1/tasks?${ticketsQuery}` });
|
|
||||||
const res = await fetch(
|
const res = await fetch(
|
||||||
`${labelStudioApiUrl}/projects/1/tasks?${ticketsQuery}`,
|
`${labelStudioApiUrl}/projects/1/tasks?${ticketsQuery}`,
|
||||||
{ headers },
|
{ headers },
|
||||||
);
|
);
|
||||||
console.log({ res });
|
|
||||||
const tasksResult: any = await res.json();
|
const tasksResult: any = await res.json();
|
||||||
console.log({ tasksResult });
|
|
||||||
|
|
||||||
return tasksResult;
|
return tasksResult;
|
||||||
};
|
};
|
||||||
|
|
@ -63,14 +60,11 @@ const fetchFromLabelStudio = async (
|
||||||
|
|
||||||
for await (const page of pages) {
|
for await (const page of pages) {
|
||||||
const docs = await getLabelStudioTickets(page + 1);
|
const docs = await getLabelStudioTickets(page + 1);
|
||||||
console.log({ page, docs });
|
|
||||||
|
|
||||||
if (docs && docs.length > 0) {
|
if (docs && docs.length > 0) {
|
||||||
for (const doc of docs) {
|
for (const doc of docs) {
|
||||||
const updatedAt = new Date(doc.updated_at);
|
const updatedAt = new Date(doc.updated_at);
|
||||||
console.log({ updatedAt, minUpdatedTimestamp });
|
|
||||||
if (updatedAt > minUpdatedTimestamp) {
|
if (updatedAt > minUpdatedTimestamp) {
|
||||||
console.log(`Adding doc`, { doc });
|
|
||||||
allDocs.push(doc);
|
allDocs.push(doc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -79,7 +73,6 @@ const fetchFromLabelStudio = async (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log({ allDocs });
|
|
||||||
return allDocs;
|
return allDocs;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -93,9 +86,7 @@ const sendToLeafcutter = async (tickets: LabelStudioTicket[]) => {
|
||||||
},
|
},
|
||||||
} = config;
|
} = config;
|
||||||
|
|
||||||
console.log({ tickets });
|
|
||||||
const filteredTickets = tickets.filter((ticket) => ticket.is_labeled);
|
const filteredTickets = tickets.filter((ticket) => ticket.is_labeled);
|
||||||
console.log({ filteredTickets });
|
|
||||||
const finalTickets: LeafcutterTicket[] = filteredTickets.map((ticket) => {
|
const finalTickets: LeafcutterTicket[] = filteredTickets.map((ticket) => {
|
||||||
const {
|
const {
|
||||||
id,
|
id,
|
||||||
|
|
@ -131,8 +122,7 @@ const sendToLeafcutter = async (tickets: LabelStudioTicket[]) => {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("Sending to Leafcutter");
|
console.info("Sending to Leafcutter");
|
||||||
console.log({ finalTickets });
|
|
||||||
|
|
||||||
const result = await fetch(opensearchApiUrl, {
|
const result = await fetch(opensearchApiUrl, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|
@ -157,15 +147,7 @@ const importLeafcutterTask = async (): Promise<void> => {
|
||||||
? new Date(res.value.minUpdatedTimestamp as string)
|
? new Date(res.value.minUpdatedTimestamp as string)
|
||||||
: new Date("2023-03-01");
|
: new Date("2023-03-01");
|
||||||
const newLastTimestamp = new Date();
|
const newLastTimestamp = new Date();
|
||||||
console.log({
|
|
||||||
contributorName,
|
|
||||||
settingName,
|
|
||||||
res,
|
|
||||||
startTimestamp,
|
|
||||||
newLastTimestamp,
|
|
||||||
});
|
|
||||||
const tickets = await fetchFromLabelStudio(startTimestamp);
|
const tickets = await fetchFromLabelStudio(startTimestamp);
|
||||||
console.log({ tickets });
|
|
||||||
await sendToLeafcutter(tickets);
|
await sendToLeafcutter(tickets);
|
||||||
await db.settings.upsert(settingName, {
|
await db.settings.upsert(settingName, {
|
||||||
minUpdatedTimestamp: newLastTimestamp,
|
minUpdatedTimestamp: newLastTimestamp,
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,6 @@ const receiveSignalMessageTask = async ({
|
||||||
filename,
|
filename,
|
||||||
mimeType,
|
mimeType,
|
||||||
}: ReceiveSignalMessageTaskOptions): Promise<void> => {
|
}: ReceiveSignalMessageTaskOptions): Promise<void> => {
|
||||||
console.log({ token, to, from });
|
|
||||||
const worker = await getWorkerUtils();
|
const worker = await getWorkerUtils();
|
||||||
const row = await db
|
const row = await db
|
||||||
.selectFrom("SignalBot")
|
.selectFrom("SignalBot")
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ const sendSignalMessageTask = async ({
|
||||||
to,
|
to,
|
||||||
message,
|
message,
|
||||||
}: SendSignalMessageTaskOptions): Promise<void> => {
|
}: SendSignalMessageTaskOptions): Promise<void> => {
|
||||||
console.log({ token, to });
|
|
||||||
const bot = await db
|
const bot = await db
|
||||||
.selectFrom("SignalBot")
|
.selectFrom("SignalBot")
|
||||||
.selectAll()
|
.selectAll()
|
||||||
|
|
@ -34,7 +33,6 @@ const sendSignalMessageTask = async ({
|
||||||
message,
|
message,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
console.log({ response });
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error({ error });
|
console.error({ error });
|
||||||
throw error;
|
throw error;
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,6 @@ const receiveWhatsappMessageTask = async ({
|
||||||
filename,
|
filename,
|
||||||
mimeType,
|
mimeType,
|
||||||
}: ReceiveWhatsappMessageTaskOptions): Promise<void> => {
|
}: ReceiveWhatsappMessageTaskOptions): Promise<void> => {
|
||||||
console.log({ token, to, from });
|
|
||||||
|
|
||||||
const worker = await getWorkerUtils();
|
const worker = await getWorkerUtils();
|
||||||
const row = await db
|
const row = await db
|
||||||
.selectFrom("WhatsappBot")
|
.selectFrom("WhatsappBot")
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,6 @@ const sendWhatsappMessageTask = async ({
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify(params),
|
body: JSON.stringify(params),
|
||||||
});
|
});
|
||||||
console.log({ result });
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error({ error });
|
console.error({ error });
|
||||||
throw new Error("Failed to send message");
|
throw new Error("Failed to send message");
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,10 @@
|
||||||
|
import { Suspense } from "react";
|
||||||
import { About } from "@link-stack/leafcutter-ui";
|
import { About } from "@link-stack/leafcutter-ui";
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
return <About />;
|
return (
|
||||||
|
<Suspense>
|
||||||
|
<About />
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,12 @@
|
||||||
|
import { Suspense } from "react";
|
||||||
import { FAQ } from "@link-stack/leafcutter-ui";
|
import { FAQ } from "@link-stack/leafcutter-ui";
|
||||||
|
|
||||||
export default function Page() {
|
export const dynamic = "force-dynamic";
|
||||||
return <FAQ />;
|
|
||||||
|
export default async function Page() {
|
||||||
|
return (
|
||||||
|
<Suspense>
|
||||||
|
<FAQ />
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@ import { ReactNode } from "react";
|
||||||
import "app/_styles/global.css";
|
import "app/_styles/global.css";
|
||||||
import { InternalLayout } from "../_components/InternalLayout";
|
import { InternalLayout } from "../_components/InternalLayout";
|
||||||
|
|
||||||
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
type LayoutProps = {
|
type LayoutProps = {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -69,19 +69,17 @@ export const getServerSideProps: GetServerSideProps = async (
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
console.log({ query });
|
|
||||||
const dataResponse = await client.search({
|
const dataResponse = await client.search({
|
||||||
index: "demo_data",
|
index: "demo_data",
|
||||||
size: 200,
|
size: 200,
|
||||||
body: { query },
|
body: { query },
|
||||||
});
|
});
|
||||||
console.log({ dataResponse });
|
|
||||||
res.props.data = dataResponse.body.hits.hits.map((hit) => ({
|
res.props.data = dataResponse.body.hits.hits.map((hit) => ({
|
||||||
id: hit._id,
|
id: hit._id,
|
||||||
...hit._source,
|
...hit._source,
|
||||||
}));
|
}));
|
||||||
console.log({ data: res.props.data });
|
|
||||||
console.log(res.props.data[0]);
|
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ export const Setup: FC = () => {
|
||||||
} = useLeafcutterContext();
|
} = useLeafcutterContext();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
setTimeout(() => router.push("/"), 4000);
|
setTimeout(() => router.push("/"), 2000);
|
||||||
}, [router]);
|
}, [router]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,10 @@
|
||||||
|
import { Suspense } from "react";
|
||||||
import { Setup } from "./_components/Setup";
|
import { Setup } from "./_components/Setup";
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
return <Setup />;
|
return (
|
||||||
|
<Suspense>
|
||||||
|
<Setup />
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,9 @@ const getVisualization = async (visualizationID: string) => {
|
||||||
);
|
);
|
||||||
const hit = hits[0];
|
const hit = hits[0];
|
||||||
const visualization = {
|
const visualization = {
|
||||||
id: hit._id.split(":")[1],
|
id: hit?._id.split(":")[1],
|
||||||
title: hit._source.visualization.title,
|
title: hit?._source?.visualization.title,
|
||||||
description: hit._source.visualization.description,
|
description: hit?._source?.visualization.description,
|
||||||
url: `/app/visualize?security_tenant=global#/edit/${
|
url: `/app/visualize?security_tenant=global#/edit/${
|
||||||
hit._id.split(":")[1]
|
hit._id.split(":")[1]
|
||||||
}?embed=true`,
|
}?embed=true`,
|
||||||
|
|
@ -34,12 +34,13 @@ const getVisualization = async (visualizationID: string) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
type PageProps = {
|
type PageProps = {
|
||||||
params: {
|
params: Promise<{
|
||||||
visualizationID: string;
|
visualizationID: string;
|
||||||
};
|
}>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default async function Page({ params: { visualizationID } }: PageProps) {
|
export default async function Page({ params }: PageProps) {
|
||||||
|
const { visualizationID } = await params;
|
||||||
const visualization = await getVisualization(visualizationID);
|
const visualization = await getVisualization(visualizationID);
|
||||||
|
|
||||||
return <VisualizationDetail {...visualization} editing={false} />;
|
return <VisualizationDetail {...visualization} editing={false} />;
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,6 @@ const MenuItem = ({
|
||||||
return (
|
return (
|
||||||
<Link href={href} passHref>
|
<Link href={href} passHref>
|
||||||
<ListItem
|
<ListItem
|
||||||
button
|
|
||||||
sx={{
|
sx={{
|
||||||
paddingLeft: "62px",
|
paddingLeft: "62px",
|
||||||
backgroundColor: selected ? leafcutterLightBlue : "transparent",
|
backgroundColor: selected ? leafcutterLightBlue : "transparent",
|
||||||
|
|
|
||||||
|
|
@ -22,20 +22,17 @@ export const authOptions: NextAuthOptions = {
|
||||||
Credentials({
|
Credentials({
|
||||||
name: "Link",
|
name: "Link",
|
||||||
credentials: {
|
credentials: {
|
||||||
authToken: { label: "AuthToken", type: "text", },
|
authToken: { label: "AuthToken", type: "text" },
|
||||||
},
|
},
|
||||||
async authorize(credentials, req) {
|
async authorize(credentials, req) {
|
||||||
const { headers } = req;
|
const { headers } = req;
|
||||||
console.log({ headers });
|
|
||||||
const leafcutterUser = headers?.["x-leafcutter-user"];
|
const leafcutterUser = headers?.["x-leafcutter-user"];
|
||||||
const authToken = credentials?.authToken;
|
const authToken = credentials?.authToken;
|
||||||
|
|
||||||
if (!leafcutterUser || leafcutterUser.trim() === "") {
|
if (!leafcutterUser || leafcutterUser.trim() === "") {
|
||||||
console.log("no leafcutter user");
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log({ authToken });
|
|
||||||
return null;
|
return null;
|
||||||
/*
|
/*
|
||||||
try {
|
try {
|
||||||
|
|
@ -48,14 +45,13 @@ export const authOptions: NextAuthOptions = {
|
||||||
|
|
||||||
return user;
|
return user;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log({ e });
|
console.error({ e });
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
*/
|
*/
|
||||||
}
|
},
|
||||||
})
|
}),
|
||||||
|
|
||||||
],
|
],
|
||||||
secret: process.env.NEXTAUTH_SECRET,
|
secret: process.env.NEXTAUTH_SECRET,
|
||||||
/*
|
/*
|
||||||
|
|
@ -77,4 +73,3 @@ export const authOptions: NextAuthOptions = {
|
||||||
}
|
}
|
||||||
},*/
|
},*/
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,8 @@ const userMetadataIndexName = "user_metadata";
|
||||||
|
|
||||||
const baseURL = `https://${process.env.OPENSEARCH_USERNAME}:${process.env.OPENSEARCH_PASSWORD}@${process.env.OPENSEARCH_URL}`;
|
const baseURL = `https://${process.env.OPENSEARCH_USERNAME}:${process.env.OPENSEARCH_PASSWORD}@${process.env.OPENSEARCH_URL}`;
|
||||||
|
|
||||||
const createClient = () => new Client({
|
const createClient = () =>
|
||||||
|
new Client({
|
||||||
node: baseURL,
|
node: baseURL,
|
||||||
auth: {
|
auth: {
|
||||||
username: process.env.OPENSEARCH_USERNAME!,
|
username: process.env.OPENSEARCH_USERNAME!,
|
||||||
|
|
@ -19,9 +20,10 @@ const createClient = () => new Client({
|
||||||
ssl: {
|
ssl: {
|
||||||
rejectUnauthorized: false,
|
rejectUnauthorized: false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const createUserClient = (username: string, password: string) => new Client({
|
const createUserClient = (username: string, password: string) =>
|
||||||
|
new Client({
|
||||||
node: baseURL,
|
node: baseURL,
|
||||||
auth: {
|
auth: {
|
||||||
username,
|
username,
|
||||||
|
|
@ -30,7 +32,7 @@ const createUserClient = (username: string, password: string) => new Client({
|
||||||
ssl: {
|
ssl: {
|
||||||
rejectUnauthorized: false,
|
rejectUnauthorized: false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const checkAuth = async (username: string, password: string) => {
|
export const checkAuth = async (username: string, password: string) => {
|
||||||
const client = createUserClient(username, password);
|
const client = createUserClient(username, password);
|
||||||
|
|
@ -115,7 +117,7 @@ export const getUserMetadata = async (username: string) => {
|
||||||
await client.create({
|
await client.create({
|
||||||
id: username,
|
id: username,
|
||||||
index: userMetadataIndexName,
|
index: userMetadataIndexName,
|
||||||
body: { username, savedSearches: [] }
|
body: { username, savedSearches: [] },
|
||||||
});
|
});
|
||||||
|
|
||||||
res = await client.get({
|
res = await client.get({
|
||||||
|
|
@ -132,7 +134,7 @@ export const saveUserMetadata = async (username: string, metadata: any) => {
|
||||||
await client.update({
|
await client.update({
|
||||||
id: username,
|
id: username,
|
||||||
index: userMetadataIndexName,
|
index: userMetadataIndexName,
|
||||||
body: { doc: { username, ...metadata } }
|
body: { doc: { username, ...metadata } },
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -181,7 +183,7 @@ const getIndexPattern: any = async (index: string) => {
|
||||||
sort: ["updated_at:desc"],
|
sort: ["updated_at:desc"],
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res.body.hits.total.value === 0) {
|
if (res?.body?.hits?.total?.valueOf() === 0) {
|
||||||
// eslint-disable-next-line no-use-before-define
|
// eslint-disable-next-line no-use-before-define
|
||||||
return createCurrentUserIndexPattern(index);
|
return createCurrentUserIndexPattern(index);
|
||||||
}
|
}
|
||||||
|
|
@ -226,7 +228,7 @@ interface createUserVisualizationProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createUserVisualization = async (
|
export const createUserVisualization = async (
|
||||||
props: createUserVisualizationProps
|
props: createUserVisualizationProps,
|
||||||
) => {
|
) => {
|
||||||
const { email, query, visualizationID, title, description } = props;
|
const { email, query, visualizationID, title, description } = props;
|
||||||
const userIndex = await getCurrentUserIndex(email);
|
const userIndex = await getCurrentUserIndex(email);
|
||||||
|
|
@ -279,7 +281,7 @@ interface updateVisualizationProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const updateUserVisualization = async (
|
export const updateUserVisualization = async (
|
||||||
props: updateVisualizationProps
|
props: updateVisualizationProps,
|
||||||
) => {
|
) => {
|
||||||
const { email, id, query, title, description } = props;
|
const { email, id, query, title, description } = props;
|
||||||
const userIndex = await getCurrentUserIndex(email);
|
const userIndex = await getCurrentUserIndex(email);
|
||||||
|
|
@ -300,8 +302,7 @@ export const updateUserVisualization = async (
|
||||||
body,
|
body,
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// eslint-disable-next-line no-console
|
console.error({ e });
|
||||||
console.log({ e });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return id;
|
return id;
|
||||||
|
|
@ -469,10 +470,18 @@ export const performQuery = async (searchQuery: any, limit: number) => {
|
||||||
const results = hits.map((hit: any) => ({
|
const results = hits.map((hit: any) => ({
|
||||||
...hit._source,
|
...hit._source,
|
||||||
id: hit._id,
|
id: hit._id,
|
||||||
incident: Array.isArray(hit._source.incident) ? hit._source.incident.join(", ") : hit._source.incident,
|
incident: Array.isArray(hit._source.incident)
|
||||||
technology: Array.isArray(hit._source.technology) ? hit._source.technology.join(", ") : hit._source.technology,
|
? hit._source.incident.join(", ")
|
||||||
targeted_group: Array.isArray(hit._source.targeted_group) ? hit._source.targeted_group.join(", ") : hit._source.targeted_group,
|
: hit._source.incident,
|
||||||
country: Array.isArray(hit._source.country) ? hit._source.country.join(", ") : hit._source.country,
|
technology: Array.isArray(hit._source.technology)
|
||||||
|
? hit._source.technology.join(", ")
|
||||||
|
: hit._source.technology,
|
||||||
|
targeted_group: Array.isArray(hit._source.targeted_group)
|
||||||
|
? hit._source.targeted_group.join(", ")
|
||||||
|
: hit._source.targeted_group,
|
||||||
|
country: Array.isArray(hit._source.country)
|
||||||
|
? hit._source.country.join(", ")
|
||||||
|
: hit._source.country,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
|
|
@ -570,7 +579,6 @@ export const getTemplates = async (limit: number) => {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const rawResponse = await client.search({
|
const rawResponse = await client.search({
|
||||||
index: globalIndex,
|
index: globalIndex,
|
||||||
size: limit,
|
size: limit,
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,12 @@ import { NextRequest, NextResponse } from "next/server";
|
||||||
|
|
||||||
export const GET = async (req: NextRequest) => {
|
export const GET = async (req: NextRequest) => {
|
||||||
const validDomains = "localhost";
|
const validDomains = "localhost";
|
||||||
console.log({ req });
|
|
||||||
|
|
||||||
return NextResponse.json({ response: "ok" });
|
return NextResponse.json({ response: "ok" });
|
||||||
};
|
};
|
||||||
|
|
||||||
export const POST = async (req: NextRequest) => {
|
export const POST = async (req: NextRequest) => {
|
||||||
const validDomains = "localhost";
|
const validDomains = "localhost";
|
||||||
console.log({ req });
|
|
||||||
|
|
||||||
return NextResponse.json({ response: "ok" });
|
return NextResponse.json({ response: "ok" });
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,8 @@ import { getTrends } from "app/_lib/opensearch";
|
||||||
|
|
||||||
export const GET = async () => {
|
export const GET = async () => {
|
||||||
const results = await getTrends(5);
|
const results = await getTrends(5);
|
||||||
console.log({ results });
|
|
||||||
|
|
||||||
NextResponse.json(results);
|
NextResponse.json(results);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const dynamic = 'force-dynamic';
|
export const dynamic = "force-dynamic";
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,8 @@ export const POST = async (req: NextRequest) => {
|
||||||
rejectUnauthorized: false,
|
rejectUnauthorized: false,
|
||||||
},
|
},
|
||||||
headers: {
|
headers: {
|
||||||
authorization
|
authorization,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const succeeded = [];
|
const succeeded = [];
|
||||||
|
|
@ -28,11 +28,15 @@ export const POST = async (req: NextRequest) => {
|
||||||
const country = ticket.country[0] ?? "none";
|
const country = ticket.country[0] ?? "none";
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
const translatedCountry = taxonomy.country[country]?.display ?? "none";
|
const translatedCountry = taxonomy.country[country]?.display ?? "none";
|
||||||
const countryDetails: any = unRegions.find((c) => c.name === translatedCountry);
|
const countryDetails: any = unRegions.find(
|
||||||
|
(c) => c.name === translatedCountry,
|
||||||
|
);
|
||||||
const augmentedTicket = {
|
const augmentedTicket = {
|
||||||
...ticket,
|
...ticket,
|
||||||
region: countryDetails['sub-region']?.toLowerCase().replace(" ", "-") ?? null,
|
region:
|
||||||
continent: countryDetails.region?.toLowerCase().replace(" ", "-") ?? null,
|
countryDetails["sub-region"]?.toLowerCase().replace(" ", "-") ?? null,
|
||||||
|
continent:
|
||||||
|
countryDetails.region?.toLowerCase().replace(" ", "-") ?? null,
|
||||||
};
|
};
|
||||||
const out = await client.create({
|
const out = await client.create({
|
||||||
id: uuid(),
|
id: uuid(),
|
||||||
|
|
@ -40,10 +44,9 @@ export const POST = async (req: NextRequest) => {
|
||||||
refresh: true,
|
refresh: true,
|
||||||
body: augmentedTicket,
|
body: augmentedTicket,
|
||||||
});
|
});
|
||||||
console.log(out);
|
|
||||||
succeeded.push(id);
|
succeeded.push(id);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
console.error(e);
|
||||||
failed.push(id);
|
failed.push(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -52,4 +55,3 @@ export const POST = async (req: NextRequest) => {
|
||||||
|
|
||||||
return NextResponse.json(results);
|
return NextResponse.json(results);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,13 @@ import { getServerSession } from "next-auth";
|
||||||
import { authOptions } from "app/_lib/auth";
|
import { authOptions } from "app/_lib/auth";
|
||||||
import { deleteUserVisualization } from "app/_lib/opensearch";
|
import { deleteUserVisualization } from "app/_lib/opensearch";
|
||||||
|
|
||||||
export const POST = async (req: NextRequest, res: NextResponse) => {
|
export const POST = async (req: NextRequest) => {
|
||||||
const session = await getServerSession(authOptions);
|
const session = await getServerSession(authOptions);
|
||||||
const { user: { email } }: any = session;
|
const {
|
||||||
|
user: { email },
|
||||||
|
}: any = session;
|
||||||
const { id } = await req.json();
|
const { id } = await req.json();
|
||||||
await deleteUserVisualization(email as string, id);
|
await deleteUserVisualization(email as string, id);
|
||||||
|
|
||||||
return NextResponse.json({ id });
|
return NextResponse.json({ id });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
2
apps/leafcutter/next-env.d.ts
vendored
2
apps/leafcutter/next-env.d.ts
vendored
|
|
@ -3,4 +3,4 @@
|
||||||
/// <reference types="next/navigation-types/compat/navigation" />
|
/// <reference types="next/navigation-types/compat/navigation" />
|
||||||
|
|
||||||
// NOTE: This file should not be edited
|
// NOTE: This file should not be edited
|
||||||
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
|
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,5 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
transpilePackages: ["@link-stack/leafcutter-ui", "@link-stack/opensearch-common"],
|
transpilePackages: ["@link-stack/leafcutter-ui", "@link-stack/opensearch-common"],
|
||||||
experimental: {
|
|
||||||
missingSuspenseWithCSRBailout: false,
|
|
||||||
},
|
|
||||||
poweredByHeader: false,
|
poweredByHeader: false,
|
||||||
rewrites: async () => ({
|
rewrites: async () => ({
|
||||||
fallback: [
|
fallback: [
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@link-stack/leafcutter",
|
"name": "@link-stack/leafcutter",
|
||||||
"version": "2.2.0",
|
"version": "2.4.0b1",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev -p 3001",
|
"dev": "next dev -p 3001",
|
||||||
"login": "aws sso login --sso-session cdr",
|
"login": "aws sso login --sso-session cdr",
|
||||||
|
|
@ -13,37 +13,37 @@
|
||||||
"lint": "next lint"
|
"lint": "next lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emotion/cache": "^11.13.1",
|
"@emotion/cache": "^11.14.0",
|
||||||
"@emotion/react": "^11.13.3",
|
"@emotion/react": "^11.14.0",
|
||||||
"@emotion/server": "^11.11.0",
|
"@emotion/server": "^11.11.0",
|
||||||
"@emotion/styled": "^11.13.0",
|
"@emotion/styled": "^11.14.0",
|
||||||
"@link-stack/leafcutter-ui": "*",
|
"@link-stack/leafcutter-ui": "*",
|
||||||
"@link-stack/opensearch-common": "*",
|
"@link-stack/opensearch-common": "*",
|
||||||
"@mui/icons-material": "^5",
|
"@mui/icons-material": "^6",
|
||||||
"@mui/material": "^5",
|
"@mui/material": "^6",
|
||||||
"@mui/material-nextjs": "^5",
|
"@mui/material-nextjs": "^6",
|
||||||
"@mui/x-date-pickers-pro": "^7.18.0",
|
"@mui/x-date-pickers-pro": "^7.24.1",
|
||||||
"@opensearch-project/opensearch": "^2.12.0",
|
"@opensearch-project/opensearch": "^3.2.0",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"http-proxy-middleware": "^3.0.2",
|
"http-proxy-middleware": "^3.0.3",
|
||||||
"material-ui-popup-state": "^5.3.1",
|
"material-ui-popup-state": "^5.3.3",
|
||||||
"next": "14.2.13",
|
"next": "15.1.6",
|
||||||
"next-auth": "^4.24.8",
|
"next-auth": "^4.24.11",
|
||||||
"react": "18.3.1",
|
"react": "19.0.0",
|
||||||
"react-cookie": "^7.2.0",
|
"react-cookie": "^7.2.2",
|
||||||
"react-cookie-consent": "^9.0.0",
|
"react-cookie-consent": "^9.0.0",
|
||||||
"react-dom": "18.3.1",
|
"react-dom": "19.0.0",
|
||||||
"react-iframe": "^1.8.5",
|
"react-iframe": "^1.8.5",
|
||||||
"react-polyglot": "^0.7.2",
|
"react-polyglot": "^0.7.2",
|
||||||
"sharp": "^0.33.5",
|
"sharp": "^0.33.5",
|
||||||
"uuid": "^10.0.0"
|
"uuid": "^11.0.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^22.7.3",
|
"@types/node": "^22.10.10",
|
||||||
"@types/react": "18.3.9",
|
"@types/react": "19.0.8",
|
||||||
"@types/uuid": "^10.0.0",
|
"@types/uuid": "^10.0.0",
|
||||||
"@link-stack/eslint-config": "*",
|
"@link-stack/eslint-config": "*",
|
||||||
"@link-stack/typescript-config": "*",
|
"@link-stack/typescript-config": "*",
|
||||||
"typescript": "5.6.2"
|
"typescript": "5.7.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,17 +24,17 @@ const withAuthInfo =
|
||||||
const requestSignature = req.query.signature;
|
const requestSignature = req.query.signature;
|
||||||
const url = new URL(req.headers.referer as string);
|
const url = new URL(req.headers.referer as string);
|
||||||
const referrerSignature = url.searchParams.get("signature");
|
const referrerSignature = url.searchParams.get("signature");
|
||||||
|
|
||||||
console.log({ requestSignature, referrerSignature });
|
|
||||||
const isAppPath = !!req.url?.startsWith("/app");
|
const isAppPath = !!req.url?.startsWith("/app");
|
||||||
const isResourcePath = !!req.url?.match(/\/(api|app|bootstrap|3961|ui|translations|internal|login|node_modules)/);
|
const isResourcePath = !!req.url?.match(
|
||||||
|
/\/(api|app|bootstrap|3961|ui|translations|internal|login|node_modules)/,
|
||||||
|
);
|
||||||
|
|
||||||
if (requestSignature && isAppPath) {
|
if (requestSignature && isAppPath) {
|
||||||
console.log("Has Signature");
|
console.info("Has Signature");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (referrerSignature && isResourcePath) {
|
if (referrerSignature && isResourcePath) {
|
||||||
console.log("Has Signature");
|
console.info("Has Signature");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!email) {
|
if (!email) {
|
||||||
|
|
|
||||||
2
apps/leafcutter/public/robots.txt
Normal file
2
apps/leafcutter/public/robots.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
User-agent: *
|
||||||
|
Disallow: /
|
||||||
|
|
@ -29,6 +29,7 @@ export const Login: FC<LoginProps> = ({ session }) => {
|
||||||
typeof window !== "undefined" && window.location.origin
|
typeof window !== "undefined" && window.location.origin
|
||||||
? window.location.origin
|
? window.location.origin
|
||||||
: "";
|
: "";
|
||||||
|
const callbackUrl = `${origin}/setup`;
|
||||||
const [provider, setProvider] = useState(undefined);
|
const [provider, setProvider] = useState(undefined);
|
||||||
const [email, setEmail] = useState("");
|
const [email, setEmail] = useState("");
|
||||||
const [password, setPassword] = useState("");
|
const [password, setPassword] = useState("");
|
||||||
|
|
@ -157,7 +158,7 @@ export const Login: FC<LoginProps> = ({ session }) => {
|
||||||
sx={buttonStyles}
|
sx={buttonStyles}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
signIn("google", {
|
signIn("google", {
|
||||||
callbackUrl: `${origin}`,
|
callbackUrl,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|
@ -173,7 +174,7 @@ export const Login: FC<LoginProps> = ({ session }) => {
|
||||||
sx={buttonStyles}
|
sx={buttonStyles}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
signIn("apple", {
|
signIn("apple", {
|
||||||
callbackUrl: `${window.location.origin}`,
|
callbackUrl,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|
@ -214,7 +215,7 @@ export const Login: FC<LoginProps> = ({ session }) => {
|
||||||
signIn("credentials", {
|
signIn("credentials", {
|
||||||
email,
|
email,
|
||||||
password,
|
password,
|
||||||
callbackUrl: `${origin}/setup`,
|
callbackUrl,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { ReactNode } from "react";
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
|
|
||||||
type ClientOnlyProps = { children: JSX.Element };
|
type ClientOnlyProps = { children: ReactNode };
|
||||||
const ClientOnly = (props: ClientOnlyProps) => {
|
const ClientOnly = (props: ClientOnlyProps) => {
|
||||||
const { children } = props;
|
const { children } = props;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,10 @@
|
||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
import { OpenSearchWrapper } from "@link-stack/leafcutter-ui";
|
import { OpenSearchWrapper } from "@link-stack/leafcutter-ui";
|
||||||
|
|
||||||
export const Home: FC = () => (
|
type HomeProps = {
|
||||||
<OpenSearchWrapper
|
url: string;
|
||||||
url="/app/visualize#/edit/237b8f00-e6a0-11ee-94b3-d7b7409294e7?embed=true"
|
};
|
||||||
marginTop="0"
|
|
||||||
/>
|
export const Home: FC<HomeProps> = ({ url }) => (
|
||||||
|
<OpenSearchWrapper url={url} margin={0} />
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -501,7 +501,17 @@ export const Sidebar: FC<SidebarProps> = ({
|
||||||
selected={pathname.endsWith("/reporting")}
|
selected={pathname.endsWith("/reporting")}
|
||||||
open={open}
|
open={open}
|
||||||
/>
|
/>
|
||||||
{leafcutterEnabled && (
|
{roles.includes("admin") && leafcutterEnabled && (
|
||||||
|
<MenuItem
|
||||||
|
name="Opensearch"
|
||||||
|
href="/opensearch"
|
||||||
|
Icon={InsightsIcon}
|
||||||
|
iconSize={20}
|
||||||
|
selected={pathname.startsWith("/opensearch")}
|
||||||
|
open={open}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{false && leafcutterEnabled && (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
name="Leafcutter"
|
name="Leafcutter"
|
||||||
href="/leafcutter"
|
href="/leafcutter"
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,6 @@ export const ZammadWrapper: FC<ZammadWrapperProps> = ({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
redirect: "manual",
|
redirect: "manual",
|
||||||
});
|
});
|
||||||
console.log({ res });
|
|
||||||
if (res.type === "opaqueredirect") {
|
if (res.type === "opaqueredirect") {
|
||||||
setAuthenticated(true);
|
setAuthenticated(true);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -69,7 +68,6 @@ export const ZammadWrapper: FC<ZammadWrapperProps> = ({
|
||||||
}, [session]);
|
}, [session]);
|
||||||
|
|
||||||
if (!session || !authenticated) {
|
if (!session || !authenticated) {
|
||||||
console.log("Not authenticated");
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ width: "100%" }}>
|
<Box sx={{ width: "100%" }}>
|
||||||
<Grid
|
<Grid
|
||||||
|
|
@ -89,7 +87,6 @@ export const ZammadWrapper: FC<ZammadWrapperProps> = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
if (session && authenticated) {
|
if (session && authenticated) {
|
||||||
console.log("Session and authenticated");
|
|
||||||
return (
|
return (
|
||||||
<Iframe
|
<Iframe
|
||||||
id={id}
|
id={id}
|
||||||
|
|
@ -102,10 +99,6 @@ export const ZammadWrapper: FC<ZammadWrapperProps> = ({
|
||||||
const linkElement = document.querySelector(
|
const linkElement = document.querySelector(
|
||||||
`#${id}`,
|
`#${id}`,
|
||||||
) as HTMLIFrameElement;
|
) as HTMLIFrameElement;
|
||||||
|
|
||||||
console.log({ path });
|
|
||||||
console.log({ id });
|
|
||||||
console.log({ linkElement });
|
|
||||||
if (
|
if (
|
||||||
linkElement.contentDocument &&
|
linkElement.contentDocument &&
|
||||||
linkElement.contentDocument?.querySelector &&
|
linkElement.contentDocument?.querySelector &&
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
import { Create } from "@link-stack/bridge-ui";
|
import { Create } from "@link-stack/bridge-ui";
|
||||||
|
|
||||||
type PageProps = {
|
type PageProps = {
|
||||||
params: { segment: string[] };
|
params: Promise<{ segment: string[] }>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function Page({ params: { segment } }: PageProps) {
|
export default async function Page({ params }: PageProps) {
|
||||||
|
const { segment } = await params;
|
||||||
const service = segment[0];
|
const service = segment[0];
|
||||||
|
|
||||||
return <Create service={service} />;
|
return <Create service={service} />;
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
import { db } from "@link-stack/bridge-common";
|
import { db } from "@link-stack/bridge-common";
|
||||||
import { serviceConfig, Detail } from "@link-stack/bridge-ui";
|
import { serviceConfig, Detail } from "@link-stack/bridge-ui";
|
||||||
|
|
||||||
type Props = {
|
type PageProps = {
|
||||||
params: { segment: string[] };
|
params: Promise<{ segment: string[] }>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default async function Page({ params: { segment } }: Props) {
|
export default async function Page({ params }: PageProps) {
|
||||||
|
const { segment } = await params;
|
||||||
const service = segment[0];
|
const service = segment[0];
|
||||||
const id = segment?.[1];
|
const id = segment?.[1];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,11 @@ import { db } from "@link-stack/bridge-common";
|
||||||
import { serviceConfig, Edit } from "@link-stack/bridge-ui";
|
import { serviceConfig, Edit } from "@link-stack/bridge-ui";
|
||||||
|
|
||||||
type PageProps = {
|
type PageProps = {
|
||||||
params: { segment: string[] };
|
params: Promise<{ segment: string[] }>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default async function Page({ params: { segment } }: PageProps) {
|
export default async function Page({ params }: PageProps) {
|
||||||
|
const { segment } = await params;
|
||||||
const service = segment[0];
|
const service = segment[0];
|
||||||
const id = segment?.[1];
|
const id = segment?.[1];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,13 @@ import { db } from "@link-stack/bridge-common";
|
||||||
import { serviceConfig, List } from "@link-stack/bridge-ui";
|
import { serviceConfig, List } from "@link-stack/bridge-ui";
|
||||||
|
|
||||||
type PageProps = {
|
type PageProps = {
|
||||||
params: {
|
params: Promise<{
|
||||||
segment: string[];
|
segment: string[];
|
||||||
};
|
}>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default async function Page({ params: { segment } }: PageProps) {
|
export default async function Page({ params }: PageProps) {
|
||||||
|
const { segment } = await params;
|
||||||
const service = segment[0];
|
const service = segment[0];
|
||||||
|
|
||||||
if (!service) return null;
|
if (!service) return null;
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,20 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect } from "react";
|
||||||
import { signOut } from "next-auth/react";
|
import { signOut } from "next-auth/react";
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
|
useEffect(() => {
|
||||||
|
const multistepSignOut = async () => {
|
||||||
|
const response = await fetch("/api/logout", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
credentials: "same-origin",
|
||||||
|
});
|
||||||
signOut({ callbackUrl: "/login" });
|
signOut({ callbackUrl: "/login" });
|
||||||
|
};
|
||||||
|
multistepSignOut();
|
||||||
|
}, []);
|
||||||
|
|
||||||
return <div />;
|
return <div />;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
5
apps/link/app/(main)/opensearch/page.tsx
Normal file
5
apps/link/app/(main)/opensearch/page.tsx
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
import { OpenSearchWrapper } from "@link-stack/leafcutter-ui";
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return <OpenSearchWrapper url="/app/visualize#/" margin={50} />;
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { FC, useState, useEffect } from "react";
|
import { FC, useState, useEffect, useActionState } from "react";
|
||||||
import { useFormState } from "react-dom";
|
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { Grid } from "@mui/material";
|
import { Grid } from "@mui/material";
|
||||||
import {
|
import {
|
||||||
|
|
@ -44,7 +43,7 @@ export const TicketCreateDialog: FC<TicketCreateDialogProps> = ({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const [formState, formAction] = useFormState(
|
const [formState, formAction] = useActionState(
|
||||||
createTicketAction,
|
createTicketAction,
|
||||||
initialState,
|
initialState,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,6 @@ export const TicketList: FC<TicketListProps> = ({ title, tickets }) => {
|
||||||
columns={gridColumns}
|
columns={gridColumns}
|
||||||
onRowClick={onRowClick}
|
onRowClick={onRowClick}
|
||||||
getRowID={(row: any) => {
|
getRowID={(row: any) => {
|
||||||
console.log({ row });
|
|
||||||
return row.internalId;
|
return row.internalId;
|
||||||
}}
|
}}
|
||||||
buttons={
|
buttons={
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { FC, useEffect, useState } from "react";
|
import { FC, useEffect, useState } from "react";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
import { getOverviewTicketsAction } from "app/_actions/overviews";
|
import { getOverviewTicketsAction } from "app/_actions/overviews";
|
||||||
|
|
||||||
import { TicketList } from "./TicketList";
|
import { TicketList } from "./TicketList";
|
||||||
|
|
@ -12,6 +13,19 @@ type ZammadOverviewProps = {
|
||||||
export const ZammadOverview: FC<ZammadOverviewProps> = ({ name }) => {
|
export const ZammadOverview: FC<ZammadOverviewProps> = ({ name }) => {
|
||||||
const [tickets, setTickets] = useState([]);
|
const [tickets, setTickets] = useState([]);
|
||||||
|
|
||||||
|
if (typeof window !== "undefined") {
|
||||||
|
useEffect(() => {
|
||||||
|
const hash = window?.location?.hash;
|
||||||
|
|
||||||
|
if (hash) {
|
||||||
|
const ticketID = hash.replace("#ticket/zoom/", "");
|
||||||
|
if (ticketID && !isNaN(parseInt(ticketID, 10))) {
|
||||||
|
redirect(`/tickets/${ticketID}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [window?.location?.hash]);
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchTickets = async () => {
|
const fetchTickets = async () => {
|
||||||
const { tickets } = await getOverviewTicketsAction(name);
|
const { tickets } = await getOverviewTicketsAction(name);
|
||||||
|
|
|
||||||
|
|
@ -6,14 +6,15 @@ const getSection = (overview: string) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
type MetadataProps = {
|
type MetadataProps = {
|
||||||
params: {
|
params: Promise<{
|
||||||
overview: string;
|
overview: string;
|
||||||
};
|
}>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function generateMetadata({
|
export async function generateMetadata({
|
||||||
params: { overview },
|
params,
|
||||||
}: MetadataProps): Promise<Metadata> {
|
}: MetadataProps): Promise<Metadata> {
|
||||||
|
const { overview } = await params;
|
||||||
const section = getSection(overview);
|
const section = getSection(overview);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -22,12 +23,13 @@ export async function generateMetadata({
|
||||||
}
|
}
|
||||||
|
|
||||||
type PageProps = {
|
type PageProps = {
|
||||||
params: {
|
params: Promise<{
|
||||||
overview: string;
|
overview: string;
|
||||||
};
|
}>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function Page({ params: { overview } }: PageProps) {
|
export default async function Page({ params }: PageProps) {
|
||||||
|
const { overview } = await params;
|
||||||
const section = getSection(overview);
|
const section = getSection(overview);
|
||||||
|
|
||||||
return <ZammadOverview name={section} />;
|
return <ZammadOverview name={section} />;
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
import { Metadata } from "next";
|
import { Metadata } from "next";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import { getServerSession } from "app/_lib/authentication";
|
import { Home } from "./_components/Home";
|
||||||
import { Home } from "@link-stack/leafcutter-ui";
|
// import { getServerSession } from "app/_lib/authentication";
|
||||||
import { getUserVisualizations } from "@link-stack/opensearch-common";
|
// import { Home } from "@link-stack/leafcutter-ui";
|
||||||
import { LeafcutterWrapper } from "@link-stack/leafcutter-ui";
|
// import { getUserVisualizations } from "@link-stack/opensearch-common";
|
||||||
|
// import { LeafcutterWrapper } from "@link-stack/leafcutter-ui";
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "CDR Link - Home",
|
title: "CDR Link - Home",
|
||||||
|
|
@ -11,21 +12,31 @@ export const metadata: Metadata = {
|
||||||
|
|
||||||
export default async function Page() {
|
export default async function Page() {
|
||||||
const leafcutterEnabled = process.env.LEAFCUTTER_ENABLED === "true";
|
const leafcutterEnabled = process.env.LEAFCUTTER_ENABLED === "true";
|
||||||
|
const dashboardURL = process.env.LEAFCUTTER_DEFAULT_DASHBOARD_URL;
|
||||||
|
|
||||||
if (!leafcutterEnabled) {
|
if (!leafcutterEnabled) {
|
||||||
redirect("/overview/recent");
|
redirect("/overview/recent");
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
const session = await getServerSession();
|
const session = await getServerSession();
|
||||||
const {
|
const {
|
||||||
user: { email },
|
user: { email },
|
||||||
}: any = session;
|
}: any = session;
|
||||||
|
*/
|
||||||
const visualizations = await getUserVisualizations(email ?? "none", 20);
|
let visualizations = [];
|
||||||
|
/*
|
||||||
|
try {
|
||||||
|
visualizations = await getUserVisualizations(email ?? "none", 20);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e.meta);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LeafcutterWrapper>
|
<LeafcutterWrapper>
|
||||||
<Home visualizations={visualizations} showWelcome={false} />
|
<Home visualizations={visualizations} showWelcome={false} />
|
||||||
</LeafcutterWrapper>
|
</LeafcutterWrapper>
|
||||||
);
|
);
|
||||||
|
*/
|
||||||
|
|
||||||
|
return <Home url={dashboardURL} />;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,13 @@ import { ZammadWrapper } from "app/(main)/_components/ZammadWrapper";
|
||||||
export const Setup: FC = () => {
|
export const Setup: FC = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
|
const fingerprint = localStorage.getItem("fingerprint");
|
||||||
|
if (!fingerprint || fingerprint === "") {
|
||||||
|
const newFingerprint = `${Math.floor(
|
||||||
|
Math.random() * 100000000,
|
||||||
|
)}`.padStart(8, "0");
|
||||||
|
localStorage.setItem("fingerprint", newFingerprint);
|
||||||
|
}
|
||||||
setTimeout(() => router.push("/"), 4000);
|
setTimeout(() => router.push("/"), 4000);
|
||||||
}, [router]);
|
}, [router]);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
import { TicketDetail } from "./_components/TicketDetail";
|
import { TicketDetail } from "./_components/TicketDetail";
|
||||||
|
|
||||||
type PageProps = {
|
type PageProps = {
|
||||||
params: {
|
params: Promise<{
|
||||||
id: string;
|
id: string;
|
||||||
};
|
}>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function Page({ params: { id } }: PageProps) {
|
export default async function Page({ params }: PageProps) {
|
||||||
|
const { id } = await params;
|
||||||
|
|
||||||
return <TicketDetail id={id} />;
|
return <TicketDetail id={id} />;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,14 +37,10 @@ export const TicketEdit: FC<TicketEditProps> = ({ id }) => {
|
||||||
const [agents, setAgents] = useState<any>();
|
const [agents, setAgents] = useState<any>();
|
||||||
const [pendingVisible, setPendingVisible] = useState(false);
|
const [pendingVisible, setPendingVisible] = useState(false);
|
||||||
|
|
||||||
const filteredStates =
|
|
||||||
ticketStates?.filter(
|
|
||||||
(state: any) => !["new", "merged", "removed"].includes(state.label),
|
|
||||||
) ?? [];
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchAgents = async () => {
|
const fetchAgents = async () => {
|
||||||
const result = await getAgentsAction();
|
const groupID = formState?.values?.group?.split("/")?.pop();
|
||||||
|
const result = await getAgentsAction(groupID);
|
||||||
setAgents(result);
|
setAgents(result);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -67,7 +63,7 @@ export const TicketEdit: FC<TicketEditProps> = ({ id }) => {
|
||||||
fetchTicketPriorities();
|
fetchTicketPriorities();
|
||||||
fetchAgents();
|
fetchAgents();
|
||||||
fetchGroups();
|
fetchGroups();
|
||||||
}, []);
|
}, [formState.values.group]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchTicket = async () => {
|
const fetchTicket = async () => {
|
||||||
|
|
@ -95,7 +91,7 @@ export const TicketEdit: FC<TicketEditProps> = ({ id }) => {
|
||||||
[name]: value,
|
[name]: value,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const stateName = filteredStates?.find(
|
const stateName = ticketStates?.find(
|
||||||
(state: any) => state.id === formState.values.state,
|
(state: any) => state.id === formState.values.state,
|
||||||
)?.name;
|
)?.name;
|
||||||
setPendingVisible(stateName?.includes("pending") ?? false);
|
setPendingVisible(stateName?.includes("pending") ?? false);
|
||||||
|
|
@ -140,7 +136,7 @@ export const TicketEdit: FC<TicketEditProps> = ({ id }) => {
|
||||||
label="State"
|
label="State"
|
||||||
formState={formState}
|
formState={formState}
|
||||||
updateFormState={updateFormState}
|
updateFormState={updateFormState}
|
||||||
getOptions={() => filteredStates}
|
getOptions={() => ticketStates}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid
|
<Grid
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
import { TicketEdit } from "./_components/TicketEdit";
|
import { TicketEdit } from "./_components/TicketEdit";
|
||||||
|
|
||||||
type PageProps = {
|
type PageProps = {
|
||||||
params: {
|
params: Promise<{
|
||||||
id: string;
|
id: string;
|
||||||
};
|
}>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function Page({ params: { id } }: PageProps) {
|
export default async function Page({ params }: PageProps) {
|
||||||
|
const { id } = await params;
|
||||||
|
|
||||||
return <TicketEdit id={id} />;
|
return <TicketEdit id={id} />;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,11 @@
|
||||||
"use client";
|
|
||||||
|
|
||||||
import { Grid } from "@mui/material";
|
import { Grid } from "@mui/material";
|
||||||
|
|
||||||
type LayoutProps = {
|
type LayoutProps = {
|
||||||
detail: any;
|
detail: any;
|
||||||
edit: any;
|
edit: any;
|
||||||
params: {
|
|
||||||
id: string;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function Layout({ detail, edit, params: { id } }: LayoutProps) {
|
export default async function Layout({ detail, edit }: LayoutProps) {
|
||||||
return (
|
return (
|
||||||
<Grid container spacing={0} sx={{ height: "100vh" }} direction="row">
|
<Grid container spacing={0} sx={{ height: "100vh" }} direction="row">
|
||||||
<Grid item sx={{ height: "100vh" }} xs={9}>
|
<Grid item sx={{ height: "100vh" }} xs={9}>
|
||||||
|
|
|
||||||
|
|
@ -47,10 +47,10 @@ export const getOverviewTicketsAction = async (name: string) => {
|
||||||
try {
|
try {
|
||||||
if (name === "Recent") {
|
if (name === "Recent") {
|
||||||
const recent = await executeREST({ path: "/api/v1/recent_view" });
|
const recent = await executeREST({ path: "/api/v1/recent_view" });
|
||||||
|
const uniqueIDs = new Set(recent.map((rec: any) => rec.o_id));
|
||||||
for (const rec of recent) {
|
for (const id of uniqueIDs) {
|
||||||
const tkt = await executeREST({
|
const tkt = await executeREST({
|
||||||
path: `/api/v1/tickets/${rec.o_id}`,
|
path: `/api/v1/tickets/${id}`,
|
||||||
});
|
});
|
||||||
tickets.push({
|
tickets.push({
|
||||||
...tkt,
|
...tkt,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
"use server";
|
"use server";
|
||||||
|
|
||||||
import { revalidatePath } from "next/cache";
|
|
||||||
import { getTicketQuery } from "app/_graphql/getTicketQuery";
|
import { getTicketQuery } from "app/_graphql/getTicketQuery";
|
||||||
import { getTicketArticlesQuery } from "app/_graphql/getTicketArticlesQuery";
|
import { getTicketArticlesQuery } from "app/_graphql/getTicketArticlesQuery";
|
||||||
import { createTicketMutation } from "app/_graphql/createTicketMutation";
|
import { createTicketMutation } from "app/_graphql/createTicketMutation";
|
||||||
|
|
@ -75,7 +74,6 @@ export const updateTicketAction = async (
|
||||||
ticketID: string,
|
ticketID: string,
|
||||||
ticketInfo: Record<string, any>,
|
ticketInfo: Record<string, any>,
|
||||||
) => {
|
) => {
|
||||||
console.log({ ticketID, ticketInfo });
|
|
||||||
try {
|
try {
|
||||||
const input = {};
|
const input = {};
|
||||||
if (ticketInfo.state) {
|
if (ticketInfo.state) {
|
||||||
|
|
@ -158,13 +156,12 @@ export const getTicketStatesAction = async () => {
|
||||||
const states = await executeREST({
|
const states = await executeREST({
|
||||||
path: "/api/v1/ticket_states",
|
path: "/api/v1/ticket_states",
|
||||||
});
|
});
|
||||||
|
|
||||||
const formattedStates =
|
const formattedStates =
|
||||||
states?.map((state: any) => ({
|
states?.map((state: any) => ({
|
||||||
value: `gid://zammad/Ticket::State/${state.id}`,
|
value: `gid://zammad/Ticket::State/${state.id}`,
|
||||||
label: state.name,
|
label: state.name,
|
||||||
|
disabled: ["new", "merged", "removed"].includes(state.name),
|
||||||
})) ?? [];
|
})) ?? [];
|
||||||
|
|
||||||
return formattedStates;
|
return formattedStates;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e.message);
|
console.error(e.message);
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,16 @@
|
||||||
|
|
||||||
import { executeREST } from "app/_lib/zammad";
|
import { executeREST } from "app/_lib/zammad";
|
||||||
|
|
||||||
export const getAgentsAction = async () => {
|
export const getAgentsAction = async (groupID: number) => {
|
||||||
try {
|
try {
|
||||||
const users = await executeREST({
|
const group = await executeREST({
|
||||||
path: "/api/v1/users",
|
path: `/api/v1/groups/${groupID}`,
|
||||||
});
|
});
|
||||||
|
const { user_ids: groupUserIDs } = group;
|
||||||
|
const path = `/api/v1/users/search?query=role_ids:2&limit=1000`;
|
||||||
|
const users = await executeREST({ path });
|
||||||
const agents =
|
const agents =
|
||||||
users?.filter((user: any) => user.role_ids.includes(2)) ?? [];
|
users?.filter((user: any) => groupUserIDs.includes(user.id)) ?? [];
|
||||||
const formattedAgents = agents
|
const formattedAgents = agents
|
||||||
.map((agent: any) => ({
|
.map((agent: any) => ({
|
||||||
label: `${agent.firstname} ${agent.lastname}`,
|
label: `${agent.firstname} ${agent.lastname}`,
|
||||||
|
|
@ -49,7 +52,6 @@ export const getUsersAction = async () => {
|
||||||
const users = await executeREST({
|
const users = await executeREST({
|
||||||
path: "/api/v1/users",
|
path: "/api/v1/users",
|
||||||
});
|
});
|
||||||
console.log({ users });
|
|
||||||
const formattedUsers = users
|
const formattedUsers = users
|
||||||
.map((customer: any) => ({
|
.map((customer: any) => ({
|
||||||
label: customer.login,
|
label: customer.login,
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ export const ZammadLoginProvider: FC<PropsWithChildren> = ({ children }) => {
|
||||||
const response = await fetch("/api/v1/users/me", {
|
const response = await fetch("/api/v1/users/me", {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
"X-Browser-Fingerprint": `${session.expires}`,
|
"X-Browser-Fingerprint": localStorage.getItem("fingerprint") || "",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import {
|
||||||
import Google from "next-auth/providers/google";
|
import Google from "next-auth/providers/google";
|
||||||
import Credentials from "next-auth/providers/credentials";
|
import Credentials from "next-auth/providers/credentials";
|
||||||
import Apple from "next-auth/providers/apple";
|
import Apple from "next-auth/providers/apple";
|
||||||
|
import { Redis } from "ioredis";
|
||||||
|
|
||||||
const headers = { Authorization: `Token ${process.env.ZAMMAD_API_TOKEN}` };
|
const headers = { Authorization: `Token ${process.env.ZAMMAD_API_TOKEN}` };
|
||||||
|
|
||||||
|
|
@ -36,6 +37,9 @@ const fetchUser = async (email: string) => {
|
||||||
const getUserRoles = async (email: string) => {
|
const getUserRoles = async (email: string) => {
|
||||||
try {
|
try {
|
||||||
const user = await fetchUser(email);
|
const user = await fetchUser(email);
|
||||||
|
if (!user) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
const allRoles = await fetchRoles();
|
const allRoles = await fetchRoles();
|
||||||
const roles = user.role_ids.map((roleID: number) => {
|
const roles = user.role_ids.map((roleID: number) => {
|
||||||
const role = allRoles[roleID];
|
const role = allRoles[roleID];
|
||||||
|
|
@ -43,7 +47,7 @@ const getUserRoles = async (email: string) => {
|
||||||
});
|
});
|
||||||
return roles.filter((role: string) => role !== null);
|
return roles.filter((role: string) => role !== null);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log({ e });
|
console.error({ e });
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -109,6 +113,9 @@ export const authOptions: NextAuthOptions = {
|
||||||
signOut: "/logout",
|
signOut: "/logout",
|
||||||
},
|
},
|
||||||
providers,
|
providers,
|
||||||
|
session: {
|
||||||
|
maxAge: 3 * 24 * 60 * 60,
|
||||||
|
},
|
||||||
secret: process.env.NEXTAUTH_SECRET,
|
secret: process.env.NEXTAUTH_SECRET,
|
||||||
callbacks: {
|
callbacks: {
|
||||||
signIn: async ({ user }) => {
|
signIn: async ({ user }) => {
|
||||||
|
|
@ -116,6 +123,11 @@ export const authOptions: NextAuthOptions = {
|
||||||
return roles.includes("admin") || roles.includes("agent");
|
return roles.includes("admin") || roles.includes("agent");
|
||||||
},
|
},
|
||||||
session: async ({ session, token }) => {
|
session: async ({ session, token }) => {
|
||||||
|
const redis = new Redis(process.env.REDIS_URL);
|
||||||
|
const isInvalidated = await redis.get(`invalidated:${token.sub}`);
|
||||||
|
if (isInvalidated) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
session.user.roles = token.roles ?? [];
|
session.user.roles = token.roles ?? [];
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
|
|
||||||
|
|
@ -13,21 +13,18 @@ export const fetchLeafcutter = async (url: string, options: any) => {
|
||||||
const json = await res.json();
|
const json = await res.json();
|
||||||
return json;
|
return json;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log({ error });
|
console.error({ error });
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const data = await fetchData(url, options);
|
const data = await fetchData(url, options);
|
||||||
console.log({ data });
|
|
||||||
|
|
||||||
if (!data) {
|
if (!data) {
|
||||||
const csrfURL = `${process.env.NEXT_PUBLIC_LEAFCUTTER_URL}/api/auth/csrf`;
|
const csrfURL = `${process.env.NEXT_PUBLIC_LEAFCUTTER_URL}/api/auth/csrf`;
|
||||||
const csrfData = await fetchData(csrfURL, {});
|
const csrfData = await fetchData(csrfURL, {});
|
||||||
console.log({ csrfData });
|
|
||||||
const authURL = `${process.env.NEXT_PUBLIC_LEAFCUTTER_URL}/api/auth/callback/credentials`;
|
const authURL = `${process.env.NEXT_PUBLIC_LEAFCUTTER_URL}/api/auth/callback/credentials`;
|
||||||
const authData = await fetchData(authURL, { method: "POST" });
|
const authData = await fetchData(authURL, { method: "POST" });
|
||||||
console.log({ authData });
|
|
||||||
if (!authData) {
|
if (!authData) {
|
||||||
return null;
|
return null;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -37,5 +34,3 @@ export const fetchLeafcutter = async (url: string, options: any) => {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,11 @@ import { getServerSession } from "app/_lib/authentication";
|
||||||
import { cookies } from "next/headers";
|
import { cookies } from "next/headers";
|
||||||
|
|
||||||
const getHeaders = async () => {
|
const getHeaders = async () => {
|
||||||
const allCookies = cookies().getAll();
|
const allCookies = (await cookies()).getAll();
|
||||||
const session = await getServerSession();
|
const session = await getServerSession();
|
||||||
const headers = {
|
const finalHeaders = {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
Accept: "application/json",
|
Accept: "application/json",
|
||||||
"X-Browser-Fingerprint": `${session.expires}`,
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
"X-CSRF-Token": session.user.zammadCsrfToken,
|
"X-CSRF-Token": session.user.zammadCsrfToken,
|
||||||
Cookie: allCookies
|
Cookie: allCookies
|
||||||
|
|
@ -15,7 +14,7 @@ const getHeaders = async () => {
|
||||||
.join("; "),
|
.join("; "),
|
||||||
};
|
};
|
||||||
|
|
||||||
return headers;
|
return finalHeaders;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface ExecuteGraphQLOptions {
|
interface ExecuteGraphQLOptions {
|
||||||
|
|
|
||||||
38
apps/link/app/api/logout/route.ts
Normal file
38
apps/link/app/api/logout/route.ts
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
import { Redis } from "ioredis";
|
||||||
|
import { getToken } from "next-auth/jwt";
|
||||||
|
|
||||||
|
export async function POST(request: NextRequest) {
|
||||||
|
const token = await getToken({
|
||||||
|
req: request,
|
||||||
|
secret: process.env.NEXTAUTH_SECRET,
|
||||||
|
});
|
||||||
|
const allCookies = request.cookies.getAll();
|
||||||
|
const zammadURL = process.env.ZAMMAD_URL ?? "http://zammad-nginx:8080";
|
||||||
|
const signOutURL = `${zammadURL}/api/v1/signout`;
|
||||||
|
const headers = {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Accept: "application/json",
|
||||||
|
Cookie: allCookies
|
||||||
|
.map((cookie) => `${cookie.name}=${cookie.value}`)
|
||||||
|
.join("; "),
|
||||||
|
};
|
||||||
|
|
||||||
|
await fetch(signOutURL, { headers });
|
||||||
|
|
||||||
|
const cookiePrefixesToRemove = ["_zammad"];
|
||||||
|
const response = NextResponse.json({ message: "ok" });
|
||||||
|
|
||||||
|
for (const cookie of allCookies) {
|
||||||
|
if (
|
||||||
|
cookiePrefixesToRemove.some((prefix) => cookie.name.startsWith(prefix))
|
||||||
|
) {
|
||||||
|
response.cookies.set(cookie.name, "", { path: "/", maxAge: 0 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const redis = new Redis(process.env.REDIS_URL);
|
||||||
|
await redis.setex(`invalidated:${token.sub}`, 24 * 60 * 60, "1");
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
@ -12,9 +12,11 @@ const rewriteURL = (
|
||||||
path = path.slice(1);
|
path = path.slice(1);
|
||||||
}
|
}
|
||||||
const destinationURL = `${destinationBaseURL}/${path}`;
|
const destinationURL = `${destinationBaseURL}/${path}`;
|
||||||
console.log(`Rewriting ${request.url} to ${destinationURL}`);
|
console.info(`Rewriting ${request.url} to ${destinationURL}`);
|
||||||
const requestHeaders = new Headers(request.headers);
|
const requestHeaders = new Headers(request.headers);
|
||||||
|
|
||||||
requestHeaders.delete("x-forwarded-user");
|
requestHeaders.delete("x-forwarded-user");
|
||||||
|
requestHeaders.delete("x-forwarded-roles");
|
||||||
requestHeaders.delete("connection");
|
requestHeaders.delete("connection");
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(headers)) {
|
for (const [key, value] of Object.entries(headers)) {
|
||||||
|
|
@ -29,6 +31,9 @@ const rewriteURL = (
|
||||||
const checkRewrites = async (request: NextRequestWithAuth) => {
|
const checkRewrites = async (request: NextRequestWithAuth) => {
|
||||||
const linkBaseURL = process.env.LINK_URL ?? "http://localhost:3000";
|
const linkBaseURL = process.env.LINK_URL ?? "http://localhost:3000";
|
||||||
const zammadURL = process.env.ZAMMAD_URL ?? "http://zammad-nginx:8080";
|
const zammadURL = process.env.ZAMMAD_URL ?? "http://zammad-nginx:8080";
|
||||||
|
const opensearchBaseURL =
|
||||||
|
process.env.OPENSEARCH_DASHBOARDS_URL ??
|
||||||
|
"http://opensearch-dashboards:5601";
|
||||||
|
|
||||||
const zammadPaths = [
|
const zammadPaths = [
|
||||||
"/zammad",
|
"/zammad",
|
||||||
|
|
@ -38,19 +43,38 @@ const checkRewrites = async (request: NextRequestWithAuth) => {
|
||||||
"/graphql",
|
"/graphql",
|
||||||
"/cable",
|
"/cable",
|
||||||
];
|
];
|
||||||
|
const isSetupMode = process.env.SETUP_MODE === "true";
|
||||||
const { token } = request.nextauth;
|
const { token } = request.nextauth;
|
||||||
const email = token?.email?.toLowerCase() ?? "unknown";
|
const email = token?.email?.toLowerCase() ?? "unknown";
|
||||||
let headers = { "x-forwarded-user": email };
|
const roles = (token?.roles as string[]) ?? [];
|
||||||
|
let headers = {
|
||||||
|
"x-forwarded-user": email,
|
||||||
|
"x-forwarded-roles": roles.join(","),
|
||||||
|
};
|
||||||
|
|
||||||
if (request.nextUrl.pathname.startsWith("/zammad")) {
|
if (request.nextUrl.pathname.startsWith("/dashboards")) {
|
||||||
|
return rewriteURL(
|
||||||
|
request,
|
||||||
|
`${linkBaseURL}/dashboards`,
|
||||||
|
opensearchBaseURL,
|
||||||
|
headers,
|
||||||
|
);
|
||||||
|
} else if (request.nextUrl.pathname.startsWith("/zammad")) {
|
||||||
return rewriteURL(request, `${linkBaseURL}/zammad`, zammadURL, headers);
|
return rewriteURL(request, `${linkBaseURL}/zammad`, zammadURL, headers);
|
||||||
} else if (zammadPaths.some((p) => request.nextUrl.pathname.startsWith(p))) {
|
} else if (zammadPaths.some((p) => request.nextUrl.pathname.startsWith(p))) {
|
||||||
return rewriteURL(request, linkBaseURL, zammadURL, headers);
|
return rewriteURL(request, linkBaseURL, zammadURL, headers);
|
||||||
|
} else if (request.nextUrl.pathname.startsWith("/api/v1")) {
|
||||||
|
if ((email && email !== "unknown") || isSetupMode) {
|
||||||
|
return NextResponse.next();
|
||||||
|
} else {
|
||||||
|
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
const isDev = process.env.NODE_ENV === "development";
|
const isDev = process.env.NODE_ENV === "development";
|
||||||
const nonce = Buffer.from(crypto.randomUUID()).toString("base64");
|
const nonce = Buffer.from(crypto.randomUUID()).toString("base64");
|
||||||
const cspHeader = `
|
const cspHeader = `
|
||||||
default-src 'self';
|
default-src 'self';
|
||||||
|
frame-src 'self' https://digiresilience.org;
|
||||||
connect-src 'self';
|
connect-src 'self';
|
||||||
script-src 'self' 'nonce-${nonce}' 'strict-dynamic' ${isDev ? "'unsafe-eval'" : ""};
|
script-src 'self' 'nonce-${nonce}' 'strict-dynamic' ${isDev ? "'unsafe-eval'" : ""};
|
||||||
style-src 'self' 'unsafe-inline';
|
style-src 'self' 'unsafe-inline';
|
||||||
|
|
@ -59,7 +83,7 @@ const checkRewrites = async (request: NextRequestWithAuth) => {
|
||||||
object-src 'none';
|
object-src 'none';
|
||||||
base-uri 'self';
|
base-uri 'self';
|
||||||
form-action 'self';
|
form-action 'self';
|
||||||
frame-ancestors 'none';
|
frame-ancestors 'self';
|
||||||
upgrade-insecure-requests;
|
upgrade-insecure-requests;
|
||||||
`;
|
`;
|
||||||
const contentSecurityPolicyHeaderValue = cspHeader
|
const contentSecurityPolicyHeaderValue = cspHeader
|
||||||
|
|
@ -116,5 +140,7 @@ export default withAuth(checkRewrites, {
|
||||||
});
|
});
|
||||||
|
|
||||||
export const config = {
|
export const config = {
|
||||||
matcher: ["/((?!ws|wss|api|_next/static|_next/image|favicon.ico).*)"],
|
matcher: [
|
||||||
|
"/((?!ws|wss|api/signal|api/whatsapp|_next/static|_next/image|favicon.ico).*)",
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
|
||||||
2
apps/link/next-env.d.ts
vendored
2
apps/link/next-env.d.ts
vendored
|
|
@ -2,4 +2,4 @@
|
||||||
/// <reference types="next/image-types/global" />
|
/// <reference types="next/image-types/global" />
|
||||||
|
|
||||||
// NOTE: This file should not be edited
|
// NOTE: This file should not be edited
|
||||||
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
|
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ const nextConfig = {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "X-Frame-Options",
|
key: "X-Frame-Options",
|
||||||
value: "DENY",
|
value: "SAMEORIGIN",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "X-Content-Type-Options",
|
key: "X-Content-Type-Options",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@link-stack/link",
|
"name": "@link-stack/link",
|
||||||
"version": "2.2.0",
|
"version": "2.4.0b1",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
|
|
@ -12,38 +12,39 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@chatscope/chat-ui-kit-react": "^2.0.3",
|
"@chatscope/chat-ui-kit-react": "^2.0.3",
|
||||||
"@chatscope/chat-ui-kit-styles": "^1.4.0",
|
"@chatscope/chat-ui-kit-styles": "^1.4.0",
|
||||||
"@emotion/cache": "^11.13.1",
|
"@emotion/cache": "^11.14.0",
|
||||||
"@emotion/react": "^11.13.3",
|
"@emotion/react": "^11.14.0",
|
||||||
"@emotion/server": "^11.11.0",
|
"@emotion/server": "^11.11.0",
|
||||||
"@emotion/styled": "^11.13.0",
|
"@emotion/styled": "^11.14.0",
|
||||||
"@link-stack/bridge-common": "*",
|
"@link-stack/bridge-common": "*",
|
||||||
"@link-stack/bridge-ui": "*",
|
"@link-stack/bridge-ui": "*",
|
||||||
"@link-stack/leafcutter-ui": "*",
|
"@link-stack/leafcutter-ui": "*",
|
||||||
"@link-stack/opensearch-common": "*",
|
"@link-stack/opensearch-common": "*",
|
||||||
"@link-stack/ui": "*",
|
"@link-stack/ui": "*",
|
||||||
"@mui/icons-material": "^5",
|
"@mui/icons-material": "^6",
|
||||||
"@mui/material": "^5",
|
"@mui/material": "^6",
|
||||||
"@mui/material-nextjs": "^5",
|
"@mui/material-nextjs": "^6",
|
||||||
"@mui/x-data-grid-pro": "^7.18.0",
|
"@mui/x-data-grid-pro": "^7.24.1",
|
||||||
"@mui/x-date-pickers": "^7.18.0",
|
"@mui/x-date-pickers": "^7.24.1",
|
||||||
"@mui/x-date-pickers-pro": "^7.18.0",
|
"@mui/x-date-pickers-pro": "^7.24.1",
|
||||||
"@mui/x-license": "^7.18.0",
|
"@mui/x-license": "^7.24.1",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"graphql-request": "^7.1.0",
|
"graphql-request": "^7.1.2",
|
||||||
"mui-chips-input": "^2.1.5",
|
"ioredis": "^5.4.2",
|
||||||
"next": "14.2.13",
|
"mui-chips-input": "^4.0.1",
|
||||||
"next-auth": "^4.24.8",
|
"next": "15.1.6",
|
||||||
"react": "18.3.1",
|
"next-auth": "^4.24.11",
|
||||||
"react-cookie": "^7.2.0",
|
"react": "19.0.0",
|
||||||
"react-dom": "18.3.1",
|
"react-cookie": "^7.2.2",
|
||||||
|
"react-dom": "19.0.0",
|
||||||
"react-iframe": "^1.8.5",
|
"react-iframe": "^1.8.5",
|
||||||
"react-polyglot": "^0.7.2",
|
"react-polyglot": "^0.7.2",
|
||||||
"sharp": "^0.33.5"
|
"sharp": "^0.33.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@link-stack/eslint-config": "*",
|
"@link-stack/eslint-config": "*",
|
||||||
"@types/node": "^22.7.3",
|
"@types/node": "^22.10.10",
|
||||||
"@types/react": "18.3.9",
|
"@types/react": "19.0.8",
|
||||||
"@types/uuid": "^10.0.0"
|
"@types/uuid": "^10.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
BIN
apps/link/public/.DS_Store
vendored
BIN
apps/link/public/.DS_Store
vendored
Binary file not shown.
2
apps/link/public/robots.txt
Normal file
2
apps/link/public/robots.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
User-agent: *
|
||||||
|
Disallow: /
|
||||||
|
|
@ -25,6 +25,7 @@ services:
|
||||||
context: ../../
|
context: ../../
|
||||||
dockerfile: ./apps/bridge-frontend/Dockerfile
|
dockerfile: ./apps/bridge-frontend/Dockerfile
|
||||||
container_name: bridge-frontend
|
container_name: bridge-frontend
|
||||||
|
image: registry.gitlab.com/digiresilience/link/link-stack/bridge-frontend:${LINK_STACK_VERSION}
|
||||||
restart: ${RESTART}
|
restart: ${RESTART}
|
||||||
ports:
|
ports:
|
||||||
- 8006:3000
|
- 8006:3000
|
||||||
|
|
@ -35,5 +36,6 @@ services:
|
||||||
context: ../../
|
context: ../../
|
||||||
dockerfile: ./apps/bridge-worker/Dockerfile
|
dockerfile: ./apps/bridge-worker/Dockerfile
|
||||||
container_name: bridge-worker
|
container_name: bridge-worker
|
||||||
|
image: registry.gitlab.com/digiresilience/link/link-stack/bridge-worker:${LINK_STACK_VERSION}
|
||||||
restart: ${RESTART}
|
restart: ${RESTART}
|
||||||
environment: *common-bridge-variables
|
environment: *common-bridge-variables
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ services:
|
||||||
LEAFCUTTER_URL: https://lc.digiresilience.org
|
LEAFCUTTER_URL: https://lc.digiresilience.org
|
||||||
BRIDGE_URL: http://bridge-frontend:3000
|
BRIDGE_URL: http://bridge-frontend:3000
|
||||||
ZAMMAD_URL: http://zammad-nginx:8080
|
ZAMMAD_URL: http://zammad-nginx:8080
|
||||||
|
REDIS_URL: "redis://zammad-redis:6379"
|
||||||
NEXTAUTH_URL: ${LINK_URL}
|
NEXTAUTH_URL: ${LINK_URL}
|
||||||
NEXTAUTH_SECRET: ${NEXTAUTH_SECRET}
|
NEXTAUTH_SECRET: ${NEXTAUTH_SECRET}
|
||||||
NEXTAUTH_AUDIENCE: ${NEXTAUTH_AUDIENCE}
|
NEXTAUTH_AUDIENCE: ${NEXTAUTH_AUDIENCE}
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@ services:
|
||||||
restart: ${RESTART}
|
restart: ${RESTART}
|
||||||
environment:
|
environment:
|
||||||
- discovery.type=single-node
|
- discovery.type=single-node
|
||||||
# - plugins.security.ssl.transport.enforce_hostname_verification=false
|
- plugins.security.ssl.transport.enforce_hostname_verification=false
|
||||||
# - plugins.security.ssl.transport.resolve_hostname=false
|
- plugins.security.ssl.transport.resolve_hostname=false
|
||||||
- cluster.routing.allocation.disk.watermark.low=3gb
|
- cluster.routing.allocation.disk.watermark.low=3gb
|
||||||
- cluster.routing.allocation.disk.watermark.high=2gb
|
- cluster.routing.allocation.disk.watermark.high=2gb
|
||||||
- cluster.routing.allocation.disk.watermark.flood_stage=500mb
|
- cluster.routing.allocation.disk.watermark.flood_stage=500mb
|
||||||
|
|
@ -47,10 +47,10 @@ services:
|
||||||
- ../opensearch-dashboards/opensearch_dashboards.yml:/usr/share/opensearch-dashboards/config/opensearch_dashboards.yml
|
- ../opensearch-dashboards/opensearch_dashboards.yml:/usr/share/opensearch-dashboards/config/opensearch_dashboards.yml
|
||||||
environment:
|
environment:
|
||||||
OPENSEARCH_HOSTS: '["https://opensearch:9200"]'
|
OPENSEARCH_HOSTS: '["https://opensearch:9200"]'
|
||||||
# OPENSEARCH_SECURITY_AUTH_TYPE: "proxy"
|
OPENSEARCH_SECURITY_AUTH_TYPE: "proxy"
|
||||||
# OPENSEARCH_SECURITY_PROXYCACHE_USER_HEADER: "x-proxy-user"
|
OPENSEARCH_SECURITY_PROXYCACHE_USER_HEADER: "x-proxy-user"
|
||||||
# OPENSEARCH_SECURITY_PROXYCACHE_ROLES_HEADER: "x-proxy-roles"
|
OPENSEARCH_SECURITY_PROXYCACHE_ROLES_HEADER: "x-proxy-roles"
|
||||||
# OPENSEARCH_REQUESTHEADERSALLOWLIST: '["securitytenant","Authorization","x-forwarded-for","x-proxy-user","x-proxy-roles"]'
|
OPENSEARCH_REQUESTHEADERSALLOWLIST: '["securitytenant","Authorization","x-forwarded-for","x-proxy-user","x-proxy-roles"]'
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
opensearch-data:
|
opensearch-data:
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
services:
|
services:
|
||||||
signal-cli-rest-api:
|
signal-cli-rest-api:
|
||||||
|
container_name: signal-cli-rest-api
|
||||||
|
build: ../signal-cli-rest-api
|
||||||
image: registry.gitlab.com/digiresilience/link/link-stack/signal-cli-rest-api:develop
|
image: registry.gitlab.com/digiresilience/link/link-stack/signal-cli-rest-api:develop
|
||||||
platform: linux/amd64
|
|
||||||
environment:
|
environment:
|
||||||
- MODE=json-rpc
|
- MODE=json-rpc
|
||||||
volumes:
|
volumes:
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@ x-zammad-vars: &common-zammad-variables
|
||||||
|
|
||||||
services:
|
services:
|
||||||
zammad-init:
|
zammad-init:
|
||||||
platform: linux/x86_64
|
|
||||||
container_name: zammad-init
|
container_name: zammad-init
|
||||||
command: ["zammad-init"]
|
command: ["zammad-init"]
|
||||||
depends_on:
|
depends_on:
|
||||||
|
|
@ -47,7 +46,6 @@ services:
|
||||||
<<: *common-global-variables
|
<<: *common-global-variables
|
||||||
|
|
||||||
zammad-nginx:
|
zammad-nginx:
|
||||||
platform: linux/x86_64
|
|
||||||
container_name: zammad-nginx
|
container_name: zammad-nginx
|
||||||
command: ["zammad-nginx"]
|
command: ["zammad-nginx"]
|
||||||
expose:
|
expose:
|
||||||
|
|
@ -72,7 +70,6 @@ services:
|
||||||
- zammad-var:/opt/zammad/var:ro
|
- zammad-var:/opt/zammad/var:ro
|
||||||
|
|
||||||
zammad-railsserver:
|
zammad-railsserver:
|
||||||
platform: linux/x86_64
|
|
||||||
container_name: zammad-railsserver
|
container_name: zammad-railsserver
|
||||||
command: ["zammad-railsserver"]
|
command: ["zammad-railsserver"]
|
||||||
depends_on:
|
depends_on:
|
||||||
|
|
@ -103,7 +100,6 @@ services:
|
||||||
- redis-data:/data
|
- redis-data:/data
|
||||||
|
|
||||||
zammad-scheduler:
|
zammad-scheduler:
|
||||||
platform: linux/x86_64
|
|
||||||
container_name: zammad-scheduler
|
container_name: zammad-scheduler
|
||||||
command: ["zammad-scheduler"]
|
command: ["zammad-scheduler"]
|
||||||
depends_on:
|
depends_on:
|
||||||
|
|
@ -123,7 +119,6 @@ services:
|
||||||
- zammad-storage:/opt/zammad/storage
|
- zammad-storage:/opt/zammad/storage
|
||||||
|
|
||||||
zammad-websocket:
|
zammad-websocket:
|
||||||
platform: linux/x86_64
|
|
||||||
container_name: zammad-websocket
|
container_name: zammad-websocket
|
||||||
command: ["zammad-websocket"]
|
command: ["zammad-websocket"]
|
||||||
depends_on:
|
depends_on:
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
FROM memcached:1.6.31-bookworm
|
FROM memcached:1.6.34-bookworm
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
FROM nginxproxy/nginx-proxy:1.6.1
|
FROM nginxproxy/nginx-proxy:1.6.4
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
FROM opensearchproject/opensearch-dashboards:2.17.0
|
FROM opensearchproject/opensearch-dashboards:2.18.0
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,2 @@
|
||||||
FROM opensearchproject/opensearch:2.17.0
|
FROM opensearchproject/opensearch:2.18.0
|
||||||
RUN /usr/share/opensearch/bin/opensearch-plugin install ingest-attachment -b
|
RUN /usr/share/opensearch/bin/opensearch-plugin install ingest-attachment -b
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
FROM redis:7.4.0-bookworm
|
FROM redis:7.4.2-bookworm
|
||||||
|
|
|
||||||
|
|
@ -17,14 +17,13 @@ const files = {
|
||||||
|
|
||||||
const finalFiles = files[app]
|
const finalFiles = files[app]
|
||||||
.map((file) => ['-f', `docker/compose/${file}.yml`]).flat();
|
.map((file) => ['-f', `docker/compose/${file}.yml`]).flat();
|
||||||
const finalCommand = command === "up" ? ["up", "-d"] : [command];
|
const finalCommand = command === "up" ? ["up", "-d", "--remove-orphans"] : [command];
|
||||||
|
|
||||||
const dockerCompose = spawn('docker', ['compose', '--env-file', '.env', ...finalFiles, ...finalCommand]);
|
const dockerCompose = spawn('docker', ['compose', '--env-file', '.env', ...finalFiles, ...finalCommand]);
|
||||||
|
|
||||||
dockerCompose.stdout.on('data', (data) => {
|
dockerCompose.stdout.on('data', (data) => {
|
||||||
console.log(`${data}`);
|
console.info(`${data}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
dockerCompose.stderr.on('data', (data) => {
|
dockerCompose.stderr.on('data', (data) => {
|
||||||
console.log(`${data}`);
|
console.info(`${data}`);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
FROM bbernhard/signal-cli-rest-api:0.89
|
FROM bbernhard/signal-cli-rest-api:0.90
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,25 @@
|
||||||
ARG ZAMMAD_VERSION=6.3.1
|
ARG ZAMMAD_VERSION=6.4.1
|
||||||
|
|
||||||
FROM node:22-slim as node
|
FROM node:22-slim AS node
|
||||||
|
|
||||||
FROM zammad/zammad-docker-compose:${ZAMMAD_VERSION} AS builder
|
FROM zammad/zammad-docker-compose:${ZAMMAD_VERSION} AS builder
|
||||||
|
USER root
|
||||||
COPY --from=node /opt /opt
|
COPY --from=node /opt /opt
|
||||||
COPY --from=node /usr/local/bin /usr/local/bin
|
COPY --from=node /usr/local/bin /usr/local/bin
|
||||||
|
COPY --from=node /usr/local/lib /usr/local/lib
|
||||||
|
COPY --from=node /usr/lib /usr/lib
|
||||||
SHELL ["/bin/bash", "-e", "-o", "pipefail", "-c"]
|
SHELL ["/bin/bash", "-e", "-o", "pipefail", "-c"]
|
||||||
|
RUN npm install -g pnpm
|
||||||
|
RUN pnpm --version
|
||||||
WORKDIR ${ZAMMAD_DIR}
|
WORKDIR ${ZAMMAD_DIR}
|
||||||
RUN mkdir -p /opt/zammad/contrib/link/addons
|
RUN mkdir -p /opt/zammad/contrib/link/addons
|
||||||
COPY addons contrib/link/addons
|
COPY addons contrib/link/addons
|
||||||
COPY setup.rb contrib/link/setup.rb
|
COPY setup.rb contrib/link/setup.rb
|
||||||
COPY install.rb contrib/link/install.rb
|
COPY install.rb contrib/link/install.rb
|
||||||
|
|
||||||
USER root
|
|
||||||
RUN sed -i '/script\/build\/cleanup\.sh/d' contrib/docker/setup.sh
|
RUN sed -i '/script\/build\/cleanup\.sh/d' contrib/docker/setup.sh
|
||||||
|
RUN sed -i '/touch db\/schema.rb/a ZAMMAD_SAFE_MODE=1 DATABASE_URL=postgresql:\/\/zammad:\/zammad bundle exec rails runner \/opt\/zammad\/contrib\/link\/install.rb' contrib/docker/setup.sh
|
||||||
|
RUN cat contrib/docker/setup.sh
|
||||||
RUN contrib/docker/setup.sh builder
|
RUN contrib/docker/setup.sh builder
|
||||||
ARG EMBEDDED=false
|
ARG EMBEDDED=false
|
||||||
RUN if [ "$EMBEDDED" = "true" ] ; then sed -i '/proxy_set_header X-Forwarded-User "";/d' ${ZAMMAD_DIR}/contrib/nginx/zammad.conf; fi
|
RUN if [ "$EMBEDDED" = "true" ] ; then sed -i '/proxy_set_header X-Forwarded-User "";/d' ${ZAMMAD_DIR}/contrib/nginx/zammad.conf; fi
|
||||||
|
|
@ -22,11 +28,9 @@ RUN sed -i '/^[[:space:]]*# es config/a\
|
||||||
bundle exec rails runner /opt/zammad/contrib/link/setup.rb\n\
|
bundle exec rails runner /opt/zammad/contrib/link/setup.rb\n\
|
||||||
bundle exec rake zammad:package:migrate\n\
|
bundle exec rake zammad:package:migrate\n\
|
||||||
' /docker-entrypoint.sh
|
' /docker-entrypoint.sh
|
||||||
RUN ZAMMAD_SAFE_MODE=1 DATABASE_URL=postgresql://zammad:/zammad bundle exec rails runner /opt/zammad/contrib/link/install.rb
|
|
||||||
RUN ZAMMAD_SAFE_MODE=1 DATABASE_URL=postgresql://zammad:/zammad bundle exec rake assets:precompile
|
|
||||||
|
|
||||||
FROM zammad/zammad-docker-compose:${ZAMMAD_VERSION} as runner
|
FROM zammad/zammad-docker-compose:${ZAMMAD_VERSION} AS runner
|
||||||
USER ${ZAMMAD_USER}
|
USER zammad
|
||||||
COPY --from=builder --chown=zammad:zammad ${ZAMMAD_DIR} ${ZAMMAD_DIR}
|
COPY --from=builder --chown=zammad:zammad ${ZAMMAD_DIR} ${ZAMMAD_DIR}
|
||||||
COPY --from=builder /usr/local/bundle /usr/local/bundle
|
COPY --from=builder /usr/local/bundle /usr/local/bundle
|
||||||
COPY --from=builder /docker-entrypoint.sh /docker-entrypoint.sh
|
COPY --from=builder /docker-entrypoint.sh /docker-entrypoint.sh
|
||||||
|
|
|
||||||
6509
package-lock.json
generated
6509
package-lock.json
generated
File diff suppressed because it is too large
Load diff
46
package.json
46
package.json
|
|
@ -1,13 +1,13 @@
|
||||||
{
|
{
|
||||||
"name": "@link-stack",
|
"name": "@link-stack",
|
||||||
"version": "2.2.0",
|
"version": "2.4.0b1",
|
||||||
"description": "Link from the Center for Digital Resilience",
|
"description": "Link from the Center for Digital Resilience",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "dotenv -- turbo dev",
|
"dev": "dotenv -- turbo dev",
|
||||||
"build": "dotenv -- turbo build",
|
"build": "dotenv -- turbo build",
|
||||||
"migrate": "dotenv -- npm run migrate --workspace=database",
|
"migrate": "dotenv -- npm run migrate --workspace=database",
|
||||||
"lint": "dotenv turbo lint",
|
"lint": "dotenv turbo lint",
|
||||||
"update-version": "find . -name 'package.json' -exec sed -i -E 's/\"version\": \"[^\"]+\"/\"version\": \"2.2.0\"/' {} +",
|
"update-version": "find . -name 'package.json' -exec sed -i -E 's/\"version\": \"[^\"]+\"/\"version\": \"2.4.0b1\"/' {} +",
|
||||||
"upgrade:setup": "npm i -g npm-check-updates",
|
"upgrade:setup": "npm i -g npm-check-updates",
|
||||||
"upgrade:check": "ncu && ncu -ws",
|
"upgrade:check": "ncu && ncu -ws",
|
||||||
"upgrade": "ncu -u -x eslint -x kysely && ncu -ws -u -x eslint -x kysely && npm i",
|
"upgrade": "ncu -u -x eslint -x kysely && ncu -ws -u -x eslint -x kysely && npm i",
|
||||||
|
|
@ -46,39 +46,33 @@
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://gitlab.com/digiresilience/link/link-stack.git"
|
"url": "git+https://gitlab.com/digiresilience/link/link-stack.git"
|
||||||
},
|
},
|
||||||
"packageManager": "npm@10.8.3",
|
"packageManager": "npm@11.0.0",
|
||||||
"author": "Darren Clarke",
|
"author": "Darren Clarke",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"dotenv-cli": "latest",
|
"dotenv-cli": "latest",
|
||||||
"eslint": "^8",
|
"eslint": "^9",
|
||||||
"turbo": "^2.1.2",
|
"turbo": "^2.3.4",
|
||||||
"typescript": "latest"
|
"typescript": "latest",
|
||||||
|
"react": "^19.0.0",
|
||||||
|
"react-dom": "^19.0.0",
|
||||||
|
"@types/react": "^19.0.8"
|
||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"eslint-config-next": {
|
"material-ui-popup-state": {
|
||||||
"eslint": "$eslint"
|
"react": "$react"
|
||||||
},
|
},
|
||||||
"eslint-plugin-import": {
|
"@chatscope/chat-ui-kit-react": {
|
||||||
"eslint": "$eslint"
|
"react": "$react",
|
||||||
|
"react-dom": "$react-dom"
|
||||||
},
|
},
|
||||||
"eslint-plugin-jsx-a11y": {
|
"@mui/icons-material": {
|
||||||
"eslint": "$eslint"
|
"react": "$react"
|
||||||
},
|
},
|
||||||
"eslint-plugin-react": {
|
"mui-chips-input": {
|
||||||
"eslint": "$eslint"
|
"react": "$react",
|
||||||
},
|
"react-dom": "$react-dom",
|
||||||
"@typescript-eslint/eslint-plugin": {
|
"@types/react": "$@types/react"
|
||||||
"eslint": "$eslint"
|
|
||||||
},
|
|
||||||
"@typescript-eslint/parser": {
|
|
||||||
"eslint": "$eslint"
|
|
||||||
},
|
|
||||||
"eslint-plugin-promise": {
|
|
||||||
"eslint": "$eslint"
|
|
||||||
},
|
|
||||||
"@babel/eslint-parser": {
|
|
||||||
"eslint": "$eslint"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@link-stack/bridge-common",
|
"name": "@link-stack/bridge-common",
|
||||||
"version": "2.2.0",
|
"version": "2.4.0b1",
|
||||||
"main": "build/main/index.js",
|
"main": "build/main/index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"author": "Darren Clarke <darren@redaranj.com>",
|
"author": "Darren Clarke <darren@redaranj.com>",
|
||||||
|
|
@ -9,14 +9,14 @@
|
||||||
"build": "tsc -p tsconfig.json"
|
"build": "tsc -p tsconfig.json"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@auth/kysely-adapter": "^1.5.2",
|
"@auth/kysely-adapter": "^1.7.4",
|
||||||
"graphile-worker": "^0.16.6",
|
"graphile-worker": "^0.16.6",
|
||||||
"kysely": "0.26.1",
|
"kysely": "0.26.1",
|
||||||
"pg": "^8.13.0"
|
"pg": "^8.13.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@link-stack/eslint-config": "*",
|
"@link-stack/eslint-config": "*",
|
||||||
"@link-stack/typescript-config": "*",
|
"@link-stack/typescript-config": "*",
|
||||||
"typescript": "^5.6.2"
|
"typescript": "^5.7.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,6 @@ export const QRCode: FC<QRCodeProps> = ({
|
||||||
if (!verified && getValue && refreshInterval) {
|
if (!verified && getValue && refreshInterval) {
|
||||||
const interval = setInterval(async () => {
|
const interval = setInterval(async () => {
|
||||||
const { qr, kind } = await getValue(token);
|
const { qr, kind } = await getValue(token);
|
||||||
console.log({ kind });
|
|
||||||
setValue(qr);
|
setValue(qr);
|
||||||
setKind(kind);
|
setKind(kind);
|
||||||
}, refreshInterval * 1000);
|
}, refreshInterval * 1000);
|
||||||
|
|
|
||||||
|
|
@ -3,18 +3,19 @@ type ServiceLayoutProps = {
|
||||||
detail: any;
|
detail: any;
|
||||||
edit: any;
|
edit: any;
|
||||||
create: any;
|
create: any;
|
||||||
params: {
|
params: Promise<{
|
||||||
segment: string[];
|
segment: string[];
|
||||||
};
|
}>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ServiceLayout = ({
|
export const ServiceLayout = async ({
|
||||||
children,
|
children,
|
||||||
detail,
|
detail,
|
||||||
edit,
|
edit,
|
||||||
create,
|
create,
|
||||||
params: { segment },
|
params,
|
||||||
}: ServiceLayoutProps) => {
|
}: ServiceLayoutProps) => {
|
||||||
|
const { segment } = await params;
|
||||||
const length = segment?.length ?? 0;
|
const length = segment?.length ?? 0;
|
||||||
const isCreate = length === 2 && segment[1] === "create";
|
const isCreate = length === 2 && segment[1] === "create";
|
||||||
const isEdit = length === 3 && segment[2] === "edit";
|
const isEdit = length === 3 && segment[2] === "edit";
|
||||||
|
|
|
||||||
|
|
@ -5,19 +5,21 @@ import { getService } from "./utils";
|
||||||
export const getBot = async (
|
export const getBot = async (
|
||||||
_req: NextRequest,
|
_req: NextRequest,
|
||||||
params: ServiceParams,
|
params: ServiceParams,
|
||||||
): Promise<NextResponse> => getService(params)?.getBot(params);
|
): Promise<NextResponse> => (await getService(params))?.getBot(params);
|
||||||
|
|
||||||
export const sendMessage = async (
|
export const sendMessage = async (
|
||||||
req: NextRequest,
|
req: NextRequest,
|
||||||
params: ServiceParams,
|
params: ServiceParams,
|
||||||
): Promise<NextResponse> => getService(params)?.sendMessage(req, params);
|
): Promise<NextResponse> =>
|
||||||
|
(await getService(params))?.sendMessage(req, params);
|
||||||
|
|
||||||
export const receiveMessage = async (
|
export const receiveMessage = async (
|
||||||
req: NextRequest,
|
req: NextRequest,
|
||||||
params: ServiceParams,
|
params: ServiceParams,
|
||||||
): Promise<NextResponse> => getService(params)?.receiveMessage(req, params);
|
): Promise<NextResponse> =>
|
||||||
|
(await getService(params))?.receiveMessage(req, params);
|
||||||
|
|
||||||
export const handleWebhook = async (
|
export const handleWebhook = async (
|
||||||
req: NextRequest,
|
req: NextRequest,
|
||||||
params: ServiceParams,
|
params: ServiceParams,
|
||||||
): Promise<NextResponse> => getService(params)?.handleWebhook(req);
|
): Promise<NextResponse> => (await getService(params))?.handleWebhook(req);
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue