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.

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