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.

654 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.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()
  248. {
  249. return new Promise(function(resolve, reject)
  250. {
  251. var q = "select name,url, published, category_id from posts order " +
  252. "by post_id desc limit 10";
  253. fetch(q).then(function(sqlPosts)
  254. {
  255. fetchWithCategoryInformation(sqlPosts).then(function(data)
  256. {
  257. resolve(data);
  258. })
  259. });
  260. });
  261. },
  262. /**
  263. * Returns a list of all the pinned posts in the database.
  264. *
  265. * @returns {Promise}
  266. */
  267. getPinnedPosts: function()
  268. {
  269. return new Promise(function(resolve, reject)
  270. {
  271. var q = "select name,url, category_id from posts where pinned=1 order " +
  272. "by post_id desc limit 10";
  273. fetch(q).then(function(sqlPosts)
  274. {
  275. fetchWithCategoryInformation(sqlPosts).then(function(data)
  276. {
  277. resolve(data);
  278. })
  279. });
  280. });
  281. },
  282. /**
  283. * Function which checks to see if a user successfully logged in based on
  284. * the post data which they sent
  285. *
  286. * @param postData the post data
  287. * @return {*|Promise} a json object with {pass: , user: }
  288. * the pass is whether or not they logged in successfully and the user is
  289. * the username they successfully logged in with
  290. */
  291. checkLogin: function(postData)
  292. {
  293. const post = qs.parse(postData);
  294. return new Promise(function(resolve, reject)
  295. {
  296. var result = Object();
  297. result.pass = false;
  298. if(post.username && post.password)
  299. {
  300. const cleanName = sanitizer.sanitize(post.username);
  301. const cleanPassword = sanitizer.sanitize(post.password);
  302. const getSalt = "select * from users where user_name='" +
  303. cleanName + "'";
  304. fetch(getSalt).then(function(saltResult)
  305. {
  306. if(saltResult.length == 1)
  307. {
  308. const hashedPassword = crypto.createHash('sha256')
  309. .update(cleanPassword + saltResult[0].salt)
  310. .digest('hex');
  311. if(saltResult[0].password === hashedPassword)
  312. {
  313. result.pass = true;
  314. result.user = cleanName;
  315. resolve(result);
  316. }
  317. else
  318. {
  319. resolve(result)
  320. }
  321. }
  322. else
  323. {
  324. //incorrect username
  325. resolve(result);
  326. }
  327. })
  328. }
  329. else
  330. {
  331. //no login attempts were made
  332. resolve(result);
  333. }
  334. });
  335. },
  336. /**
  337. * Fetches a promise containing every post in the database
  338. * @returns {Array}
  339. */
  340. getAllPosts: function()
  341. {
  342. return fetch("select * from posts order by published desc");
  343. },
  344. getAllUsers: function()
  345. {
  346. return fetch("select * from users");
  347. },
  348. getUserByID: function(userID)
  349. {
  350. const cleanID = sanitizer.sanitize(userID);
  351. const q = "select * from users where user_id='" + cleanID + "'";
  352. return fetch(q);
  353. },
  354. removeUser: function(user_id)
  355. {
  356. const cleanID = sanitizer.sanitize(user_id);
  357. return insert("delete from users where user_id='" + cleanID + "'");
  358. },
  359. addUser: function(username, password)
  360. {
  361. const cleanName = sanitizer.sanitize(username);
  362. const cleanPassword = sanitizer.sanitize(password);
  363. const hashedPassword = createHashedPassword(cleanPassword);
  364. const q = "insert into users(user_name, password, salt) values('" + cleanName + "'," +
  365. "'" + hashedPassword.pass + "','" + hashedPassword.salt + "')";
  366. return insert(q);
  367. },
  368. updateUser: function(userID, username, password)
  369. {
  370. const cleanID = sanitizer.sanitize(userID);
  371. const cleanName = sanitizer.sanitize(username);
  372. const cleanPassword = sanitizer.sanitize(password);
  373. const hashedPassword = createHashedPassword(cleanPassword);
  374. const q = "update users " +
  375. "set user_name='" + cleanName + "'" +
  376. ",password='" + hashedPassword.pass + "'" +
  377. ",salt='" + hashedPassword.salt + "'" +
  378. " where user_id='" + cleanID + "'";
  379. return insert(q);
  380. },
  381. /**
  382. * Fetches the sql category information based on it's id
  383. * @param categoryId
  384. * @returns {Array}
  385. */
  386. getCategory: function(categoryId)
  387. {
  388. return fetch("select * from categories where category_id='"
  389. + categoryId + "'");
  390. },
  391. /**Returns download information associated with a download name
  392. *
  393. * @param downloadURL
  394. * @returns {Array}
  395. */
  396. getDownload: function(downloadURL)
  397. {
  398. var cleanD = sanitizer.sanitize(downloadURL);
  399. var q = "select * from downloads where name='" + cleanD + "' limit 1";
  400. return new Promise(function(resolve, reject)
  401. {
  402. fetch(q).then(function(sqlData)
  403. {
  404. return module.exports.incrementDownloadCount(sqlData);
  405. }).then(function(sqlData)
  406. {
  407. resolve(sqlData)
  408. }).catch(function(error)
  409. {
  410. reject(error);
  411. })
  412. });
  413. },
  414. /** Increments the download count in the database
  415. *
  416. * @param sqlRow
  417. * @returns {*|Promise}
  418. */
  419. incrementDownloadCount: function(sqlRow)
  420. {
  421. return new Promise(function(resolve, reject)
  422. {
  423. if(sqlRow.length == 1)
  424. {
  425. var q = "update downloads set download_count='" +
  426. (sqlRow[0].download_count + 1) + "' where download_id='" +
  427. sqlRow[0].download_id + "'";
  428. console.log(q);
  429. insert(q).then(function(r)
  430. {
  431. resolve(sqlRow);
  432. }).catch(function(err)
  433. {
  434. reject(err);
  435. })
  436. }
  437. else
  438. {
  439. resolve(sqlRow);
  440. }
  441. });
  442. },
  443. /**
  444. * Fetches all the downloads from the database
  445. *
  446. * @returns {Array}
  447. */
  448. getAllDownloads: function()
  449. {
  450. return fetch("select * from downloads");
  451. },
  452. /**
  453. * Inserts a download row into the database
  454. *
  455. * @param name of the download
  456. * @param file name of file
  457. * @returns {*|the}
  458. */
  459. addDownload: function(name, file)
  460. {
  461. const q = "insert into downloads (name, file, download_count) " +
  462. "values('" + name + "', '" + file + "', '0')";
  463. return insert(q);
  464. },
  465. /**
  466. *
  467. * @param id
  468. */
  469. removeDownload: function(id)
  470. {
  471. const q = "delete from downloads where download_id='" + id + "'";
  472. return insert(q);
  473. },
  474. /**
  475. * Based on the post data submitted by the user this function updates
  476. * the information on the post in the database
  477. * @param postData
  478. * @returns {*|the}
  479. */
  480. editPost: function(postData)
  481. {
  482. const url = postData.edit_name_new.split(" ").join("-").toLowerCase();
  483. console.log(postData);
  484. var pinned = ("pinned_checkbox" in postData) == false ? "NULL": "1";
  485. console.log(pinned);
  486. const q = "update posts " +
  487. "set category_id='" + postData.edit_cat_num + "' " +
  488. ",name='" + postData.edit_name_new + "' " +
  489. ",url='" + url + "' " +
  490. ",picture_url='" + postData.edit_pic + "' " +
  491. ",published='" + postData.edit_date + "' " +
  492. ",pinned=" + pinned+
  493. " where post_id='" + postData.edit_post_2 + "'";
  494. console.log(q);
  495. return insert(q);
  496. },
  497. /**
  498. * Function which returns a promise which contains the string of the
  499. * entire sitemap for the blog.
  500. * @returns {Promise|*}
  501. */
  502. getSiteMap: function()
  503. {
  504. return new Promise(function(resolve, reject)
  505. {
  506. const base = "http://jrtechs.net/";
  507. var sm = base + "\n";
  508. var promises = [];
  509. module.exports.getCategories().then(function(categories)
  510. {
  511. categories.forEach(function(cat)
  512. {
  513. promises.push(new Promise(function(res, rej)
  514. {
  515. sm += base + "category/" + cat.url + "\n";
  516. module.exports.getPostsFromCategory(cat.url).then(function(posts)
  517. {
  518. posts.forEach(function(post)
  519. {
  520. sm += base + cat.url + "/" + post.url + "\n";
  521. });
  522. res()
  523. })
  524. }));
  525. });
  526. Promise.all(promises).then(function()
  527. {
  528. resolve(sm);
  529. }).catch(function(error)
  530. {
  531. throw error;
  532. });
  533. });
  534. });
  535. },
  536. /**
  537. * Logs visited page for backend server analytics.
  538. *
  539. * @param ip
  540. * @param page
  541. */
  542. logTraffic: function(ip, page)
  543. {
  544. if(page.length > 40)
  545. {
  546. console.log("Error, request too long to log ip:"
  547. + ip + " page: " + page);
  548. return;
  549. }
  550. if(ip.length > 20)
  551. {
  552. ip = "";
  553. }
  554. const q = "insert into traffic_log (url, ip, date) values " +
  555. "('" + page + "', '" + ip + "', now())";
  556. insert(q);
  557. },
  558. getTraffic: function()
  559. {
  560. return fetch("select * from traffic_log");
  561. }
  562. };