Fixes: https://todo.code.netlandish.com/~netlandish/links/117
Changelog-fixed: Issue blocking the removal of ALL tags from an object.
---
api/api_test.go | 81 +++++++++++++-
api/graph/schema.resolvers.go | 204 +++++++++++++++++++++-------------
2 files changed, 204 insertions(+), 81 deletions(-)
diff --git a/api/api_test.go b/api/api_test.go
index d098661..b0125bd 100644
--- a/api/api_test.go
+++ b/api/api_test.go
@@ -3346,7 +3346,7 @@ func TestAPI(t *testing.T) {
opA.Var("title", "Link A isolation")
opA.Var("url", "https://example.com/link-a-isolation")
opA.Var("description", "")
- opA.Var("visibility", models.OrgLinkVisibilityPublic)
+ opA.Var("visibility", models.OrgLinkVisibilityPrivate)
opA.Var("tags", "isolation-common, isolation-unique-a")
opA.Var("slug", "personal-org")
opA.Var("unread", false)
@@ -3366,7 +3366,7 @@ func TestAPI(t *testing.T) {
opB.Var("title", "Link B isolation")
opB.Var("url", "https://example.com/link-b-isolation")
opB.Var("description", "")
- opB.Var("visibility", models.OrgLinkVisibilityPublic)
+ opB.Var("visibility", models.OrgLinkVisibilityPrivate)
opB.Var("tags", "isolation-common, isolation-unique-b")
opB.Var("slug", "personal-org")
opB.Var("unread", false)
@@ -3398,7 +3398,7 @@ func TestAPI(t *testing.T) {
opUpdate.Var("hash", linkA.Link.Hash)
opUpdate.Var("title", "Link A isolation")
opUpdate.Var("url", "https://example.com/link-a-isolation")
- opUpdate.Var("visibility", models.OrgLinkVisibilityPublic)
+ opUpdate.Var("visibility", models.OrgLinkVisibilityPrivate)
opUpdate.Var("tags", "isolation-unique-a")
err = links.Execute(ctx, opUpdate, &updatedA)
c.NoError(err)
@@ -3426,4 +3426,79 @@ func TestAPI(t *testing.T) {
c.True(tagSlugs["isolation-common"], "Link B should still have 'isolation-common' tag")
c.True(tagSlugs["isolation-unique-b"], "Link B should still have 'isolation-unique-b' tag")
})
+
+ t.Run("remove all tags from link", func(t *testing.T) {
+ c := require.New(t)
+ type AddLinkResponse struct {
+ Link models.OrgLink `json:"addLink"`
+ }
+ type UpdateLinkResponse struct {
+ Link models.OrgLink `json:"updateLink"`
+ }
+
+ addLinkQuery := `mutation AddLink($title: String!, $url: String!, $description: String,
+ $visibility: LinkVisibility!, $unread: Boolean!, $starred: Boolean!,
+ $archive: Boolean! $slug: String!, $tags: String) {
+ addLink(input: {
+ title: $title,
+ url: $url,
+ description: $description,
+ visibility: $visibility,
+ unread: $unread,
+ starred: $starred,
+ archive: $archive,
+ orgSlug: $slug,
+ tags: $tags}) {
+ id
+ hash
+ }
+ }`
+
+ var linkA AddLinkResponse
+ opA := gqlclient.NewOperation(addLinkQuery)
+ opA.Var("title", "Link with tags to remove")
+ opA.Var("url", "https://test-tags.example.org/remove-all-tags")
+ opA.Var("description", "")
+ opA.Var("visibility", models.OrgLinkVisibilityPrivate)
+ opA.Var("tags", "tag-to-remove-1, tag-to-remove-2")
+ opA.Var("slug", "personal-org")
+ opA.Var("unread", false)
+ opA.Var("starred", false)
+ opA.Var("archive", false)
+ err := links.Execute(ctx, opA, &linkA)
+ c.NoError(err)
+ c.NotZero(linkA.Link.ID)
+
+ tagLinksA, err := models.GetTagLinks(dbCtx,
+ &database.FilterOptions{Filter: sq.Expr("org_link_id = ?", linkA.Link.ID)})
+ c.NoError(err)
+ c.Equal(2, len(tagLinksA), "Link should have 2 tags after creation")
+
+ updateLinkQuery := `mutation UpdateLink($hash: String!, $title: String!,
+ $url: String!, $visibility: LinkVisibility!,
+ $tags: String) {
+ updateLink(input: {
+ title: $title,
+ hash: $hash,
+ url: $url,
+ visibility: $visibility,
+ tags: $tags,
+ }) {id, hash}
+ }`
+
+ var updatedA UpdateLinkResponse
+ opUpdate := gqlclient.NewOperation(updateLinkQuery)
+ opUpdate.Var("hash", linkA.Link.Hash)
+ opUpdate.Var("title", "Link with tags to remove")
+ opUpdate.Var("url", "https://test-tags.example.org/remove-all-tags")
+ opUpdate.Var("visibility", models.OrgLinkVisibilityPrivate)
+ opUpdate.Var("tags", "")
+ err = links.Execute(ctx, opUpdate, &updatedA)
+ c.NoError(err)
+
+ tagLinksAAfter, err := models.GetTagLinks(dbCtx,
+ &database.FilterOptions{Filter: sq.Expr("org_link_id = ?", linkA.Link.ID)})
+ c.NoError(err)
+ c.Equal(0, len(tagLinksAAfter), "Link should have 0 tags after update with empty tags")
+ })
}
diff --git a/api/graph/schema.resolvers.go b/api/graph/schema.resolvers.go
index 74e3a84..b04fe6e 100644
--- a/api/graph/schema.resolvers.go
+++ b/api/graph/schema.resolvers.go
@@ -655,7 +655,8 @@ func (r *mutationResolver) UpdateLink(ctx context.Context, input *model.UpdateLi
WithCode(valid.ErrValidationCode)
}
tags := make([]string, 0)
- if input.Tags != nil && *input.Tags != "" {
+ tagsProvided := input.Tags != nil
+ if tagsProvided && *input.Tags != "" {
tags = strings.Split(strings.TrimSpace(*input.Tags), ",")
validator.Expect(len(tags) <= 10, "%s", lt.Translate("Tags may not exceed 10")).
WithField("tags").
@@ -761,35 +762,50 @@ func (r *mutationResolver) UpdateLink(ctx context.Context, input *model.UpdateLi
return nil, err
}
- if len(tags) > 0 {
- // Calculate the difference between original tags and new ones
- tagIDs, err := links.ProcessTags(ctx, tags)
- if err != nil {
- return nil, err
- }
- originalTags := orgLink.Tags
- tagIDsToRemove := make([]int, 0)
- m := make(map[int]bool)
- for _, id := range tagIDs {
- m[id] = true
- }
-
- for _, originalTag := range originalTags {
- if !m[originalTag.ID] {
- tagIDsToRemove = append(tagIDsToRemove, originalTag.ID)
- }
- }
-
- if len(tagIDsToRemove) > 0 {
- err = models.RemoveTagsFromLink(ctx, orgLink.ID, tagIDsToRemove)
+ if tagsProvided {
+ if len(tags) > 0 {
+ // Calculate the difference between original tags and new ones
+ tagIDs, err := links.ProcessTags(ctx, tags)
if err != nil {
return nil, err
}
- }
+ originalTags := orgLink.Tags
+ tagIDsToRemove := make([]int, 0)
+ m := make(map[int]bool)
+ for _, id := range tagIDs {
+ m[id] = true
+ }
- err = models.CreateBatchTagLinks(ctx, orgLink.ID, tagIDs)
- if err != nil {
- return nil, err
+ for _, originalTag := range originalTags {
+ if !m[originalTag.ID] {
+ tagIDsToRemove = append(tagIDsToRemove, originalTag.ID)
+ }
+ }
+
+ if len(tagIDsToRemove) > 0 {
+ err = models.RemoveTagsFromLink(ctx, orgLink.ID, tagIDsToRemove)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ err = models.CreateBatchTagLinks(ctx, orgLink.ID, tagIDs)
+ if err != nil {
+ return nil, err
+ }
+ } else {
+ // Tags field provided but empty - remove all existing tags
+ originalTags := orgLink.Tags
+ if len(originalTags) > 0 {
+ tagIDsToRemove := make([]int, len(originalTags))
+ for i, t := range originalTags {
+ tagIDsToRemove[i] = t.ID
+ }
+ err = models.RemoveTagsFromLink(ctx, orgLink.ID, tagIDsToRemove)
+ if err != nil {
+ return nil, err
+ }
+ }
}
}
@@ -2457,7 +2473,8 @@ func (r *mutationResolver) UpdateLinkShort(ctx context.Context, input *model.Upd
}
tags := make([]string, 0)
- if input.Tags != nil && *input.Tags != "" {
+ tagsProvided := input.Tags != nil
+ if tagsProvided && *input.Tags != "" {
tags = strings.Split(strings.TrimSpace(*input.Tags), ",")
validator.Expect(len(tags) <= 10, "%s", lt.Translate("Tags may not exceed 10")).
WithField("tags").
@@ -2558,35 +2575,50 @@ func (r *mutationResolver) UpdateLinkShort(ctx context.Context, input *model.Upd
return nil, err
}
- if len(tags) > 0 {
- tagIDs, err := links.ProcessTags(ctx, tags)
- if err != nil {
- return nil, err
- }
- // Calculate the difference between original tags and new ones
- originalTags := linkShort.Tags
- tagIDsToRemove := make([]int, 0)
- m := make(map[int]bool)
- for _, id := range tagIDs {
- m[id] = true
- }
-
- for _, originalTag := range originalTags {
- if !m[originalTag.ID] {
- tagIDsToRemove = append(tagIDsToRemove, originalTag.ID)
- }
- }
-
- if len(tagIDsToRemove) > 0 {
- err = models.RemoveTagsFromLinkShort(ctx, linkShort.ID, tagIDsToRemove)
+ if tagsProvided {
+ if len(tags) > 0 {
+ tagIDs, err := links.ProcessTags(ctx, tags)
if err != nil {
return nil, err
}
- }
+ // Calculate the difference between original tags and new ones
+ originalTags := linkShort.Tags
+ tagIDsToRemove := make([]int, 0)
+ m := make(map[int]bool)
+ for _, id := range tagIDs {
+ m[id] = true
+ }
- err = models.CreateBatchTagLinkShorts(ctx, linkShort.ID, tagIDs)
- if err != nil {
- return nil, err
+ for _, originalTag := range originalTags {
+ if !m[originalTag.ID] {
+ tagIDsToRemove = append(tagIDsToRemove, originalTag.ID)
+ }
+ }
+
+ if len(tagIDsToRemove) > 0 {
+ err = models.RemoveTagsFromLinkShort(ctx, linkShort.ID, tagIDsToRemove)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ err = models.CreateBatchTagLinkShorts(ctx, linkShort.ID, tagIDs)
+ if err != nil {
+ return nil, err
+ }
+ } else {
+ // Tags field provided but empty - remove all existing tags
+ originalTags := linkShort.Tags
+ if len(originalTags) > 0 {
+ tagIDsToRemove := make([]int, len(originalTags))
+ for i, t := range originalTags {
+ tagIDsToRemove[i] = t.ID
+ }
+ err = models.RemoveTagsFromLinkShort(ctx, linkShort.ID, tagIDsToRemove)
+ if err != nil {
+ return nil, err
+ }
+ }
}
}
@@ -3177,7 +3209,8 @@ func (r *mutationResolver) UpdateListing(ctx context.Context, input *model.Updat
}
tags := make([]string, 0)
- if input.Tags != nil && *input.Tags != "" {
+ tagsProvided := input.Tags != nil
+ if tagsProvided && *input.Tags != "" {
tags = strings.Split(strings.TrimSpace(*input.Tags), ",")
validator.Expect(len(tags) <= 10, "%s", lt.Translate("Tags may not exceed 10")).
WithField("tags").
@@ -3393,35 +3426,50 @@ func (r *mutationResolver) UpdateListing(ctx context.Context, input *model.Updat
return nil, err
}
- if len(tags) > 0 {
- tagIDs, err := links.ProcessTags(ctx, tags)
- if err != nil {
- return nil, err
- }
- // Calculate the difference between original tags and new ones
- originalTags := listing.Tags
- tagIDsToRemove := make([]int, 0)
- m := make(map[int]bool)
- for _, id := range tagIDs {
- m[id] = true
- }
-
- for _, originalTag := range originalTags {
- if !m[originalTag.ID] {
- tagIDsToRemove = append(tagIDsToRemove, originalTag.ID)
- }
- }
-
- if len(tagIDsToRemove) > 0 {
- err = models.RemoveTagsFromListing(ctx, listing.ID, tagIDsToRemove)
+ if tagsProvided {
+ if len(tags) > 0 {
+ tagIDs, err := links.ProcessTags(ctx, tags)
if err != nil {
return nil, err
}
- }
+ // Calculate the difference between original tags and new ones
+ originalTags := listing.Tags
+ tagIDsToRemove := make([]int, 0)
+ m := make(map[int]bool)
+ for _, id := range tagIDs {
+ m[id] = true
+ }
- err = models.CreateBatchTagListings(ctx, listing.ID, tagIDs)
- if err != nil {
- return nil, err
+ for _, originalTag := range originalTags {
+ if !m[originalTag.ID] {
+ tagIDsToRemove = append(tagIDsToRemove, originalTag.ID)
+ }
+ }
+
+ if len(tagIDsToRemove) > 0 {
+ err = models.RemoveTagsFromListing(ctx, listing.ID, tagIDsToRemove)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ err = models.CreateBatchTagListings(ctx, listing.ID, tagIDs)
+ if err != nil {
+ return nil, err
+ }
+ } else {
+ // Tags field provided but empty - remove all existing tags
+ originalTags := listing.Tags
+ if len(originalTags) > 0 {
+ tagIDsToRemove := make([]int, len(originalTags))
+ for i, t := range originalTags {
+ tagIDsToRemove[i] = t.ID
+ }
+ err = models.RemoveTagsFromListing(ctx, listing.ID, tagIDsToRemove)
+ if err != nil {
+ return nil, err
+ }
+ }
}
}
--
2.49.1