Scorpiotur commited on
Commit
b6965ce
·
verified ·
1 Parent(s): 6288f67

Create a complete Node.js/Express backend for Sentimint crypto trading bot SaaS. Requirements: 1. User authentication (JWT) 2. Stripe integration for payments ($99/$299/$999 tiers) 3. Trading bot integration with queue system 4. Real-time WebSocket for live trades 5. Complete API for frontend Must include: - Database models (User, Subscription, Trade) - API endpoints for auth, trading, dashboard, billing - Stripe webhook handling for subscriptions - WebSocket server for real-time updates - Security middleware - Ready to connect with existing React frontend Skip affiliate system for now - will add later. Make it production-ready with error handling. ({/* Affiliate Program - Coming Soon! */} <Badge>Launching July 2025</Badge>) - Follow Up Deployment

Browse files
Files changed (1) hide show
  1. index.html +1480 -439
index.html CHANGED
@@ -3,508 +3,1549 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Express Auth API Documentation</title>
7
  <script src="https://cdn.tailwindcss.com"></script>
8
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
  <style>
10
- .method-badge {
11
- @apply px-2 py-1 rounded text-xs font-bold;
12
- }
13
- .method-get { @apply bg-blue-100 text-blue-800; }
14
- .method-post { @apply bg-green-100 text-green-800; }
15
- .endpoint-card:hover {
16
- transform: translateY(-2px);
17
- box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
18
- }
19
- .tab-active {
20
- @apply border-b-2 border-blue-500 text-blue-600;
21
- }
22
  .code-block {
23
- font-family: 'Courier New', monospace;
24
- @apply bg-gray-800 text-gray-100 p-4 rounded overflow-x-auto;
 
 
 
 
 
 
 
25
  }
26
- .test-input {
27
- @apply w-full p-2 border rounded focus:ring-2 focus:ring-blue-500 focus:border-blue-500;
 
 
 
28
  }
 
 
 
 
29
  </style>
30
  </head>
31
- <body class="bg-gray-50">
32
  <div class="container mx-auto px-4 py-8">
33
  <header class="mb-12 text-center">
34
- <h1 class="text-4xl font-bold text-gray-800 mb-2">Express Auth API</h1>
35
- <p class="text-xl text-gray-600">Simple authentication system with JWT</p>
36
  <div class="mt-6 flex justify-center space-x-4">
37
- <span class="px-3 py-1 bg-blue-100 text-blue-800 rounded-full text-sm">Express.js</span>
38
- <span class="px-3 py-1 bg-green-100 text-green-800 rounded-full text-sm">MongoDB</span>
39
- <span class="px-3 py-1 bg-purple-100 text-purple-800 rounded-full text-sm">JWT</span>
40
- <span class="px-3 py-1 bg-yellow-100 text-yellow-800 rounded-full text-sm">bcrypt</span>
 
41
  </div>
42
  </header>
43
 
44
- <div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
45
- <!-- Documentation Section -->
46
- <div class="lg:col-span-2">
47
- <div class="bg-white rounded-lg shadow-md p-6 mb-8">
48
- <h2 class="text-2xl font-semibold text-gray-800 mb-4">Project Structure</h2>
49
- <div class="code-block mb-4">
50
- <pre>project-root/
51
- ├── server.js # Main server file
52
- ── routes/
53
- │ └── auth.js # Authentication routes
54
- ├── middleware/
55
- ── auth.js # JWT verification middleware
56
- ── models/
57
- ── User.js # Mongoose User model</pre>
58
- </div>
59
- </div>
60
-
61
- <div class="bg-white rounded-lg shadow-md p-6 mb-8">
62
- <h2 class="text-2xl font-semibold text-gray-800 mb-4">API Endpoints</h2>
63
-
64
- <!-- Register Endpoint -->
65
- <div class="endpoint-card bg-white border border-gray-200 rounded-lg p-4 mb-6 transition-all duration-200">
66
- <div class="flex items-center mb-3">
67
- <span class="method-badge method-post">POST</span>
68
- <span class="ml-2 font-mono text-gray-700">/api/auth/register</span>
69
- </div>
70
- <p class="text-gray-600 mb-3">Register a new user account.</p>
71
-
72
- <div class="mb-4">
73
- <h4 class="font-medium text-gray-700 mb-2">Request Body</h4>
74
- <div class="code-block">
75
- <pre>{
76
- "name": "John Doe",
77
- "email": "john@example.com",
78
- "password": "securepassword123"
79
- }</pre>
80
- </div>
81
- </div>
82
-
83
- <div>
84
- <h4 class="font-medium text-gray-700 mb-2">Response (Success)</h4>
85
- <div class="code-block">
86
- <pre>{
87
- "message": "User registered successfully",
88
- "user": {
89
- "_id": "507f1f77bcf86cd799439011",
90
- "name": "John Doe",
91
- "email": "john@example.com"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
  }
93
- }</pre>
94
- </div>
95
- </div>
96
- </div>
97
-
98
- <!-- Login Endpoint -->
99
- <div class="endpoint-card bg-white border border-gray-200 rounded-lg p-4 mb-6 transition-all duration-200">
100
- <div class="flex items-center mb-3">
101
- <span class="method-badge method-post">POST</span>
102
- <span class="ml-2 font-mono text-gray-700">/api/auth/login</span>
103
- </div>
104
- <p class="text-gray-600 mb-3">Authenticate user and return JWT token.</p>
105
-
106
- <div class="mb-4">
107
- <h4 class="font-medium text-gray-700 mb-2">Request Body</h4>
108
- <div class="code-block">
109
- <pre>{
110
- "email": "john@example.com",
111
- "password": "securepassword123"
112
- }</pre>
113
- </div>
114
- </div>
115
-
116
- <div>
117
- <h4 class="font-medium text-gray-700 mb-2">Response (Success)</h4>
118
- <div class="code-block">
119
- <pre>{
120
- "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
121
- "user": {
122
- "_id": "507f1f77bcf86cd799439011",
123
- "name": "John Doe",
124
- "email": "john@example.com"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
  }
126
- }</pre>
127
- </div>
128
- </div>
129
- </div>
130
-
131
- <!-- Profile Endpoint -->
132
- <div class="endpoint-card bg-white border border-gray-200 rounded-lg p-4 mb-6 transition-all duration-200">
133
- <div class="flex items-center mb-3">
134
- <span class="method-badge method-get">GET</span>
135
- <span class="ml-2 font-mono text-gray-700">/api/user/profile</span>
136
- </div>
137
- <p class="text-gray-600 mb-3">Get authenticated user's profile (protected route).</p>
138
-
139
- <div class="mb-4">
140
- <h4 class="font-medium text-gray-700 mb-2">Headers</h4>
141
- <div class="code-block">
142
- <pre>Authorization: Bearer &lt;JWT_TOKEN&gt;</pre>
143
- </div>
144
- </div>
145
-
146
- <div>
147
- <h4 class="font-medium text-gray-700 mb-2">Response (Success)</h4>
148
- <div class="code-block">
149
- <pre>{
150
- "_id": "507f1f77bcf86cd799439011",
151
- "name": "John Doe",
152
- "email": "john@example.com",
153
- "createdAt": "2023-07-15T10:00:00.000Z"
154
- }</pre>
155
- </div>
156
- </div>
157
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
 
160
- <!-- Code Implementation -->
161
- <div class="bg-white rounded-lg shadow-md p-6">
162
- <h2 class="text-2xl font-semibold text-gray-800 mb-4">Implementation</h2>
163
-
164
- <div class="mb-8">
165
- <h3 class="text-xl font-medium text-gray-700 mb-3">1. Install Dependencies</h3>
166
- <div class="code-block">
167
- <pre>npm install express mongoose jsonwebtoken bcryptjs cors dotenv</pre>
168
- </div>
169
- </div>
170
-
171
- <div class="mb-8">
172
- <h3 class="text-xl font-medium text-gray-700 mb-3">2. server.js</h3>
173
- <div class="code-block">
174
- <pre>require('dotenv').config();
 
 
 
 
 
 
 
 
 
 
 
 
 
175
  const express = require('express');
176
- const mongoose = require('mongoose');
177
  const cors = require('cors');
178
- const authRoutes = require('./routes/auth');
 
 
 
 
 
 
 
 
 
 
 
 
179
 
 
180
  const app = express();
181
 
182
- // Middleware
183
- app.use(cors());
184
- app.use(express.json());
185
 
186
- // Routes
187
- app.use('/api/auth', authRoutes);
 
 
 
188
 
189
- // Database connection
190
- mongoose.connect(process.env.MONGO_URI)
191
- .then(() => {
192
- app.listen(process.env.PORT, () => {
193
- console.log(`Server running on port ${process.env.PORT}`);
194
- });
195
- })
196
- .catch(err => console.log(err));</pre>
197
- </div>
198
- </div>
199
-
200
- <div class="mb-8">
201
- <h3 class="text-xl font-medium text-gray-700 mb-3">3. models/User.js</h3>
202
- <div class="code-block">
203
- <pre>const mongoose = require('mongoose');
204
- const bcrypt = require('bcryptjs');
205
 
206
- const userSchema = new mongoose.Schema({
207
- name: { type: String, required: true },
208
- email: { type: String, required: true, unique: true },
209
- password: { type: String, required: true },
210
- }, { timestamps: true });
 
 
211
 
212
- // Hash password before saving
213
- userSchema.pre('save', async function(next) {
214
- if (!this.isModified('password')) return next();
215
- this.password = await bcrypt.hash(this.password, 10);
216
- next();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
217
  });
218
 
