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.

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