Post-tags (#9)
Reviewed-on: #9 Co-authored-by: Armored Dragon <publicmail@armoreddragon.com> Co-committed-by: Armored Dragon <publicmail@armoreddragon.com>pull/2/head
parent
d24f87e23a
commit
78923279be
|
@ -74,7 +74,7 @@ async function registerUser(username, password, options) {
|
||||||
return { success: true, message: `Successfully created ${username}` };
|
return { success: true, message: `Successfully created ${username}` };
|
||||||
}
|
}
|
||||||
// Posts
|
// Posts
|
||||||
async function getBlog({ id, visibility = "PUBLISHED", owner_id, limit = 10, page = 0, tags = [], search_title = false, search_content = false, search }) {
|
async function getBlog({ id, visibility = "PUBLISHED", owner_id, limit = 10, page = 0, search_title = false, search_content = false, search_tags = false, search }) {
|
||||||
// If we have an ID, we want a single post
|
// If we have an ID, we want a single post
|
||||||
if (id) {
|
if (id) {
|
||||||
// Get the post by the id
|
// Get the post by the id
|
||||||
|
@ -110,14 +110,22 @@ async function getBlog({ id, visibility = "PUBLISHED", owner_id, limit = 10, pag
|
||||||
ownerid: owner_id,
|
ownerid: owner_id,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
AND: [],
|
|
||||||
|
AND: [
|
||||||
|
{
|
||||||
|
OR: [
|
||||||
|
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
// Build the "where_object" object
|
// Build the "where_object" object
|
||||||
if (tags.length > 0) {
|
if (search){
|
||||||
|
if (search_tags) where_object["AND"][0]["OR"].push({ tags: { hasSome: [search?.toLowerCase()] }});
|
||||||
|
if (search_title) where_object["AND"][0]["OR"].push({ title: { contains: search, mode: "insensitive" } });
|
||||||
|
if (search_content) where_object["AND"][0]["OR"].push({ content: { contains: search, mode: "insensitive" } });
|
||||||
}
|
}
|
||||||
if (search_title) where_object["AND"].push({ title: { contains: search, mode: "insensitive" } });
|
|
||||||
if (search_content) where_object["AND"].push({ content: { contains: search, mode: "insensitive" } });
|
|
||||||
|
|
||||||
// Execute search
|
// Execute search
|
||||||
const blog_posts = await prisma.blogPost.findMany({
|
const blog_posts = await prisma.blogPost.findMany({
|
||||||
|
@ -171,6 +179,7 @@ async function postBlog(blog_post, owner_id) {
|
||||||
content: blog_post.content,
|
content: blog_post.content,
|
||||||
visibility: blog_post.visibility,
|
visibility: blog_post.visibility,
|
||||||
publish_date: blog_post.publish_date,
|
publish_date: blog_post.publish_date,
|
||||||
|
tags: blog_post.tags,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Save to database
|
// Save to database
|
||||||
|
@ -203,13 +212,15 @@ async function postBlog(blog_post, owner_id) {
|
||||||
}
|
}
|
||||||
async function deleteBlog(blog_id, requester_id) {
|
async function deleteBlog(blog_id, requester_id) {
|
||||||
const user = await getUser({ id: requester_id });
|
const user = await getUser({ id: requester_id });
|
||||||
const post = await getPost({ id: blog_id });
|
const post = await getBlog({ id: blog_id });
|
||||||
|
|
||||||
let can_delete = post.owner.id === user.data.id || user.data.role === "ADMIN";
|
if (!post.success) return { success: false, message: post.message || "Post does not exist" };
|
||||||
|
|
||||||
|
let can_delete = post.data.owner.id === user.data.id || user.data.role === "ADMIN";
|
||||||
|
|
||||||
if (can_delete) {
|
if (can_delete) {
|
||||||
await prisma.blogPost.delete({ where: { id: post.id } });
|
await prisma.blogPost.delete({ where: { id: post.data.id } });
|
||||||
_deleteS3Directory(post.id, "blog");
|
_deleteS3Directory(post.data.id, "blog");
|
||||||
return { success: true };
|
return { success: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -217,27 +228,35 @@ async function deleteBlog(blog_id, requester_id) {
|
||||||
}
|
}
|
||||||
async function updateBlog(blog_post, requester_id) {
|
async function updateBlog(blog_post, requester_id) {
|
||||||
const user = await getUser({ id: requester_id });
|
const user = await getUser({ id: requester_id });
|
||||||
const post = await getPost({ id: blog_post.id, raw: true });
|
const post = await getBlog({ id: blog_post.id, raw: true });
|
||||||
|
let publish_date = null;
|
||||||
|
|
||||||
delete blog_post.id;
|
delete blog_post.id;
|
||||||
|
|
||||||
let can_update = post.owner.id === user.data.id || user.data.role === "ADMIN";
|
if (!post.success) return { success: false, message: post.message || "Post not found" };
|
||||||
|
|
||||||
|
let can_update = post.data.owner.id === user.data.id || user.data.role === "ADMIN";
|
||||||
|
|
||||||
if (!can_update) return { success: false, message: "User not permitted" };
|
if (!can_update) return { success: false, message: "User not permitted" };
|
||||||
|
|
||||||
|
// FIXME: Unsure if this actually works
|
||||||
|
// Check if we already have a formatted publish date
|
||||||
|
if (typeof blog_post.publish_date !== "object") {
|
||||||
const [year, month, day] = blog_post.date.split("-");
|
const [year, month, day] = blog_post.date.split("-");
|
||||||
const [hour, minute] = blog_post.time.split(":");
|
const [hour, minute] = blog_post.time.split(":");
|
||||||
let publish_date = new Date(year, month - 1, day, hour, minute);
|
publish_date = new Date(year, month - 1, day, hour, minute);
|
||||||
|
}
|
||||||
|
|
||||||
let blog_post_formatted = {
|
let blog_post_formatted = {
|
||||||
title: blog_post.title,
|
title: blog_post.title,
|
||||||
description: blog_post.description,
|
description: blog_post.description,
|
||||||
content: blog_post.content,
|
content: blog_post.content,
|
||||||
visibility: blog_post.unlisted ? "UNLISTED" : "PUBLISHED",
|
visibility: blog_post.unlisted ? "UNLISTED" : "PUBLISHED",
|
||||||
publish_date: publish_date,
|
publish_date: publish_date || blog_post.publish_date,
|
||||||
|
tags: blog_post.tags,
|
||||||
};
|
};
|
||||||
|
|
||||||
await prisma.blogPost.update({ where: { id: post.id }, data: blog_post_formatted });
|
await prisma.blogPost.update({ where: { id: post.data.id }, data: blog_post_formatted });
|
||||||
|
|
||||||
let uploaded_images = [];
|
let uploaded_images = [];
|
||||||
let uploaded_thumbnail = "DEFAULT";
|
let uploaded_thumbnail = "DEFAULT";
|
||||||
|
@ -253,24 +272,24 @@ async function updateBlog(blog_post, requester_id) {
|
||||||
}
|
}
|
||||||
|
|
||||||
let data_to_update = {
|
let data_to_update = {
|
||||||
images: [...post.raw_images, ...uploaded_images],
|
images: [...post.data.raw_images, ...uploaded_images],
|
||||||
};
|
};
|
||||||
|
|
||||||
if (blog_post.thumbnail) {
|
if (blog_post.thumbnail) {
|
||||||
const image_data = Buffer.from(blog_post.thumbnail.data_blob.split(",")[1], "base64");
|
const image_data = Buffer.from(blog_post.thumbnail.data_blob.split(",")[1], "base64");
|
||||||
const name = await _uploadImage(post.id, "blog", true, image_data, blog_post.thumbnail.id);
|
const name = await _uploadImage(post.data.id, "blog", true, image_data, blog_post.thumbnail.id);
|
||||||
uploaded_thumbnail = name;
|
uploaded_thumbnail = name;
|
||||||
|
|
||||||
data_to_update.thumbnail = uploaded_thumbnail;
|
data_to_update.thumbnail = uploaded_thumbnail;
|
||||||
}
|
}
|
||||||
|
|
||||||
await prisma.blogPost.update({ where: { id: post.id }, data: data_to_update });
|
await prisma.blogPost.update({ where: { id: post.data.id }, data: data_to_update });
|
||||||
|
|
||||||
return { success: true };
|
return { success: true };
|
||||||
}
|
}
|
||||||
async function deleteImage(image, requester_id) {
|
async function deleteImage(image, requester_id) {
|
||||||
const user = await getUser({ id: requester_id });
|
const user = await getUser({ id: requester_id });
|
||||||
const post = await getPost({ id: image.parent, raw: true });
|
const post = await getBlog({ id: image.parent, raw: true });
|
||||||
|
|
||||||
// Check if post exists
|
// Check if post exists
|
||||||
if (!post) return { success: false, message: "Post does not exist" };
|
if (!post) return { success: false, message: "Post does not exist" };
|
||||||
|
@ -391,9 +410,7 @@ function _format_blog_content(content, images) {
|
||||||
const video = /{video:([^}]+)}/g;
|
const video = /{video:([^}]+)}/g;
|
||||||
|
|
||||||
content = content.replace(video, (match, inner_content) => {
|
content = content.replace(video, (match, inner_content) => {
|
||||||
return `<div class='video-embed'><iframe src="${_getVideoEmbed(
|
return `<div class='video-embed'><iframe src="${_getVideoEmbed(inner_content)}" frameborder="0" allow="accelerometer; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div>`;
|
||||||
inner_content
|
|
||||||
)}" frameborder="0" allow="accelerometer; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div>`;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
content = content.replace(image_regex, (match, image_name) => {
|
content = content.replace(image_regex, (match, image_name) => {
|
||||||
|
|
|
@ -9,8 +9,7 @@ async function postRegister(req, res) {
|
||||||
|
|
||||||
// User registration disabled?
|
// User registration disabled?
|
||||||
// We also check if the server was setup. If it was not set up, the server will proceed anyways.
|
// 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"])
|
if (!core.settings["ACCOUNT_REGISTRATION"] && core.settings["SETUP_COMPLETE"]) return res.json({ success: false, message: "Account registrations are disabled" });
|
||||||
return res.json({ success: false, message: "Account registrations are disabled" });
|
|
||||||
|
|
||||||
// User data valid?
|
// User data valid?
|
||||||
if (!form_validation.success) return res.json({ success: false, message: form_validation.message });
|
if (!form_validation.success) return res.json({ success: false, message: form_validation.message });
|
||||||
|
@ -70,8 +69,15 @@ async function deleteBlog(req, res) {
|
||||||
return res.json(await core.deleteBlog(req.body.id, req.session.user.id));
|
return res.json(await core.deleteBlog(req.body.id, req.session.user.id));
|
||||||
}
|
}
|
||||||
async function patchBlog(req, res) {
|
async function patchBlog(req, res) {
|
||||||
|
// FIXME: validate does not return post id
|
||||||
|
// Can user change post?
|
||||||
|
// User is admin, or user is author
|
||||||
|
|
||||||
|
// Validate blog info
|
||||||
|
const valid = await validate.postBlog(req.body);
|
||||||
|
|
||||||
// TODO: Permissions for updating blog
|
// TODO: Permissions for updating blog
|
||||||
return res.json(await core.updateBlog(req.body, req.session.user.id));
|
return res.json(await core.updateBlog({ ...valid.data, id: req.body.id }, req.session.user.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { postRegister, postLogin, postSetting, deleteImage, postBlog, deleteBlog, patchBlog };
|
module.exports = { postRegister, postLogin, postSetting, deleteImage, postBlog, deleteBlog, patchBlog };
|
||||||
|
|
|
@ -25,6 +25,19 @@ async function postBlog(blog_object) {
|
||||||
const [hour, minute] = blog_object.time.split(":");
|
const [hour, minute] = blog_object.time.split(":");
|
||||||
let publish_date = new Date(year, month - 1, day, hour, minute);
|
let publish_date = new Date(year, month - 1, day, hour, minute);
|
||||||
|
|
||||||
|
// Go though our tags and ensure they are:
|
||||||
|
let valid_tag_array = [];
|
||||||
|
blog_object.tags.forEach((tag) => {
|
||||||
|
// Trimmed
|
||||||
|
tag = tag.trim();
|
||||||
|
|
||||||
|
// Lowercase
|
||||||
|
tag = tag.toLowerCase();
|
||||||
|
|
||||||
|
// Non-empty
|
||||||
|
if (tag.length !== 0) valid_tag_array.push(tag);
|
||||||
|
});
|
||||||
|
|
||||||
// Format our data to save
|
// Format our data to save
|
||||||
let blog_post_formatted = {
|
let blog_post_formatted = {
|
||||||
title: blog_object.title,
|
title: blog_object.title,
|
||||||
|
@ -32,6 +45,7 @@ async function postBlog(blog_object) {
|
||||||
content: blog_object.content,
|
content: blog_object.content,
|
||||||
visibility: blog_object.visibility,
|
visibility: blog_object.visibility,
|
||||||
publish_date: publish_date,
|
publish_date: publish_date,
|
||||||
|
tags: valid_tag_array,
|
||||||
images: blog_object.images,
|
images: blog_object.images,
|
||||||
thumbnail: blog_object.thumbnail,
|
thumbnail: blog_object.thumbnail,
|
||||||
};
|
};
|
||||||
|
|
|
@ -27,7 +27,7 @@ async function author(req, res) {
|
||||||
res.render("author.ejs", { ...getDefaults(req), blog_post: profile.data });
|
res.render("author.ejs", { ...getDefaults(req), blog_post: profile.data });
|
||||||
}
|
}
|
||||||
async function blogList(req, res) {
|
async function blogList(req, res) {
|
||||||
const blog_list = await core.getBlog({ owner_id: req.session.user?.id, page: req.query.page || 0, search: req.query.search, search_title: true });
|
const blog_list = await core.getBlog({ owner_id: req.session.user?.id, page: req.query.page || 0, search: req.query.search, search_tags: true, search_title: true });
|
||||||
res.render("blogList.ejs", {
|
res.render("blogList.ejs", {
|
||||||
...getDefaults(req),
|
...getDefaults(req),
|
||||||
blog_list: blog_list.data,
|
blog_list: blog_list.data,
|
||||||
|
|
|
@ -221,6 +221,7 @@ a.bad {
|
||||||
.container .setting-row .setting-toggleable input {
|
.container .setting-row .setting-toggleable input {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: auto 0 auto auto;
|
margin: auto 0 auto auto;
|
||||||
|
height: 25px;
|
||||||
width: 100px;
|
width: 100px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
|
@ -66,7 +66,7 @@
|
||||||
input {
|
input {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: auto 0 auto auto;
|
margin: auto 0 auto auto;
|
||||||
// width: 100%;
|
height: 25px;
|
||||||
width: 100px;
|
width: 100px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
|
@ -130,6 +130,20 @@
|
||||||
outline: 0;
|
outline: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.e-tags {
|
||||||
|
min-height: 40px;
|
||||||
|
width: 100%;
|
||||||
|
background-color: #222;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
padding: 5px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
.e-tags input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.e-settings {
|
.e-settings {
|
||||||
min-height: 40px;
|
min-height: 40px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
|
@ -145,6 +145,21 @@ $background-body: #222;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.e-tags {
|
||||||
|
min-height: 40px;
|
||||||
|
width: 100%;
|
||||||
|
background-color: $background-body;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
padding: 5px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
|
||||||
|
input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.e-settings {
|
.e-settings {
|
||||||
min-height: 40px;
|
min-height: 40px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
|
@ -88,10 +88,18 @@ async function publishBlog(unlisted, edit) {
|
||||||
description: qs("#description").value,
|
description: qs("#description").value,
|
||||||
content: qs("#content").value,
|
content: qs("#content").value,
|
||||||
visibility: unlisted ? "UNLISTED" : "PUBLISHED",
|
visibility: unlisted ? "UNLISTED" : "PUBLISHED",
|
||||||
|
tags: [],
|
||||||
date: qs("#date").value,
|
date: qs("#date").value,
|
||||||
time: qs("#time").value,
|
time: qs("#time").value,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Get our tags, trim them, then shove them into an array
|
||||||
|
const tags_value = qs("#tags").value || "";
|
||||||
|
if (tags_value.length) {
|
||||||
|
let tags_array = qs("#tags").value.split(",");
|
||||||
|
tags_array.forEach((tag) => form_data.tags.push(tag.trim()));
|
||||||
|
}
|
||||||
|
|
||||||
// If we have a thumbnail, read the thumbnail image and store it
|
// If we have a thumbnail, read the thumbnail image and store it
|
||||||
if (pending_thumbnail.data_blob) {
|
if (pending_thumbnail.data_blob) {
|
||||||
form_data.thumbnail = { ...pending_thumbnail, data_blob: await _readFile(pending_thumbnail.data_blob) };
|
form_data.thumbnail = { ...pending_thumbnail, data_blob: await _readFile(pending_thumbnail.data_blob) };
|
||||||
|
@ -161,8 +169,7 @@ function customDragString() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateImages() {
|
function updateImages() {
|
||||||
const image_div = (img_id, img_url) =>
|
const image_div = (img_id, img_url) => `<div class="image"><img data-image_id="${img_id}" src="${img_url}" /><div><a onclick="deleteImage('${img_id}')">X</a></div></div>`;
|
||||||
`<div class="image"><img data-image_id="${img_id}" src="${img_url}" /><div><a onclick="deleteImage('${img_id}')">X</a></div></div>`;
|
|
||||||
|
|
||||||
// Clear existing listings
|
// Clear existing listings
|
||||||
qsa(".e-image-area .image").forEach((entry) => entry.remove());
|
qsa(".e-image-area .image").forEach((entry) => entry.remove());
|
||||||
|
|
|
@ -67,7 +67,9 @@
|
||||||
</div>
|
</div>
|
||||||
<textarea id="content" placeholder="Tell us about your subject..."><%= existing_blog.raw_content %></textarea>
|
<textarea id="content" placeholder="Tell us about your subject..."><%= existing_blog.raw_content %></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="e-tags">
|
||||||
|
<input id="tags" type="text" placeholder="Enter a comma separated list of tags" value="<%= existing_blog.tags %>" />
|
||||||
|
</div>
|
||||||
<div class="e-settings">
|
<div class="e-settings">
|
||||||
<div class="publish-date">
|
<div class="publish-date">
|
||||||
<div>Publish On</div>
|
<div>Publish On</div>
|
||||||
|
|
|
@ -36,8 +36,7 @@ model BlogPost {
|
||||||
ownerid String?
|
ownerid String?
|
||||||
|
|
||||||
// Tags
|
// Tags
|
||||||
organization_tag String?
|
tags String[]
|
||||||
keyword_tags String[]
|
|
||||||
|
|
||||||
// Dates
|
// Dates
|
||||||
publish_date DateTime?
|
publish_date DateTime?
|
||||||
|
|
Loading…
Reference in New Issue