From bc92cd56817691a6c47adeffef536090592a114a Mon Sep 17 00:00:00 2001 From: Armored-Dragon Date: Wed, 15 Nov 2023 18:49:09 +0000 Subject: [PATCH] database cleanup (#3) Reviewed-on: https://git.armoreddragon.com/ArmoredDragon/yet-another-blog/pulls/3 Co-authored-by: Armored-Dragon Co-committed-by: Armored-Dragon --- backend/core/core.js | 209 ++++++++++++++++++++------------ backend/core/form_validation.js | 29 ----- backend/core/internal_api.js | 111 ++++++++++------- backend/form_validation.js | 45 +++++++ backend/page_scripts.js | 74 ++--------- backend/permissions.js | 4 +- backend/settings.js | 45 ------- frontend/public/js/newBlog.js | 4 +- package-lock.json | 9 -- package.json | 1 - prisma/schema.prisma | 25 +++- yab.js | 25 ++-- 12 files changed, 287 insertions(+), 294 deletions(-) delete mode 100644 backend/core/form_validation.js create mode 100644 backend/form_validation.js delete mode 100644 backend/settings.js diff --git a/backend/core/core.js b/backend/core/core.js index 42a5d3c..547054a 100644 --- a/backend/core/core.js +++ b/backend/core/core.js @@ -1,6 +1,5 @@ const { PrismaClient } = require("@prisma/client"); const prisma = new PrismaClient(); -const crypto = require("crypto"); const sharp = require("sharp"); const { S3Client, PutObjectCommand, GetObjectCommand, DeleteObjectCommand, ListObjectsCommand, DeleteObjectsCommand } = require("@aws-sdk/client-s3"); const { getSignedUrl } = require("@aws-sdk/s3-request-presigner"); @@ -12,61 +11,77 @@ const s3 = new S3Client({ region: process.env.S3_REGION, endpoint: process.env.S3_ENDPOINT, }); -const settings = require("../settings"); const md = require("markdown-it")(); +let settings = { + SETUP_COMPLETE: false, + ACCOUNT_REGISTRATION: false, + HIDE_LOGIN: false, + BLOG_UPLOADING: false, + + USER_MINIMUM_PASSWORD_LENGTH: 7, + + BLOG_MINIMUM_TITLE_LENGTH: 7, + BLOG_MINIMUM_DESCRIPTION_LENGTH: 7, + BLOG_MINIMUM_CONTENT_LENGTH: 7, +}; +let groups = []; +_getSettings(); +_getGroups(); + async function registerUser(username, password, options) { - const new_user = await prisma.user.create({ data: { username: username, password: password, ...options } }); + let user_database_entry; + let user_profile_database_entry; - if (new_user.id) { - // If the user was created as an admin, make sure that the server knows the setup process is complete. - if (options.role === "ADMIN") settings.act("SETUP_COMPLETE", true); + // Create the entry in the database + try { + user_database_entry = await prisma.user.create({ data: { username: username, password: password, ...options } }); + } catch (e) { + let message; - // Create a user profile page - const profile_page = await prisma.profilePage.create({ data: { owner: new_user.id } }); - if (!profile_page.id) return { success: false, message: `Error creating profile page for user ${new_user.username}` }; + if (e.code === "P2002") message = "Username already exists"; + else message = "Unknown error"; - // User has been successfully created - return { success: true, message: `Successfully created ${new_user.username}` }; + return { success: false, message: message }; } - return { success: false, message: "Unknown error" }; + // Create a user profile page + try { + user_profile_database_entry = await prisma.profilePage.create({ data: { owner: { connect: { id: user_database_entry.id } } } }); + } catch (e) { + return { success: false, message: `Error creating profile page for user ${username}` }; + } + + // Master user was created; server initialized + postSetting("SETUP_COMPLETE", true); + + // User has been successfully created + return { success: true, message: `Successfully created ${username}` }; } -async function getUser({ id, username } = {}) { - if (id || username) { - let user; - if (id) user = await prisma.user.findUnique({ where: { id: id } }); - else if (username) user = await prisma.user.findUnique({ where: { username: username } }); - if (!user) return { success: false, message: "No matching user" }; - else return { success: true, data: user }; - } +async function getUser({ id, username } = {}) { + let user; + if (id) user = await prisma.user.findUnique({ where: { id: id } }); + else if (username) user = await prisma.user.findUnique({ where: { username: username } }); + + if (!user) return { success: false, message: "No matching user" }; + else return { success: true, data: user }; } async function postBlog(blog_post, owner_id) { - // Check if user has permissions to upload a blog post const user = await getUser({ id: owner_id }); - if (!user.success) return { success: false, message: "User not found" }; + // Check if user has permissions to upload a blog post + if (user.data.role !== "ADMIN" && user.data.role !== "AUTHOR") return { success: false, message: "User is not permitted" }; - const [year, month, day] = blog_post.date.split("-"); - const [hour, minute] = blog_post.time.split(":"); - let publish_date = new Date(year, month - 1, day, hour, minute); - - let blog_post_formatted = { - title: blog_post.title, - description: blog_post.description, - content: blog_post.content, - visibility: blog_post.unlisted ? "UNLISTED" : "PUBLISHED", - publish_date: publish_date, - }; - - const database_blog = await prisma.blogPost.create({ data: { ...blog_post_formatted, owner: { connect: { id: owner_id } } } }); + // Save to database + const database_blog = await prisma.blogPost.create({ data: { ...blog_post, owner: { connect: { id: owner_id } } } }); + // Init image vars let uploaded_images = []; let uploaded_thumbnail = "DEFAULT"; + // For Each image, upload to S3 if (blog_post.images) { - // For Each image, upload to S3 for (let i = 0; blog_post.images.length > i; i++) { const image = blog_post.images[i]; const image_data = Buffer.from(image.data_blob.split(",")[1], "base64"); @@ -82,12 +97,13 @@ async function postBlog(blog_post, owner_id) { uploaded_thumbnail = name; } + // Update the blog post to include references to our images await prisma.blogPost.update({ where: { id: database_blog.id }, data: { images: uploaded_images, thumbnail: uploaded_thumbnail } }); return { success: true, blog_id: database_blog.id }; } async function deleteBlog(blog_id, requester_id) { const user = await getUser({ id: requester_id }); - const post = await getBlogList({ id: blog_id }); + const post = await getBlog({ id: blog_id }); let can_delete = post.owner.id === user.data.id || user.data.role === "ADMIN"; @@ -101,7 +117,7 @@ async function deleteBlog(blog_id, requester_id) { } async function updateBlog(blog_post, requester_id) { const user = await getUser({ id: requester_id }); - const post = await getBlogList({ id: blog_post.id, raw: true }); + const post = await getBlog({ id: blog_post.id, raw: true }); delete blog_post.id; @@ -152,42 +168,24 @@ async function updateBlog(blog_post, requester_id) { return { success: true }; } -async function getBlogList({ id, visibility = "PUBLISHED", owner_id, raw = false } = {}, { limit = 10, page = 0 } = {}) { +async function getBlog({ id, visibility = "PUBLISHED", owner_id, limit = 10, page = 0 } = {}) { if (id) { // Get the database entry for the blog post let post = await prisma.blogPost.findUnique({ where: { id: id }, include: { owner: true } }); + if (!post) return { success: false, message: "Post does not exist" }; - if (!post) return null; - - if (raw) { - // Had to do this, only God knows why. - post.raw_images = []; - post.images.forEach((image) => post.raw_images.push(image)); - - post.raw_thumbnail = post.thumbnail; - post.raw_content = post.content; - } - - // Get the image urls for the post - for (i = 0; post.images.length > i; i++) { - post.images[i] = await _getImage(post.id, "blog", post.images[i]); - } - - // get thumbnail URL - post.thumbnail = await _getImage(post.id, "blog", post.thumbnail); - - // Render the markdown contents of the post - post.content = md.render(post.content); - - // Replace custom formatting with what we want - post.content = _format_blog_content(post.content, post.images); + // Render the post + const rendered_post = _renderPost(post, true); // Return the post with valid image urls - return post; + return rendered_post; } + let rendered_post_list = []; + const where_object = { OR: [ + // Standard discovery: Public, and after the publish date { AND: [ { @@ -201,6 +199,7 @@ async function getBlogList({ id, visibility = "PUBLISHED", owner_id, raw = false ], }, + // User owns the post { ownerid: owner_id, }, @@ -214,30 +213,21 @@ async function getBlogList({ id, visibility = "PUBLISHED", owner_id, raw = false include: { owner: true }, orderBy: [{ publish_date: "desc" }, { created_date: "desc" }], }); - // Get the thumbnails - for (i = 0; blog_posts.length > i; i++) { - blog_posts[i].thumbnail = await _getImage(blog_posts[i].id, "blog", blog_posts[i].thumbnail); - // Get the image urls for the post - for (imgindx = 0; blog_posts[i].images.length > imgindx; imgindx++) { - blog_posts[i].images[imgindx] = await _getImage(blog_posts[i].id, "blog", blog_posts[i].images[imgindx]); - } - - // Render the markdown contents of the post - blog_posts[i].content = md.render(blog_posts[i].content); - - // Replace custom formatting with what we want - blog_posts[i].content = _format_blog_content(blog_posts[i].content, blog_posts[i].images); + // Render each of the posts in the list + for (blog_post of blog_posts) { + rendered_post_list.push(await _renderPost(blog_post, true)); } + // Calculate pagination let pagination = await prisma.blogPost.count({ where: where_object, }); - return { data: blog_posts, pagination: _getNavigationList(page, Math.ceil(pagination / limit)) }; + return { data: rendered_post_list, pagination: _getNavigationList(page, Math.ceil(pagination / limit)) }; } async function deleteImage(image, requester_id) { const user = await getUser({ id: requester_id }); - const post = await getBlogList({ id: image.parent, raw: true }); + const post = await getBlog({ id: image.parent, raw: true }); // Check if post exists if (!post) return { success: false, message: "Post does not exist" }; @@ -319,6 +309,34 @@ async function _deleteS3Directory(id, type) { // If there are more objects to delete (truncated result), recursively call the function again // if (listed_objects.IsTruncated) await emptyS3Directory(bucket, dir); } +async function _renderPost(blog_post, raw, { post_type = "blog" } = {}) { + if (raw) { + // Had to do this, only God knows why. + blog_post.raw_images = []; + if (blog_post.images) blog_post.images.forEach((image) => blog_post.raw_images.push(image)); + + blog_post.raw_thumbnail = blog_post.thumbnail; + blog_post.raw_content = blog_post.content; + } + + if (blog_post.images) { + // Get the image urls for the post + for (i = 0; blog_post.images.length > i; i++) { + blog_post.images[i] = await _getImage(blog_post.id, post_type, blog_post.images[i]); + } + } + + // get thumbnail URL + blog_post.thumbnail = await _getImage(blog_post.id, post_type, blog_post.thumbnail); + + // Render the markdown contents of the post + blog_post.content = md.render(blog_post.content); + + // Replace custom formatting with what we want + blog_post.content = _format_blog_content(blog_post.content, blog_post.images); + + return blog_post; +} function _format_blog_content(content, images) { // Replace Images const image_regex = /{image:([^}]+)}/g; @@ -348,5 +366,36 @@ function _getNavigationList(current_page, max_page) { const pageList = [current_page - 2, current_page - 1, current_page, current_page + 1, current_page + 2].filter((num) => num >= 0 && num < max_page); return pageList.slice(0, 5); } +async function _getSettings() { + // Go though each object key in our settings to get the value if it exists + Object.keys(settings).forEach(async (key) => { + let found_value = await prisma.setting.findUnique({ where: { id: key } }); + if (!found_value) return; -module.exports = { registerUser, getUser, postBlog, updateBlog, getBlogList, deleteBlog, deleteImage }; + return (settings[key] = JSON.parse(found_value.value)); + }); +} +async function getSetting(key, { parse = true }) { + if (!settings[key]) return null; + + if (parse) { + return JSON.parse(settings[key]); + } + return settings[key]; +} +async function postSetting(key, value) { + try { + if (!Object.keys(settings).includes(key)) return { success: false, message: "Setting not valid" }; + + await prisma.setting.upsert({ where: { id: key }, update: { value: value }, create: { id: key, value: value } }); + settings[key] = JSON.parse(value); + + return { success: true }; + } catch (e) { + return { success: false, message: e.message }; + } +} +async function _getGroups() { + const group_list = await prisma.group.findMany(); +} +module.exports = { registerUser, getUser, postBlog, updateBlog, getBlogList: getBlog, deleteBlog, deleteImage, postSetting, getSetting, settings }; diff --git a/backend/core/form_validation.js b/backend/core/form_validation.js deleted file mode 100644 index c6ae3a9..0000000 --- a/backend/core/form_validation.js +++ /dev/null @@ -1,29 +0,0 @@ -const settings = require("../settings"); - -async function userRegistration(username, password) { - const active_settings = settings.getSettings(); - if (!username) return { success: false, message: "No username provided" }; - if (!password) return { success: false, message: "No password provided" }; - if (password.length < active_settings.USER_MINIMUM_PASSWORD_LENGTH) return { success: false, message: "Password not long enough" }; - - // Check if username only uses URL safe characters - if (!_isUrlSafe(username)) return { success: false, message: "Username is not URL safe" }; - - // All good! Validation complete - return { success: true }; -} - -async function blogPost(blog_object) { - // TODO: Validate blog posts before upload - // Check title length - // Check description length - // Check content length - // Check valid date -} - -function _isUrlSafe(str) { - const pattern = /^[A-Za-z0-9\-_.~]+$/; - return pattern.test(str); -} - -module.exports = { userRegistration }; diff --git a/backend/core/internal_api.js b/backend/core/internal_api.js index 0589320..1e64863 100644 --- a/backend/core/internal_api.js +++ b/backend/core/internal_api.js @@ -1,60 +1,79 @@ -const validate = require("./form_validation"); const core = require("./core"); -const settings = require("../settings"); +const bcrypt = require("bcrypt"); +const validate = require("../form_validation"); -async function registerUser(username, password) { - // Get current and relevant settings - const active_settings = settings.getSettings(); - const form_valid = await validate.userRegistration(username, password); // Check form for errors +async function postRegister(req, res) { + const { username, password } = req.body; // Get the username and password from the request body - // Set variables for easy reading - const registration_allowed = active_settings.ACCOUNT_REGISTRATION; - const setup_complete = active_settings.SETUP_COMPLETE; + const form_validation = await validate.registerUser(username, password); // Check form for errors - if (!registration_allowed && setup_complete) return { success: false, message: "Registration is disabled" }; // Registration disabled - if (!form_valid.success) return form_valid; // Registration details did not validate + // User registration disabled? + // We also check if the server was setup. If it was not set up, the server will proceed anyways. + if (!core.settings["ACCOUNT_REGISTRATION"] && core.settings["SETUP_COMPLETE"]) + return res.json({ success: false, message: "Account registrations are disabled" }); - // Does a user using that username exist already? - const existing_user = await core.getUser({ username: username }); - if (existing_user.success) return { success: false, message: "Username is taken" }; + // User data valid? + if (!form_validation.success) return res.json({ success: false, message: form_validation.message }); - // Register the user in the database - const role = setup_complete ? undefined : "ADMIN"; - const registration_status = await core.registerUser(username, password, { role: role }); + // If setup incomplete, set the user role to Admin. This is the initial user so it will be the master user. + const role = core.settings["SETUP_COMPLETE"] ? undefined : "ADMIN"; - if (registration_status.success) return registration_status; - else return registration_status; + const hashed_password = await bcrypt.hash(password, 10); // Hash the password for security :^) + res.json(await core.registerUser(username, hashed_password, { role: role })); } +async function postLogin(req, res) { + const { username, password } = req.body; // Get the username and password from the request body -async function loginUser(username, password) { // Get the user by username const existing_user = await core.getUser({ username: username }); + if (!existing_user.success) return res.json({ success: false, message: existing_user.message }); - // Check for errors or problems - if (!existing_user.success) return { success: false, message: "User does not exist" }; - if (existing_user.role === "LOCKED") return { success: false, message: "Account is locked: Contact your administrator" }; - return { success: true, data: { username: existing_user.data.username, id: existing_user.data.id, password: existing_user.data.password } }; + // Check the password + const password_match = await bcrypt.compare(password, existing_user.data.password); + if (!password_match) return res.json({ success: false, message: "Incorrect password" }); + + // Send the cookies to the user & return successful + req.session.user = { username: username, id: existing_user.data.id }; + res.json({ success: true }); +} +async function postSetting(request, response) { + const user = await core.getUser({ id: request.session.user.id }); + + // TODO: Permissions for changing settings + if (!user.success) return response.json({ success: false, message: user.message }); + if (user.data.role !== "ADMIN") return response.json({ success: false, message: "User is not permitted" }); + + await core.postSetting(request.body.setting_name, request.body.value); + + response.json({ success: true }); +} +async function deleteImage(req, res) { + // TODO: Permissions for deleting image + return res.json(await core.deleteImage(req.body, req.session.user.id)); +} +async function postBlog(req, res) { + // Get user + const user = await core.getUser({ id: req.session.user.id }); + if (!user.success) return user; + + // TODO: Permissions for uploading posts + // Can user upload? + // const permissions = await permissions.postBlog(user); + + // TODO: Validation for uploading posts + // Validate blog info + const valid = await validate.postBlog(req.body); + + // Upload blog post + return res.json(await core.postBlog(valid.data, req.session.user.id)); +} +async function deleteBlog(req, res) { + // TODO: Permissions for deleting blog + return res.json(await core.deleteBlog(req.body.id, req.session.user.id)); +} +async function patchBlog(req, res) { + // TODO: Permissions for updating blog + return res.json(await core.updateBlog(req.body, req.session.user.id)); } -async function getBlogList({ id, visibility, owner_id, raw } = {}, { page = 0, limit = 10 } = {}) { - const blog_list = await core.getBlogList({ id: id, visibility: visibility, owner_id: owner_id, raw: raw }, { page: page, limit: limit }); - return blog_list; -} - -async function getUser({ id } = {}) { - return await core.getUser({ id: id }); -} - -async function postBlog(blog_post, owner_id) { - return await core.postBlog(blog_post, owner_id); -} -async function deleteBlog(blog_id, owner_id) { - return await core.deleteBlog(blog_id, owner_id); -} -async function updateBlog(blog_post, requester_id) { - return await core.updateBlog(blog_post, requester_id); -} -async function deleteImage(image_data, requester_id) { - return await core.deleteImage(image_data, requester_id); -} -module.exports = { registerUser, loginUser, postBlog, getBlogList, deleteBlog, updateBlog, deleteImage, getUser }; +module.exports = { postRegister, postLogin, postSetting, deleteImage, postBlog, deleteBlog, patchBlog }; diff --git a/backend/form_validation.js b/backend/form_validation.js new file mode 100644 index 0000000..212110c --- /dev/null +++ b/backend/form_validation.js @@ -0,0 +1,45 @@ +const core = require("./core/core"); + +async function registerUser(username, password) { + if (!username) return { success: false, message: "No username provided" }; + if (!password) return { success: false, message: "No password provided" }; + if (password.length < core.settings["USER_MINIMUM_PASSWORD_LENGTH"]) return { success: false, message: "Password not long enough" }; + + // Check if username only uses URL safe characters + if (!_isUrlSafe(username)) return { success: false, message: "Username is not URL safe" }; + + // All good! Validation complete + return { success: true }; +} + +async function postBlog(blog_object) { + // TODO: Validate blog posts before upload + // Check title length + // Check description length + // Check content length + // Check valid date + // Return formatted object + + // Get the publish date in a standard format + const [year, month, day] = blog_object.date.split("-"); + const [hour, minute] = blog_object.time.split(":"); + let publish_date = new Date(year, month - 1, day, hour, minute); + + // Format our data to save + let blog_post_formatted = { + title: blog_object.title, + description: blog_object.description, + content: blog_object.content, + visibility: blog_object.visibility, + publish_date: publish_date, + }; + + return { success: true, data: blog_post_formatted }; +} + +function _isUrlSafe(str) { + const pattern = /^[A-Za-z0-9\-_.~]+$/; + return pattern.test(str); +} + +module.exports = { registerUser, postBlog }; diff --git a/backend/page_scripts.js b/backend/page_scripts.js index f5b6c15..f578187 100644 --- a/backend/page_scripts.js +++ b/backend/page_scripts.js @@ -1,16 +1,14 @@ -const internal = require("./core/internal_api"); const external = require("./core/external_api"); -const bcrypt = require("bcrypt"); -const settings = require("./settings"); +const core = require("./core/core"); function getDefaults(req) { - const active_settings = settings.getSettings(); - return { logged_in_user: req.session.user, website_name: process.env.WEBSITE_NAME, settings: active_settings }; + return { logged_in_user: req.session.user, website_name: process.env.WEBSITE_NAME, settings: core.settings }; } async function index(request, response) { // Check if the master admin has been created - const is_setup_complete = (await settings.act("SETUP_COMPLETE")) || false; + + const is_setup_complete = core.settings["SETUP_COMPLETE"]; if (!is_setup_complete) return response.redirect("/register"); response.redirect("/blog"); @@ -25,7 +23,7 @@ function author(request, response) { response.render("author.ejs", getDefaults(request)); } async function blogList(req, res) { - const blog_list = await internal.getBlogList({ owner_id: req.session.user?.id }, { page: req.query.page || 0 }); + const blog_list = await core.getBlogList({ owner_id: req.session.user?.id, page: req.query.page || 0 }); res.render("blogList.ejs", { ...getDefaults(req), blog_list: blog_list.data, @@ -35,7 +33,7 @@ async function blogList(req, res) { }); } async function blogSingle(req, res) { - const blog = await internal.getBlogList({ id: req.params.blog_id }); + const blog = await core.getBlogList({ id: req.params.blog_id }); if (blog === null) return res.redirect("/blog"); res.render("blogSingle.ejs", { ...getDefaults(req), blog_post: blog }); } @@ -53,16 +51,16 @@ function blogNew(request, response) { response.render("blogNew.ejs", { ...getDefaults(request), existing_blog: existing_blog }); } async function blogEdit(req, res) { - const existing_blog = await internal.getBlogList({ id: req.params.blog_id, raw: true }); - - let published_date_parts = new Date(existing_blog.publish_date).toLocaleDateString().split("/"); - const formatted_date = `${published_date_parts[2]}-${published_date_parts[0].padStart(2, "0")}-${published_date_parts[1].padStart(2, "0")}`; - existing_blog.publish_date = formatted_date; + const existing_blog = await core.getBlogList({ id: req.params.blog_id, raw: true }); let published_time_parts = new Date(existing_blog.publish_date).toLocaleTimeString([], { timeStyle: "short" }).slice(0, 4).split(":"); const formatted_time = `${published_time_parts[0].padStart(2, "0")}:${published_time_parts[1].padStart(2, "0")}`; existing_blog.publish_time = formatted_time; + let published_date_parts = new Date(existing_blog.publish_date).toLocaleDateString().split("/"); + const formatted_date = `${published_date_parts[2]}-${published_date_parts[0].padStart(2, "0")}-${published_date_parts[1].padStart(2, "0")}`; + existing_blog.publish_date = formatted_date; + res.render("blogNew.ejs", { ...getDefaults(req), existing_blog: existing_blog }); } async function admin(request, response) { @@ -72,49 +70,9 @@ async function atom(req, res) { res.type("application/xml"); res.send(await external.getFeed({ type: "atom" })); } -// async function rss(req, res) { -// res.type("application/rss+xml"); -// res.send(await external.getFeed({ type: "rss" })); -// } -async function registerPost(request, response) { - const hashedPassword = await bcrypt.hash(request.body.password, 10); // Hash the password for security :^) - response.json(await internal.registerUser(request.body.username, hashedPassword)); -} -async function loginPost(request, response) { - const login = await internal.loginUser(request.body.username, request.body.password); +// Internal API ------------------------------ - if (!login.success) return response.json(login); - - const password_match = await bcrypt.compare(request.body.password, login.data.password); - if (!password_match) return response.json({ success: false, message: "Incorrect password" }); - - request.session.user = { username: login.data.username, id: login.data.id }; - response.json({ success: true }); -} -async function settingPost(request, response) { - const user = await internal.getUser({ id: request.session.user.id }); - - if (!user.success) return response.json({ success: false, message: user.message }); - if (user.data.role !== "ADMIN") return response.json({ success: false, message: "User is not permitted" }); - - settings.act(request.body.setting_name, request.body.value); - - response.json({ success: true }); -} -async function deleteImage(req, res) { - res.json(await internal.deleteImage(req.body, req.session.user.id)); -} - -async function postBlog(req, res) { - return res.json(await internal.postBlog(req.body, req.session.user.id)); -} -async function deleteBlog(req, res) { - return res.json(await internal.deleteBlog(req.body.id, req.session.user.id)); -} -async function updateBlog(req, res) { - return res.json(await internal.updateBlog(req.body, req.session.user.id)); -} module.exports = { index, register, @@ -126,12 +84,4 @@ module.exports = { blogSingle, admin, atom, - // rss, - registerPost, - loginPost, - settingPost, - postBlog, - deleteBlog, - deleteImage, - updateBlog, }; diff --git a/backend/permissions.js b/backend/permissions.js index 6de55a6..b8117a0 100644 --- a/backend/permissions.js +++ b/backend/permissions.js @@ -1,3 +1,3 @@ -// TODO: Permissions file +function postBlog(user) {} -function checkPermissions(role, { minimum = true }) {} +module.exports = { postBlog }; diff --git a/backend/settings.js b/backend/settings.js deleted file mode 100644 index 4328bdb..0000000 --- a/backend/settings.js +++ /dev/null @@ -1,45 +0,0 @@ -const persistent_setting = require("node-persist"); -persistent_setting.init({ dir: "data/site/" }); - -let settings = { - SETUP_COMPLETE: false, - ACCOUNT_REGISTRATION: false, - HIDE_LOGIN: false, - BLOG_UPLOADING: false, - - USER_MINIMUM_PASSWORD_LENGTH: 6, - - BLOG_MINIMUM_TITLE_LENGTH: 6, - BLOG_MINIMUM_DESCRIPTION_LENGTH: 6, - BLOG_MINIMUM_CONTENT_LENGTH: 6, -}; - -async function act(key, value) { - // Change value if we have a value field - if (value) { - // Just incase the value is a string instead of a boolean - value = String(value).toLowerCase() === "true"; - - await persistent_setting.setItem(key, value); - settings[key] = value; - } - - // Return the current setting - return settings[key]; -} - -function getSettings() { - return settings; -} - -// Initialize our settings -setTimeout(async () => { - for (let i = 0; Object.keys(settings).length > i; i++) { - const setting_title = Object.keys(settings)[i]; - const setting_value = await persistent_setting.getItem(setting_title); - - settings[setting_title] = setting_value == true || setting_value == "true"; - } -}, 3000); - -module.exports = { act, getSettings }; diff --git a/frontend/public/js/newBlog.js b/frontend/public/js/newBlog.js index 3711683..1ff8810 100644 --- a/frontend/public/js/newBlog.js +++ b/frontend/public/js/newBlog.js @@ -82,7 +82,7 @@ async function publishBlog(unlisted, edit) { title: qs("#title").value, description: qs("#description").value, content: qs("#content").value, - unlisted: unlisted ? true : false, + visibility: unlisted ? "UNLISTED" : "PUBLISHED", date: qs("#date").value, time: qs("#time").value, }; @@ -114,7 +114,7 @@ async function publishBlog(unlisted, edit) { const res = await request("/api/web/blog", method, form_data); if (res.body.success) { - window.location.href = `/blog/${res.body.blog_id}`; + window.location.href = `/blog/${res.body.blog_id || blog_id}`; } } diff --git a/package-lock.json b/package-lock.json index a059812..2340992 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,6 @@ "feed": "^4.2.2", "markdown-it": "^13.0.1", "multer": "^1.4.5-lts.1", - "node-persist": "^3.1.3", "sharp": "^0.32.5" }, "devDependencies": { @@ -3075,14 +3074,6 @@ } } }, - "node_modules/node-persist": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/node-persist/-/node-persist-3.1.3.tgz", - "integrity": "sha512-CaFv+kSZtsc+VeDRldK1yR47k1vPLBpzYB9re2z7LIwITxwBtljMq3s8VQnnr+x3E8pQfHbc5r2IyJsBLJhtXg==", - "engines": { - "node": ">=10.12.0" - } - }, "node_modules/nodemon": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.0.1.tgz", diff --git a/package.json b/package.json index 25dccae..dd8fdff 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,6 @@ "feed": "^4.2.2", "markdown-it": "^13.0.1", "multer": "^1.4.5-lts.1", - "node-persist": "^3.1.3", "sharp": "^0.32.5" } } diff --git a/prisma/schema.prisma b/prisma/schema.prisma index adf710a..e81ec2b 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -14,7 +14,9 @@ model User { id String @id @unique @default(uuid()) username String @unique password String - role Role @default(VISITOR) + + role Role @default(USER) + group String? blog_posts BlogPost[] profile_page ProfilePage? @@ -30,9 +32,13 @@ model BlogPost { thumbnail String? images String[] visibility PostStatus @default(UNLISTED) - owner User? @relation(fields: [ownerid], references: [id]) + owner User? @relation(fields: [ownerid], references: [id], onDelete: Cascade) ownerid String? + // Tags + organization_tag String? + keyword_tags String[] + // Dates publish_date DateTime? created_date DateTime @default(now()) @@ -43,14 +49,23 @@ model ProfilePage { content String? images String[] visibility PostStatus @default(UNLISTED) - owner User @relation(fields: [ownerid], references: [id]) + owner User @relation(fields: [ownerid], references: [id], onDelete: Cascade) ownerid String @unique } +model Setting { + id String @unique + value Json +} + +model Group { + id String @unique + permissions String[] +} + enum Role { LOCKED - VISITOR - AUTHOR + USER ADMIN } diff --git a/yab.js b/yab.js index cb2d6f0..3eb9e0a 100644 --- a/yab.js +++ b/yab.js @@ -7,6 +7,7 @@ const path = require("path"); // Local modules const page_scripts = require("./backend/page_scripts"); +const internal = require("./backend/core/internal_api"); // Express settings app.set("view-engine", "ejs"); @@ -23,30 +24,28 @@ app.use( }) ); -// Account Creation Endpoints -app.get("/login", page_scripts.login); -app.post("/login", checkNotAuthenticated, page_scripts.loginPost); -app.get("/register", checkNotAuthenticated, page_scripts.register); -app.post("/register", checkNotAuthenticated, page_scripts.registerPost); +// API +app.post("/login", checkNotAuthenticated, internal.postLogin); +app.post("/register", checkNotAuthenticated, internal.postRegister); +app.post("/setting", checkAuthenticated, internal.postSetting); +app.post("/api/web/blog", checkAuthenticated, internal.postBlog); +app.delete("/api/web/blog/image", checkAuthenticated, internal.deleteImage); +app.delete("/api/web/blog", checkAuthenticated, internal.deleteBlog); +app.patch("/api/web/blog", checkAuthenticated, internal.patchBlog); -// Account Required Endpoints -app.post("/setting", checkAuthenticated, page_scripts.settingPost); -app.get("/blog/new", checkAuthenticated, page_scripts.blogNew); -app.post("/api/web/blog", checkAuthenticated, page_scripts.postBlog); -app.delete("/api/web/blog", checkAuthenticated, page_scripts.deleteBlog); -app.patch("/api/web/blog", checkAuthenticated, page_scripts.updateBlog); -app.delete("/api/web/blog/image", checkAuthenticated, page_scripts.deleteImage); // app.delete("/logout", page_scripts.logout); // Endpoints app.get("/", page_scripts.index); +app.get("/login", page_scripts.login); +app.get("/register", checkNotAuthenticated, page_scripts.register); app.get("/author/:author_username", page_scripts.author); app.get("/admin", checkAuthenticated, page_scripts.admin); app.get("/blog", page_scripts.blogList); +app.get("/blog/new", checkAuthenticated, page_scripts.blogNew); app.get("/blog/:blog_id", page_scripts.blogSingle); app.get("/blog/:blog_id/edit", checkAuthenticated, page_scripts.blogEdit); app.get("/atom", page_scripts.atom); -// app.get("/rss", page_scripts.rss); function checkAuthenticated(req, res, next) { if (req.session.user) return next();