-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathvite.config.js
More file actions
367 lines (335 loc) · 14.9 KB
/
Copy pathvite.config.js
File metadata and controls
367 lines (335 loc) · 14.9 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
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import fs from 'fs';
import path from 'path';
import http from 'http'; // Import the http module
// Helper function to read request body
async function readRequestBody(req) {
return new Promise((resolve, reject) => {
let body = '';
req.on('data', chunk => {
body += chunk.toString();
});
req.on('end', () => {
try {
resolve(JSON.parse(body));
} catch (e) {
reject(new Error('Invalid JSON'));
}
});
req.on('error', err => {
reject(err);
});
});
}
// --- Hit Counter Variables ---
const hitsFilePath = path.resolve(__dirname, 'data/hits.json');
const postUrl = 'http://127.0.0.1:5000/data'; // Target URL for posting hit count
const postInterval = 60 * 1000; // 60 seconds
let hitCount = 0;
let postIntervalId = null;
// --- Hit Counter Functions ---
function loadHitCount() {
try {
if (fs.existsSync(hitsFilePath)) {
const data = fs.readFileSync(hitsFilePath, 'utf-8');
const jsonData = JSON.parse(data);
hitCount = jsonData.count || 0;
console.log(`[HitCounter] Loaded hit count: ${hitCount}`);
} else {
hitCount = 0;
saveHitCount(); // Create the file with initial count 0
console.log(`[HitCounter] Initialized hits file: ${hitsFilePath}`);
}
} catch (error) {
console.error('[HitCounter] Error loading hit count:', error);
hitCount = 0; // Reset count on error
}
}
function saveHitCount() {
try {
const dataDir = path.dirname(hitsFilePath);
if (!fs.existsSync(dataDir)) {
fs.mkdirSync(dataDir, { recursive: true });
}
fs.writeFileSync(hitsFilePath, JSON.stringify({ count: hitCount }, null, 2), 'utf-8');
// console.log(`[HitCounter] Saved hit count: ${hitCount}`); // Optional: Log every save
} catch (error) {
console.error('[HitCounter] Error saving hit count:', error);
}
}
function postHitCount() {
const payload = JSON.stringify({
sensor_name: "PuckDraftTotalHits",
sensor_value: hitCount
});
const options = {
hostname: '127.0.0.1',
port: 5000,
path: '/data',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(payload)
}
};
const req = http.request(options, (res) => {
let responseBody = '';
res.on('data', (chunk) => {
responseBody += chunk;
});
res.on('end', () => {
if (res.statusCode >= 200 && res.statusCode < 300) {
console.log(`[HitCounter] Successfully posted hit count ${hitCount} to ${postUrl}. Status: ${res.statusCode}`);
} else {
console.error(`[HitCounter] Failed to post hit count to ${postUrl}. Status: ${res.statusCode}, Response: ${responseBody}`);
}
});
});
req.on('error', (error) => {
console.error(`[HitCounter] Error posting hit count to ${postUrl}:`, error.message);
});
req.write(payload);
req.end();
}
// --- Vite Plugins ---
// Custom Vite plugin for handling API requests
function apiPlugin() {
const playersFilePath = path.resolve(__dirname, 'data/players.json');
return {
name: 'custom-api-plugin',
configureServer(server) {
// --- Hit Counter Setup ---
loadHitCount(); // Load initial count
// Middleware to increment hit count on every request
server.middlewares.use((req, res, next) => {
hitCount++;
// Optional: Log every hit - can be noisy
// console.log(`[HitCounter] Hit received. Count: ${hitCount}`);
next(); // Pass control to the next middleware
});
// Start periodic posting
if (postIntervalId) clearInterval(postIntervalId); // Clear previous interval if vite restarts
postIntervalId = setInterval(() => {
saveHitCount(); // Save count before posting
postHitCount();
}, postInterval);
console.log(`[HitCounter] Started posting hit count every ${postInterval / 1000} seconds to ${postUrl}`);
// --- API Endpoint Setup ---
// Ensure the data directory exists (also ensures for hits.json)
const dataDir = path.dirname(playersFilePath);
if (!fs.existsSync(dataDir)) {
fs.mkdirSync(dataDir, { recursive: true });
console.log(`Created data directory: ${dataDir}`);
}
// Ensure the players file exists
if (!fs.existsSync(playersFilePath)) {
fs.writeFileSync(playersFilePath, '[]', 'utf-8');
console.log(`Created initial players file: ${playersFilePath}`);
}
server.middlewares.use('/api/players', async (req, res, next) => {
console.log(`[API Middleware] Received ${req.method} request for ${req.url}`); // Debug log
if (req.method === 'GET') {
try {
const data = fs.readFileSync(playersFilePath, 'utf-8');
console.log('[API Middleware] Sending player data (GET)'); // Debug log
res.setHeader('Content-Type', 'application/json');
res.end(data);
} catch (error) {
console.error('[API Middleware] Error reading players.json (GET):', error);
res.statusCode = 500;
res.end(JSON.stringify({ message: 'Error reading player data' }));
}
} else if (req.method === 'POST') {
try {
const playersData = await readRequestBody(req);
console.log('[API Middleware] Received data to save (POST):', playersData); // Debug log
fs.writeFileSync(playersFilePath, JSON.stringify(playersData, null, 2), 'utf-8');
console.log('[API Middleware] Players saved successfully (POST)'); // Debug log
res.statusCode = 200;
res.end(JSON.stringify({ message: 'Players saved successfully' }));
} catch (error) {
console.error('[API Middleware] Error processing request (POST):', error);
res.statusCode = error.message === 'Invalid JSON' ? 400 : 500;
res.end(JSON.stringify({ message: error.message || 'Error saving player data' }));
}
} else {
// Method Not Allowed for other HTTP methods on this specific path
console.log(`[API Middleware] Method ${req.method} not allowed for /api/players`); // Debug log
res.statusCode = 405; // Method Not Allowed
res.setHeader('Allow', 'GET, POST');
res.end(JSON.stringify({ message: `Method ${req.method} Not Allowed` }));
// We don't call next() here because we've handled the request (by rejecting it)
}
});
// --- Game Status API Endpoint ---
const gameStatusFilePath = path.resolve(__dirname, 'data/gamestatus.json');
if (!fs.existsSync(gameStatusFilePath)) {
// Default is not cancelled, no BBQ, no message, and teams unlocked.
fs.writeFileSync(gameStatusFilePath, JSON.stringify({ cancelledFor: null, bbqOn: false, message: '', teamsLocked: false }), 'utf-8');
console.log(`Created initial gamestatus file: ${gameStatusFilePath}`);
}
server.middlewares.use('/api/gamestatus', async (req, res, next) => {
console.log(`[GameStatus API] Received ${req.method} request`);
if (req.method === 'GET') {
try {
const data = fs.readFileSync(gameStatusFilePath, 'utf-8');
res.setHeader('Content-Type', 'application/json');
res.end(data);
} catch (error) {
console.error('[GameStatus API] Error reading gamestatus.json (GET):', error);
res.statusCode = 500;
res.end(JSON.stringify({ message: 'Error reading game status' }));
}
} else if (req.method === 'POST') {
try {
const statusData = await readRequestBody(req);
fs.writeFileSync(gameStatusFilePath, JSON.stringify(statusData, null, 2), 'utf-8');
res.statusCode = 200;
res.end(JSON.stringify({ message: 'Game status saved' }));
} catch (error) {
console.error('[GameStatus API] Error processing request (POST):', error);
res.statusCode = error.message === 'Invalid JSON' ? 400 : 500;
res.end(JSON.stringify({ message: error.message || 'Error saving game status' }));
}
} else {
res.statusCode = 405;
res.setHeader('Allow', 'GET, POST');
res.end(JSON.stringify({ message: `Method ${req.method} Not Allowed` }));
}
});
// --- Previous Games API Endpoint ---
server.middlewares.use('/api/previous-games', (req, res, next) => {
const dataDir = path.resolve(__dirname, 'data');
try {
const files = fs.readdirSync(dataDir);
const teamFiles = files.filter(file => /^\d+\.teams\.json$/.test(file));
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify(teamFiles));
} catch (error) {
console.error('[PreviousGames API] Error reading data directory:', error);
res.statusCode = 500;
res.end(JSON.stringify({ message: 'Error reading game files' }));
}
});
// --- Teams API Endpoint ---
server.middlewares.use('/api/teams', async (req, res, next) => {
console.log(`[Teams API] Received ${req.method} request for ${req.url}`);
if (req.method === 'GET') {
const parsedUrl = new URL(req.url, `http://${req.headers.host}`);
const filename = parsedUrl.searchParams.get('filename');
if (!filename) {
res.statusCode = 400;
return res.end(JSON.stringify({ message: 'Missing filename query parameter' }));
}
try {
const teamsFilePath = path.resolve(__dirname, 'data', filename);
if (fs.existsSync(teamsFilePath)) {
const data = fs.readFileSync(teamsFilePath, 'utf-8');
res.setHeader('Content-Type', 'application/json');
res.end(data);
} else {
res.statusCode = 404;
res.end(JSON.stringify({ message: 'Teams file not found' }));
}
} catch (error) {
console.error('[Teams API] Error reading teams file (GET):', error);
res.statusCode = 500;
res.end(JSON.stringify({ message: 'Error reading teams data' }));
}
} else if (req.method === 'POST') {
try {
const { filename, teams, score, vote } = await readRequestBody(req);
if (!filename) {
res.statusCode = 400;
return res.end(JSON.stringify({ message: 'Missing filename' }));
}
const teamsFilePath = path.resolve(__dirname, 'data', filename);
if (teams) {
// Saving full team data
fs.writeFileSync(teamsFilePath, JSON.stringify(teams, null, 2), 'utf-8');
res.statusCode = 200;
res.end(JSON.stringify({ message: 'Teams saved successfully' }));
} else if (score) {
// Updating score for an existing game
if (!fs.existsSync(teamsFilePath)) {
res.statusCode = 404;
return res.end(JSON.stringify({ message: 'Game file not found to update score.' }));
}
const gameData = JSON.parse(fs.readFileSync(teamsFilePath, 'utf-8'));
gameData.scoreLight = typeof score.light === 'number' ? score.light : gameData.scoreLight;
gameData.scoreDark = typeof score.dark === 'number' ? score.dark : gameData.scoreDark;
fs.writeFileSync(teamsFilePath, JSON.stringify(gameData, null, 2), 'utf-8');
res.statusCode = 200;
res.end(JSON.stringify({ message: 'Score saved successfully' }));
} else if (vote) {
// Saving a vote for a team
if (!fs.existsSync(teamsFilePath)) {
res.statusCode = 404;
return res.end(JSON.stringify({ message: 'Game file not found to vote on.' }));
}
const gameData = JSON.parse(fs.readFileSync(teamsFilePath, 'utf-8'));
if (vote === 'Light') gameData.votesLight = (gameData.votesLight || 0) + 1;
else if (vote === 'Dark') gameData.votesDark = (gameData.votesDark || 0) + 1;
fs.writeFileSync(teamsFilePath, JSON.stringify(gameData, null, 2), 'utf-8');
res.statusCode = 200;
res.end(JSON.stringify({ message: 'Vote saved successfully', votesLight: gameData.votesLight, votesDark: gameData.votesDark }));
} else {
res.statusCode = 400;
res.end(JSON.stringify({ message: 'Request body must contain "teams", "score", or "vote" object.' }));
}
} catch (error) {
console.error('[Teams API] Error processing request (POST):', error);
res.statusCode = error.message === 'Invalid JSON' ? 400 : 500;
res.end(JSON.stringify({ message: error.message || 'Error processing request' }));
}
} else {
res.statusCode = 405;
res.setHeader('Allow', 'GET, POST');
res.end(JSON.stringify({ message: `Method ${req.method} Not Allowed` }));
}
});
// --- 404 Handler for undefined routes ---
// This middleware should run after specific API handlers.
// It checks if a request is for a valid SPA route or a static asset.
server.middlewares.use((req, res, next) => {
const parsedUrl = new URL(req.url, `http://${req.headers.host}`);
const pathname = parsedUrl.pathname;
// Valid client-side routes from your router configuration
const validAppRoutes = ['/', '/login', '/leader', '/pick', '/previous-games'];
// Paths to let Vite handle (API, static assets, internal)
const isApiCall = pathname.startsWith('/api/');
const isViteInternal = pathname.startsWith('/@');
const isStaticAsset = /\.\w+($|\?)/.test(pathname); // Matches paths with extensions like .js, .css, .jpg
// If the request is for a known asset type or a valid app route, let it pass.
if (isApiCall || isViteInternal || isStaticAsset || validAppRoutes.includes(pathname)) {
return next();
}
// Otherwise, it's an unrecognized path. Return a 404 error.
console.log(`[404 Handler] Returning 404 for unrecognized path: ${pathname}`);
res.statusCode = 404;
res.setHeader('Content-Type', 'text/plain');
res.end('Not Found');
});
// --- Server Shutdown Hook (Optional but good practice) ---
server.httpServer?.on('close', () => {
console.log('[HitCounter] Server closing. Saving final hit count...');
if (postIntervalId) clearInterval(postIntervalId); // Stop posting
saveHitCount(); // Save count one last time
});
}
};
}
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
apiPlugin() // Add our custom API plugin
],
server: {
host: true, // Allows access from network hosts like `fnhl.ca`
hmr: {
host: 'fnhl.ca'
}
},
});