diff --git a/.nfs0000000000542e1500000001 b/.nfs0000000000542e1500000001 new file mode 100644 index 0000000..9e0ac12 Binary files /dev/null and b/.nfs0000000000542e1500000001 differ diff --git a/README.md b/README.md index 16d7275..778e86c 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ See the software in action: [deadvey.com](https://deadvey.com)
# Confiuration Read the [configuation guide](CONFIG.md) for configuration help (in config.json) -# features +# Features * post creation via the web frontend (no need to remote to your server to make a post) * multi user * powerful customisation @@ -17,17 +17,16 @@ Read the [configuation guide](CONFIG.md) for configuration help (in config.json) * Markdown syntax in posts * Commenting on posts * sign up and delete account +* ejs # Bugs * probably scales like shit * probably insecure as hell -# planned features/todo list +# Planned features/todo list * custom CSS _file_ * custom strings use format indicators * seperate functions into modules -* builtin crypto -* ejs * user specific RSS feeds * atom * federation (looks tricky) @@ -37,30 +36,15 @@ Read the [configuation guide](CONFIG.md) for configuration help (in config.json) * inline comments and docs * give each post a hard postID to prevent potential issues * clean up code a bit +* comment pages? -# format indicators -* %% - A literal % -* %A - List of tags -* %B - List of tags, each one with a hyperlink to that tag page -* %C - Post content -* %D - Published date in the format specified by date_format -* %E - Edited date in the format specified by date_format -* %F - Pretty name -* %G - Tag name (used for the tag page only) -* %H - Frontpage hit count -* %I - User description -* %J - URL delete account -* %L - URL Permanent link to the post -* %M - comments -* %N - the username of the user (poster) -* %P - URL to create a new post -* %O - URL to edit this post -* %Q - URL to sign up -* %R - Site wide RSS feed -* %S - post seperator as defined by post_seperator -* %T - Title -* %U - URL the the user (poster) -* %W - Site Description as defined by site_description -* %X - Comment submission box -* %Y - Site Name as defined by site_name -* %Z - Attribution (to me) and source code link and license +TODO (not finished) +# EJS variable names +* config.variable_name - pass any variable in config.json +* hitcount - value in hitcount.txt (a single number) +## Posts (/views/posts/) +* post - an object that includes the data for that post, eg post.title, post.content etc +* user - the object of the user who posted this +* index - an int that refers to the index of the current post +## Comments (/views/partials/comment.ejs) +* comment - an object storing the comment, eg comment.name, comment.content diff --git a/app.js b/app.js index 36fc436..4006924 100755 --- a/app.js +++ b/app.js @@ -6,8 +6,10 @@ const crypto = require('crypto'); // For encrypting passwords, I use sha512 // fromUnixTime(): Create a date from a Unix timestamp (in seconds). Decimal values will be discarded. // format(): Return the formatted date string in the given format. The result may vary by locale. // getUnixTime(): Get the seconds timestamp of the given date. -// find out more at https://date-fns.org/ \or docs: https://date-fns.org/docs/Getting-Startedyyyyyyyy +// find out more at https://date-fns.org/ \or docs: https://date-fns.org/docs/Getting-Started const { fromUnixTime, format, getUnixTime } = require("date-fns") // A date utility library +const ejs = require("ejs") +const func = require("./functions.js") // There's only one possible argument, so we can just check if the user passed that one // TODO I plan on adding more such as --help and --post so I should make this more robust at some point @@ -20,6 +22,7 @@ let users // contains a list of users, each user is an object containing usernam let posts // contains a list of posts, let comments // contains a list of comments let config // contains a set of configuration for the site, see example-config.js for an example +let ejs_templates try { // We're going to try and import the modules, @@ -27,6 +30,7 @@ try { posts = require('./posts.json'); comments = require('./comments.json'); config = require('./config.json'); + ejs_templates = require("./ejs-templates.js") } catch (error) { // if they don't all import then @@ -56,6 +60,9 @@ const app = express(); app.use(express.urlencoded({ extended: true })); app.use(express.json()); app.use(express.static(config.root_path)); +// set the view engine to ejs +app.set('view engine', 'ejs'); + // Initialise the program by creating users.js, comments.js, posts.js and config.js // All require default content in them to start off with // Then exit successfully @@ -110,16 +117,6 @@ function get_userID(username) { return -1 // If user is not present, return -1 } -// The configuration defines a date format using the date-fns (a datetime library) syntax -// eg "yyyy-MM-dd" -// this converts unix time (an integer) into a string that is formatted according to config.js -// uses date-fns's fromUnixTime() and format() functions -// returns the formatted date (string) -function unix_time_to_date_format(unix_time) { - date = fromUnixTime(unix_time) - formatted_date = format(date, config.date_format) - return formatted_date -} // This is similar to the above function, however, instead of formatting to the users // configuration, it formats to RFC-822 which is the date format used by RSS feeds @@ -173,8 +170,8 @@ function replace_format_indicators(template, post_index=-1, tag_name="tag", user .replaceAll("%A", (post_object["tags"])) .replaceAll("%B", (hyperlink_tags(post_object["tags"]))) .replaceAll("%C", converter.makeHtml(post_object["content"])) - .replaceAll("%D", unix_time_to_date_format(post_object["pubdate"])) - .replaceAll("%E", unix_time_to_date_format(post_object["editdate"])) + //.replaceAll("%D", unix_time_to_date_format(post_object["pubdate"])) + //.replaceAll("%E", unix_time_to_date_format(post_object["editdate"])) .replaceAll("%F", users[post_object["userID"]]['prettyname']) .replaceAll("%G", tag_name) .replaceAll("%I", converter.makeHtml(users[post_object['userID']]['description'])) @@ -235,13 +232,13 @@ function return_comments(post_id) { .replaceAll(/>> ([0-9]*)/g, ">> $1") .replaceAll(/>> ([0-9]*)/g, ">> $1") .replaceAll("\n", "
") - comment_content += `
${comment['name']} ${unix_time_to_date_format(comment['pubdate'])} No. ${comment['id']}
${comment['content']}

` + //comment_content += `
${comment['name']} ${unix_time_to_date_format(comment['pubdate'])} No. ${comment['id']}
${comment['content']}

` } return comment_content } // RSS protocol gets -app.get(config.rss_path, (req,res) => { +app.get(config.rss_url, (req,res) => { if (config.rss == false) { res.send("Sorry, RSS is disabled!") } @@ -275,6 +272,7 @@ app.get(config.rss_path, (req,res) => { }); app.get("/", (req,res) => { + // Increment the hitcount if (config.enable_hitcount) { let hitcount = parseInt(fs.readFileSync('hitcount.txt')) hitcount += 1 @@ -282,51 +280,68 @@ app.get("/", (req,res) => { fs.writeFileSync(`${__dirname}/hitcount.txt`, `${hitcount}`, 'utf-8'); } - header_div = config.timeline_header - header_div = replace_format_indicators(header_div); - posts_div = ""; - counter = posts.length - 1; - while ((counter >= 0) && (counter > (posts.length - (config.timeline_length + 1)))) - { - let post = config.timeline_post_format; - posts_div += replace_format_indicators(post, counter); - counter -= 1; - } - res.send(`
${posts_div}
`); + res.render("pages/timeline", + { + config: config, + posts: posts, + users: users, + comments: comments.comments, + hitcount: fs.readFileSync("hitcount.txt"), + fromUnixTime: fromUnixTime, + format: format, + getUnixTime: getUnixTime, + hyperlink_tags: hyperlink_tags, + }) }); // / app.get("/user/:username", (req, res) => { - header_div = config.user_page_header - header_div = replace_format_indicators(header_div,-1,"tag",get_userID(req.params.username)) - posts_div = ""; - for (let post_index = posts.length-1; post_index >= 0; post_index--) { - if (users[posts[post_index]["userID"]]["username"] == req.params.username) { - let post = config.user_post_format; - posts_div += replace_format_indicators(post, post_index); - } - } - res.send(`
${posts_div}
`); + const userID = get_userID(req.params.username) + console.log(userID) + console.log(users[userID].prettyname) + res.render("pages/user", + { + config: config, + posts: posts, + user: users[userID], + userID: userID, + comments: comments.comments, + fromUnixTime: fromUnixTime, + format: format, + getUnixTime: getUnixTime, + hyperlink_tags: hyperlink_tags, + }) }); // /user/:username app.get("/post/:post_index", (req, res) => { - post_div = ""; - let post = config.post_page_format; - post_div += replace_format_indicators(post, req.params.post_index); - res.send(`
${post_div}
`); + const postID = req.params.post_index + res.render("pages/post", + { + config, + post: posts[postID], + postID: postID, + user: users[posts[postID].userID], + comments: comments.comments[postID], + fromUnixTime, + format, + getUnixTime, + hyperlink_tags, + }) }); // /post/:post_index app.get("/tag/:tag", (req,res) => { - const tag = req.params.tag - let header_div = config.tag_page_header - header_div = replace_format_indicators(header_div,0,tag) - let page_content = "" - for (let i = posts.length-1; i >= 0; i--) { - if (posts[i]['tags'].includes(tag)) { - let post = config.tag_post_format; - page_content += replace_format_indicators(post, i); - }; - }; - res.send(`
${page_content}
`); + const tag = req.params.tag + res.render("pages/tag", + { + config: config, + tag: tag, + posts: posts, + users: users, + comments: comments.comments, + fromUnixTime: fromUnixTime, + format: format, + getUnixTime: getUnixTime, + hyperlink_tags: hyperlink_tags, + }) }); // /tag/:tag -app.get("/post", (req,res) => { +app.get(config.new_post_url, (req,res) => { res.send(`


@@ -337,7 +352,7 @@ app.get("/post", (req,res) => { * Markdown supported
`); }); // /post -app.get("/signup", (req,res) => { +app.get(config.signup_url, (req,res) => { // if the server does allow signup if (config.allow_signup == true) { // Send the page for signing up to the server @@ -359,14 +374,14 @@ app.get("/signup", (req,res) => { console.log("Error, invalid value for allow_signup (bool)") } }); // /signup -app.get("/delete_account", (req,res) => { +app.get(config.delete_account_url, (req,res) => { res.send(`




`); }); // /delete_account -app.get("/edit/:post_id", (req,res) => { +app.get(`${config.edit_post_base_url}/:post_id`, (req,res) => { const post_id = req.params.post_id const post = posts[post_id] const user = users[post['userID']] @@ -409,13 +424,13 @@ app.post("/submit_post", (req,res) => { const content = escape_input(req.body.content) const tags = escape_input(req.body.tags).split(','); const unix_timestamp = getUnixTime(new Date()) - console.log(username, "is submitting a post titled:", title); if (get_userID(username) == -1) { - res.send("User does not exist") + res.send(`${config.user_doesnt_exit}`) } else if (users[get_userID(username)]['hash'] == password) { // Password matches + console.log(username, "is submitting a post titled:", title); posts.push({ "userID": get_userID(username), "title": title, @@ -430,7 +445,7 @@ app.post("/submit_post", (req,res) => { res.redirect(302, "/"); } else { - res.send(`Invalid Password for user`,username); + res.send(`${config.incorrect_password}`) } }); // /submit_post app.post("/submit_signup", (req,res) => { diff --git a/custom.css b/custom.css new file mode 120000 index 0000000..ecac342 --- /dev/null +++ b/custom.css @@ -0,0 +1 @@ +example-custom.css \ No newline at end of file diff --git a/ejs-templates.js b/ejs-templates.js new file mode 100644 index 0000000..c893a17 --- /dev/null +++ b/ejs-templates.js @@ -0,0 +1 @@ +export let timeline = '
${posts_div}
' diff --git a/example-config.json b/example-config.json index 37d44ee..e2ed85b 100755 --- a/example-config.json +++ b/example-config.json @@ -9,9 +9,13 @@ "enable_hitcount": true, "charset": "UTF-8", "root_path": "/path/to/root/of/website", + "delete_account_url": "/delete_account", + "new_post_url": "/post", + "signup_url": "/signup", + "edit_post_base_url": "/edit", "default_comenter_username": "Anon", "rss": true, - "rss_path": "/rss", + "rss_url": "/rss", "date_format": "yyyy-MM-dd", "time_zone": "+0000", "timeline_header": "

%Y

%W

Create Post
RSS Feed
Sign Up
Delete Account
Hit count: %H%S", diff --git a/example-custom.css b/example-custom.css new file mode 100644 index 0000000..5e2752a --- /dev/null +++ b/example-custom.css @@ -0,0 +1,3 @@ +body { + /* Put your custom css here */ +} diff --git a/functions.js b/functions.js new file mode 100644 index 0000000..46a182a --- /dev/null +++ b/functions.js @@ -0,0 +1,10 @@ +// The configuration defines a date format using the date-fns (a datetime library) syntax +// eg "yyyy-MM-dd" +// this converts unix time (an integer) into a string that is formatted according to config.js +// uses date-fns's fromUnixTime() and format() functions +// returns the formatted date (string) +export function unix_time_to_date_format(unix_time, format) { + date = fromUnixTime(unix_time) + formatted_date = format(date, format) + return formatted_date +} diff --git a/package.json b/package.json index c02b2a9..95052f9 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "dependencies": { "date-fns": "^4.1.0", + "ejs": "^3.1.10", "express": "^5.1.0", "showdown": "^2.1.0" } diff --git a/views/headers/tag.ejs b/views/headers/tag.ejs new file mode 100644 index 0000000..2ba4e40 --- /dev/null +++ b/views/headers/tag.ejs @@ -0,0 +1,4 @@ +

+ Post's tagged "<%- tag %>": +

+<%- config.seperator %> diff --git a/views/headers/timeline.ejs b/views/headers/timeline.ejs new file mode 100644 index 0000000..159fe9e --- /dev/null +++ b/views/headers/timeline.ejs @@ -0,0 +1,13 @@ +

+ <%- config.site_name %> +

+

+ <%- config.site_description %> +

+RSS Feed
+Sign Up
+Delete Account
+<% if (config.enable_hitcount == true) { %> +Hitcount: <%= hitcount %> +<% } %> +<%- config.seperator %> diff --git a/views/headers/user.ejs b/views/headers/user.ejs new file mode 100644 index 0000000..949aefc --- /dev/null +++ b/views/headers/user.ejs @@ -0,0 +1,4 @@ +

+ <%- user.prettyname %>'s posts +

+<%- config.seperator %> diff --git a/views/pages/post.ejs b/views/pages/post.ejs new file mode 100644 index 0000000..85c2917 --- /dev/null +++ b/views/pages/post.ejs @@ -0,0 +1,14 @@ + + + + <%- include('../partials/head'); %> + + +
+ <%- include('../posts/post'); %> +
+ + + diff --git a/views/pages/tag.ejs b/views/pages/tag.ejs new file mode 100644 index 0000000..fc54ed8 --- /dev/null +++ b/views/pages/tag.ejs @@ -0,0 +1,23 @@ + + + + <%- include('../partials/head'); %> + + + +
+ <% for (let index = posts.length - 1; index >= 0; index--) { %> + <% posts[index].tags.forEach((current_tag, tag_index) => { %> + <% if (current_tag == tag) { %> + <%- include('../posts/tag', {post: posts[index], postID: index}); %> + <% } %> + <% }) %> + <% } %> +
+ + + diff --git a/views/pages/timeline.ejs b/views/pages/timeline.ejs new file mode 100644 index 0000000..4df5505 --- /dev/null +++ b/views/pages/timeline.ejs @@ -0,0 +1,19 @@ + + + + <%- include('../partials/head'); %> + + + +
+ <% for (let index = posts.length - 1; index >= 0; index--) { %> + <%- include('../posts/timeline', {post: posts[index], index: index, user: users[posts[index].userID]}); %> + <% } %> +
+ + + diff --git a/views/pages/user.ejs b/views/pages/user.ejs new file mode 100644 index 0000000..6d386c7 --- /dev/null +++ b/views/pages/user.ejs @@ -0,0 +1,21 @@ + + + + <%- include('../partials/head'); %> + + + +
+ <% for (let index = posts.length - 1; index >= 0; index--) { %> + <% if (posts[index].userID == userID) { %> + <%- include('../posts/user', {post: posts[index], index: index, user: user}); %> + <% } %> + <% } %> +
+ + + diff --git a/views/partials/comment.ejs b/views/partials/comment.ejs new file mode 100644 index 0000000..7f94274 --- /dev/null +++ b/views/partials/comment.ejs @@ -0,0 +1,2 @@ +<%= comment.name %> <%= format(fromUnixTime(comment.pubdate), config.date_format) %> No. <%= comment.id %>:
+<%= comment.content %> diff --git a/views/partials/footer.ejs b/views/partials/footer.ejs new file mode 100644 index 0000000..8710c02 --- /dev/null +++ b/views/partials/footer.ejs @@ -0,0 +1,2 @@ +Site is ran by deadvey
+<%- config.attribution %> diff --git a/views/partials/head.ejs b/views/partials/head.ejs new file mode 100644 index 0000000..9d29ced --- /dev/null +++ b/views/partials/head.ejs @@ -0,0 +1,8 @@ + +<%= config.site_name %> + + + + diff --git a/views/posts/post.ejs b/views/posts/post.ejs new file mode 100644 index 0000000..4f8cd28 --- /dev/null +++ b/views/posts/post.ejs @@ -0,0 +1,29 @@ +

+ <%= post.title %> +

+<%= post.content %>
+ + By <%= user.username %>
+
+
+<%- hyperlink_tags(post.tags) %>
+Edit
+Published: <%= format(fromUnixTime(post.pubdate), config.date_format) %>
+Last Modified: <%= format(fromUnixTime(post.editdate), config.date_format) %>
+ +<%- config.seperator %> + + +
+ +
+
+ +
+ +<% comments.forEach((comment, postID) => { %> + <%- include('../partials/comment', {comment: comment}) %> +<% }) %> + +<%- config.seperator %> + diff --git a/views/posts/tag.ejs b/views/posts/tag.ejs new file mode 100644 index 0000000..e1ea9c7 --- /dev/null +++ b/views/posts/tag.ejs @@ -0,0 +1,21 @@ +

+ <%= post.title %> +

+<%= post.content %>
+Permalink
+<%- hyperlink_tags(post.tags) %> + + +
+ +
+
+ +
+ +<% comments[postID].forEach((comment) => { %> + <%- include('../partials/comment', {comment: comment}) %> +<% }) %> + +<%- config.seperator %> + diff --git a/views/posts/timeline.ejs b/views/posts/timeline.ejs new file mode 100644 index 0000000..6951340 --- /dev/null +++ b/views/posts/timeline.ejs @@ -0,0 +1,23 @@ +

+ <%= post.title %> +

+<%= post.content %>
+Permalink
+ + By <%= user.username %>
+
+ + +
+ +
+
+ +
+ +<% comments[index].forEach((comment, index) => { %> + <%- include('../partials/comment', {comment: comment}) %> +<% }) %> + +<%- config.seperator %> + diff --git a/views/posts/user.ejs b/views/posts/user.ejs new file mode 100644 index 0000000..f449c02 --- /dev/null +++ b/views/posts/user.ejs @@ -0,0 +1,20 @@ +

+ <%= post.title %> +

+<%= post.content %>
+Permalink
+ + +
+ +
+
+ +
+ +<% comments[index].forEach((comment, index) => { %> + <%- include('../partials/comment', {comment: comment}) %> +<% }) %> + +<%- config.seperator %> +