From e0b530c6061d16766f218e9f0b534881c9338ee5 Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Tue, 23 Apr 2024 08:13:48 -0500 Subject: [PATCH] Tags. Search by tags. Return tags used by posts. Signed-off-by: Armored Dragon --- backend/core/core.js | 61 ++++++++++++++++++-- backend/page_scripts.js | 4 +- frontend/views/themes/default/css/index.css | 5 +- frontend/views/themes/default/css/index.scss | 6 +- frontend/views/themes/default/ejs/index.ejs | 4 +- prisma/schema.prisma | 14 ++++- 6 files changed, 84 insertions(+), 10 deletions(-) diff --git a/backend/core/core.js b/backend/core/core.js index 8c46a9a..a7f09f1 100644 --- a/backend/core/core.js +++ b/backend/core/core.js @@ -131,9 +131,19 @@ async function getPost({ requester_id, post_id, visibility = "PUBLISHED" } = {}, // Get a single post if (post_id) { let post; - post = await prisma.post.findUnique({ where: { id: post_id }, include: { owner: true } }); + post = await prisma.post.findUnique({ where: { id: post_id }, include: { owner: true, tags: true } }); if (!post) return _r(false, "Post does not exist"); post = _stripPrivatePost(post); + + // Tags + let post_tags = []; + post.raw_tags = []; + post.tags.forEach((tag) => { + post_tags.push(tag.name); + post.raw_tags.push(); + }); + post.tags = post_tags; + // Render post return { success: true, data: await _renderPost(post) }; } @@ -170,7 +180,7 @@ async function getPost({ requester_id, post_id, visibility = "PUBLISHED" } = {}, }; // Build the "where_object" object if (search) { - if (search_tags) where_object["AND"][0]["OR"].push({ tags: { hasSome: [search?.toLowerCase()] } }); + if (search_tags) where_object["AND"][0]["OR"].push({ tags: { some: { name: 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" } }); } @@ -179,7 +189,7 @@ async function getPost({ requester_id, post_id, visibility = "PUBLISHED" } = {}, where: where_object, take: limit, skip: Math.max(page, 0) * limit, - include: { owner: true }, + include: { owner: true, tags: true }, orderBy: [{ publish_date: "desc" }, { created_date: "desc" }], }); @@ -187,6 +197,10 @@ async function getPost({ requester_id, post_id, visibility = "PUBLISHED" } = {}, post = _stripPrivatePost(post); post = await _renderPost(post); post_list.push(post); + + let post_tags = []; + post.tags.forEach((tag) => post_tags.push(tag.name)); + post.tags = post_tags; } // Calculate pagination @@ -226,6 +240,20 @@ async function editPost({ requester_id, post_id, post_content }) { publish_date = new Date(year, month - 1, day, hour, minute); } + // Handle tags ---- + let database_tag_list = []; + const existing_tags = post.tags?.map((tag) => ({ name: tag })) || []; + + // Add new tags + for (let tag_index = 0; post_content.tags.length > tag_index; tag_index++) { + let tag = post_content.tags[tag_index]; + + // Check to see if tag exists, create if necessary, + let database_tag = await prisma.tag.upsert({ where: { name: tag }, update: {}, create: { name: tag } }); + + database_tag_list.push(database_tag); + } + // Rebuild the post to save let post_formatted = { title: post_content.title, @@ -233,7 +261,7 @@ async function editPost({ requester_id, post_id, post_content }) { content: post_content.content, visibility: post_content.visibility || "PRIVATE", publish_date: publish_date || post_content.publish_date, - tags: post_content.tags, + tags: { disconnect: [...existing_tags], connect: [...database_tag_list] }, media: [...post.raw_media, ...post_content.media], }; @@ -345,6 +373,29 @@ async function getMedia({ parent_id, file_name }) { // Unreferenced images and media will be deleted async function deleteMedia({ parent_id, file_name }) {} +async function getTags({ order = "count" } = {}) { + if (order == "count") { + return await prisma.tag.findMany({ + include: { _count: { select: { posts: true } } }, + where: { + posts: { + some: {}, + }, + }, + take: 15, + orderBy: { + posts: { + _count: "desc", + }, + }, + }); + } +} + +// TODO: +// Will be done automatically in the background +async function deleteTag({ tag_id }) {} + // async function deleteImage(image, requester_id) { // const user = await getUser({ id: requester_id }); // const post = await getBlog({ id: image.parent, raw: true }); @@ -540,4 +591,4 @@ const _r = (s, m) => { return { success: s, message: m }; }; -module.exports = { settings, newUser, getUser, editUser, getPost, newPost, editPost, getBiography, updateBiography, uploadMedia, deleteBlog, postSetting, getSetting }; +module.exports = { settings, newUser, getUser, editUser, getPost, newPost, editPost, getBiography, updateBiography, uploadMedia, deleteBlog, getTags, postSetting, getSetting }; diff --git a/backend/page_scripts.js b/backend/page_scripts.js index c0d84b8..5bee6e8 100644 --- a/backend/page_scripts.js +++ b/backend/page_scripts.js @@ -14,6 +14,7 @@ async function index(request, response) { // if (!is_setup_complete) return response.redirect("/register"); const blog_list = await core.getPost({ requester_id: request.session.user?.id, page: request.query.page || 0 }); + const tags = await core.getTags(); blog_list.data.forEach((post) => { let published_date_parts = new Date(post.publish_date).toLocaleDateString().split("/"); @@ -27,6 +28,7 @@ async function index(request, response) { pagination: blog_list.pagination, current_page: request.query.page || 0, loaded_page: request.path, + tags: tags, }); } function register(request, response) { @@ -52,7 +54,7 @@ async function authorEdit(request, response) { response.render(getThemePage("authorEdit"), { ...getDefaults(request), profile: author.data }); } async function blogList(req, res) { - const blog_list = await core.getPost({ requester_id: req.session.user?.id }, { search: req.query.search, search_title: true }); + const blog_list = await core.getPost({ requester_id: req.session.user?.id }, { search: req.query.search, search_title: true, search_tags: true, search_content: true }); blog_list.data.forEach((post) => { let published_date_parts = new Date(post.publish_date).toLocaleDateString().split("/"); diff --git a/frontend/views/themes/default/css/index.css b/frontend/views/themes/default/css/index.css index 6e6f309..7ccc688 100644 --- a/frontend/views/themes/default/css/index.css +++ b/frontend/views/themes/default/css/index.css @@ -85,7 +85,8 @@ height: -moz-fit-content; height: fit-content; background-color: lightgray; - margin-bottom: 0.1rem; + margin-bottom: 0.5rem; + margin-right: 0.5rem; } .post-list-container .tag-list .list .tag::before { display: none; @@ -98,6 +99,8 @@ padding: 0.2rem 0.3rem; box-sizing: border-box; border-radius: 4px; + color: black; + text-decoration: none; } .tag::before { diff --git a/frontend/views/themes/default/css/index.scss b/frontend/views/themes/default/css/index.scss index 5b6e23a..c089a8b 100644 --- a/frontend/views/themes/default/css/index.scss +++ b/frontend/views/themes/default/css/index.scss @@ -95,7 +95,8 @@ .tag { height: fit-content; background-color: lightgray; - margin-bottom: 0.1rem; + margin-bottom: 0.5rem; + margin-right: 0.5rem; } .tag::before { @@ -111,6 +112,9 @@ padding: 0.2rem 0.3rem; box-sizing: border-box; border-radius: 4px; + + color: black; + text-decoration: none; } .tag::before { diff --git a/frontend/views/themes/default/ejs/index.ejs b/frontend/views/themes/default/ejs/index.ejs index 04db330..cb3f6d8 100644 --- a/frontend/views/themes/default/ejs/index.ejs +++ b/frontend/views/themes/default/ejs/index.ejs @@ -22,7 +22,9 @@
TAGS
-
Tag 1
+ <% for(tag of tags) { %> + <%= tag.name %> + <% } %>
diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 1d8d508..36a38ff 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -35,7 +35,7 @@ model Post { ownerid String? // Tags - tags String[] + tags Tag[] // Dates publish_date DateTime? @@ -62,6 +62,18 @@ model Group { permissions String[] } +model Tag { + id String @id @unique @default(uuid()) + name String @unique + type TagMode @default(NORMAL) + posts Post[] +} + +enum TagMode { + NORMAL + ALIAS +} + enum Role { LOCKED USER