Skip to content
This repository was archived by the owner on Mar 29, 2026. It is now read-only.

Commit 853fc3d

Browse files
committed
feat: enhance media upload hook and API for conversation handling
1 parent d344cea commit 853fc3d

31 files changed

Lines changed: 2108 additions & 521 deletions

File tree

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@
55
"name": "project-root"
66
},
77
{
8-
"path": "./server",
8+
"path": "./apps/server",
99
"name": "server"
1010
},
1111
{
12-
"path": "./client",
13-
"name": "client"
12+
"path": "./apps/web",
13+
"name": "web"
1414
}
1515
]
1616
}

apps/server/package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,17 +33,21 @@
3333
"langchain": "^0.3.18",
3434
"pdf-parse": "^1.1.1",
3535
"playwright": "^1.51.0",
36+
"qrcode": "^1.5.4",
37+
"socket.io": "^4.8.1",
3638
"telegraf": "^4.16.3",
3739
"uploadthing": "^7.5.2",
3840
"uuid": "^11.0.5",
41+
"whatsapp-web.js": "^1.26.0",
3942
"zod": "^3.24.2"
4043
},
4144
"devDependencies": {
4245
"@neural-lift/eslint-config": "workspace:*",
4346
"@types/body-parser": "^1.19.5",
4447
"@types/cors": "^2.8.17",
45-
"@types/express": "^5.0.0",
48+
"@types/express": "4.17.21",
4649
"@types/node": "^22.13.4",
50+
"@types/qrcode": "^1.5.5",
4751
"prisma": "6.4.1",
4852
"ts-node": "^10.9.2",
4953
"tsx": "^4.19.2",

apps/server/src/configs/app.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ interface AppConfig {
55
PORT: string;
66
BASE_API_PATH: string;
77
SERVER_ORIGIN: string;
8+
CLIENT_ORIGIN: string;
89
RATE_TIME_LIMIT: string;
910
RATE_REQUEST_LIMIT: string;
1011
}
@@ -23,6 +24,7 @@ const config = (): AppConfig => ({
2324
'SERVER_ORIGIN',
2425
`http://localhost:${getEnv('PORT', '3000')}`
2526
),
27+
CLIENT_ORIGIN: getEnv('CLIENT_ORIGIN', 'http://localhost:5173'),
2628

2729
RATE_TIME_LIMIT: getEnv('RATE_TIME_LIMIT', '15'),
2830
RATE_REQUEST_LIMIT: getEnv('RATE_REQUEST_LIMIT', '1000'),

apps/server/src/configs/server.ts

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import http from 'http';
2+
import path from 'path';
3+
import cors from 'cors';
4+
import express, { Application } from 'express';
5+
import { Server as SocketServer } from 'socket.io';
6+
7+
import { errorHandler } from '../middleware/errors';
8+
import v1Router from '../routes';
9+
import { WhatsappBotInstance } from '../utils/integrations/whatsapp';
10+
import appConfig from './app';
11+
import { limiter } from './limiter';
12+
13+
// Map untuk menyimpan instance bot
14+
const botInstances: Map<string, WhatsappBotInstance> = new Map();
15+
const MAX_INSTANCES = 3; // Batas untuk 1GB RAM
16+
17+
export class Server {
18+
public app: Application;
19+
public port: number;
20+
private io: SocketServer;
21+
private server: http.Server;
22+
23+
constructor(port: number) {
24+
this.app = express();
25+
this.port = port;
26+
this.server = http.createServer(this.app);
27+
this.io = new SocketServer(this.server, {
28+
cors: {
29+
origin: appConfig.CLIENT_ORIGIN,
30+
},
31+
});
32+
this.initializeMiddlewares();
33+
this.initializeRoutes();
34+
this.initializeSocket();
35+
}
36+
37+
private initializeMiddlewares() {
38+
// Add your middlewares here
39+
this.app.use(express.json());
40+
this.app.use(cors());
41+
this.app.use(limiter);
42+
}
43+
44+
private initializeRoutes() {
45+
// Add your routes here
46+
47+
// Serve the static files from the React app
48+
this.app.get('/', (_req, res) => {
49+
res.sendFile(path.join(__dirname, '../../web/dist/index.html'));
50+
});
51+
this.app.use(
52+
express.static(path.join(__dirname, '../../web/dist'), {
53+
maxAge: '1d',
54+
})
55+
);
56+
57+
// Endpoint untuk memulai bot
58+
this.app.get('/api/start-bot', async (req, res) => {
59+
const { userId, agentId } = req.query;
60+
if (!userId || !agentId) {
61+
return res.status(400).json({ error: 'Missing userId or agentId' });
62+
}
63+
64+
if (botInstances.has(userId as string)) {
65+
return res.json({ message: `Bot for ${userId} already running` });
66+
}
67+
68+
if (botInstances.size >= MAX_INSTANCES) {
69+
const oldestUserId = botInstances.keys().next().value;
70+
const oldestBot = botInstances.get(oldestUserId!);
71+
await oldestBot?.disconnect();
72+
botInstances.delete(oldestUserId!); // Hapus dari Map setelah disconnect
73+
}
74+
75+
const bot = new WhatsappBotInstance(
76+
{ userId: userId as string, agentId: agentId as string },
77+
this.io
78+
);
79+
botInstances.set(userId as string, bot);
80+
res.json({ message: `Bot for ${userId} started. Please check QR code.` });
81+
});
82+
83+
// V1 Routes
84+
this.app.use('/api', v1Router);
85+
86+
// Error Handler
87+
this.app.use(errorHandler);
88+
89+
// Catch-all route to handle client-side routing
90+
this.app.get('*', (_req, res) => {
91+
res.sendFile(path.join(__dirname, '../../web/dist/index.html'));
92+
});
93+
}
94+
95+
private initializeSocket() {
96+
this.io.on('connection', (socket) => {
97+
console.log('Client connected: ', socket.id);
98+
99+
socket.on('join', (userId) => {
100+
socket.join(userId);
101+
console.log(`Client ${socket.id} joined room ${userId}`);
102+
});
103+
104+
socket.on('disconnect', () => {
105+
console.log('Client disconnected: ', socket.id);
106+
});
107+
});
108+
}
109+
110+
public listen() {
111+
this.server.listen(this.port, () => {
112+
console.log(`Server is running on ${this.getBaseUrl()}`);
113+
});
114+
}
115+
116+
private getBaseUrl() {
117+
return `http://localhost:${this.port}`;
118+
}
119+
120+
public getIo() {
121+
return this.io;
122+
}
123+
124+
public async shutdown() {
125+
// for (const )
126+
}
127+
}

apps/server/src/controllers/conversation/index.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,19 @@ const getConversationId = asyncHandler(async (req, res) => {
2323
});
2424
});
2525

26-
const conversationLists = asyncHandler(async (_req, res) => {
27-
const conversations = await ConversationService.getAllConversations();
26+
const conversationLists = asyncHandler(async (req, res) => {
27+
const query = req.query;
28+
const includeAllMessages = query.messages === 'all';
29+
const chatOnly = query.chatOnly === 'true';
30+
let conversations;
31+
32+
if (includeAllMessages) {
33+
conversations = chatOnly
34+
? await ConversationService.getAllConversationsChatOnly()
35+
: await ConversationService.getAllConversationsAndMessages();
36+
} else {
37+
conversations = await ConversationService.getAllConversations();
38+
}
2839

2940
new AppResponse({
3041
res,

apps/server/src/index.ts

Lines changed: 14 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,20 @@
1-
import path from 'path';
2-
import cors from 'cors';
3-
import express, { Application } from 'express';
1+
import { Server } from './configs/server';
42

53
import 'dotenv/config';
64

7-
import { limiter } from './configs/limiter';
8-
import { errorHandler } from './middleware/errors';
9-
import v1Router from './routes';
10-
11-
export class Server {
12-
public app: Application;
13-
public port: number;
14-
15-
constructor(port: number) {
16-
this.app = express();
17-
this.port = port;
18-
this.initializeMiddlewares();
19-
this.initializeRoutes();
20-
}
21-
22-
private initializeMiddlewares() {
23-
// Add your middlewares here
24-
this.app.use(express.json());
25-
this.app.use(cors());
26-
this.app.use(limiter);
27-
}
28-
29-
private initializeRoutes() {
30-
// Add your routes here
31-
32-
// Serve the static files from the React app
33-
this.app.get('/', (_req, res) => {
34-
res.sendFile(path.join(__dirname, '../../web/dist/index.html'));
35-
});
36-
this.app.use(
37-
express.static(path.join(__dirname, '../../web/dist'), {
38-
maxAge: '1d',
39-
})
40-
);
41-
42-
// V1 Routes
43-
this.app.use('/api', v1Router);
44-
45-
// Error Handler
46-
this.app.use(errorHandler);
47-
48-
// Catch-all route to handle client-side routing
49-
this.app.get('*', (_req, res) => {
50-
res.sendFile(path.join(__dirname, '../../web/dist/index.html'));
51-
});
52-
}
5+
const port = 3000;
6+
const appServer = new Server(port);
7+
appServer.listen();
538

54-
public listen() {
55-
this.app.listen(this.port, () => {
56-
console.log(`Server is running on ${this.getBaseUrl()}`);
57-
});
58-
}
9+
// Graceful shutdown untuk SIGINT dan SIGTERM
10+
process.on('SIGINT', async () => {
11+
await appServer.shutdown();
12+
process.exit(0);
13+
});
5914

60-
private getBaseUrl() {
61-
return `http://localhost:${this.port}`;
62-
}
63-
}
15+
process.on('SIGTERM', async () => {
16+
await appServer.shutdown();
17+
process.exit(0);
18+
});
6419

65-
const port = 3000;
66-
const server = new Server(port);
67-
server.listen();
20+
export default appServer;

apps/server/src/services/agent/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,11 @@ export class AgentService {
154154
try {
155155
const agents = await db.agent.findMany({
156156
include: {
157-
conversations: true,
157+
_count: {
158+
select: {
159+
conversations: true,
160+
},
161+
},
158162
datasources: {
159163
select: {
160164
datasource: true,

apps/server/src/services/conversation/index.ts

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,44 @@ export class ConversationService {
8484
try {
8585
const conversations = await db.conversation.findMany({
8686
include: {
87-
messages: {
88-
take: 1,
87+
messages: { take: 1 },
88+
user: true,
89+
},
90+
});
91+
92+
return conversations;
93+
} catch (error) {
94+
console.error(error);
95+
throw error;
96+
}
97+
}
98+
99+
public static async getAllConversationsAndMessages() {
100+
try {
101+
const conversations = await db.conversation.findMany({
102+
include: {
103+
messages: true,
104+
user: true,
105+
},
106+
});
107+
108+
return conversations;
109+
} catch (error) {
110+
console.error(error);
111+
throw error;
112+
}
113+
}
114+
115+
public static async getAllConversationsChatOnly() {
116+
try {
117+
const conversations = await db.conversation.findMany({
118+
where: {
119+
chatId: {
120+
not: null,
89121
},
122+
},
123+
include: {
124+
messages: true,
90125
user: true,
91126
},
92127
});

0 commit comments

Comments
 (0)