samarthya-bot 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +92 -0
- package/backend/.env.example +23 -0
- package/backend/bin/samarthya.js +384 -0
- package/backend/config/constants.js +71 -0
- package/backend/config/db.js +13 -0
- package/backend/controllers/auditController.js +86 -0
- package/backend/controllers/authController.js +154 -0
- package/backend/controllers/chatController.js +158 -0
- package/backend/controllers/fileController.js +268 -0
- package/backend/controllers/platformController.js +54 -0
- package/backend/controllers/screenController.js +91 -0
- package/backend/controllers/telegramController.js +120 -0
- package/backend/controllers/toolsController.js +56 -0
- package/backend/controllers/whatsappController.js +214 -0
- package/backend/fix_toolRegistry.js +25 -0
- package/backend/middleware/auth.js +28 -0
- package/backend/models/AuditLog.js +28 -0
- package/backend/models/BackgroundJob.js +13 -0
- package/backend/models/Conversation.js +40 -0
- package/backend/models/Memory.js +17 -0
- package/backend/models/User.js +24 -0
- package/backend/package-lock.json +3766 -0
- package/backend/package.json +41 -0
- package/backend/public/assets/index-Ckf0GO1B.css +1 -0
- package/backend/public/assets/index-Do4jNsZS.js +19 -0
- package/backend/public/assets/index-Ui-pyZvK.js +25 -0
- package/backend/public/favicon.svg +17 -0
- package/backend/public/index.html +18 -0
- package/backend/public/manifest.json +16 -0
- package/backend/routes/audit.js +9 -0
- package/backend/routes/auth.js +11 -0
- package/backend/routes/chat.js +11 -0
- package/backend/routes/files.js +14 -0
- package/backend/routes/platform.js +18 -0
- package/backend/routes/screen.js +10 -0
- package/backend/routes/telegram.js +8 -0
- package/backend/routes/tools.js +9 -0
- package/backend/routes/whatsapp.js +11 -0
- package/backend/server.js +134 -0
- package/backend/services/background/backgroundService.js +81 -0
- package/backend/services/llm/llmService.js +444 -0
- package/backend/services/memory/memoryService.js +159 -0
- package/backend/services/planner/plannerService.js +182 -0
- package/backend/services/security/securityService.js +166 -0
- package/backend/services/telegram/telegramService.js +49 -0
- package/backend/services/tools/toolRegistry.js +879 -0
- package/backend/services/whatsapp/whatsappService.js +254 -0
- package/backend/test_email.js +29 -0
- package/backend/test_parser.js +10 -0
- package/package.json +49 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
const User = require('../models/User');
|
|
2
|
+
const bcrypt = require('bcryptjs');
|
|
3
|
+
const jwt = require('jsonwebtoken');
|
|
4
|
+
|
|
5
|
+
// Register
|
|
6
|
+
exports.register = async (req, res) => {
|
|
7
|
+
try {
|
|
8
|
+
const { name, email, password, language, city, workType } = req.body;
|
|
9
|
+
|
|
10
|
+
const exists = await User.findOne({ email });
|
|
11
|
+
if (exists) {
|
|
12
|
+
return res.status(400).json({ success: false, message: 'Email already registered' });
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const salt = await bcrypt.genSalt(10);
|
|
16
|
+
const hashed = await bcrypt.hash(password, salt);
|
|
17
|
+
|
|
18
|
+
const user = await User.create({
|
|
19
|
+
name,
|
|
20
|
+
email,
|
|
21
|
+
password: hashed,
|
|
22
|
+
language: language || 'hinglish',
|
|
23
|
+
city: city || '',
|
|
24
|
+
workType: workType || 'personal',
|
|
25
|
+
activePack: workType || 'personal'
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET, { expiresIn: '30d' });
|
|
29
|
+
|
|
30
|
+
res.status(201).json({
|
|
31
|
+
success: true,
|
|
32
|
+
token,
|
|
33
|
+
user: {
|
|
34
|
+
id: user._id,
|
|
35
|
+
name: user.name,
|
|
36
|
+
email: user.email,
|
|
37
|
+
language: user.language,
|
|
38
|
+
city: user.city,
|
|
39
|
+
workType: user.workType,
|
|
40
|
+
activePack: user.activePack,
|
|
41
|
+
permissions: user.permissions
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
} catch (error) {
|
|
45
|
+
res.status(500).json({ success: false, message: error.message });
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// Login
|
|
50
|
+
exports.login = async (req, res) => {
|
|
51
|
+
try {
|
|
52
|
+
const { email, password } = req.body;
|
|
53
|
+
|
|
54
|
+
const user = await User.findOne({ email });
|
|
55
|
+
if (!user) {
|
|
56
|
+
return res.status(401).json({ success: false, message: 'Invalid credentials' });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const isMatch = await bcrypt.compare(password, user.password);
|
|
60
|
+
if (!isMatch) {
|
|
61
|
+
return res.status(401).json({ success: false, message: 'Invalid credentials' });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET, { expiresIn: '30d' });
|
|
65
|
+
|
|
66
|
+
res.json({
|
|
67
|
+
success: true,
|
|
68
|
+
token,
|
|
69
|
+
user: {
|
|
70
|
+
id: user._id,
|
|
71
|
+
name: user.name,
|
|
72
|
+
email: user.email,
|
|
73
|
+
language: user.language,
|
|
74
|
+
city: user.city,
|
|
75
|
+
workType: user.workType,
|
|
76
|
+
activePack: user.activePack,
|
|
77
|
+
permissions: user.permissions
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
} catch (error) {
|
|
81
|
+
res.status(500).json({ success: false, message: error.message });
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// Get Profile
|
|
86
|
+
exports.getProfile = async (req, res) => {
|
|
87
|
+
try {
|
|
88
|
+
const user = await User.findById(req.user.id).select('-password');
|
|
89
|
+
if (!user) return res.status(404).json({ success: false, message: 'User not found' });
|
|
90
|
+
res.json({ success: true, user });
|
|
91
|
+
} catch (error) {
|
|
92
|
+
res.status(500).json({ success: false, message: error.message });
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// Update Profile
|
|
97
|
+
exports.updateProfile = async (req, res) => {
|
|
98
|
+
try {
|
|
99
|
+
const { name, language, city, workType, activePack, permissions } = req.body;
|
|
100
|
+
|
|
101
|
+
const updateData = {};
|
|
102
|
+
if (name) updateData.name = name;
|
|
103
|
+
if (language) updateData.language = language;
|
|
104
|
+
if (city) updateData.city = city;
|
|
105
|
+
if (workType) updateData.workType = workType;
|
|
106
|
+
if (activePack) updateData.activePack = activePack;
|
|
107
|
+
if (permissions) updateData.permissions = permissions;
|
|
108
|
+
|
|
109
|
+
const user = await User.findByIdAndUpdate(req.user.id, updateData, { new: true }).select('-password');
|
|
110
|
+
res.json({ success: true, user });
|
|
111
|
+
} catch (error) {
|
|
112
|
+
res.status(500).json({ success: false, message: error.message });
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
// Quick Demo Login (No password needed - for development)
|
|
117
|
+
exports.demoLogin = async (req, res) => {
|
|
118
|
+
try {
|
|
119
|
+
let user = await User.findOne({ email: 'demo@samarthya.com' });
|
|
120
|
+
|
|
121
|
+
if (!user) {
|
|
122
|
+
const salt = await bcrypt.genSalt(10);
|
|
123
|
+
const hashed = await bcrypt.hash('demo123', salt);
|
|
124
|
+
user = await User.create({
|
|
125
|
+
name: 'Demo User',
|
|
126
|
+
email: 'demo@samarthya.com',
|
|
127
|
+
password: hashed,
|
|
128
|
+
language: 'hinglish',
|
|
129
|
+
city: 'Mumbai',
|
|
130
|
+
workType: 'personal',
|
|
131
|
+
activePack: 'personal'
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET, { expiresIn: '30d' });
|
|
136
|
+
|
|
137
|
+
res.json({
|
|
138
|
+
success: true,
|
|
139
|
+
token,
|
|
140
|
+
user: {
|
|
141
|
+
id: user._id,
|
|
142
|
+
name: user.name,
|
|
143
|
+
email: user.email,
|
|
144
|
+
language: user.language,
|
|
145
|
+
city: user.city,
|
|
146
|
+
workType: user.workType,
|
|
147
|
+
activePack: user.activePack,
|
|
148
|
+
permissions: user.permissions
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
} catch (error) {
|
|
152
|
+
res.status(500).json({ success: false, message: error.message });
|
|
153
|
+
}
|
|
154
|
+
};
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
const Conversation = require('../models/Conversation');
|
|
2
|
+
const User = require('../models/User');
|
|
3
|
+
const plannerService = require('../services/planner/plannerService');
|
|
4
|
+
const securityService = require('../services/security/securityService');
|
|
5
|
+
|
|
6
|
+
// Send message and get AI response
|
|
7
|
+
exports.sendMessage = async (req, res) => {
|
|
8
|
+
try {
|
|
9
|
+
const { message, conversationId } = req.body;
|
|
10
|
+
const userId = req.user.id;
|
|
11
|
+
|
|
12
|
+
if (!message || !message.trim()) {
|
|
13
|
+
return res.status(400).json({ success: false, message: 'Message is required' });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const user = await User.findById(userId);
|
|
17
|
+
if (!user) return res.status(404).json({ success: false, message: 'User not found' });
|
|
18
|
+
|
|
19
|
+
// Get or create conversation
|
|
20
|
+
let conversation;
|
|
21
|
+
if (conversationId) {
|
|
22
|
+
conversation = await Conversation.findOne({ _id: conversationId, userId });
|
|
23
|
+
if (!conversation) {
|
|
24
|
+
return res.status(404).json({ success: false, message: 'Conversation not found' });
|
|
25
|
+
}
|
|
26
|
+
} else {
|
|
27
|
+
conversation = await Conversation.create({
|
|
28
|
+
userId,
|
|
29
|
+
title: message.substring(0, 60) + (message.length > 60 ? '...' : ''),
|
|
30
|
+
messages: [],
|
|
31
|
+
context: {
|
|
32
|
+
activePack: user.activePack,
|
|
33
|
+
language: user.language
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Add user message
|
|
39
|
+
conversation.messages.push({
|
|
40
|
+
role: 'user',
|
|
41
|
+
content: message,
|
|
42
|
+
language: user.language
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Process through planner
|
|
46
|
+
const conversationHistory = conversation.messages.slice(-20).map(m => ({
|
|
47
|
+
role: m.role,
|
|
48
|
+
content: m.content
|
|
49
|
+
}));
|
|
50
|
+
|
|
51
|
+
const result = await plannerService.processMessage(user, conversationHistory, message);
|
|
52
|
+
|
|
53
|
+
// Add assistant response
|
|
54
|
+
conversation.messages.push({
|
|
55
|
+
role: 'assistant',
|
|
56
|
+
content: result.response,
|
|
57
|
+
language: result.language,
|
|
58
|
+
toolCalls: result.toolCalls,
|
|
59
|
+
metadata: {
|
|
60
|
+
tokensUsed: result.tokensUsed,
|
|
61
|
+
model: result.model,
|
|
62
|
+
sensitiveDataDetected: result.sensitiveDataWarnings.map(w => w.type)
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
await conversation.save();
|
|
67
|
+
|
|
68
|
+
// Emit via socket if available
|
|
69
|
+
if (req.io) {
|
|
70
|
+
req.io.to(userId).emit('message', {
|
|
71
|
+
conversationId: conversation._id,
|
|
72
|
+
message: {
|
|
73
|
+
role: 'assistant',
|
|
74
|
+
content: result.response,
|
|
75
|
+
toolCalls: result.toolCalls,
|
|
76
|
+
sensitiveDataWarnings: result.sensitiveDataWarnings,
|
|
77
|
+
timestamp: new Date()
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
res.json({
|
|
83
|
+
success: true,
|
|
84
|
+
conversationId: conversation._id,
|
|
85
|
+
message: {
|
|
86
|
+
role: 'assistant',
|
|
87
|
+
content: result.response,
|
|
88
|
+
toolCalls: result.toolCalls,
|
|
89
|
+
sensitiveDataWarnings: result.sensitiveDataWarnings,
|
|
90
|
+
tokensUsed: result.tokensUsed,
|
|
91
|
+
model: result.model,
|
|
92
|
+
language: result.language
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
} catch (error) {
|
|
96
|
+
console.error('Chat error:', error);
|
|
97
|
+
res.status(500).json({ success: false, message: error.message });
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
// Get all conversations
|
|
102
|
+
exports.getConversations = async (req, res) => {
|
|
103
|
+
try {
|
|
104
|
+
const conversations = await Conversation.find({ isActive: true })
|
|
105
|
+
.select('title context isPinned createdAt updatedAt')
|
|
106
|
+
.sort({ isPinned: -1, updatedAt: -1 })
|
|
107
|
+
.limit(50);
|
|
108
|
+
|
|
109
|
+
res.json({ success: true, conversations });
|
|
110
|
+
} catch (error) {
|
|
111
|
+
res.status(500).json({ success: false, message: error.message });
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
// Get single conversation with messages
|
|
116
|
+
exports.getConversation = async (req, res) => {
|
|
117
|
+
try {
|
|
118
|
+
const conversation = await Conversation.findOne({
|
|
119
|
+
_id: req.params.id
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
if (!conversation) {
|
|
123
|
+
return res.status(404).json({ success: false, message: 'Conversation not found' });
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
res.json({ success: true, conversation });
|
|
127
|
+
} catch (error) {
|
|
128
|
+
res.status(500).json({ success: false, message: error.message });
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
// Delete conversation
|
|
133
|
+
exports.deleteConversation = async (req, res) => {
|
|
134
|
+
try {
|
|
135
|
+
await Conversation.findOneAndUpdate(
|
|
136
|
+
{ _id: req.params.id },
|
|
137
|
+
{ isActive: false }
|
|
138
|
+
);
|
|
139
|
+
res.json({ success: true, message: 'Conversation deleted' });
|
|
140
|
+
} catch (error) {
|
|
141
|
+
res.status(500).json({ success: false, message: error.message });
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
// Pin/Unpin conversation
|
|
146
|
+
exports.togglePin = async (req, res) => {
|
|
147
|
+
try {
|
|
148
|
+
const conv = await Conversation.findOne({ _id: req.params.id });
|
|
149
|
+
if (!conv) return res.status(404).json({ success: false, message: 'Not found' });
|
|
150
|
+
|
|
151
|
+
conv.isPinned = !conv.isPinned;
|
|
152
|
+
await conv.save();
|
|
153
|
+
|
|
154
|
+
res.json({ success: true, isPinned: conv.isPinned });
|
|
155
|
+
} catch (error) {
|
|
156
|
+
res.status(500).json({ success: false, message: error.message });
|
|
157
|
+
}
|
|
158
|
+
};
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
const fs = require('fs').promises;
|
|
2
|
+
const fsSync = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
|
|
6
|
+
const BASE_DIR = path.join(os.homedir(), 'SamarthyaBot_Files');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Get user's sandboxed directory
|
|
10
|
+
*/
|
|
11
|
+
function getUserDir(userId) {
|
|
12
|
+
return path.join(BASE_DIR, String(userId));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Ensure user directory exists
|
|
17
|
+
*/
|
|
18
|
+
async function ensureUserDir(userId) {
|
|
19
|
+
const userDir = getUserDir(userId);
|
|
20
|
+
await fs.mkdir(userDir, { recursive: true });
|
|
21
|
+
// Create default subdirectories
|
|
22
|
+
await fs.mkdir(path.join(userDir, 'notes'), { recursive: true });
|
|
23
|
+
await fs.mkdir(path.join(userDir, 'reminders'), { recursive: true });
|
|
24
|
+
await fs.mkdir(path.join(userDir, 'calendar'), { recursive: true });
|
|
25
|
+
await fs.mkdir(path.join(userDir, 'downloads'), { recursive: true });
|
|
26
|
+
return userDir;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Validate path is within user's sandbox
|
|
31
|
+
*/
|
|
32
|
+
function validatePath(userDir, requestedPath) {
|
|
33
|
+
const resolved = path.resolve(userDir, requestedPath || '');
|
|
34
|
+
if (!resolved.startsWith(userDir)) {
|
|
35
|
+
throw new Error('Access denied: Path outside sandbox');
|
|
36
|
+
}
|
|
37
|
+
return resolved;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* GET /api/files/list — List files and folders
|
|
42
|
+
*/
|
|
43
|
+
exports.listFiles = async (req, res) => {
|
|
44
|
+
try {
|
|
45
|
+
const userId = req.user._id || req.user.id;
|
|
46
|
+
const userDir = await ensureUserDir(userId);
|
|
47
|
+
const subPath = req.query.path || '';
|
|
48
|
+
const fullPath = validatePath(userDir, subPath);
|
|
49
|
+
|
|
50
|
+
const entries = await fs.readdir(fullPath, { withFileTypes: true });
|
|
51
|
+
const items = [];
|
|
52
|
+
|
|
53
|
+
for (const entry of entries) {
|
|
54
|
+
const entryPath = path.join(fullPath, entry.name);
|
|
55
|
+
try {
|
|
56
|
+
const stat = await fs.stat(entryPath);
|
|
57
|
+
items.push({
|
|
58
|
+
name: entry.name,
|
|
59
|
+
type: entry.isDirectory() ? 'folder' : 'file',
|
|
60
|
+
size: entry.isDirectory() ? null : stat.size,
|
|
61
|
+
modified: stat.mtime,
|
|
62
|
+
extension: entry.isDirectory() ? null : path.extname(entry.name).slice(1).toLowerCase(),
|
|
63
|
+
path: subPath ? `${subPath}/${entry.name}` : entry.name
|
|
64
|
+
});
|
|
65
|
+
} catch (e) {
|
|
66
|
+
// Skip inaccessible
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Sort: folders first, then alphabetical
|
|
71
|
+
items.sort((a, b) => {
|
|
72
|
+
if (a.type !== b.type) return a.type === 'folder' ? -1 : 1;
|
|
73
|
+
return a.name.localeCompare(b.name);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
res.json({
|
|
77
|
+
success: true,
|
|
78
|
+
currentPath: subPath || '/',
|
|
79
|
+
items,
|
|
80
|
+
totalItems: items.length
|
|
81
|
+
});
|
|
82
|
+
} catch (error) {
|
|
83
|
+
res.status(400).json({ success: false, error: error.message });
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* GET /api/files/read — Read file contents (text only)
|
|
89
|
+
*/
|
|
90
|
+
exports.readFile = async (req, res) => {
|
|
91
|
+
try {
|
|
92
|
+
const userId = req.user._id || req.user.id;
|
|
93
|
+
const userDir = await ensureUserDir(userId);
|
|
94
|
+
const filePath = validatePath(userDir, req.query.path);
|
|
95
|
+
|
|
96
|
+
const stat = await fs.stat(filePath);
|
|
97
|
+
if (stat.isDirectory()) {
|
|
98
|
+
return res.status(400).json({ success: false, error: 'Cannot read a directory' });
|
|
99
|
+
}
|
|
100
|
+
if (stat.size > 2 * 1024 * 1024) {
|
|
101
|
+
return res.status(400).json({ success: false, error: 'File too large (max 2MB for preview)' });
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
105
|
+
const ext = path.extname(filePath).slice(1).toLowerCase();
|
|
106
|
+
|
|
107
|
+
res.json({
|
|
108
|
+
success: true,
|
|
109
|
+
name: path.basename(filePath),
|
|
110
|
+
content,
|
|
111
|
+
size: stat.size,
|
|
112
|
+
extension: ext,
|
|
113
|
+
modified: stat.mtime
|
|
114
|
+
});
|
|
115
|
+
} catch (error) {
|
|
116
|
+
res.status(400).json({ success: false, error: error.message });
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* GET /api/files/download — Download a file
|
|
122
|
+
*/
|
|
123
|
+
exports.downloadFile = async (req, res) => {
|
|
124
|
+
try {
|
|
125
|
+
const userId = req.user._id || req.user.id;
|
|
126
|
+
const userDir = await ensureUserDir(userId);
|
|
127
|
+
const filePath = validatePath(userDir, req.query.path);
|
|
128
|
+
|
|
129
|
+
const stat = await fs.stat(filePath);
|
|
130
|
+
if (stat.isDirectory()) {
|
|
131
|
+
return res.status(400).json({ success: false, error: 'Cannot download a directory' });
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
res.download(filePath, path.basename(filePath));
|
|
135
|
+
} catch (error) {
|
|
136
|
+
res.status(400).json({ success: false, error: error.message });
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* POST /api/files/mkdir — Create a folder
|
|
142
|
+
*/
|
|
143
|
+
exports.createFolder = async (req, res) => {
|
|
144
|
+
try {
|
|
145
|
+
const userId = req.user._id || req.user.id;
|
|
146
|
+
const userDir = await ensureUserDir(userId);
|
|
147
|
+
const { name, parentPath } = req.body;
|
|
148
|
+
|
|
149
|
+
if (!name || /[/\\<>:|?*"]/.test(name)) {
|
|
150
|
+
return res.status(400).json({ success: false, error: 'Invalid folder name' });
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const folderPath = validatePath(userDir, parentPath ? `${parentPath}/${name}` : name);
|
|
154
|
+
await fs.mkdir(folderPath, { recursive: true });
|
|
155
|
+
|
|
156
|
+
res.json({ success: true, message: `Folder "${name}" created`, path: parentPath ? `${parentPath}/${name}` : name });
|
|
157
|
+
} catch (error) {
|
|
158
|
+
res.status(400).json({ success: false, error: error.message });
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* DELETE /api/files/delete — Delete a file or empty folder
|
|
164
|
+
*/
|
|
165
|
+
exports.deleteFile = async (req, res) => {
|
|
166
|
+
try {
|
|
167
|
+
const userId = req.user._id || req.user.id;
|
|
168
|
+
const userDir = await ensureUserDir(userId);
|
|
169
|
+
const filePath = validatePath(userDir, req.query.path);
|
|
170
|
+
|
|
171
|
+
// Prevent deleting root
|
|
172
|
+
if (filePath === userDir) {
|
|
173
|
+
return res.status(400).json({ success: false, error: 'Cannot delete root directory' });
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const stat = await fs.stat(filePath);
|
|
177
|
+
if (stat.isDirectory()) {
|
|
178
|
+
await fs.rm(filePath, { recursive: true });
|
|
179
|
+
} else {
|
|
180
|
+
await fs.unlink(filePath);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
res.json({ success: true, message: 'Deleted successfully' });
|
|
184
|
+
} catch (error) {
|
|
185
|
+
res.status(400).json({ success: false, error: error.message });
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* POST /api/files/upload — Upload a file
|
|
191
|
+
*/
|
|
192
|
+
exports.uploadFile = async (req, res) => {
|
|
193
|
+
try {
|
|
194
|
+
const userId = req.user._id || req.user.id;
|
|
195
|
+
const userDir = await ensureUserDir(userId);
|
|
196
|
+
const { filename, content, parentPath } = req.body;
|
|
197
|
+
|
|
198
|
+
if (!filename || !content) {
|
|
199
|
+
return res.status(400).json({ success: false, error: 'Filename and content required' });
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const safeName = filename.replace(/[/\\<>:|?*"]/g, '_');
|
|
203
|
+
const filePath = validatePath(userDir, parentPath ? `${parentPath}/${safeName}` : safeName);
|
|
204
|
+
|
|
205
|
+
// Handle base64 content (for binary files)
|
|
206
|
+
if (content.startsWith('data:')) {
|
|
207
|
+
const base64Data = content.split(',')[1];
|
|
208
|
+
await fs.writeFile(filePath, Buffer.from(base64Data, 'base64'));
|
|
209
|
+
} else {
|
|
210
|
+
await fs.writeFile(filePath, content, 'utf-8');
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const stat = await fs.stat(filePath);
|
|
214
|
+
res.json({
|
|
215
|
+
success: true,
|
|
216
|
+
message: `File "${safeName}" uploaded`,
|
|
217
|
+
size: stat.size,
|
|
218
|
+
path: parentPath ? `${parentPath}/${safeName}` : safeName
|
|
219
|
+
});
|
|
220
|
+
} catch (error) {
|
|
221
|
+
res.status(400).json({ success: false, error: error.message });
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* GET /api/files/stats — Get storage stats
|
|
227
|
+
*/
|
|
228
|
+
exports.getStats = async (req, res) => {
|
|
229
|
+
try {
|
|
230
|
+
const userId = req.user._id || req.user.id;
|
|
231
|
+
const userDir = await ensureUserDir(userId);
|
|
232
|
+
|
|
233
|
+
let totalSize = 0;
|
|
234
|
+
let fileCount = 0;
|
|
235
|
+
let folderCount = 0;
|
|
236
|
+
|
|
237
|
+
async function walk(dir) {
|
|
238
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
239
|
+
for (const entry of entries) {
|
|
240
|
+
const fullPath = path.join(dir, entry.name);
|
|
241
|
+
if (entry.isDirectory()) {
|
|
242
|
+
folderCount++;
|
|
243
|
+
await walk(fullPath);
|
|
244
|
+
} else {
|
|
245
|
+
fileCount++;
|
|
246
|
+
const stat = await fs.stat(fullPath);
|
|
247
|
+
totalSize += stat.size;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
await walk(userDir);
|
|
253
|
+
|
|
254
|
+
res.json({
|
|
255
|
+
success: true,
|
|
256
|
+
stats: {
|
|
257
|
+
totalSize,
|
|
258
|
+
totalSizeFormatted: totalSize < 1024 ? `${totalSize} B` :
|
|
259
|
+
totalSize < 1048576 ? `${(totalSize / 1024).toFixed(1)} KB` :
|
|
260
|
+
`${(totalSize / 1048576).toFixed(1)} MB`,
|
|
261
|
+
fileCount,
|
|
262
|
+
folderCount
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
} catch (error) {
|
|
266
|
+
res.status(400).json({ success: false, error: error.message });
|
|
267
|
+
}
|
|
268
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
const BackgroundJob = require('../models/BackgroundJob');
|
|
2
|
+
const fs = require('fs').promises;
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
const toolRegistry = require('../services/tools/toolRegistry');
|
|
6
|
+
|
|
7
|
+
exports.getPlatformStatus = async (req, res) => {
|
|
8
|
+
try {
|
|
9
|
+
const activeJobs = await BackgroundJob.countDocuments({ isActive: true });
|
|
10
|
+
|
|
11
|
+
let pluginsCount = 0;
|
|
12
|
+
try {
|
|
13
|
+
const pluginDir = path.join(os.homedir(), 'SamarthyaBot_Files', 'plugins');
|
|
14
|
+
const files = await fs.readdir(pluginDir);
|
|
15
|
+
pluginsCount = files.filter(f => f.endsWith('.js')).length;
|
|
16
|
+
} catch (e) { }
|
|
17
|
+
|
|
18
|
+
res.json({
|
|
19
|
+
success: true,
|
|
20
|
+
status: {
|
|
21
|
+
uptime: process.uptime(),
|
|
22
|
+
totalTools: toolRegistry.getAllTools().length,
|
|
23
|
+
activeBackgroundJobs: activeJobs,
|
|
24
|
+
loadedPlugins: pluginsCount,
|
|
25
|
+
osName: os.type() + ' ' + os.release(),
|
|
26
|
+
ramUsage: Math.round((os.totalmem() - os.freemem()) / os.totalmem() * 100)
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
} catch (error) {
|
|
30
|
+
res.status(500).json({ success: false, message: error.message });
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
exports.getBackgroundJobs = async (req, res) => {
|
|
35
|
+
try {
|
|
36
|
+
const jobs = await BackgroundJob.find().sort({ nextRunAt: 1 }).limit(20);
|
|
37
|
+
res.json({ success: true, jobs });
|
|
38
|
+
} catch (error) {
|
|
39
|
+
res.status(500).json({ success: false, message: error.message });
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
exports.emergencyStop = async (req, res) => {
|
|
44
|
+
try {
|
|
45
|
+
// Stop all active background jobs
|
|
46
|
+
await BackgroundJob.updateMany({ isActive: true }, { $set: { isActive: false } });
|
|
47
|
+
|
|
48
|
+
// In a real system you might also want to set a global flag in plannerService to reject new processing
|
|
49
|
+
res.json({ success: true, message: "🚨 EMERGENCY STOP ENGAGED. All background jobs halted." });
|
|
50
|
+
console.warn("🛑 EMERGENCY KILL SWITCH ACTIVATED BY USER 🛑");
|
|
51
|
+
} catch (error) {
|
|
52
|
+
res.status(500).json({ success: false, message: error.message });
|
|
53
|
+
}
|
|
54
|
+
};
|