219
- module.exports = mongoose.model('User', userSchema);</pre>
220
- </div>
221
- </div>
222
-
223
- <div class="mb-8">
224
- <h3 class="text-xl font-medium text-gray-700 mb-3">4. routes/auth.js</h3>
225
- <div class="code-block">
226
- <pre>const express = require('express');
227
- const router = express.Router();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
228
  const jwt = require('jsonwebtoken');
229
- const bcrypt = require('bcryptjs');
230
  const User = require('../models/User');
231
- const auth = require('../middleware/auth');
232
 
233
- // Register
234
- router.post('/register', async (req, res) => {
235
- try {
236
- const { name, email, password } = req.body;
237
-
238
- // Check if user exists
239
- let user = await User.findOne({ email });
240
- if (user) {
241
- return res.status(400).json({ message: 'User already exists' });
242
- }
243
-
244
- // Create new user
245
- user = new User({ name, email, password });
246
- await user.save();
247
-
248
- // Return user without password
249
- const userResponse = { _id: user._id, name: user.name, email: user.email };
250
- res.status(201).json({ message: 'User registered successfully', user: userResponse });
251
- } catch (err) {
252
- res.status(500).json({ message: 'Server error' });
253
  }
254
- });
255
 
256
- // Login
257
- router.post('/login', async (req, res) => {
258
- try {
259
- const { email, password } = req.body;
260
-
261
- // Check if user exists
262
- const user = await User.findOne({ email });
263
- if (!user) {
264
- return res.status(400).json({ message: 'Invalid credentials' });
265
- }
266
 
267
- // Check password
268
- const isMatch = await bcrypt.compare(password, user.password);
269
- if (!isMatch) {
270
- return res.status(400).json({ message: 'Invalid credentials' });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
271
  }
272
-
273
- // Create JWT token
274
- const token = jwt.sign(
275
- { userId: user._id },
276
- process.env.JWT_SECRET,
277
- { expiresIn: '1h' }
278
- );
279
-
280
- // Return token and user data
281
- const userResponse = { _id: user._id, name: user.name, email: user.email };
282
- res.json({ token, user: userResponse });
283
- } catch (err) {
284
- res.status(500).json({ message: 'Server error' });
285
  }
286
- });
287
 
288
- // Get user profile (protected)
289
- router.get('/user/profile', auth, async (req, res) => {
290
- try {
291
- const user = await User.findById(req.userId).select('-password');
292
- res.json(user);
293
- } catch (err) {
294
- res.status(500).json({ message: 'Server error' });
295
  }
296
- });
297
 
298
- module.exports = router;</pre>
299
- </div>
300
- </div>
301
-
302
- <div class="mb-8">
303
- <h3 class="text-xl font-medium text-gray-700 mb-3">5. middleware/auth.js</h3>
304
- <div class="code-block">
305
- <pre>const jwt = require('jsonwebtoken');
306
-
307
- module.exports = (req, res, next) => {
308
- const token = req.header('Authorization')?.replace('Bearer ', '');
309
-
310
- if (!token) {
311
- return res.status(401).json({ message: 'No token, authorization denied' });
312
  }
313
-
314
- try {
315
- const decoded = jwt.verify(token, process.env.JWT_SECRET);
316
- req.userId = decoded.userId;
317
- next();
318
- } catch (err) {
319
- res.status(401).json({ message: 'Token is not valid' });
 
 
320
  }
321
- };</pre>
322
- </div>
323
- </div>
324
-
325
- <div>
326
- <h3 class="text-xl font-medium text-gray-700 mb-3">6. .env Configuration</h3>
327
- <div class="code-block">
328
- <pre>PORT=5000
329
- MONGO_URI=mongodb://localhost:27017/auth_demo
330
- JWT_SECRET=your_jwt_secret_key</pre>
331
- </div>
332
- </div>
333
- </div>
334
- </div>
335
-
336
- <!-- Testing Section -->
337
- <div class="lg:col-span-1">
338
- <div class="bg-white rounded-lg shadow-md p-6 sticky top-6">
339
- <h2 class="text-2xl font-semibold text-gray-800 mb-4">API Tester</h2>
340
-
341
- <div class="mb-4">
342
- <label class="block text-gray-700 mb-2">Base URL</label>
343
- <input type="text" id="baseUrl" value="http://localhost:5000" class="test-input">
344
- </div>
345
-
346
- <div class="flex border-b mb-4">
347
- <button class="tab-btn py-2 px-4 font-medium text-gray-600 tab-active" data-tab="register">Register</button>
348
- <button class="tab-btn py-2 px-4 font-medium text-gray-600" data-tab="login">Login</button>
349
- <button class="tab-btn py-2 px-4 font-medium text-gray-600" data-tab="profile">Profile</button>
350
- </div>
351
-
352
- <!-- Register Form -->
353
- <div id="register-tab" class="tab-content">
354
- <div class="mb-4">
355
- <label class="block text-gray-700 mb-2">Name</label>
356
- <input type="text" id="register-name" class="test-input" placeholder="John Doe">
357
- </div>
358
- <div class="mb-4">
359
- <label class="block text-gray-700 mb-2">Email</label>
360
- <input type="email" id="register-email" class="test-input" placeholder="john@example.com">
361
- </div>
362
- <div class="mb-4">
363
- <label class="block text-gray-700 mb-2">Password</label>
364
- <input type="password" id="register-password" class="test-input" placeholder="••••••••">
365
- </div>
366
- <button id="register-btn" class="w-full bg-blue-500 hover:bg-blue-600 text-white py-2 px-4 rounded transition">
367
- Register
368
- </button>
369
- </div>
370
-
371
- <!-- Login Form -->
372
- <div id="login-tab" class="tab-content hidden">
373
- <div class="mb-4">
374
- <label class="block text-gray-700 mb-2">Email</label>
375
- <input type="email" id="login-email" class="test-input" placeholder="john@example.com">
376
- </div>
377
- <div class="mb-4">
378
- <label class="block text-gray-700 mb-2">Password</label>
379
- <input type="password" id="login-password" class="test-input" placeholder="••••••••">
380
- </div>
381
- <button id="login-btn" class="w-full bg-green-500 hover:bg-green-600 text-white py-2 px-4 rounded transition">
382
- Login
383
- </button>
384
- </div>
385
-
386
- <!-- Profile Form -->
387
- <div id="profile-tab" class="tab-content hidden">
388
- <div class="mb-4">
389
- <label class="block text-gray-700 mb-2">JWT Token</label>
390
- <input type="text" id="profile-token" class="test-input" placeholder="Paste your JWT token here">
391
- </div>
392
- <button id="profile-btn" class="w-full bg-purple-500 hover:bg-purple-600 text-white py-2 px-4 rounded transition">
393
- Get Profile
394
- </button>
395
- </div>
396
-
397
- <!-- Response Section -->
398
- <div class="mt-6">
399
- <h3 class="text-lg font-medium text-gray-700 mb-2">Response</h3>
400
- <div id="response" class="code-block min-h-32">
401
- <p class="text-gray-400">Response will appear here...</p>
402
- </div>
403
- </div>
404
  </div>
405
  </div>
406
- </div>
407
- </div>
408
 
409
- <script>
410
- // Tab switching
411
- const tabs = document.querySelectorAll('.tab-btn');
412
- tabs.forEach(tab => {
413
- tab.addEventListener('click', () => {
414
- // Update active tab
415
- tabs.forEach(t => t.classList.remove('tab-active'));
416
- tab.classList.add('tab-active');
417
-
418
- // Show corresponding content
419
- document.querySelectorAll('.tab-content').forEach(content => {
420
- content.classList.add('hidden');
421
- });
422
- document.getElementById(`${tab.dataset.tab}-tab`).classList.remove('hidden');
423
- });
424
- });
425
 
426
- // API Testing
427
- const baseUrl = document.getElementById('baseUrl');
428
- const responseDiv = document.getElementById('response');
429
-
430
- // Register
431
- document.getElementById('register-btn').addEventListener('click', async () => {
432
- const name = document.getElementById('register-name').value;
433
- const email = document.getElementById('register-email').value;
434
- const password = document.getElementById('register-password').value;
435
-
436
- if (!name || !email || !password) {
437
- responseDiv.innerHTML = '<p class="text-red-400">Please fill all fields</p>';
438
- return;
 
 
 
 
 
 
 
 
439
  }
 
 
 
 
 
440
 
441
  try {
442
- const res = await fetch(`${baseUrl.value}/api/auth/register`, {
443
- method: 'POST',
444
- headers: { 'Content-Type': 'application/json' },
445
- body: JSON.stringify({ name, email, password })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
446
  });
447
-
448
- const data = await res.json();
449
- responseDiv.innerHTML = `<pre>${JSON.stringify(data, null, 2)}</pre>`;
450
  } catch (err) {
451
- responseDiv.innerHTML = `<p class="text-red-400">Error: ${err.message}</p>`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
452
  }
453
  });
454
-
455
- // Login
456
- document.getElementById('login-btn').addEventListener('click', async () => {
457
- const email = document.getElementById('login-email').value;
458
- const password = document.getElementById('login-password').value;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
459
 
460
- if (!email || !password) {
461
- responseDiv.innerHTML = '<p class="text-red-400">Please fill all fields</p>';
462
- return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
463
  }
 
 
 
464
 
465
- try {
466
- const res = await fetch(`${baseUrl.value}/api/auth/login`, {
467
- method: 'POST',
468
- headers: { 'Content-Type': 'application/json' },
469
- body: JSON.stringify({ email, password })
470
- });
 
471
 
472
- const data = await res.json();
473
- responseDiv.innerHTML = `<pre>${JSON.stringify(data, null, 2)}</pre>`;
 
474
 
475
- // Store token for profile testing
476
- if (data.token) {
477
- document.getElementById('profile-token').value = data.token;
478
- }
479
- } catch (err) {
480
- responseDiv.innerHTML = `<p class="text-red-400">Error: ${err.message}</p>`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
481
  }
482
- });
 
 
 
 
 
 
 
 
 
 
 
