feat: Add centralized logging system with @link-stack/logger package
- Create new @link-stack/logger package wrapping Pino for structured logging - Replace all console.log/error/warn statements across the monorepo - Configure environment-aware logging (pretty-print in dev, JSON in prod) - Add automatic redaction of sensitive fields (passwords, tokens, etc.) - Remove dead commented-out logger file from bridge-worker - Follow Pino's standard argument order (context object first, message second) - Support log levels via LOG_LEVEL environment variable - Export TypeScript types for better IDE support This provides consistent, structured logging across all applications and packages, making debugging easier and production logs more parseable.
This commit is contained in:
parent
5b89bfce7c
commit
c1feaa4cb1
42 changed files with 3824 additions and 2422 deletions
|
|
@ -10,6 +10,9 @@ import {
|
||||||
CamelCasePlugin,
|
CamelCasePlugin,
|
||||||
} from "kysely";
|
} from "kysely";
|
||||||
import pkg from "pg";
|
import pkg from "pg";
|
||||||
|
import { createLogger } from "@link-stack/logger";
|
||||||
|
|
||||||
|
const logger = createLogger('bridge-migrations-migrate');
|
||||||
const { Pool } = pkg;
|
const { Pool } = pkg;
|
||||||
import * as dotenv from "dotenv";
|
import * as dotenv from "dotenv";
|
||||||
|
|
||||||
|
|
@ -72,17 +75,17 @@ export const migrate = async (arg: string) => {
|
||||||
|
|
||||||
results?.forEach((it) => {
|
results?.forEach((it) => {
|
||||||
if (it.status === "Success") {
|
if (it.status === "Success") {
|
||||||
console.info(
|
logger.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") {
|
||||||
console.error(`Failed to execute migration "${it.migrationName}"`);
|
logger.error(`Failed to execute migration "${it.migrationName}"`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error("Failed to migrate");
|
logger.error("Failed to migrate");
|
||||||
console.error(error);
|
logger.error(error);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
"migrate:down:one": "tsx migrate.ts down:one"
|
"migrate:down:one": "tsx migrate.ts down:one"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@link-stack/logger": "*",
|
||||||
"dotenv": "^16.4.7",
|
"dotenv": "^16.4.7",
|
||||||
"kysely": "0.27.6",
|
"kysely": "0.27.6",
|
||||||
"pg": "^8.14.1",
|
"pg": "^8.14.1",
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,9 @@ import {
|
||||||
SendMessageRoute,
|
SendMessageRoute,
|
||||||
ReceiveMessageRoute,
|
ReceiveMessageRoute,
|
||||||
} from "./routes.js";
|
} from "./routes.js";
|
||||||
|
import { createLogger } from "@link-stack/logger";
|
||||||
|
|
||||||
|
const logger = createLogger('bridge-whatsapp-index');
|
||||||
|
|
||||||
const server = Hapi.server({ port: 5000 });
|
const server = Hapi.server({ port: 5000 });
|
||||||
|
|
||||||
|
|
@ -34,6 +37,6 @@ const main = async () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
main().catch((err) => {
|
main().catch((err) => {
|
||||||
console.error(err);
|
logger.error(err);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,9 @@ import makeWASocket, {
|
||||||
useMultiFileAuthState,
|
useMultiFileAuthState,
|
||||||
} from "@whiskeysockets/baileys";
|
} from "@whiskeysockets/baileys";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
|
import { createLogger } from "@link-stack/logger";
|
||||||
|
|
||||||
|
const logger = createLogger('bridge-whatsapp-service');
|
||||||
|
|
||||||
export type AuthCompleteCallback = (error?: string) => void;
|
export type AuthCompleteCallback = (error?: string) => void;
|
||||||
|
|
||||||
|
|
@ -57,7 +60,7 @@ export default class WhatsappService extends Service {
|
||||||
try {
|
try {
|
||||||
connection.end(null);
|
connection.end(null);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
logger.error({ error }, 'Connection reset error');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.connections = {};
|
this.connections = {};
|
||||||
|
|
@ -92,27 +95,27 @@ export default class WhatsappService extends Service {
|
||||||
isNewLogin,
|
isNewLogin,
|
||||||
} = update;
|
} = update;
|
||||||
if (qr) {
|
if (qr) {
|
||||||
console.info("got qr code");
|
logger.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.info("got new login");
|
logger.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.info("opened connection");
|
logger.info('opened connection');
|
||||||
} else if (connectionState === "close") {
|
} else if (connectionState === "close") {
|
||||||
console.info("connection closed due to ", lastDisconnect?.error);
|
logger.info({ lastDisconnect }, 'connection closed');
|
||||||
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.info("reconnecting after got new login");
|
logger.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.info("reconnecting");
|
logger.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 +124,12 @@ export default class WhatsappService extends Service {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (events["creds.update"]) {
|
if (events["creds.update"]) {
|
||||||
console.info("creds update");
|
logger.info('creds update');
|
||||||
await saveCreds();
|
await saveCreds();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (events["messages.upsert"]) {
|
if (events["messages.upsert"]) {
|
||||||
console.info("messages upsert");
|
logger.info('messages upsert');
|
||||||
const upsert = events["messages.upsert"];
|
const upsert = events["messages.upsert"];
|
||||||
const { messages } = upsert;
|
const { messages } = upsert;
|
||||||
if (messages) {
|
if (messages) {
|
||||||
|
|
@ -149,7 +152,7 @@ export default class WhatsappService extends Service {
|
||||||
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.info(`using WA v${version.join(".")}, isLatest: ${isLatest}`);
|
logger.info({ version: version.join('.'), isLatest }, 'using WA version');
|
||||||
|
|
||||||
await this.createConnection(botID, this.server, {
|
await this.createConnection(botID, this.server, {
|
||||||
browser: WhatsappService.browserDescription,
|
browser: WhatsappService.browserDescription,
|
||||||
|
|
@ -169,9 +172,9 @@ export default class WhatsappService extends Service {
|
||||||
message,
|
message,
|
||||||
messageTimestamp,
|
messageTimestamp,
|
||||||
} = webMessageInfo;
|
} = webMessageInfo;
|
||||||
console.info("Message type debug");
|
logger.info('Message type debug');
|
||||||
for (const key in message) {
|
for (const key in message) {
|
||||||
console.info(key, !!message[key as keyof proto.IMessage]);
|
logger.info({ key, exists: !!message[key as keyof proto.IMessage] }, 'Message field');
|
||||||
}
|
}
|
||||||
const isValidMessage =
|
const isValidMessage =
|
||||||
message && remoteJid !== "status@broadcast" && !fromMe;
|
message && remoteJid !== "status@broadcast" && !fromMe;
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,14 @@
|
||||||
import { run } from "graphile-worker";
|
import { run } from "graphile-worker";
|
||||||
|
import { createLogger } from "@link-stack/logger";
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
import { fileURLToPath } from "url";
|
import { fileURLToPath } from "url";
|
||||||
|
|
||||||
|
const logger = createLogger('bridge-worker');
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = path.dirname(__filename);
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
const startWorker = async () => {
|
const startWorker = async () => {
|
||||||
console.info("Starting worker...");
|
logger.info("Starting worker...");
|
||||||
|
|
||||||
await run({
|
await run({
|
||||||
connectionString: process.env.DATABASE_URL,
|
connectionString: process.env.DATABASE_URL,
|
||||||
|
|
@ -30,6 +32,6 @@ const main = async () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
main().catch((err) => {
|
main().catch((err) => {
|
||||||
console.error(err);
|
logger.error({ error: err }, 'Worker failed to start');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,9 @@
|
||||||
import Twilio from "twilio";
|
import Twilio from "twilio";
|
||||||
import { CallInstance } from "twilio/lib/rest/api/v2010/account/call";
|
import { CallInstance } from "twilio/lib/rest/api/v2010/account/call";
|
||||||
import { Zammad, getOrCreateUser } from "./zammad.js";
|
import { Zammad, getOrCreateUser } from "./zammad.js";
|
||||||
|
import { createLogger } from "@link-stack/logger";
|
||||||
|
|
||||||
|
const logger = createLogger('bridge-worker-common');
|
||||||
|
|
||||||
type SavedVoiceProvider = any;
|
type SavedVoiceProvider = any;
|
||||||
|
|
||||||
|
|
@ -63,7 +66,7 @@ export const createZammadTicket = async (
|
||||||
});
|
});
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (error.isBoom) {
|
if (error.isBoom) {
|
||||||
console.error(error.output);
|
logger.error({ output: error.output }, 'Zammad ticket creation failed');
|
||||||
throw new Error("Failed to create zamamd ticket");
|
throw new Error("Failed to create zamamd ticket");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
//import { defState } from "@digiresilience/montar";
|
|
||||||
//import { configureLogger } from "@digiresilience/bridge-common";
|
|
||||||
// import config from "@digiresilience/bridge-config";
|
|
||||||
|
|
||||||
//export const logger = defState("workerLogger", {
|
|
||||||
// start: async () => configureLogger(config),
|
|
||||||
//});
|
|
||||||
//export default logger;
|
|
||||||
|
|
||||||
export const logger = {};
|
|
||||||
export default logger;
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
import { Readable } from "stream";
|
import { Readable } from "stream";
|
||||||
import ffmpeg from "fluent-ffmpeg";
|
import ffmpeg from "fluent-ffmpeg";
|
||||||
import * as R from "remeda";
|
import * as R from "remeda";
|
||||||
|
import { createLogger } from "@link-stack/logger";
|
||||||
|
|
||||||
|
const logger = createLogger('bridge-worker-media-convert');
|
||||||
|
|
||||||
const requiredCodecs = ["mp3", "webm", "wav"];
|
const requiredCodecs = ["mp3", "webm", "wav"];
|
||||||
|
|
||||||
|
|
@ -36,7 +39,7 @@ export const convert = (
|
||||||
.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);
|
logger.error({ error: err }, 'FFmpeg conversion error');
|
||||||
reject(err);
|
reject(err);
|
||||||
})
|
})
|
||||||
.on("end", () => {
|
.on("end", () => {
|
||||||
|
|
@ -58,7 +61,7 @@ export const selfCheck = (): Promise<boolean> => {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
ffmpeg.getAvailableFormats((err, codecs) => {
|
ffmpeg.getAvailableFormats((err, codecs) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error("FFMPEG error:", err);
|
logger.error({ error: err }, 'FFMPEG error');
|
||||||
resolve(false);
|
resolve(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hapi/wreck": "^18.1.0",
|
"@hapi/wreck": "^18.1.0",
|
||||||
"@link-stack/bridge-common": "*",
|
"@link-stack/bridge-common": "*",
|
||||||
|
"@link-stack/logger": "*",
|
||||||
"@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",
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
import { db } from "@link-stack/bridge-common";
|
import { db } from "@link-stack/bridge-common";
|
||||||
|
import { createLogger } from "@link-stack/logger";
|
||||||
|
|
||||||
|
const logger = createLogger('notify-webhooks');
|
||||||
|
|
||||||
export interface NotifyWebhooksOptions {
|
export interface NotifyWebhooksOptions {
|
||||||
backendId: string;
|
backendId: string;
|
||||||
|
|
@ -10,11 +13,10 @@ const notifyWebhooksTask = async (
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
const { backendId, payload } = options;
|
const { backendId, payload } = options;
|
||||||
|
|
||||||
console.log(`[notify-webhooks] Processing webhook notification:`, {
|
logger.debug({
|
||||||
backendId,
|
backendId,
|
||||||
payloadKeys: Object.keys(payload),
|
payloadKeys: Object.keys(payload),
|
||||||
payload: JSON.stringify(payload, null, 2),
|
}, 'Processing webhook notification');
|
||||||
});
|
|
||||||
|
|
||||||
const webhooks = await db
|
const webhooks = await db
|
||||||
.selectFrom("Webhook")
|
.selectFrom("Webhook")
|
||||||
|
|
@ -22,20 +24,19 @@ const notifyWebhooksTask = async (
|
||||||
.where("backendId", "=", backendId)
|
.where("backendId", "=", backendId)
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
console.log(`[notify-webhooks] Found ${webhooks.length} webhooks for backend ${backendId}`);
|
logger.debug({ count: webhooks.length, backendId }, 'Found webhooks');
|
||||||
|
|
||||||
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 };
|
||||||
const body = JSON.stringify(payload);
|
const body = JSON.stringify(payload);
|
||||||
|
|
||||||
console.log(`[notify-webhooks] Sending webhook:`, {
|
logger.debug({
|
||||||
url: endpointUrl,
|
url: endpointUrl,
|
||||||
method: httpMethod,
|
method: httpMethod,
|
||||||
bodyLength: body.length,
|
bodyLength: body.length,
|
||||||
headers: Object.keys(finalHeaders),
|
headerKeys: Object.keys(finalHeaders),
|
||||||
payload: body,
|
}, 'Sending webhook');
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await fetch(endpointUrl, {
|
const result = await fetch(endpointUrl, {
|
||||||
|
|
@ -44,26 +45,26 @@ const notifyWebhooksTask = async (
|
||||||
body,
|
body,
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(`[notify-webhooks] Webhook response:`, {
|
logger.debug({
|
||||||
url: endpointUrl,
|
url: endpointUrl,
|
||||||
status: result.status,
|
status: result.status,
|
||||||
statusText: result.statusText,
|
statusText: result.statusText,
|
||||||
ok: result.ok,
|
ok: result.ok,
|
||||||
});
|
}, 'Webhook response');
|
||||||
|
|
||||||
if (!result.ok) {
|
if (!result.ok) {
|
||||||
const responseText = await result.text();
|
const responseText = await result.text();
|
||||||
console.error(`[notify-webhooks] Webhook error response:`, {
|
logger.error({
|
||||||
url: endpointUrl,
|
url: endpointUrl,
|
||||||
status: result.status,
|
status: result.status,
|
||||||
response: responseText.substring(0, 500), // First 500 chars
|
responseSample: responseText.substring(0, 500),
|
||||||
});
|
}, 'Webhook error response');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`[notify-webhooks] Webhook request failed:`, {
|
logger.error({
|
||||||
url: endpointUrl,
|
url: endpointUrl,
|
||||||
error: error instanceof Error ? error.message : error,
|
error: error instanceof Error ? error.message : error,
|
||||||
});
|
}, 'Webhook request failed');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
import { db } from "@link-stack/bridge-common";
|
import { db } from "@link-stack/bridge-common";
|
||||||
|
import { createLogger } from "@link-stack/logger";
|
||||||
|
|
||||||
|
const logger = createLogger('bridge-worker-send-facebook-message');
|
||||||
|
|
||||||
interface SendFacebookMessageTaskOptions {
|
interface SendFacebookMessageTaskOptions {
|
||||||
token: string;
|
token: string;
|
||||||
|
|
@ -32,7 +35,7 @@ const sendFacebookMessageTask = async (
|
||||||
body: JSON.stringify(outgoingMessage),
|
body: JSON.stringify(outgoingMessage),
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error({ error });
|
logger.error({ error });
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
import { db, getWorkerUtils } from "@link-stack/bridge-common";
|
import { db, getWorkerUtils } from "@link-stack/bridge-common";
|
||||||
|
import { createLogger } from "@link-stack/logger";
|
||||||
import * as signalApi from "@link-stack/signal-api";
|
import * as signalApi from "@link-stack/signal-api";
|
||||||
|
|
||||||
|
const logger = createLogger('fetch-signal-messages');
|
||||||
|
|
||||||
const { Configuration, MessagesApi, AttachmentsApi } = signalApi;
|
const { Configuration, MessagesApi, AttachmentsApi } = signalApi;
|
||||||
const config = new Configuration({
|
const config = new Configuration({
|
||||||
basePath: process.env.BRIDGE_SIGNAL_URL,
|
basePath: process.env.BRIDGE_SIGNAL_URL,
|
||||||
|
|
@ -59,8 +62,7 @@ const processMessage = async ({
|
||||||
const { attachments } = dataMessage;
|
const { attachments } = dataMessage;
|
||||||
const rawTimestamp = dataMessage?.timestamp;
|
const rawTimestamp = dataMessage?.timestamp;
|
||||||
|
|
||||||
// Debug logging for group detection
|
logger.debug({
|
||||||
console.log(`[fetch-signal-messages] Processing message:`, {
|
|
||||||
sourceUuid,
|
sourceUuid,
|
||||||
source,
|
source,
|
||||||
rawTimestamp,
|
rawTimestamp,
|
||||||
|
|
@ -71,7 +73,7 @@ const processMessage = async ({
|
||||||
groupV2Id: dataMessage?.groupV2?.id,
|
groupV2Id: dataMessage?.groupV2?.id,
|
||||||
groupContextType: dataMessage?.groupContext?.type,
|
groupContextType: dataMessage?.groupContext?.type,
|
||||||
groupInfoType: dataMessage?.groupInfo?.type,
|
groupInfoType: dataMessage?.groupInfo?.type,
|
||||||
});
|
}, 'Processing message');
|
||||||
const timestamp = new Date(rawTimestamp);
|
const timestamp = new Date(rawTimestamp);
|
||||||
|
|
||||||
const formattedAttachments = await fetchAttachments(attachments);
|
const formattedAttachments = await fetchAttachments(attachments);
|
||||||
|
|
@ -148,9 +150,7 @@ const fetchSignalMessagesTask = async ({
|
||||||
number: phoneNumber,
|
number: phoneNumber,
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(
|
logger.debug({ botId: id, phoneNumber }, 'Fetching messages for bot');
|
||||||
`[fetch-signal-messages] Fetching messages for bot ${id} (${phoneNumber})`,
|
|
||||||
);
|
|
||||||
|
|
||||||
for (const message of messages) {
|
for (const message of messages) {
|
||||||
const formattedMessages = await processMessage({
|
const formattedMessages = await processMessage({
|
||||||
|
|
@ -160,14 +160,14 @@ const fetchSignalMessagesTask = async ({
|
||||||
});
|
});
|
||||||
for (const formattedMessage of formattedMessages) {
|
for (const formattedMessage of formattedMessages) {
|
||||||
if (formattedMessage.to !== formattedMessage.from) {
|
if (formattedMessage.to !== formattedMessage.from) {
|
||||||
console.log(`[fetch-signal-messages] Creating job for message:`, {
|
logger.debug({
|
||||||
messageId: formattedMessage.messageId,
|
messageId: formattedMessage.messageId,
|
||||||
from: formattedMessage.from,
|
from: formattedMessage.from,
|
||||||
to: formattedMessage.to,
|
to: formattedMessage.to,
|
||||||
isGroup: formattedMessage.isGroup,
|
isGroup: formattedMessage.isGroup,
|
||||||
hasMessage: !!formattedMessage.message,
|
hasMessage: !!formattedMessage.message,
|
||||||
hasAttachment: !!formattedMessage.attachment,
|
hasAttachment: !!formattedMessage.attachment,
|
||||||
});
|
}, 'Creating job for message');
|
||||||
|
|
||||||
await worker.addJob(
|
await worker.addJob(
|
||||||
"signal/receive-signal-message",
|
"signal/receive-signal-message",
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,9 @@
|
||||||
/*
|
/*
|
||||||
import { URLSearchParams } from "url";
|
import { URLSearchParams } from "url";
|
||||||
import { withDb, AppDatabase } from "../../lib/db.js";
|
import { withDb, AppDatabase } from "../../lib/db.js";
|
||||||
|
import { createLogger } from "@link-stack/logger";
|
||||||
|
|
||||||
|
const logger = createLogger('bridge-worker-import-leafcutter');
|
||||||
// import { loadConfig } from "@digiresilience/bridge-config";
|
// import { loadConfig } from "@digiresilience/bridge-config";
|
||||||
|
|
||||||
const config: any = {};
|
const config: any = {};
|
||||||
|
|
@ -122,7 +125,7 @@ const sendToLeafcutter = async (tickets: LabelStudioTicket[]) => {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
console.info("Sending to Leafcutter");
|
logger.info("Sending to Leafcutter");
|
||||||
|
|
||||||
const result = await fetch(opensearchApiUrl, {
|
const result = await fetch(opensearchApiUrl, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
import { db, getWorkerUtils } from "@link-stack/bridge-common";
|
import { db, getWorkerUtils } from "@link-stack/bridge-common";
|
||||||
|
import { createLogger } from "@link-stack/logger";
|
||||||
import * as signalApi from "@link-stack/signal-api";
|
import * as signalApi from "@link-stack/signal-api";
|
||||||
const { Configuration, GroupsApi } = signalApi;
|
const { Configuration, GroupsApi } = signalApi;
|
||||||
|
|
||||||
|
const logger = createLogger('bridge-worker-receive-signal-message');
|
||||||
|
|
||||||
interface ReceiveSignalMessageTaskOptions {
|
interface ReceiveSignalMessageTaskOptions {
|
||||||
token: string;
|
token: string;
|
||||||
to: string;
|
to: string;
|
||||||
|
|
@ -27,7 +30,7 @@ const receiveSignalMessageTask = async ({
|
||||||
mimeType,
|
mimeType,
|
||||||
isGroup,
|
isGroup,
|
||||||
}: ReceiveSignalMessageTaskOptions): Promise<void> => {
|
}: ReceiveSignalMessageTaskOptions): Promise<void> => {
|
||||||
console.log(`[receive-signal-message] Processing incoming message:`, {
|
logger.debug({
|
||||||
messageId,
|
messageId,
|
||||||
from,
|
from,
|
||||||
to,
|
to,
|
||||||
|
|
@ -35,7 +38,7 @@ const receiveSignalMessageTask = async ({
|
||||||
hasMessage: !!message,
|
hasMessage: !!message,
|
||||||
hasAttachment: !!attachment,
|
hasAttachment: !!attachment,
|
||||||
token,
|
token,
|
||||||
});
|
}, 'Processing incoming message');
|
||||||
const worker = await getWorkerUtils();
|
const worker = await getWorkerUtils();
|
||||||
const row = await db
|
const row = await db
|
||||||
.selectFrom("SignalBot")
|
.selectFrom("SignalBot")
|
||||||
|
|
@ -50,20 +53,18 @@ const receiveSignalMessageTask = async ({
|
||||||
// Check if auto-group creation is enabled and this is NOT already a group message
|
// Check if auto-group creation is enabled and this is NOT already a group message
|
||||||
const enableAutoGroups = process.env.BRIDGE_SIGNAL_AUTO_GROUPS === "true";
|
const enableAutoGroups = process.env.BRIDGE_SIGNAL_AUTO_GROUPS === "true";
|
||||||
|
|
||||||
console.log(`[receive-signal-message] Auto-groups config:`, {
|
logger.debug({
|
||||||
enableAutoGroups,
|
enableAutoGroups,
|
||||||
isGroup,
|
isGroup,
|
||||||
shouldCreateGroup: enableAutoGroups && !isGroup && from && to,
|
shouldCreateGroup: enableAutoGroups && !isGroup && from && to,
|
||||||
});
|
}, 'Auto-groups config');
|
||||||
|
|
||||||
// If this is already a group message and auto-groups is enabled,
|
// If this is already a group message and auto-groups is enabled,
|
||||||
// use group provided in 'to'
|
// use group provided in 'to'
|
||||||
if (enableAutoGroups && isGroup && to) {
|
if (enableAutoGroups && isGroup && to) {
|
||||||
// Signal sends the internal ID (base64) in group messages
|
// Signal sends the internal ID (base64) in group messages
|
||||||
// We should NOT add "group." prefix - that's for sending messages, not receiving
|
// We should NOT add "group." prefix - that's for sending messages, not receiving
|
||||||
console.log(
|
logger.debug('Message is from existing group with internal ID');
|
||||||
`[receive-signal-message] Message is from existing group with internal ID`,
|
|
||||||
);
|
|
||||||
|
|
||||||
finalTo = to;
|
finalTo = to;
|
||||||
} else if (enableAutoGroups && !isGroup && from && to) {
|
} else if (enableAutoGroups && !isGroup && from && to) {
|
||||||
|
|
@ -75,9 +76,7 @@ const receiveSignalMessageTask = async ({
|
||||||
|
|
||||||
// Always create a new group for direct messages to the helpdesk
|
// Always create a new group for direct messages to the helpdesk
|
||||||
// This ensures each conversation gets its own group/ticket
|
// This ensures each conversation gets its own group/ticket
|
||||||
console.log(
|
logger.info({ from }, 'Creating new group for user');
|
||||||
`[receive-signal-message] Creating new group for user ${from}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Include timestamp to make each group unique
|
// Include timestamp to make each group unique
|
||||||
const timestamp = new Date()
|
const timestamp = new Date()
|
||||||
|
|
@ -96,10 +95,7 @@ const receiveSignalMessageTask = async ({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(
|
logger.debug({ createGroupResponse }, 'Group creation response from Signal API');
|
||||||
`[receive-signal-message] Full group creation response from Signal API:`,
|
|
||||||
JSON.stringify(createGroupResponse, null, 2),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (createGroupResponse.id) {
|
if (createGroupResponse.id) {
|
||||||
// The createGroupResponse.id already contains the full group identifier (group.BASE64)
|
// The createGroupResponse.id already contains the full group identifier (group.BASE64)
|
||||||
|
|
@ -108,31 +104,21 @@ const receiveSignalMessageTask = async ({
|
||||||
// Fetch the group details to get the actual internalId
|
// Fetch the group details to get the actual internalId
|
||||||
// The base64 part of the ID is NOT the same as the internalId!
|
// The base64 part of the ID is NOT the same as the internalId!
|
||||||
try {
|
try {
|
||||||
console.log(
|
logger.debug('Fetching group details to get internalId');
|
||||||
`[receive-signal-message] Fetching group details to get internalId...`,
|
|
||||||
);
|
|
||||||
const groups = await groupsClient.v1GroupsNumberGet({
|
const groups = await groupsClient.v1GroupsNumberGet({
|
||||||
number: row.phoneNumber,
|
number: row.phoneNumber,
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(
|
logger.debug({ groupsSample: groups.slice(0, 3) }, 'Groups for bot');
|
||||||
`[receive-signal-message] All groups for bot:`,
|
|
||||||
JSON.stringify(groups.slice(0, 3), null, 2), // Show first 3 to avoid too much output
|
|
||||||
);
|
|
||||||
|
|
||||||
const createdGroup = groups.find((g) => g.id === finalTo);
|
const createdGroup = groups.find((g) => g.id === finalTo);
|
||||||
if (createdGroup) {
|
if (createdGroup) {
|
||||||
console.log(
|
logger.debug({ createdGroup }, 'Found created group details');
|
||||||
`[receive-signal-message] Found created group details:`,
|
|
||||||
JSON.stringify(createdGroup, null, 2),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (createdGroup && createdGroup.internalId) {
|
if (createdGroup && createdGroup.internalId) {
|
||||||
createdInternalId = createdGroup.internalId;
|
createdInternalId = createdGroup.internalId;
|
||||||
console.log(
|
logger.debug({ createdInternalId }, 'Got actual internalId');
|
||||||
`[receive-signal-message] Got actual internalId: ${createdInternalId}`,
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
// Fallback: extract base64 part from ID
|
// Fallback: extract base64 part from ID
|
||||||
if (finalTo.startsWith("group.")) {
|
if (finalTo.startsWith("group.")) {
|
||||||
|
|
@ -140,36 +126,32 @@ const receiveSignalMessageTask = async ({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (fetchError) {
|
} catch (fetchError) {
|
||||||
console.log(
|
logger.debug('Could not fetch group details, using ID base64 part');
|
||||||
`[receive-signal-message] Could not fetch group details, using ID base64 part`,
|
|
||||||
);
|
|
||||||
// Fallback: extract base64 part from ID
|
// Fallback: extract base64 part from ID
|
||||||
if (finalTo.startsWith("group.")) {
|
if (finalTo.startsWith("group.")) {
|
||||||
createdInternalId = finalTo.substring(6);
|
createdInternalId = finalTo.substring(6);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`[receive-signal-message] Group created successfully:`, {
|
logger.debug({
|
||||||
fullGroupId: finalTo,
|
fullGroupId: finalTo,
|
||||||
internalId: createdInternalId,
|
internalId: createdInternalId,
|
||||||
});
|
}, 'Group created successfully');
|
||||||
console.log(`[receive-signal-message] Created new Signal group:`, {
|
logger.debug({
|
||||||
groupId: finalTo,
|
groupId: finalTo,
|
||||||
internalId: createdInternalId,
|
internalId: createdInternalId,
|
||||||
groupName,
|
groupName,
|
||||||
forPhoneNumber: from,
|
forPhoneNumber: from,
|
||||||
botNumber: row.phoneNumber,
|
botNumber: row.phoneNumber,
|
||||||
response: createGroupResponse,
|
response: createGroupResponse,
|
||||||
});
|
}, 'Created new Signal group');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now handle notifications and message forwarding for both new and existing groups
|
// Now handle notifications and message forwarding for both new and existing groups
|
||||||
if (finalTo && finalTo.startsWith("group.")) {
|
if (finalTo && finalTo.startsWith("group.")) {
|
||||||
// Forward the user's initial message to the group using quote feature
|
// Forward the user's initial message to the group using quote feature
|
||||||
try {
|
try {
|
||||||
console.log(
|
logger.debug('Forwarding initial message to group using quote feature');
|
||||||
`[receive-signal-message] Forwarding initial message to group using quote feature`,
|
|
||||||
);
|
|
||||||
|
|
||||||
const attributionMessage = `Message from ${from}:\n"${message}"\n\n---\nSupport team: Your request has been received. An agent will respond shortly.`;
|
const attributionMessage = `Message from ${from}:\n"${message}"\n\n---\nSupport team: Your request has been received. An agent will respond shortly.`;
|
||||||
|
|
||||||
|
|
@ -183,21 +165,14 @@ const receiveSignalMessageTask = async ({
|
||||||
quoteTimestamp: Date.parse(sentAt),
|
quoteTimestamp: Date.parse(sentAt),
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(
|
logger.debug({ finalTo }, 'Successfully forwarded initial message to group');
|
||||||
`[receive-signal-message] Successfully forwarded initial message to group ${finalTo}`,
|
|
||||||
);
|
|
||||||
} catch (forwardError) {
|
} catch (forwardError) {
|
||||||
console.error(
|
logger.error({ error: forwardError }, 'Error forwarding message to group');
|
||||||
"[receive-signal-message] Error forwarding message to group:",
|
|
||||||
forwardError,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send a response to the original DM informing about the group
|
// Send a response to the original DM informing about the group
|
||||||
try {
|
try {
|
||||||
console.log(
|
logger.debug('Sending group notification to original DM');
|
||||||
`[receive-signal-message] Sending group notification to original DM`,
|
|
||||||
);
|
|
||||||
|
|
||||||
const dmNotification = `Hello! A private support group has been created for your conversation.\n\nGroup name: ${groupName}\n\nPlease look for the new group in your Signal app to continue the conversation. Our support team will respond there shortly.\n\nThank you for contacting support!`;
|
const dmNotification = `Hello! A private support group has been created for your conversation.\n\nGroup name: ${groupName}\n\nPlease look for the new group in your Signal app to continue the conversation. Our support team will respond there shortly.\n\nThank you for contacting support!`;
|
||||||
|
|
||||||
|
|
@ -208,14 +183,9 @@ const receiveSignalMessageTask = async ({
|
||||||
conversationId: null,
|
conversationId: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(
|
logger.debug('Successfully sent group notification to user DM');
|
||||||
`[receive-signal-message] Successfully sent group notification to user DM`,
|
|
||||||
);
|
|
||||||
} catch (dmError) {
|
} catch (dmError) {
|
||||||
console.error(
|
logger.error({ error: dmError }, 'Error sending DM notification');
|
||||||
"[receive-signal-message] Error sending DM notification:",
|
|
||||||
dmError,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
|
@ -227,16 +197,14 @@ const receiveSignalMessageTask = async ({
|
||||||
errorMessage?.toString().toLowerCase().includes("exists");
|
errorMessage?.toString().toLowerCase().includes("exists");
|
||||||
|
|
||||||
if (isAlreadyExists) {
|
if (isAlreadyExists) {
|
||||||
console.log(
|
logger.debug({ from }, 'Group might already exist, continuing with original recipient');
|
||||||
`[receive-signal-message] Group might already exist for ${from}, continuing with original recipient`,
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
console.error("[receive-signal-message] Error creating Signal group:", {
|
logger.error({
|
||||||
error: errorMessage,
|
error: errorMessage,
|
||||||
from,
|
from,
|
||||||
to,
|
to,
|
||||||
botNumber: row.phoneNumber,
|
botNumber: row.phoneNumber,
|
||||||
});
|
}, 'Error creating Signal group');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
import { db, getWorkerUtils } from "@link-stack/bridge-common";
|
import { db, getWorkerUtils } from "@link-stack/bridge-common";
|
||||||
|
import { createLogger } from "@link-stack/logger";
|
||||||
import * as signalApi from "@link-stack/signal-api";
|
import * as signalApi from "@link-stack/signal-api";
|
||||||
const { Configuration, MessagesApi, GroupsApi } = signalApi;
|
const { Configuration, MessagesApi, GroupsApi } = signalApi;
|
||||||
|
|
||||||
|
const logger = createLogger('bridge-worker-send-signal-message');
|
||||||
|
|
||||||
interface SendSignalMessageTaskOptions {
|
interface SendSignalMessageTaskOptions {
|
||||||
token: string;
|
token: string;
|
||||||
to: string;
|
to: string;
|
||||||
|
|
@ -21,12 +24,12 @@ const sendSignalMessageTask = async ({
|
||||||
quoteAuthor,
|
quoteAuthor,
|
||||||
quoteTimestamp,
|
quoteTimestamp,
|
||||||
}: SendSignalMessageTaskOptions): Promise<void> => {
|
}: SendSignalMessageTaskOptions): Promise<void> => {
|
||||||
console.log(`[send-signal-message] Processing outgoing message:`, {
|
logger.debug({
|
||||||
token,
|
token,
|
||||||
to,
|
to,
|
||||||
conversationId,
|
conversationId,
|
||||||
messageLength: message?.length,
|
messageLength: message?.length,
|
||||||
});
|
}, 'Processing outgoing message');
|
||||||
const bot = await db
|
const bot = await db
|
||||||
.selectFrom("SignalBot")
|
.selectFrom("SignalBot")
|
||||||
.selectAll()
|
.selectAll()
|
||||||
|
|
@ -55,12 +58,12 @@ const sendSignalMessageTask = async ({
|
||||||
const isGroupId = isUUID || isGroupPrefix || isBase64;
|
const isGroupId = isUUID || isGroupPrefix || isBase64;
|
||||||
const enableAutoGroups = process.env.BRIDGE_SIGNAL_AUTO_GROUPS === "true";
|
const enableAutoGroups = process.env.BRIDGE_SIGNAL_AUTO_GROUPS === "true";
|
||||||
|
|
||||||
console.log(`[send-signal-message] Recipient analysis:`, {
|
logger.debug({
|
||||||
to,
|
to,
|
||||||
isGroupId,
|
isGroupId,
|
||||||
enableAutoGroups,
|
enableAutoGroups,
|
||||||
shouldCreateGroup: enableAutoGroups && !isGroupId && to && conversationId,
|
shouldCreateGroup: enableAutoGroups && !isGroupId && to && conversationId,
|
||||||
});
|
}, 'Recipient analysis');
|
||||||
|
|
||||||
// If sending to a phone number and auto-groups is enabled, create a group first
|
// If sending to a phone number and auto-groups is enabled, create a group first
|
||||||
if (enableAutoGroups && !isGroupId && to && conversationId) {
|
if (enableAutoGroups && !isGroupId && to && conversationId) {
|
||||||
|
|
@ -90,9 +93,7 @@ const sendSignalMessageTask = async ({
|
||||||
const createdGroup = groups.find((g) => g.id === finalTo);
|
const createdGroup = groups.find((g) => g.id === finalTo);
|
||||||
if (createdGroup && createdGroup.internalId) {
|
if (createdGroup && createdGroup.internalId) {
|
||||||
internalId = createdGroup.internalId;
|
internalId = createdGroup.internalId;
|
||||||
console.log(
|
logger.debug({ internalId }, 'Got actual internalId');
|
||||||
`[send-signal-message] Got actual internalId: ${internalId}`,
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
// Fallback: extract base64 part from ID
|
// Fallback: extract base64 part from ID
|
||||||
if (finalTo.startsWith("group.")) {
|
if (finalTo.startsWith("group.")) {
|
||||||
|
|
@ -100,22 +101,20 @@ const sendSignalMessageTask = async ({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (fetchError) {
|
} catch (fetchError) {
|
||||||
console.log(
|
logger.debug('Could not fetch group details, using ID base64 part');
|
||||||
`[send-signal-message] Could not fetch group details, using ID base64 part`,
|
|
||||||
);
|
|
||||||
// Fallback: extract base64 part from ID
|
// Fallback: extract base64 part from ID
|
||||||
if (finalTo.startsWith("group.")) {
|
if (finalTo.startsWith("group.")) {
|
||||||
internalId = finalTo.substring(6);
|
internalId = finalTo.substring(6);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log(`[send-signal-message] Created new Signal group:`, {
|
logger.debug({
|
||||||
groupId: finalTo,
|
groupId: finalTo,
|
||||||
internalId,
|
internalId,
|
||||||
groupName,
|
groupName,
|
||||||
conversationId,
|
conversationId,
|
||||||
originalRecipient: to,
|
originalRecipient: to,
|
||||||
botNumber: bot.phoneNumber,
|
botNumber: bot.phoneNumber,
|
||||||
});
|
}, 'Created new Signal group');
|
||||||
|
|
||||||
// Notify Zammad about the new group ID via webhook
|
// Notify Zammad about the new group ID via webhook
|
||||||
await worker.addJob("common/notify-webhooks", {
|
await worker.addJob("common/notify-webhooks", {
|
||||||
|
|
@ -131,23 +130,23 @@ const sendSignalMessageTask = async ({
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (groupError) {
|
} catch (groupError) {
|
||||||
console.error("[send-signal-message] Error creating Signal group:", {
|
logger.error({
|
||||||
error: groupError instanceof Error ? groupError.message : groupError,
|
error: groupError instanceof Error ? groupError.message : groupError,
|
||||||
to,
|
to,
|
||||||
conversationId,
|
conversationId,
|
||||||
});
|
}, 'Error creating Signal group');
|
||||||
// Continue with original recipient if group creation fails
|
// Continue with original recipient if group creation fails
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`[send-signal-message] Sending message via API:`, {
|
logger.debug({
|
||||||
fromNumber: number,
|
fromNumber: number,
|
||||||
toRecipient: finalTo,
|
toRecipient: finalTo,
|
||||||
originalTo: to,
|
originalTo: to,
|
||||||
recipientChanged: to !== finalTo,
|
recipientChanged: to !== finalTo,
|
||||||
groupCreated,
|
groupCreated,
|
||||||
isGroupRecipient: finalTo.startsWith("group."),
|
isGroupRecipient: finalTo.startsWith("group."),
|
||||||
});
|
}, 'Sending message via API');
|
||||||
|
|
||||||
// Build the message data with optional quote parameters
|
// Build the message data with optional quote parameters
|
||||||
const messageData: signalApi.ApiSendMessageV2 = {
|
const messageData: signalApi.ApiSendMessageV2 = {
|
||||||
|
|
@ -156,12 +155,12 @@ const sendSignalMessageTask = async ({
|
||||||
message,
|
message,
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log(`[send-signal-message] Message data being sent:`, {
|
logger.debug({
|
||||||
number,
|
number,
|
||||||
recipients: [finalTo],
|
recipients: [finalTo],
|
||||||
message: message.substring(0, 50) + "...",
|
message: message.substring(0, 50) + "...",
|
||||||
hasQuoteParams: !!(quoteMessage && quoteAuthor && quoteTimestamp),
|
hasQuoteParams: !!(quoteMessage && quoteAuthor && quoteTimestamp),
|
||||||
});
|
}, 'Message data being sent');
|
||||||
|
|
||||||
// Add quote parameters if all are provided
|
// Add quote parameters if all are provided
|
||||||
if (quoteMessage && quoteAuthor && quoteTimestamp) {
|
if (quoteMessage && quoteAuthor && quoteTimestamp) {
|
||||||
|
|
@ -169,28 +168,28 @@ const sendSignalMessageTask = async ({
|
||||||
messageData.quoteAuthor = quoteAuthor;
|
messageData.quoteAuthor = quoteAuthor;
|
||||||
messageData.quoteMessage = quoteMessage;
|
messageData.quoteMessage = quoteMessage;
|
||||||
|
|
||||||
console.log(`[send-signal-message] Including quote in message:`, {
|
logger.debug({
|
||||||
quoteAuthor,
|
quoteAuthor,
|
||||||
quoteMessage: quoteMessage.substring(0, 50) + "...",
|
quoteMessage: quoteMessage.substring(0, 50) + "...",
|
||||||
quoteTimestamp,
|
quoteTimestamp,
|
||||||
});
|
}, 'Including quote in message');
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await messagesClient.v2SendPost({
|
const response = await messagesClient.v2SendPost({
|
||||||
data: messageData,
|
data: messageData,
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(`[send-signal-message] Message sent successfully:`, {
|
logger.debug({
|
||||||
to: finalTo,
|
to: finalTo,
|
||||||
groupCreated,
|
groupCreated,
|
||||||
response: response?.timestamp || "no timestamp",
|
response: response?.timestamp || "no timestamp",
|
||||||
});
|
}, 'Message sent successfully');
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
// Try to get the actual error message from the response
|
// Try to get the actual error message from the response
|
||||||
if (error.response) {
|
if (error.response) {
|
||||||
try {
|
try {
|
||||||
const errorBody = await error.response.text();
|
const errorBody = await error.response.text();
|
||||||
console.error(`[send-signal-message] Signal API error:`, {
|
logger.error({
|
||||||
status: error.response.status,
|
status: error.response.status,
|
||||||
statusText: error.response.statusText,
|
statusText: error.response.statusText,
|
||||||
body: errorBody,
|
body: errorBody,
|
||||||
|
|
@ -200,12 +199,12 @@ const sendSignalMessageTask = async ({
|
||||||
toRecipients: [finalTo],
|
toRecipients: [finalTo],
|
||||||
hasQuote: !!quoteMessage,
|
hasQuote: !!quoteMessage,
|
||||||
},
|
},
|
||||||
});
|
}, 'Signal API error');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(`[send-signal-message] Could not parse error response`);
|
logger.error('Could not parse error response');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.error(`[send-signal-message] Full error:`, error);
|
logger.error({ error }, 'Full error details');
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,9 @@ import { withDb, AppDatabase } from "../../lib/db.js";
|
||||||
import { twilioClientFor } from "../../lib/common.js";
|
import { twilioClientFor } from "../../lib/common.js";
|
||||||
import { CallInstance } from "twilio/lib/rest/api/v2010/account/call";
|
import { CallInstance } from "twilio/lib/rest/api/v2010/account/call";
|
||||||
import workerUtils from "../../lib/utils.js";
|
import workerUtils from "../../lib/utils.js";
|
||||||
|
import { createLogger } from "@link-stack/logger";
|
||||||
|
|
||||||
|
const logger = createLogger('bridge-worker-twilio-recording');
|
||||||
|
|
||||||
interface WebhookPayload {
|
interface WebhookPayload {
|
||||||
startTime: string;
|
startTime: string;
|
||||||
|
|
@ -20,7 +23,7 @@ const getTwilioRecording = async (url: string) => {
|
||||||
const { payload } = await Wreck.get(url);
|
const { payload } = await Wreck.get(url);
|
||||||
return { recording: payload as Buffer };
|
return { recording: payload as Buffer };
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error(error.output);
|
logger.error(error.output);
|
||||||
return { error: error.output };
|
return { error: error.output };
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
import { db } from "@link-stack/bridge-common";
|
import { db } from "@link-stack/bridge-common";
|
||||||
|
import { createLogger } from "@link-stack/logger";
|
||||||
|
|
||||||
|
const logger = createLogger('bridge-worker-send-whatsapp-message');
|
||||||
|
|
||||||
interface SendWhatsappMessageTaskOptions {
|
interface SendWhatsappMessageTaskOptions {
|
||||||
token: string;
|
token: string;
|
||||||
|
|
@ -26,7 +29,7 @@ const sendWhatsappMessageTask = async ({
|
||||||
body: JSON.stringify(params),
|
body: JSON.stringify(params),
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error({ error });
|
logger.error({ error });
|
||||||
throw new Error("Failed to send message");
|
throw new Error("Failed to send message");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,9 @@ import Google from "next-auth/providers/google";
|
||||||
import Apple from "next-auth/providers/apple";
|
import Apple from "next-auth/providers/apple";
|
||||||
import Credentials from "next-auth/providers/credentials";
|
import Credentials from "next-auth/providers/credentials";
|
||||||
import { checkAuth } from "./opensearch";
|
import { checkAuth } from "./opensearch";
|
||||||
|
import { createLogger } from "@link-stack/logger";
|
||||||
|
|
||||||
|
const logger = createLogger('leafcutter-auth');
|
||||||
|
|
||||||
export const authOptions: NextAuthOptions = {
|
export const authOptions: NextAuthOptions = {
|
||||||
pages: {
|
pages: {
|
||||||
|
|
@ -45,7 +48,7 @@ export const authOptions: NextAuthOptions = {
|
||||||
|
|
||||||
return user;
|
return user;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error({ e });
|
logger.error({ e });
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
/* eslint-disable no-underscore-dangle */
|
/* eslint-disable no-underscore-dangle */
|
||||||
import { Client } from "@opensearch-project/opensearch";
|
import { Client } from "@opensearch-project/opensearch";
|
||||||
import { v4 as uuid } from "uuid";
|
import { v4 as uuid } from "uuid";
|
||||||
|
import { createLogger } from "@link-stack/logger";
|
||||||
|
|
||||||
|
const logger = createLogger('leafcutter-opensearch');
|
||||||
|
|
||||||
/* Common */
|
/* Common */
|
||||||
|
|
||||||
|
|
@ -302,7 +305,7 @@ export const updateUserVisualization = async (
|
||||||
body,
|
body,
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error({ e });
|
logger.error({ e });
|
||||||
}
|
}
|
||||||
|
|
||||||
return id;
|
return id;
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,9 @@ import { Client } from "@opensearch-project/opensearch";
|
||||||
import { v4 as uuid } from "uuid";
|
import { v4 as uuid } from "uuid";
|
||||||
import taxonomy from "app/_config/taxonomy.json";
|
import taxonomy from "app/_config/taxonomy.json";
|
||||||
import unRegions from "app/_config/unRegions.json";
|
import unRegions from "app/_config/unRegions.json";
|
||||||
|
import { createLogger } from "@link-stack/logger";
|
||||||
|
|
||||||
|
const logger = createLogger('leafcutter-index');
|
||||||
|
|
||||||
export const POST = async (req: NextRequest) => {
|
export const POST = async (req: NextRequest) => {
|
||||||
const { tickets } = await req.json();
|
const { tickets } = await req.json();
|
||||||
|
|
@ -46,7 +49,7 @@ export const POST = async (req: NextRequest) => {
|
||||||
});
|
});
|
||||||
succeeded.push(id);
|
succeeded.push(id);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
logger.error(e);
|
||||||
failed.push(id);
|
failed.push(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@
|
||||||
"@emotion/server": "^11.11.0",
|
"@emotion/server": "^11.11.0",
|
||||||
"@emotion/styled": "^11.14.0",
|
"@emotion/styled": "^11.14.0",
|
||||||
"@link-stack/leafcutter-ui": "*",
|
"@link-stack/leafcutter-ui": "*",
|
||||||
|
"@link-stack/logger": "*",
|
||||||
"@link-stack/opensearch-common": "*",
|
"@link-stack/opensearch-common": "*",
|
||||||
"@mui/icons-material": "^6",
|
"@mui/icons-material": "^6",
|
||||||
"@mui/material": "^6",
|
"@mui/material": "^6",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
import { createProxyMiddleware } from "http-proxy-middleware";
|
import { createProxyMiddleware } from "http-proxy-middleware";
|
||||||
import { NextApiRequest, NextApiResponse } from "next";
|
import { NextApiRequest, NextApiResponse } from "next";
|
||||||
import { getToken } from "next-auth/jwt";
|
import { getToken } from "next-auth/jwt";
|
||||||
|
import { createLogger } from "@link-stack/logger";
|
||||||
|
|
||||||
|
const logger = createLogger('leafcutter-[[...path]]');
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
||||||
|
|
@ -30,11 +33,11 @@ const withAuthInfo =
|
||||||
);
|
);
|
||||||
|
|
||||||
if (requestSignature && isAppPath) {
|
if (requestSignature && isAppPath) {
|
||||||
console.info("Has Signature");
|
logger.info("Has Signature");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (referrerSignature && isResourcePath) {
|
if (referrerSignature && isResourcePath) {
|
||||||
console.info("Has Signature");
|
logger.info("Has Signature");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!email) {
|
if (!email) {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
import { Metadata } from "next";
|
import { Metadata } from "next";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import { Home } from "./_components/Home";
|
import { Home } from "./_components/Home";
|
||||||
|
import { createLogger } from "@link-stack/logger";
|
||||||
|
|
||||||
|
const logger = createLogger('link-page');
|
||||||
// import { getServerSession } from "app/_lib/authentication";
|
// import { getServerSession } from "app/_lib/authentication";
|
||||||
// import { Home } from "@link-stack/leafcutter-ui";
|
// import { Home } from "@link-stack/leafcutter-ui";
|
||||||
// import { getUserVisualizations } from "@link-stack/opensearch-common";
|
// import { getUserVisualizations } from "@link-stack/opensearch-common";
|
||||||
|
|
@ -28,7 +31,7 @@ export default async function Page() {
|
||||||
try {
|
try {
|
||||||
visualizations = await getUserVisualizations(email ?? "none", 20);
|
visualizations = await getUserVisualizations(email ?? "none", 20);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e.meta);
|
logger.error({ meta: e.meta }, "Error metadata");
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
"use server";
|
"use server";
|
||||||
|
|
||||||
import { executeREST } from "app/_lib/zammad";
|
import { executeREST } from "app/_lib/zammad";
|
||||||
|
import { createLogger } from "@link-stack/logger";
|
||||||
|
|
||||||
|
const logger = createLogger('link-groups');
|
||||||
|
|
||||||
export const getGroupsAction = async () => {
|
export const getGroupsAction = async () => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -15,7 +18,7 @@ export const getGroupsAction = async () => {
|
||||||
|
|
||||||
return formattedGroups;
|
return formattedGroups;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e.message);
|
logger.error({ error: e }, "Error occurred");
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,9 @@
|
||||||
import { executeGraphQL, executeREST } from "app/_lib/zammad";
|
import { executeGraphQL, executeREST } from "app/_lib/zammad";
|
||||||
import { getTicketOverviewCountsQuery } from "app/_graphql/getTicketOverviewCountsQuery";
|
import { getTicketOverviewCountsQuery } from "app/_graphql/getTicketOverviewCountsQuery";
|
||||||
import { getTicketsByOverviewQuery } from "app/_graphql/getTicketsByOverviewQuery";
|
import { getTicketsByOverviewQuery } from "app/_graphql/getTicketsByOverviewQuery";
|
||||||
|
import { createLogger } from "@link-stack/logger";
|
||||||
|
|
||||||
|
const logger = createLogger('link-overviews');
|
||||||
|
|
||||||
const overviewLookup = {
|
const overviewLookup = {
|
||||||
Assigned: "My Assigned Tickets",
|
Assigned: "My Assigned Tickets",
|
||||||
|
|
@ -36,7 +39,7 @@ export const getOverviewTicketCountsAction = async () => {
|
||||||
|
|
||||||
return counts;
|
return counts;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e.message);
|
logger.error({ error: e }, "Error occurred");
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -91,7 +94,7 @@ export const getOverviewTicketsAction = async (name: string) => {
|
||||||
|
|
||||||
return { tickets: sortedTickets };
|
return { tickets: sortedTickets };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e.message);
|
logger.error({ error: e }, "Error occurred");
|
||||||
return { tickets, message: e.message ?? "" };
|
return { tickets, message: e.message ?? "" };
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
"use server";
|
"use server";
|
||||||
import { executeGraphQL } from "app/_lib/zammad";
|
import { executeGraphQL } from "app/_lib/zammad";
|
||||||
import { searchQuery } from "@/app/_graphql/searchQuery";
|
import { searchQuery } from "@/app/_graphql/searchQuery";
|
||||||
|
import { createLogger } from "@link-stack/logger";
|
||||||
|
|
||||||
|
const logger = createLogger('link-search');
|
||||||
|
|
||||||
export const searchAllAction = async (query: string, limit: number) => {
|
export const searchAllAction = async (query: string, limit: number) => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -11,7 +14,7 @@ export const searchAllAction = async (query: string, limit: number) => {
|
||||||
|
|
||||||
return result?.search;
|
return result?.search;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e.message);
|
logger.error({ error: e }, "Error occurred");
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,9 @@ import { createTicketMutation } from "app/_graphql/createTicketMutation";
|
||||||
import { updateTicketMutation } from "app/_graphql/updateTicketMutation";
|
import { updateTicketMutation } from "app/_graphql/updateTicketMutation";
|
||||||
import { updateTagsMutation } from "app/_graphql/updateTagsMutation";
|
import { updateTagsMutation } from "app/_graphql/updateTagsMutation";
|
||||||
import { executeGraphQL, executeREST } from "app/_lib/zammad";
|
import { executeGraphQL, executeREST } from "app/_lib/zammad";
|
||||||
|
import { createLogger } from "@link-stack/logger";
|
||||||
|
|
||||||
|
const logger = createLogger('link-tickets');
|
||||||
|
|
||||||
export const createTicketAction = async (
|
export const createTicketAction = async (
|
||||||
currentState: any,
|
currentState: any,
|
||||||
|
|
@ -35,7 +38,7 @@ export const createTicketAction = async (
|
||||||
success: true,
|
success: true,
|
||||||
};
|
};
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e.message);
|
logger.error({ error: e }, "Error occurred");
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
values: {},
|
values: {},
|
||||||
|
|
@ -62,7 +65,7 @@ export const createTicketArticleAction = async (
|
||||||
success: true,
|
success: true,
|
||||||
};
|
};
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e.message);
|
logger.error({ error: e }, "Error occurred");
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: e?.message ?? "Unknown error",
|
message: e?.message ?? "Unknown error",
|
||||||
|
|
@ -115,7 +118,7 @@ export const updateTicketAction = async (
|
||||||
success: true,
|
success: true,
|
||||||
};
|
};
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e.message);
|
logger.error({ error: e }, "Error occurred");
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: e?.message ?? "Unknown error",
|
message: e?.message ?? "Unknown error",
|
||||||
|
|
@ -132,7 +135,7 @@ export const getTicketAction = async (id: string) => {
|
||||||
|
|
||||||
return ticketData?.ticket;
|
return ticketData?.ticket;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e.message);
|
logger.error({ error: e }, "Error occurred");
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -146,7 +149,7 @@ export const getTicketArticlesAction = async (id: string) => {
|
||||||
|
|
||||||
return ticketData?.ticketArticles;
|
return ticketData?.ticketArticles;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e.message);
|
logger.error({ error: e }, "Error occurred");
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -164,7 +167,7 @@ export const getTicketStatesAction = async () => {
|
||||||
})) ?? [];
|
})) ?? [];
|
||||||
return formattedStates;
|
return formattedStates;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e.message);
|
logger.error({ error: e }, "Error occurred");
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -177,7 +180,7 @@ export const getTagsAction = async () => {
|
||||||
|
|
||||||
return tags;
|
return tags;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e.message);
|
logger.error({ error: e }, "Error occurred");
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -196,7 +199,7 @@ export const getTicketPrioritiesAction = async () => {
|
||||||
|
|
||||||
return formattedPriorities;
|
return formattedPriorities;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e.message);
|
logger.error({ error: e }, "Error occurred");
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
"use server";
|
"use server";
|
||||||
|
|
||||||
import { executeREST } from "app/_lib/zammad";
|
import { executeREST } from "app/_lib/zammad";
|
||||||
|
import { createLogger } from "@link-stack/logger";
|
||||||
|
|
||||||
|
const logger = createLogger('link-users');
|
||||||
|
|
||||||
export const getAgentsAction = async (groupID: number) => {
|
export const getAgentsAction = async (groupID: number) => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -21,7 +24,7 @@ export const getAgentsAction = async (groupID: number) => {
|
||||||
|
|
||||||
return formattedAgents;
|
return formattedAgents;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e.message);
|
logger.error({ error: e }, "Error occurred");
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -42,7 +45,7 @@ export const getCustomersAction = async () => {
|
||||||
|
|
||||||
return formattedCustomers;
|
return formattedCustomers;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e.message);
|
logger.error({ error: e }, "Error occurred");
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -61,7 +64,7 @@ export const getUsersAction = async () => {
|
||||||
|
|
||||||
return formattedUsers;
|
return formattedUsers;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e.message);
|
logger.error({ error: e }, "Error occurred");
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,9 @@ 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";
|
import { Redis } from "ioredis";
|
||||||
import AzureADProvider from "next-auth/providers/azure-ad";
|
import AzureADProvider from "next-auth/providers/azure-ad";
|
||||||
|
import { createLogger } from "@link-stack/logger";
|
||||||
|
|
||||||
|
const logger = createLogger('link-authentication');
|
||||||
|
|
||||||
const headers = { Authorization: `Token ${process.env.ZAMMAD_API_TOKEN}` };
|
const headers = { Authorization: `Token ${process.env.ZAMMAD_API_TOKEN}` };
|
||||||
|
|
||||||
|
|
@ -48,7 +51,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.error({ e });
|
logger.error({ error: e, email }, 'Failed to get user roles');
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,7 @@
|
||||||
|
import { createLogger } from "@link-stack/logger";
|
||||||
|
|
||||||
|
const logger = createLogger('link-utils');
|
||||||
|
|
||||||
export const fetchLeafcutter = async (url: string, options: any) => {
|
export const fetchLeafcutter = async (url: string, options: any) => {
|
||||||
/*
|
/*
|
||||||
|
|
||||||
|
|
@ -13,7 +17,7 @@ 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.error({ error });
|
logger.error({ error }, "Error occurred");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
import { withAuth, NextRequestWithAuth } from "next-auth/middleware";
|
import { withAuth, NextRequestWithAuth } from "next-auth/middleware";
|
||||||
|
import { createLogger } from "@link-stack/logger";
|
||||||
|
|
||||||
|
const logger = createLogger('link-middleware');
|
||||||
|
|
||||||
const rewriteURL = (
|
const rewriteURL = (
|
||||||
request: NextRequestWithAuth,
|
request: NextRequestWithAuth,
|
||||||
|
|
@ -7,14 +10,17 @@ const rewriteURL = (
|
||||||
destinationBaseURL: string,
|
destinationBaseURL: string,
|
||||||
headers: any = {},
|
headers: any = {},
|
||||||
) => {
|
) => {
|
||||||
console.log("Rewriting URL");
|
logger.debug({
|
||||||
console.log({ request, originBaseURL, destinationBaseURL, headers });
|
originBaseURL,
|
||||||
|
destinationBaseURL,
|
||||||
|
headerKeys: Object.keys(headers)
|
||||||
|
}, "Rewriting URL");
|
||||||
let path = request.url.replace(originBaseURL, "");
|
let path = request.url.replace(originBaseURL, "");
|
||||||
if (path.startsWith("/")) {
|
if (path.startsWith("/")) {
|
||||||
path = path.slice(1);
|
path = path.slice(1);
|
||||||
}
|
}
|
||||||
const destinationURL = `${destinationBaseURL}/${path}`;
|
const destinationURL = `${destinationBaseURL}/${path}`;
|
||||||
console.info(`Rewriting ${request.url} to ${destinationURL}`);
|
logger.debug({ from: request.url, to: destinationURL }, "URL rewrite");
|
||||||
const requestHeaders = new Headers(request.headers);
|
const requestHeaders = new Headers(request.headers);
|
||||||
|
|
||||||
requestHeaders.delete("x-forwarded-user");
|
requestHeaders.delete("x-forwarded-user");
|
||||||
|
|
@ -32,7 +38,7 @@ 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";
|
||||||
console.log({ linkBaseURL });
|
logger.debug({ linkBaseURL }, "Link base URL");
|
||||||
const opensearchBaseURL =
|
const opensearchBaseURL =
|
||||||
process.env.OPENSEARCH_DASHBOARDS_URL ??
|
process.env.OPENSEARCH_DASHBOARDS_URL ??
|
||||||
"http://opensearch-dashboards:5601";
|
"http://opensearch-dashboards:5601";
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@
|
||||||
"@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/logger": "*",
|
||||||
"@link-stack/opensearch-common": "*",
|
"@link-stack/opensearch-common": "*",
|
||||||
"@link-stack/ui": "*",
|
"@link-stack/ui": "*",
|
||||||
"@mui/icons-material": "^6",
|
"@mui/icons-material": "^6",
|
||||||
|
|
|
||||||
5682
package-lock.json
generated
5682
package-lock.json
generated
File diff suppressed because it is too large
Load diff
19
package.json
19
package.json
|
|
@ -62,21 +62,10 @@
|
||||||
"typescript": "latest"
|
"typescript": "latest"
|
||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"material-ui-popup-state": {
|
"react": "^19.0.0",
|
||||||
"react": "$react"
|
"react-dom": "^19.0.0",
|
||||||
},
|
"@types/react": "^19.0.12",
|
||||||
"@chatscope/chat-ui-kit-react": {
|
"@mui/material": "^6.5.0"
|
||||||
"react": "$react",
|
|
||||||
"react-dom": "$react-dom"
|
|
||||||
},
|
|
||||||
"@mui/icons-material": {
|
|
||||||
"react": "$react"
|
|
||||||
},
|
|
||||||
"mui-chips-input": {
|
|
||||||
"react": "$react",
|
|
||||||
"react-dom": "$react-dom",
|
|
||||||
"@types/react": "$@types/react"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"npm": ">=10",
|
"npm": ">=10",
|
||||||
|
|
|
||||||
36
packages/logger/package.json
Normal file
36
packages/logger/package.json
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
{
|
||||||
|
"name": "@link-stack/logger",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Shared logging utility for Link Stack monorepo",
|
||||||
|
"main": "./dist/index.js",
|
||||||
|
"module": "./dist/index.mjs",
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"require": "./dist/index.js",
|
||||||
|
"import": "./dist/index.mjs",
|
||||||
|
"types": "./dist/index.d.ts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsup src/index.ts --format cjs,esm --dts --clean",
|
||||||
|
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
|
||||||
|
"lint": "eslint . --ext .ts",
|
||||||
|
"type-check": "tsc --noEmit"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"pino": "^9.5.0",
|
||||||
|
"pino-pretty": "^13.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@link-stack/eslint-config": "*",
|
||||||
|
"@link-stack/typescript-config": "*",
|
||||||
|
"@types/node": "^22.10.5",
|
||||||
|
"eslint": "^9.17.0",
|
||||||
|
"tsup": "^8.3.5",
|
||||||
|
"typescript": "^5.7.3"
|
||||||
|
},
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
}
|
||||||
|
}
|
||||||
56
packages/logger/src/config.ts
Normal file
56
packages/logger/src/config.ts
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
import type { LoggerOptions } from 'pino';
|
||||||
|
|
||||||
|
export const getLogLevel = (): string => {
|
||||||
|
return process.env.LOG_LEVEL || (process.env.NODE_ENV === 'production' ? 'info' : 'debug');
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getPinoConfig = (): LoggerOptions => {
|
||||||
|
const isDevelopment = process.env.NODE_ENV !== 'production';
|
||||||
|
|
||||||
|
const baseConfig: LoggerOptions = {
|
||||||
|
level: getLogLevel(),
|
||||||
|
formatters: {
|
||||||
|
level: (label) => {
|
||||||
|
return { level: label.toUpperCase() };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
timestamp: () => `,"timestamp":"${new Date(Date.now()).toISOString()}"`,
|
||||||
|
redact: {
|
||||||
|
paths: [
|
||||||
|
'password',
|
||||||
|
'token',
|
||||||
|
'secret',
|
||||||
|
'api_key',
|
||||||
|
'apiKey',
|
||||||
|
'authorization',
|
||||||
|
'cookie',
|
||||||
|
'*.password',
|
||||||
|
'*.token',
|
||||||
|
'*.secret',
|
||||||
|
'*.api_key',
|
||||||
|
'*.apiKey',
|
||||||
|
],
|
||||||
|
censor: '[REDACTED]',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isDevelopment) {
|
||||||
|
// In development, use pretty printing for better readability
|
||||||
|
return {
|
||||||
|
...baseConfig,
|
||||||
|
transport: {
|
||||||
|
target: 'pino-pretty',
|
||||||
|
options: {
|
||||||
|
colorize: true,
|
||||||
|
translateTime: 'SYS:standard',
|
||||||
|
ignore: 'pid,hostname',
|
||||||
|
singleLine: false,
|
||||||
|
messageFormat: '{msg}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// In production, use JSON for structured logging
|
||||||
|
return baseConfig;
|
||||||
|
};
|
||||||
35
packages/logger/src/index.ts
Normal file
35
packages/logger/src/index.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
import pino, { Logger as PinoLogger } from 'pino';
|
||||||
|
import { getPinoConfig } from './config';
|
||||||
|
|
||||||
|
export type Logger = PinoLogger;
|
||||||
|
|
||||||
|
// Create the default logger instance
|
||||||
|
export const logger: Logger = pino(getPinoConfig());
|
||||||
|
|
||||||
|
// Factory function to create child loggers with context
|
||||||
|
export const createLogger = (name: string, context?: Record<string, any>): Logger => {
|
||||||
|
return logger.child({ name, ...context });
|
||||||
|
};
|
||||||
|
|
||||||
|
// Export log levels for consistency
|
||||||
|
export const LogLevel = {
|
||||||
|
TRACE: 'trace',
|
||||||
|
DEBUG: 'debug',
|
||||||
|
INFO: 'info',
|
||||||
|
WARN: 'warn',
|
||||||
|
ERROR: 'error',
|
||||||
|
FATAL: 'fatal',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type LogLevelType = typeof LogLevel[keyof typeof LogLevel];
|
||||||
|
|
||||||
|
// Utility to check if a log level is enabled
|
||||||
|
export const isLogLevelEnabled = (level: LogLevelType): boolean => {
|
||||||
|
return logger.isLevelEnabled(level);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Re-export pino types that might be useful
|
||||||
|
export type { LoggerOptions, DestinationStream } from 'pino';
|
||||||
|
|
||||||
|
// Default export for convenience
|
||||||
|
export default logger;
|
||||||
21
packages/logger/tsconfig.json
Normal file
21
packages/logger/tsconfig.json
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es2022",
|
||||||
|
"lib": ["es2022"],
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"outDir": "./dist",
|
||||||
|
"rootDir": "./src",
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"allowJs": false,
|
||||||
|
"types": ["node"]
|
||||||
|
},
|
||||||
|
"include": ["src/**/*"],
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
/* eslint-disable no-underscore-dangle */
|
/* eslint-disable no-underscore-dangle */
|
||||||
import { Client } from "@opensearch-project/opensearch";
|
import { Client } from "@opensearch-project/opensearch";
|
||||||
import { v4 as uuid } from "uuid";
|
import { v4 as uuid } from "uuid";
|
||||||
|
import { createLogger } from "@link-stack/logger";
|
||||||
|
|
||||||
|
const logger = createLogger('opensearch-common-opensearch');
|
||||||
|
|
||||||
/* Common */
|
/* Common */
|
||||||
|
|
||||||
|
|
@ -284,7 +287,7 @@ export const updateUserVisualization = async (
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.error({ e });
|
logger.error({ e });
|
||||||
}
|
}
|
||||||
|
|
||||||
return id;
|
return id;
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
"build": "tsc -p tsconfig.json"
|
"build": "tsc -p tsconfig.json"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@link-stack/logger": "*",
|
||||||
"@opensearch-project/opensearch": "^3.4.0",
|
"@opensearch-project/opensearch": "^3.4.0",
|
||||||
"uuid": "^11.1.0"
|
"uuid": "^11.1.0"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,12 @@ import { promises as fs } from "fs";
|
||||||
import { glob } from "glob";
|
import { glob } from "glob";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import os from "os";
|
import os from "os";
|
||||||
|
import { createLogger } from "@link-stack/logger";
|
||||||
|
|
||||||
|
const logger = createLogger('zammad-addon-build');
|
||||||
|
|
||||||
const packageFile = async (actualPath: string): Promise<any> => {
|
const packageFile = async (actualPath: string): Promise<any> => {
|
||||||
console.info(`Packaging: ${actualPath}`);
|
logger.info({ actualPath }, 'Packaging file');
|
||||||
const packagePath = actualPath.slice(4);
|
const packagePath = actualPath.slice(4);
|
||||||
const data = await fs.readFile(actualPath, "utf-8");
|
const data = await fs.readFile(actualPath, "utf-8");
|
||||||
const content = Buffer.from(data, "utf-8").toString("base64");
|
const content = Buffer.from(data, "utf-8").toString("base64");
|
||||||
|
|
@ -74,10 +77,10 @@ export const createZPM = async ({
|
||||||
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
await fs.unlink(file);
|
await fs.unlink(file);
|
||||||
console.info(`${file} was deleted`);
|
logger.info({ file }, 'File was deleted');
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
logger.error({ error: err }, 'Error removing old addon files');
|
||||||
}
|
}
|
||||||
await fs.writeFile(
|
await fs.writeFile(
|
||||||
`../../docker/zammad/addons/${name}-v${version}.zpm`,
|
`../../docker/zammad/addons/${name}-v${version}.zpm`,
|
||||||
|
|
@ -89,7 +92,7 @@ export const createZPM = async ({
|
||||||
const main = async () => {
|
const main = async () => {
|
||||||
const packageJSON = JSON.parse(await fs.readFile("./package.json", "utf-8"));
|
const packageJSON = JSON.parse(await fs.readFile("./package.json", "utf-8"));
|
||||||
const { name: fullName, displayName, version } = packageJSON;
|
const { name: fullName, displayName, version } = packageJSON;
|
||||||
console.info(`Building addon ${displayName} v${version}`);
|
logger.info({ displayName, version }, 'Building addon');
|
||||||
const name = fullName.split("/").pop();
|
const name = fullName.split("/").pop();
|
||||||
await createZPM({ name, displayName, version });
|
await createZPM({ name, displayName, version });
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@link-stack/logger": "*",
|
||||||
"glob": "^11.0.1"
|
"glob": "^11.0.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue