tambahannya
This commit is contained in:
@@ -11,6 +11,7 @@ import WebhookRoute from "./server/routes/webhook_route";
|
||||
import cors from "@elysiajs/cors";
|
||||
import WaHookRoute from "./server/routes/wa_hook_route";
|
||||
import FlowRoute from "./server/routes/flow_route";
|
||||
import LogsRoute from "./server/routes/logs_route";
|
||||
|
||||
const Docs = new Elysia().use(
|
||||
Swagger({
|
||||
@@ -44,7 +45,8 @@ const app = new Elysia()
|
||||
.use(Docs)
|
||||
.use(Auth)
|
||||
.use(WaHookRoute)
|
||||
.get("*", html)
|
||||
.use(LogsRoute)
|
||||
.get("*", html)
|
||||
.listen(3000, () => {
|
||||
console.log("Server running at http://localhost:3000");
|
||||
});
|
||||
|
||||
43
src/server/lib/logger.ts
Normal file
43
src/server/lib/logger.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
// src/lib/logger.ts
|
||||
import pino from 'pino'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
// Pastikan folder logs ada
|
||||
const LOG_DIR = path.join(process.cwd(), process.env.APP_LOGS_PATH || './.logs')
|
||||
if (!fs.existsSync(LOG_DIR)) {
|
||||
fs.mkdirSync(LOG_DIR, { recursive: true })
|
||||
}
|
||||
|
||||
// Konfigurasi logger
|
||||
export const logger = pino({
|
||||
level: process.env.LOG_LEVEL ?? 'info',
|
||||
timestamp: pino.stdTimeFunctions.isoTime, // ISO Format (2025-10-22T11:00:00.000Z)
|
||||
transport: process.env.NODE_ENV === 'production'
|
||||
? {
|
||||
targets: [
|
||||
{
|
||||
target: 'pino/file',
|
||||
options: { destination: `${LOG_DIR}/app.log`, mkdir: true },
|
||||
level: 'info',
|
||||
}
|
||||
]
|
||||
}
|
||||
: {
|
||||
targets: [
|
||||
{
|
||||
target: 'pino-pretty',
|
||||
options: {
|
||||
colorize: true,
|
||||
translateTime: 'SYS:standard', // tampil lebih human-readable
|
||||
},
|
||||
level: 'debug',
|
||||
},
|
||||
{
|
||||
target: 'pino/file',
|
||||
options: { destination: `${LOG_DIR}/app.log`, mkdir: true },
|
||||
level: 'info',
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
93
src/server/lib/wa-api/wa-api.ts
Normal file
93
src/server/lib/wa-api/wa-api.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import { WhatsAppAPI } from "whatsapp-api-js";
|
||||
import { Document, Image, Text } from "whatsapp-api-js/messages";
|
||||
import type { IncomingHttpHeaders } from "http";
|
||||
import { logger } from "../logger";
|
||||
|
||||
|
||||
// Jangan hardcode — ini hanya contoh
|
||||
const TOKEN: string = process.env.WA_TOKEN!;
|
||||
const APP_SECRET: string = process.env.WA_APP_SECRET!;
|
||||
|
||||
logger.info("WA API started");
|
||||
|
||||
// Inisialisasi WhatsApp API dengan typing generik jika diperlukan (contoh: number sebagai tipe session)
|
||||
const Whatsapp = new WhatsAppAPI<number>({
|
||||
token: TOKEN,
|
||||
appSecret: APP_SECRET
|
||||
});
|
||||
|
||||
// Tipe untuk request body dari server (bisa disesuaikan dengan framework seperti Express, Elysia, Hono, dll)
|
||||
interface PostRequest {
|
||||
data: string | Buffer;
|
||||
headers: IncomingHttpHeaders & {
|
||||
"x-hub-signature-256"?: string;
|
||||
};
|
||||
}
|
||||
|
||||
// Fungsi handler webhook POST
|
||||
export async function post(req: PostRequest) {
|
||||
logger.info("WA API received");
|
||||
const signature = req.headers["x-hub-signature-256"] ?? "";
|
||||
return await Whatsapp.post(
|
||||
JSON.parse(req.data.toString()),
|
||||
req.data.toString(),
|
||||
signature,
|
||||
);
|
||||
}
|
||||
|
||||
export function whatsappApiInit() {
|
||||
// Handler jika ada pesan masuk dari user
|
||||
Whatsapp.on.message = async ({ phoneID, from, message, name, Whatsapp, reply }) => {
|
||||
logger.info(
|
||||
`User ${name} (${from}) sent to bot ${phoneID} ${JSON.stringify(message)}`
|
||||
);
|
||||
|
||||
let response;
|
||||
|
||||
switch (message.type) {
|
||||
case "text":
|
||||
logger.info("Text received");
|
||||
response = await reply(
|
||||
new Text(`*${name}* said:\n\n${message.text.body}`),
|
||||
true
|
||||
);
|
||||
logger.info("Text sent");
|
||||
break;
|
||||
|
||||
case "image":
|
||||
logger.info("Image received");
|
||||
response = await reply(
|
||||
new Image(message.image.id, true, `Nice photo, ${name}`)
|
||||
);
|
||||
logger.info("Image sent");
|
||||
break;
|
||||
|
||||
case "document":
|
||||
logger.info("Document received");
|
||||
response = await reply(
|
||||
new Document(message.document.id, true, undefined, "Our document")
|
||||
);
|
||||
logger.info("Document sent");
|
||||
break;
|
||||
|
||||
default:
|
||||
logger.info(
|
||||
"Unhandled message type. More types available: contacts, locations, templates, interactive, reactions, audio, video, etc."
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
logger.info("Response sent");
|
||||
|
||||
// Tandai pesan sudah dibaca
|
||||
Whatsapp.markAsRead(phoneID, message.id);
|
||||
|
||||
return 200;
|
||||
};
|
||||
|
||||
// Handler saat pesan berhasil terkirim
|
||||
Whatsapp.on.sent = ({ phoneID, to, message }) => {
|
||||
logger.info(`Bot ${phoneID} sent to user ${to} ${message}`);
|
||||
};
|
||||
|
||||
}
|
||||
26
src/server/routes/logs_route.ts
Normal file
26
src/server/routes/logs_route.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import Elysia from "elysia";
|
||||
import fs from "fs/promises";
|
||||
import path from "path";
|
||||
|
||||
const LOGS_PATH = process.env.APP_LOGS_PATH || "./.logs";
|
||||
|
||||
const LogsRoute = new Elysia({
|
||||
prefix: "/logs", // lebih aman pakai "/" di depan
|
||||
tags: ["logs"]
|
||||
})
|
||||
.get("/app", async () => {
|
||||
const filePath = path.join(LOGS_PATH, "app.log");
|
||||
|
||||
try {
|
||||
const logs = await fs.readFile(filePath, "utf-8");
|
||||
// Format: array line-by-line (optional)
|
||||
return logs.split("\n").filter(Boolean);
|
||||
} catch (err) {
|
||||
return {
|
||||
error: "Log file not found or cannot be read",
|
||||
details: (err as Error).message,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
export default LogsRoute;
|
||||
@@ -2,6 +2,9 @@ import Elysia, { t } from "elysia";
|
||||
import { prisma } from "../lib/prisma";
|
||||
import type { WAHookMessage } from "types/wa_messages";
|
||||
import _ from "lodash";
|
||||
import { whatsappApiInit } from "../lib/wa-api/wa-api";
|
||||
|
||||
whatsappApiInit()
|
||||
|
||||
async function fetchWithTimeout(input: RequestInfo, init: RequestInit, timeoutMs = 120_000) {
|
||||
const controller = new AbortController()
|
||||
|
||||
Reference in New Issue
Block a user