From a1da324f566a71cf8b8b27799f368e81a83d5ad5 Mon Sep 17 00:00:00 2001 From: Damillora Date: Sat, 16 Apr 2022 19:23:19 +0700 Subject: [PATCH] feat: negative tag search --- pkg/app/tag_routes.go | 5 +- pkg/services/post.go | 134 +- pkg/services/tag.go | 52 +- web/app/package.json | 5 +- web/app/rollup.config.js | 2 + web/app/src/EditPostPanel.svelte | 5 +- web/app/src/api.js | 7 +- .../TagNotes/ViewTagNotesPanel.svelte | 6 - web/app/src/routes/Home.svelte | 9 +- web/app/src/routes/Post/Posts.svelte | 8 +- web/app/src/routes/Post/Upload.svelte | 6 +- web/app/yarn.lock | 1341 +++++++---------- 12 files changed, 739 insertions(+), 841 deletions(-) diff --git a/pkg/app/tag_routes.go b/pkg/app/tag_routes.go index e548c9b..bad0dd7 100644 --- a/pkg/app/tag_routes.go +++ b/pkg/app/tag_routes.go @@ -70,7 +70,10 @@ func tagGetRelated(c *gin.Context) { } func tagAutocomplete(c *gin.Context) { - tags := services.GetTagAutocomplete() + tagParam := c.Query("tag") + positiveParam := c.Query("positive") + + tags := services.GetTagAutocomplete(tagParam, positiveParam != "") c.JSON(http.StatusOK, tags) } diff --git a/pkg/services/post.go b/pkg/services/post.go index 8cc3b7f..ec9ea92 100644 --- a/pkg/services/post.go +++ b/pkg/services/post.go @@ -2,6 +2,7 @@ package services import ( "fmt" + "strings" "github.com/Damillora/Shioriko/pkg/database" "github.com/Damillora/Shioriko/pkg/models" @@ -17,36 +18,74 @@ func GetPostAll(page int) []database.Post { } func GetPostTags(page int, tagSyntax []string) []database.Post { - tags, err := ParseReadTags(tagSyntax) + positiveTagSyntax := []string{} + negativeTagSyntax := []string{} + for _, tag := range tagSyntax { + if strings.HasPrefix(tag, "-") { + negativeTagSyntax = append(negativeTagSyntax, strings.TrimPrefix(tag, "-")) + } else { + positiveTagSyntax = append(positiveTagSyntax, tag) + + } + } + positiveTags, err := ParseReadTags(positiveTagSyntax) + if err != nil { + return []database.Post{} + } + negativeTags, err := ParseReadTags(negativeTagSyntax) if err != nil { return []database.Post{} } - var tagIds []string - for _, tag := range tags { - tagIds = append(tagIds, tag.ID) + var positiveTagIds []string + for _, tag := range positiveTags { + positiveTagIds = append(positiveTagIds, tag.ID) } - fmt.Printf("%v", tagIds) - var postIds []string + var negativeTagIds []string + for _, tag := range negativeTags { + negativeTagIds = append(negativeTagIds, tag.ID) + } + + var positivePostIds []string database.DB. - Model(&tags). + Model(&positiveTags). Joins("join post_tags on post_tags.tag_id = tags.id"). Select("post_tags.post_id"). - Where("post_tags.tag_id IN ?", tagIds). + Where("post_tags.tag_id IN ?", positiveTagIds). Group("post_tags.post_id"). - Having("count(*) = ?", len(tagIds)). + Having("count(*) = ?", len(positiveTagIds)). Distinct(). - Find(&postIds) + Find(&positivePostIds) + var negativePostIds []string + database.DB. + Model(&positiveTags). + Joins("join post_tags on post_tags.tag_id = tags.id"). + Select("post_tags.post_id"). + Where("post_tags.tag_id IN ?", negativeTagIds). + Group("post_tags.post_id"). + Having("count(*) = ?", len(negativeTagIds)). + Distinct(). + Find(&negativePostIds) var posts []database.Post - database.DB. + query := database.DB. Joins("Blob"). Preload("Tags"). - Preload("Tags.TagType"). - Where("posts.id IN ?", postIds). - Order("created_at desc"). + Preload("Tags.TagType") + + if len(positivePostIds) > 0 && len(negativePostIds) > 0 { + query = query. + Where("posts.id IN ? AND posts.id NOT IN ?", positivePostIds, negativePostIds) + } else if len(positivePostIds) > 0 { + query = query. + Where("posts.id IN ?", positivePostIds) + } else if len(negativePostIds) > 0 { + query = query. + Where("posts.id NOT IN ?", negativePostIds) + } + query.Order("created_at desc"). Offset((page - 1) * perPage). Limit(20). Find(&posts) @@ -118,28 +157,73 @@ func CountPostPages() int { return int(count) } func CountPostPagesTag(tagSyntax []string) int { - tags, err := ParseReadTags(tagSyntax) + positiveTagSyntax := []string{} + negativeTagSyntax := []string{} + + for _, tag := range tagSyntax { + if strings.HasPrefix(tag, "-") { + negativeTagSyntax = append(negativeTagSyntax, strings.TrimPrefix(tag, "-")) + } else { + positiveTagSyntax = append(positiveTagSyntax, tag) + + } + } + positiveTags, err := ParseReadTags(positiveTagSyntax) + if err != nil { + return 0 + } + negativeTags, err := ParseReadTags(negativeTagSyntax) if err != nil { return 0 } - var tagIds []string - for _, tag := range tags { - tagIds = append(tagIds, tag.ID) + var positiveTagIds []string + for _, tag := range positiveTags { + positiveTagIds = append(positiveTagIds, tag.ID) } - fmt.Printf("%v", tagIds) - var count int64 + var negativeTagIds []string + for _, tag := range negativeTags { + negativeTagIds = append(negativeTagIds, tag.ID) + } + + var positivePostIds []string database.DB. - Model(&tags). - Distinct(). + Model(&positiveTags). Joins("join post_tags on post_tags.tag_id = tags.id"). Select("post_tags.post_id"). - Where("post_tags.tag_id IN ?", tagIds). + Where("post_tags.tag_id IN ?", positiveTagIds). Group("post_tags.post_id"). - Having("count(*) = ?", len(tagIds)). - Count(&count) + Having("count(*) = ?", len(positiveTagIds)). + Distinct(). + Find(&positivePostIds) + var negativePostIds []string + database.DB. + Model(&positiveTags). + Joins("join post_tags on post_tags.tag_id = tags.id"). + Select("post_tags.post_id"). + Where("post_tags.tag_id IN ?", negativeTagIds). + Group("post_tags.post_id"). + Having("count(*) = ?", len(negativeTagIds)). + Distinct(). + Find(&negativePostIds) + var count int64 + query := database.DB. + Model(&database.Post{}) + if len(positivePostIds) > 0 && len(negativePostIds) > 0 { + query = query. + Where("posts.id IN ? AND posts.id NOT IN ?", positivePostIds, negativePostIds) + } else if len(positivePostIds) > 0 { + query = query. + Where("posts.id IN ?", positivePostIds) + } else if len(negativePostIds) > 0 { + query = query. + Where("posts.id NOT IN ?", negativePostIds) + } + query. + Count(&count) + fmt.Println(count) return int(count) } diff --git a/pkg/services/tag.go b/pkg/services/tag.go index 9a97ed6..220b855 100644 --- a/pkg/services/tag.go +++ b/pkg/services/tag.go @@ -61,12 +61,56 @@ func GetTag(tagString string) (*models.TagReadModel, error) { return &tagModel, nil } -func GetTagAutocomplete() []string { +func GetTagAutocomplete(searchValue string, forcePositive bool) []string { + if forcePositive { + return getPositiveTagAutocomplete(strings.TrimPrefix(searchValue, "-")) + } + if strings.HasPrefix(searchValue, "-") { + return getNegativeTagAutocomplete(strings.TrimPrefix(searchValue, "-")) + } else { + return getPositiveTagAutocomplete(searchValue) + } +} + +func getPositiveTagAutocomplete(searchValue string) []string { var tags []string - result := database.DB.Model(&database.Tag{}). + query := database.DB.Model(&database.Tag{}). Joins("join tag_types on tag_types.id = tags.tag_type_id"). - Select("concat(tag_types.name,':',tags.name) as name"). - Find(&tags) + Select("concat(tag_types.name,':',tags.name) as name") + + tagFields := strings.Split(searchValue, ":") + if len(tagFields) == 2 { + query = query. + Where("tags.name LIKE ?", "%"+tagFields[1]+"%"). + Where("tag_types.name = ?", tagFields[0]) + } else if len(tagFields) == 1 { + query = query. + Where("tags.name LIKE ?", "%"+tagFields[0]+"%") + } + + result := query.Find(&tags) + if result.Error != nil { + return []string{} + } + return tags +} +func getNegativeTagAutocomplete(searchValue string) []string { + var tags []string + query := database.DB.Model(&database.Tag{}). + Joins("join tag_types on tag_types.id = tags.tag_type_id"). + Select("concat('-',tag_types.name,':',tags.name) as name") + + tagFields := strings.Split(searchValue, ":") + if len(tagFields) == 2 { + query = query. + Where("tags.name LIKE ?", "%"+tagFields[1]+"%"). + Where("tag_types.name = ?", tagFields[0]) + } else if len(tagFields) == 1 { + query = query. + Where("tags.name LIKE ?", "%"+tagFields[0]+"%") + } + + result := query.Find(&tags) if result.Error != nil { return []string{} } diff --git a/web/app/package.json b/web/app/package.json index d687ffa..1112e22 100644 --- a/web/app/package.json +++ b/web/app/package.json @@ -18,16 +18,13 @@ "svelte": "^3.0.0" }, "dependencies": { - "@svelte-parts/editor": "^0.0.26", - "@tiptap/core": "^2.0.0-beta.174", - "@tiptap/starter-kit": "^2.0.0-beta.183", + "@rollup/plugin-json": "^4.1.0", "axios": "^0.21.1", "bulma": "^0.9.2", "node-sass": "^6.0.0", "postcss": "^8.2.14", "query-string": "^7.0.0", "sirv-cli": "^1.0.0", - "svelte-infinite-scroll": "^1.5.2", "svelte-preprocess": "^4.7.3", "svelte-routing": "^1.6.0", "svelte-tags-input": "^2.7.1" diff --git a/web/app/rollup.config.js b/web/app/rollup.config.js index 569f68c..4bcbd91 100644 --- a/web/app/rollup.config.js +++ b/web/app/rollup.config.js @@ -1,3 +1,4 @@ +import json from '@rollup/plugin-json'; import svelte from 'rollup-plugin-svelte'; import commonjs from '@rollup/plugin-commonjs'; import resolve from '@rollup/plugin-node-resolve'; @@ -40,6 +41,7 @@ export default { entryFileNames: 'bundle.js' }, plugins: [ + json(), svelte({ compilerOptions: { // enable run-time checks when not in production diff --git a/web/app/src/EditPostPanel.svelte b/web/app/src/EditPostPanel.svelte index 392a618..be2416d 100644 --- a/web/app/src/EditPostPanel.svelte +++ b/web/app/src/EditPostPanel.svelte @@ -25,8 +25,8 @@ form.tags = value.detail.tags; }; - const onAutocomplete = async () => { - const list = await getTagAutocomplete(); + const onAutocomplete = async (tag) => { + const list = await getTagAutocomplete({ tag, positive: true }); return list; }; @@ -97,6 +97,7 @@ addKeys={[9, 32]} on:tags={onTagChange} autoComplete={onAutocomplete} + autoCompleteFilter={false} /> diff --git a/web/app/src/api.js b/web/app/src/api.js index 314d49c..31b0bf4 100644 --- a/web/app/src/api.js +++ b/web/app/src/api.js @@ -53,8 +53,11 @@ export async function getRelatedTags({ tag }) { const response = await axios.get(endpoint); return response.data; } -export async function getTagAutocomplete() { - const endpoint = url + "/api/tag-autocomplete"; +export async function getTagAutocomplete({ tag, positive }) { + let endpoint = url + "/api/tag-autocomplete?tag=" + tag; + if (positive) { + endpoint = endpoint + "&positive=true"; + } const response = await axios.get(endpoint); return response.data; } diff --git a/web/app/src/components/TagNotes/ViewTagNotesPanel.svelte b/web/app/src/components/TagNotes/ViewTagNotesPanel.svelte index 9a7ae08..1d274cc 100644 --- a/web/app/src/components/TagNotes/ViewTagNotesPanel.svelte +++ b/web/app/src/components/TagNotes/ViewTagNotesPanel.svelte @@ -21,9 +21,3 @@ - - diff --git a/web/app/src/routes/Home.svelte b/web/app/src/routes/Home.svelte index 430d0b8..ad0200e 100644 --- a/web/app/src/routes/Home.svelte +++ b/web/app/src/routes/Home.svelte @@ -9,10 +9,10 @@ searchTerms = value.detail.tags; }; - const onAutocomplete = async () => { - const list = await getTagAutocomplete(); - return list; - }; + const onAutocomplete = async (tag) => { + const list = await getTagAutocomplete({ tag }); + return list; + }; const onSearch = (i) => { if (searchTerms.length > 0) { @@ -41,6 +41,7 @@ addKeys={[9, 32]} on:tags={onTagChange} autoComplete={onAutocomplete} + autoCompleteFilter={false} /> diff --git a/web/app/src/routes/Post/Posts.svelte b/web/app/src/routes/Post/Posts.svelte index 6c60273..e28e3ac 100644 --- a/web/app/src/routes/Post/Posts.svelte +++ b/web/app/src/routes/Post/Posts.svelte @@ -42,7 +42,7 @@ pagination = paginate(page, totalPages); } - if (searchTerms.length == 1) { + if (searchTerms.filter(x => !x.startsWith("-")).length == 1) { tagInfo = await getTag({ tag: searchTerms[0] }); } }; @@ -52,8 +52,8 @@ searchTerms = value.detail.tags; }; - const onAutocomplete = async () => { - const list = await getTagAutocomplete(); + const onAutocomplete = async (tag) => { + const list = await getTagAutocomplete({ tag }); return list; }; @@ -100,6 +100,7 @@ addKeys={[9, 32]} on:tags={onTagChange} autoComplete={onAutocomplete} + autoCompleteFilter={false} /> @@ -167,7 +168,6 @@