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.

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