-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathserver.cjs
More file actions
271 lines (229 loc) · 8.45 KB
/
Copy pathserver.cjs
File metadata and controls
271 lines (229 loc) · 8.45 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
// @ts-nocheck
// Development server to handle API requests and serve static files
// This file uses CommonJS format for Node.js compatibility
require('dotenv').config();
console.log('CLICKHOUSE_HOST:', process.env.CLICKHOUSE_HOST);
const express = require('express');
const cors = require('cors');
const axios = require('axios');
const { JWT } = require('google-auth-library');
const path = require('path');
const fs = require('fs');
const { createClient } = require('@clickhouse/client');
// Import the Clickhouse service - using dynamic import for ES modules
let clickhouseService;
(async () => {
try {
clickhouseService = await import('./dist-server/services/clickhouseService.js');
console.log('Clickhouse service imported successfully');
} catch (error) {
console.error('Error importing Clickhouse service:', error);
}
})();
// Create Express app
const app = express();
const PORT = process.env.PORT || 3001;
// Enable CORS for all routes
app.use(cors({
origin: process.env.NODE_ENV === 'production'
? ['https://communication-dashboard.vercel.app', 'https://dashboard.moving.tech']
: 'http://localhost:3000',
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization', 'token']
}));
app.use(express.json());
// Base URL for the target API
const API_BASE_URL = 'https://dashboard.moving.tech';
// Proxy middleware function
const createProxyMiddleware = async (req, res) => {
// The request path already includes /api, so we don't need to add it again
const targetURL = `${API_BASE_URL}${req.url}`;
console.log('Proxying request to:', targetURL);
try {
// Forward the original headers, method, and body
const response = await axios({
url: targetURL,
method: req.method,
data: req.body,
headers: {
...req.headers,
host: 'dashboard.moving.tech',
// Remove the origin header to prevent CORS issues
origin: undefined,
// Remove connection headers that might cause problems
connection: undefined,
'access-control-request-method': undefined,
'access-control-request-headers': undefined
},
});
// Send the proxied response back to the client
res.status(response.status).json(response.data);
} catch (error) {
console.error(`Error proxying request to ${targetURL}:`, error.message);
// Forward error response if available
if (error.response) {
res.status(error.response.status).json(error.response.data);
} else {
res.status(500).json({
error: 'An error occurred while proxying the request',
message: error.message,
});
}
}
};
// Route all API requests to the proxy middleware, except for generate-fcm-token and download-data
app.all('/api/*', (req, res, next) => {
if (req.path === '/api/generate-fcm-token' || req.path === '/api/download-data') {
return next();
}
createProxyMiddleware(req, res);
});
// Handle the FCM token generation endpoint
app.post('/api/generate-fcm-token', async (req, res) => {
try {
const { serviceAccount } = req.body;
// Enhanced validation
if (!serviceAccount) {
return res.status(400).json({
error: 'Service account data is required'
});
}
if (!serviceAccount.client_email || typeof serviceAccount.client_email !== 'string') {
return res.status(400).json({
error: 'Invalid service account: client_email is required and must be a string'
});
}
if (!serviceAccount.private_key || typeof serviceAccount.private_key !== 'string') {
return res.status(400).json({
error: 'Invalid service account: private_key is required and must be a string'
});
}
// Validate email format
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(serviceAccount.client_email)) {
return res.status(400).json({
error: 'Invalid service account: client_email must be a valid email address'
});
}
// Validate private key format (should start with -----BEGIN PRIVATE KEY-----)
if (!serviceAccount.private_key.includes('-----BEGIN PRIVATE KEY-----')) {
return res.status(400).json({
error: 'Invalid service account: private_key must be in the correct PEM format'
});
}
// Create JWT client using service account credentials
const jwtClient = new JWT({
email: serviceAccount.client_email,
key: serviceAccount.private_key,
scopes: ['https://www.googleapis.com/auth/firebase.messaging']
});
// Get access token
const token = await jwtClient.authorize();
if (!token || !token.access_token) {
throw new Error('Failed to obtain access token from Google');
}
// Return the token with Bearer prefix
return res.status(200).json({ token: `Bearer ${token.access_token}` });
} catch (error) {
console.error('Error generating FCM token:', error);
// Enhanced error response
const errorMessage = error.message || 'Internal server error';
const errorDetails = {
message: errorMessage,
type: error.name,
code: error.code || 'UNKNOWN_ERROR'
};
return res.status(500).json({
error: 'Failed to generate FCM token',
details: errorDetails
});
}
});
// Add API routes before static file serving
app.get('/api/test', (req, res) => {
res.json({ message: 'API server is running!' });
});
// Add data download endpoint
app.get('/api/download-data', async (req, res) => {
try {
const { city, variant } = req.query;
if (!city) {
return res.status(400).json({ error: 'City parameter is required' });
}
console.log('Received request for city:', city, 'variant:', variant);
console.log('clickhouseService:', clickhouseService);
console.log('downloadData function:', clickhouseService?.downloadData);
if (!clickhouseService) {
throw new Error('Clickhouse service not initialized');
}
if (!clickhouseService.downloadData) {
throw new Error('downloadData function not found in clickhouseService');
}
// Execute query using the service's downloadData function
console.log('Executing downloadData...');
const result = await clickhouseService.downloadData(city, variant);
console.log('Got result:', result?.substring(0, 100));
// Set headers for CSV download
res.setHeader('Content-Type', 'text/csv');
res.setHeader('Content-Disposition', `attachment; filename="${city}_${variant || 'ALL'}_${new Date().toISOString().slice(0, 10)}.csv"`);
// Send the CSV data directly
res.send(result);
} catch (error) {
console.error('Error downloading data:', error);
res.status(500).json({ error: 'Failed to download data: ' + error.message });
}
});
// Check if dist directory exists and serve static files
const distPath = path.join(__dirname, 'dist');
if (fs.existsSync(distPath)) {
console.log('Dist directory found! Contents:', fs.readdirSync(distPath));
// Serve static files from the dist directory
app.use(express.static(distPath));
} else {
console.log('WARNING: Dist directory not found at:', distPath);
}
// Default route
app.get('/', (req, res) => {
res.redirect('/login');
});
// Debug endpoint to check server status
app.get('/api/debug', (req, res) => {
const info = {
currentDirectory: __dirname,
distPath: path.join(__dirname, 'dist'),
distExists: fs.existsSync(path.join(__dirname, 'dist')),
environment: process.env.NODE_ENV || 'development',
apiBaseUrl: API_BASE_URL
};
if (info.distExists) {
info.distContents = fs.readdirSync(path.join(__dirname, 'dist'));
}
res.json(info);
});
// For handling client-side routing (SPA)
app.get('*', (req, res, next) => {
// Skip API routes
if (req.path.startsWith('/api/')) {
return next();
}
console.log('Handling client-side route:', req.path);
// Send the index.html file for all non-API routes
const path = require('path');
const fs = require('fs');
const indexPath = path.join(__dirname, 'dist', 'index.html');
if (fs.existsSync(indexPath)) {
res.sendFile(indexPath);
} else {
// During development, allow the Vite dev server to handle the request
res.status(404).json({
message: `Index file not found. In development, this route (${req.path}) should be handled by the Vite dev server.`
});
}
});
// Start the server
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
// Export the Express app for Vercel
module.exports = app;