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.

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