-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathserver.js
570 lines (491 loc) · 21.4 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
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
require('dotenv').config();
const express = require('express');
const bodyParser = require('body-parser');
const { summary } = require('date-streaks');
const format = require('pg-format');
const app = express();
const Pusher = require('pusher');
const pgp = require('pg-promise')();
const db = pgp({
host: process.env.DB_HOST,
port: 5432,
database: process.env.DB_NAME,
user: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD
});
//Pusher
const pusher = new Pusher({
appId: '826493',
key: 'afc88b08b01a286ce8f6',
secret: process.env.PUSHER_SECRET,
cluster: 'us3',
encrypted: true
});
// passport
const passport = require('passport');
const cookieParser = require('cookie-parser');
const LocalStrategy = require('passport-local').Strategy;
// const SlackStrategy = require('passport-slack').Strategy;
const connectEnsureLogin = require('connect-ensure-login');
const bcrypt = require('bcrypt');
const SALT_ROUNDS = 12;
//managing session cookies
const cookieExpirationDate = new Date();
const cookieExpirationDays = 30;
cookieExpirationDate.setDate(cookieExpirationDate.getDate() + cookieExpirationDays);
app.use(bodyParser.json());
app.use('/static', express.static('static'));
app.set('view engine', 'hbs');
app.use(cookieParser());
app.use(require('express-session')({
secret: process.env.EXPRESS_SESSION_STRING, // used to generate session ids
resave: false,
saveUninitialized: false
}));
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(userId, done) {
// db call to get user
getUserByID(userId)
.then(user => {
done(null, user);
})
});
//
app.use(passport.initialize());
app.use(passport.session());
app.get('/api/auth/account', requireUser, (req, res) => {
res.json({ user_id: req.user.id, username: req.user.username, email: req.user.email });
})
app.post('/logout', function (req, res){
req.session.destroy(function (err) {
res.json('logged out'); //Inside a callback… bulletproof!
});
});
function requireUser (req, res, next) {
if (!req.user) {
res.status(401).send('Not Authenticated')
} else {
next();
}
};
// helper function for Passport but should i just be using app.get(/user/:id)
function getUserByUsername(username) {
return db.one(
'SELECT id, username, email, password FROM users WHERE username=$1', [username]
)
.catch((error) => {
console.log('failed to get user', error);
});
}
// helper function for Passport but should i just be using app.get(/user/:id)
function getUserByID(id) {
return db.one(
'SELECT id, username, email, password FROM users WHERE id=$1', [id]
)
.catch((error) => {
console.log('failed to get user', error);
});
}
passport.use(new LocalStrategy(
async (username, password, done) => {
// get user.password with username in the db
const user = await getUserByUsername(username);
if (!user) {
return done(null, false);
}
// if passwords match make passwordMatches true else run await bcrypt compare
function testPassword(test_password, db_password) {
if (db_password == test_password) {
// would this make passwordMatches truthy?
return true;
} else {
return bcrypt.compare(test_password, db_password)
}
}
const passwordMatches = await testPassword(password, user.password)
if (passwordMatches) {
done(null, user);
} else {
done(null, false);
}
}
));
// PASSPORT route to accept logins
app.post('/api/login', passport.authenticate('local', { failureRedirect: '/login' }), (req, res) => {
res.json({ user_id: req.user.id, username: req.user.username, email: req.user.email });
});
// changing state even when not successful - need to add a catch
// create new users
app.post('/api/users/create', function(req, res){
const {first_name, last_name, username, email, password, type} = req.body
bcrypt.genSalt(SALT_ROUNDS)
.then( salt => {
return bcrypt.hash(password, salt);
})
.then( hashedPassword => {
db.one("INSERT INTO users (first_name, last_name, username, email, password, type, creation_date) VALUES ($1, $2, $3, $4, $5, $6, CURRENT_TIMESTAMP) RETURNING id, first_name, last_name, username, email, password, type", [first_name, last_name, username, email, hashedPassword, type])
.then((data) => {
res.json(data)
res.status(200).send({update: "success"});
})
})
.catch(error => {
res.json({
error: error.message
});
});
})
// get all objectives
app.get('/api/objectives', function(req, res){
db.any('SELECT * FROM objectives')
.then(data => res.json(data))
.catch(error => res.json({ error: error.message }));
});
// get all organizations
app.get('/api/organizations', function(req, res){
db.any('SELECT * FROM organizations')
.then(data => res.json(data))
.catch(error => res.json({ error: error.message }));
});
// get all courses
app.get('/api/courses', function(req, res){
db.any('SELECT * FROM courses')
.then(data => res.json(data))
.catch(error => res.json({ error: error.message }));
});
// get objectives for given course
app.get('/api/courses/:id/objectives', function(req, res){
const { id } = req.params;
db.any('SELECT objectives.id AS objective_id, objectives.number, objectives.objective, objectives.lesson_id, lessons.course_id FROM objectives, lessons WHERE objectives.lesson_id = lessons.id AND lessons.course_id=$1', [id])
.then(data => res.json(data))
.catch(error => res.json({ error: error.message }));
});
// get all lessons
app.get('/api/lessons', function(req, res){
db.any('SELECT * FROM lessons')
.then(data => res.json(data))
.catch(error => res.json({ error: error.message }));
});
// get all users
app.get('/api/users', function(req, res){
db.any('SELECT * FROM users')
.then(data => res.json(data))
.catch(error => res.json({ error: error.message }));
});
// retrieve User by id
app.get('/api/users/:username', (req, res) => {
const { username } = req.params;
return db
.one('SELECT id, first_name, last_name, photo, username, email, password, tel, bio, location, creation_date FROM users WHERE username=$1', [username])
.then(data => res.json(data))
.catch(error => res.json({ error: error.message }));
});
// get all activities
app.get('/api/activities', function(req, res){
db.any('SELECT * FROM activities')
.then(data => res.json(data))
.catch(error => res.json({ error: error.message }));
});
// get objectives from activities for a given User Id
app.get('/api/users/:id/objectives', (req, res) => {
const { id } = req.params;
return db
.any('SELECT objectives.id AS objective_id, objectives.number, objectives.objective, objectives.url, objectives.lesson_id, objectives.mastery_score, objectives.favicon, activities.id AS activity_id, activities.complete, activities.completion_time, activities.user_id, users.first_name, users.last_name, users.photo, users.username FROM objectives, activities, users WHERE activities.objective_id = objectives.id AND activities.user_id = users.id AND activities.user_id=$1', [id])
.then(data => {
res.json(data)
})
.catch(error => res.json({ error: error.message }));
});
// get all completed activties after a given time
app.get('/api/activties/since/:earliestCompletionTime', (req, res) => {
const { earliestCompletionTime } = req.params;
return db
.any('SELECT activities.user_id, activities.id AS activity_id, activities.objective_id, activities.lesson_id, activities.course_id, activities.complete, activities.completion_time, users.username, users.photo, users.first_name, users.last_name, users.location, objectives.mastery_score FROM activities, users, objectives WHERE activities.complete = true AND activities.user_id = users.id AND objectives.id = activities.objective_id')
.then(data => {
const timeFilteredObjectives = data.filter(activity => {
const objectiveTimes = Date.parse(new Date(activity.completion_time))
if (objectiveTimes >= earliestCompletionTime) {
return true
} else {
return false
}
})
res.json(timeFilteredObjectives);
})
.catch(error => res.json({ error: error.message }));
})
// get course from activities info for a given User Id
app.get('/api/users/:id/courses', (req, res) => {
const { id } = req.params;
return db
.any('SELECT courses.id AS course_id, courses.name, courses.url, courses.badge, activities.id AS activity_id, activities.complete, activities.completion_time, activities.user_id, activities.created_at, users.username FROM courses, activities, users WHERE activities.course_id = courses.id AND activities.user_id = users.id AND activities.user_id=$1', [id])
.then(data => {
res.json(data)
})
.catch(error => res.json({ error: error.message }));
});
// get all previous objectives and activity info for a given User Id and last objective time
app.get('/api/users/:id/objectives/complete/:lastestStatusTime', (req, res) => {
const { id, lastestStatusTime } = req.params;
return db
.any('SELECT objectives.id AS objective_id, objectives.number, objectives.objective, objectives.url, objectives.lesson_id, objectives.mastery_score, activities.id AS activity_id, activities.complete, activities.completion_time, activities.user_id, users.first_name, users.last_name, users.photo, users.username FROM objectives, activities, users WHERE activities.objective_id = objectives.id AND activities.user_id = users.id AND activities.user_id=$1 AND activities.complete = true ORDER BY activities.completion_time ASC', [id])
.then(data => {
// 1. take the status user id and pull out all their completed objectives
// 2. filter those objectives for anything before the last status completed timeout
// 3. Once you have this array, find the objective score by finding the length of the array
// 4. find the mastery score for that user at that point in time by reducing over the objectives.mastery_score
const previousCompletedObjectives = data.filter(status => {
const previousObjectiveTimes = new Date(status.completion_time)
if (Date.parse(previousObjectiveTimes) <= lastestStatusTime) {
return true
} else {
return false
}
})
const dates = previousCompletedObjectives.map(objective => (
objective.completion_time
))
const statusStreakSummary = summary({ dates });
const statusObjectivesScore = previousCompletedObjectives.length
const statusMasteryScore = previousCompletedObjectives.reduce(function(acc, cur) {
return acc + cur.mastery_score
}, 0);
res.json({ mastery: statusMasteryScore, streak: statusStreakSummary.currentStreak, objectives: statusObjectivesScore });
})
.catch(error => res.json({ error: error.message }));
});
//get objective info from activity id
function getObjectivesByActivityId(id) {
return db.any('SELECT objectives.id AS objective_id, objectives.number, objectives.objective, objectives.url, objectives.lesson_id, objectives.mastery_score, activities.id AS activity_id, activities.complete, activities.completion_time, activities.user_id, users.first_name, users.last_name, users.photo, users.username FROM activities, objectives, users WHERE activities.objective_id = objectives.id AND activities.user_id = users.id AND activities.id=$1', [id])
.catch((error) => {
console.log('failed to get objectives', error);
});
}
// update completed activities
app.patch('/api/activities/:activityId', (req, res) => {
const activityId = req.params.activityId
const {complete} = req.body
db.one(`UPDATE activities SET complete = $1, completion_time = CURRENT_TIMESTAMP WHERE id = $2 RETURNING id AS activity_id, complete, completion_time`, [complete, activityId])
.then(async data => {
console.log('data', data);
const objectiveInfo = await getObjectivesByActivityId(data.activity_id)
const activityUpdateData = [data, objectiveInfo]
pusher.trigger('activityUpdate', 'activityComplete', {
"message": activityUpdateData
});
})
.catch(error => {
res.json({
error: error.message
});
});
})
app.get('/api/users/:id/scores', (req, res) => {
const { id } = req.params;
db.one('SELECT scores.id AS score_id, scores.user_id, scores.mastery, scores.streak, scores.objective_count FROM scores WHERE user_id=$1', [id])
.then(data => {
res.json(data)
})
.catch(error => res.json({ error: error.message }));
})
// if change to desc then need to update status lastest objective completion_time
app.get('/api/activities/objectives/complete', (req, res) => {
db.any('SELECT objectives.id AS objective_id, objectives.number, objectives.objective, objectives.url, objectives.lesson_id, objectives.mastery_score, activities.id AS activity_id, activities.complete, activities.completion_time, activities.user_id, users.first_name, users.last_name, users.photo, users.username FROM objectives, activities, users WHERE activities.objective_id = objectives.id AND activities.user_id = users.id AND activities.complete = true ORDER BY activities.completion_time DESC')
.then(data => {
res.json(data)
})
.catch(error => res.json({ error: error.message }));
})
//get all objectives for a course helper method
function getObjectivesByCourseId(id) {
return db.any(
'SELECT objectives.id AS objective_id, objectives.number AS objective_number, objectives.lesson_id, lessons.id AS lesson_id, lessons.course_id FROM objectives, lessons, courses WHERE objectives.lesson_id = lessons.id AND lessons.course_id = courses.id AND courses.id = $1', [id]
)
.catch((error) => {
console.log('failed to get user', error);
});
}
const newActivities = async (userId, courseId, selectiveObjectiveNumber) => {
const allCourseObjectives = await getObjectivesByCourseId(courseId)
//for any objective where objective.number < slectedobjective.number then
// completed is true and completed time is now
const isoDateNow = new Date().toISOString()
const objectiveActivities = allCourseObjectives.map(function(objective) {
if(objective.objective_number < selectiveObjectiveNumber){
return ['objective', objective.objective_id, null, null, userId, true, isoDateNow, isoDateNow]
} else {
return ['objective', objective.objective_id, null, null, userId, false, null, isoDateNow]
}
})
const courseActivity = [['course', null, null, Number(courseId), userId, false, null, isoDateNow]]
console.log('courseActivity', courseActivity);
const combinedActivites = objectiveActivities.concat(courseActivity)
console.log('combinedActivites', combinedActivites);
return combinedActivites
}
const insertActivities = async (activities) => {
const query1 = format("INSERT INTO activities (type, objective_id, lesson_id, course_id, user_id, complete, completion_time, created_at) VALUES %L RETURNING id, type, objective_id, lesson_id, course_id, user_id, complete, completion_time, created_at", activities)
const {rows} = await db.query(query1);
return rows
}
// create user activities when they enroll in a course
app.post('/api/users/activities/create', async (req, res) => {
// theses aren't match proparly yet
const { currentUser, course, objective } = req.body;
try {
const formattedActivitiesToInsert = await newActivities(currentUser.user_id, course.id, objective.number)
const rows = await insertActivities(formattedActivitiesToInsert)
res.status(200).send({update: "success"});
res.end()
} catch (error) {
res.status(500).send({update: "Activities not created"});
}
})
app.patch('/api/users/:id/update', (req, res) => {
const userId = req.params.id
const {updatedInfo} = req.body
db.one(`UPDATE users SET bio = $2, location = $3, photo = $4 WHERE id = $1 RETURNING *`, [userId, updatedInfo.bio, updatedInfo.location, updatedInfo.profilePicture])
.then((data) => {
res.json(data)
})
.catch(error => {
res.json({
error: error.message
});
});
})
app.get('/api/users/:id/settings', (req, res) => {
const { id } = req.params;
db.one('SELECT users.id AS user_id, users.photo, users.bio, users.location FROM users WHERE id=$1', [id])
.then(data => {
res.json(data)
})
.catch(error => res.json({ error: error.message }));
})
//BROWSER extension
//get all activity data from browser url
function getActivitiesByUrl(tabUrl) {
return db.one('SELECT * FROM objectives WHERE objectives.url=$1', [tabUrl])
.catch((error) => {
console.log('failed to get objective from url', error);
});
}
// get all tags for given objective
function getTagsByObjectiveId(id) {
return db.any('SELECT tags.topic, tags.id AS tag_id, objective_tags.id AS objective_tag_id from tags, objective_tags WHERE tags.id = objective_tags.tag_id AND objective_tags.objective_id=$1', [id])
.catch((error) => {
console.log('failed to get tags', error);
});
}
//get tags for a given objectives
app.get('/api/objective/:id/tags', (req, res) => {
const { id } = req.params;
db.any('SELECT tags.topic, tags.id AS tag_id FROM tags, objective_tags WHERE tags.id = objective_tags.tag_id AND objective_tags.objective_id=$1', [id])
.then(data => {
res.json(data)
})
.catch(error => res.json({ error: error.message }));
})
// get all tags
app.get('/api/tags', (req, res) => {
db.any('SELECT tags.topic, tags.id AS tag_id FROM tags')
.then(data => {
res.json(data)
})
.catch(error => res.json({ error: error.message }));
})
// get all tags from search term
app.get('/api/tags/search', (req, res) => {
console.log('req.query.q', req.query.q);
const searchTerm = req.query.q
db.any('SELECT tags.topic, tags.id AS tag_id FROM tags WHERE tags.topic LIKE $1', [searchTerm])
.then(data => {
res.json(data)
})
.catch(error => res.json({ error: error.message }));
})
//add new tags to db
const createNewTags = async (newTags) => {
const formattedNewTags = newTags.map(tag => (
[tag.topic]
))
console.log('formattedNewTags', formattedNewTags);
const insertTagsQuery = format("INSERT INTO tags (topic) VALUES %L RETURNING id AS tag_id, topic", formattedNewTags)
console.log('new tags - insertTagsQuery', insertTagsQuery);
try {
let returnedTagObjects = await db.query(insertTagsQuery);
console.log('new tags - returnedTagObjects', returnedTagObjects);
return returnedTagObjects
} catch (e) {
console.error(e);
}
}
const formatObjectiveTagsToInsert = (newTagsInDb, existingTags, objective_id) => {
const formattedNewTabs = newTagsInDb.map(tag => ([tag.tag_id, objective_id]))
const formattedOldTabs = existingTags.map(tag => ([tag.tag_id, objective_id]))
const formattedObjectiveTags = formattedNewTabs.concat(formattedOldTabs)
return formattedObjectiveTags
}
//add new objectiveTags to db
const insertObjectiveTags = async (objectiveTags) => {
console.log('insertObjectiveTags - objectiveTags', objectiveTags);
const insertObjectiveTagsQuery = format("INSERT INTO objective_tags (tag_id, objective_id) VALUES %L RETURNING id, tag_id, objective_id", objectiveTags)
console.log('insertObjectiveTagsQuery', insertObjectiveTagsQuery);
const {rows} = await db.query(insertObjectiveTagsQuery);
return rows
}
// add array of new topic tags to topic [{topic: ?, objective_id: ?}, {}]
app.post('/api/tags', async (req, res) => {
const {newTags, objective_id} = req.body
const newTagsArr = newTags.filter(tag => !tag.tag_id)
const existingTags = newTags.filter(tag => !!tag.tag_id)
console.log('newTagsArr', newTagsArr);
console.log('existingTags', existingTags);
try {
const newTagsInDb = await createNewTags(newTagsArr)
const formattedObjectiveTagsToInsert = await formatObjectiveTagsToInsert(newTagsInDb, existingTags, objective_id)
const rows = await insertObjectiveTags(formattedObjectiveTagsToInsert)
res.status(200).send({update: "success"});
res.end()
} catch (error) {
res.status(500).send({update: "Tags not created"});
}
})
//receive current context tab activities and topics
app.get('/api/tabContext', async (req, res) => {
console.log('ext receive', req.params);
console.log('req.query.url', req.query.url);
const tabUrl = req.query.url
const user_id = 57
// i changed this friday 155am
// i need to create a check for both node and objective url matches tab
// they then need seperate logic routes
// or i could just make them both objectives for now and simply add favicons to objectives.
const tabObjective = await getActivitiesByUrl(tabUrl)
console.log('confirmedObjective tabObjective', tabObjective);
db.any('SELECT activities.id AS activity_id, activities.objective_id, objectives.number, objectives.objective, objectives.url, objectives.mastery_score, objectives.lesson_id, activities.complete, activities.completion_time, activities.user_id FROM activities, objectives WHERE objectives.url=$1 AND activities.user_id=$2 AND activities.objective_id=$3', [tabUrl, user_id, tabObjective.id])
.then(async activityData => {
console.log('activityData 1', activityData);
const objective_id = activityData[0].objective_id
const objectiveTags = await getTagsByObjectiveId(objective_id)
console.log('activityData 2', activityData);
console.log('objectiveTags', objectiveTags);
const extData = [activityData, objectiveTags]
console.log('extData', extData);
return res.json(extData)
})
.catch(error => res.json({ error: error.message }));
})
// index route
app.get('/', function(req, res) {
res.render('index');
});
app.get('*', function(req, res) {
res.render('index');
});
const port = process.env.PORT || 9090;
app.listen( port, function(){
console.log(`Listening on port number ${port}`);
});