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.

586 lines
16 KiB

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