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.

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