database cleanup (#3)

Reviewed-on: #3
Co-authored-by: Armored-Dragon <forgejo3829105@armoreddragon.com>
Co-committed-by: Armored-Dragon <forgejo3829105@armoreddragon.com>
pull/2/head
Armored Dragon 2023-11-15 18:49:09 +00:00 committed by Armored Dragon
parent 624b46e345
commit bc92cd5681
12 changed files with 287 additions and 294 deletions

View File

@ -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,28 +11,55 @@ const s3 = new S3Client({
region: process.env.S3_REGION,
endpoint: process.env.S3_ENDPOINT,
});
const settings = require("../settings");
const md = require("markdown-it")();
async function registerUser(username, password, options) {
const new_user = await prisma.user.create({ data: { username: username, password: password, ...options } });
let settings = {
SETUP_COMPLETE: false,
ACCOUNT_REGISTRATION: false,
HIDE_LOGIN: false,
BLOG_UPLOADING: false,
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);
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) {
let user_database_entry;
let user_profile_database_entry;
// Create the entry in the database
try {
user_database_entry = await prisma.user.create({ data: { username: username, password: password, ...options } });
} catch (e) {
let message;
if (e.code === "P2002") message = "Username already exists";
else message = "Unknown error";
return { success: false, message: 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}` };
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 ${new_user.username}` };
return { success: true, message: `Successfully created ${username}` };
}
return { success: false, message: "Unknown error" };
}
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 } });
@ -41,32 +67,21 @@ async function getUser({ id, 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";
if (blog_post.images) {
// For Each image, upload to S3
if (blog_post.images) {
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 each of the posts in the list
for (blog_post of blog_posts) {
rendered_post_list.push(await _renderPost(blog_post, true));
}
// 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);
}
// 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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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,
};

View File

@ -1,3 +1,3 @@
// TODO: Permissions file
function postBlog(user) {}
function checkPermissions(role, { minimum = true }) {}
module.exports = { postBlog };

View File

@ -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 };

View File

@ -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}`;
}
}

9
package-lock.json generated
View File

@ -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",

View File

@ -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"
}
}

View File

@ -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
}

25
yab.js
View File

@ -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();