~netlandish/links

50e3aa2ad780f9b8d425fdeb878afb93aa6dd9bf — Yader Velasquez 9 months ago 9d9316a
Add exclude support for list/shorts API
Improve helper function to customize query

References: https://todo.code.netlandish.com/~netlandish/links/47
M api/graph/generated.go => api/graph/generated.go +18 -2
@@ 19763,7 19763,7 @@ func (ec *executionContext) unmarshalInputGetLinkShortInput(ctx context.Context,
		asMap[k] = v
	}

	fieldsInOrder := [...]string{"orgSlug", "limit", "after", "before", "tag"}
	fieldsInOrder := [...]string{"orgSlug", "limit", "after", "before", "tag", "excludeTag"}
	for _, k := range fieldsInOrder {
		v, ok := asMap[k]
		if !ok {


@@ 19810,6 19810,14 @@ func (ec *executionContext) unmarshalInputGetLinkShortInput(ctx context.Context,
			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
			}
		}
	}



@@ 19883,7 19891,7 @@ func (ec *executionContext) unmarshalInputGetListingInput(ctx context.Context, o
		asMap[k] = v
	}

	fieldsInOrder := [...]string{"orgSlug", "limit", "after", "before", "tag"}
	fieldsInOrder := [...]string{"orgSlug", "limit", "after", "before", "tag", "excludeTag"}
	for _, k := range fieldsInOrder {
		v, ok := asMap[k]
		if !ok {


@@ 19930,6 19938,14 @@ func (ec *executionContext) unmarshalInputGetListingInput(ctx context.Context, o
			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
			}
		}
	}


M api/graph/model/models_gen.go => api/graph/model/models_gen.go +12 -10
@@ 158,11 158,12 @@ type GetLinkInput struct {
}

type GetLinkShortInput struct {
	OrgSlug string  `json:"orgSlug"`
	Limit   *int    `json:"limit,omitempty"`
	After   *Cursor `json:"after,omitempty"`
	Before  *Cursor `json:"before,omitempty"`
	Tag     *string `json:"tag,omitempty"`
	OrgSlug    string  `json:"orgSlug"`
	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"`
}

