Post searching (#8)

Ability to search for posts

Reviewed-on: #8
Co-authored-by: Armored-Dragon <forgejo3829105@armoreddragon.com>
Co-committed-by: Armored-Dragon <forgejo3829105@armoreddragon.com>
pull/2/head
Armored Dragon 2023-12-30 23:45:44 +00:00 committed by Armored Dragon
parent 5ac2196d00
commit d24f87e23a
7 changed files with 182 additions and 69 deletions

View File

@ -73,7 +73,83 @@ async function registerUser(username, password, options) {
// User has been successfully created // User has been successfully created
return { success: true, message: `Successfully created ${username}` }; return { success: true, message: `Successfully created ${username}` };
} }
// Posts
async function getBlog({ id, visibility = "PUBLISHED", owner_id, limit = 10, page = 0, tags = [], search_title = false, search_content = false, search }) {
// If we have an ID, we want a single post
if (id) {
// Get the post by the id
let post = await prisma.blogPost.findUnique({ where: { id: id }, include: { owner: true } });
if (!post) return { success: false, message: "Post does not exist" };
// Render the post
const rendered_post = await _renderPost(post, true);
// Return the post with valid image urls
return { data: rendered_post, success: true };
}
// Otherwise build WHERE_OBJECT using data we do have
let rendered_post_list = [];
let where_object = {
OR: [
// Standard discovery: Public, and after the publish date
{
AND: [
{
visibility: "PUBLISHED",
},
{
publish_date: {
lte: new Date(),
},
},
],
},
// User owns the post
{
ownerid: owner_id,
},
],
AND: [],
};
// Build the "where_object" object
if (tags.length > 0) {
}
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
const blog_posts = await prisma.blogPost.findMany({
where: where_object,
take: limit,
skip: Math.max(page, 0) * limit,
include: { owner: true },
orderBy: [{ publish_date: "desc" }, { created_date: "desc" }],
});
// Render each of the posts in the list
for (post of blog_posts) {
rendered_post_list.push(await _renderPost(post, true));
}
// Calculate pagination
let pagination = await prisma.blogPost.count({
where: where_object,
});
return { data: rendered_post_list, pagination: _getNavigationList(page, Math.ceil(pagination / limit)), success: true };
}
async function getAuthorPage({ author_id }) {
// Get the post by the id
let post = await prisma.profilePage.findUnique({ where: { ownerid: author_id }, include: { owner: true } });
if (!post) return { success: false, message: "Post does not exist" };
// Render the post
const rendered_post = await _renderPost(post, true);
// Return the post with valid image urls
return { data: rendered_post, success: true };
}
async function getUser({ id, username } = {}) { async function getUser({ id, username } = {}) {
let user; let user;
if (id) user = await prisma.user.findUnique({ where: { id: id } }); if (id) user = await prisma.user.findUnique({ where: { id: id } });
@ -127,7 +203,7 @@ 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 getBlog({ id: blog_id }); const post = await getPost({ id: blog_id });
let can_delete = post.owner.id === user.data.id || user.data.role === "ADMIN"; let can_delete = post.owner.id === user.data.id || user.data.role === "ADMIN";
@ -141,7 +217,7 @@ 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 getBlog({ id: blog_post.id, raw: true }); const post = await getPost({ id: blog_post.id, raw: true });
delete blog_post.id; delete blog_post.id;
@ -192,66 +268,9 @@ async function updateBlog(blog_post, requester_id) {
return { success: true }; return { success: true };
} }
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" };
// Render the post
const rendered_post = _renderPost(post, true);
// Return the post with valid image urls
return rendered_post;
}
let rendered_post_list = [];
const where_object = {
OR: [
// Standard discovery: Public, and after the publish date
{
AND: [
{
visibility: "PUBLISHED",
},
{
publish_date: {
lte: new Date(),
},
},
],
},
// User owns the post
{
ownerid: owner_id,
},
],
};
const blog_posts = await prisma.blogPost.findMany({
where: where_object,
take: limit,
skip: Math.max(page, 0) * limit,
include: { owner: true },
orderBy: [{ publish_date: "desc" }, { created_date: "desc" }],
});
// 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: rendered_post_list, pagination: _getNavigationList(page, Math.ceil(pagination / limit)) };
}
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 getBlog({ id: image.parent, raw: true }); const post = await getPost({ 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" };
@ -461,4 +480,4 @@ async function postSetting(key, value) {
async function _getGroups() { async function _getGroups() {
const group_list = await prisma.group.findMany(); const group_list = await prisma.group.findMany();
} }
module.exports = { registerUser, getUser, postBlog, updateBlog, getBlogList: getBlog, deleteBlog, deleteImage, postSetting, getSetting, settings }; module.exports = { settings, registerUser, getUser, getAuthorPage, postBlog, updateBlog, getBlog, deleteBlog, deleteImage, postSetting, getSetting };

View File

@ -8,7 +8,6 @@ function getDefaults(req) {
async function index(request, response) { async function index(request, response) {
// Check if the master admin has been created // Check if the master admin has been created
const is_setup_complete = core.settings["SETUP_COMPLETE"]; const is_setup_complete = core.settings["SETUP_COMPLETE"];
if (!is_setup_complete) return response.redirect("/register"); if (!is_setup_complete) return response.redirect("/register");
@ -20,11 +19,15 @@ function register(request, response) {
function login(request, response) { function login(request, response) {
response.render("login.ejs", getDefaults(request)); response.render("login.ejs", getDefaults(request));
} }
function author(request, response) { async function author(req, res) {
response.render("author.ejs", getDefaults(request)); const user = await core.getUser({ id: req.params.author_id });
// FIXME: Bandage fix for author get error
if (!user.success) return res.redirect("/");
const profile = await core.getAuthorPage({ author_id: user.data.id });
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.getBlogList({ owner_id: req.session.user?.id, page: req.query.page || 0 }); const blog_list = await core.getBlog({ owner_id: req.session.user?.id, page: req.query.page || 0, search: req.query.search, 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,
@ -34,9 +37,9 @@ async function blogList(req, res) {
}); });
} }
async function blogSingle(req, res) { async function blogSingle(req, res) {
const blog = await core.getBlogList({ id: req.params.blog_id }); const blog = await core.getBlog({ id: req.params.blog_id });
if (blog.success === false) return res.redirect("/blog"); if (blog.success === false) return res.redirect("/blog");
res.render("blogSingle.ejs", { ...getDefaults(req), blog_post: blog }); res.render("blogSingle.ejs", { ...getDefaults(req), blog_post: blog.data });
} }
function blogNew(request, response) { function blogNew(request, response) {
// TODO: Turn date formatting into function // TODO: Turn date formatting into function
@ -52,7 +55,8 @@ function blogNew(request, response) {
response.render("blogNew.ejs", { ...getDefaults(request), existing_blog: existing_blog }); response.render("blogNew.ejs", { ...getDefaults(request), existing_blog: existing_blog });
} }
async function blogEdit(req, res) { async function blogEdit(req, res) {
const existing_blog = await core.getBlogList({ id: req.params.blog_id, raw: true }); let existing_blog = await core.getBlog({ id: req.params.blog_id, raw: true });
if (existing_blog.success) existing_blog = existing_blog.data; // FIXME: Quickfix for .success/.data issue
let published_time_parts = new Date(existing_blog.publish_date).toLocaleTimeString([], { timeStyle: "short" }).slice(0, 4).split(":"); 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")}`; const formatted_time = `${published_time_parts[0].padStart(2, "0")}:${published_time_parts[1].padStart(2, "0")}`;

View File

@ -14,6 +14,30 @@
border-radius: 5px; border-radius: 5px;
} }
.search-area {
width: 100%;
height: 30px;
margin-bottom: 10px;
display: flex;
flex-direction: row;
}
.search-area input {
width: 50%;
background-color: black;
border: 0;
outline: 0;
border-radius: 5px;
color: white;
height: 100%;
text-indent: 5px;
margin: 0 10px 0 auto;
}
.search-area button {
height: 100%;
min-width: 100px;
margin: 0 auto 0 0;
}
.blog-entry { .blog-entry {
width: 100%; width: 100%;
display: grid; display: grid;
@ -79,6 +103,20 @@
} }
@media screen and (max-width: 500px) { @media screen and (max-width: 500px) {
.search-area {
height: 60px;
flex-direction: column;
}
.search-area input {
width: 100%;
height: 30px;
margin: 0;
}
.search-area button {
height: 30px;
width: 100%;
margin: 0;
}
.page .blog-entry { .page .blog-entry {
grid-template-columns: 75px auto; grid-template-columns: 75px auto;
margin-bottom: 20px; margin-bottom: 20px;

View File

@ -19,6 +19,32 @@ $quiet-text: #9f9f9f;
} }
} }
.search-area {
width: 100%;
height: 30px;
margin-bottom: 10px;
display: flex;
flex-direction: row;
input {
width: 50%;
background-color: black;
border: 0;
outline: 0;
border-radius: 5px;
color: white;
height: 100%;
text-indent: 5px;
margin: 0 10px 0 auto;
}
button {
height: 100%;
min-width: 100px;
margin: 0 auto 0 0;
}
}
.blog-entry { .blog-entry {
width: 100%; width: 100%;
display: grid; display: grid;
@ -89,6 +115,20 @@ $quiet-text: #9f9f9f;
} }
@media screen and (max-width: 500px) { @media screen and (max-width: 500px) {
.search-area {
height: 60px;
flex-direction: column;
input {
width: 100%;
height: 30px;
margin: 0;
}
button {
height: 30px;
width: 100%;
margin: 0;
}
}
.page { .page {
.blog-entry { .blog-entry {
grid-template-columns: 75px auto; grid-template-columns: 75px auto;

View File

@ -0,0 +1,6 @@
qs("#search-btn").addEventListener("click", search);
function search() {
const url_query = `search=${qs("input").value}`;
window.location.href = `${window.location.origin}${window.location.pathname}?${url_query}`;
}

View File

@ -14,6 +14,10 @@
<div class="page"> <div class="page">
<%if(logged_in_user) {%> <%- include("partials/blog-admin.ejs") %> <%}%> <%if(logged_in_user) {%> <%- include("partials/blog-admin.ejs") %> <%}%>
<!-- Search area -->
<div class="search-area">
<input type="text" placeholder="Search..." /><button id="search-btn"><span>Search</span></button>
</div>
<!-- --> <!-- -->
<% for(post of blog_list) { %> <% for(post of blog_list) { %>
<!-- --> <!-- -->
@ -25,3 +29,5 @@
<%- include("partials/footer.ejs") %> <%- include("partials/footer.ejs") %>
</body> </body>
</html> </html>
<script defer src="/js/postList.js"></script>

2
yab.js
View File

@ -39,7 +39,7 @@ app.patch("/api/web/blog", checkAuthenticated, internal.patchBlog);
app.get("/", page_scripts.index); app.get("/", page_scripts.index);
app.get("/login", page_scripts.login); app.get("/login", page_scripts.login);
app.get("/register", checkNotAuthenticated, page_scripts.register); app.get("/register", checkNotAuthenticated, page_scripts.register);
app.get("/author/:author_username", page_scripts.author); app.get("/author/:author_id", page_scripts.author);
app.get("/admin", checkAuthenticated, page_scripts.admin); app.get("/admin", checkAuthenticated, page_scripts.admin);
app.get("/blog", page_scripts.blogList); app.get("/blog", page_scripts.blogList);
app.get("/blog/new", checkAuthenticated, page_scripts.blogNew); app.get("/blog/new", checkAuthenticated, page_scripts.blogNew);