Merge pull request #11 from PretendoNetwork/dev

added basically everything for the backend except pnid stuff
This commit is contained in:
mrjvs
2018-10-16 18:40:39 +02:00
committed by GitHub
27 changed files with 980 additions and 134 deletions

2
.gitignore vendored
View File

@@ -59,4 +59,4 @@ typings/
# keep config and blog posts out of this
config.json
posts/
static-text.json

View File

@@ -2,18 +2,14 @@
"http": {
"port": 8080
},
"blog": {
"authors": [
{
"name": "redducks",
"description": "passionate and violence loving programmer",
"image": "https://img00.deviantart.net/a0f8/i/2012/356/3/3/generic_anime_girl__1_by_light1523-d5ou547.png"
}
]
},
"database": {
"url": "mongodb://localhost:27017/pretendo_website"
},
"contactWebhook": {
"port": 443,
"host": "host.com",
"path": "/webhookurl"
},
"secrets": {
"session": "session secret here"
}

View File

@@ -1,17 +1,12 @@
/*
common.js -
common page functionality.
api.js -
common api returns
*/
// shows 404 template.
function sendDefault404(res) {
res.status(404).send('404');
}
// use for any api return. it has basic layout used for every return.
function sendApiReturn(res, data, errors) {
function sendReturn(res, data, errors) {
res.status(200).json(
// combine 2 objects
Object.assign({
@@ -64,8 +59,7 @@ function sendApiError(res, code, errors) {
}
module.exports = {
sendDefault404,
sendApiReturn,
sendReturn,
sendApi404,
sendApiGenericError,
sendApiError,

View File

@@ -1,59 +0,0 @@
/*
blog-helper.js -
blog post helper functionality
*/
// imports
const fs = require('fs');
const authors = require('../config.json').blog.authors;
const showdown = require('showdown');
const converter = new showdown.Converter({metadata: true});
function getAuthorByID(authorId) {
return authors[authorId];
}
function getBlogPostAsMarkdown(postId) {
try {
return fs.readFileSync(`posts/${postId}.md`);
} catch(exception) {
return null;
}
}
function getBlogPostAsHtml(postId) {
const markdown = getBlogPostAsMarkdown(postId);
if (!markdown) return;
return converter.makeHtml(markdown);
}
function getBlogPostExpressReady(postId) {
const markdown = getBlogPostAsMarkdown(postId);
if (!markdown) return;
const html = converter.makeHtml(markdown);
const metadata = converter.getMetadata();
const hbsObject = {
content: html,
date: metadata.releaseDate,
category: metadata.category,
author: getAuthorByID(metadata.authorId)
};
return hbsObject;
}
function writeMarkdownToFile(text) {
const length = fs.readdirSync('posts').length;
fs.writeFileSync(`posts/${length}.md`, text);
}
module.exports = {
getBlogPostAsHtml,
getBlogPostAsMarkdown,
getBlogPostExpressReady,
getAuthorByID,
writeMarkdownToFile
};

15
helpers/util.js Normal file
View File

@@ -0,0 +1,15 @@
/*
util.js -
small commonly used utilities
*/
// shows 404 template. takes express response object
function sendDefault404(res) {
res.status(404).send('404');
}
module.exports = {
sendDefault404
};

View File

@@ -6,23 +6,23 @@ Middleware file for authentication checking
*/
// imports
const common = require('../helpers/common');
const apiHelper = require('../helpers/api');
// middleware to use if admin authentication is required
function adminAuthenticationRequired(req, res, next) {
function adminAuthNeeded(req, res, next) {
if (req.isAuthenticated() && req.user.role && req.user.role === 'admin') {
return next();
} else {
common.sendApiAuthError(res);
apiHelper.sendApiAuthError(res);
}
}
// middleware to use if authentication
function authenticationOptional(req, res, next) {
// middleware to use if authentication is optional
function authOptional(req, res, next) {
return next();
}
module.exports = {
adminAuthenticationRequired,
authenticationOptional
adminAuthNeeded,
authOptional
};

View File

@@ -20,6 +20,7 @@ const adminUserSchema = new mongoose.Schema({
unique: true,
trim: true
},
// non hashed, gets hashed at save
password: {
type: String,
required: [true, 'Password is required.'],

104
models/blog-post.js Normal file
View File

@@ -0,0 +1,104 @@
/*
blog-post.js -
file containing the model file for a blog post
*/
// imports
const mongoose = require('mongoose');
const postAuthor = require('./post-author').postAuthorModel;
const showdown = require('showdown');
const moment = require('moment');
const converter = new showdown.Converter();
converter.setFlavor('github');
// admin user database layout
const blogPostSchema = new mongoose.Schema({
// html in content
content: {
type: String,
required: [true, 'Content is required.'],
trim: true
},
// title of blog post
name: {
type: String,
required: [true, 'Name is required']
},
// short description of blog post
short: {
type: String,
required: [true, 'Short version is required']
},
meta: {
slug: {
type: String,
required: [true, 'Author is required'],
trim: true
},
author: {
type: String,
required: [true, 'Author is required'],
trim: true
},
date: {
type: Date,
default: () => {
return new Date(moment(new Date(), 'YYYY-MM-DD'));
}
},
category: {
type: String,
required: [true, 'category is required'],
trim: true
}
}
});
blogPostSchema.methods.postTemplate = function(callback) {
const self = this;
postAuthor.findById(this.meta.author, function (err, author) {
callback(err, {
content: self.content,
title: self.name,
date: self.meta.date,
category: self.meta.category,
author: author.authorTemplate()
});
});
};
blogPostSchema.methods.postShortTemplate = function() {
return {
content: this.short,
title: this.name,
url: moment(this.meta.date, 'YYYY-MM-DD') + '/' + this.meta.slug
};
};
blogPostSchema.statics.markdownToHtml = function(markdown) {
return converter.makeHtml(markdown);
};
blogPostSchema.statics.getPost = function(date, slug, callback) {
return blogPostModel.findOne({
'meta.date': date,
'meta.slug': slug
}, callback);
};
// not tested
blogPostSchema.statics.latestPostsShortTemlate = function(amount, callback) {
blogPostModel.find({}).sort({'meta.date': 'desc'}).exec(function(err, posts) {
if (err) return callback(err);
let out = [];
for (let i = 0, l = posts.length; i < ( amount+1 < l ? amount+1 : l); i++)
out += posts[i].postShortTemplate();
callback(err, out);
});
};
const blogPostModel = mongoose.model('blogPost', blogPostSchema);
module.exports = {
blogPostModel,
blogPostSchema
};

43
models/post-author.js Normal file
View File

@@ -0,0 +1,43 @@
/*
post-author.js -
file containing the model for authors
*/
// imports
const mongoose = require('mongoose');
// post author database layout
const postAuthorSchema = new mongoose.Schema({
name: {
type: String,
required: [true, 'Name is required'],
trim: true
},
description: {
type: String,
required: [true, 'Description is required'],
trim: true
},
// profile picture image link
image: {
type: String,
trim: true
}
});
postAuthorSchema.methods.authorTemplate = function() {
return {
name: this.name,
description: this.description,
profilePicture: this.image
};
};
const postAuthorModel = mongoose.model('postAuthor', postAuthorSchema);
module.exports = {
postAuthorModel,
postAuthorSchema
};

36
models/progress-list.js Normal file
View File

@@ -0,0 +1,36 @@
/*
progress-list.js -
file containing the model for progress list
*/
// imports
const mongoose = require('mongoose');
// progress text schema
const progressListSchema = new mongoose.Schema({
state: {
type: Number, /* 0 - nothing, 1 - no support, 2 - partially working, 3 - works */
default: 0
},
isGame: {
type: Boolean, /* true - is game, false - is backend service */
required: [true, 'isGame is required']
},
title: {
type: String,
required: [true, 'Title is required']
},
description: {
type: String,
required: [true, 'Description is required']
}
});
const progressListModel = mongoose.model('progress', progressListSchema);
module.exports = {
progressListModel,
progressListSchema
};

5
package-lock.json generated
View File

@@ -1746,6 +1746,11 @@
}
}
},
"moment": {
"version": "2.22.2",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.22.2.tgz",
"integrity": "sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y="
},
"mongodb": {
"version": "3.1.6",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.1.6.tgz",

View File

@@ -22,6 +22,7 @@
"express": "^4.16.2",
"express-handlebars": "^3.0.0",
"express-session": "^1.15.6",
"moment": "^2.22.2",
"mongoose": "^5.3.2",
"mongoose-unique-validator": "^2.0.2",
"passport": "^0.4.0",

View File

@@ -42,7 +42,7 @@ module.exports = (app) => {
});
}
));
//Configuring app to have sessions
//Configuring app to have sessions, dont change since it would break everything
passport.serializeUser(function(user, done) {
done(null, user.id);
});

View File

@@ -1,18 +1,24 @@
/*
admin.js -
file for handling admin panel routes
file for handling admin api.
*/
// imports
const router = require('express').Router();
const passport = require('passport');
const common = require('../helpers/common');
const moment = require('moment');
const apiHelper = require('../helpers/api');
const adminUserMiddleware = require('../middleware/admin-authentication');
const adminUser = require('../models/admin-user');
// display admin panel
// database models
const adminUser = require('../models/admin-user');
const blogPost = require('../models/blog-post');
const postAuthor = require('../models/post-author');
const progressList = require('../models/progress-list');
// renders admin.hbs
router.get('/admin', (req, res) => {
res.render('admin');
});
@@ -23,20 +29,21 @@ router.get('/admin', (req, res) => {
* signs admin user in
*
* post {
* username - username of admin account
* password - password of admin account
* username
* password
* }
* return {
* code: http code
* success: boolean - true if login succesfull
* username: undefined | string - username if login was successfull
* success: boolean
* username: undefined | string - only if login was successfull
* role: undefined | string - role of user if login was successfull
* errors: Strings[messages] - not yet :(
* }
*/
// TODO make login somehow display errors in correct format.
// middleware does the authentication work. this just returns a success
router.post('/admin/api/v1/login', passport.authenticate('adminUserStrategy'), function (req, res) {
common.sendApiReturn(res, {
apiHelper.sendReturn(res, {
username: req.user.username,
role: req.user.role ? req.user.role : undefined
});
@@ -60,10 +67,10 @@ router.post('/admin/api/v1/login', passport.authenticate('adminUserStrategy'), f
* errors: Strings[messages]
* }
*/
router.post('/admin/api/v1/register', adminUserMiddleware.adminAuthenticationRequired, (req, res) => {
router.post('/admin/api/v1/register', adminUserMiddleware.adminAuthNeeded, (req, res) => {
if (!req.body) {
// no post body
common.sendApiGenericError(res);
apiHelper.sendApiGenericError(res);
return;
}
@@ -73,20 +80,80 @@ router.post('/admin/api/v1/register', adminUserMiddleware.adminAuthenticationReq
password
});
// saving to database
newUser.save().then(() => {
// successfull
common.sendApiReturn(res, {
apiHelper.sendReturn(res, {
username: req.user.username,
role: req.user.role ? req.user.role : undefined
});
return;
}).catch((rejection) => {
// TODO format exception so it doesnt have a huge list of errors
common.sendApiError(res, 500, [rejection]);
apiHelper.sendApiError(res, 500, [rejection]);
return;
});
});
/*
* /admin/api/v1/removeadmin
* - requires admin auth
*
* registers a new admin user
*
* post {
* id - id of the admin user
* }
* return {
* code: httpcode
* success: boolean - true if delete was successull
* errors: Strings[messages]
* }
*/
router.post('/admin/api/v1/removeadmin', adminUserMiddleware.adminAuthNeeded, (req, res) => {
if (!req.body) {
// no post body
apiHelper.sendApiGenericError(res);
return;
}
const { id } = req.body;
adminUser.adminUserModel.findByIdAndDelete(id, (err) => {
if (err) return apiHelper.sendApiError(res, 500, [err]);
// successfull
apiHelper.sendReturn(res, {});
});
});
/*
* /admin/api/v1/listadmins
* - requires admin auth
*
* gets list of admins
*
* return {
* code: httpcode
* success: boolean - true if delete was successull
* errors: Strings[messages]
* }
*/
router.get('/admin/api/v1/listadmins', adminUserMiddleware.adminAuthNeeded, (req, res) => {
adminUser.adminUserModel.find({}, (err, admins) => {
// TODO format exception so it doesnt have a huge list of errors
if (err) return apiHelper.sendApiError(res, 500, [err]);
// formats admin list and removes password hash
const output = [];
for (let i = 0, l = admins.length; i < l; i++) {
admins[i].password = undefined;
output.push(admins[i]);
}
apiHelper.sendReturn(res, {
admins: output
});
});
});
/*
* /admin/api/v1/check
*
@@ -100,8 +167,8 @@ router.post('/admin/api/v1/register', adminUserMiddleware.adminAuthenticationReq
* errors: Strings[messages]
* }
*/
router.get('/admin/api/v1/check', adminUserMiddleware.authenticationOptional, (req, res) => {
common.sendApiReturn(res, {
router.get('/admin/api/v1/check', adminUserMiddleware.authOptional, (req, res) => {
apiHelper.sendReturn(res, {
isAuthed: req.user ? true : false,
role: req.user ? (req.user.role ? req.user.role : undefined) : undefined
});
@@ -118,9 +185,283 @@ router.get('/admin/api/v1/check', adminUserMiddleware.authenticationOptional, (r
* errors: Strings[messages]
* }
*/
router.get('/admin/api/v1/logout', adminUserMiddleware.adminAuthenticationRequired, (req, res) => {
router.get('/admin/api/v1/logout', adminUserMiddleware.adminAuthNeeded, (req, res) => {
req.logout();
common.sendApiReturn(res, {});
apiHelper.sendReturn(res, {});
});
/*
* /admin/api/v1/newpost
*
* posts a new blog post
*
* post {
* content - content of the blog post in markdown
* title - title of the blog post
* author - id of the author
* short - short description of content in plain text
* category - category name of the blog post
*
* }
* return {
* code: http code
* success: boolean - true if login succesfull
* url: string | undefined - url of the blog post if successfull
* errors: Strings[messages]
* }
*/
router.post('/admin/api/v1/newpost', adminUserMiddleware.adminAuthNeeded, function (req, res) {
if (!req.body) return apiHelper.sendApiGenericError(res);
const { content, title, author, category, short } = req.body;
const newBlogPost = new blogPost.blogPostModel({
content: blogPost.blogPostModel.markdownToHtml(content),
name: title,
short,
meta: {
author,
category,
slug: title // convert title to slug
.trim()
.replace(/\s/g, '-')
.replace(/[^A-z0-9-]/g, '')
.toLowerCase()
}
});
// saving post to database
newBlogPost.save().then((post) => {
apiHelper.sendReturn(res, {
url: moment(post.meta.date, 'YYYY-MM-DD') + '/' + post.meta.slug
});
}).catch((rejection) => {
// TODO format exception so it doesnt have a huge list of errors
apiHelper.sendApiError(res, 500, [rejection]);
return;
});
});
/*
* /admin/api/v1/editpost
*
* edits a blog post
*
* post {
* id - id of the blog post to be edited
* content - content of the blog post IN HTML
* title - title of the blog post
* author - id of the author
* short - short description of content in plain text
* category - category name of the blog post
* }
* return {
* code: http code
* success: boolean - true if login succesfull
* url: string | undefined - url of the blog post if successfull
* errors: Strings[messages]
* }
*/
router.post('/admin/api/v1/editpost', adminUserMiddleware.adminAuthNeeded, function (req, res) {
if (!req.body) return apiHelper.sendApiGenericError(res);
const { id, content, title, author, category, short } = req.body;
blogPost.blogPostModel.findByIdAndUpdate(id, {
'content': content,
'name': title,
'short': short,
'meta.author': author,
'meta.category': category
}, (err, post) => {
if (err) return apiHelper.sendApiError(res, 500, [err]);
apiHelper.sendReturn(res, {
url: moment(post.meta.date, 'YYYY-MM-DD') + '/' + post.meta.slug
});
});
});
/*
* /admin/api/v1/newauthor
*
* creates new author
*
* post {
* name - author name
* description - author description
* image - image url for profile picture
* }
* return {
* code: http code
* success: boolean - true if author succesfull
* id: string | undefined - id of the new author if successfull
* errors: Strings[messages]
* }
*/
router.post('/admin/api/v1/newauthor', adminUserMiddleware.adminAuthNeeded, function (req, res) {
if (!req.body) return apiHelper.sendApiGenericError(res);
const { name, description, image } = req.body;
const newAuthor = new postAuthor.postAuthorModel({
name,
description,
image
});
// saving author to database
newAuthor.save().then((author) => {
apiHelper.sendReturn(res, {
id: author.id
});
}).catch((rejection) => {
// TODO format exception so it doesnt have a huge list of errors
apiHelper.sendApiError(res, 500, [rejection]);
return;
});
});
/*
* /admin/api/v1/editauthor
*
* edit an existing author
*
* post {
* id - id of author to edit
* name - author name
* description - author description
* image - image url for profile picture
* }
* return {
* code: http code
* success: boolean - true if author succesfull
* id: String - id of the edited author
* errors: Strings[messages]
* }
*/
router.post('/admin/api/v1/editauthor', adminUserMiddleware.adminAuthNeeded, function (req, res) {
if (!req.body) return apiHelper.sendApiGenericError(res);
const { id, name, description, image } = req.body;
// updating author in database
postAuthor.postAuthorModel.findByIdAndUpdate(id, {
name,
description,
image
}, (err, author) => {
// TODO format exception so it doesnt have a huge list of errors
if (err) return apiHelper.sendApiError(res, 500, [err]);
apiHelper.sendReturn(res, {
id: author.id
});
});
});
/*
* /admin/api/v1/newprogress
*
* creates a new progress entry
*
* post {
* title - progress entry name
* description - progress entry description
* state - 0: backend service entry, 1: no support, 2: partial support, 3: fullly working
* }
* return {
* code: http code
* success: boolean - true if progress succesfull
* id: String | undefined - sends if successfull
* errors: Strings[messages]
* }
*/
router.post('/admin/api/v1/newprogress', adminUserMiddleware.adminAuthNeeded, function (req, res) {
if (!req.body) return apiHelper.sendApiGenericError(res);
// parses state and isGame to be valid
const { title, description } = req.body;
let { state } = req.body;
let isGame = false;
if (state != '1' && state != '2' && state != '3') {
state = undefined;
} else {
state = parseInt(state);
isGame = true;
}
const newProgress = new progressList.progressListModel({
title,
description,
isGame,
state
});
// saving progress to database
newProgress.save().then((progress) => {
apiHelper.sendReturn(res, {
id: progress.id
});
}).catch((rejection) => {
// TODO format exception so it doesnt have a huge list of errors
apiHelper.sendApiError(res, 500, [rejection]);
return;
});
});
/*
* /admin/api/v1/editprogress
*
* edit an existing progress entry
*
* post {
* title - progress entry name
* description - progress entry description
* state - 0: backend service entry, 1: no support, 2: partial support, 3: fullly working
* id - id of entry you want to edit
* }
* return {
* code: http code
* success: boolean - true if progress succesfull
* id: String - id of the edited progress entry
* errors: Strings[messages]
* }
*/
router.post('/admin/api/v1/editprogress', adminUserMiddleware.adminAuthNeeded, function (req, res) {
if (!req.body) return apiHelper.sendApiGenericError(res);
// parsing state and isGame to be valid
const { title, description, id } = req.body;
let { state } = req.body;
let isGame = false;
if (state != '1' && state != '2' && state != '3') {
state = undefined;
} else {
state = parseInt(state);
isGame = true;
}
// updating progress in database
progressList.progressListModel.findByIdAndUpdate(id, {
title,
description,
state,
isGame
}, (err, progress) => {
// TODO format exception so it doesnt have a huge list of errors
if (err) return apiHelper.sendApiError(res, 500, [err]);
apiHelper.sendReturn(res, {
id: progress.id
});
});
});
// api 404
router.use('/admin/api', (req, res) => {
apiHelper.sendApi404(res);
});
// export the router

View File

@@ -7,25 +7,99 @@ file for handling routes regarding blog posts.
// imports
const router = require('express').Router();
const blogHelper = require('../helpers/blog-helper.js');
const apiHelper = require('../helpers/api');
const utilHelper = require('../helpers/util');
const blogPostModel = require('../models/blog-post').blogPostModel;
const postAuthorModel = require('../models/post-author').postAuthorModel;
// display blog post
router.get('/news/:id', async (req, res) => {
if (isNaN(req.params.id)) {
res.statusCode = 404;
res.render('404');
return;
// display single blog post
router.get('/news/:date/:title', (req, res) => {
// date format YYYY-MM-DD
if (/[0-9]{4}-[0-9]{2}-[0-9]{2}/.test(req.params.date) && /([a-z]|[0-9]|-)+/.test(req.params.title.toLowerCase())) {
// params are correct format
blogPostModel.getPost(new Date(req.params.date), req.params.title.toLowerCase(), (err, post) => {
// error exists or no post exists with the date and name
if (err || !post) {
console.log('error: ' + err + ' and post: ' + post);
return utilHelper.send404(res);
}
// render blogpost
post.postTemplate((err, postTemplate) => {
if (err) return utilHelper.send404(res);
res.render('post', {
post: postTemplate
});
});
});
} else {
// params are incorrect
utilHelper.send404(res);
}
});
const hbsObject = blogHelper.getBlogPostExpressReady(req.params.id);
// display latest blogposts
router.get('/news', (req, res) => {
// sort blogposts on date descending
blogPostModel.find({}).sort({'meta.date': 'desc'}).exec(function(err, posts) {
if (err || !posts) {
return utilHelper.send404(res);
}
if (!hbsObject) {
res.statusCode = 404;
res.render('404');
return;
}
// makes posts template ready
const postCollection = [];
for (let i = 0, l = posts.length; i < l; i++) {
postCollection.push(posts[i].postShortTemplate());
}
res.render('post', hbsObject);
res.render('post-collection', {
posts: postCollection
});
});
});
/*
* /api/v1/listauthors
*
* gets a list of all authors
*
* return {
* code: http code
* success: boolean - true if author succesfull
* authorList: Objects[{_id, name, description, image}] - list of authors with information
* errors: Strings[messages]
* }
*/
router.get('/api/v1/listauthors', function (req, res) {
postAuthorModel.find({}, (err, authors) => {
// TODO format exception so it doesnt have a huge list of errors
if (err) return apiHelper.sendApiError(res, 500, [err]);
apiHelper.sendReturn(res, {
authorList: authors
});
});
});
/*
* /api/v1/listblog
*
* gets a list of all posts
*
* return {
* code: http code
* success: boolean - true if post succesfull
* postList: Objects[{_id, content, meta}] - list of posts with information
* errors: Strings[messages]
* }
*/
router.get('/api/v1/listblog', function (req, res) {
blogPostModel.find({}, (err, posts) => {
// TODO format exception so it doesnt have a huge list of errors
if (err) return apiHelper.sendApiError(res, 500, [err]);
apiHelper.sendReturn(res, {
postList: posts
});
});
});
// export router

79
routes/contact.js Normal file
View File

@@ -0,0 +1,79 @@
/*
contact.js -
file for handling routes regarding contact
*/
// imports
const router = require('express').Router();
const apiHelper = require('../helpers/api');
const config = require('../config.json');
const https = require('https');
// display contact page
router.get('/contact', (req, res) => {
res.render('contact');
});
/*
* /api/v1/sendmessage
*
* registers a new admin user
*
* post {
* email - email of sender
* subject - subject of message
* message - actual message
* }
* return {
* code: httpcode
* success: boolean - true if sending was successull
* errors: Strings[messages]
* }
*/
router.post('/api/v1/sendmessage', function (req, res) {
if (!req.body) return apiHelper.sendApiGenericError(res);
const { email, subject, message } = req.body;
if (email && subject && message && message.length < 2000) {
// request body has everything
const postData = JSON.stringify({
content: 'email: ' + email + ' \n subject: ' + subject + ' \n\n' + message
});
// request object
const request = https.request({
hostname: config.contactWebhook.host,
port: config.contactWebhook.port,
path: config.contactWebhook.path,
method : 'POST',
headers : {
'Content-Type': 'application/json',
'Cache-Control': 'no-cache',
'Content-Length': postData.length
}
}, () => {
// sends success
apiHelper.sendReturn(res, {});
});
// error handling
request.on('error', (e) => {
apiHelper.sendApiGenericError(res);
console.log('request errored' + e);
});
// write post data to request
request.write(postData);
request.end();
} else {
// TODO give more detailed response
return apiHelper.sendApiGenericError(res);
}
});
// export router
module.exports = router;

View File

@@ -14,10 +14,5 @@ router.get('/', (req, res) => {
res.render('home');
});
// display contact page
router.get('/contact', (req, res) => {
res.render('contact');
});
// export the router
module.exports = router;

56
routes/progress.js Normal file
View File

@@ -0,0 +1,56 @@
/*
progress.js -
file for handling routes regarding progress
*/
// imports
const router = require('express').Router();
const apiHelper = require('../helpers/api');
const utilHelper = require('../helpers/util');
const staticText = require('../static-text.json');
const progressListModel = require('../models/progress-list').progressListModel;
// display progress
router.get('/progress', (req, res) => {
progressListModel.find({}, (err, progress) => {
if (err) return utilHelper.send404(res);
// filtering games and backend
const games = progress.filter(i => i.isGame);
const backends = progress.filter(i => !i.isGame);
res.render('progress', {
games,
backends,
summary: staticText.progressSummary
});
});
});
/*
* /api/v1/listprogress
*
* gets a list of progress
*
* return {
* code: http code
* success: boolean - true if progress succesfull
* progressList: Objects[{_id, title, description, state}] - list of progress with information
* errors: Strings[messages]
* }
*/
router.get('/api/v1/listprogress', function (req, res) {
progressListModel.find({}, (err, progress) => {
// TODO format exception so it doesnt have a huge list of errors
if (err) return apiHelper.sendApiError(res, 500, [err]);
apiHelper.sendReturn(res, {
progressList: progress
});
});
});
// export router
module.exports = router;

View File

@@ -13,7 +13,7 @@ const mongoStore = require('connect-mongo')(session);
const mongoose = require('mongoose');
const bodyParser = require('body-parser');
const config = require('./config.json');
const common = require('./helpers/common');
const utilHelper = require('./helpers/util');
const passportconfig = require('./passport.config.js');
// setup console colors
@@ -45,6 +45,7 @@ app.use(session({
collection: 'sessions'
})
}));
// setups up passportjs authentication
passportconfig(app);
// handlebars templating setup
@@ -59,17 +60,29 @@ app.set('view engine', '.hbs');
const locations = {
home: require('./routes/home'),
posts: require('./routes/blog'),
admin: require('./routes/admin')
admin: require('./routes/admin'),
contact: require('./routes/contact'),
progress: require('./routes/progress')
};
// static files
app.use('/assets', express.static('assets'));
// page map
app.use('/', locations.home);
app.use('/', locations.contact);
app.use('/', locations.posts);
app.use('/', locations.admin);
app.use('/', locations.progress);
app.use((req, res) => {
common.sendDefault404(res);
utilHelper.send404(res);
});
// TODO improve error handling
// TODO remove param decoding errors from logs example: "host/test/%"
// 4 parameters required to read the error, cant help the eslint error
app.use((err, req, res, next) => { // eslint-disable-line no-unused-vars
console.error(err.stack);
return res.status(500).send('Something broke!');
});
// startup

12
setupAdminUser.js Normal file
View File

@@ -0,0 +1,12 @@
const adminUser = require('./models/admin-user');
const newUser = new adminUser.adminUserModel({
username: 'admin',
password: 'root'
});
newUser.save().then(() => {
console.log('success, user admin pass root');
}).catch((rejection) => {
console.log(rejection);
});

4
static-text.example.json Normal file
View File

@@ -0,0 +1,4 @@
{
"progressSummary": "This is a progress summary",
"headerText": "A work-in-progress server replacement for nintendo 3ds and wiiu"
}

4
static-text.json Normal file
View File

@@ -0,0 +1,4 @@
{
"progressSummary": "This is a progress summary",
"headerText": "A work-in-progress server replacement for nintendo 3ds and wiiu"
}

View File

@@ -20,7 +20,107 @@
<button>submit login</button>
</form>
<h2>delete admin</h2>
<form action="/admin/api/v1/removeadmin" method="POST">
<p>id</p>
<input type="text" name="id">
<button>delete admin</button>
</form>
<h2>create blog post</h2>
<form action="/admin/api/v1/newpost" method="POST">
<p>content</p>
<textarea name="content" cols="30" rows="10"></textarea>
<p>short version</p>
<textarea name="short" cols="30" rows="10"></textarea>
<p>category</p>
<input type="text" name="category">
<p>title</p>
<input type="text" name="title">
<p>author id</p>
<input type="text" name="author">
<button>create post</button>
</form>
<h2>edit blog post</h2>
<form action="/admin/api/v1/editpost" method="POST">
<p>content</p>
<textarea name="content" cols="30" rows="10"></textarea>
<p>short version</p>
<textarea name="short" cols="30" rows="10"></textarea>
<p>id</p>
<input type="text" name="id">
<p>category</p>
<input type="text" name="category">
<p>title</p>
<input type="text" name="title">
<p>author id</p>
<input type="text" name="author">
<button>edit post</button>
</form>
<h2>new author</h2>
<form action="/admin/api/v1/newauthor" method="POST">
<p>name</p>
<input type="text" name="name">
<p>description</p>
<input type="text" name="description">
<p>image link</p>
<input type="text" name="image">
<button>submit author</button>
</form>
<h2>edit author</h2>
<form action="/admin/api/v1/editauthor" method="POST">
<p>id</p>
<input type="text" name="id">
<p>name</p>
<input type="text" name="name">
<p>description</p>
<input type="text" name="description">
<p>image link</p>
<input type="text" name="image">
<button>edit author</button>
</form>
<h2>new progress entry</h2>
<form action="/admin/api/v1/newprogress" method="POST">
<p>title</p>
<input type="text" name="title">
<p>description</p>
<input type="text" name="description">
<p>state</p>
<p>0 - backend service<br>1 - no support<br>2 - partial support<br>3 - fully working</p>
<input type="text" name="state">
<button>submit progress</button>
</form>
<h2>edit progress entry</h2>
<form action="/admin/api/v1/editprogress" method="POST">
<p>id</p>
<input type="text" name="id">
<p>title</p>
<input type="text" name="title">
<p>description</p>
<input type="text" name="description">
<p>state</p>
<p>0 - backend service<br>1 - no support<br>2 - partial support<br>3 - fully working</p>
<input type="text" name="state">
<button>edit progress</button>
</form>
<br>
<a href="/admin/api/v1/listadmins">List of admin users</a>
<br>
<a href="/api/v1/listblog">List of blog posts</a>
<br>
<a href="/api/v1/listprogress">List of progress</a>
<br>
<a href="/api/v1/listauthors">List of authors</a>
<br>
<a href="/admin/api/v1/check">Check if logged in</a>
<br>
<a href="/admin/api/v1/logout">Logout</a>
{{> footer }}
</body>

View File

@@ -5,7 +5,16 @@
<body>
{{> navbar }}
<h1>contact form here</h1>
<h1>contact form</h1>
<form action="/api/v1/sendmessage" method="POST">
<p>content</p>
<textarea name="message" cols="30" rows="10"></textarea>
<p>subject</p>
<input type="text" name="subject">
<p>email</p>
<input type="text" name="email">
<button>send message</button>
</form>
{{> footer }}
</body>

View File

@@ -0,0 +1,7 @@
<h1>Latest posts</h1>
{{#each posts }}
<strong>{{ this.title }}</strong>
<p>{{ this.content }}</p>
<a href="{{ this.url }}">link</a>
<br><br>
{{/each}}

View File

@@ -5,17 +5,18 @@
<body>
{{> navbar }}
<h1>blog post</h1>
<p>{{ date }}</p>
<p>{{ category }}</p>
<h1>{{ post.title }}</h1>
<p>{{ post.date }}</p>
<p>{{ post.category }}</p>
<article>
{{{ content }}}
{{{ post.content }}}
</article>
<p>{{ author.name }} - {{ author.description }}</p>
<img src="{{ author.image }}">
<p>{{ post.author.name }} - {{ post.author.description }}</p>
<img src="{{ post.author.profilePicture }}">
{{> footer }}
</body>
</html>

15
views/progress.hbs Normal file
View File

@@ -0,0 +1,15 @@
<h1>progress summary</h1>
<p>{{ summary }}</p>
<br>
<h2>game support</h2>
{{#each games }}
<strong>{{ this.title }}</strong>
<p>{{ this.description }}</p>
<p>state: {{ this.state }}</p>
{{/each}}
<br><br>
<h2>backend support</h2>
{{#each backends }}
<strong>{{ this.title }}</strong>
<p>{{ this.description }}</p>
{{/each}}