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.

684 lines
18 KiB

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