Peter Sanchez: 1 Allow removal of all tags from a bookmark, note, listing or short url. 2 files changed, 204 insertions(+), 81 deletions(-)
Copy & paste the following snippet into your terminal to import this patchset into git:
curl -s https://lists.code.netlandish.com/~netlandish/links-dev/patches/201/mbox | git am -3Learn more about email & git
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
Applied. To git@git.code.netlandish.com:~netlandish/links babd2b9..a484451 master -> master