483
 
484
- // Profile
485
- document.getElementById('profile-btn').addEventListener('click', async () => {
486
- const token = document.getElementById('profile-token').value;
487
-
488
- if (!token) {
489
- responseDiv.innerHTML = '<p class="text-red-400">Please provide a token</p>';
490
- return;
491
- }
492
-
493
- try {
494
- const res = await fetch(`${baseUrl.value}/api/user/profile`, {
495
- method: 'GET',
496
- headers: {
497
- 'Content-Type': 'application/json',
498
- 'Authorization': `Bearer ${token}`
499
- }
 
500
  });
 
 
501
 
502
- const data = await res.json();
503
- responseDiv.innerHTML = `<pre>${JSON.stringify(data, null, 2)}</pre>`;
504
- } catch (err) {
505
- responseDiv.innerHTML = `<p class="text-red-400">Error: ${err.message}</p>`;
 
506
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
507
  });
508
- </script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
509
  <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Scorpiotur/backend" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
510
  </html>
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Sentimint Backend Documentation</title>
7
  <script src="https://cdn.tailwindcss.com"></script>
8
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
  <style>
 
 
 
 
 
 
 
 
 
 
 
 
10
  .code-block {
11
+ background-color: #1e293b;
12
+ color: #f8fafc;
13
+ padding: 1rem;
14
+ border-radius: 0.5rem;
15
+ overflow-x: auto;
16
+ font-family: 'Courier New', Courier, monospace;
17
+ font-size: 0.875rem;
18
+ line-height: 1.5;
19
+ margin: 1rem 0;
20
  }
21
+ .endpoint {
22
+ background-color: #0f172a;
23
+ padding: 0.5rem 1rem;
24
+ border-radius: 0.5rem;
25
+ margin: 0.5rem 0;
26
  }
27
+ .method-get { color: #4ade80; }
28
+ .method-post { color: #60a5fa; }
29
+ .method-put { color: #fbbf24; }
30
+ .method-delete { color: #f87171; }
31
  </style>
32
  </head>
33
+ <body class="bg-slate-900 text-slate-100">
34
  <div class="container mx-auto px-4 py-8">
35
  <header class="mb-12 text-center">
36
+ <h1 class="text-4xl font-bold text-emerald-400 mb-2">Sentimint Backend</h1>
37
+ <p class="text-xl text-slate-300">Production-ready Node.js/Express backend for crypto trading bot SaaS</p>
38
  <div class="mt-6 flex justify-center space-x-4">
39
+ <span class="px-4 py-2 bg-emerald-800 rounded-full text-sm">JWT Authentication</span>
40
+ <span class="px-4 py-2 bg-blue-800 rounded-full text-sm">LemonSqueezy Payments</span>
41
+ <span class="px-4 py-2 bg-amber-800 rounded-full text-sm">Trading Queue</span>
42
+ <span class="px-4 py-2 bg-purple-800 rounded-full text-sm">WebSocket</span>
43
+ <span class="px-4 py-2 bg-pink-800 rounded-full text-sm">Affiliate System</span>
44
  </div>
45
  </header>
46
 
47
+ <section class="mb-12">
48
+ <h2 class="text-2xl font-semibold text-emerald-400 mb-4">Project Structure</h2>
49
+ <div class="code-block">
50
+ <pre>
51
+ sentimint-backend/
52
+ ├── config/ # Configuration files
53
+ │ ├── database.js # Database connection
54
+ ├── jwt.js # JWT configuration
55
+ │ └── websocket.js # WebSocket configuration
56
+ ── controllers/ # Route controllers
57
+ ├── auth.controller.js
58
+ ── bot.controller.js
59
+ │ ├── payment.controller.js
60
+ │ ├── affiliate.controller.js
61
+ │ └── user.controller.js
62
+ ├── models/ # Database models
63
+ │ ├── User.js
64
+ │ ├── Subscription.js
65
+ │ ├── Trade.js
66
+ │ ├── Affiliate.js
67
+ │ └── Queue.js
68
+ ├── middleware/ # Custom middleware
69
+ │ ├── auth.js
70
+ │ ├── error.js
71
+ │ └── validation.js
72
+ ├── routes/ # API routes
73
+ │ ├── auth.routes.js
74
+ │ ├── bot.routes.js
75
+ │ ├── payment.routes.js
76
+ │ ├── affiliate.routes.js
77
+ │ └── user.routes.js
78
+ ├── services/ # Business logic
79
+ │ ├── auth.service.js
80
+ │ ├── bot.service.js
81
+ │ ├── payment.service.js
82
+ │ ├── affiliate.service.js
83
+ │ ├── queue.service.js
84
+ │ └── websocket.service.js
85
+ ├── utils/ # Utility functions
86
+ │ ├── logger.js
87
+ │ ├── helpers.js
88
+ │ └── validators.js
89
+ ├── queues/ # Bull queue processors
90
+ │ └── trading.queue.js
91
+ ├── app.js # Express app setup
92
+ ├── server.js # Server entry point
93
+ └── .env # Environment variables</pre>
94
+ </div>
95
+ </section>
96
+
97
+ <section class="mb-12">
98
+ <h2 class="text-2xl font-semibold text-emerald-400 mb-4">Database Models</h2>
99
+
100
+ <div class="mb-8">
101
+ <h3 class="text-xl font-medium text-blue-300 mb-2">User Model</h3>
102
+ <div class="code-block">
103
+ <pre>
104
+ const mongoose = require('mongoose');
105
+ const bcrypt = require('bcryptjs');
106
+
107
+ const UserSchema = new mongoose.Schema({
108
+ email: {
109
+ type: String,
110
+ required: true,
111
+ unique: true,
112
+ trim: true,
113
+ lowercase: true
114
+ },
115
+ password: {
116
+ type: String,
117
+ required: true,
118
+ minlength: 8
119
+ },
120
+ isVerified: {
121
+ type: Boolean,
122
+ default: false
123
+ },
124
+ role: {
125
+ type: String,
126
+ enum: ['user', 'admin'],
127
+ default: 'user'
128
+ },
129
+ tradingEnabled: {
130
+ type: Boolean,
131
+ default: false
132
+ },
133
+ apiKey: {
134
+ type: String,
135
+ default: ''
136
+ },
137
+ apiSecret: {
138
+ type: String,
139
+ default: ''
140
+ },
141
+ affiliateCode: {
142
+ type: String,
143
+ unique: true
144
+ },
145
+ referredBy: {
146
+ type: mongoose.Schema.Types.ObjectId,
147
+ ref: 'User'
148
+ },
149
+ resetPasswordToken: String,
150
+ resetPasswordExpire: Date,
151
+ emailVerificationToken: String,
152
+ emailVerificationExpire: Date,
153
+ lastLogin: Date,
154
+ loginHistory: [{
155
+ ip: String,
156
+ device: String,
157
+ timestamp: Date
158
+ }]
159
+ }, {
160
+ timestamps: true
161
+ });
162
+
163
+ // Hash password before saving
164
+ UserSchema.pre('save', async function(next) {
165
+ if (!this.isModified('password')) return next();
166
+
167
+ const salt = await bcrypt.genSalt(10);
168
+ this.password = await bcrypt.hash(this.password, salt);
169
+ next();
170
+ });
171
+
172
+ // Generate affiliate code if not exists
173
+ UserSchema.pre('save', function(next) {
174
+ if (!this.affiliateCode) {
175
+ this.affiliateCode = Math.random().toString(36).substring(2, 8) +
176
+ Math.random().toString(36).substring(2, 8);
177
  }
178
+ next();
179
+ });
180
+
181
+ // Method to compare passwords
182
+ UserSchema.methods.matchPassword = async function(enteredPassword) {
183
+ return await bcrypt.compare(enteredPassword, this.password);
184
+ };
185
+
186
+ module.exports = mongoose.model('User', UserSchema);</pre>
187
+ </div>
188
+ </div>
189
+
190
+ <div class="mb-8">
191
+ <h3 class="text-xl font-medium text-blue-300 mb-2">Subscription Model</h3>
192
+ <div class="code-block">
193
+ <pre>
194
+ const mongoose = require('mongoose');
195
+
196
+ const SubscriptionSchema = new mongoose.Schema({
197
+ user: {
198
+ type: mongoose.Schema.Types.ObjectId,
199
+ ref: 'User',
200
+ required: true
201
+ },
202
+ lemonSqueezyId: {
203
+ type: String,
204
+ required: true
205
+ },
206
+ orderId: {
207
+ type: String,
208
+ required: true
209
+ },
210
+ productId: {
211
+ type: String,
212
+ required: true,
213
+ enum: ['basic', 'pro', 'enterprise'] // Corresponds to $99/$299/$999 tiers
214
+ },
215
+ status: {
216
+ type: String,
217
+ enum: ['active', 'expired', 'cancelled', 'pending'],
218
+ default: 'pending'
219
+ },
220
+ currentPeriodEnd: Date,
221
+ renewsAt: Date,
222
+ trialEndsAt: Date,
223
+ isUsageBased: {
224
+ type: Boolean,
225
+ default: false
226
+ },
227
+ subscriptionItemId: String,
228
+ variantId: String,
229
+ paymentMethod: String,
230
+ billingAnchor: Number,
231
+ urls: {
232
+ updatePaymentMethod: String,
233
+ customerPortal: String
234
+ },
235
+ cancelReason: String,
236
+ cancelledAt: Date,
237
+ metadata: mongoose.Schema.Types.Mixed
238
+ }, {
239
+ timestamps: true
240
+ });
241
+
242
+ module.exports = mongoose.model('Subscription', SubscriptionSchema);</pre>
243
+ </div>
244
+ </div>
245
+
246
+ <div class="mb-8">
247
+ <h3 class="text-xl font-medium text-blue-300 mb-2">Trade Model</h3>
248
+ <div class="code-block">
249
+ <pre>
250
+ const mongoose = require('mongoose');
251
+
252
+ const TradeSchema = new mongoose.Schema({
253
+ user: {
254
+ type: mongoose.Schema.Types.ObjectId,
255
+ ref: 'User',
256
+ required: true
257
+ },
258
+ botId: {
259
+ type: String,
260
+ required: true
261
+ },
262
+ exchange: {
263
+ type: String,
264
+ required: true,
265
+ enum: ['binance', 'kucoin', 'coinbase', 'kraken']
266
+ },
267
+ pair: {
268
+ type: String,
269
+ required: true
270
+ },
271
+ direction: {
272
+ type: String,
273
+ enum: ['long', 'short'],
274
+ required: true
275
+ },
276
+ entryPrice: {
277
+ type: Number,
278
+ required: true
279
+ },
280
+ exitPrice: {
281
+ type: Number
282
+ },
283
+ amount: {
284
+ type: Number,
285
+ required: true
286
+ },
287
+ leverage: {
288
+ type: Number,
289
+ default: 1
290
+ },
291
+ status: {
292
+ type: String,
293
+ enum: ['pending', 'open', 'closed', 'cancelled', 'failed'],
294
+ default: 'pending'
295
+ },
296
+ pnl: {
297
+ type: Number
298
+ },
299
+ pnlPercentage: {
300
+ type: Number
301
+ },
302
+ fees: {
303
+ type: Number
304
+ },
305
+ strategy: {
306
+ type: String
307
+ },
308
+ indicators: mongoose.Schema.Types.Mixed,
309
+ notes: String,
310
+ closedAt: Date,
311
+ metadata: mongoose.Schema.Types.Mixed
312
+ }, {
313
+ timestamps: true
314
+ });
315
+
316
+ module.exports = mongoose.model('Trade', TradeSchema);</pre>
317
+ </div>
318
+ </div>
319
+
320
+ <div class="mb-8">
321
+ <h3 class="text-xl font-medium text-blue-300 mb-2">Affiliate Model</h3>
322
+ <div class="code-block">
323
+ <pre>
324
+ const mongoose = require('mongoose');
325
+
326
+ const AffiliateSchema = new mongoose.Schema({
327
+ affiliate: {
328
+ type: mongoose.Schema.Types.ObjectId,
329
+ ref: 'User',
330
+ required: true
331
+ },
332
+ referredUser: {
333
+ type: mongoose.Schema.Types.ObjectId,
334
+ ref: 'User',
335
+ required: true
336
+ },
337
+ commissionRate: {
338
+ type: Number,
339
+ default: 0.3 // 30% commission
340
+ },
341
+ commissionAmount: {
342
+ type: Number,
343
+ default: 0
344
+ },
345
+ status: {
346
+ type: String,
347
+ enum: ['pending', 'eligible', 'paid'],
348
+ default: 'pending'
349
+ },
350
+ paymentId: String,
351
+ paidAt: Date,
352
+ subscriptionTier: {
353
+ type: String,
354
+ enum: ['basic', 'pro', 'enterprise']
355
+ },
356
+ metadata: mongoose.Schema.Types.Mixed
357
+ }, {
358
+ timestamps: true
359
+ });
360
+
361
+ // Index for faster queries
362
+ AffiliateSchema.index({ affiliate: 1, referredUser: 1 }, { unique: true });
363
+
364
+ module.exports = mongoose.model('Affiliate', AffiliateSchema);</pre>
365
+ </div>
366
+ </div>
367
+
368
+ <div class="mb-8">
369
+ <h3 class="text-xl font-medium text-blue-300 mb-2">Queue Model</h3>
370
+ <div class="code-block">
371
+ <pre>
372
+ const mongoose = require('mongoose');
373
+
374
+ const QueueSchema = new mongoose.Schema({
375
+ jobId: {
376
+ type: String,
377
+ required: true,
378
+ unique: true
379
+ },
380
+ type: {
381
+ type: String,
382
+ required: true,
383
+ enum: ['trade', 'analysis', 'alert']
384
+ },
385
+ status: {
386
+ type: String,
387
+ enum: ['queued', 'processing', 'completed', 'failed', 'cancelled'],
388
+ default: 'queued'
389
+ },
390
+ priority: {
391
+ type: Number,
392
+ default: 0
393
+ },
394
+ data: mongoose.Schema.Types.Mixed,
395
+ result: mongoose.Schema.Types.Mixed,
396
+ error: mongoose.Schema.Types.Mixed,
397
+ startedAt: Date,
398
+ completedAt: Date,
399
+ attempts: {
400
+ type: Number,
401
+ default: 0
402
+ },
403
+ maxAttempts: {
404
+ type: Number,
405
+ default: 3
406
+ },
407
+ delay: {
408
+ type: Number,
409
+ default: 0
410
+ },
411
+ timeout: {
412
+ type: Number,
413
+ default: 30000 // 30 seconds
414
+ },
415
+ createdBy: {
416
+ type: mongoose.Schema.Types.ObjectId,
417
+ ref: 'User'
418
  }
419
+ }, {
420
+ timestamps: true
421
+ });
422
+
423
+ module.exports = mongoose.model('Queue', QueueSchema);</pre>
424
+ </div>
425
+ </div>
426
+ </section>
427
+
428
+ <section class="mb-12">
429
+ <h2 class="text-2xl font-semibold text-emerald-400 mb-4">API Endpoints</h2>
430
+
431
+ <div class="mb-8">
432
+ <h3 class="text-xl font-medium text-blue-300 mb-2">Authentication</h3>
433
+
434
+ <div class="endpoint">
435
+ <span class="method-post">POST</span> /api/v1/auth/register - Register a new user
436
+ </div>
437
+ <div class="endpoint">
438
+ <span class="method-post">POST</span> /api/v1/auth/login - Login user
439
+ </div>
440
+ <div class="endpoint">
441
+ <span class="method-post">POST</span> /api/v1/auth/forgot-password - Request password reset
442
+ </div>
443
+ <div class="endpoint">
444
+ <span class="method-put">PUT</span> /api/v1/auth/reset-password/:token - Reset password
445
+ </div>
446
+ <div class="endpoint">
447
+ <span class="method-get">GET</span> /api/v1/auth/verify-email/:token - Verify email
448
+ </div>
449
+ <div class="endpoint">
450
+ <span class="method-post">POST</span> /api/v1/auth/resend-verification - Resend verification email
451
+ </div>
452
+ <div class="endpoint">
453
+ <span class="method-get">GET</span> /api/v1/auth/me - Get current user
454
+ </div>
455
+ <div class="endpoint">
456
+ <span class="method-post">POST</span> /api/v1/auth/refresh-token - Refresh access token
457
+ </div>
458
+ <div class="endpoint">
459
+ <span class="method-post">POST</span> /api/v1/auth/logout - Logout user
460
+ </div>
461
+ </div>
462
+
463
+ <div class="mb-8">
464
+ <h3 class="text-xl font-medium text-blue-300 mb-2">User</h3>
465
+
466
+ <div class="endpoint">
467
+ <span class="method-put">PUT</span> /api/v1/users/me - Update user profile
468
+ </div>
469
+ <div class="endpoint">
470
+ <span class="method-put">PUT</span> /api/v1/users/password - Change password
471
+ </div>
472
+ <div class="endpoint">
473
+ <span class="method-post">POST</span> /api/v1/users/api-keys - Set exchange API keys
474
+ </div>
475
+ <div class="endpoint">
476
+ <span class="method-delete">DELETE</span> /api/v1/users/api-keys - Remove API keys
477
+ </div>
478
+ <div class="endpoint">
479
+ <span class="method-get">GET</span> /api/v1/users/activity - Get user activity
480
+ </div>
481
+ </div>
482
+
483
+ <div class="mb-8">
484
+ <h3 class="text-xl font-medium text-blue-300 mb-2">Subscription & Payments</h3>
485
+
486
+ <div class="endpoint">
487
+ <span class="method-get">GET</span> /api/v1/subscriptions/plans - Get available subscription plans
488
+ </div>
489
+ <div class="endpoint">
490
+ <span class="method-post">POST</span> /api/v1/subscriptions/checkout - Create checkout session
491
+ </div>
492
+ <div class="endpoint">
493
+ <span class="method-get">GET</span> /api/v1/subscriptions/me - Get user's subscription
494
+ </div>
495
+ <div class="endpoint">
496
+ <span class="method-post">POST</span> /api/v1/subscriptions/cancel - Request cancellation
497
+ </div>
498
+ <div class="endpoint">
499
+ <span class="method-post">POST</span> /api/v1/subscriptions/update-payment-method - Update payment method
500
+ </div>
501
+ <div class="endpoint">
502
+ <span class="method-post">POST</span> /api/v1/subscriptions/webhook - LemonSqueezy webhook handler
503
+ </div>
504
+ <div class="endpoint">
505
+ <span class="method-get">GET</span> /api/v1/subscriptions/invoices - Get subscription invoices
506
+ </div>
507
+ </div>
508
+
509
+ <div class="mb-8">
510
+ <h3 class="text-xl font-medium text-blue-300 mb-2">Trading Bot</h3>
511
+
512
+ <div class="endpoint">
513
+ <span class="method-get">GET</span> /api/v1/bot/status - Get bot status
514
  </div>
515
+ <div class="endpoint">
516
+ <span class="method-post">POST</span> /api/v1/bot/start - Start trading bot
517
+ </div>
518
+ <div class="endpoint">
519
+ <span class="method-post">POST</span> /api/v1/bot/stop - Stop trading bot
520
+ </div>
521
+ <div class="endpoint">
522
+ <span class="method-post">POST</span> /api/v1/bot/strategies - Add/update strategy
523
+ </div>
524
+ <div class="endpoint">
525
+ <span class="method-get">GET</span> /api/v1/bot/strategies - Get strategies
526
+ </div>
527
+ <div class="endpoint">
528
+ <span class="method-delete">DELETE</span> /api/v1/bot/strategies/:id - Delete strategy
529
+ </div>
530
+ <div class="endpoint">
531
+ <span class="method-post">POST</span> /api/v1/bot/trade - Execute trade
532
+ </div>
533
+ <div class="endpoint">
534
+ <span class="method-get">GET</span> /api/v1/bot/trades - Get trade history
535
+ </div>
536
+ <div class="endpoint">
537
+ <span class="method-get">GET</span> /api/v1/bot/performance - Get performance metrics
538
+ </div>
539
+ <div class="endpoint">
540
+ <span class="method-post">POST</span> /api/v1/bot/queue - Add to trading queue
541
+ </div>
542
+ <div class="endpoint">
543
+ <span class="method-get">GET</span> /api/v1/bot/queue - Get queue status
544
+ </div>
545
+ </div>
546
+
547
+ <div class="mb-8">
548
+ <h3 class="text-xl font-medium text-blue-300 mb-2">Affiliate System</h3>
549
 
550
+ <div class="endpoint">
551
+ <span class="method-get">GET</span> /api/v1/affiliates/stats - Get affiliate stats
552
+ </div>
553
+ <div class="endpoint">
554
+ <span class="method-get">GET</span> /api/v1/affiliates/referrals - Get referral list
555
+ </div>
556
+ <div class="endpoint">
557
+ <span class="method-get">GET</span> /api/v1/affiliates/commissions - Get commission history
558
+ </div>
559
+ <div class="endpoint">
560
+ <span class="method-get">GET</span> /api/v1/affiliates/link - Get affiliate link
561
+ </div>
562
+ <div class="endpoint">
563
+ <span class="method-post">POST</span> /api/v1/affiliates/payout - Request payout
564
+ </div>
565
+ <div class="endpoint">
566
+ <span class="method-get">GET</span> /api/v1/affiliates/payouts - Get payout history
567
+ </div>
568
+ </div>
569
+ </section>
570
+
571
+ <section class="mb-12">
572
+ <h2 class="text-2xl font-semibold text-emerald-400 mb-4">Core Implementation</h2>
573
+
574
+ <div class="mb-8">
575
+ <h3 class="text-xl font-medium text-blue-300 mb-2">app.js - Express Setup</h3>
576
+ <div class="code-block">
577
+ <pre>
578
  const express = require('express');
 
579
  const cors = require('cors');
580
+ const helmet = require('helmet');
581
+ const rateLimit = require('express-rate-limit');
582
+ const mongoSanitize = require('express-mongo-sanitize');
583
+ const xss = require('xss-clean');
584
+ const hpp = require('hpp');
585
+ const cookieParser = require('cookie-parser');
586
+ const compression = require('compression');
587
+ const path = require('path');
588
+ const http = require('http');
589
+ const socketio = require('socket.io');
590
+ const logger = require('./utils/logger');
591
+ const errorHandler = require('./middleware/error');
592
+ const websocket = require('./services/websocket.service');
593
 
594
+ // Create express app
595
  const app = express();
596
 
597
+ // Trust proxy
598
+ app.set('trust proxy', true);
 
599
 
600
+ // Enable CORS
601
+ app.use(cors({
602
+ origin: process.env.FRONTEND_URL,
603
+ credentials: true
604
+ }));
605
 
606
+ // Set security HTTP headers
607
+ app.use(helmet());
 
 
 
 
 
 
 
 
 
 
 
 
 
 
608
 
609
+ // Rate limiting
610
+ const limiter = rateLimit({
611
+ windowMs: 15 * 60 * 1000, // 15 minutes
612
+ max: 200, // limit each IP to 200 requests per windowMs
613
+ message: 'Too many requests from this IP, please try again later'
614
+ });
615
+ app.use('/api', limiter);
616
 
617
+ // Body parser, reading data from body into req.body
618
+ app.use(express.json({ limit: '10kb' }));
619
+ app.use(express.urlencoded({ extended: true, limit: '10kb' }));
620
+ app.use(cookieParser());
621
+
622
+ // Data sanitization against NoSQL query injection
623
+ app.use(mongoSanitize());
624
+
625
+ // Data sanitization against XSS
626
+ app.use(xss());
627
+
628
+ // Prevent parameter pollution
629
+ app.use(hpp());
630
+
631
+ // Compress responses
632
+ app.use(compression());
633
+
634
+ // Static files
635
+ app.use(express.static(path.join(__dirname, 'public')));
636
+
637
+ // API routes
638
+ app.use('/api/v1/auth', require('./routes/auth.routes'));
639
+ app.use('/api/v1/users', require('./routes/user.routes'));
640
+ app.use('/api/v1/subscriptions', require('./routes/payment.routes'));
641
+ app.use('/api/v1/bot', require('./routes/bot.routes'));
642
+ app.use('/api/v1/affiliates', require('./routes/affiliate.routes'));
643
+
644
+ // Health check endpoint
645
+ app.get('/health', (req, res) => res.status(200).send('OK'));
646
+
647
+ // Handle 404
648
+ app.all('*', (req, res, next) => {
649
+ res.status(404).json({
650
+ status: 'fail',
651
+ message: `Can't find ${req.originalUrl} on this server!`
652
+ });
653
  });
654
 
655
+ // Error handling middleware
656
+ app.use(errorHandler);
657
+
658
+ // Create HTTP server
659
+ const server = http.createServer(app);
660
+
661
+ // Set up Socket.io
662
+ const io = socketio(server, {
663
+ cors: {
664
+ origin: process.env.FRONTEND_URL,
665
+ methods: ['GET', 'POST'],
666
+ credentials: true
667
+ }
668
+ });
669
+
670
+ // Initialize WebSocket service
671
+ websocket.initialize(io);
672
+
673
+ module.exports = server;</pre>
674
+ </div>
675
+ </div>
676
+
677
+ <div class="mb-8">
678
+ <h3 class="text-xl font-medium text-blue-300 mb-2">server.js - Entry Point</h3>
679
+ <div class="code-block">
680
+ <pre>
681
+ const app = require('./app');
682
+ const mongoose = require('mongoose');
683
+ const config = require('./config/database');
684
+ const logger = require('./utils/logger');
685
+ const queueService = require('./services/queue.service');
686
+
687
+ // Handle uncaught exceptions
688
+ process.on('uncaughtException', err => {
689
+ logger.error('UNCAUGHT EXCEPTION! 💥 Shutting down...');
690
+ logger.error(err.name, err.message);
691
+ process.exit(1);
692
+ });
693
+
694
+ // Connect to database
695
+ mongoose.connect(config.uri, config.options)
696
+ .then(() => logger.info('DB connection successful!'))
697
+ .catch(err => {
698
+ logger.error('DB connection failed!');
699
+ logger.error(err);
700
+ process.exit(1);
701
+ });
702
+
703
+ // Start server
704
+ const port = process.env.PORT || 3000;
705
+ const server = app.listen(port, () => {
706
+ logger.info(`Server running on port ${port}...`);
707
+ });
708
+
709
+ // Initialize queues
710
+ queueService.initialize();
711
+
712
+ // Handle unhandled promise rejections
713
+ process.on('unhandledRejection', err => {
714
+ logger.error('UNHANDLED REJECTION! 💥 Shutting down...');
715
+ logger.error(err.name, err.message);
716
+ server.close(() => {
717
+ process.exit(1);
718
+ });
719
+ });
720
+
721
+ // Handle SIGTERM
722
+ process.on('SIGTERM', () => {
723
+ logger.info('👋 SIGTERM RECEIVED. Shutting down gracefully');
724
+ server.close(() => {
725
+ logger.info('💥 Process terminated!');
726
+ });
727
+ });</pre>
728
+ </div>
729
+ </div>
730
+
731
+ <div class="mb-8">
732
+ <h3 class="text-xl font-medium text-blue-300 mb-2">WebSocket Service</h3>
733
+ <div class="code-block">
734
+ <pre>
735
+ const logger = require('../utils/logger');
736
  const jwt = require('jsonwebtoken');
 
737
  const User = require('../models/User');
 
738
 
739
+ class WebSocketService {
740
+ constructor() {
741
+ this.io = null;
742
+ this.connectedUsers = new Map();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
743
  }
 
744
 
745
+ initialize(io) {
746
+ this.io = io;
 
 
 
 
 
 
 
 
747
 
748
+ // Authentication middleware
749
+ io.use(async (socket, next) => {
750
+ try {
751
+ const token = socket.handshake.auth.token;
752
+
753
+ if (!token) {
754
+ return next(new Error('Authentication error: Token not provided'));
755
+ }
756
+
757
+ const decoded = jwt.verify(token, process.env.JWT_SECRET);
758
+ const user = await User.findById(decoded.id);
759
+
760
+ if (!user) {
761
+ return next(new Error('Authentication error: User not found'));
762
+ }
763
+
764
+ socket.user = user;
765
+ next();
766
+ } catch (err) {
767
+ next(new Error('Authentication error: Invalid token'));
768
+ }
769
+ });
770
+
771
+ // Connection handler
772
+ io.on('connection', (socket) => {
773
+ logger.info(`New WebSocket connection: ${socket.id}`);
774
+
775
+ // Store user connection
776
+ if (socket.user) {
777
+ this.connectedUsers.set(socket.user._id.toString(), socket);
778
+ logger.info(`User ${socket.user.email} connected via WebSocket`);
779
+ }
780
+
781
+ // Trade updates subscription
782
+ socket.on('subscribe:trades', (data) => {
783
+ if (socket.user) {
784
+ socket.join(`user:${socket.user._id}:trades`);
785
+ logger.info(`User ${socket.user.email} subscribed to trade updates`);
786
+ }
787
+ });
788
+
789
+ // Bot status subscription
790
+ socket.on('subscribe:bot-status', (data) => {
791
+ if (socket.user) {
792
+ socket.join(`user:${socket.user._id}:bot-status`);
793
+ logger.info(`User ${socket.user.email} subscribed to bot status updates`);
794
+ }
795
+ });
796
+
797
+ // Queue updates subscription
798
+ socket.on('subscribe:queue', (data) => {
799
+ if (socket.user) {
800
+ socket.join(`user:${socket.user._id}:queue`);
801
+ logger.info(`User ${socket.user.email} subscribed to queue updates`);
802
+ }
803
+ });
804
+
805
+ // Disconnection handler
806
+ socket.on('disconnect', () => {
807
+ logger.info(`WebSocket disconnected: ${socket.id}`);
808
+ if (socket.user) {
809
+ this.connectedUsers.delete(socket.user._id.toString());
810
+ }
811
+ });
812
+ });
813
+ }
814
+
815
+ // Send trade update to specific user
816
+ sendTradeUpdate(userId, trade) {
817
+ if (this.io) {
818
+ this.io.to(`user:${userId}:trades`).emit('trade:update', trade);
819
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
820
  }
 
821
 
822
+ // Send bot status update to specific user
823
+ sendBotStatusUpdate(userId, status) {
824
+ if (this.io) {
825
+ this.io.to(`user:${userId}:bot-status`).emit('bot:status', status);
826
+ }
 
 
827
  }
 
828
 
829
+ // Send queue update to specific user
830
+ sendQueueUpdate(userId, queueItem) {
831
+ if (this.io) {
832
+ this.io.to(`user:${userId}:queue`).emit('queue:update', queueItem);
833
+ }
 
 
 
 
 
 
 
 
 
834
  }
835
+
836
+ // Send notification to specific user
837
+ sendNotification(userId, notification) {
838
+ if (this.io) {
839
+ const socket = this.connectedUsers.get(userId.toString());
840
+ if (socket) {
841
+ socket.emit('notification', notification);
842
+ }
843
+ }
844
  }
845
+ }
846
+
847
+ module.exports = new WebSocketService();</pre>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
848
  </div>
849
  </div>
 
 
850
 
851
+ <div class="mb-8">
852
+ <h3 class="text-xl font-medium text-blue-300 mb-2">Trading Queue Service</h3>
853
+ <div class="code-block">
854
+ <pre>
855
+ const Queue = require('bull');
856
+ const mongoose = require('mongoose');
857
+ const logger = require('../utils/logger');
858
+ const Trade = require('../models/Trade');
859
+ const QueueModel = require('../models/Queue');
860
+ const websocket = require('./websocket.service');
861
+ const { executeTrade } = require('./bot.service');
 
 
 
 
 
862
 
863
+ class QueueService {
864
+ constructor() {
865
+ this.tradingQueue = null;
866
+ }
867
+
868
+ initialize() {
869
+ // Create trading queue
870
+ this.tradingQueue = new Queue('trading', {
871
+ redis: {
872
+ host: process.env.REDIS_HOST,
873
+ port: process.env.REDIS_PORT,
874
+ password: process.env.REDIS_PASSWORD
875
+ },
876
+ defaultJobOptions: {
877
+ removeOnComplete: true,
878
+ removeOnFail: true,
879
+ attempts: 3,
880
+ backoff: {
881
+ type: 'exponential',
882
+ delay: 5000
883
+ }
884
  }
885
+ });
886
+
887
+ // Process queue jobs
888
+ this.tradingQueue.process('execute-trade', async (job) => {
889
+ const { userId, tradeData } = job.data;
890
 
891
  try {
892
+ // Update queue status in DB
893
+ await QueueModel.findOneAndUpdate(
894
+ { jobId: job.id.toString() },
895
+ { status: 'processing', startedAt: new Date() },
896
+ { new: true }
897
+ );
898
+
899
+ // Execute the trade
900
+ const trade = await executeTrade(userId, tradeData);
901
+
902
+ // Update queue status
903
+ await QueueModel.findOneAndUpdate(
904
+ { jobId: job.id.toString() },
905
+ { status: 'completed', completedAt: new Date(), result: trade },
906
+ { new: true }
907
+ );
908
+
909
+ // Send WebSocket update
910
+ websocket.sendQueueUpdate(userId, {
911
+ jobId: job.id.toString(),
912
+ status: 'completed',
913
+ trade
914
  });
915
+
916
+ return trade;
 
917
  } catch (err) {
918
+ // Update queue status
919
+ await QueueModel.findOneAndUpdate(
920
+ { jobId: job.id.toString() },
921
+ {
922
+ status: 'failed',
923
+ completedAt: new Date(),
924
+ error: err.message,
925
+ attempts: job.attemptsMade
926
+ },
927
+ { new: true }
928
+ );
929
+
930
+ // Send WebSocket update
931
+ websocket.sendQueueUpdate(userId, {
932
+ jobId: job.id.toString(),
933
+ status: 'failed',
934
+ error: err.message
935
+ });
936
+
937
+ throw err;
938
  }
939
  });
940
+
941
+ // Event listeners
942
+ this.tradingQueue.on('completed', (job, result) => {
943
+ logger.info(`Job ${job.id} completed with result:`, result);
944
+ });
945
+
946
+ this.tradingQueue.on('failed', (job, err) => {
947
+ logger.error(`Job ${job.id} failed with error:`, err);
948
+ });
949
+
950
+ this.tradingQueue.on('error', (err) => {
951
+ logger.error('Queue error:', err);
952
+ });
953
+ }
954
+
955
+ async addTradeToQueue(userId, tradeData) {
956
+ try {
957
+ // Add job to queue
958
+ const job = await this.tradingQueue.add('execute-trade', { userId, tradeData }, {
959
+ priority: tradeData.priority || 0,
960
+ delay: tradeData.delay || 0
961
+ });
962
+
963
+ // Save to database
964
+ const queueItem = new QueueModel({
965
+ jobId: job.id.toString(),
966
+ type: 'trade',
967
+ status: 'queued',
968
+ priority: tradeData.priority || 0,
969
+ data: tradeData,
970
+ createdBy: userId
971
+ });
972
+
973
+ await queueItem.save();
974
+
975
+ // Send WebSocket update
976
+ websocket.sendQueueUpdate(userId, {
977
+ jobId: job.id.toString(),
978
+ status: 'queued',
979
+ position: await job.getState()
980
+ });
981
+
982
+ return queueItem;
983
+ } catch (err) {
984
+ logger.error('Error adding to queue:', err);
985
+ throw err;
986
+ }
987
+ }
988
+
989
+ async getQueueStatus(userId) {
990
+ try {
991
+ const jobs = await this.tradingQueue.getJobs(['waiting', 'active', 'completed', 'failed']);
992
+
993
+ // Filter jobs for this user
994
+ const userJobs = jobs.filter(job => job.data.userId.toString() === userId.toString());
995
 
996
+ // Get from database for more details
997
+ const queueItems = await QueueModel.find({
998
+ createdBy: userId,
999
+ status: { $in: ['queued', 'processing'] }
1000
+ }).sort('-createdAt');
1001
+
1002
+ return {
1003
+ waiting: await this.tradingQueue.getWaitingCount(),
1004
+ active: await this.tradingQueue.getActiveCount(),
1005
+ completed: await this.tradingQueue.getCompletedCount(),
1006
+ failed: await this.tradingQueue.getFailedCount(),
1007
+ userJobs: queueItems
1008
+ };
1009
+ } catch (err) {
1010
+ logger.error('Error getting queue status:', err);
1011
+ throw err;
1012
+ }
1013
+ }
1014
+ }
1015
+
1016
+ module.exports = new QueueService();</pre>
1017
+ </div>
1018
+ </div>
1019
+
1020
+ <div class="mb-8">
1021
+ <h3 class="text-xl font-medium text-blue-300 mb-2">LemonSqueezy Payment Service</h3>
1022
+ <div class="code-block">
1023
+ <pre>
1024
+ const axios = require('axios');
1025
+ const crypto = require('crypto');
1026
+ const logger = require('../utils/logger');
1027
+ const Subscription = require('../models/Subscription');
1028
+ const User = require('../models/User');
1029
+ const Affiliate = require('../models/Affiliate');
1030
+ const websocket = require('./websocket.service');
1031
+
1032
+ class PaymentService {
1033
+ constructor() {
1034
+ this.apiUrl = 'https://api.lemonsqueezy.com/v1';
1035
+ this.headers = {
1036
+ 'Accept': 'application/vnd.api+json',
1037
+ 'Content-Type': 'application/vnd.api+json',
1038
+ 'Authorization': `Bearer ${process.env.LEMON_SQUEEZY_API_KEY}`
1039
+ };
1040
+ }
1041
+
1042
+ async createCheckout(user, variantId, affiliateCode = null) {
1043
+ try {
1044
+ // Create checkout URL
1045
+ const response = await axios.post(`${this.apiUrl}/checkouts`, {
1046
+ data: {
1047
+ type: 'checkouts',
1048
+ attributes: {
1049
+ checkout_data: {
1050
+ email: user.email,
1051
+ custom: {
1052
+ user_id: user._id.toString(),
1053
+ affiliate_code: affiliateCode
1054
+ }
1055
+ },
1056
+ product_options: {
1057
+ enabled_variants: [variantId],
1058
+ redirect_url: `${process.env.FRONTEND_URL}/dashboard`,
1059
+ receipt_button_text: 'Go to Dashboard',
1060
+ receipt_thank_you_note: 'Thank you for subscribing to Sentimint!'
1061
+ },
1062
+ expires_at: null,
1063
+ test_mode: process.env.NODE_ENV !== 'production'
1064
+ },
1065
+ relationships: {
1066
+ store: {
1067
+ data: {
1068
+ type: 'stores',
1069
+ id: process.env.LEMON_SQUEEZY_STORE_ID
1070
+ }
1071
+ },
1072
+ variant: {
1073
+ data: {
1074
+ type: 'variants',
1075
+ id: variantId
1076
+ }
1077
+ }
1078
+ }
1079
+ }
1080
+ }, { headers: this.headers });
1081
+
1082
+ return response.data.data.attributes.url;
1083
+ } catch (err) {
1084
+ logger.error('Error creating checkout:', err.response?.data || err.message);
1085
+ throw err;
1086
+ }
1087
+ }
1088
+
1089
+ async handleWebhook(payload, signature) {
1090
+ try {
1091
+ // Verify webhook signature
1092
+ const hmac = crypto.createHmac('sha256', process.env.LEMON_SQUEEZY_WEBHOOK_SECRET);
1093
+ const digest = hmac.update(JSON.stringify(payload)).digest('hex');
1094
+
1095
+ if (signature !== digest) {
1096
+ throw new Error('Invalid webhook signature');
1097
  }
1098
+
1099
+ const eventName = payload.meta.event_name;
1100
+ const eventData = payload.data;
1101
 
1102
+ logger.info(`Processing LemonSqueezy webhook: ${eventName}`);
1103
+
1104
+ // Handle different event types
1105
+ switch (eventName) {
1106
+ case 'order_created':
1107
+ await this.handleOrderCreated(eventData);
1108
+ break;
1109
 
1110
+ case 'subscription_created':
1111
+ await this.handleSubscriptionCreated(eventData);
1112
+ break;
1113
 
1114
+ case 'subscription_updated':
1115
+ await this.handleSubscriptionUpdated(eventData);
1116
+ break;
1117
+
1118
+ case 'subscription_cancelled':
1119
+ await this.handleSubscriptionCancelled(eventData);
1120
+ break;
1121
+
1122
+ case 'subscription_expired':
1123
+ await this.handleSubscriptionExpired(eventData);
1124
+ break;
1125
+
1126
+ case 'subscription_resumed':
1127
+ await this.handleSubscriptionResumed(eventData);
1128
+ break;
1129
+
1130
+ case 'subscription_payment_success':
1131
+ await this.handlePaymentSuccess(eventData);
1132
+ break;
1133
+
1134
+ case 'subscription_payment_failed':
1135
+ await this.handlePaymentFailed(eventData);
1136
+ break;
1137
+
1138
+ case 'subscription_payment_recovered':
1139
+ await this.handlePaymentRecovered(eventData);
1140
+ break;
1141
+
1142
+ default:
1143
+ logger.info(`Unhandled webhook event: ${eventName}`);
1144
  }
1145
+
1146
+ return { success: true };
1147
+ } catch (err) {
1148
+ logger.error('Error processing webhook:', err);
1149
+ throw err;
1150
+ }
1151
+ }
1152
+
1153
+ async handleOrderCreated(data) {
1154
+ const customData = data.attributes.custom_data || {};
1155
+ const userId = customData.user_id;
1156
+ const affiliateCode = customData.affiliate_code;
1157
 
1158
+ if (!userId) return;
1159
+
1160
+ // Check if this is a subscription purchase
1161
+ const includedSubscriptions = payload.included?.filter(item => item.type === 'subscriptions');
1162
+ if (!includedSubscriptions || includedSubscriptions.length === 0) return;
1163
+
1164
+ // Handle affiliate commission if applicable
1165
+ if (affiliateCode) {
1166
+ const affiliateUser = await User.findOne({ affiliateCode });
1167
+ if (affiliateUser) {
1168
+ const tier = this.getTierFromVariantId(data.attributes.variant_id);
1169
+
1170
+ const affiliateRecord = new Affiliate({
1171
+ affiliate: affiliateUser._id,
1172
+ referredUser: userId,
1173
+ subscriptionTier: tier,
1174
+ status: 'pending'
1175
  });
1176
+
1177
+ await affiliateRecord.save();
1178
 
1179
+ // Notify affiliate
1180
+ websocket.sendNotification(affiliateUser._id, {
1181
+ type: 'affiliate',
1182
+ message: `New referral: ${data.attributes.user_email}`
1183
+ });
1184
  }
1185
+ }
1186
+ }
1187
+
1188
+ async handleSubscriptionCreated(data) {
1189
+ const userId = data.attributes.user_id;
1190
+ if (!userId) return;
1191
+
1192
+ const user = await User.findById(userId);
1193
+ if (!user) return;
1194
+
1195
+ const variantId = data.attributes.variant_id;
1196
+ const tier = this.getTierFromVariantId(variantId);
1197
+
1198
+ // Create or update subscription
1199
+ const subscription = await Subscription.findOneAndUpdate(
1200
+ { user: userId },
1201
+ {
1202
+ lemonSqueezyId: data.id,
1203
+ orderId: data.attributes.order_id,
1204
+ productId: tier,
1205
+ status: 'active',
1206
+ currentPeriodEnd: new Date(data.attributes.renews_at),
1207
+ renewsAt: new Date(data.attributes.renews_at),
1208
+ urls: {
1209
+ updatePaymentMethod: data.attributes.urls.update_payment_method,
1210
+ customerPortal: data.attributes.urls.customer_portal
1211
+ },
1212
+ paymentMethod: data.attributes.payment_method,
1213
+ billingAnchor: data.attributes.billing_anchor
1214
+ },
1215
+ { upsert: true, new: true }
1216
+ );
1217
+
1218
+ // Enable trading for user
1219
+ user.tradingEnabled = true;
1220
+ await user.save();
1221
+
1222
+ // Send notification
1223
+ websocket.sendNotification(userId, {
1224
+ type: 'subscription',
1225
+ message: `Your ${tier} subscription is now active!`
1226
+ });
1227
+
1228
+ return subscription;
1229
+ }
1230
+
1231
+ async handleSubscriptionUpdated(data) {
1232
+ const subscription = await Subscription.findOne({ lemonSqueezyId: data.id });
1233
+ if (!subscription) return;
1234
+
1235
+ // Update subscription details
1236
+ subscription.currentPeriodEnd = new Date(data.attributes.renews_at);
1237
+ subscription.renewsAt = new Date(data.attributes.renews_at);
1238
+ subscription.paymentMethod = data.attributes.payment_method;
1239
+ subscription.urls.updatePaymentMethod = data.attributes.urls.update_payment_method;
1240
+ subscription.urls.customerPortal = data.attributes.urls.customer_portal;
1241
+
1242
+ await subscription.save();
1243
+
1244
+ // Send notification
1245
+ websocket.sendNotification(subscription.user, {
1246
+ type: 'subscription',
1247
+ message: 'Your subscription has been updated'
1248
+ });
1249
+ }
1250
+
1251
+ async handleSubscriptionCancelled(data) {
1252
+ const subscription = await Subscription.findOne({ lemonSqueezyId: data.id });
1253
+ if (!subscription) return;
1254
+
1255
+ // Update subscription status
1256
+ subscription.status = 'cancelled';
1257
+ subscription.cancelReason = data.attributes.cancellation_reason;
1258
+ subscription.cancelledAt = new Date(data.attributes.ends_at);
1259
+
1260
+ await subscription.save();
1261
+
1262
+ // Disable trading for user
1263
+ const user = await User.findById(subscription.user);
1264
+ if (user) {
1265
+ user.tradingEnabled = false;
1266
+ await user.save();
1267
+ }
1268
+
1269
+ // Send notification
1270
+ websocket.sendNotification(subscription.user, {
1271
+ type: 'subscription',
1272
+ message: 'Your subscription has been cancelled'
1273
+ });
1274
+ }
1275
+
1276
+ async handlePaymentSuccess(data) {
1277
+ const subscription = await Subscription.findOne({ lemonSqueezyId: data.attributes.subscription_id });
1278
+ if (!subscription) return;
1279
+
1280
+ // Update subscription renewal date
1281
+ subscription.currentPeriodEnd = new Date(data.attributes.renews_at);
1282
+ subscription.renewsAt = new Date(data.attributes.renews_at);
1283
+ await subscription.save();
1284
+
1285
+ // Handle affiliate commission
1286
+ const affiliateRecord = await Affiliate.findOne({
1287
+ referredUser: subscription.user,
1288
+ status: 'pending'
1289
+ });
1290
+
1291
+ if (affiliateRecord) {
1292
+ const tier = subscription.productId;
1293
+ let commissionAmount = 0;
1294
+
1295
+ // Calculate commission based on tier
1296
+ if (tier === 'basic') commissionAmount = 99 * 0.3; // 30% of $99
1297
+ if (tier === 'pro') commissionAmount = 299 * 0.3; // 30% of $299
1298
+ if (tier === 'enterprise') commissionAmount = 999 * 0.3; // 30% of $999
1299
+
1300
+ affiliateRecord.commissionAmount = commissionAmount;
1301
+ affiliateRecord.status = 'eligible';
1302
+ await affiliateRecord.save();
1303
+
1304
+ // Notify affiliate
1305
+ websocket.sendNotification(affiliateRecord.affiliate, {
1306
+ type: 'affiliate',
1307
+ message: `You've earned $${commissionAmount.toFixed(2)} from a referral!`
1308
+ });
1309
+ }
1310
+
1311
+ // Send notification to user
1312
+ websocket.sendNotification(subscription.user, {
1313
+ type: 'payment',
1314
+ message: 'Your subscription payment was successful'
1315
  });
1316
+ }
1317
+
1318
+ getTierFromVariantId(variantId) {
1319
+ // Map LemonSqueezy variant IDs to subscription tiers
1320
+ const variantMap = {
1321
+ [process.env.LEMON_SQUEEZY_BASIC_VARIANT_ID]: 'basic',
1322
+ [process.env.LEMON_SQUEEZY_PRO_VARIANT_ID]: 'pro',
1323
+ [process.env.LEMON_SQUEEZY_ENTERPRISE_VARIANT_ID]: 'enterprise'
1324
+ };
1325
+
1326
+ return variantMap[variantId] || 'basic';
1327
+ }
1328
+ }
1329
+
1330
+ module.exports = new PaymentService();</pre>
1331
+ </div>
1332
+ </div>
1333
+ </section>
1334
+
1335
+ <section class="mb-12">
1336
+ <h2 class="text-2xl font-semibold text-emerald-400 mb-4">Environment Variables</h2>
1337
+ <div class="code-block">
1338
+ <pre>
1339
+ # Server
1340
+ NODE_ENV=development
1341
+ PORT=3000
1342
+ FRONTEND_URL=http://localhost:3000
1343
+
1344
+ # Database
1345
+ MONGODB_URI=mongodb://localhost:27017/sentimint
1346
+ MONGODB_OPTIONS={}
1347
+
1348
+ # JWT
1349
+ JWT_SECRET=your_jwt_secret
1350
+ JWT_EXPIRE=30d
1351
+ JWT_COOKIE_EXPIRE=30
1352
+
1353
+ # LemonSqueezy
1354
+ LEMON_SQUEEZY_API_KEY=your_api_key
1355
+ LEMON_SQUEEZY_STORE_ID=your_store_id
1356
+ LEMON_SQUEEZY_WEBHOOK_SECRET=your_webhook_secret
1357
+ LEMON_SQUEEZY_BASIC_VARIANT_ID=your_basic_variant_id
1358
+ LEMON_SQUEEZY_PRO_VARIANT_ID=your_pro_variant_id
1359
+ LEMON_SQUEEZY_ENTERPRISE_VARIANT_ID=your_enterprise_variant_id
1360
+
1361
+ # Redis
1362
+ REDIS_HOST=localhost
1363
+ REDIS_PORT=6379
1364
+ REDIS_PASSWORD=
1365
+
1366
+ # Email (optional)
1367
+ SMTP_HOST=
1368
+ SMTP_PORT=
1369
+ SMTP_USERNAME=
1370
+ SMTP_PASSWORD=
1371
+ EMAIL_FROM=</pre>
1372
+ </div>
1373
+ </section>
1374
+
1375
+ <section class="mb-12">
1376
+ <h2 class="text-2xl font-semibold text-emerald-400 mb-4">Installation & Setup</h2>
1377
+
1378
+ <div class="mb-6">
1379
+ <h3 class="text-xl font-medium text-blue-300 mb-2">1. Prerequisites</h3>
1380
+ <ul class="list-disc pl-6 space-y-2">
1381
+ <li>Node.js v16+</li>
1382
+ <li>MongoDB</li>
1383
+ <li>Redis</li>
1384
+ <li>LemonSqueezy account with configured products</li>
1385
+ </ul>
1386
+ </div>
1387
+
1388
+ <div class="mb-6">
1389
+ <h3 class="text-xl font-medium text-blue-300 mb-2">2. Installation</h3>
1390
+ <div class="code-block">
1391
+ <pre>
1392
+ # Clone the repository
1393
+ git clone https://github.com/your-repo/sentimint-backend.git
1394
+ cd sentimint-backend
1395
+
1396
+ # Install dependencies
1397
+ npm install
1398
+
1399
+ # Create .env file and configure environment variables
1400
+ cp .env.example .env
1401
+ nano .env
1402
+
1403
+ # Start the server
1404
+ npm run dev</pre>
1405
+ </div>
1406
+ </div>
1407
+
1408
+ <div class="mb-6">
1409
+ <h3 class="text-xl font-medium text-blue-300 mb-2">3. Production Deployment</h3>
1410
+ <div class="code-block">
1411
+ <pre>
1412
+ # Build for production
1413
+ npm run build
1414
+
1415
+ # Start in production mode
1416
+ npm start
1417
+
1418
+ # Using PM2 (recommended)
1419
+ npm install -g pm2
1420
+ pm2 start dist/server.js --name sentimint-backend</pre>
1421
+ </div>
1422
+ </div>
1423
+ </section>
1424
+
1425
+ <section class="mb-12">
1426
+ <h2 class="text-2xl font-semibold text-emerald-400 mb-4">Connecting with React Frontend</h2>
1427
+
1428
+ <div class="mb-6">
1429
+ <h3 class="text-xl font-medium text-blue-300 mb-2">API Client Setup</h3>
1430
+ <div class="code-block">
1431
+ <pre>
1432
+ // src/api/client.js
1433
+ import axios from 'axios';
1434
+
1435
+ const apiClient = axios.create({
1436
+ baseURL: process.env.REACT_APP_API_URL || 'http://localhost:3000/api/v1',
1437
+ withCredentials: true
1438
+ });
1439
+
1440
+ // Add request interceptor for JWT
1441
+ apiClient.interceptors.request.use((config) => {
1442
+ const token = localStorage.getItem('token');
1443
+ if (token) {
1444
+ config.headers.Authorization = `Bearer ${token}`;
1445
+ }
1446
+ return config;
1447
+ });
1448
+
1449
+ // Add response interceptor for error handling
1450
+ apiClient.interceptors.response.use(
1451
+ (response) => response,
1452
+ (error) => {
1453
+ if (error.response?.status === 401) {
1454
+ // Handle unauthorized (token expired)
1455
+ localStorage.removeItem('token');
1456
+ window.location.href = '/login';
1457
+ }
1458
+ return Promise.reject(error);
1459
+ }
1460
+ );
1461
+
1462
+ export default apiClient;</pre>
1463
+ </div>
1464
+ </div>
1465
+
1466
+ <div class="mb-6">
1467
+ <h3 class="text-xl font-medium text-blue-300 mb-2">WebSocket Setup</h3>
1468
+ <div class="code-block">
1469
+ <pre>
1470
+ // src/api/websocket.js
1471
+ import io from 'socket.io-client';
1472
+
1473
+ let socket;
1474
+
1475
+ export const connectWebSocket = (token) => {
1476
+ socket = io(process.env.REACT_APP_API_URL || 'http://localhost:3000', {
1477
+ auth: { token },
1478
+ transports: ['websocket']
1479
+ });
1480
+
1481
+ return socket;
1482
+ };
1483
+
1484
+ export const disconnectWebSocket = () => {
1485
+ if (socket) {
1486
+ socket.disconnect();
1487
+ }
1488
+ };
1489
+
1490
+ export const getSocket = () => socket;</pre>
1491
+ </div>
1492
+ </div>
1493
+
1494
+ <div class="mb-6">
1495
+ <h3 class="text-xl font-medium text-blue-300 mb-2">Example API Calls</h3>
1496
+ <div class="code-block">
1497
+ <pre>
1498
+ // Example API calls from React frontend
1499
+ import apiClient from './client';
1500
+
1501
+ // User registration
1502
+ export const register = async (userData) => {
1503
+ const response = await apiClient.post('/auth/register', userData);
1504
+ return response.data;
1505
+ };
1506
+
1507
+ // User login
1508
+ export const login = async (credentials) => {
1509
+ const response = await apiClient.post('/auth/login', credentials);
1510
+ return response.data;
1511
+ };
1512
+
1513
+ // Get user profile
1514
+ export const getMe = async () => {
1515
+ const response = await apiClient.get('/users/me');
1516
+ return response.data;
1517
+ };
1518
+
1519
+ // Start trading bot
1520
+ export const startBot = async (strategy) => {
1521
+ const response = await apiClient.post('/bot/start', { strategy });
1522
+ return response.data;
1523
+ };
1524
+
1525
+ // Get trade history
1526
+ export const getTrades = async (params) => {
1527
+ const response = await apiClient.get('/bot/trades', { params });
1528
+ return response.data;
1529
+ };</pre>
1530
+ </div>
1531
+ </div>
1532
+ </section>
1533
+
1534
+ <footer class="mt-12 pt-8 border-t border-slate-700 text-center text-slate-400">
1535
+ <p>Sentimint Crypto Trading Bot Backend - Production Ready</p>
1536
+ <p class="mt-2">Designed for seamless integration with React frontend</p>
1537
+ <div class="mt-4 flex justify-center space-x-4">
1538
+ <a href="#" class="text-emerald-400 hover:text-emerald-300">
1539
+ <i class="fab fa-github"></i> GitHub
1540
+ </a>
1541
+ <a href="#" class="text-emerald-400 hover:text-emerald-300">
1542
+ <i class="fas fa-book"></i> Documentation
1543
+ </a>
1544
+ <a href="#" class="text-emerald-400 hover:text-emerald-300">
1545
+ <i class="fas fa-code"></i> API Reference
1546
+ </a>
1547
+ </div>
1548
+ </footer>
1549
+ </div>
1550
  <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Scorpiotur/backend" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
1551
  </html>