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