type GetListingDetailInput struct {


@@ 174,11 175,12 @@ type GetListingDetailInput struct {
}

type GetListingInput struct {
	OrgSlug string  `json:"orgSlug"`
	Limit   *int    `json:"limit,omitempty"`
	After   *Cursor `json:"after,omitempty"`
	Before  *Cursor `json:"before,omitempty"`
	Tag     *string `json:"tag,omitempty"`
	OrgSlug    string  `json:"orgSlug"`
	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"`
}

type GetPaymentInput struct {

M api/graph/schema.graphqls => api/graph/schema.graphqls +2 -0
@@ 454,6 454,7 @@ input GetLinkShortInput {
    after: Cursor
    before: Cursor
    tag: String
    excludeTag: String
}

input GetListingInput {


@@ 462,6 463,7 @@ input GetListingInput {
    after: Cursor
    before: Cursor
    tag: String
    excludeTag: String
}

input GetListingDetailInput {

M api/graph/schema.resolvers.go => api/graph/schema.resolvers.go +26 -17
@@ 4754,7 4754,8 @@ func (r *queryResolver) GetOrgLinks(ctx context.Context, input *model.GetLinkInp
			excludeTagList = strings.Split(*input.ExcludeTag, ",")
		}
		// Filter based on tags
		subQText, subQVal, err := links.GetTagSubQuery(tagList, excludeTagList)
		f := links.NewTagQuery("tag_links", "org_link_id")
		subQText, subQVal, err := f.GetSubQuery(tagList, excludeTagList)
		if err != nil {
			return nil, err
		}


@@ 5066,20 5067,24 @@ func (r *queryResolver) GetLinkShorts(ctx context.Context, input *model.GetLinkS
		return nil, nil
	}

	if input.Tag != nil && *input.Tag != "" {
		subQ := sq.Select("tl.link_short_id").
			From("tag_link_shorts tl").
			LeftJoin("tags t ON t.id = tl.tag_id").
			Where("t.slug = ?", *input.Tag)
	if (input.Tag != nil && *input.Tag != "") || (input.ExcludeTag != nil && *input.ExcludeTag != "") {
		var tagList = make([]string, 0)
		if input.Tag != nil && *input.Tag != "" {
			tagList = strings.Split(*input.Tag, ",")
		}

		subQText, _, err := subQ.ToSql()
		var excludeTagList = make([]string, 0)
		if input.ExcludeTag != nil && *input.ExcludeTag != "" {
			excludeTagList = strings.Split(*input.ExcludeTag, ",")
		}
		f := links.NewTagQuery("tag_link_shorts", "link_short_id")
		subQText, subQVal, err := f.GetSubQuery(tagList, excludeTagList)
		if err != nil {
			return nil, err
		}

		linkOpts.Filter = sq.And{
			linkOpts.Filter,
			sq.Expr("l.id IN ("+subQText+")", *input.Tag),
			sq.Expr("l.id IN ("+subQText+")", subQVal...),
		}
	}



@@ 5258,20 5263,24 @@ func (r *queryResolver) GetListings(ctx context.Context, input *model.GetListing
		return nil, nil
	}

	if input.Tag != nil && *input.Tag != "" {
		subQ := sq.Select("tl.listing_id").
			From("tag_listings tl").
			LeftJoin("tags t ON t.id = tl.tag_id").
			Where("t.slug = ?", *input.Tag)
	if (input.Tag != nil && *input.Tag != "") || (input.ExcludeTag != nil && *input.ExcludeTag != "") {
		var tagList = make([]string, 0)
		if input.Tag != nil && *input.Tag != "" {
			tagList = strings.Split(*input.Tag, ",")
		}

		subQText, _, err := subQ.ToSql()
		var excludeTagList = make([]string, 0)
		if input.ExcludeTag != nil && *input.ExcludeTag != "" {
			excludeTagList = strings.Split(*input.ExcludeTag, ",")
		}
		f := links.NewTagQuery("tag_listings", "listing_id")
		subQText, subQVal, err := f.GetSubQuery(tagList, excludeTagList)
		if err != nil {
			return nil, err
		}

		listingOpts.Filter = sq.And{
			listingOpts.Filter,
			sq.Expr("l.id IN ("+subQText+")", *input.Tag),
			sq.Expr("l.id IN ("+subQText+")", subQVal...),
		}
	}


M helpers.go => helpers.go +19 -7
@@ 709,7 709,19 @@ func CreateNoteURL(domain string) (string, string) {
	return noteURL.String(), noteHash
}

func GetTagSubQuery(tags, excludeTags []string) (string, []interface{}, error) {
type TagQuery struct {
	table  string
	column string
}

func NewTagQuery(t, c string) *TagQuery {
	return &TagQuery{
		table:  t,
		column: c,
	}
}

func (t TagQuery) GetSubQuery(tags, excludeTags []string) (string, []interface{}, error) {
	subQOpts := &database.FilterOptions{
		Filter: sq.And{},
	}


@@ 728,8 740,8 @@ func GetTagSubQuery(tags, excludeTags []string) (string, []interface{}, error) {
		// 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").
		excludeSubQ := sq.Select(fmt.Sprintf("tl.%s", t.column)).
			From(fmt.Sprintf("%s tl", t.table)).
			LeftJoin("tags t ON t.id = tl.tag_id").
			Where(sq.Eq{"t.slug": excludeTags})
		excludeSubQText, excludeSubQVal, err := excludeSubQ.ToSql()


@@ 739,12 751,12 @@ func GetTagSubQuery(tags, excludeTags []string) (string, []interface{}, error) {

		subQOpts.Filter = sq.And{
			subQOpts.Filter,
			sq.Expr("tl.org_link_id NOT IN ("+excludeSubQText+")", excludeSubQVal...),
			sq.Expr(fmt.Sprintf("tl.%s NOT IN (%s)", t.column, excludeSubQText), excludeSubQVal...),
		}
	}

	subQ := sq.Select("tl.org_link_id").
		From("tag_links tl").
	subQ := sq.Select(fmt.Sprintf("tl.%s", t.column)).
		From(fmt.Sprintf("%s tl", t.table)).
		LeftJoin("tags t ON t.id = tl.tag_id").
		Where(subQOpts.Filter)



@@ 755,7 767,7 @@ func GetTagSubQuery(tags, excludeTags []string) (string, []interface{}, error) {
		// include ONLY the org_link_id that have ALL
		// target tags
		subQ = subQ.
			GroupBy("tl.org_link_id").
			GroupBy(fmt.Sprintf("tl.%s", t.column)).
			Having(sq.Eq{"COUNT(DISTINCT tag_id)": len(tags)})
	}