Peter Sanchez: 1 Because of the way tags are stored we always used the first representation of the tag when displaying it. For instance, a user may save `Test` as a tag and another user may save `test` as a tag but the both users will see `Test` (first version) as their tag display. 16 files changed, 131 insertions(+), 94 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/215/mbox | git am -3Learn more about email & git
This is now fixed and each user/organization can set their own tag casing. In cases where there are multiple casing of the same tag in a single organization then the first one to be saved is used. Fixes: https://todo.code.netlandish.com/~netlandish/links/122 Changelog-updated: Tag casing is now preserved at the organization and bookmark level. --- api/graph/schema.resolvers.go | 46 +++++++++---------- cmd/migrations.go | 7 +++ core/import.go | 6 +-- helpers.go | 23 ++++++---- .../0009_add_tag_junction_name.down.sql | 3 ++ migrations/0009_add_tag_junction_name.up.sql | 11 +++++ models/base_url.go | 2 +- models/link_short.go | 4 +- models/listing.go | 4 +- models/models.go | 9 ++++ models/org_link.go | 4 +- models/organization.go | 2 +- models/tag.go | 18 ++++++-- models/tag_link_shorts.go | 30 ++++++------ models/tag_links.go | 26 +++++------ models/tag_listing.go | 30 ++++++------ 16 files changed, 131 insertions(+), 94 deletions(-) create mode 100644 migrations/0009_add_tag_junction_name.down.sql create mode 100644 migrations/0009_add_tag_junction_name.up.sql diff --git a/api/graph/schema.resolvers.go b/api/graph/schema.resolvers.go index 79104c8..2c11efb 100644 --- a/api/graph/schema.resolvers.go +++ b/api/graph/schema.resolvers.go @@ -598,11 +598,11 @@ func (r *mutationResolver) AddLink(ctx context.Context, input *model.LinkInput) } if len(tags) > 0 { - tagIDs, err := links.ProcessTags(ctx, tags) + newTags, err := links.ProcessTags(ctx, tags) if err != nil { return nil, err } - err = models.CreateBatchTagLinks(ctx, OrgLink.ID, tagIDs) + err = models.CreateBatchTagLinks(ctx, OrgLink.ID, newTags) if err != nil { return nil, err } @@ -784,15 +784,15 @@ func (r *mutationResolver) UpdateLink(ctx context.Context, input *model.UpdateLi if tagsProvided { if len(tags) > 0 { // Calculate the difference between original tags and new ones - tagIDs, err := links.ProcessTags(ctx, tags) + newTags, 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 _, tag := range newTags { + m[tag.ID] = true } for _, originalTag := range originalTags { @@ -808,7 +808,7 @@ func (r *mutationResolver) UpdateLink(ctx context.Context, input *model.UpdateLi } } - err = models.CreateBatchTagLinks(ctx, orgLink.ID, tagIDs) + err = models.CreateBatchTagLinks(ctx, orgLink.ID, newTags) if err != nil { return nil, err } @@ -1095,11 +1095,11 @@ func (r *mutationResolver) AddNote(ctx context.Context, input *model.NoteInput) } if len(tags) > 0 { - tagIDs, err := links.ProcessTags(ctx, tags) + newTags, err := links.ProcessTags(ctx, tags) if err != nil { return nil, err } - err = models.CreateBatchTagLinks(ctx, OrgLinkNote.ID, tagIDs) + err = models.CreateBatchTagLinks(ctx, OrgLinkNote.ID, newTags) if err != nil { return nil, err } @@ -2444,11 +2444,11 @@ func (r *mutationResolver) AddLinkShort(ctx context.Context, input *model.LinkSh } if len(tags) > 0 { - tagIDs, err := links.ProcessTags(ctx, tags) + newTags, err := links.ProcessTags(ctx, tags) if err != nil { return nil, err } - err = models.CreateBatchTagLinkShorts(ctx, linkShort.ID, tagIDs) + err = models.CreateBatchTagLinkShorts(ctx, linkShort.ID, newTags) if err != nil { return nil, err } @@ -2621,7 +2621,7 @@ func (r *mutationResolver) UpdateLinkShort(ctx context.Context, input *model.Upd if tagsProvided { if len(tags) > 0 { - tagIDs, err := links.ProcessTags(ctx, tags) + newTags, err := links.ProcessTags(ctx, tags) if err != nil { return nil, err } @@ -2629,8 +2629,8 @@ func (r *mutationResolver) UpdateLinkShort(ctx context.Context, input *model.Upd originalTags := linkShort.Tags tagIDsToRemove := make([]int, 0) m := make(map[int]bool) - for _, id := range tagIDs { - m[id] = true + for _, tag := range newTags { + m[tag.ID] = true } for _, originalTag := range originalTags { @@ -2646,7 +2646,7 @@ func (r *mutationResolver) UpdateLinkShort(ctx context.Context, input *model.Upd } } - err = models.CreateBatchTagLinkShorts(ctx, linkShort.ID, tagIDs) + err = models.CreateBatchTagLinkShorts(ctx, linkShort.ID, newTags) if err != nil { return nil, err } @@ -3040,11 +3040,11 @@ func (r *mutationResolver) AddListing(ctx context.Context, input *model.AddListi } if len(tags) > 0 { - tagIDs, err := links.ProcessTags(ctx, tags) + newTags, err := links.ProcessTags(ctx, tags) if err != nil { return nil, err } - err = models.CreateBatchTagListings(ctx, listing.ID, tagIDs) + err = models.CreateBatchTagListings(ctx, listing.ID, newTags) if err != nil { return nil, err } @@ -3472,7 +3472,7 @@ func (r *mutationResolver) UpdateListing(ctx context.Context, input *model.Updat if tagsProvided { if len(tags) > 0 { - tagIDs, err := links.ProcessTags(ctx, tags) + newTags, err := links.ProcessTags(ctx, tags) if err != nil { return nil, err } @@ -3480,8 +3480,8 @@ func (r *mutationResolver) UpdateListing(ctx context.Context, input *model.Updat originalTags := listing.Tags tagIDsToRemove := make([]int, 0) m := make(map[int]bool) - for _, id := range tagIDs { - m[id] = true + for _, tag := range newTags { + m[tag.ID] = true } for _, originalTag := range originalTags { @@ -3497,7 +3497,7 @@ func (r *mutationResolver) UpdateListing(ctx context.Context, input *model.Updat } } - err = models.CreateBatchTagListings(ctx, listing.ID, tagIDs) + err = models.CreateBatchTagListings(ctx, listing.ID, newTags) if err != nil { return nil, err } @@ -4432,17 +4432,17 @@ func (r *mutationResolver) RenameTag(ctx context.Context, input model.TagInput) } oTag := tags[0] - tagIDs, err := links.ProcessTags(ctx, []string{*input.NewTag}) + newTags, err := links.ProcessTags(ctx, []string{*input.NewTag}) if err != nil { return nil, err } - if len(tagIDs) == 0 { + if len(newTags) == 0 { validator.Error("%s", lt.Translate("newTag value is not valid.")). WithField("newTag"). WithCode(valid.ErrValidationCode) return nil, nil } - nTag := &models.Tag{ID: tagIDs[0]} + nTag := &models.Tag{ID: newTags[0].ID} err = nTag.Load(ctx) if err != nil { return nil, err diff --git a/cmd/migrations.go b/cmd/migrations.go index 77ed113..8e7c71a 100644 --- a/cmd/migrations.go +++ b/cmd/migrations.go @@ -80,5 +80,12 @@ func GetMigrations() []migrate.Migration { 0, links.MigrateFS, ), + migrate.FSFileMigration( + "0009_add_tag_junction_name", + "migrations/0009_add_tag_junction_name.up.sql", + "migrations/0009_add_tag_junction_name.down.sql", + 0, + links.MigrateFS, + ), } } diff --git a/core/import.go b/core/import.go index 11ac49a..648daa6 100644 --- a/core/import.go +++ b/core/import.go @@ -400,12 +400,12 @@ func importOrgLinks(ctx context.Context, objAdapter *importAdapter, baseURLMap m tags = []string{} } if len(tags) > 0 { - tagIDs, err := links.ProcessTags(ctx, tags) + newTags, err := links.ProcessTags(ctx, tags) if err != nil { return err } - if len(tagIDs) > 0 { - err = models.CreateBatchTagLinks(ctx, link.ID, tagIDs) + if len(newTags) > 0 { + err = models.CreateBatchTagLinks(ctx, link.ID, newTags) if err != nil { return err } diff --git a/helpers.go b/helpers.go index df0d56f..09524fe 100644 --- a/helpers.go +++ b/helpers.go @@ -761,28 +761,31 @@ func LangForContext(ctx context.Context) string { return lang } -func ProcessTags(ctx context.Context, tags []string) ([]int, error) { - tagIDs := make([]int, 0) +func ProcessTags(ctx context.Context, tags []string) ([]models.TagInput, error) { + tagInputs := make([]models.TagInput, 0) for _, tag := range tags { - tag := strings.TrimSpace(tag) - tag = strings.TrimPrefix(tag, "#") - if tag != "" { - slug := Slugify(tag) - if len(tag) > 50 || len(slug) > 50 { + originalName := strings.TrimSpace(tag) + originalName = strings.TrimPrefix(originalName, "#") + if originalName != "" { + slug := Slugify(originalName) + if len(originalName) > 50 || len(slug) > 50 { continue } Tag := &models.Tag{ - Name: tag, + Name: originalName, Slug: slug, } err := Tag.Store(ctx) if err != nil { return nil, err } - tagIDs = append(tagIDs, Tag.ID) + tagInputs = append(tagInputs, models.TagInput{ + ID: Tag.ID, + Name: originalName, + }) } } - return tagIDs, nil + return tagInputs, nil } func CreateNoteURL(c echo.Context) (string, string) { diff --git a/migrations/0009_add_tag_junction_name.down.sql b/migrations/0009_add_tag_junction_name.down.sql new file mode 100644 index 0000000..a52d644 --- /dev/null +++ b/migrations/0009_add_tag_junction_name.down.sql @@ -0,0 +1,3 @@ +ALTER TABLE tag_links DROP COLUMN name; +ALTER TABLE tag_link_shorts DROP COLUMN name; +ALTER TABLE tag_listings DROP COLUMN name; diff --git a/migrations/0009_add_tag_junction_name.up.sql b/migrations/0009_add_tag_junction_name.up.sql new file mode 100644 index 0000000..064efb4 --- /dev/null +++ b/migrations/0009_add_tag_junction_name.up.sql @@ -0,0 +1,11 @@ +ALTER TABLE tag_links ADD COLUMN name VARCHAR(50) DEFAULT ''; +ALTER TABLE tag_link_shorts ADD COLUMN name VARCHAR(50) DEFAULT ''; +ALTER TABLE tag_listings ADD COLUMN name VARCHAR(50) DEFAULT ''; + +UPDATE tag_links tl SET name = t.name FROM tags t WHERE tl.tag_id = t.id; +UPDATE tag_link_shorts tls SET name = t.name FROM tags t WHERE tls.tag_id = t.id; +UPDATE tag_listings tll SET name = t.name FROM tags t WHERE tll.tag_id = t.id; + +ALTER TABLE tag_links ALTER COLUMN name SET NOT NULL; +ALTER TABLE tag_link_shorts ALTER COLUMN name SET NOT NULL; +ALTER TABLE tag_listings ALTER COLUMN name SET NOT NULL; diff --git a/models/base_url.go b/models/base_url.go index f4d10e8..8a88578 100644 --- a/models/base_url.go +++ b/models/base_url.go @@ -65,7 +65,7 @@ func GetBaseURLs(ctx context.Context, opts *database.FilterOptions) ([]*BaseURL, rows, err := q. Columns("b.id", "b.url", "b.title", "b.counter", "b.data", "b.public_ready", "b.hash", "b.parse_attempts", "b.last_parse_attempt", "b.created_on", "b.visibility", - fmt.Sprintf("json_agg(t ORDER BY %s)::jsonb", tagOrder)). + fmt.Sprintf("json_agg(CASE WHEN t.id IS NOT NULL THEN json_build_object('id', t.id, 'name', tl.name, 'slug', t.slug, 'created_on', t.created_on) END ORDER BY %s)::jsonb", tagOrder)). From("base_urls b"). LeftJoin("org_links ol ON ol.base_url_id = b.id"). LeftJoin("organizations o ON o.id = ol.org_id"). diff --git a/models/link_short.go b/models/link_short.go index 828b931..a2b03bf 100644 --- a/models/link_short.go +++ b/models/link_short.go @@ -22,7 +22,7 @@ func GetLinkShorts(ctx context.Context, opts *database.FilterOptions) ([]*LinkSh q := opts.GetBuilder(nil) rows, err := q. Columns("l.id", "l.title", "l.url", "l.short_code", "l.domain_id", "l.org_id", "l.user_id", - "l.created_on", "l.updated_on", "d.lookup_name", "json_agg(t)::jsonb"). + "l.created_on", "l.updated_on", "d.lookup_name", "json_agg(CASE WHEN t.id IS NOT NULL THEN json_build_object('id', t.id, 'name', tl.name, 'slug', t.slug, 'created_on', t.created_on) END)::jsonb"). From("link_shorts AS l"). InnerJoin("domains d ON l.domain_id = d.id"). LeftJoin("tag_link_shorts tl ON tl.link_short_id = l.id"). @@ -236,7 +236,7 @@ func ExportLinkShorts(ctx context.Context, opts *database.FilterOptions) ([]*Exp if err := database.WithTx(ctx, database.TxOptionsRO, func(tx *sql.Tx) error { q := opts.GetBuilder(nil) rows, err := q. - Columns("l.id", "l.title", "l.url", "l.short_code", "l.created_on", "json_agg(t)::jsonb"). + Columns("l.id", "l.title", "l.url", "l.short_code", "l.created_on", "json_agg(CASE WHEN t.id IS NOT NULL THEN json_build_object('id', t.id, 'name', tl.name, 'slug', t.slug, 'created_on', t.created_on) END)::jsonb"). From("link_shorts AS l"). LeftJoin("tag_link_shorts tl ON tl.link_short_id = l.id"). LeftJoin("tags t ON t.id = tl.tag_id"). diff --git a/models/listing.go b/models/listing.go index 7e58ce8..b7c9636 100644 --- a/models/listing.go +++ b/models/listing.go @@ -22,7 +22,7 @@ func GetListings(ctx context.Context, opts *database.FilterOptions) ([]*Listing, rows, err := q. Columns("l.id", "l.title", "l.slug", "l.image", "l.metadata", "l.domain_id", "l.org_id", "l.user_id", "l.is_default", "l.is_active", "l.created_on", "l.updated_on", "d.lookup_name", - "json_agg(t)::jsonb"). + "json_agg(CASE WHEN t.id IS NOT NULL THEN json_build_object('id', t.id, 'name', tl.name, 'slug', t.slug, 'created_on', t.created_on) END)::jsonb"). From("listings l"). InnerJoin("domains d ON l.domain_id = d.id"). InnerJoin("organizations o ON l.org_id = o.id"). @@ -220,7 +220,7 @@ func ExportListings(ctx context.Context, opts *database.FilterOptions) ([]*Expor if err := database.WithTx(ctx, database.TxOptionsRO, func(tx *sql.Tx) error { q := opts.GetBuilder(nil) rows, err := q. - Columns("l.id", "l.title", "l.slug", "l.created_on", "json_agg(t)::jsonb", "json_agg(ll)::jsonb"). + Columns("l.id", "l.title", "l.slug", "l.created_on", "json_agg(CASE WHEN t.id IS NOT NULL THEN json_build_object('id', t.id, 'name', tl.name, 'slug', t.slug, 'created_on', t.created_on) END)::jsonb", "json_agg(ll)::jsonb"). From("listings l"). LeftJoin("listing_links ll ON l.id = ll.listing_id"). LeftJoin("tag_listings tl ON tl.listing_id = l.id"). diff --git a/models/models.go b/models/models.go index e47190f..bb61912 100644 --- a/models/models.go +++ b/models/models.go @@ -138,11 +138,18 @@ type Tag struct { Count int `db:"-" json:"count"` } +// TagInput represents a tag with its ID and user's original name +type TagInput struct { + ID int + Name string +} + // TagLink ... type TagLink struct { ID int `db:"id"` TagID int `db:"tag_id"` OrgLinkID int `db:"org_link_id"` + Name string `db:"name"` CreatedOn time.Time `db:"created_on"` } @@ -150,6 +157,7 @@ type TagLinkShort struct { ID int `db:"id"` TagID int `db:"tag_id"` LinkShortID int `db:"link_short_id"` + Name string `db:"name"` CreatedOn time.Time `db:"created_on"` } @@ -157,6 +165,7 @@ type TagListing struct { ID int `db:"id"` TagID int `db:"tag_id"` ListingID int `db:"listing_id"` + Name string `db:"name"` CreatedOn time.Time `db:"created_on"` } diff --git a/models/org_link.go b/models/org_link.go index 8ea8f88..0148c5b 100644 --- a/models/org_link.go +++ b/models/org_link.go @@ -37,7 +37,7 @@ func GetOrgLinks(ctx context.Context, opts *database.FilterOptions) ([]*OrgLink, rows, err := q. Columns("ol.id", "ol.title", "ol.url", "ol.description", "ol.base_url_id", "ol.org_id", "ol.user_id", "ol.visibility", "ol.unread", "ol.starred", "ol.archive_url", "ol.type", "ol.hash", - "ol.created_on", "ol.updated_on", "o.slug", "u.full_name", fmt.Sprintf("json_agg(t ORDER BY %s)::jsonb", tagOrder), "b.data", "b.counter", "b.hash"). + "ol.created_on", "ol.updated_on", "o.slug", "u.full_name", fmt.Sprintf("json_agg(CASE WHEN t.id IS NOT NULL THEN json_build_object('id', t.id, 'name', tl.name, 'slug', t.slug, 'created_on', t.created_on) END ORDER BY %s)::jsonb", tagOrder), "b.data", "b.counter", "b.hash"). From("org_links ol"). Join("organizations o ON o.id = ol.org_id"). Join("users u ON ol.user_id = u.id"). @@ -325,7 +325,7 @@ func ExportOrgLinks(ctx context.Context, opts *database.FilterOptions) ([]*Expor q := opts.GetBuilder(nil) rows, err := q. Columns("ol.id", "ol.title", "ol.url", "ol.description", "ol.visibility", - "ol.unread", "ol.starred", "ol.hash", "ol.created_on", fmt.Sprintf("json_agg(t ORDER BY %s)::jsonb", tagOrder)). + "ol.unread", "ol.starred", "ol.hash", "ol.created_on", fmt.Sprintf("json_agg(CASE WHEN t.id IS NOT NULL THEN json_build_object('id', t.id, 'name', tl.name, 'slug', t.slug, 'created_on', t.created_on) END ORDER BY %s)::jsonb", tagOrder)). From("org_links ol"). LeftJoin("tag_links tl ON tl.org_link_id = ol.id"). LeftJoin("tags t ON t.id = tl.tag_id"). diff --git a/models/organization.go b/models/organization.go index 61cf9eb..58a6c4e 100644 --- a/models/organization.go +++ b/models/organization.go @@ -354,7 +354,7 @@ func (o *Organization) TagCloud(ctx context.Context, order TagCloudOrdering) ([] sq.Eq{"ol.visibility": OrgLinkVisibilityPublic}, } } - return GetTagCloud(ctx, opts) + return GetTagCloud(ctx, opts, o.ID) } // DeleteTagForService expects service value to be verified and tag to be provided. diff --git a/models/tag.go b/models/tag.go index 3793dd6..c9cbcfa 100644 --- a/models/tag.go +++ b/models/tag.go @@ -204,14 +204,26 @@ func (t *Tag) Delete(ctx context.Context) error { return err } -func GetTagCloud(ctx context.Context, opts *database.FilterOptions) ([]*Tag, error) { +func GetTagCloud(ctx context.Context, opts *database.FilterOptions, orgID int) ([]*Tag, error) { var tags []*Tag tz := timezone.ForContext(ctx) if err := database.WithTx(ctx, database.TxOptionsRO, func(tx *sql.Tx) error { q := opts.GetBuilder(nil) + + nameColumn := "t.name" + if orgID > 0 { + nameColumn = fmt.Sprintf(`( + SELECT tl2.name FROM tag_links tl2 + JOIN org_links ol2 ON ol2.id = tl2.org_link_id + WHERE tl2.tag_id = t.id AND ol2.org_id = %d + ORDER BY tl2.created_on ASC + LIMIT 1 + )`, orgID) + } + rows, err := q. - Columns("t.id", "t.name", "t.slug", "t.created_on", "count(*) as tag_count"). + Columns("t.id", nameColumn, "t.slug", "t.created_on", "count(*) as tag_count"). From("tags t"). Join("tag_links tl on tl.tag_id = t.id"). Join("org_links ol on ol.id = tl.org_link_id"). @@ -273,7 +285,7 @@ func LinkTagCloud[T TaggableModel](ctx context.Context, Filter: sq.Eq{field: ids}, OrderBy: TagCloudOrderString(order), } - return GetTagCloud(ctx, opts) + return GetTagCloud(ctx, opts, 0) } // GetDefaultTagOrder returns a users preferred tag ordering diff --git a/models/tag_link_shorts.go b/models/tag_link_shorts.go index 4b60411..50ff3c8 100644 --- a/models/tag_link_shorts.go +++ b/models/tag_link_shorts.go @@ -21,7 +21,7 @@ func GetTagLinkShorts(ctx context.Context, opts *database.FilterOptions) ([]*Tag if err := database.WithTx(ctx, database.TxOptionsRO, func(tx *sql.Tx) error { q := opts.GetBuilder(nil) rows, err := q. - Columns("id", "link_short_id", "tag_id", "created_on"). + Columns("id", "link_short_id", "tag_id", "name", "created_on"). From("tag_link_shorts"). Distinct(). PlaceholderFormat(database.GetPlaceholderFormat()). @@ -37,7 +37,7 @@ func GetTagLinkShorts(ctx context.Context, opts *database.FilterOptions) ([]*Tag for rows.Next() { var tl TagLinkShort - if err = rows.Scan(&tl.ID, &tl.LinkShortID, &tl.TagID, &tl.CreatedOn); err != nil { + if err = rows.Scan(&tl.ID, &tl.LinkShortID, &tl.TagID, &tl.Name, &tl.CreatedOn); err != nil { return err } err = tl.ToLocalTZ(tz) @@ -60,28 +60,24 @@ func GetTagLinkShort(ctx context.Context, id int) (*TagLinkShort, error) { return tl, err } -func CreateBatchTagLinkShorts(ctx context.Context, linkShortID int, tagIDs []int) error { - if len(tagIDs) == 0 { +func CreateBatchTagLinkShorts(ctx context.Context, linkShortID int, tags []TagInput) error { + if len(tags) == 0 { return nil } err := database.WithTx(ctx, nil, func(tx *sql.Tx) error { var err error q := sq. Insert("tag_link_shorts"). - Columns("link_short_id", "tag_id") + Columns("link_short_id", "tag_id", "name") - for _, tagID := range tagIDs { - q = q.Values(linkShortID, tagID) + for _, tag := range tags { + q = q.Values(linkShortID, tag.ID, tag.Name) } _, err = q. - Suffix("ON CONFLICT (link_short_id, tag_id) DO NOTHING"). + Suffix("ON CONFLICT (link_short_id, tag_id) DO UPDATE SET name = EXCLUDED.name"). PlaceholderFormat(database.GetPlaceholderFormat()). RunWith(tx). ExecContext(ctx) - - if err != nil { - return err - } return err }) return err @@ -114,12 +110,12 @@ func (tl *TagLinkShort) Load(ctx context.Context) error { tz := timezone.ForContext(ctx) err := database.WithTx(ctx, database.TxOptionsRO, func(tx *sql.Tx) error { err := sq. - Select("id", "link_short_id", "tag_id", "created_on"). + Select("id", "link_short_id", "tag_id", "name", "created_on"). From("tag_link_shorts"). Where("id = ?", tl.ID). PlaceholderFormat(database.GetPlaceholderFormat()). RunWith(tx). - ScanContext(ctx, &tl.ID, &tl.LinkShortID, &tl.TagID, &tl.CreatedOn) + ScanContext(ctx, &tl.ID, &tl.LinkShortID, &tl.TagID, &tl.Name, &tl.CreatedOn) if err != nil { if err == sql.ErrNoRows { return nil @@ -139,9 +135,9 @@ func (tl *TagLinkShort) Store(ctx context.Context) error { if tl.ID == 0 { err = sq. Insert("tag_link_shorts"). - Columns("link_short_id", "tag_id"). - Values(tl.LinkShortID, tl.TagID). - Suffix(`RETURNING id, created_on`). + Columns("link_short_id", "tag_id", "name"). + Values(tl.LinkShortID, tl.TagID, tl.Name). + Suffix(`ON CONFLICT (link_short_id, tag_id) DO UPDATE SET name = EXCLUDED.name RETURNING id, created_on`). PlaceholderFormat(database.GetPlaceholderFormat()). RunWith(tx). ScanContext(ctx, &tl.ID, &tl.CreatedOn) diff --git a/models/tag_links.go b/models/tag_links.go index 81c4a1a..81f804c 100644 --- a/models/tag_links.go +++ b/models/tag_links.go @@ -21,7 +21,7 @@ func GetTagLinks(ctx context.Context, opts *database.FilterOptions) ([]*TagLink, if err := database.WithTx(ctx, database.TxOptionsRO, func(tx *sql.Tx) error { q := opts.GetBuilder(nil) rows, err := q. - Columns("id", "org_link_id", "tag_id", "created_on"). + Columns("id", "org_link_id", "tag_id", "name", "created_on"). From("tag_links"). Distinct(). PlaceholderFormat(database.GetPlaceholderFormat()). @@ -37,7 +37,7 @@ func GetTagLinks(ctx context.Context, opts *database.FilterOptions) ([]*TagLink, for rows.Next() { var tl TagLink - if err = rows.Scan(&tl.ID, &tl.OrgLinkID, &tl.TagID, &tl.CreatedOn); err != nil { + if err = rows.Scan(&tl.ID, &tl.OrgLinkID, &tl.TagID, &tl.Name, &tl.CreatedOn); err != nil { return err } err = tl.ToLocalTZ(tz) @@ -60,21 +60,21 @@ func GetTagLink(ctx context.Context, id int) (*TagLink, error) { return tl, err } -func CreateBatchTagLinks(ctx context.Context, linkID int, tagIDs []int) error { - if len(tagIDs) == 0 { +func CreateBatchTagLinks(ctx context.Context, linkID int, tags []TagInput) error { + if len(tags) == 0 { return nil } err := database.WithTx(ctx, nil, func(tx *sql.Tx) error { var err error q := sq. Insert("tag_links"). - Columns("org_link_id", "tag_id") + Columns("org_link_id", "tag_id", "name") - for _, tagID := range tagIDs { - q = q.Values(linkID, tagID) + for _, tag := range tags { + q = q.Values(linkID, tag.ID, tag.Name) } _, err = q. - Suffix("ON CONFLICT (org_link_id, tag_id) DO NOTHING"). + Suffix("ON CONFLICT (org_link_id, tag_id) DO UPDATE SET name = EXCLUDED.name"). PlaceholderFormat(database.GetPlaceholderFormat()). RunWith(tx). ExecContext(ctx) @@ -110,12 +110,12 @@ func (tl *TagLink) Load(ctx context.Context) error { tz := timezone.ForContext(ctx) err := database.WithTx(ctx, database.TxOptionsRO, func(tx *sql.Tx) error { err := sq. - Select("id", "org_link_id", "tag_id", "created_on"). + Select("id", "org_link_id", "tag_id", "name", "created_on"). From("tag_links"). Where("id = ?", tl.ID). PlaceholderFormat(database.GetPlaceholderFormat()). RunWith(tx). - ScanContext(ctx, &tl.ID, &tl.OrgLinkID, &tl.TagID, &tl.CreatedOn) + ScanContext(ctx, &tl.ID, &tl.OrgLinkID, &tl.TagID, &tl.Name, &tl.CreatedOn) if err != nil { if err == sql.ErrNoRows { return nil @@ -135,9 +135,9 @@ func (tl *TagLink) Store(ctx context.Context) error { if tl.ID == 0 { err = sq. Insert("tag_links"). - Columns("org_link_id", "tag_id"). - Values(tl.OrgLinkID, tl.TagID). - Suffix(`RETURNING id, created_on`). + Columns("org_link_id", "tag_id", "name"). + Values(tl.OrgLinkID, tl.TagID, tl.Name). + Suffix(`ON CONFLICT (org_link_id, tag_id) DO UPDATE SET name = EXCLUDED.name RETURNING id, created_on`). PlaceholderFormat(database.GetPlaceholderFormat()). RunWith(tx). ScanContext(ctx, &tl.ID, &tl.CreatedOn) diff --git a/models/tag_listing.go b/models/tag_listing.go index df89dc0..dc0939d 100644 --- a/models/tag_listing.go +++ b/models/tag_listing.go @@ -21,7 +21,7 @@ func GetTagListings(ctx context.Context, opts *database.FilterOptions) ([]*TagLi if err := database.WithTx(ctx, database.TxOptionsRO, func(tx *sql.Tx) error { q := opts.GetBuilder(nil) rows, err := q. - Columns("id", "listing_id", "tag_id", "created_on"). + Columns("id", "listing_id", "tag_id", "name", "created_on"). From("tag_listings"). Distinct(). PlaceholderFormat(database.GetPlaceholderFormat()). @@ -37,7 +37,7 @@ func GetTagListings(ctx context.Context, opts *database.FilterOptions) ([]*TagLi for rows.Next() { var tl TagListing - if err = rows.Scan(&tl.ID, &tl.ListingID, &tl.TagID, &tl.CreatedOn); err != nil { + if err = rows.Scan(&tl.ID, &tl.ListingID, &tl.TagID, &tl.Name, &tl.CreatedOn); err != nil { return err } err = tl.ToLocalTZ(tz) @@ -60,28 +60,24 @@ func GetTagListing(ctx context.Context, id int) (*TagListing, error) { return tl, err } -func CreateBatchTagListings(ctx context.Context, linkShortID int, tagIDs []int) error { - if len(tagIDs) == 0 { +func CreateBatchTagListings(ctx context.Context, listingID int, tags []TagInput) error { + if len(tags) == 0 { return nil } err := database.WithTx(ctx, nil, func(tx *sql.Tx) error { var err error q := sq. Insert("tag_listings"). - Columns("listing_id", "tag_id") + Columns("listing_id", "tag_id", "name") - for _, tagID := range tagIDs { - q = q.Values(linkShortID, tagID) + for _, tag := range tags { + q = q.Values(listingID, tag.ID, tag.Name) } _, err = q. - Suffix("ON CONFLICT (listing_id, tag_id) DO NOTHING"). + Suffix("ON CONFLICT (listing_id, tag_id) DO UPDATE SET name = EXCLUDED.name"). PlaceholderFormat(database.GetPlaceholderFormat()). RunWith(tx). ExecContext(ctx) - - if err != nil { - return err - } return err }) return err @@ -114,12 +110,12 @@ func (tl *TagListing) Load(ctx context.Context) error { tz := timezone.ForContext(ctx) err := database.WithTx(ctx, database.TxOptionsRO, func(tx *sql.Tx) error { err := sq. - Select("id", "listing_id", "tag_id", "created_on"). + Select("id", "listing_id", "tag_id", "name", "created_on"). From("tag_listings"). Where("id = ?", tl.ID). PlaceholderFormat(database.GetPlaceholderFormat()). RunWith(tx). - ScanContext(ctx, &tl.ID, &tl.ListingID, &tl.TagID, &tl.CreatedOn) + ScanContext(ctx, &tl.ID, &tl.ListingID, &tl.TagID, &tl.Name, &tl.CreatedOn) if err != nil { if err == sql.ErrNoRows { return nil @@ -139,9 +135,9 @@ func (tl *TagListing) Store(ctx context.Context) error { if tl.ID == 0 { err = sq. Insert("tag_listings"). - Columns("listing_id", "tag_id"). - Values(tl.ListingID, tl.TagID). - Suffix(`RETURNING id, created_on`). + Columns("listing_id", "tag_id", "name"). + Values(tl.ListingID, tl.TagID, tl.Name). + Suffix(`ON CONFLICT (listing_id, tag_id) DO UPDATE SET name = EXCLUDED.name RETURNING id, created_on`). PlaceholderFormat(database.GetPlaceholderFormat()). RunWith(tx). ScanContext(ctx, &tl.ID, &tl.CreatedOn) -- 2.52.0
Applied. To git@git.code.netlandish.com:~netlandish/links 7a60d1d..77c2dcf master -> master