Browse Source

Merge pull request #41 from jrtechs/templateEngine

Template Engine Upgrade
pull/46/head
Jeffery Russell 6 years ago
committed by GitHub
parent
commit
77cb2746dd
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
47 changed files with 1300 additions and 1318 deletions
  1. +8
    -94
      README.md
  2. +38
    -15
      admin/admin.js
  3. +129
    -0
      admin/adminDownloads.js
  4. +138
    -0
      admin/adminHome.js
  5. +0
    -15
      admin/category/addCategory.html
  6. +0
    -98
      admin/category/addCategory.js
  7. +0
    -19
      admin/downloads/addDownload.html
  8. +0
    -191
      admin/downloads/manageDownloads.js
  9. +0
    -28
      admin/login/login.html
  10. +27
    -15
      admin/login/login.js
  11. +103
    -0
      admin/posts.js
  12. +0
    -182
      admin/posts/editPost.js
  13. +0
    -54
      admin/posts/newPost.html
  14. +0
    -77
      admin/posts/newPost.js
  15. +49
    -0
      blog/category.js
  16. +32
    -0
      blog/homePage.js
  17. +47
    -0
      blog/posts.js
  18. +67
    -31
      blog/renderBlogPost.js
  19. +52
    -0
      blog/renderNextBar.js
  20. +1
    -1
      blogContent/posts/web-development/node-website-optimization.md
  21. +60
    -0
      docs/sqlConfig.md
  22. +1
    -1
      includes/contact.js
  23. +24
    -7
      includes/html/adminHeader.html
  24. +1
    -2
      includes/html/header.html
  25. +16
    -6
      includes/includes.js
  26. +0
    -59
      posts/category.js
  27. +0
    -46
      posts/homePage.js
  28. +0
    -68
      posts/posts.js
  29. +0
    -55
      posts/renderBatchOfPreviewes.js
  30. +0
    -66
      posts/renderNextBar.js
  31. +0
    -28
      posts/singlePost.js
  32. +0
    -38
      sidebar/categoriesSideBar.js
  33. +0
    -35
      sidebar/popularPosts.js
  34. +0
    -10
      sidebar/projectSidebar.html
  35. +0
    -38
      sidebar/recentPosts.js
  36. +53
    -12
      sidebar/sidebar.js
  37. +12
    -6
      sites/admin.js
  38. +21
    -11
      sites/blog.js
  39. +2
    -1
      sites/projects.js
  40. +66
    -0
      templates/admin/adminDownloads.html
  41. +103
    -0
      templates/admin/adminHome.html
  42. +65
    -0
      templates/admin/adminMain.html
  43. +68
    -0
      templates/admin/adminPosts.html
  44. +62
    -0
      templates/blog/blogMain.html
  45. +36
    -0
      templates/blog/sideBar.html
  46. +2
    -9
      utils/sql.js
  47. +17
    -0
      utils/utils.js

+ 8
- 94
README.md View File

