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.

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