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.

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