@ -31,67 +31,6 @@ the [Creative Commons Attribution-ShareAlike 4.0 International](https://creative
unless otherwise stated.
## MYSQL Schema
![](docs/blogSql.svg)
```mysql
create database jrtechs_blog;
use jrtechs_blog;
create table users(
user_id mediumint unsigned not null AUTO_INCREMENT,
user_name varchar(60) not null,
password char(64) not null,
salt char(64) not null,
primary key(user_id)
);
create table categories(
category_id mediumint unsigned not null AUTO_INCREMENT,
name varchar(60) not null,
url varchar(60) not null,
primary key(category_id)
);
create table posts(
post_id mediumint unsigned not null AUTO_INCREMENT,
category_id mediumint unsigned not null,
picture_url varchar(100) not null,
published datetime not null,
name varchar(100) not null,
url varchar(100) not null,
primary key(post_id)
);
create table downloads(
download_id mediumint unsigned not null AUTO_INCREMENT,
file varchar(40) not null,
name varchar(40) not null,
download_count mediumint not null,
primary key(download_id)
);
create table popular_posts(
popular_post_id mediumint unsigned not null AUTO_INCREMENT,
post_id mediumint unsigned not null,
primary key(popular_post_id)
);
create table traffic_log(
log_id mediumint unsigned not null AUTO_INCREMENT,
url varchar(60) not null,
ip varchar(20) not null,
date datetime not null,
primary key(log_id)
);
grant all on jrtechs_blog.* to blog_user@localhost identified by "password";
```
## Node Dependencies
```bash
@ -110,7 +49,7 @@ npm install memory-cache --save
npm install request
npm install nodemailer
npm install nodemailer-smtp-transport
npm install whiskers
npm install node-pandoc
```
@ -140,7 +79,8 @@ apt-get install optipng
## NGINX Configuration
```
#jrtechs.net.conf
server {
server
{
listen 80;
server_name www.jrtechs.net jrtechs.net;
@ -148,7 +88,8 @@ server {
return 301 https://jrtechs.net$request_uri;
}
server {
server
{
listen 443 ssl http2;
server_name jrtechs.net;
@ -167,38 +108,11 @@ server {
}
```
```
#admin.jrtechs.net.conf
server {
listen 80;
server_name www.admin.jrtechs.net admin.jrtechs.net;
# redirect http requests to https
return 301 https://admin.jrtechs.net$request_uri;
}
server {
listen 443 ssl http2;
server_name admin.jrtechs.net;
ssl_certificate /etc/letsencrypt/live/admin.jrtechs.net/cert.pem;
ssl_certificate_key /etc/letsencrypt/live/admin.jrtechs.net/privkey.pem;
location / {
proxy_pass http://localhost:8001;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
```
## Projects Sites
As I develop more projects I would like an easy way to add and host them on my website without having to create another subdomain and generate more ssl certs. I simply want the project site to be accessible under https://jrtechs.net/project_name.
As I develop more projects I would like an easy way to add and host them on my website without having to create another sub-domain and generate more ssl certs.
I simply want the project site to be accessible under https://jrtechs.net/project_name.
### State Diagrm of Plan
### State Diagram of Plan
![diagram](docs/projectsSites.svg)

+ 38
- 15
admin/admin.js View File

@ -1,5 +1,13 @@
/**
* Renders the admin page contents
* Determines what template and controls that will be
* displayed based on the url such as
* /
* /blog
* /downloads
*
* For each controls it calls that "pages" associated javascript file
* which fetches the template, deals with post data and gathers context
* for the template engine.
*/
//file IO
@ -9,38 +17,53 @@ const utils = require('../utils/utils.js');
module.exports=
{
/**
* Method calls the admin widgets it correct order
*
* @param request
* @return {*|Promise}
* @param request -- used to get post data
* @param clientAddress -- used to see if user is banned for login
* @param templateContext -- used by whiskers for information to plug
* in the template
* @param filename -- specific admin page requested
* @returns {Promise} resolves once everything has been added to the template context
*/
main: function(request, clientAddress)
main: function(request, clientAddress, templateContext, filename)
{
console.log("admin main called");
return new Promise(function(resolve, reject)
{
//if logged in
if(request.session && request.session.user)
{
console.log(filename);
templateContext.loggedIn = true;
utils.getPostData(request).then(function (postData)
{
console.log(postData);
Promise.all([require("./posts/newPost.js").main(postData),
require("./category/addCategory.js").main(postData),
require("./posts/editPost.js").main(postData),
require("./downloads/manageDownloads.js").main(postData)])
.then(function(content)
console.log("temp 1");
var page = "./adminHome.js";
if(filename.includes('/downloads'))
{
resolve(content.join(''));
page = "./adminDownloads.js";
console.log("downloads time")
}
else if(filename.includes("/posts"))
{
page = "./posts.js";
}
require(page).main(postData, templateContext).then(function(template)
{
templateContext.adminPage = template;
resolve();
}).catch(function(error)
{
reject(error);
console.log(error);
});
});
}
else
{
require("./login/login.js").main(request).then(function(html)
require("./login/login.js").main(request, clientAddress, templateContext).then(function()
{
resolve(html);
resolve();
}).catch(function(err)
{
console.log(err);

+ 129
- 0
admin/adminDownloads.js View File

@ -0,0 +1,129 @@
/**
* File which deals with adding and removing downloads from
* the admin section of the website.
*
* @author Jeffery Russell 6-30-18
*/
/** Whiskers template file */
const TEMPLATE_FILE = "admin/adminDownloads.html";
const includes = require('../includes/includes.js');
//updates db
const sql = require('../utils/sql');
//parses post data
const qs = require('querystring');
/**
* Processes post requests from the addDownload form
*
* @param postData
* @returns {*|Promise}
*/
const addDownloadPostData = function(postData)
{
return new Promise(function(resolve, reject)
{
const post = qs.parse(postData);
if(post.add_download)
{
sql.addDownload(post.add_download_name, post.add_download_file)
.then(function()
{
resolve();
}).catch(function(error)
{
reject(error);
})
}
else
{
resolve();
}
});
};
/**
* Removes a download if requested by the
* post data from an admin.
*/
const removeDownloads = function(postData)
{
return new Promise(function(resolve, reject)
{
const post = qs.parse(postData);
if(post.delete_download)
{
sql.removeDownload(post.delete_download).then(function()
{
resolve();
}).catch(function(err)
{
reject(err);
});
}
else
{
resolve();
}
});
};
/**
* Fetches the download items in the database so that
* the template engine can use it to display them in
* a table.
*
* @param templateContext-- context item used by whiskers
* @returns {Promise}
*/
const displayDownloads = function(templateContext)
{
return new Promise(function(resolve, reject)
{
sql.getAllDownloads().then(function(downloads)
{
templateContext.downloads = downloads;
resolve();
}).catch(function(error)
{
reject(error);
});
});
};
module.exports=
{
/** Fetches context information for the template and handles
* post data for the downloads.
*
* @param postData posted by user
* @param templateContext json object used as the template context
* @returns {Promise} renders the template used for this page
*/
main: function(postData, templateContext)
{
return new Promise(function(resolve, reject)
{
Promise.all([includes.fetchTemplate(TEMPLATE_FILE),
addDownloadPostData(postData),
removeDownloads(postData),
displayDownloads(templateContext)]).then(function(template)
{
resolve(template[0]);
}).catch(function(error)
{
console.log("error in add downloads.js");
reject(error);
});
});
}
};

+ 138
- 0
admin/adminHome.js View File

@ -0,0 +1,138 @@
const TEMPLATE_FILE = "admin/adminHome.html";
const includes = require('../includes/includes.js');
const sql = require('../utils/sql');
const qs = require('querystring');
/**
* Checks for post data regarding adding a new category.
* If a post is made with add_category, it parses the url-- replaces spaces
* with dashes -- and calls a insert method on the database
*
* @param postData
* @return {*|Promise}
*/
const processPostAddCategory = function(postData)
{
return new Promise(function(resolve, reject)
{
const post = qs.parse(postData);
if(post.add_category)
{
const url = post.add_category.split(" ").join("-").toLowerCase();
const q = "insert into categories (name, url) values " +
"('" + post.add_category + "','" + url + "')";
if(sql.insert(q) != 0)
{
console.log("category added");
}
else
{
console.log("error adding category");
}
}
resolve("");
});
};
/**
* Displays all the categories in the database
* @return {*|Promise}
*/
const appendCategoriesToTemplate = function(templateContext)
{
return new Promise(function(resolve, reject)
{
sql.getCategories().then(function(categories)
{
templateContext.categories = categories;
resolve();
}).catch(function(error)
{
reject(error);
})
});
};
/**
*
* @param postData
* @return {*|Promise}
*/
const processPost = function(postData)
{
return new Promise(function(resolve, reject)
{
var post = qs.parse(postData);
if(post.add_post_name)
{
var urls = post.add_post_name;
urls = urls.split(" ").join("-");
urls =urls.toLowerCase();
var q = "insert into blog (category_id, picture_url, published, name, url) values ";
q += "('" + post.add_post_category + "', '" + post.add_post_picture +
"', '" + post.add_post_date + "', '" + post.add_post_name + "', '" + urls + "')";
sql.insert(q).then(function()
{
var map = require('../utils/generateSiteMap');
map.main();
resolve("");
}).catch(function(error)
{
reject(error);
})
}
else if(post.clear_cache)
{
require("../sites/blog.js").clearCache();
require("../includes/includes.js").clearCache();
}
else if(post.git_pull)
{
const execSync = require('child_process').execSync;
code = execSync('git pull')
}
else
{
resolve("");
}
});
};
module.exports=
{
/**
*
* @param postData posted by user
* @param templateContext json object used as the template context
* @returns {Promise} renders the template used for this page
*/
main: function(postData, templateContext)
{
console.log("called");
return new Promise(function(resolve, reject)
{
Promise.all([includes.fetchTemplate(TEMPLATE_FILE),
processPostAddCategory(postData),
appendCategoriesToTemplate(templateContext),
processPost(postData)])
.then(function(template)
{
resolve(template[0]);
}).catch(function(error)
{
console.log("error in add downloads.js");
reject(error);
});
});
}
};

+ 0
- 15
admin/category/addCategory.html View File

@ -1,15 +0,0 @@
<div class="blogPost">
<h1 class="text-center">Add Category</h1>
<form action="/admin" method ="post" class="p-2">
<div class="form-group">
<input class="form-control" type="text" name="add_category" required>
<label>Category</label>
</div>
<div class="text-center">
<input type="submit" name="submit" value="Add"
class="btn btn-lg btn-secondary"/>
</div>
</form>
</div>
<br>

+ 0
- 98
admin/category/addCategory.js View File

@ -1,98 +0,0 @@
//file io
const utils = require('../../utils/utils.js');
//update db
const sql = require('../../utils/sql');
//parse post data
const qs = require('querystring');
/**
* Displays all the categories in the database
* @return {*|Promise}
*/
const printCategories = function()
{
var html = "<div class=\"blogPost\">" +
"<h1 class=\"text-center\">Categories</h1>" +
"<div class=\"\"><table class=\"table table-striped\">" +
"<thead class=\"thead-dark\">" +
"<tr>" +
"<td>Name</td><td>URL</td><td>Edit</td>" +
"</tr></thead><tbody>";
return new Promise(function(resolve, reject)
{
sql.getCategories().then(function(categories)
{
categories.forEach(function(c)
{
html +="<tr>" +
"<td>" + c.name + "</td>" +
"<td>" + c.url + "</td>" +
"<td>" + c.category_id + "</td>" +
"</tr>";
});
resolve(html + "</tbody></table></div></div>");
}).catch(function(error)
{
reject(error);
})
});
};
/**
* Checks for post data regarding adding a new category.
* If a post is made with add_category, it parses the url-- replaces spaces
* with dashes -- and calls a insert method on the database
*
* @param postData
* @return {*|Promise}
*/
const processPost = function(postData)
{
return new Promise(function(resolve, reject)
{
const post = qs.parse(postData);
if(post.add_category)
{
const url = post.add_category.split(" ").join("-").toLowerCase();
const q = "insert into categories (name, url) values " +
"('" + post.add_category + "','" + url + "')";
if(sql.insert(q) != 0)
{
console.log("category added");
}
else
{
console.log("error adding category");
}
}
resolve("");
});
};
module.exports=
{
main: function(postData)
{
return new Promise(function(resolve, reject)
{
Promise.all([utils.include("./admin/category/addCategory.html"),
printCategories(),
processPost(postData)]).then(function(html)
{
resolve("<div class=\"col-md-6\">" +
html.join('') +
"</div></div>");
}).catch(function(error)
{
console.log("error in addCategory.js");
reject(error);
})
});
}
};

+ 0
- 19
admin/downloads/addDownload.html View File

@ -1,19 +0,0 @@
<div class="blogPost">
<h1 class="text-center">Add Download</h1>
<form action="/admin" method ="post" class="p-2">
<div class="form-group">
<input class="form-control" type="text" name="add_download_name" required>
<label>Download Name</label>
</div>
<div class="form-group">
<input class="form-control" type="text" name="add_download_file" required>
<label>File name</label>
</div>
<div class="text-center">
<input type="submit" name="add_download" value="Add Download"
class="btn btn-lg btn-secondary"/>
</div>
</form>
</div>
<br>

+ 0
- 191
admin/downloads/manageDownloads.js View File

@ -1,191 +0,0 @@
/**
* File which deals with adding and removing downloads from
* the admin section of the website.
*
* @author Jeffery Russell 6-30-18
*/
//file IO
const utils = require('../../utils/utils.js');
//updates db
const sql = require('../../utils/sql');
//parses post data
const qs = require('querystring');
/**
* Processes post requests from the addDownload form
*
* @param postData
* @returns {*|Promise}
*/
const addDownloadPostData = function(postData)
{
return new Promise(function(resolve, reject)
{
const post = qs.parse(postData);
if(post.add_download)
{
sql.addDownload(post.add_download_name, post.add_download_file)
.then(function()
{
resolve("");
}).catch(function(error)
{
reject(error);
})
}
else
{
resolve("");
}
});
};
/**
* Displays the addDownload form the the user
*
* @param postData
* @returns {*|Promise}
*/
const addDownload = function(postData)
{
return new Promise(function(resolve, reject)
{
Promise.all([addDownloadPostData(postData),
utils.include("./admin/downloads/addDownload.html")]).then(function(html)
{
resolve("<div class=\"col-md-6\">" + html.join('') + "</div>");
}).catch(function(error)
{
reject(error);
})
});
};
/**
* Handel form requests from the downloads table
*
* @param postData
* @returns {*|Promise}
*/
const displayDownloadsPostData = function(postData)
{
return new Promise(function(resolve, reject)
{
const post = qs.parse(postData);
if(post.delete_download)
{
sql.removeDownload(post.delete_download).then(function()
{
resolve(postData);
}).catch(function(err)
{
reject(err);
});
}
else
{
resolve(postData);
}
});
};
/**
* Renders a single download row in the downloads table
*
* @param download
* @returns {*|Promise}
*/
const renderDownloadRow = function(download)
{
return "<tr>" +
"<td>" + download.name + "</td>" +
"<td>" + download.file + "</td>" +
"<td>" + download.download_count + "</td>" +
"<td><form action=\"/admin\" method =\"post\" >\n" +
" <input type=\"submit\" name=\"submit\" value=\"Delete\"\n" +
" class=\"btn btn-secondary\"/>\n" +
"<input type='hidden' name='delete_download' value='" +
download.download_id + "'/>"+
"</form></td>" +
"</tr>";
};
/**
* Displays all the download information in a table
* @param postData
* @returns {*|Promise}
*/
const displayDownloads = function(postData)
{
var html = "<div class=\"col-md-6\">";
return new Promise(function(resolve, reject)
{
displayDownloadsPostData(postData).then(function()
{
html += "<div class='blogPost'>" +
"<h1 class=\"text-center\">Downloads</h1>" +
"<div class=\"\"><table class=\"table table-striped\">" +
"<thead class=\"thead-dark\"><tr>" +
"<td>Download Name</td><td>File</td>" +
"<td>Download Count</td><td>Delete</td>" +
"</tr></thead><tbody>";
sql.getAllDownloads().then(function(downloads)
{
var downloadPromises = [];
downloads.forEach(function(download)
{
downloadPromises.push(renderDownloadRow(download));
});
Promise.all(downloadPromises).then(function(htmls)
{
const htmlafter = "</tbody></table></div></div><br>" +
"</div>";
resolve(html + htmls.join('') + htmlafter);
});
}).catch(function(error)
{
reject(error);
});
});
});
};
module.exports=
{
/**
* Renders tha download section of the admin page
*
* @param postData
* @returns {Promise}
*/
main: function(postData)
{
return new Promise(function(resolve, reject)
{
Promise.all([addDownload(postData),
displayDownloads(postData)]).then(function(html)
{
resolve("<div class=\"row\">" + html.join('') + "</div>");
}).catch(function(error)
{
console.log("error in add downloads.js");
reject(error);
});
});
}
};

+ 0
- 28
admin/login/login.html View File

@ -1,28 +0,0 @@
<div class="col-md-8">
<div class="blogPost">
<div class="text-center">
<h2>Login</h2>
</div>
<form action="/admin" method ="post" class="p-2">
<div class="form-group">
<label for="username1">User Name</label>
<input class="form-control" type="test" id="username1" name="username" placeholder="Enter username" required>
</div>
<div class="form-group">
<label for="password1">Password</label>
<input class="form-control" type="password" name="password" id="password1" placeholder="Password" required>
</div>
<div class="text-center">
<button class="btn btn-lg btn-secondary">Login</button>
</div>
<br>
</form>
<!--
/\_/\ ___
= o_o =_______ \ \
__^ __( \.__) )
(@)<_____>__(_____)____/
-->
</div>
</div>

+ 27
- 15
admin/login/login.js View File

@ -6,6 +6,8 @@ const sql = require('../../utils/sql');
const qs = require('querystring');
const DEBUG = false;
/**
* Processes post data to see if the user has successfully
@ -15,10 +17,19 @@ const qs = require('querystring');
* @param request
* @returns {Promise}
*/
const processLogin = function(request, clientAddress)
const processLogin = function(request, clientAddress, templateContext)
{
return new Promise(function(resolve, reject)
{
if(DEBUG)
{
//what actually logs in the user
request.session.user = 1;
console.log("user has logged in");
templateContext.goodLoginAttempt = true;
resolve();
}
utils.getPostData(request).then(function(postData)
{
const post = qs.parse(postData);
@ -37,10 +48,12 @@ const processLogin = function(request, clientAddress)
//what actually logs in the user
request.session.user = loginResult.user;
console.log("user has logged in");
resolve("<meta http-equiv=\"refresh\" content=\"0\">");
templateContext.goodLoginAttempt = true;
resolve();
}
else
{
templateContext.invalid = true;
banIP(clientAddress);
console.log("Invader!");
resolve("Wrong!");
@ -110,27 +123,26 @@ module.exports=
* @param request express request containing post data
* @returns {Promise} resolves html of login page
*/
main: function(request, clientAddress)
main: function(request, clientAddress, templateContext)
{
if(isBanned(clientAddress))
{
return utils.printBannedPage();
}
else
return new Promise(function(resolve, reject)
{
return new Promise(function(resolve, reject)
if(isBanned(clientAddress))
{
Promise.all([utils.include("./admin/login/login.html"),
require("../../sidebar/sidebar.js").main(),
processLogin(request, clientAddress)]).then(function(html)
templateContext.banned = true;
resolve();
}
else
{
processLogin(request, clientAddress, templateContext).then(function()
{
resolve(html.join('') + "</div>");
resolve();
}).catch(function(err)
{
reject(err);
})
});
}
}
});
},
};

+ 103
- 0
admin/posts.js View File

@ -0,0 +1,103 @@
/** Whiskers template file
* this has stuff for both editing blog and viewing a list of blog*/
const TEMPLATE_FILE = "admin/adminPosts.html";
const includes = require('../includes/includes.js');
const sql = require('../utils/sql');
//parses the post data
const qs = require('querystring');
/**
* Detects if the post data came from the edit form in blog table or edit post
* in the edit post form.
*
* @param postData
* @param renderContext
* @returns {Promise}
*/
const processPostData = function(postData, renderContext)
{
return new Promise(function(resolve, reject)
{
var postParsed = qs.parse(postData);
if(postParsed.edit_post)
{
renderContext.editPost = true;
sql.getPostById(postParsed.edit_post).then(function(post)
{
post.published = post.published.toISOString().split('T')[0];
renderContext.post = post;
resolve();
});
}
else if(postParsed.edit_post_2)
{
sql.editPost(postParsed).then(function()
{
resolve();
}).catch(function(error)
{
reject(error);
});
}
else
{
resolve();
}
});
};
/**
* Grabs and appends the list of blog from the SQL database to
* the template context for the template renderer.
*
* @param templateContext
* @returns {Promise}
*/
const fetchPostsInformation = function(templateContext)
{
return new Promise(function(resolve, reject)
{
sql.getAllPosts().then(function(posts)
{
templateContext.posts = posts;
resolve();
}).catch(function(error)
{
reject(error);
})
});
};
module.exports=
{
/**
* Fetches context information for the admin blog page and handles post
* data sent regarding editing blog.
*
* @param postData posted by user
* @param templateContext json object used as the template context
* @returns {Promise} renders the template used for this page
*/
main: function(postData, templateContext)
{
return new Promise(function(resolve, reject)
{
Promise.all([includes.fetchTemplate(TEMPLATE_FILE),
processPostData(postData, templateContext),
fetchPostsInformation(templateContext)]).then(function(template)
{
resolve(template[0]);
}).catch(function(error)
{
console.log("error in add admin blog.js");
reject(error);
});
});
}
};

+ 0
- 182
admin/posts/editPost.js View File

@ -1,182 +0,0 @@
/**
* File which renders the edit form for the posts and processes
* the post data generated by edit forms.
*
* @type {Promise|*}
*/
//parses the post data
const qs = require('querystring');
//updates db
const sql = require('../../utils/sql');
/**
* Displays a single row in the posts view
*
* @param post
*/
const renderPostRow = function(post)
{
return "<tr>" +
"<td>" + post.category_id + "</td>" +
"<td>" + post.name + "</td>" +
"<td>" + post.picture_url + "</td>" +
"<td>" + post.published + "</td>" +
"<td><form action=\"/admin\" method =\"post\" >\n" +
"<input type=\"submit\" name=\"submit\" value=\"Edit\"\n" +
" class=\"btn btn-secondary\"/>\n" +
"<input type='hidden' name='edit_post' value='" + post.post_id + "'/>"+
"</form></td>" +
"</tr>";
};
/**
* Displays all the posts in a table
*/
const postsTable = function()
{
const html = "<div class='blogPost p-2'>" +
"<h1 class=\"text-center\">Posts</h1>" +
"<div class=\"\"><table class=\"table table-striped\">" +
"<thead class=\"thead-dark\"><tr>" +
"<td>Category #</td><td>Name</td><td>Header Picture</td><td>Date</td><td>Edit</td>" +
"</tr></thead><tbody>";
return new Promise(function(resolve, reject)
{
sql.getAllPosts().then(function(posts)
{
var postPromises = [];
posts.forEach(function(post)
{
postPromises.push(renderPostRow(post));
});
Promise.all(postPromises).then(function(htmls)
{
resolve(html + htmls.join('') + "</tbody></table></div></div><br>");
}).catch(function(error)
{
reject(error);
});
}).catch(function(error)
{
reject(error);
})
});
};
/**
* Displays the edit form for edit posts
*
* @param post_id
*/
const displayRenderForm = function(post_id)
{
return new Promise(function(resolve, reject)
{
sql.getPostById(post_id).then(function(post)
{
const html = "<div class='blogPost'>"+
"<h1 class=\"text-center\">Edit Post</h1>"+
"<form action=\"/admin\" method =\"post\" >"+
" <div class=\"form-group\">\n" +
" <input class=\"form-control\" type=\"text\" name=\"edit_cat_num\" value='" + post.category_id + "' required>\n" +
" <label class=\"w3-label w3-validate\">Category Number</label>\n" +
" </div>"+
" <div class=\"form-group\">\n" +
" <input class=\"form-control\" type=\"text\" name=\"edit_name_new\" value='" + post.name + "' required>\n" +
" <label class=\"w3-label w3-validate\">Post Title</label>\n" +
" </div>"+
" <div class=\"form-group\">\n" +
" <input class=\"form-control\" type=\"text\" name=\"edit_pic\" value='" + post.picture_url + "' required>\n" +
" <label class=\"w3-label w3-validate\">Picture URL</label>\n" +
" </div>"+
" <div class=\"form-group\">\n" +
" <input class=\"form-control\" type=\"date\" name=\"edit_date\" value='" + post.published.toISOString().split('T')[0] + "' required>\n" +
" <label class=\"w3-label w3-validate\">Published Date</label>\n" +
" </div>"+
" <div><input type=\"submit\" name=\"submit\" value=\"Edit\"\n" +
" class=\"btn btn-lg btn-secondary\"/></div>"+
"<input type='hidden' name='edit_post_2' value='" + post_id + "'/>"+
"</form>"+
"</div><br>";
resolve(html);
}).catch(function(error)
{
reject(error);
});
});
};
/**
* Detects if the post data came from the edit form in posts table or edit post
* in the edit post form. Based on this, this function will call one of two functions
*
* @param postData
*/
const processPost = function(postData)
{
return new Promise(function(resolve, reject)
{
var postParsed = qs.parse(postData);
if(postParsed.edit_post)
{
//display edit form
displayRenderForm(postParsed.edit_post).then(function(html)
{
resolve(html);
}).catch(function(error)
{
reject(error);
});
}
else if(postParsed.edit_post_2)
{
sql.editPost(postParsed).then(function(html)
{
resolve(html);
}).catch(function(error)
{
reject(error);
});
}
else
{
resolve("");
}
});
};
module.exports=
{
/**
* Method which calls helper functions which processes post data for editing posts
* and calls a function which displays all the posts in a table
*
* @param postData
*/
main: function(postData)
{
return new Promise(function(resolve, reject)
{
Promise.all([processPost(postData),
postsTable()]).then(function(html)
{
resolve("<br>" + html.join(''));
}).catch(function(error)
{
console.log("error in edit post.js");
reject(error);
})
});
}
};

+ 0
- 54
admin/posts/newPost.html View File

@ -1,54 +0,0 @@
<div class="col-md-6">
<div class="blogPost">
<h1 class="text-center">Server Controls</h1>
<div class="text-center">
<form action="/admin" method="post">
<input type="submit" name="clearCache" value="Clear Cache" class="btn btn-lg btn-secondary" />
<input type="hidden" name="clear_cache" value="true">
</form>
<br>
<form action="/admin" method="post">
<input type="submit" name="gitPull" value="Pull from Git" class="btn btn-lg btn-secondary" />
<input type="hidden" name="git_pull" value="true">
</form>
</div>
</div>
<br>
<div class="blogPost">
<h1 class="text-center">New Post</h1>
<form action="/admin" method ="post" class="p-2">
<!-- Post category -->
<div class="form-group">
<input class="form-control" type="text" name="add_post_category" required>
<label class="w3-label w3-validate">Category</label>
</div>
<!-- Post name -->
<div class="form-group">
<input class="form-control" type="text" name="add_post_name" required>
<label class="w3-label w3-validate">Name</label>
</div>
<!-- Post header picture -->
<div class="form-group">
<input class="form-control" type="text" name="add_post_picture" value="n/a" required>
<label class="w3-label w3-validate">Picture</label>
</div>
<!-- Post date -->
<div class="form-group">
<input class="w3-input" type="date" name="add_post_date" required>
<label class="w3-label w3-validate">Date</label>
</div>
<div class="text-center">
<input type="submit" name="submit" value="Add"
class="btn btn-lg btn-secondary"/>
</div>
</form>
</div>
</div>

+ 0
- 77
admin/posts/newPost.js View File

@ -1,77 +0,0 @@
const utils = require('../../utils/utils.js');
const sql = require('../../utils/sql');
const qs = require('querystring');
const Promise = require('promise');
/**
*
* @param postData
* @return {*|Promise}
*/
const processPost = function(postData)
{
return new Promise(function(resolve, reject)
{
var post = qs.parse(postData);
if(post.add_post_name)
{
var urls = post.add_post_name;
urls = urls.split(" ").join("-");
urls =urls.toLowerCase();
var q = "insert into posts (category_id, picture_url, published, name, url) values ";
q += "('" + post.add_post_category + "', '" + post.add_post_picture +
"', '" + post.add_post_date + "', '" + post.add_post_name + "', '" + urls + "')";
sql.insert(q).then(function()
{
var map = require('../../utils/generateSiteMap');
map.main();
resolve("");
}).catch(function(error)
{
reject(error);
})
}
else if(post.clear_cache)
{
require("../../sites/blog.js").clearCache();
require("../../includes/includes.js").clearCache();
}
else if(post.git_pull)
{
const execSync = require('child_process').execSync;
code = execSync('git pull')
}
else
{
resolve("");
}
});
};
module.exports=
{
/**
*
* @param postData
* @return {*}
*/
main: function(postData)
{
return new Promise(function(resolve, reject)
{
Promise.all([utils.include("./admin/posts/newPost.html"), processPost(postData)]).then(function(html)
{
resolve(html.join(''));
}).catch(function(error)
{
reject(error);
})
});
}
};

+ 49
- 0
blog/category.js View File

@ -0,0 +1,49 @@
/** DB query */
const sql = require('../utils/sql');
/** Object used to render blog post previews */
const blogBodyRenderer = require('./renderBlogPost');
module.exports=
{
/**
* Calls blog and sidebar modules to render blog contents in order
*
* @param requestURL
* @param request
* @returns {Promise}
*/
main: function(requestURL, request, templateContext)
{
return new Promise(function(resolve, reject)
{
var page = request.query.page;
const splitURL = requestURL.split("/");
if(splitURL.length >= 3)
{
sql.getPostsFromCategory(splitURL[2]).then(function(posts)
{
Promise.all([blogBodyRenderer.renderBatchOfPosts(requestURL, posts, page, 5, templateContext),
require('./renderNextBar').main(requestURL, page, 5, posts.length, templateContext)]).then(function()
{
resolve();
});
}).catch(function()
{
delete templateContext["posts"];
resolve();
});
}
else
{
//page is not found but, posts list will be empty
// so 404 will display
resolve();
}
});
}
};

+ 32
- 0
blog/homePage.js View File

@ -0,0 +1,32 @@
const sql = require('../utils/sql');
const blogPostRenderer = require('./renderBlogPost.js');
module.exports=
{
/**
* Renders the previews of recent blog blog and the side bar
*
* @param res
* @param fileName request url
*/
main: function(requestURL, request, templateContext)
{
var page = request.query.page;
return new Promise(function(resolve, reject)
{
sql.getAllPosts().then(function(posts)
{
Promise.all([blogPostRenderer.renderBatchOfPosts(requestURL, posts, page, 5, templateContext),
require('./renderNextBar').main(requestURL, page, 5, posts.length, templateContext)]).then(function()
{
resolve();
});
}).catch(function(error)
{
reject(error);
})
});
}
};

+ 47
- 0
blog/posts.js View File

@ -0,0 +1,47 @@
/** DB queries */
const sql = require('../utils/sql');
/** Object used to render blog post previews */
const blogBodyRenderer = require('./renderBlogPost');
module.exports=
{
/**
* Calls blog and sidebar modules to render blog contents in order
*
* @param requestURL
* @returns {Promise|*}
*/
main: function(requestURL, request, templateContext)
{
return new Promise(function(resolve, reject)
{
const splitURL = requestURL.split("/");
//user entered /category/name/ or /category/name
if(splitURL.length == 3 || splitURL.length == 4)
{
sql.getPost(requestURL).then(function(posts)
{
if(posts.length != 0)
{
blogBodyRenderer.renderBatchOfPosts(requestURL, posts, 1, 1, templateContext).then(function()
{
resolve();
});
}
else
{
resolve();
}
})
}
else
{
//404 will print
resolve();
}
});
}
};

utils/renderBlogPost.js → blog/renderBlogPost.js View File

@ -25,10 +25,10 @@ module.exports=
return new Promise(function(resolve, reject)
{
Promise.all([module.exports.generateBlogPostHeader(post),
module.exports.generateBlogPostBody(post, blocks),
module.exports.generateBlogPostFooter()]).then(function(content)
module.exports.generateBlogPostBody(post, blocks)])
.then(function()
{
resolve(content.join(''));
resolve(post);
}).catch(function(error)
{
reject(error);
@ -45,23 +45,11 @@ module.exports=
*/
generateBlogPostHeader: function(post)
{
var htmlHead = "<div class=\"blogPost\">";
//image
if(!(post.picture_url === "n/a"))
{
htmlHead +="<img src=\"/blogContent/headerImages/" + post.picture_url +
"\" alt=\"\" style=\"width:100%; height:10%\">";
}
htmlHead += "<div class=\"p-4\"><div class=\"\">";
//title
htmlHead += "<h3><b>" + post.name + "</b></h3>";
//date
htmlHead += "<h5><span class=\"w3-opacity\">" +
post.published.toDateString() + "</span></h5>";
htmlHead +="</div>" + "<div class=\"\">";
if(post.picture_url !== "n/a")
post. hasPicture = true;
return htmlHead;
post.published = post.published.toDateString();
return;
},
@ -79,7 +67,11 @@ module.exports=
{
sql.getCategory(post.category_id).then(function(category)
{
resolve(module.exports.generateBlogPostComponent(category[0].url, post.url, blocks));
module.exports.generateBlogPostComponent(category[0].url, post.url, blocks).then(function(html)
{
post.blogBody = html;
resolve();
});
});
})
},
@ -101,6 +93,7 @@ module.exports=
const pathName = "blogContent/posts/" + categoryURL + "/"
+ postURL + ".md";
var markDown = utils.getFileContents(pathName).toString();
markDown = markDown.split("(media/").join("(" + "../blogContent/posts/"
+ categoryURL + "/media/");
@ -160,17 +153,6 @@ module.exports=
})
},
/** Method to return the footer of the html blog post.
*
* @returns {string}
*/
generateBlogPostFooter: function()
{
return "</div></div></div><br><br>";
},
/**
* Converts markdown into html.
*
@ -207,4 +189,58 @@ module.exports=
}
});
},
/**
* Renders a bunch of blog post previews to the user
*
* @param baseURL-- url of the page
* @param posts -- sql data about the blog to render
* @param currentPage -- the current page to render
* @param numOfPosts -- number of blog to render
* @returns {Promise} renders the html of the blog
*/
renderBatchOfPosts: function(baseURL, posts, currentPage, numOfPosts, templateContext)
{
if(typeof currentPage == "undefined")
{
currentPage = 1;
}
else
{
currentPage = Number(currentPage);
}
return new Promise(function(resolve, reject)
{
const promises = [];
for(var i = (currentPage-1) * numOfPosts; i < (currentPage-1) * numOfPosts + numOfPosts; i++)
{
if(i < posts.length)
{
promises.push(new Promise(function(res, rej)
{
module.exports.generateBlogPost(posts[i], posts.length === 1 ? -1: 3).then(function(tempContext)
{
res(tempContext);
}).catch(function(error)
{
rej();
})
}));
}
}
//promises.push(require('../blog/renderNextBar').main(baseURL, currentPage, numOfPosts, blog.length));
Promise.all(promises).then(function(posts)
{
templateContext.posts = posts;
resolve();
}).catch(function(error)
{
reject(error);
});
});
}
}

+ 52
- 0
blog/renderNextBar.js View File

@ -0,0 +1,52 @@
/**
* Determines if the requested page is out of bounds
*
* @param page current page
* @param postsPerPage - number of blog rendered on each page
* @param totalPosts - total blog in this category/total
* @returns {boolean} if this is a valid page
*/
const isValidPage = function(page, postsPerPage, totalPosts)
{
return !(page === 0 || page -1 >= totalPosts/postsPerPage);
};
module.exports=
{
/**
* Renders two buttons on the bottom of the page to
* go to the left or right
*
* Used by the home page and categories pages
* @param baseURL -- base url of page being rendered
* @param currentPage -- current page being rendered
* @param postsPerPage -- number of blog on each page
* @param totalPosts -- total amount of blog in the category
* @returns {Promise} promise which renders the buttons
*/
main: function(baseURL, currentPage, postsPerPage, totalPosts, templateContext)
{
if(typeof currentPage == "undefined")
currentPage = 1;
currentPage = Number(currentPage);
if(!isValidPage(currentPage, postsPerPage, totalPosts))
{
reject("Invalid Page");
}
var nextPage = currentPage + 1;
var previousPage = currentPage - 1;
if (isValidPage(previousPage, postsPerPage, totalPosts))
{
templateContext.newPostsURL = baseURL + "?page=" + previousPage;
}
if (isValidPage(nextPage, postsPerPage, totalPosts))
{
templateContext.oldPostsURL = baseURL + "?page=" + nextPage
}
}
};

+ 1
- 1
blogContent/posts/web-development/node-website-optimization.md View File

@ -131,7 +131,7 @@ Another Good Async Example:
```javascript
/**
* Calls posts and sidebar modules to render blog contents in order
* Calls blog and sidebar modules to render blog contents in order
*
* @param requestURL
* @returns {Promise|*}

+ 60
- 0
docs/sqlConfig.md View File

@ -0,0 +1,60 @@
# MYSQL Schema
![](docs/blogSql.svg)
```mysql
create database jrtechs_blog;
use jrtechs_blog;
create table users(
user_id mediumint unsigned not null AUTO_INCREMENT,
user_name varchar(60) not null,
password char(64) not null,
salt char(64) not null,
primary key(user_id)
);
create table categories(
category_id mediumint unsigned not null AUTO_INCREMENT,
name varchar(60) not null,
url varchar(60) not null,
primary key(category_id)
);
create table posts(
post_id mediumint unsigned not null AUTO_INCREMENT,
category_id mediumint unsigned not null,
picture_url varchar(100) not null,
published datetime not null,
name varchar(100) not null,
url varchar(100) not null,
primary key(post_id)
);
create table downloads(
download_id mediumint unsigned not null AUTO_INCREMENT,
file varchar(40) not null,
name varchar(40) not null,
download_count mediumint not null,
primary key(download_id)
);
create table popular_posts(
popular_post_id mediumint unsigned not null AUTO_INCREMENT,
post_id mediumint unsigned not null,
primary key(popular_post_id)
);
create table traffic_log(
log_id mediumint unsigned not null AUTO_INCREMENT,
url varchar(60) not null,
ip varchar(20) not null,
date datetime not null,
primary key(log_id)
);
grant all on jrtechs_blog.* to blog_user@localhost identified by "password";
```

+ 1
- 1
includes/contact.js View File

@ -190,7 +190,7 @@ module.exports =
* Displays the contact page along with the header, sidebar, and footer.
* This uses the admin header because it doesn't need any minified css
* which has been purged of some css classes which are not used in any
* of the blog posts.
* of the blog blog.
*
* @param request -- main express request
* @param result -- renders the html of the contact page

+ 24
- 7
includes/html/adminHeader.html View File

@ -18,7 +18,7 @@
<link rel="apple-touch-icon" sizes="180x180" href="/includes/img/favicon/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/includes/img/favicon/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/includes/img/favicon/favicon-16x16.png">
<link rel="manifest" href="/includes/img/favicon/site.webmanifest">
<link rel="manifest" href="/includes/img/favicon/site.json">
<link rel="mask-icon" href="/includes/img/favicon/safari-pinned-tab.svg" color="#5bbad5">
<meta name="msapplication-TileColor" content="#da532c">
<meta name="theme-color" content="#ffffff">
@ -29,9 +29,23 @@
}
p{font-size:18px;}
</style>
<script type="text/x-mathjax-config">
MathJax.Hub.Config({
CommonHTML: { linebreaks: { automatic: true, width: "container" }, scale: 75 },
"HTML-CSS": { linebreaks: { automatic: true, width: "container" }, scale: 75 },
SVG: { linebreaks: { automatic: true, width: "container" }, scale: 75 }
});
</script>
<script type="text/javascript" async
src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-MML-AM_CHTML">
</script>
<script src="../../admin/login/login.js"></script>
</head>
<body>
<body class="no-mathjax">
<div class="navbar navbar-expand-lg navbar-dark fixed-top bg-primary" id="mainNav">
<div class="container">
@ -44,10 +58,16 @@
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto">
<li class="nav-item active">
<a class="nav-link" href="https://jrtechs.net">Home <span class="sr-only">(current)</span></a>
<a class="nav-link" href="https://jrtechs.net">Live Blog<span class="sr-only">(current)</span></a>
</li>
<li class="nav-item">
<a class="nav-link" href="/admin">Admin Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/admin/posts">Posts</a>
</li>
<li class="nav-item">
<a class="nav-link" href="https://jrtechs.net/contact/">Contact</a>
<a class="nav-link" href="/admin/downloads">Downloads</a>
</li>
</ul>
<ul class="nav navbar-nav navbar-right">
@ -65,6 +85,3 @@
</div>
</div>
<br><br><br><br><br>
<div class="container">
<div class="row">

+ 1
- 2
includes/html/header.html View File

@ -45,10 +45,9 @@
<script type="text/javascript" async
src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-MML-AM_CHTML">
</script>
\
</head>
<body class="no-mathjax">
<body>
<div class="navbar navbar-expand-lg navbar-dark fixed-top bg-primary" id="mainNav">
<div class="container">

+ 16
- 6
includes/includes.js View File

@ -10,6 +10,10 @@
//used for file IO
const utils = require('../utils/utils.js');
const HEADER_KEY = "header";
const FOOTER_KEY = "footer";
//name of header file
const HEADER_FILE = "includes/html/header.html";
@ -77,9 +81,9 @@ module.exports =
* @param result
* @return {*} a promise retrieved from the utils.include function
*/
printHeader: function()
printHeader: function(templateContext)
{
return utils.include(HEADER_FILE);
return utils.includeInObject(HEADER_KEY, templateContext, HEADER_FILE);
},
@ -88,9 +92,9 @@ module.exports =
*
* @return {*|Promise}
*/
printFooter: function()
printFooter: function(templateContext)
{
return utils.include(FOOTER_FILE);
return utils.includeInObject(FOOTER_KEY, templateContext, FOOTER_FILE);
},
/**
@ -98,9 +102,9 @@ module.exports =
*
* @returns {*|Promise}
*/
printAdminHeader()
printAdminHeader(templateContext)
{
return utils.include(ADMIN_HEADER);
return utils.includeInObject(HEADER_KEY, templateContext, ADMIN_HEADER);
},
@ -138,6 +142,12 @@ module.exports =
},
fetchTemplate: function(templateName)
{
return utils.include("templates/" + templateName);
},
/**Sends the user an image from the specified fileName.
*
* @param result

+ 0
- 59
posts/category.js View File

@ -1,59 +0,0 @@
/** DB query */
const sql = require('../utils/sql');
/** Object used to render blog post previews */
const batchPreview = require('../posts/renderBatchOfPreviewes');
/**
* Renders all posts in a single category
*
* @param resultURL
* @returns {*}
*/
const renderPosts = function(resultURL, page)
{
const splitURL = resultURL.split("/");
if(splitURL.length >= 3)
{
return new Promise(function(resolve, reject)
{
sql.getPostsFromCategory(splitURL[2]).then(function(posts)
{
resolve(batchPreview.main(resultURL, posts, page, 5));
}).catch(function(error)
{
reject(error);
})
});
}
else
{
reject("Page Not Found");
}
};
module.exports=
{
/**
* Calls posts and sidebar modules to render blog contents in order
*
* @param requestURL
* @param request
* @returns {Promise}
*/
main: function(requestURL, request)
{
return new Promise(function(resolve, reject)
{
var page = request.query.page;
Promise.all([renderPosts(requestURL, page),
require("../sidebar/sidebar.js").main()]).then(function(content)
{
resolve(content.join(''));
}).catch(function(err)
{
reject(err);
})
});
}
};

+ 0
- 46
posts/homePage.js View File

@ -1,46 +0,0 @@
const sql = require('../utils/sql');
const batchPreview = require('../posts/renderBatchOfPreviewes');
/**Renders each recent post for the homepage of the website
*
* @param result
* @returns {*|Promise}
*/
var renderRecentPosts = function(baseURL, page)
{
return new Promise(function(resolve, reject)
{
sql.getRecentPostSQL().then(function(posts)
{
resolve(batchPreview.main(baseURL, posts, page, 5));
}).catch(function(error)
{
reject(error);
})
});
};
module.exports=
{
/**
* Renders the previews of recent blog posts and the side bar
*
* @param res
* @param fileName request url
*/
main: function(requestURL, request)
{
var page = request.query.page;
return new Promise(function(resolve, reject)
{
Promise.all([renderRecentPosts(requestURL, page), require("../sidebar/sidebar.js").main()]).then(function(content)
{
resolve(content.join(''));
}).catch(function(error)
{
reject(error);
});
})
}
};

+ 0
- 68
posts/posts.js View File

@ -1,68 +0,0 @@
/** DB queries */
const sql = require('../utils/sql');
/**
* Function responsible for calling the appropriate sql requests to query
* database and serve correct blog post
*
* @param requestURL url requested from client
* @return {*|Promise} returns a resolved promise to preserve execution order
*/
const renderPost = function(requestURL)
{
return new Promise(function(resolve, reject)
{
const splitURL = requestURL.split("/");
//user entered /category/name/ or /category/name
if(splitURL.length == 3 || splitURL.length == 4)
{
sql.getPost(requestURL).then(function(post)
{
if(post != 0)
{
return require("../posts/singlePost.js").renderPost(post);
}
else
{
reject("Page Not Found");
}
}).then(function(html)
{
resolve("<div class='col-md-8'>" + html + "</div>");
}).catch(function(error)
{
reject(error);
})
}
else
{
reject("Page Not Found");
}
});
};
module.exports=
{
/**
* Calls posts and sidebar modules to render blog contents in order
*
* @param requestURL
* @returns {Promise|*}
*/
main: function(requestURL, request)
{
return new Promise(function(resolve, reject)
{
Promise.all([renderPost(requestURL),
require("../sidebar/sidebar.js").main()]).then(function(content)
{
resolve(content.join(''));
}).catch(function(error)
{
reject(error);
})
});
}
};

+ 0
- 55
posts/renderBatchOfPreviewes.js View File

@ -1,55 +0,0 @@
module.exports=
{
/**
* Renders a bunch of blog post previews to the user
*
* @param baseURL-- url of the page
* @param posts -- sql data about the posts to render
* @param currentPage -- the current page to render
* @param numOfPosts -- number of posts to render
* @returns {Promise} renders the html of the posts
*/
main: function(baseURL, posts, currentPage, numOfPosts)
{
if(typeof currentPage == "undefined")
{
currentPage = 1;
}
else
{
currentPage = Number(currentPage);
}
return new Promise(function(resolve, reject)
{
const promises = [];
for(var i = (currentPage-1) * numOfPosts; i < (currentPage-1) * numOfPosts + numOfPosts; i++)
{
if(i < posts.length)
{
promises.push(new Promise(function(res, rej)
{
require("../posts/singlePost.js")
.renderPreview(posts[i]).then(function(html)
{
res(html);
}).catch(function(error)
{
reject(error)
})
}));
}
}
promises.push(require('../posts/renderNextBar').main(baseURL, currentPage, numOfPosts, posts.length));
Promise.all(promises).then(function(content)
{
resolve("<div class='col-md-8'>" + content.join('') + "</div>");
}).catch(function(error)
{
reject(error);
});
});
}
};

+ 0
- 66
posts/renderNextBar.js View File

@ -1,66 +0,0 @@
/**
* Determines if the requested page is out of bounds
*
* @param page current page
* @param postsPerPage - number of posts rendered on each page
* @param totalPosts - total posts in this category/total
* @returns {boolean} if this is a valid page
*/
const isValidPage = function(page, postsPerPage, totalPosts)
{
return !(page === 0 || page -1 >= totalPosts/postsPerPage);
};
module.exports=
{
/**
* Renders two buttons on the bottom of the page to
* go to the left or right
*
* Used by the home page and categories pages
* @param baseURL -- base url of page being rendered
* @param currentPage -- current page being rendered
* @param postsPerPage -- number of posts on each page
* @param totalPosts -- total amount of posts in the category
* @returns {Promise} promise which renders the buttons
*/
main: function(baseURL, currentPage, postsPerPage, totalPosts)
{
return new Promise(function(resolve, reject)
{
if(!isValidPage(currentPage, postsPerPage, totalPosts))
{
reject("Invalid Page");
}
var nextPage = currentPage + 1;
var previousPage = currentPage - 1;
var olderPosts = "";
var newerPosts = "";
if (isValidPage(previousPage, postsPerPage, totalPosts))
{
newerPosts = "<button class=\"btn btn-secondary btn-lg " +
"w3-padding-large w3-white w3-border\" onclick=\"location.href='" +
baseURL + "?page=" + previousPage +
"'\"><b>Newer Posts &raquo;</b></button>";
}
if (isValidPage(nextPage, postsPerPage, totalPosts))
{
olderPosts = "<button class=\"btn btn-secondary btn-lg " +
"w3-padding-large w3-white w3-border\" onclick=\"location.href='" +
baseURL + "?page=" + nextPage +
"'\"><b>Older Posts &raquo;</b></button>";
}
resolve(" <div class=\"row\">\n" +
" <div class=\"col-6\">" + newerPosts + "</div>\n" +
" <div class=\"col-6\"><span class=\"float-right\">" + olderPosts + "</span></div>\n" +
" <br><br></div>");
})
}
};

+ 0
- 28
posts/singlePost.js View File

@ -1,28 +0,0 @@
const postGenerator = require('../utils/renderBlogPost.js');
module.exports=
{
/**
* Renders a preview of the post with a link to view more
*
* @param res
* @param post
*/
renderPreview: function(post)
{
return postGenerator.generateBlogPost(post, 3);
},
/**
* renderPost() displays a single blog post in it's entirety
*
* @param res result sent to user
* @param post sql data about the blog post
* @return {*|Promise}
*/
renderPost: function(post)
{
return postGenerator.generateBlogPost(post, -1);
}
};

+ 0
- 38
sidebar/categoriesSideBar.js View File

@ -1,38 +0,0 @@
const sql = require('../utils/sql');
module.exports=
{
/**
* Responsible for querying the database and displaying all
* categories that the blog has in the sidebar
*
* @param res
* @return {*|Promise}
*/
main: function()
{
return new Promise(function(resolve, reject)
{
var content = "<br><br><div class=\"container\">";
content += "<div class=\"list-group\">";
content += " <a href=\"#\" class=\"list-group-item list-group-item-action flex-column align-items-start active\">\n" +
" <h5 class=\"mb-1\">Categories</h5>\n" +
" </a>";
sql.getCategories().then(function(categories)
{
categories.forEach(function(cat)
{
content += "<a class=\"list-group-item\" href='/category/" + cat.url + "'>" + cat.name + "<br></a>";
});
content += "</div></div><br>";
resolve(content);
}).catch(function(error)
{
reject(error);
});
});
}
};

+ 0
- 35
sidebar/popularPosts.js View File

@ -1,35 +0,0 @@
const sql = require('../utils/sql');
module.exports=
{
/**Renders the popular posts sidebar.
*
* @param res
* @returns {*|Promise}
*/
main: function(res)
{
return new Promise(function(resolve, reject)
{
res.write("<div class=\"w3-card w3-margin\">");
res.write("<div class=\"w3-container w3-padding\">" +
"<h4>Popular Posts</h4></div>");
res.write("<div class=\"w3-sidebar w3-bar-block\">");
sql.getPopularPosts().then(function(posts)
{
posts.forEach(function(cat)
{
console.log(cat);
res.write("<a class=\"w3-bar-item w3-button\" href='"
+ url + "'>" + p.name + "<br></a>");
});
res.write("</div></div>");
resolve();
});
});
}
};

+ 0
- 10
sidebar/projectSidebar.html View File

@ -1,10 +0,0 @@
<div class="container">
<div class="list-group">
<a href="#" class="list-group-item list-group-item-action flex-column align-items-start active">
<h5 class="mb-1">Project Sites</h5>
</a>
<a class="list-group-item" href='https://jrtechs.net/steam/'>Steam Graph Analysis<br></a>
<a class="list-group-item" href='https://jrtechs.me/'>Portfolio<br></a>
<a class="list-group-item" href='https://clubpanda.jrtechs.net/'>Club Panda<br></a>
</div>
</div><br>

+ 0
- 38
sidebar/recentPosts.js View File

@ -1,38 +0,0 @@
const Promise = require('promise');
const sql = require('../utils/sql');
module.exports=
{
/** Renders the the recent post sidebar.
*
* @returns {*|Promise}
*/
main: function()
{
return new Promise(function(resolve, reject)
{
var content = "<div class=\"container\">";
content +="<div class=\"list-group\">";
content +=" <a href=\"#\" class=\"list-group-item list-group-item-action flex-column align-items-start active\">\n" +
" <h5 class=\"mb-1\">Recent Posts</h5>\n" +
" </a>";
sql.getRecentPosts().then(function(posts)
{
posts.forEach(function(p)
{
var url = '/' + p.category + '/' + p.url;
content += "<a class=\"list-group-item\" href='"
+ url + "'>" + p.name + "<br></a>";
});
content +="</div></div>";
resolve(content);
}).catch(function(error)
{
reject(error);
})
});
}
};

+ 53
- 12
sidebar/sidebar.js View File

@ -1,22 +1,63 @@
const utils = require('../utils/utils.js');
const sql = require('../utils/sql');
const TEMPLATE_FILE = "blog/sideBar.html";
const includes = require('../includes/includes.js');
const getInformationForRecentPosts = function(templateContext)
{
return new Promise(function(resolve, reject)
{
sql.getRecentPosts().then(function(posts)
{
posts.forEach(function(p)
{
p.url = '/' + p.category + '/' + p.url;
});
templateContext.recentPosts = posts;
resolve();
}).catch(function(error)
{
reject(error);
})
});
};
const getInformationForCategories = function(templateContext)
{
return new Promise(function(resolve, reject)
{
sql.getCategories().then(function(categories)
{
categories.forEach(function(cat)
{
cat.url = "/category/" + cat.url;
});
templateContext.categories = categories;
resolve();
}).catch(function(error)
{
reject(error);
});
});
};
module.exports=
{
/** Method which renders the entire sidebar through calling
* appropriate widget js files.
*
* @param res
* @returns {*|Promise}
*/
main: function()
main: function(templateContext)
{
return new Promise(function(resolve, reject)
{
Promise.all([utils.include("sidebar/projectSidebar.html"),
require("../sidebar/recentPosts.js").main(),
require("../sidebar/categoriesSideBar.js").main()]).then(function(content)
Promise.all([includes.fetchTemplate(TEMPLATE_FILE),
getInformationForRecentPosts(templateContext),
getInformationForCategories(templateContext)])
.then(function(content)
{
resolve("<div class=\"col-md-4\">" + content.join('') + "</div>");
templateContext.sideBar = content[0];
resolve();
}).catch(function(error)
{
reject(error);

+ 12
- 6
sites/admin.js View File

@ -1,10 +1,15 @@
//sending static content
const includes = require('../includes/includes.js');
//used for file IO
const utils = require('../utils/utils.js');
//used to append static content to result
const contentLoader = require('../includes/staticContentServer.js');
const whiskers = require('whiskers');
/**
* @author Jeffery Russell 11-3-18
*
@ -13,7 +18,7 @@ const contentLoader = require('../includes/staticContentServer.js');
module.exports=
{
/**
* Calls posts and sidebar modules to render blog contents in order
* Calls blog and sidebar modules to render blog contents in order
*
* @param requestURL
* @returns {Promise|*}
@ -34,11 +39,13 @@ module.exports=
const file = "../admin/admin.js";
Promise.all([includes.printAdminHeader(),
require(file).main(request, clientAddress),
includes.printFooter()]).then(function(content)
var templateContext = Object();
Promise.all([includes.printAdminHeader(templateContext),
require(file).main(request, clientAddress, templateContext, filename),
includes.printFooter(templateContext),
includes.fetchTemplate("admin/adminMain.html")]).then(function(content)
{
result.write(content.join(''));
result.write(whiskers.render(content.join(''), templateContext));
result.end();
}).catch(function(err)
@ -47,6 +54,5 @@ module.exports=
throw err;
});
}
}
};

+ 21
- 11
sites/blog.js View File

@ -4,6 +4,10 @@ const includes = require('../includes/includes.js');
//used to append static content to result
const contentLoader = require('../includes/staticContentServer.js');
const whiskers = require('whiskers');
const TEMPLATE_FILE="blog/blogMain.html";
//caching program to make the application run faster
const cache = require('memory-cache');
@ -49,36 +53,42 @@ module.exports=
const html = cache.get(filename + "?page=" + page);
result.writeHead(200, {'Content-Type': 'text/html'});
if (html == null) {
if (html == null)
{
var file = "";
if (filename === '' || filename === '/')
{
file = "../posts/homePage.js";
file = "../blog/homePage.js";
}
else
{
var urlSplit = filename.split("/");
if (urlSplit.length >= 2 && urlSplit[1] === 'category') //single category page
file = "../posts/category.js";
file = "../blog/category.js";
else
{
file = "../posts/posts.js";
page = 1; // all posts are single page, everyone must be one to ensure
file = "../blog/posts.js";
page = 1; // all blog are single page, everyone must be one to ensure
// cache is not tricked into storing same blog post a ton of times
}
}
Promise.all([includes.printHeader(),
require(file).main(filename, request),
includes.printFooter()]).then(function (content)
var templateContext = Object();
Promise.all([includes.fetchTemplate(TEMPLATE_FILE),
includes.printHeader(templateContext),
includes.printFooter(templateContext),
require(file).main(filename, request, templateContext),
require("../sidebar/sidebar.js").main(templateContext)])
.then(function (content)
{
result.write(content.join(''));
var html = whiskers.render(content[0], templateContext);
result.write(html);
result.end();
cache.put(filename + "?page=" + page, content.join(''));
cache.put(filename + "?page=" + page, html);
}).catch(function (err)
{
console.log(err);
cache.del(filename + "?page=" + page);
utils.print404().then(function(content)
{

+ 2
- 1
sites/projects.js View File

@ -14,7 +14,7 @@ const contentLoader = require('../includes/staticContentServer.js');
module.exports=
{
/**
* Calls posts and sidebar modules to render blog contents in order
* Calls blog and sidebar modules to render blog contents in order
*
* @param requestURL
* @returns {Promise|*}
@ -32,6 +32,7 @@ module.exports=
if (!contentLoader.serveStaticContent(request, result, filename, "/blogContent/projects"))
{
console.log(filename);
//do something?
}
}

+ 66
- 0
templates/admin/adminDownloads.html View File

@ -0,0 +1,66 @@
<div class="row">
<!-- Add Download -->
<div class="col-md-6">
<div class="blogPost">
<h1 class="text-center">Add Download</h1>
<form action="/admin/downloads/" method ="post" class="p-2">
<div class="form-group">
<input class="form-control" type="text" name="add_download_name" required>
<label>Download Name</label>
</div>
<div class="form-group">
<input class="form-control" type="text" name="add_download_file" required>
<label>File name</label>
</div>
<div class="text-center">
<input type="submit" name="add_download" value="Add Download"
class="btn btn-lg btn-secondary"/>
</div>
</form>
</div>
<br>
</div>
<!-- Downloads -->
<div class="col-md-6">
<div class='blogPost'>
<h1 class="text-center">Downloads</h1>
<div class="">
<table class="table table-striped">
<thead class="thead-dark">
<tr>
<td>Download Name</td>
<td>File</td>
<td>Download Count</td>
<td>Delete</td>
</tr>
</thead>
<tbody>
{for download in downloads}
<tr>
<td>
{download.name}
</td>
<td>
{download.file}
</td>
<td>
{download.download_count}
</td>
<td>
<form action="/admin/downloads/" method ="post" >
<input type="submit" name="submit" value="Delete" class="btn btn-secondary"/>
<input type='hidden' name='delete_download' value='{download.download_id}' />
</form>
</td>
</tr>
{/for}
</tbody>
</table>
</div>
</div>
</div>
</div>

+ 103
- 0
templates/admin/adminHome.html View File

@ -0,0 +1,103 @@
<div class="row">
<div class="col-md-6">
<div class="blogPost">
<h1 class="text-center">Server Controls</h1>
<div class="text-center">
<form action="/admin" method="post">
<input type="submit" name="clearCache" value="Clear Cache" class="btn btn-lg btn-secondary" />
<input type="hidden" name="clear_cache" value="true">
</form>
<br>
<form action="/admin" method="post">
<input type="submit" name="gitPull" value="Pull from Git" class="btn btn-lg btn-secondary" />
<input type="hidden" name="git_pull" value="true">
</form>
</div>
</div>
</div>
<div class="col-md-6">
<div class="blogPost">
<h1 class="text-center">Add Category</h1>
<form action="/admin" method ="post" class="p-2">
<div class="form-group">
<input class="form-control" type="text" name="add_category" required>
<label>Category</label>
</div>
<div class="text-center">
<input type="submit" name="submit" value="Add"
class="btn btn-lg btn-secondary"/>
</div>
</form>
</div>
<br>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="blogPost">
<h1 class="text-center">New Post</h1>
<form action="/admin" method ="post" class="p-2">
<!-- Post category -->
<div class="form-group">
<input class="form-control" type="text" name="add_post_category" required>
<label class="w3-label w3-validate">Category</label>
</div>
<!-- Post name -->
<div class="form-group">
<input class="form-control" type="text" name="add_post_name" required>
<label class="w3-label w3-validate">Name</label>
</div>
<!-- Post header picture -->
<div class="form-group">
<input class="form-control" type="text" name="add_post_picture" value="n/a" required>
<label class="w3-label w3-validate">Picture</label>
</div>
<!-- Post date -->
<div class="form-group">
<input class="w3-input" type="date" name="add_post_date" required>
<label class="w3-label w3-validate">Date</label>
</div>
<div class="text-center">
<input type="submit" name="submit" value="Add"
class="btn btn-lg btn-secondary"/>
</div>
</form>
</div>
</div>
<div class="col-md-6">
<h1 class="text-center">Categories</h1>
<div class="blogPost">
<table class="table table-striped">
<thead class="thead-dark">
<tr>
<td>Name</td>
<td>URL</td>
<td>Edit</td>
</tr>
</thead>
<tbody>
{for cat in categories}
<tr>
<td>{cat.name}</td>
<td>{cat.url}</td>
<td>{cat.category_id}</td>
</tr>>
{/for}
</tbody>
</table>
</div>
</div>
</div>

+ 65
- 0
templates/admin/adminMain.html View File

@ -0,0 +1,65 @@
{header}
<div class="container">
{if loggedIn}
{>adminPage}
{else}
{if goodLoginAttempt}
<meta http-equiv="refresh" content="0">
{/if}
<div class="row">
{if banned}
<div class="align-content-center">
<br>
<h1>Yikes</h1>
<br>
<img src="/includes/img/404.jpg" alt="Page not found meme" width="70%" />
<br><br><br><br>
</div>
{else}
<div class="col-md-8">
<div class="blogPost">
<div class="text-center">
<h2>Login</h2>
</div>
<form action="/admin/" method ="post" class="p-2">
<div class="form-group">
<label for="username1">User Name</label>
<input class="form-control" type="text" id="username1" name="username" placeholder="Enter username" required>
</div>
<div class="form-group">
<label for="password1">Password</label>
<input class="form-control" type="password" name="password" id="password1" placeholder="Password" required>
</div>
<div class="text-center">
<button class="btn btn-lg btn-secondary">Login</button>
</div>
<br>
</form>
{if invalid}
<p>
Invalid login attempt.
</p>
{/if}
<!--
/\_/\ ___
= o_o =_______ \ \
__^ __( \.__) )
(@)<_____>__(_____)____/
-->
</div>
<br /><br />
</div>
{/if}
</div>
{/if}
</div>
{footer}

+ 68
- 0
templates/admin/adminPosts.html View File

@ -0,0 +1,68 @@
<div class="row">
<div class="col-12">
{if editPost}
<div class='blogPost'>
<h1 class="text-center">Edit Post</h1>
<form action="/admin/posts" method ="post" >
<div class="form-group">
<input class="form-control" type="text" name="edit_cat_num" value='{post.category_id}' required>
<label class="w3-label w3-validate">Category Number</label>
</div>
<div class="form-group">
<input class="form-control" type="text" name="edit_name_new" value='{post.name}' required>
<label class="w3-label w3-validate">Post Title</label>
</div>
<div class="form-group">
<input class="form-control" type="text" name="edit_pic" value='{post.picture_url}' required>
<label class="w3-label w3-validate">Picture URL</label>
</div>
<div class="form-group">
<input class="form-control" type="date" name="edit_date" value='{post.published}' required>
<label class="w3-label w3-validate">Published Date</label>
</div>
<div>
<input type="submit" name="submit" value="Edit" class="btn btn-lg btn-secondary"/>
</div>
<input type='hidden' name='edit_post_2' value='{post.post_id}'/>
</form>
</div>
<br>
{/if}
</div>
</div>
<div class="row">
<div class="col-12">
<div class='blogPost p-2'>
<h1 class="text-center">Posts</h1>
<table class="table table-striped">
<thead class="thead-dark">
<tr>
<td>Category #</td>
<td>Name</td>
<td>Header Picture</td>
<td>Date</td>
<td>Edit</td>
</tr>
</thead>
<tbody>
{for post in posts}
<tr>
<td>{post.category_id}</td>
<td>{post.name}</td>
<td>{post.picture_url}</td>
<td>{post.published}</td>
<td>
<form action="/admin/posts" method ="post" >
<input type="submit" name="submit" value="Edit" class="btn btn-secondary"/>
<input type='hidden' name='edit_post' value='{post.post_id}' />
</form>
</td>
</tr>
{/for}
</tbody>
</table>
</div>
<br>
</div>
</div>

+ 62
- 0
templates/blog/blogMain.html View File

@ -0,0 +1,62 @@
{header}
<div class="container">
<div class="row">
<div class="col-md-8 col-12">
{for post in posts}
<div class="blogPost">
{if post.hasPicture}
<img src="/blogContent/headerImages/{post.picture_url}" style="width:100%;">
{/if}
<div class="p-4">
<h3><b>{post.name}</b></h3>
<h5>
<span class="w3-opacity">{post.published}</span>
</h5>
{post.blogBody}
</div>
</div>
<br>
<br>
{else}
<div class="row p-lg-0">
<h1 class="align-content-center">Page Not Found</h1>
<div class="align-content-center">
<img src="/includes/img/404.jpg" alt="Page not found" width="70%" />
</div>
</div>
<br><br>
{/for}
<div class="row">
<div class="col-6">
{if newPostsURL}
<button class="btn btn-secondary btn-l" onclick="location.href='{newPostsURL}'">
<b>Older Posts &raquo;</b>
</button>
{/if}
</div>
<div class="col-6">
{if oldPostsURL}
<span class="float-right">
<button class="btn btn-secondary btn-l" onclick="location.href='{oldPostsURL}'">
<b>Older Posts &raquo;</b>
</button>
</span>
{/if}
</div>
</div>
<br>
</div>
<div class="col-md-4 col-4">
{>sideBar}
</div>
</div>
</div>
{footer}

+ 36
- 0
templates/blog/sideBar.html View File

@ -0,0 +1,36 @@
<div class="container">
<div class="list-group">
<a href="#" class="list-group-item list-group-item-action flex-column align-items-start active">
<h5 class="mb-1">Project Sites</h5>
</a>
<a class="list-group-item" href='https://jrtechs.net/steam/'>Steam Graph Analysis<br></a>
<a class="list-group-item" href='https://jrtechs.me/'>Portfolio<br></a>
<a class="list-group-item" href='https://clubpanda.jrtechs.net/'>Club Panda<br></a>
</div>
</div>
<br>
<div class="container">
<div class="list-group">
<a href="#" class="list-group-item list-group-item-action flex-column align-items-start active">
<h5 class="mb-1">Recent Posts</h5>
</a>
{for recentPost in recentPosts}
<a class="list-group-item" href='{recentPost.url}'>{recentPost.name}<br></a>
{/for}
</div>
</div>
<br>
<div class="container">
<div class="list-group">
<a href="#" class="list-group-item list-group-item-action flex-column align-items-start active">
<h5 class="mb-1">Categories</h5>
</a>
{for cat in categories}
<a class="list-group-item" href='{cat.url}'>{cat.name}<br></a>
{/for}
</div>
</div>
<br>

+ 2
- 9
utils/sql.js View File

@ -130,14 +130,7 @@ module.exports=
fetch(q2).then(function (result_posts)
{
if(result_posts != 0)
{
resolve(result_posts[0]);
}
else
{
resolve(0);
}
resolve(result_posts);
});
}
else
@ -163,7 +156,7 @@ module.exports=
/**
* Function which currently returns all posts of a particular
* Function which currently returns all blog of a particular
* category from the database
* @param requestURL
* @return {*|Promise}

+ 17
- 0
utils/utils.js View File

@ -31,6 +31,23 @@ module.exports=
});
},
includeInObject: function(key, context, fileName)
{
return new Promise(function(resolve, reject)
{
module.exports.include(fileName).then(function(result)
{
context[key] = result;
resolve();
}).catch(function(error)
{
context[key] = "File Not Found";
reject(error);
console.log(error);
})
})
},
/**
* Method which return the contents of a file as a string

Loading…
Cancel
Save