Subject: [PATCH links] api: getTags was missing the tag count. It's been added.

From: Peter Sanchez <[email-masked]>
Date: Sat, 7 Mar 2026 10:10:26 -0600

Changelog-fixed: getTags was not including tag count. Now it is.
Changelog-updated: api version bumped to 0.11.2
---
 api/graph/schema.resolvers.go | 32 ++++++++++++-----
 models/tag.go                 | 66 +++++++++++++++++++++++++++++++++++
 2 files changed, 89 insertions(+), 9 deletions(-)

diff --git a/api/graph/schema.resolvers.go b/api/graph/schema.resolvers.go
index 55e5c5a..f4990c3 100644
--- a/api/graph/schema.resolvers.go
+++ b/api/graph/schema.resolvers.go
@@ -5127,7 +5127,7 @@ func (r *queryResolver) Version(ctx context.Context) (*model.Version, error) {
 	return &model.Version{
 		Major:           0,
 		Minor:           11,
-		Patch:           1,
+		Patch:           2,
 		DeprecationDate: nil,
 	}, nil
 }
@@ -5854,22 +5854,36 @@ func (r *queryResolver) GetTags(ctx context.Context, input model.GetTagsInput) (
 			fq = sq.Eq{"ll.org_id": org.ID}
 		}
 	} else {
-		// No service specified. Give tags for all services
-		fq = sq.Or{
-			sq.Eq{"ol.org_id": org.ID},
-			sq.Eq{"ll.org_id": org.ID},
-			sq.Eq{"s.org_id": org.ID},
-		}
+		fq = sq.Expr(`(
+			EXISTS (SELECT 1 FROM tag_links _tl JOIN org_links _ol ON _ol.id = _tl.org_link_id WHERE _tl.tag_id = t.id AND _ol.org_id = ?)
+			OR EXISTS (SELECT 1 FROM tag_link_shorts _ts JOIN link_shorts _s ON _s.id = _ts.link_short_id WHERE _ts.tag_id = t.id AND _s.org_id = ?)
+			OR EXISTS (SELECT 1 FROM tag_listings _tll JOIN listings _ll ON _ll.id = _tll.listing_id WHERE _tll.tag_id = t.id AND _ll.org_id = ?)
+		)`, org.ID, org.ID, org.ID)
 	}
 
 	opts := &database.FilterOptions{
 		Filter:  fq,
 		Limit:   250,
-		OrderBy: "name ASC",
+		OrderBy: "t.id ASC",
 	}
 
 	// Set pagination limit to 250 for tag pagination
 	ctx = PaginationContext(ctx, 250)
+
+	if input.Limit == nil {
+		defaultLimit := 250
+		input.Limit = &defaultLimit
+	}
+
+	getTagsFn := func(ctx context.Context, opts *database.FilterOptions) ([]*models.Tag, error) {
+		var svc *string
+		if input.Service != nil {
+			s := string(*input.Service)
+			svc = &s
+		}
+		return models.GetTagsWithCount(ctx, opts, svc, org.ID)
+	}
+
 	tags, pageInfo, err := QueryModel(
 		ctx,
 		opts,
@@ -5878,7 +5892,7 @@ func (r *queryResolver) GetTags(ctx context.Context, input model.GetTagsInput) (
 		input.Limit,
 		input.Before,
 		input.After,
-		models.GetTags,
+		getTagsFn,
 		func(tag *models.Tag) int {
 			return tag.ID
 		},
diff --git a/models/tag.go b/models/tag.go
index c9cbcfa..4eb502b 100644
--- a/models/tag.go
+++ b/models/tag.go
@@ -67,6 +67,72 @@ func TagRelationOrderString(v TagCloudOrdering) string {
 	return "LOWER(t.name) ASC"
 }
 
+// GetTagsWithCount returns tags with usage counts.
+func GetTagsWithCount(ctx context.Context, opts *database.FilterOptions, service *string, orgID int) ([]*Tag, error) {
+	if opts == nil {
+		opts = &database.FilterOptions{}
+	}
+	tz := timezone.ForContext(ctx)
+	tags := make([]*Tag, 0)
+	if err := database.WithTx(ctx, database.TxOptionsRO, func(tx *sql.Tx) error {
+		q := opts.GetBuilder(nil)
+		q = q.Columns("t.id", "t.name", "t.slug", "t.created_on").
+			From("tags t")
+
+		if service != nil {
+			switch *service {
+			case DomainServiceLinks:
+				q = q.Join("tag_links tl ON tl.tag_id = t.id").
+					Join("org_links ol ON ol.id = tl.org_link_id").
+					Column("count(*) as tag_count")
+			case DomainServiceShort:
+				q = q.Join("tag_link_shorts ts ON ts.tag_id = t.id").
+					Join("link_shorts s ON ts.link_short_id = s.id").
+					Column("count(*) as tag_count")
+			case DomainServiceList:
+				q = q.Join("tag_listings tll ON tll.tag_id = t.id").
+					Join("listings ll ON tll.listing_id = ll.id").
+					Column("count(*) as tag_count")
+			}
+			q = q.GroupBy("t.id", "t.name", "t.slug", "t.created_on")
+		} else {
+			q = q.Column(`(
+				(SELECT COUNT(*) FROM tag_links _tl JOIN org_links _ol ON _ol.id = _tl.org_link_id WHERE _tl.tag_id = t.id AND _ol.org_id = ?) +
+				(SELECT COUNT(*) FROM tag_link_shorts _ts JOIN link_shorts _s ON _s.id = _ts.link_short_id WHERE _ts.tag_id = t.id AND _s.org_id = ?) +
+				(SELECT COUNT(*) FROM tag_listings _tll JOIN listings _ll ON _ll.id = _tll.listing_id WHERE _tll.tag_id = t.id AND _ll.org_id = ?)
+			) as tag_count`, orgID, orgID, orgID)
+		}
+
+		rows, err := q.
+			PlaceholderFormat(database.GetPlaceholderFormat()).
+			RunWith(tx).
+			QueryContext(ctx)
+		if err != nil {
+			if err == sql.ErrNoRows {
+				return nil
+			}
+			return err
+		}
+		defer rows.Close()
+
+		for rows.Next() {
+			var t Tag
+			if err = rows.Scan(&t.ID, &t.Name, &t.Slug, &t.CreatedOn, &t.Count); err != nil {
+				return err
+			}
+			err = t.ToLocalTZ(tz)
+			if err != nil {
+				return err
+			}
+			tags = append(tags, &t)
+		}
+		return nil
+	}); err != nil {
+		return nil, err
+	}
+	return tags, nil
+}
+
 // GetTags ...
 func GetTags(ctx context.Context, opts *database.FilterOptions) ([]*Tag, error) {
 	if opts == nil {
-- 
2.52.0