From a74880338ae6abaaef49c31b46e4ad92567f7e38 Mon Sep 17 00:00:00 2001 From: Yader Velasquez Date: Mon, 12 Feb 2024 13:02:07 -0600 Subject: [PATCH] Change tag queryset and to support many tags (inclusive and exclusive) Add support for excluded tags to graphql References: https://todo.code.netlandish.com/~netlandish/links/47 --- api/graph/generated.go | 10 ++++++- api/graph/model/models_gen.go | 15 +++++----- api/graph/schema.graphqls | 1 + api/graph/schema.resolvers.go | 12 +++----- helpers.go | 53 +++++++++++++++++++++++++++++++++++ 5 files changed, 75 insertions(+), 16 deletions(-) diff --git a/api/graph/generated.go b/api/graph/generated.go index 8f7c675..e74659c 100644 --- a/api/graph/generated.go +++ b/api/graph/generated.go @@ -19679,7 +19679,7 @@ func (ec *executionContext) unmarshalInputGetLinkInput(ctx context.Context, obj asMap[k] = v } - fieldsInOrder := [...]string{"orgSlug", "limit", "after", "before", "tag", "search", "filter"} + fieldsInOrder := [...]string{"orgSlug", "limit", "after", "before", "tag", "excludeTag", "search", "filter"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { @@ -19726,6 +19726,14 @@ func (ec *executionContext) unmarshalInputGetLinkInput(ctx context.Context, obj if err != nil { return it, err } + case "excludeTag": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("excludeTag")) + it.ExcludeTag, err = ec.unmarshalOString2áš–string(ctx, v) + if err != nil { + return it, err + } case "search": var err error diff --git a/api/graph/model/models_gen.go b/api/graph/model/models_gen.go index 4de5d4e..de1e377 100644 --- a/api/graph/model/models_gen.go +++ b/api/graph/model/models_gen.go @@ -147,13 +147,14 @@ type GetAdminOrganizationsInput struct { } type GetLinkInput struct { - OrgSlug *string `json:"orgSlug,omitempty"` - Limit *int `json:"limit,omitempty"` - After *Cursor `json:"after,omitempty"` - Before *Cursor `json:"before,omitempty"` - Tag *string `json:"tag,omitempty"` - Search *string `json:"search,omitempty"` - Filter *string `json:"filter,omitempty"` + OrgSlug *string `json:"orgSlug,omitempty"` + Limit *int `json:"limit,omitempty"` + After *Cursor `json:"after,omitempty"` + Before *Cursor `json:"before,omitempty"` + Tag *string `json:"tag,omitempty"` + ExcludeTag *string `json:"excludeTag,omitempty"` + Search *string `json:"search,omitempty"` + Filter *string `json:"filter,omitempty"` } type GetLinkShortInput struct { diff --git a/api/graph/schema.graphqls b/api/graph/schema.graphqls index 4d7d43a..479ac40 100644 --- a/api/graph/schema.graphqls +++ b/api/graph/schema.graphqls @@ -443,6 +443,7 @@ input GetLinkInput { after: Cursor, before: Cursor, tag: String, + excludeTag: String, search: String filter: String } diff --git a/api/graph/schema.resolvers.go b/api/graph/schema.resolvers.go index d67f767..b8edb08 100644 --- a/api/graph/schema.resolvers.go +++ b/api/graph/schema.resolvers.go @@ -4744,19 +4744,15 @@ func (r *queryResolver) GetOrgLinks(ctx context.Context, input *model.GetLinkInp } if input.Tag != nil && *input.Tag != "" { - subQ := sq.Select("tl.org_link_id"). - From("tag_links tl"). - LeftJoin("tags t ON t.id = tl.tag_id"). - Where("t.slug = ?", *input.Tag) - - subQText, _, err := subQ.ToSql() + // Filter based on tags + tagList := strings.Split(*input.Tag, ",") + subQText, subQVal, err := links.GetTagSubQuery(tagList, []string{}) if err != nil { return nil, err } - linkOpts.Filter = sq.And{ linkOpts.Filter, - sq.Expr("ol.id IN ("+subQText+")", *input.Tag), + sq.Expr("ol.id IN ("+subQText+")", subQVal...), } } diff --git a/helpers.go b/helpers.go index 3a03b86..6f80b08 100644 --- a/helpers.go +++ b/helpers.go @@ -708,3 +708,56 @@ func CreateNoteURL(domain string) (string, string) { return noteURL.String(), noteHash } + +func GetTagSubQuery(tags, excludeTags []string) (string, []interface{}, error) { + subQOpts := &database.FilterOptions{ + Filter: sq.And{}, + } + + if len(tags) > 0 { + subQOpts.Filter = sq.And{ + subQOpts.Filter, + sq.Eq{"t.slug": tags}, + } + } + + if len(excludeTags) > 0 { + // NOTE: Since we are using a subquery + // that get the org_link_id (tag_links) based on + // a left join we want to exclude any org_link_id + // that have a relation to an excluded target tag + // no matter if that org_link_id has any relation + // to one of the previous tags (included) + excludeSubQ := sq.Select("tl.org_link_id"). + From("tag_links tl"). + LeftJoin("tags t ON t.id = tl.tag_id"). + Where(sq.Eq{"t.slug": "alpine"}) + excludeSubQText, excludeSubQVal, err := excludeSubQ.ToSql() + if err != nil { + return "", nil, err + } + + subQOpts.Filter = sq.And{ + subQOpts.Filter, + sq.Expr("tl.org_link_id NOT IN ("+excludeSubQText+")", excludeSubQVal...), + } + } + + subQ := sq.Select("tl.org_link_id"). + From("tag_links tl"). + LeftJoin("tags t ON t.id = tl.tag_id"). + Where(subQOpts.Filter) + + if len(tags) > 0 { + // NOTE: Since we are using a subquery + // that get the org_link_id (tag_links) based on + // a left join to tags we want to make sure to + // include ONLY the org_link_id that have ALL + // target tags + subQ = subQ. + GroupBy("tl.org_link_id"). + Having(sq.Eq{"COUNT(DISTINCT tag_id)": len(tags)}) + } + + return subQ.ToSql() +} -- 2.45.2