-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathserver.js
More file actions
846 lines (728 loc) · 30 KB
/
server.js
File metadata and controls
846 lines (728 loc) · 30 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
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
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
var marked = require('marked');
var sqlite3 = require('sqlite3');
var express = require('express');
var session = require('express-session');
var override = require('method-override');
var bodyParser = require('body-parser');
var MongoStore = require('connect-mongo')(session);
var mustache = require('mustache');
var request = require('request');
var fs = require('fs');
var nodemailer = require('nodemailer');
var smtpTransport = require('nodemailer-smtp-transport');
var secrets = require('./secrets.json');
// set up transporter to send email via mandrill's smtp server
var transporter = nodemailer.createTransport(smtpTransport({
host: "smtp.mandrillapp.com",
port: 587,
auth: secrets.mandrill //{user: username, pass: apikey}
}));
var formcss;
fs.readFile("./public/formcss.css", function(err,data) {
if (err) throw(err);
formcss = data.toString();
});
// *********************************** initialization stuff ***********************************
var db = new sqlite3.Database('wiki.db');
var app = express();
app.use(override('_method'));
app.use(session({
store: new MongoStore({
host: '127.0.0.1',
port: 27017,
db: "wiki"
}),
secret: 'a fancy secret',
resave: false,
saveUninitialized: true,
cookie: {
maxAge: 600000
}
}));
app.use(bodyParser.urlencoded({
extended: false
}));
// *********************************** user login routes ***********************************
app.get("/users/new", function (req,res) {
ensureUser(req);
res.render("users/edit.ejs", {usernamefixed: false, formaction: "/users", msg:"", name:"", email:"", username:"", user: req.session.user, css: formcss, title: "new account", editable: false});
});
app.get("/user/:username/edit", function(req,res) {
ensureUser(req);
db.get("SELECT username,name,email FROM users WHERE username = ?", req.params.username, function(err,data) {
if (typeof data === 'undefined') {
res.send("user not found");
} else {
res.render("users/edit.ejs", {usernamefixed: true, formaction: "/user/"+req.params.username+"?_method=PUT", msg:"", name:data.name, email:data.email, username:data.username, user: req.session.user, css: formcss, title: data.username, editable: false});
}
});
});
app.get("/users/login", function (req,res) {
ensureUser(req);
res.render("users/login.ejs", {user: req.session.user, css: formcss, title: "", editable: false});
});
app.post("/users/login", function (req,res) {
ensureUser(req);
db.get("SELECT username FROM users WHERE username = ?", req.body.username, function(err,data) {
if (typeof data === 'undefined') {
req.session.user.loginmessage = "username "+req.body.username+" does not exist in our database";
res.render("users/login.ejs", {user: req.session.user, css: formcss, title: "", editable: false});
} else {
req.session.user.username = req.body.username;
res.redirect(req.session.user.curpage);
}
});
});
app.get("/users/logout", function (req,res) {
ensureUser(req);
req.session.user.username = "guest";
res.redirect(req.session.user.curpage);
});
// receive user update
app.put("/user/:username", function(req,res) {
ensureUser(req);
db.run("UPDATE users SET name=?, email=? WHERE username=?",
req.body.name, req.body.email, req.params.username,
function(err) {
if (err) throw(err);
res.redirect("/user/"+req.params.username);
}
);
});
// receive new user form post
app.post("/users", function (req,res) {
ensureUser(req);
db.get("SELECT username FROM users WHERE username = ?",req.body.username, function(err,data) {
// if the chosen username is taken or is "guest" then bounce user back to form
if (typeof data !== 'undefined' || req.body.username === "guest") {
res.redirect("users/edit.ejs", {formaction: "/users", msg:"that username is taken", name:req.body.name, email:req.body.email, username:req.body.username, user: req.session.user, css: formcss, title: "new account", editable: false});
// else, chosen username isn't taken so insert new user into db
} else {
db.run("INSERT INTO users (name,email,username) VALUES (?,?,?)", req.body.name, req.body.email, req.body.username,
function(err,data) {
req.session.user.username = req.body.username;
res.redirect(req.session.user.curpage);
}
);
}
});
});
// *********************************** user profile routes ***********************************
app.get("/user/:username", function(req,res) {
ensureUser(req);
db.get("SELECT username,name,email FROM users WHERE username = ?",req.params.username, function(err,userdata) {
if (typeof userdata === 'undefined') {
res.send("user not found");
} else {
db.all("SELECT docid,title,max(version) FROM versionedDocs WHERE docid IN "+
"(SELECT DISTINCT docid FROM versionedDocs WHERE userid = ?) "+
"GROUP BY docid", req.params.username, function(err,data) {
var titles = data.map(function(row) {return row.title;});
db.all("SELECT subscriptions.docid as docid, title "+
"FROM subscriptions "+
"JOIN docs ON subscriptions.docid = docs.docid "+
"JOIN versionedDocs ON docs.docid = versionedDocs.docid AND docs.version = versionedDocs.version "+
"WHERE subscriptions.username = ?", req.params.username,
function(err,subdata) {
res.render("users/show.ejs", {profile: userdata, titles: titles, subscriptions: subdata, user: req.session.user, css: formcss, title: req.params.username + "'s Profile", editable: false});
}
);
}
);
}
});
});
// *********************************** user subscriptions ***********************************
app.post("/doc/:docid/subscribe", function(req,res) {
ensureUser(req);
// if user is a guest, send them to login before they subscribe
if (req.session.user.username === "guest") {
req.session.user.prevpage = req.session.user.curpage;
req.session.user.curpage = "/doc/"+req.params.docid+"/subscribe";
req.session.user.loginmessage = "login to subscribe";
res.redirect("/users/login");
// else, user is logged in, so proceed with doc creation
} else {
db.run("INSERT INTO subscriptions (username, docid) VALUES (?,?)", req.session.user.username, req.params.docid, function(err) {
if (err) throw(err);
db.get("SELECT title FROM versionedDocs "+
"JOIN docs ON docs.docid = versionedDocs.docid AND docs.version = versionedDocs.version "+
"WHERE docs.docid = ?", req.params.docid, function(err,data) {
if (err) throw(err);
if (typeof data === 'undefined') res.send("doc not found");
else res.redirect("/doc/"+data.title);
}
);
});
}
});
app.delete("/subscription/:username/:docid", function(req,res) {
ensureUser(req);
db.run("DELETE FROM subscriptions WHERE username = ? AND docid = ?", req.params.username, req.params.docid, function(err) {
if (err) throw(err);
res.redirect("/user/"+req.params.username);
});
});
// *********************************** doc reading routes ***********************************
// retrieve index of docs
app.get("/", function (req,res) {
ensureUser(req);
res.redirect("/doc/main");
});
app.get("/docs", function (req,res) {
ensureUser(req);
res.redirect("/doc/main");
});
// search for doc
app.get("/docs/search", function(req,res) {
ensureUser(req);
if (typeof req.query.search === 'undefined' || req.query.search.length === 0) {
// if searchstring is empty then redirect to main
res.redirect("/doc/main");
} else {
// we have a search string, so find all matching current titles in versionedDocs
db.all("SELECT title FROM versionedDocs JOIN docs ON docs.docid = versionedDocs.docid AND docs.version = versionedDocs.version "+
"WHERE title LIKE ?", "%"+req.query.search+"%", function(err,titledata) {
if (err) throw(err);
// also search for matching document bodies
db.all("SELECT title FROM versionedDocs "+
"JOIN docs ON docs.docid = versionedDocs.docid AND docs.version = versionedDocs.version "+
"WHERE body LIKE ?", "%"+req.query.search+"%", function(err,bodydata) {
if (err) throw(err);
//render our results
res.render("docs/searchresults.ejs", {
titles: titledata,
bodies: bodydata,
title: "Search Results",
css: formcss,
user: req.session.user,
editable: false
});
}
);
}
);
}
});
// retrieve doc by title
app.get("/doc/:title", function (req,res) {
ensureUser(req);
// grab current version of doc as indicated by docs table
db.get( "SELECT title,body,html,css,numpanes FROM"+
" versionedDocs"+
" JOIN docs ON docs.docid = versionedDocs.docid AND docs.version = versionedDocs.version"+
" JOIN layouts ON versionedDocs.layout = layouts.name"+
" WHERE title = ?",
req.params.title,
function(err,data) {
if (err) throw(err);
// if we didn't find any doc by that title then go to a new document form
if (typeof data === 'undefined') {
// if user is a guest, send them to the login page before we allow them to create a doc
if (req.session.user.username === "guest") {
req.session.user.prevpage = req.session.user.curpage;
req.session.user.curpage = "/doc/"+req.params.title;
req.session.user.loginmessage = req.params.title + " not found. Login to create a new doc";
res.redirect("/users/login");
// user is logged in, so proceed with doc creation
} else {
db.all("SELECT name,numpanes FROM layouts", function (err,data) {
if (err) throw(err);
res.render("docs/edit.ejs", {
formaction: "/docs",
docid:0,
title: req.params.title,
body: '[""]',
comment: "created",
layoutname: data[0].name,
numpanes: data[0].numpanes,
user: req.session.user,
layouts: data,
css: formcss,
editable: false
});
});
}
// we found our doc
} else {
ensureUser(req);
req.session.user.curpage = "/doc/"+req.params.title;
// swap out [[ ]] tags for their counterparts from our keywords function
parseTagsInArray(JSON.parse(data.body), keywords, function(arr) {
// parse the document body into content panes
var contents = {title: req.params.title};
for (var i=0; i<arr.length; i++) {
contents["content"+i] = marked(arr[i]);
}
// render
res.render("docs/show.ejs",{
title: data.title,
content: mustache.render(data.html,contents),
css:data.css,
user: req.session.user,
editable: true});
});
}
});
});
// *********************************** doc edit routes ***********************************
// retrieve new doc form. uses same form as editpage
app.get("/docs/new", function (req,res) {
ensureUser(req);
// if user is a guest, send them to the login page before we allow them to create a doc
if (req.session.user.username === "guest") {
req.session.user.prevpage = req.session.user.curpage;
req.session.user.curpage = "/docs/new";
req.session.user.loginmessage = "login to create a new doc";
res.redirect("/users/login");
// user is logged in, so proceed with doc creation
} else {
db.all("SELECT name,numpanes FROM layouts", function (err,data) {
if (err) throw(err);
res.render("docs/edit.ejs", {
formaction: "/docs",
docid:0,
title: "untitled",
body: '[""]',
comment: "created",
layoutname: data[0].name,
numpanes: data[0].numpanes,
user: req.session.user,
layouts: data,
css: formcss,
editable: false
});
});
}
});
// retrieve edit page for doc
app.get("/doc/:title/edit", function (req,res) {
ensureUser(req);
// if user is a guest, send them to the login page before we allow them to edit a doc
if (req.session.user.username === "guest") {
req.session.user.prevpage = "/doc/"+req.params.title;
req.session.user.curpage = "/doc/"+req.params.title+"/edit";
req.session.user.loginmessage = "login to edit "+req.params.title;
res.redirect("/users/login");
// user is logged in, so proceed with doc editing
} else {
db.get( "SELECT title,body,docs.docid,numpanes,name "+
"FROM versionedDocs "+
"JOIN docs ON docs.docid = versionedDocs.docid AND docs.version = versionedDocs.version "+
"JOIN layouts ON versionedDocs.layout = layouts.name "+
"WHERE title = ?",
req.params.title,
function(err,data) {
if (err) throw(err);
if (typeof data === "undefined") {
res.send(req.params.title + " not found.");
} else {
db.all("SELECT name,numpanes FROM layouts", function(err,layouts) {
ensureUser(req);
res.render("docs/edit.ejs",{
formaction: "/doc/"+data.docid,
docid: data.docid,
title: data.title,
body: data.body,
comment: "updated",
layoutname: data.name,
numpanes: data.numpanes,
user: req.session.user,
layouts: layouts,
css: formcss,
editable: false});
});
}
}
);
}
});
// update doc docid.
app.post("/doc/:docid", function (req,res) {
ensureUser(req);
// get highest numbered version so we can increment by one
db.get("SELECT max(version),title FROM versionedDocs WHERE docid = ?", req.params.docid, function (err,data) {
if (err) throw(err);
// if we failed to get doc, send error message
if (typeof data === "undefined") {
res.send("oops! couldn't find the doc to update");
// else, we got our version, so increment it by one for our new version
} else {
version = data["max(version)"] + 1;
// collect all our paned content in an array
var content=[];
for (var i=0; i<req.body.numpanes; i++) {
content.push(req.body["content"+i]);
}
// block renaming of the main page
if (data.title === "main") req.body.title = data.title;
// perform db insert of our newest version to versionedDocs
db.run("INSERT INTO versionedDocs (docid,title,layout,body,version,userid,changed,comment) VALUES (?,?,?,?,?,?,strftime('%s','now'),?)",
req.params.docid, req.body.title, req.body.layout, JSON.stringify(content), version, req.session.user.username, req.body.comment,
function (err) {
if (err) throw(err);
// update docs to point to our new version
db.run("UPDATE docs SET version = ? WHERE docid = ?", version, req.params.docid, function (err) {
if (err) throw(err);
// redirect to the new document
res.redirect("/doc/"+req.body.title);
// notify subscribers of the change
notifySubscribers(req.params.docid, req.body.title, req.body.comment, req.session.user.username);
});
}
);
}
});
});
// delete doc by title
app.delete("/doc/:docid", function(req,res) {
ensureUser(req);
db.run("DELETE FROM docs WHERE docid = ?", req.params.docid, function(err) {
if (err) throw(err);
res.redirect("/doc/main");
});
});
// add new doc
app.post("/docs", function (req,res) {
ensureUser(req);
var content = [];
for (var i=0;i<req.body.numpanes; i++) {
content.push(req.body["content"+i]);
}
db.run("INSERT INTO docs (version, docid) SELECT 1, max(docid)+1 FROM versionedDocs", function (err) {
if (err) throw(err);
db.run("INSERT INTO versionedDocs (docid, title, layout, body, version, userid, changed, comment) VALUES (?,?,?,?,?,?,strftime('%s','now'),?)",
this.lastID, req.body.title, req.body.layout, JSON.stringify(content), 1, req.session.user.username, req.body.comment,
function (err) {
if (err) throw(err);
res.redirect("/doc/"+req.body.title);
});
});
});
// *********************************** doc history routes ***********************************
app.get("/doc/:docid/history", function (req,res) {
ensureUser(req);
db.all("SELECT title,userid,datetime(changed,'unixepoch') as time,comment,docid,version FROM versionedDocs WHERE docid = ?", req.params.docid, function(err,data) {
if (err) throw(err);
if (data.length === 0) {
res.send("doc not found");
} else {
res.render("docs/history.ejs", {
title: "History",
history: data,
user: req.session.user,
css: formcss,
editable: false}
);
}
});
});
//docid INTEGER, title TEXT, layout TEXT, body TEXT, version INTEGER, userid INTEGER, changed INTEGER, comment TEXT
app.post("/doc/:docid/revert/:version", function(req,res) {
ensureUser(req);
db.get("SELECT max(version) as version FROM versionedDocs WHERE docid = ?", req.params.docid, function(err,versiondata) {
if (err) throw(err);
var version = Number(versiondata.version) + 1;
db.run("INSERT INTO versionedDocs (docid, title, layout, body, version, userid, changed, comment) "+
"SELECT docid, title, layout, body, ?, userid, strftime('%s','now'), ? "+
"FROM versionedDocs WHERE docid = ? AND version = ?",
version, "reverted to version "+req.params.version, req.params.docid, req.params.version, function(err) {
if (err) throw(err);
db.get("SELECT max(version) as version, title FROM versionedDocs WHERE docid = ?", req.params.docid, function(err,data) {
if (err) throw(err);
db.run("UPDATE docs SET version = ? WHERE docid = ?", data.version, req.params.docid, function(err) {
if (err) throw(err);
res.redirect("/doc/"+data.title);
});
});
});
});
});
// *********************************** layout edit routes ***********************************
app.get("/layouts/new", function(req,res) {
ensureUser(req);
// if user is a guest, send them to the login page before we allow them to make a new layout
if (req.session.user.username === "guest") {
req.session.user.prevpage = req.session.user.curpage;
req.session.user.curpage = "/layouts/new";
req.session.user.loginmessage = "login to create new layout";
res.redirect("/users/login");
// user is logged in, so proceed with layout making
} else {
res.render("layouts/edit.ejs", {msg: "", name:"", formaction:"/layouts", numpanes:1, html:"", cssdata: "", css: formcss, user: req.session.user, title: "new layout", editable: false});
}
});
app.get("/layout/:name/edit", function (req,res) {
ensureUser(req);
db.get("SELECT name,numpanes,html,css FROM layouts WHERE name = ?", req.params.name, function(err,data) {
if (err) throw(err);
res.render("layouts/edit.ejs", {msg: "", name:data.name, formaction:"/layout/"+data.name+"?_method=PUT", numpanes:data.numpanes, html:data.html, cssdata: data.css, css: formcss, user: req.session.user, title: "Layout "+req.params.name, editable: false});
});
});
app.put("/layout/:name", function(req,res) {
ensureUser(req);
if (req.params.name === "plain" && req.body.name !== "plain") {
// if somebody tries to rename "plain" layout bounce them back to the edit form
res.render("layouts/edit.ejs", {msg: "Can't rename \"plain\" layout.", name:"plain", formaction:"/layout/"+req.params.name+"?_method=PUT", numpanes:req.body.numpanes, html:req.body.html, cssdata:req.body.css, css: formcss, user: req.session.user, title: "", editable: false});
} else {
if (req.params.name !== req.body.name) { //we are trying to rename a layout, so make sure it isn't in use by somebody else
db.get("SELECT name FROM layouts WHERE name = ?", req.body.name, function(err,data) {
if (err) throw(err);
if (typeof data === 'undefined') {
//name isn't in use so go ahead and update
doupdate();
} else {
//name is in use, so go back to the edit form
res.render(
"layouts/edit.ejs",
{msg: "That name is in use by another layout", name:req.body.name, formaction:"/layout/"+req.params.name+"?_method=PUT", numpanes:req.body.numpanes, html:req.body.html, cssdata:req.body.css, css: formcss, user: req.session.user, title: "", editable: false}
);
}
});
} else {
doupdate();
}
function doupdate() {
db.run("UPDATE layouts SET name=?, html=?, numpanes=?, css=? WHERE name=?",
req.body.name, req.body.html, req.body.numpanes, req.body.css, req.params.name,
function(err) {
if (err) throw(err);
res.redirect("/doc/main");
}
);
}
}
});
// create new layout
app.post("/layouts", function(req,res) {
ensureUser(req);
//check if layout name is taken
db.get("SELECT name FROM layouts WHERE name = ?", req.body.name, function(err,data) {
if (err) throw(err);
//if name is taken then bounce back to the form
if (typeof data !== 'undefined') {
res.render("layouts/edit.ejs", {msg: req.body.name+" is already taken", name: req.body.name, formaction:"/layouts", numpanes:req.body.numpanes, html:req.body.html, cssdata:req.body.css, css: formcss, user: req.session.user, title: "", editable: false});
// name is free, so insert into db
} else {
db.run("INSERT INTO layouts (name,html,numpanes,css) VALUES (?,?,?,?)",
req.body.name, req.body.html, req.body.numpanes, req.body.css,
function(err) {
if (err) throw(err);
res.redirect(req.session.user.curpage);
}
);
}
});
});
// *********************************** validation and ajax routes ***********************************
// is doc title available?
app.post("/titleIsAvailable", function (req,res) {
ensureUser(req);
db.get("SELECT docs.docid FROM versionedDocs "+
"JOIN docs ON docs.docid = versionedDocs.docid AND docs.version = versionedDocs.version "+
"WHERE title = ? AND docs.docid != ?", req.body.title, req.body.docid, function (err,data) {
if (typeof data === "undefined") {
res.send(true);
} else {
res.send(false);
}
});
});
// is username available?
// app.post("/usernameIsAvailable", function (req,res) {
// db.get("SELECT username FROM users", function (err,data) {
// if (typeof data === "undefined") {
// res.send(true);
// } else {
// res.send(false);
// }
// });
// });
// *********************************** server start ***********************************
app.listen(3000, function() {console.log("listening to port 3000");});
// *********************************** utility functions ***********************************
function notifySubscribers(docid, title, comment, username) {
db.all("SELECT email FROM subscriptions JOIN users ON subscriptions.username = users.username WHERE docid = ?", docid, function(err,data) {
if (err) throw(err);
if (data.length > 0) {
console.log(data);
// create mailOptions object to send to our subscribers
var mailOptions = {
from: "projectWIKI@evangriffiths.nyc",
to: data.map(function(row) {return row.email;}).join(", "),
subject: "projectWIKI: "+title + " updated",
text: username + " updated " + title + "\n"+comment
};
// send the message with our transporter
transporter.sendMail(mailOptions, function(err,info) {
if (err) {
console.log("sendMail error: "+err);
} else {
console.log("sent: "+info.response);
}
});
}
});
}
// call this to set up our session variable to deal with users entering our site on random pages
function ensureUser(req) {
if (!req.session.user) {
req.session.user = {username: "guest", curpage: "/", prevpage: "/", loginmessage: ""};
}
}
// keywords is called by parseTags to perform the actual tag replacement once a tag is located
function keywords(text,next) {
// check if the first char is a !, which means that our tag is a function
if (text.charAt(0) === '!') {
var func = text.substring(1).trim().replace(/\s+/g," ").split(' ');
if (func.length > 0) {
var args = func.slice(1);
// alldocs - display list of links of all current documents
if (func[0] === 'alldocs') {
db.all("SELECT title FROM docs JOIN versionedDocs ON docs.docid = versionedDocs.docid AND docs.version = versionedDocs.version", function (err,data) {
if (err) throw(err);
next(data.map(function(row) {return "- ["+row.title+"]("+row.title+")";}).join('\n'));
});
// recentdocs num - display list of most recently edited documents, limited to num (default 10)
} else if (func[0] === 'recentdocs') {
var limit = args.length>0 ? args[0] : 10;
db.all("SELECT title FROM docs JOIN versionedDocs ON docs.docid = versionedDocs.docid AND docs.version = versionedDocs.version ORDER BY versionedDocs.changed DESC LIMIT ?",limit , function (err,data) {
if (err) throw(err);
next(data.map(function(row) {return "- ["+row.title+"]("+row.title+")";}).join('\n'));
});
// alllayouts - display list of links to edit pages of all the layouts
} else if (func[0] === 'alllayouts') {
db.all("SELECT name FROM layouts", function(err,data){
if (err) throw(err);
next(data.map(function(row) {return "- ["+row.name+"](/layout/"+row.name+"/edit)";}).join('\n'));
});
// instagram tag - display low_resolution version of the first result of an instagram api call with tag
} else if (func[0] === 'instagram' && args.length > 0) {
var url = "https://api.instagram.com/v1/tags/" + args.join('+') + "/media/recent?client_id=" + secrets.instagram.apikey;
try {
request(url, function(err,response,body) {
if (err) {
next("instagram error: "+err);
} else {
var imgurl = "";
body = JSON.parse(body);
for (var i=0; i<body.data.length && imgurl.length===0; i++) {
if (body.data[i].type === "image") imgurl = body.data[i].images.low_resolution.url;
}
if (imgurl === "" ) {
next("instagram no image");
} else {
next("");
}
}
});
// handle unexpected request errors without crashing server
} catch (err) {
next("err thrown: "+err);
}
// referers title - display a list of all documents with [[title]] in their body somewhere
} else if (func[0] === 'referers') {
if (args.length !== 1) {
next("referers takes one argument, a doc title");
} else {
// grab doc list from db
db.all("SELECT title FROM versionedDocs "+
"JOIN docs ON docs.docid = versionedDocs.docid AND docs.version = versionedDocs.version "+
"WHERE body LIKE ?", "%[["+args[0]+"]]%",
function(err,data) {
if (err) throw(err);
// convert titles to markdown links and send them to next
next( data.map(function(row) {return "["+row.title+"](/doc/"+row.title+")";}).join('\n') );
}
);
}
// unknown function
} else {
next("unknown function: "+func);
}
// function tags with empty string inside, so render empty string
} else {
next("");
}
// no ! in front means this is a local document link
} else {
var doctitle = text.trim();
next("["+doctitle+"]("+doctitle+")"); // markdown link to doctitle
}
}
// call parseTags repeatedly and pass an array of the results to next
function parseTagsInArray(arr,callback,next) {
var results = [];
parseit(0);
function parseit(i) {
if (i < arr.length) {
parseTags(arr[i], callback, function(text) {
results.push(text);
parseit(i+1);
});
} else {
next(results);
}
}
}
// replace all occurances of [[text]] in string with the return value from callback(text)
function parseTags(string,callback,next) {
function Tag(start) {
this.start = start;
this.text = "";
this.length = 0;
}
var tags = [];
var state = 0;
var curtagid = -1;
// run through string char by char to find our tags
for (var i=0; i<string.length; i++) {
switch (state) {
case -1: //outside tag and backslashed
state = 0;
break;
case 0: //outside tag
switch (string.charAt(i)) {
case '[':
state = 1;
break;
case '\\':
state = -1;
break;
}
break;
case 1: //halfway inside tag
if (string.charAt(i) === '[') {state = 2; tags.push(new Tag(i-1));}
else {state = 0;}
break;
case 2: //inside tag
if (string.charAt(i) === '\\') {state = 3;}
else if (string.charAt(i) === ']') {state = 4;}
else {tags[tags.length-1].text += string.charAt(i);}
break;
case 3: //inside tag backslashed
state = 2;
tags[tags.length-1].text += string.charAt(i);
break;
case 4: //first exit ]
if (string.charAt(i) === ']') {tags[tags.length-1].length = i-tags[tags.length-1].start+1; state = 0;}
else if (string.charAt(i) === '\\') {tags[tags.length-1].text += string.charAt(i-1); state=3;}
else {tags[tags.length-1].text += string.charAt(i-1)+string.charAt(i); state=2;}
break;
}
}
// now that we have a list of tags, loop through them and get replacements from callback
var lastpos = 0;
innerReplaceTags(tags, -1, 0, string, "", "");
function innerReplaceTags(tags, tagid, lastpos, origstring, curstring, newstring) {
curstring += newstring;
tagid++;
if (tagid < tags.length) {
curstring += origstring.substring(lastpos, tags[tagid].start);
callback(tags[tagid].text, innerReplaceTags.bind(null,tags,tagid,tags[tagid].start+tags[tagid].length,origstring,curstring));
} else {
curstring += string.substring(lastpos);
next(curstring);
}
}
}