-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathserver.js
executable file
·451 lines (332 loc) · 11.9 KB
/
server.js
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
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
/**
* Creates an http server and serves a static 'index.html' file
* back to the client. Listens for 'API calls' from the client and
* serves back database information accordingly.
*
* Listens at address: http://localhost:{SERVER_PORT}/
**/
var fs = require('fs');
var http = require('http');
var routes = require('./routes.js');
var mimes = require('./mimes.js');
var consts = require('./constants.js');
var server = {
_server: null,
_callbacks: {},
events: {
EVT_ON_REGISTER: 'register',
EVT_ON_NEWREGISTER: 'new_register',
EVT_ON_COMMAND: 'command'
},
on: function(eventName, callback) {
if(!server._callbacks[eventName]) {
server._callbacks[eventName] = [];
}
server._callbacks[eventName].push(callback);
},
emit: function(eventName, params) {
if(!server._callbacks[eventName]) {
return;
}
if(!(params instanceof Array)) {
params = [params];
}
server._callbacks[eventName].forEach(function(fn) {
fn.apply(server, params);
});
},
handle_register_req: function(db, res, value) {
var id = value.split('id=')[1];
var name = db.find({
id: id
});
var response = {
id: id
};
if(name.length == 0) {
// if initial id is not found, attempt to
// find id with two leading zeroes
var name2 = db.find({
id: '00' + id
});
if(name2.length > 0) {
name = name2;
}
}
if(name.length > 0) {
var entry = db.get(name[0].index);
// check to see if entry has already been registered for this event
if(entry.registered) {
response.alreadyRegistered = true;
} else {
entry.visits++;
db.register(name[0]);
}
if(entry.deleted) {
db.statistics.registeredCount++;
response.alreadyRegistered = false;
entry.deleted = false;
}
response.fname = name[0].fname;
response.lname = name[0].lname;
response.registered = true;
} else {
response.registered = false;
}
res.end(JSON.stringify(response));
},
handle_register_new_req: function(db, res, value) {
var entry = {}; // create a new entry object with fields passed from client
var values = decodeURIComponent(value).split('&'); // create array of key-value pairs of passed data
// create response object
var response = {};
// format key-value pair and add to object 'entry'
values.forEach(function(item, index) {
// split value pair into key and value and save array
var valuePair = item.split('=');
// add key-value pair to entry object
entry[valuePair[0]] = valuePair[1];
});
// todo change format of name in client side
console.log('Registering \'' + entry.first + ' ' + entry.last + '\' with ID ' + entry.student_id);
// add entry id to list of registered entries
db.registerNew(entry);
//return entry id in response object
response.id = entry.student_id;
if(entry.first) {
response.fname = entry.first;
response.lname = entry.last;
response.registered = true;
} else {
response.registered = false;
response.registerError = true;
}
res.end(JSON.stringify(response));
},
handle_command_req: function(scanner, api, output, mysql, db, res, value) {
// split command string into sub-commands
var command = value.split('/'); // [1] -> target / object to apply the command to
// [2] -> action to apply to target
// [3] -> data to use when applying action to target
if(command[1] == 'export') {
// activate spreadsheet export
if(command[2] && command[2] == 'excel') {
scanner.exportDatabase(scanner, db, mysql, api, output, 'excel', function(err) {
if(err) {
return res.end('ERR: There was an error exporting the data:', err);
}
console.log('EXPORT', 'EXCEL', 'Database exported through excel command');
res.end('success');
});
} else {
// override second command if mysql server is currently being used for data
// by exporting database we are simply updating new entries and registered students
scanner.exportDatabase(scanner, db, mysql, api, output, (mysql.isConnected ? 'mysql' : command[2]), function(err) {
if(err) {
// send error message back to client and exit
return res.end('ERR: There was an error exporting the data: '+err);
}
console.log('EXPORT', 'MYSQL', 'database exported through mysql command');
res.end('success');
// if mysql db is available, also generate a spreadsheet file #excel- #csv+
scanner.exportDatabase(scanner, db, mysql, api, output, 'csv', function(err) {
if(!err) {
console.log('EXPORT', 'CSV', 'Successfully generated comma-delimited document for event.');
}
});
});
}
} else if(command[1] == 'query') {
res.end('I am not allowed to index the database yet.');
} else if(command[1] == 'student') {
// handle student deletion by id
if(!command[2]) {
console.log('ERROR', 'SERVER', 'COMMAND', 'Insuficient command parameters, command segment at index 2 not found.');
return res.end('Invalid student command, unspecified sub-action (delete, add)');
}
if(!command[3]) {
console.log('ERROR', 'SERVER', 'COMMAND', 'Insuficient command parameters, command segment at index 3 not found.');
return res.end('Invalid student command, unspecified student identifier');
}
// handle deletion of attendance records
// assume command[3] is defined
if(command[2] == 'delete') {
var subrout = decodeURIComponent(command[3]);
// handle record removal by student id
if(subrout.match(/^(00|000)[0-9]{5,6}/gi)) {
return server.handle_command_del_student(scanner, api, mysql, db, res, subrout);
}
if(subrout == 'last') {
var student_id = db.getRegistered()[db.getRegistered().length - 1];
if(student_id) {
return server.handle_command_del_student(scanner, api, mysql, db, res, student_id.id);
}
}
console.log('ERROR', 'SERVER', 'COMMAND', 'Student subroutine identifier', command[3], 'is undefined.');
return res.end('Invalid student identifier, supported identifiers include a student id, or a keyword such as "last"');
}
// assume requested sub-routine is undefined if
// we made it this far
console.log('ERROR', 'SERVER', 'COMMAND', 'Subroutine', command[2], 'is undefined.');
res.end('Invalid student command sub-routine ' + command[2]);
} else if(command[1] == 'event') {
if(command[2] == 'name') {
// set global event name,
// add event with its new name to the 'events' table in the mysql database
db.global_values[0] = decodeURIComponent(command[3] + ' (' + scanner.getEventId() + ')');
scanner.updateEventName(scanner, mysql, api, decodeURIComponent(command[3]));
// send success message back to client
res.end('success');
} else if(command[2] == 'delete') {
// remove student at top, or first index of the
// registered student array
if(command[3] == 'top') {
db.remove(scanner, mysql, db.getRegistered()[0]);
res.end('success');
} else if(command[3] == 'bottom') {
// advertise command is not yet fully implemented
console.log('Unimplemented command called.');
return res.end('This command has not been implemented yet.');
// initialize record to delete with last item on database
var recordToDelete = db.getRegistered()[db.getRegistered().length - 1];
var numberOfDeletedRecords = 0;
// iterate through records from the bottom of the list until we find next one that hasn't been deleted
while(!recordToDelete.deleted) {
// increment tally of already deleted records
numberOfDeletedRecords++;
// assign next record from bottom as record to delete
recordToDelete = db.getRegistered()[db.getRegistered().length - 1 - numberOfDeletedRecords];
}
// tell database to remove the last record on the list
db.remove(scanner, mysql, db.getRegistered()[db.getRegistered().length - 1 - numberOfDeletedRecords], function(err) {
if(err) {
// advertise error
console.log('An error occurred deleting a database record -> ' + err);
// send back error response as JSON object to client and exit
return res.end(JSON.stringify({
error : err
}));
}
console.log(db.getRegistered()[db.getRegistered().length-1].deleted);
// if success, advertise
console.log('successfully deleted entry with id ' + db.getRegistered()[db.getRegistered().length-1].id);
// send back successful response as JSON object to client
return res.end(JSON.stringify({
data : {
error : false,
length : db.statistics.registeredCount,
stats : db.statistics
}
}));
});
} else {
res.end('ERR: Invalid event action.');
}
} else {
res.end('ERR: Invalid event action.');
}
} else if(command[1] == 'request') {
if(command[2] == 'stats') {
res.writeHead(200, {'Content-type': 'application/json'});
res.end(JSON.stringify({
data : {
stats : db.statistics,
length : db.size('registered')
}
}));
}
} else {
res.end('ERR: Invalid command [' + command[1] + ']');
}
},
/**
* Handle deletion of student attendance records by student id
*/
handle_command_del_student: function(scanner, api, mysql, db, res, student_id) {
var entry = db.find({
id: student_id
});
if(!entry.length) {
console.log('ERROR', 'SERVER', 'COMMAND', 'Student id', student_id, 'was not found while attempting to unregister such entry.');
return res.end('Invalid student command, invalid student id (' + student_id + ')');
}
// tell database to remove specified record
db.remove(scanner, mysql, entry[0], function(err) {
if(err) {
console.log('ERROR', 'SERVER', 'COMMAND', 'DEL_STUDENT', err.toString());
return res.end(err.toString());
}
if(!entry[0].deleted) {
console.log('ERROR', 'SERVER', 'COMMAND', 'DEL_STUDENT', 'Record integrity check FAILED. Entry', student_id, 'was queued for removal but was not successfully marked as "deleted".');
return res.end('Record integrity check FAILED. Entry ' + student_id + ' was queued for removal but was not successfully marked as "deleted".');
}
// assume deletion success
console.log('SERVER', 'COMMAND', 'DEL_STUDENT', 'Successfully deleted entry with id "', student_id, '" from the database');
return res.end('success');
});
// tell api to remove specified record
api.send('cmd_del_student', {
studentId: student_id
});
},
handle_req: function(req, res) {
var path = routes[req.url] || req.url;
if(path == '/register') {
if(req.method == 'POST') {
var value = '';
req.on('data', function(chunk) {
value += chunk;
});
req.on('end',function() {
server.emit(server.events.EVT_ON_REGISTER, [res, value]);
});
} else {
res.end('Invalid request.');
}
} else if(path == '/register/new') {
if(req.method == 'POST') {
var value = '';
req.on('data',function(chunk) {
value += chunk;
});
req.on('end',function() {
server.emit(server.events.EVT_ON_NEWREGISTER, [res, value]);
});
} else {
res.end('Invalid request.');
}
} else if(path == '/command') {
// check the type of request being made is a post
if(req.method != 'POST') {
console.log('ERR: Invalid request.');
return res.end('Invalid request.');
}
var value = '';
req.on('data',function(chunk) {
value += chunk;
});
req.on('end',function() {
server.emit(server.events.EVT_ON_COMMAND, [res, value]);
});
} else {
fs.readFile(__dirname + path, function(err, data) {
if(err) {
res.writeHead(404);
return res.end('404. File not found.');
}
var ftype = path.split('.');
ftype = ftype[ftype.length-1];
res.writeHead(200, { 'Content-type':(mimes[ftype] || 'text/plain') });
res.end(data);
});
}
},
init: function() {
if(server._server) {
return;
}
server._server = http.createServer(server.handle_req);
server._server.listen(consts.SERVER_PORT, consts.SERVER_HOST);
}
};
module.exports = server;