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.

637 lines
17 KiB

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