feat: negative tag search

This commit is contained in:
Damillora 2022-04-16 19:23:19 +07:00
parent a0868e15a1
commit a1da324f56
12 changed files with 739 additions and 841 deletions

View File

@ -70,7 +70,10 @@ func tagGetRelated(c *gin.Context) {
} }
func tagAutocomplete(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) c.JSON(http.StatusOK, tags)
} }

View File

@ -2,6 +2,7 @@ package services
import ( import (
"fmt" "fmt"
"strings"
"github.com/Damillora/Shioriko/pkg/database" "github.com/Damillora/Shioriko/pkg/database"
"github.com/Damillora/Shioriko/pkg/models" "github.com/Damillora/Shioriko/pkg/models"
@ -17,36 +18,74 @@ func GetPostAll(page int) []database.Post {
} }
func GetPostTags(page int, tagSyntax []string) []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 { if err != nil {
return []database.Post{} return []database.Post{}
} }
var tagIds []string var positiveTagIds []string
for _, tag := range tags { for _, tag := range positiveTags {
tagIds = append(tagIds, tag.ID) 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. database.DB.
Model(&tags). Model(&positiveTags).
Joins("join post_tags on post_tags.tag_id = tags.id"). Joins("join post_tags on post_tags.tag_id = tags.id").
Select("post_tags.post_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"). Group("post_tags.post_id").
Having("count(*) = ?", len(tagIds)). Having("count(*) = ?", len(positiveTagIds)).
Distinct(). 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 var posts []database.Post
database.DB. query := database.DB.
Joins("Blob"). Joins("Blob").
Preload("Tags"). Preload("Tags").
Preload("Tags.TagType"). Preload("Tags.TagType")
Where("posts.id IN ?", postIds).
Order("created_at desc"). 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). Offset((page - 1) * perPage).
Limit(20). Limit(20).
Find(&posts) Find(&posts)
@ -118,28 +157,73 @@ func CountPostPages() int {
return int(count) return int(count)
} }
func CountPostPagesTag(tagSyntax []string) int { 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 { if err != nil {
return 0 return 0
} }
var tagIds []string var positiveTagIds []string
for _, tag := range tags { for _, tag := range positiveTags {
tagIds = append(tagIds, tag.ID) 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. database.DB.
Model(&tags). Model(&positiveTags).
Distinct().
Joins("join post_tags on post_tags.tag_id = tags.id"). Joins("join post_tags on post_tags.tag_id = tags.id").
Select("post_tags.post_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"). Group("post_tags.post_id").
Having("count(*) = ?", len(tagIds)). Having("count(*) = ?", len(positiveTagIds)).
Count(&count) 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) return int(count)
} }

View File

@ -61,12 +61,56 @@ func GetTag(tagString string) (*models.TagReadModel, error) {
return &tagModel, nil 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 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"). Joins("join tag_types on tag_types.id = tags.tag_type_id").
Select("concat(tag_types.name,':',tags.name) as name"). Select("concat(tag_types.name,':',tags.name) as name")
Find(&tags)
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 { if result.Error != nil {
return []string{} return []string{}
} }

View File

@ -18,16 +18,13 @@
"svelte": "^3.0.0" "svelte": "^3.0.0"
}, },
"dependencies": { "dependencies": {
"@svelte-parts/editor": "^0.0.26", "@rollup/plugin-json": "^4.1.0",
"@tiptap/core": "^2.0.0-beta.174",
"@tiptap/starter-kit": "^2.0.0-beta.183",
"axios": "^0.21.1", "axios": "^0.21.1",
"bulma": "^0.9.2", "bulma": "^0.9.2",
"node-sass": "^6.0.0", "node-sass": "^6.0.0",
"postcss": "^8.2.14", "postcss": "^8.2.14",
"query-string": "^7.0.0", "query-string": "^7.0.0",
"sirv-cli": "^1.0.0", "sirv-cli": "^1.0.0",
"svelte-infinite-scroll": "^1.5.2",
"svelte-preprocess": "^4.7.3", "svelte-preprocess": "^4.7.3",
"svelte-routing": "^1.6.0", "svelte-routing": "^1.6.0",
"svelte-tags-input": "^2.7.1" "svelte-tags-input": "^2.7.1"

View File

@ -1,3 +1,4 @@
import json from '@rollup/plugin-json';
import svelte from 'rollup-plugin-svelte'; import svelte from 'rollup-plugin-svelte';
import commonjs from '@rollup/plugin-commonjs'; import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve'; import resolve from '@rollup/plugin-node-resolve';
@ -40,6 +41,7 @@ export default {
entryFileNames: 'bundle.js' entryFileNames: 'bundle.js'
}, },
plugins: [ plugins: [
json(),
svelte({ svelte({
compilerOptions: { compilerOptions: {
// enable run-time checks when not in production // enable run-time checks when not in production

View File

@ -25,8 +25,8 @@
form.tags = value.detail.tags; form.tags = value.detail.tags;
}; };
const onAutocomplete = async () => { const onAutocomplete = async (tag) => {
const list = await getTagAutocomplete(); const list = await getTagAutocomplete({ tag, positive: true });
return list; return list;
}; };
@ -97,6 +97,7 @@
addKeys={[9, 32]} addKeys={[9, 32]}
on:tags={onTagChange} on:tags={onTagChange}
autoComplete={onAutocomplete} autoComplete={onAutocomplete}
autoCompleteFilter={false}
/> />
</div> </div>
</div> </div>

View File

@ -53,8 +53,11 @@ export async function getRelatedTags({ tag }) {
const response = await axios.get(endpoint); const response = await axios.get(endpoint);
return response.data; return response.data;
} }
export async function getTagAutocomplete() { export async function getTagAutocomplete({ tag, positive }) {
const endpoint = url + "/api/tag-autocomplete"; let endpoint = url + "/api/tag-autocomplete?tag=" + tag;
if (positive) {
endpoint = endpoint + "&positive=true";
}
const response = await axios.get(endpoint); const response = await axios.get(endpoint);
return response.data; return response.data;
} }

View File

@ -21,9 +21,3 @@
</div> </div>
</AuthCheck> </AuthCheck>
</div> </div>
<style>
.pre-line {
white-space: pre-line;
}
</style>

View File

@ -9,10 +9,10 @@
searchTerms = value.detail.tags; searchTerms = value.detail.tags;
}; };
const onAutocomplete = async () => { const onAutocomplete = async (tag) => {
const list = await getTagAutocomplete(); const list = await getTagAutocomplete({ tag });
return list; return list;
}; };
const onSearch = (i) => { const onSearch = (i) => {
if (searchTerms.length > 0) { if (searchTerms.length > 0) {
@ -41,6 +41,7 @@
addKeys={[9, 32]} addKeys={[9, 32]}
on:tags={onTagChange} on:tags={onTagChange}
autoComplete={onAutocomplete} autoComplete={onAutocomplete}
autoCompleteFilter={false}
/> />
</div> </div>
</div> </div>

View File

@ -42,7 +42,7 @@
pagination = paginate(page, totalPages); pagination = paginate(page, totalPages);
} }
if (searchTerms.length == 1) { if (searchTerms.filter(x => !x.startsWith("-")).length == 1) {
tagInfo = await getTag({ tag: searchTerms[0] }); tagInfo = await getTag({ tag: searchTerms[0] });
} }
}; };
@ -52,8 +52,8 @@
searchTerms = value.detail.tags; searchTerms = value.detail.tags;
}; };
const onAutocomplete = async () => { const onAutocomplete = async (tag) => {
const list = await getTagAutocomplete(); const list = await getTagAutocomplete({ tag });
return list; return list;
}; };
@ -100,6 +100,7 @@
addKeys={[9, 32]} addKeys={[9, 32]}
on:tags={onTagChange} on:tags={onTagChange}
autoComplete={onAutocomplete} autoComplete={onAutocomplete}
autoCompleteFilter={false}
/> />
</div> </div>
</div> </div>
@ -167,7 +168,6 @@
<div class="column is-full"> <div class="column is-full">
<nav <nav
class="pagination is-centered" class="pagination is-centered"
role="navigation"
aria-label="pagination" aria-label="pagination"
> >
<a <a

View File

@ -38,8 +38,8 @@
form.tags = value.detail.tags; form.tags = value.detail.tags;
}; };
const onAutocomplete = async () => { const onAutocomplete = async (tag) => {
const list = await getTagAutocomplete(); const list = await getTagAutocomplete({ tag, positive: true });
return list; return list;
}; };
@ -107,7 +107,7 @@
<div class="field"> <div class="field">
<label for="tags" class="label">Tags</label> <label for="tags" class="label">Tags</label>
<div class="control" id="tags"> <div class="control" id="tags">
<Tags addKeys={[9, 32]} on:tags={onTagChange} autoComplete={onAutocomplete} /> <Tags addKeys={[9, 32]} on:tags={onTagChange} autoComplete={onAutocomplete} autoCompleteFilter={false}/>
</div> </div>
</div> </div>
<div class="control"> <div class="control">

File diff suppressed because it is too large Load Diff