diff --git a/backend/core/core.js b/backend/core/core.js index 3557aaa..09b2b4d 100644 --- a/backend/core/core.js +++ b/backend/core/core.js @@ -73,7 +73,83 @@ async function registerUser(username, password, options) { // User has been successfully created 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 } = {}) { let user; 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) { 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"; @@ -141,7 +217,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 getBlog({ id: blog_post.id, raw: true }); + const post = await getPost({ id: blog_post.id, raw: true }); delete blog_post.id; @@ -192,66 +268,9 @@ async function updateBlog(blog_post, requester_id) { 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) { 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 if (!post) return { success: false, message: "Post does not exist" }; @@ -461,4 +480,4 @@ async function postSetting(key, value) { async function _getGroups() { 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 }; diff --git a/backend/page_scripts.js b/backend/page_scripts.js index 0be8c91..86c31ff 100644 --- a/backend/page_scripts.js +++ b/backend/page_scripts.js @@ -8,7 +8,6 @@ function getDefaults(req) { async function index(request, response) { // Check if the master admin has been created - const is_setup_complete = core.settings["SETUP_COMPLETE"]; if (!is_setup_complete) return response.redirect("/register"); @@ -20,11 +19,15 @@ function register(request, response) { function login(request, response) { response.render("login.ejs", getDefaults(request)); } -function author(request, response) { - response.render("author.ejs", getDefaults(request)); +async function author(req, res) { + 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) { - 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", { ...getDefaults(req), blog_list: blog_list.data, @@ -34,9 +37,9 @@ async function blogList(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"); - res.render("blogSingle.ejs", { ...getDefaults(req), blog_post: blog }); + res.render("blogSingle.ejs", { ...getDefaults(req), blog_post: blog.data }); } function blogNew(request, response) { // TODO: Turn date formatting into function @@ -52,7 +55,8 @@ function blogNew(request, response) { response.render("blogNew.ejs", { ...getDefaults(request), existing_blog: existing_blog }); } 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(":"); const formatted_time = `${published_time_parts[0].padStart(2, "0")}:${published_time_parts[1].padStart(2, "0")}`; diff --git a/frontend/public/css/blog-list.css b/frontend/public/css/blog-list.css index d35e84a..c561f5c 100644 --- a/frontend/public/css/blog-list.css +++ b/frontend/public/css/blog-list.css @@ -14,6 +14,30 @@ 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 { width: 100%; display: grid; @@ -79,6 +103,20 @@ } @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 { grid-template-columns: 75px auto; margin-bottom: 20px; diff --git a/frontend/public/css/blog-list.scss b/frontend/public/css/blog-list.scss index e4e9cbb..08d0736 100644 --- a/frontend/public/css/blog-list.scss +++ b/frontend/public/css/blog-list.scss @@ -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 { width: 100%; display: grid; @@ -89,6 +115,20 @@ $quiet-text: #9f9f9f; } @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 { .blog-entry { grid-template-columns: 75px auto; diff --git a/frontend/public/js/postList.js b/frontend/public/js/postList.js new file mode 100644 index 0000000..8de545a --- /dev/null +++ b/frontend/public/js/postList.js @@ -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}`; +} diff --git a/frontend/views/blogList.ejs b/frontend/views/blogList.ejs index 8bb8138..97b2379 100644 --- a/frontend/views/blogList.ejs +++ b/frontend/views/blogList.ejs @@ -14,6 +14,10 @@
<%if(logged_in_user) {%> <%- include("partials/blog-admin.ejs") %> <%}%> + +
+ +
<% for(post of blog_list) { %> @@ -25,3 +29,5 @@ <%- include("partials/footer.ejs") %> + + diff --git a/yab.js b/yab.js index 23a50e5..273c05d 100644 --- a/yab.js +++ b/yab.js @@ -39,7 +39,7 @@ app.patch("/api/web/blog", checkAuthenticated, internal.patchBlog); 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("/author/:author_id", page_scripts.author); app.get("/admin", checkAuthenticated, page_scripts.admin); app.get("/blog", page_scripts.blogList); app.get("/blog/new", checkAuthenticated, page_scripts.blogNew);