Personal blog written from scratch using Node.js, Bootstrap, and MySQL. https://jrtechs.net
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

655 lines
17 KiB

4 years ago
4 years ago
4 years ago
  1. /**
  2. * Boated file which handles all the SQL
  3. * queries ran by the server
  4. *
  5. * @author Jeffery Russell
  6. */
  7. const mysql = require('mysql');
  8. /** Sanitizer to clean user inputs and prevent SQL injections */
  9. const sanitizer = require('sanitizer');
  10. /** Crypto package used for hashing */
  11. const crypto = require('crypto');
  12. /** Used to parse post data */
  13. const qs = require('querystring');
  14. /** Used to load the config file from the disk */
  15. const config = require('../utils/configLoader').getConfig();
  16. /** SQL connection */
  17. const con = mysql.createConnection({
  18. host: config.SQL_HOST,
  19. port: config.SQL_PORT,
  20. user: config.SQL_USER,
  21. password: config.SQL_PASSWORD,
  22. database: config.SQL_DATABASE
  23. });
  24. con.connect(function(err) {
  25. if (err)
  26. console.log(err);
  27. });
  28. /**
  29. * Function used to query the database for records
  30. *
  31. * @param sqlStatement
  32. * @returns {Array}
  33. */
  34. const fetch = function(sqlStatement)
  35. {
  36. return new Promise(function(resolve, reject)
  37. {
  38. con.query(sanitizer.sanitize(sqlStatement), function (err, result)
  39. {
  40. if(err)
  41. {
  42. console.log(err);
  43. reject(err);
  44. }
  45. resolve(result);
  46. });
  47. });
  48. };
  49. /**
  50. * Function used to use insert statements into the database
  51. *
  52. * Don't worry, the input gets sanitized
  53. *
  54. * @param sqlStatement
  55. * @return the id of the new record - if there is one
  56. */
  57. const insert = function(sqlStatement)
  58. {
  59. return new Promise(function(resolve, reject)
  60. {
  61. con.query(sanitizer.sanitize(sqlStatement), function (err, result)
  62. {
  63. if (err)
  64. {
  65. console.log(err);
  66. reject();
  67. }
  68. resolve(result.insertId);
  69. });
  70. })
  71. };
  72. /**
  73. * Helper function to generate a hashed password
  74. * from a given plain text password.
  75. *
  76. * This uses 64 bits of entropy as the random salt
  77. * and uses sha256 hashing method to hash the password
  78. * combined with the salt.
  79. *
  80. * @param password
  81. * @returns {Object pass: hashedPassword, salt: salt used to hash}
  82. */
  83. const createHashedPassword = function(password)
  84. {
  85. const randBuff = crypto.randomBytes(64);
  86. const salt = crypto.createHash('sha256').update(randBuff).digest('hex');
  87. const hashPass = crypto.createHash('sha256')
  88. .update(password + salt)
  89. .digest('hex');
  90. var hashPassObject = new Object();
  91. hashPassObject.pass = hashPass;
  92. hashPassObject.salt = salt;
  93. return hashPassObject;
  94. };
  95. /**
  96. * Helper function which fetches the category url for all the
  97. * posts returned in the posts table and appends them to the
  98. * posts json objects.
  99. *
  100. * @param sqlPosts
  101. * @returns {Promise}
  102. */
  103. const fetchWithCategoryInformation = function(sqlPosts)
  104. {
  105. return new Promise(function(resolve, reject)
  106. {
  107. var promises = [];
  108. sqlPosts.forEach(function(post)
  109. {
  110. promises.push(new Promise(function(res, rej)
  111. {
  112. var getCategory = "select url from categories where " +
  113. "category_id='" + post.category_id + "'";
  114. fetch(getCategory).then(function(urls)
  115. {
  116. var obj = new Object();
  117. obj.name = post.name;
  118. obj.url = post.url;
  119. obj.published = post.published;
  120. obj.category = urls[0].url;
  121. res(obj);
  122. });
  123. }));
  124. });
  125. Promise.all(promises).then(function(goodies)
  126. {
  127. resolve(goodies);
  128. });
  129. });
  130. };
  131. module.exports=
  132. {
  133. /**
  134. * function which fetches the sql info on a post based on it's sql id
  135. * @param id
  136. * @returns {Array}
  137. */
  138. getPostById: function(id)
  139. {
  140. return new Promise((resolve, reject)=>
  141. {
  142. fetch("select * from posts where post_id='" + id + "' limit 1")
  143. .then(function(post)
  144. {
  145. if(post.length === 1)
  146. {
  147. resolve(post[0]);
  148. }
  149. else
  150. {
  151. reject();
  152. }
  153. }).catch((error)=>
  154. {
  155. reject(error);
  156. });
  157. });
  158. },
  159. insert: function(sqlStatement)
  160. {
  161. return insert(sqlStatement);
  162. },
  163. /**
  164. * Not to be mistaken for getPostData() in @file utils/utils.js,
  165. * this function extracts a post entry from the sql server
  166. *
  167. * @param requestURL url user used to request blog post
  168. * @return {*} the entry found in the data base -- if any
  169. */
  170. getPost : function(requestURL)
  171. {
  172. return new Promise(function(resolve, reject)
  173. {
  174. var splitURL = requestURL.split("/")
  175. var q = "SELECT * FROM categories WHERE url='" + splitURL[1] + "'";
  176. fetch(q).then(function (result_category)
  177. {
  178. if(result_category.length != 0)
  179. {
  180. var q2 = "SELECT * FROM posts WHERE category_id='" +
  181. result_category[0].category_id +
  182. "' AND url='" + splitURL[2] + "'";
  183. fetch(q2).then(function (result_posts)
  184. {
  185. resolve(result_posts);
  186. });
  187. }
  188. else
  189. {
  190. resolve(0);
  191. }
  192. });
  193. });
  194. },
  195. /**
  196. * Function used to retrieve all categories when making the sidebar
  197. *
  198. * @return {Promise<Response> | * | Array}
  199. */
  200. getCategories : function()
  201. {
  202. const q = "SELECT * FROM categories ORDER BY name";
  203. return fetch(q);
  204. },
  205. /**
  206. * Function which currently returns all blog of a particular
  207. * category from the database
  208. * @param requestURL
  209. * @return {*|Promise}
  210. */
  211. getPostsFromCategory: function(requestURL)
  212. {
  213. return new Promise(function(resolve, reject)
  214. {
  215. var q = "select * from categories where url ='" + requestURL + "'";
  216. fetch(q).then(function(categories)
  217. {
  218. if(categories.length != 0)
  219. {
  220. var qPosts = "select * from posts where category_id='" +
  221. categories[0].category_id + "' order by published desc";
  222. resolve(fetch(qPosts));
  223. }
  224. else
  225. {
  226. resolve([]);
  227. }
  228. });
  229. });
  230. },
  231. /**
  232. * Fetches the recent posts from the database.
  233. * @returns {Array}
  234. */
  235. getRecentPostSQL: function()
  236. {
  237. return fetch("select * from posts order by post_id desc");
  238. },
  239. /**
  240. * Helper method which returns a list of objects which contains the url
  241. * and name of thee ten most recent posts
  242. *
  243. * {[name: , url: ],[name: , url: ],[name: , url: ],...}
  244. *
  245. * @return {*|Promise}
  246. */
  247. getRecentPosts: function(limit)
  248. {
  249. limit = (limit == null) ? 10 : limit;
  250. return new Promise(function(resolve, reject)
  251. {
  252. var q = "select name,url, published, category_id from posts order " +
  253. "by post_id desc limit " + limit;
  254. fetch(q).then(function(sqlPosts)
  255. {
  256. fetchWithCategoryInformation(sqlPosts).then(function(data)
  257. {
  258. resolve(data);
  259. })
  260. });
  261. });
  262. },
  263. /**
  264. * Returns a list of all the pinned posts in the database.
  265. *
  266. * @returns {Promise}
  267. */
  268. getPinnedPosts: function()
  269. {
  270. return new Promise(function(resolve, reject)
  271. {
  272. var q = "select name,url, category_id from posts where pinned=1 order " +
  273. "by post_id desc limit 10";
  274. fetch(q).then(function(sqlPosts)
  275. {
  276. fetchWithCategoryInformation(sqlPosts).then(function(data)
  277. {
  278. resolve(data);
  279. })
  280. });
  281. });
  282. },
  283. /**
  284. * Function which checks to see if a user successfully logged in based on
  285. * the post data which they sent
  286. *
  287. * @param postData the post data
  288. * @return {*|Promise} a json object with {pass: , user: }
  289. * the pass is whether or not they logged in successfully and the user is
  290. * the username they successfully logged in with
  291. */
  292. checkLogin: function(postData)
  293. {
  294. const post = qs.parse(postData);
  295. return new Promise(function(resolve, reject)
  296. {
  297. var result = Object();
  298. result.pass = false;
  299. if(post.username && post.password)
  300. {
  301. const cleanName = sanitizer.sanitize(post.username);
  302. const cleanPassword = sanitizer.sanitize(post.password);
  303. const getSalt = "select * from users where user_name='" +
  304. cleanName + "'";
  305. fetch(getSalt).then(function(saltResult)
  306. {
  307. if(saltResult.length == 1)
  308. {
  309. const hashedPassword = crypto.createHash('sha256')
  310. .update(cleanPassword + saltResult[0].salt)
  311. .digest('hex');
  312. if(saltResult[0].password === hashedPassword)
  313. {
  314. result.pass = true;
  315. result.user = cleanName;
  316. resolve(result);
  317. }
  318. else
  319. {
  320. resolve(result)
  321. }
  322. }
  323. else
  324. {
  325. //incorrect username
  326. resolve(result);
  327. }
  328. })
  329. }
  330. else
  331. {
  332. //no login attempts were made
  333. resolve(result);
  334. }
  335. });
  336. },
  337. /**
  338. * Fetches a promise containing every post in the database
  339. * @returns {Array}
  340. */
  341. getAllPosts: function()
  342. {
  343. return fetch("select * from posts order by published desc");
  344. },
  345. getAllUsers: function()
  346. {
  347. return fetch("select * from users");
  348. },
  349. getUserByID: function(userID)
  350. {
  351. const cleanID = sanitizer.sanitize(userID);
  352. const q = "select * from users where user_id='" + cleanID + "'";
  353. return fetch(q);
  354. },
  355. removeUser: function(user_id)
  356. {
  357. const cleanID = sanitizer.sanitize(user_id);
  358. return insert("delete from users where user_id='" + cleanID + "'");
  359. },
  360. addUser: function(username, password)
  361. {
  362. const cleanName = sanitizer.sanitize(username);
  363. const cleanPassword = sanitizer.sanitize(password);
  364. const hashedPassword = createHashedPassword(cleanPassword);
  365. const q = "insert into users(user_name, password, salt) values('" + cleanName + "'," +
  366. "'" + hashedPassword.pass + "','" + hashedPassword.salt + "')";
  367. return insert(q);
  368. },
  369. updateUser: function(userID, username, password)
  370. {
  371. const cleanID = sanitizer.sanitize(userID);
  372. const cleanName = sanitizer.sanitize(username);
  373. const cleanPassword = sanitizer.sanitize(password);
  374. const hashedPassword = createHashedPassword(cleanPassword);
  375. const q = "update users " +
  376. "set user_name='" + cleanName + "'" +
  377. ",password='" + hashedPassword.pass + "'" +
  378. ",salt='" + hashedPassword.salt + "'" +
  379. " where user_id='" + cleanID + "'";
  380. return insert(q);
  381. },
  382. /**
  383. * Fetches the sql category information based on it's id
  384. * @param categoryId
  385. * @returns {Array}
  386. */
  387. getCategory: function(categoryId)
  388. {
  389. return fetch("select * from categories where category_id='"
  390. + categoryId + "'");
  391. },
  392. /**Returns download information associated with a download name
  393. *
  394. * @param downloadURL
  395. * @returns {Array}
  396. */
  397. getDownload: function(downloadURL)
  398. {
  399. var cleanD = sanitizer.sanitize(downloadURL);
  400. var q = "select * from downloads where name='" + cleanD + "' limit 1";
  401. return new Promise(function(resolve, reject)
  402. {
  403. fetch(q).then(function(sqlData)
  404. {
  405. return module.exports.incrementDownloadCount(sqlData);
  406. }).then(function(sqlData)
  407. {
  408. resolve(sqlData)
  409. }).catch(function(error)
  410. {
  411. reject(error);
  412. })
  413. });
  414. },
  415. /** Increments the download count in the database
  416. *
  417. * @param sqlRow
  418. * @returns {*|Promise}
  419. */
  420. incrementDownloadCount: function(sqlRow)
  421. {
  422. return new Promise(function(resolve, reject)
  423. {
  424. if(sqlRow.length == 1)
  425. {
  426. var q = "update downloads set download_count='" +
  427. (sqlRow[0].download_count + 1) + "' where download_id='" +
  428. sqlRow[0].download_id + "'";
  429. console.log(q);
  430. insert(q).then(function(r)
  431. {
  432. resolve(sqlRow);
  433. }).catch(function(err)
  434. {
  435. reject(err);
  436. })
  437. }
  438. else
  439. {
  440. resolve(sqlRow);
  441. }
  442. });
  443. },
  444. /**
  445. * Fetches all the downloads from the database
  446. *
  447. * @returns {Array}
  448. */
  449. getAllDownloads: function()
  450. {
  451. return fetch("select * from downloads");
  452. },
  453. /**
  454. * Inserts a download row into the database
  455. *
  456. * @param name of the download
  457. * @param file name of file
  458. * @returns {*|the}
  459. */
  460. addDownload: function(name, file)
  461. {
  462. const q = "insert into downloads (name, file, download_count) " +
  463. "values('" + name + "', '" + file + "', '0')";
  464. return insert(q);
  465. },
  466. /**
  467. *
  468. * @param id
  469. */
  470. removeDownload: function(id)
  471. {
  472. const q = "delete from downloads where download_id='" + id + "'";
  473. return insert(q);
  474. },
  475. /**
  476. * Based on the post data submitted by the user this function updates
  477. * the information on the post in the database
  478. * @param postData
  479. * @returns {*|the}
  480. */
  481. editPost: function(postData)
  482. {
  483. const url = postData.edit_name_new.split(" ").join("-").toLowerCase();
  484. console.log(postData);
  485. var pinned = ("pinned_checkbox" in postData) == false ? "NULL": "1";
  486. console.log(pinned);
  487. const q = "update posts " +
  488. "set category_id='" + postData.edit_cat_num + "' " +
  489. ",name='" + postData.edit_name_new + "' " +
  490. ",url='" + url + "' " +
  491. ",picture_url='" + postData.edit_pic + "' " +
  492. ",published='" + postData.edit_date + "' " +
  493. ",pinned=" + pinned+
  494. " where post_id='" + postData.edit_post_2 + "'";
  495. console.log(q);
  496. return insert(q);
  497. },
  498. /**
  499. * Function which returns a promise which contains the string of the
  500. * entire sitemap for the blog.
  501. * @returns {Promise|*}
  502. */
  503. getSiteMap: function()
  504. {
  505. return new Promise(function(resolve, reject)
  506. {
  507. const base = "http://jrtechs.net/";
  508. var sm = base + "\n";
  509. var promises = [];
  510. module.exports.getCategories().then(function(categories)
  511. {
  512. categories.forEach(function(cat)
  513. {
  514. promises.push(new Promise(function(res, rej)
  515. {
  516. sm += base + "category/" + cat.url + "\n";
  517. module.exports.getPostsFromCategory(cat.url).then(function(posts)
  518. {
  519. posts.forEach(function(post)
  520. {
  521. sm += base + cat.url + "/" + post.url + "\n";
  522. });
  523. res()
  524. })
  525. }));
  526. });
  527. Promise.all(promises).then(function()
  528. {
  529. resolve(sm);
  530. }).catch(function(error)
  531. {
  532. throw error;
  533. });
  534. });
  535. });
  536. },
  537. /**
  538. * Logs visited page for backend server analytics.
  539. *
  540. * @param ip
  541. * @param page
  542. */
  543. logTraffic: function(ip, page)
  544. {
  545. if(page.length > 40)
  546. {
  547. console.log("Error, request too long to log ip:"
  548. + ip + " page: " + page);
  549. return;
  550. }
  551. if(ip.length > 20)
  552. {
  553. ip = "";
  554. }
  555. const q = "insert into traffic_log (url, ip, date) values " +
  556. "('" + page + "', '" + ip + "', now())";
  557. insert(q);
  558. },
  559. getTraffic: function()
  560. {
  561. return fetch("select * from traffic_log");
  562. }
  563. };