-
Notifications
You must be signed in to change notification settings - Fork 0
/
server.js
379 lines (340 loc) · 12.2 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
// server.js
const next = require('next')
const express = require('express')
const session = require('express-session')
const app = express()
//Import express utilities
const bodyParser = require('body-parser')
const multer = require('multer')()
const fs = require('fs')
//Mailing service
const nodemailer = require('nodemailer')
//Image manipulation utilities
const Jimp = require('jimp')
//Utilities
const moment = require('moment')
//Configure next application
const dev = process.env.NODE_ENV !== 'production'
const nextApp = next({ dev })
const handle = nextApp.getRequestHandler()
//Configure firebase utilities
const firebaseAdmin = require('firebase-admin');
const firebaseConfig = {
credential: firebaseAdmin.credential.cert(JSON.parse(Buffer.from(process.env.FIREBASE_SECRET, "base64").toString("ascii"))),
apiKey: process.env.FIREBASE_API_KEY,
authDomain: "portfolio-4b1af.firebaseapp.com",
databaseURL: "https://portfolio-4b1af.firebaseio.com",
projectId: "portfolio-4b1af",
storageBucket: "portfolio-4b1af.appspot.com",
messagingSenderId: "1001483288799",
appId: "1:1001483288799:web:78f381abcf769a1299a96a",
measurementId: "G-4SD67CT2JB"
}
firebaseAdmin.initializeApp(firebaseConfig);
const firebaseStorage = firebaseAdmin.storage();
const db = firebaseAdmin.firestore();
//--------------------------------------------
//Nodemailer configuration
const sendmail = async (contact, content) => {
const testAccount = await nodemailer.createTestAccount();
const transporter = nodemailer.createTransport({
service: "gmail",
port: 587,
secure: false,
auth: {
type: "OAuth2",
user: process.env.GMAIL_USER,
clientId: process.env.GMAIL_CLIENT_ID,
refreshToken: process.env.GMAIL_REFRESH_TOKEN,
accessToken: process.env.GMAIL_ACCESS_TOKEN,
clientSecret: process.env.GMAIL_CLIENT_SECRET
}
});
const info = await transporter.sendMail({
from: contact,
to: process.env.TARGET_EMAIL,
subject: contact,
text: content
});
return info;
}
//--------------------------------------------
// Since app is running on heroku, this is used to force redirect from http to https
// Credits here: https://jaketrent.com/post/https-redirect-node-heroku
if(process.env.NODE_ENV === 'production') {
app.use((req, res, next) => {
if (req.header('x-forwarded-proto') !== 'https') {
res.redirect(`https://${req.header('host')}${req.url}`);
} else {
next();
}
});
}
//--------------------------------------------
//EXPRESS CONFIGURATION
app.set('PORT', process.env.PORT || 3000);
app.use(session({
name: 'portfolio',
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true,
maxAge: 1000*60*60*24*365,
}
}));
app.use(bodyParser.urlencoded({extended: false}))
app.use(bodyParser.json())
//--------------------------------------------
//Custom middleware functions
app.use((req, res, next) => {
if (!req.headers.cookie || !req.session.userid) {
req.session.userid = req.session.id;
}
return next();
})
//--------------------------------------------
//Utility functions
//Upload to GCloud
const errHandle = (err, res) => {
res.json({status: false, msg: err});
}
const validate = (obj) => {
for (val in obj) {
if (!obj[val]) return false;
}
return true;
}
//error handler
const uploadGCloud = async (src, destination) => {
return await firebaseStorage.bucket().upload(src, {
destination: destination,
metadata: {cacheControl: 'public, max-age=31536000'}
});
}
const deleteTemp = (path) => {
fs.unlink(path, err => {
if (err) throw err;
});
}
const uploadPicture = async (file, id, target) => {
return await Jimp.read(file.buffer).then(async (img) => {
await img.resize(516, Jimp.AUTO).quality(80).write(`uploads/temp/${id}.jpg`);
await img.resize(256, 256).quality(50).write(`uploads/temp/thumbs/${id}.jpg`);
}).then(async () => {
//Use firebase to save image to cloud
await Promise.all([
uploadGCloud(`uploads/temp/${id}.jpg`, `${target}/${id}.jpg`),
uploadGCloud(`uploads/temp/thumbs/${id}.jpg`, `${target}/thumbs/${id}.jpg`)
]).then(res => {
//Delete local image after saving to cloud
deleteTemp(`uploads/temp/${id}.jpg`);
deleteTemp(`uploads/temp/thumbs/${id}.jpg`);
});
});
}
//-------------------------------------------------
//Express+NextJS App
nextApp.prepare().then(() => {
app.get('/api/admin/authenticateuser', (req, res) => {
res.json({status: req.session.isLogged || !!req.session.user, msg: ""});
});
app.post('/api/sendmessage', (req, res) => {
if (!validate(req.body)) {
errHandle("Empty fields.", res);
return false;
}
const {contact, content} = req.body;
sendmail(contact, content).then(info => {
if (info.accepted.length) {
res.json({status: true, msg: "Message sent."});
} else {
errHandle("Message not sent. Try again.", res);
}
}).catch((err) => errHandle("Service currently unavailable.", res));
});
app.post('/api/admin/login', (req, res) => {
if (req.body.password===process.env.ADMIN_SECRET) {
req.session.user = process.env.ADMIN_USER;
req.session.isLogged = true;
res.json({status: true, msg: "Login successful."});
} else {
res.json({status: false, msg: "Invalid password."});
}
});
app.all('/api/admin/*', (req, res, next) => {
if (!req.session.user) {
res.json({status: false, msg: "Unauthenticated request."});
return false;
} else {
next();
}
});
app.post('/api/admin/addwork', multer.single("picture"), async (req, res) => {
//Validate request
if (!req.file || !validate(req.body)) {
errHandle("Please fill all the fields.", res);
return false;
}
//Get details from req.body
const {title, materials, price, status, dimension, medium} = req.body;
//Set work ID
const id = `work${Date.now()}`;
//Upload work image to cloud storage
//Use Jimp to manipulate image and save to local dir
await uploadPicture(req.file, id, 'works').then(async () => {
//Save details to database
const docRef = db.collection('works').doc(id);
await docRef.set({
title,
materials: JSON.parse(materials),
price,
status,
dimension,
medium
});
}).then(() => res.json({status: true, msg: 'Work saved succesfully.'})).catch(err => errHandle("Something went wrong with your request.", res));
});
app.post('/api/admin/addblog', multer.array("pictures"), async (req, res) => {
const {title, author, content, textcontent} = req.body;
if (!validate(req.body)) {
errHandle("Please fill the necessary fields.", res);
return false;
}
const id = `post${Date.now()}`;
let err = "";
if (req.files.length) {
for (let i = 0; i < req.files.length; i++) {
await uploadPicture(req.files[i], `${id}[${i}]`, `blogs/${id}`).catch(error => {
if (error) err = "Something went wrong with uploading pictures.";
});
if (err) break;
};
}
if (err) {
errHandle(err, res);
return false;
} else {
const docRef = db.collection('blogs').doc(id);
await docRef.set({
title,
author,
content,
excerpt: textcontent.slice(0, 200),
datePosted: Date.now(),
comments: 0,
likes: 0,
}).then(() => res.json({status: true, msg: "Blog succesfully posted."})).catch(err => errHandle(err, res));
}
});
app.get('/api/getfeatured', async (req, res) => {
const featuredRef = db.collection('works').orderBy("featured", "asc");
const snapshot = await featuredRef.limit(5).get();
let results = [];
snapshot.forEach((work) => {
results.push({...work.data(), id: work.id});
});
res.json({status: true, msg: '', results, length: results.length});
});
app.get('/api/getworks', async (req, res) => {
const worksRef = db.collection('works').orderBy(firebaseAdmin.firestore.FieldPath.documentId());
const snapshot = await worksRef.get();
let results = [];
snapshot.forEach((work) => {;
results.push({...work.data(), id: work.id});
});
res.json({status: true, msg: "", results: results});
});
app.get('/api/getblogs', async(req, res) => {
const blogsRef = db.collection('blogs').orderBy("datePosted", "desc");
const {lastdate} = req.query;
let results = [];
if (lastdate) {
const snapshot = await blogsRef.startAfter(parseInt(lastdate)).limit(12).get();
snapshot.forEach((blog) => results.push({...blog.data(), id: blog.id, commentArray: []}));
res.json({status: true, msg: "", results});
} else {
const snapshot = await blogsRef.limit(12).get();
snapshot.forEach((blog) => results.push({...blog.data(), id: blog.id, commentArray: []}));
res.json({status: true, msg: "", results});
}
});
app.get('/api/viewblog', async(req, res) => {
const {id} = req.query;
const blogRef = db.collection('blogs').doc(id);
const documentRef = await blogRef.get();
const likeRef = await blogRef.collection('likes').doc(req.session.userid).get();
const liked = likeRef.data();
const document = documentRef.data();
res.json({status: true, msg: "", document: {...document, liked: !!liked, id: documentRef.id, commentArray: []}});
});
app.post('/api/fetchcomments', async(req, res) => {
const {id, lastDate} = req.body;
const commentRef = db.collection(`blogs/${id}/comments`).orderBy('datePosted', 'desc');
let results = [];
if (lastDate) {
const comments = await commentRef.startAfter(lastDate).limit(10).get();
comments.forEach(item => results.push(item.data()));
res.json({status: true, msg: '', results});
} else {
const comments = await commentRef.limit(10).get();
comments.forEach(item => results.push(item.data()));
res.json({status: true, msg: '', results});
}
});
app.post('/api/addcomment', async(req, res) => {
const {content, author, id} = req.body;
if (!validate(req.body)) {
errHandle('Please fill the required fields.', res);
return false;
}
const blogRef = db.doc(`blogs/${id}`);
const commentRef = blogRef.collection('comments');
await commentRef.add({
author,
content,
datePosted: Date.now(),
}).then(async () => {
await blogRef.update({
comments: firebaseAdmin.firestore.FieldValue.increment(1)
}).then(() => res.json({status: true, msg: ''}));
}).catch(err => errHandle(err, res));
});
app.post('/api/likeblog', async(req, res) => {
const {id, like, purpose} = req.body;
const blogRef = db.collection('blogs').doc(`${id}`);
const likeRef = blogRef.collection('likes').doc(req.session.userid);
const isLiked = await likeRef.get();
if (purpose==='verify') {
if (isLiked.data()) res.json({status: true, msg: ''});
else errHandle('', res);
} else {
if (isLiked.data()) {
if (like) {
errHandle('Already liked.', res);
return false;
} else {
likeRef.delete().then(async () => {
return await blogRef.update({likes: firebaseAdmin.firestore.FieldValue.increment(-1)});
}).then(() => res.json({status: true, msg: ''})).catch(err => errHandle(err, res));
}
} else {
if (like) {
likeRef.set({status: true}).then(async () => {
return await blogRef.update({likes: firebaseAdmin.firestore.FieldValue.increment(1)});
}).then(() => res.json({status: true, msg: ''})).catch(err => errHandle(err, res));
} else {
errHandle('Something went wrong.', res);
return false;
}
}
}
});
app.all('*', (req, res) => {
return handle(req, res);
});
app.listen(app.get('PORT'), err => {
if (err) throw err
console.log('> Ready on http://localhost:'+app.get('PORT'))
});
})