-
Notifications
You must be signed in to change notification settings - Fork 0
/
server.js
346 lines (302 loc) · 11 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
const express = require('express');
const app = express();
const compression = require('compression');
const port = process.env.PORT || 5104;
const cookieParser = require('cookie-parser');
const crypto = require('crypto');
const cookieSecret = crypto.createHash('sha256').update('51O4').digest('hex');
const isRunningOnGoogle = !!process.env.GOOGLE_CLOUD_PROJECT;
const {SecretManagerServiceClient} = require('@google-cloud/secret-manager');
const secretClient = new SecretManagerServiceClient();
const Datastore = require('@google-cloud/datastore').Datastore;
const datastore = isRunningOnGoogle ? new Datastore() : new Datastore({
projectId: 'breakerbots-website',
keyFilename: './datastore.json'
});
const hoursPasswordHash = '9fb9297d179a9e2341c9562f94e88b76d6a3c45fdb3a0cbaca832a22aa99b7b2';
const dayjs = require('dayjs');
const { futimes } = require('fs');
dayjs.extend(require('dayjs/plugin/utc'));
dayjs.extend(require('dayjs/plugin/timezone'));
dayjs.tz.setDefault("America/Los_Angeles");
//Express Config
app.set('view engine', 'html');
app.set('views', 'src');
// @ts-ignore
app.engine('.html', require('ejs').__express);
app.use(compression());
app.disable('x-powered-by');
app.use(express.json());
app.use('/images', express.static('src/images'));
app.use('/assets', express.static('src/assets'));
//Pages
var pages = {
"/": ["index.html"],
"/robots": ["robots.html"],
"/events": ["events.html"],
"/sponsor": ["sponsor.html"],
"/contact": ["contact.html"],
"/calendar": ["calendar.html"],
"/hours": ["hours/auth.html"],
"/hours/home": ["hours/home.html", getPeopleInjection],
"/hours/people": ["hours/people.html", getPeopleInjection],
"/thankyou": ["thankyou.html"],
"/dashboard": ["dashboard2.html"],
// "/breakerlib": ["breakerlib.html"],
// "/weeklyupdates": ["weeklyupdates.html"],
// "/joinus": ["joinus.html"]
};
//Hours Auth Middleware
app.use('/hours**', cookieParser(cookieSecret), (req, res, next) => {
//first time auth with query
if (Object.keys(req.query).length > 0 && req.query.password) {
// @ts-ignore
if (crypto.createHash('sha256').update(req.query.password).digest('hex') === hoursPasswordHash) {
//save password in signed cookie
res.cookie('password', req.query.password, {
signed: true
});
//go home timmy
res.redirect('/hours/home');
} else {
//clear invalid query
res.redirect('/hours');
}
}
//cashed auth with cookie
else if (req.signedCookies && req.signedCookies.password &&
crypto.createHash('sha256').update(req.signedCookies.password).digest('hex') === hoursPasswordHash) {
if (req.baseUrl === '/hours') {
//go home timmy
res.redirect('/hours/home');
} else {
//let the king pass
next();
}
}
//invalid auth
else {
//remove invalid cookie
res.clearCookie('password', {
signed: true
});
if (req.baseUrl === '/hours') {
//let the peasant pass
next();
} else {
//send to get auth
res.redirect('/hours');
}
}
});
//Hours Redirected
app.use('/hours/home', async (req, res, next) => {
if (await inMeeting()) {
//meeting is happening --> redirect
res.redirect('/hours/people');
}
else {
next();
}
});
app.use('/hours/people', async (req, res, next) => {
if (await inMeeting()) {
next();
}
else {
//meeting is over --> redirect
res.redirect('/hours/home');
}
});
function roundToNearest15Minutes(date) {
return date.minute(Math.round(date.minute() / 15) * 15).second(0).millisecond(0);
}
async function inMeeting() {
const taskKey = datastore.key(['person', 'Meeting']);
const [entity] = await datastore.get(taskKey);
const history = entity.history;
if (history.length > 1) {
const startOfLastMeetingDate = dayjs.tz(dayjs(history[history.length - 2]));
const endOfLastMeetingDate = dayjs.tz(dayjs(history[history.length - 1]));
const currentDate = dayjs.tz(roundToNearest15Minutes(dayjs()));
return currentDate >= startOfLastMeetingDate && currentDate <= endOfLastMeetingDate;
}
else {
return false;
}
}
// // clearHours();
// async function clearHours() {
// try {
// const query = datastore.createQuery('person');np
// const [result] = await datastore.runQuery(query);
// console.log("!! CLEARING ALL HOURS DATA !!")
// for (const person of result) {
// let successStr = "cleared all "+person.history.length+" entries in " + person[datastore.KEY].name;
// person.history = [];
// await datastore.update({ key: person., data: person});
// }
// res.status(200).json({ success: true });
// return;
// } catch (err) {
// console.error(err);
// res.status(500).json({ success: false, error: err });
// }
// }
// async function addPeople([peopleToAdd]) {
// for (const person of peopleToAdd) {
// datastore.insert
// }
// }
//Hours People Injection
async function getPeopleInjection() {
try {
// const query = datastore.createQuery('person').select('__key__');
// const result = (await datastore.runQuery(query))[0];
const query = datastore.createQuery('person');
const [result] = await datastore.runQuery(query);
const people = [];
let meeting;
//convert list of object array with name + history into name + hours
for (const person of result) {
const history = person.history;
const name = person[datastore.KEY].name;
//calculate hours
let totalMs = 0;
let errorFlag = false;
let extraHours = 0;
const isMeeting = name === "Meeting";
for (let i = 0; i < history.length - 1; i += 2) {
if (history[i] !== null && history[i + 1] !== null) {
const startDate = dayjs.tz(dayjs(history[i]));
const endDate = dayjs.tz(dayjs(history[i + 1]));
const diffMs = endDate.valueOf() - startDate.valueOf();
if (diffMs > 24 * 3600000) {
console.log(name, "potential error @", i, i + 1);
errorFlag = true;
}
if (!(isMeeting && person.is_optional[i] && person.is_optional[i+1])) {
totalMs += diffMs;
}
}
}
if (!isMeeting) {
extraHours = person.extra_hours;
}
const hours = (totalMs / 3600000) + extraHours;
if (isMeeting) {
//meeting object
const startOfLastMeetingDate = dayjs.tz(dayjs(history[history.length - 2]));
const endOfLastMeetingDate = dayjs.tz(dayjs(history[history.length - 1]));
let meetingTitle = startOfLastMeetingDate.format('h:mm A') + ' - ' + endOfLastMeetingDate.format('h:mm A');
if (person.is_optional[person.is_optional.length - 1] && person.is_optional[person.is_optional.length - 2]) {
meetingTitle += " Optional"
}
meeting = { name, hours,
title: meetingTitle};
}
else {
const displayHours = hours + (errorFlag ? "*" : "");
//person object
people.push({ name, hours: displayHours, signedIn: history.length > 0 && history[history.length - 1] === null });
}
}
return { people, meeting };
}
catch (err) {
console.error(err);
return { people: [], meeting: { name: "error", hours: -1 } };
}
}
//Hours Post Requests
app.post('/hours/person', async (req, res) => {
try {
if (req.body.name) {
const taskKey = datastore.key(['person', req.body.name]);
const [entity] = await datastore.get(taskKey);
//if signed in
if (entity.history.length > 0 && entity.history[entity.history.length - 1] === null) {
//then signing out
console.log(req.body.name, "signing out");
//get last meeting info
const meetingTaskKey = datastore.key(['person', 'Meeting']);
const [meetingEntity] = await datastore.get(meetingTaskKey);
const endOfLastMeetingDate = dayjs.tz(dayjs(meetingEntity.history[meetingEntity.history.length - 1]));
const startOfLastMeetingDate = dayjs.tz(dayjs(meetingEntity.history[meetingEntity.history.length - 2]));
const startOfPersonShift = dayjs.tz(dayjs(entity.history[entity.history.length - 2]));
const currentDate = dayjs.tz(dayjs());
console.log("current date: " + currentDate.format());
console.log("end of last meeting: " + endOfLastMeetingDate.format());
console.log("start of last meeting: " + startOfLastMeetingDate.format());
console.log("start of person's shift: " + startOfPersonShift.format());
if (((currentDate.diff(endOfLastMeetingDate)) < (15*60000)) && !(startOfLastMeetingDate.isAfter(startOfPersonShift))) {
entity.history[entity.history.length - 1] = dayjs.tz(roundToNearest15Minutes(dayjs())).format();
console.log(req.body.name, "signed out successfully");
} else {
entity.history[entity.history.length - 1] = entity.history[entity.history.length - 2];
console.log(req.body.name, "signed out | time voided due to overrun");
}
}
else {
console.log(req.body.name, "signed in");
//else signing in
entity.history.push(dayjs.tz(roundToNearest15Minutes(dayjs())).format());
entity.history.push(null);
}
await datastore.update({ key: taskKey, data: entity });
res.status(200).json({ success: true });
return;
}
else {
res.status(400).json({ success: false, error: "missing name" });
return;
}
}
catch (err) {
console.error(err);
res.status(500).json({ success: false, error: err });
}
});
app.post('/hours/meeting', async (req, res) => {
try {
const startDate = roundToNearest15Minutes(dayjs(req.body.startDate));
const endDate = roundToNearest15Minutes(dayjs(req.body.endDate));
const isOptionalMeeting = req.body.isOptionalMeeting;
if (startDate.isValid() && endDate.isValid() && startDate < endDate) {
const taskKey = datastore.key(['person', 'Meeting']);
const [entity] = await datastore.get(taskKey);
entity.history.push(startDate.format());
entity.history.push(endDate.format());
entity.is_optional.push(isOptionalMeeting);
entity.is_optional.push(isOptionalMeeting);
console.log("created new meeting from ", startDate.format("h:m A"), " to ", endDate.format("h:m A"));
await datastore.update({ key: taskKey, data: entity });
res.status(200).json({ success: true });;
return;
}
else {
res.status(400).json({ success: false, error: "invalid dates" });
return;
}
}
catch (err) {
console.error(err);
res.status(500).json({ success: false, error: err });
}
});
//Serve Page
app.get(Object.keys(pages), async (req, res) => {
const page = pages[req.path];
const injection = page[1] ? await page[1]() : {};
injection.date = dayjs.tz(dayjs()).format("M/D/YYYY");
res.render('pages/' + page[0], injection);
});
app.get('/_ah/warmup', (req, res) => {
res.json({ok: true});
});
app.get('*', (req, res) => {
res.render('pages/404.html');
});
app.listen(port, () => {
console.log(`App listening on port ${port}`);
});