Received: from mail.netlandish.com (mail.netlandish.com [174.136.98.166]) by code.netlandish.com (Postfix) with ESMTP id 930679E for <~netlandish/links-dev@lists.code.netlandish.com>; Sat, 14 Jun 2025 19:36:45 +0000 (UTC) Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=209.85.222.46; helo=mail-ua1-f46.google.com; envelope-from=peter@netlandish.com; receiver= Authentication-Results: mail.netlandish.com; dkim=pass (1024-bit key; unprotected) header.d=netlandish.com header.i=@netlandish.com header.b=TRwHOUwo Received: from mail-ua1-f46.google.com (mail-ua1-f46.google.com [209.85.222.46]) by mail.netlandish.com (Postfix) with ESMTP id 8BF4C1D6434 for <~netlandish/links-dev@lists.code.netlandish.com>; Sat, 14 Jun 2025 19:37:15 +0000 (UTC) Received: by mail-ua1-f46.google.com with SMTP id a1e0cc1a2514c-87f26496daeso66896241.2 for <~netlandish/links-dev@lists.code.netlandish.com>; Sat, 14 Jun 2025 12:37:15 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=netlandish.com; s=google; t=1749929834; x=1750534634; darn=lists.code.netlandish.com; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to; bh=1jbEe7y063MSw/DoTQvZ89AoJOCi2jhjFf6DoAzmchw=; b=TRwHOUwoi7DuaYLdNU2PfwWpJ/3LsJe2xw3JcDvRpLnR+rRmswYQP2s4eFTuvW+p7B xr9pGisgDZl/B8H7ms5C8QaT6Q+LbuGTXQMVGNqJZ1jEkUbeeYHHW7IvEkPMA97sGGJR +0+FlX2xwzIPHG/8hms174eXRSzmRZd9gRtnk= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1749929834; x=1750534634; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=1jbEe7y063MSw/DoTQvZ89AoJOCi2jhjFf6DoAzmchw=; b=MiPFTy5ib3cO36/336A8X5S9xqOu/YSchuFBqbzQ+udL3tUBFfsTlxSIluM6Mkirv1 /q9OEfXrL2uhuUu+Ywau8OqM/3+xOibTs1uigJn+Kp+/ioxYGdjeELfntyY4fEawsfaq D/rm74I0gr/IZRM2AqkOmpdkQxiISvDZH0inhGKv/titAroJYCfWoSv8UqV8AOVkEqf7 WukIwKu7gE8ADcbIhyzmyqC5d9xC1RbsWzD75acdu3AcLhXQ7c4flJAijlRFZeNYR/kE PdIqreKXKl7VoKY3eUsYhTMsjzRgcmvY8xRV4yk/eLx3mPvGM3wRcnLnPVWBuYnPqWy0 iyXw== X-Gm-Message-State: AOJu0YyGhUF3kGGCOnXBTx1uZuJ4d3UUkHuc49baYiBiJfH3qEc1YOTS 564/Y05TjsGHhZSpg4gMyQCSHJdfzefTNawPg+KLX9TFDoRXbSdz5Ra25Eqryjeqxdc4lPcJXe2 qg8KtGLM= X-Gm-Gg: ASbGnctthpwcdaxDKZOvMUmGM4qjZciTw9mSpj24h807npbwyVczFQ9z1Yiwg+zUD9U QAKshzPb3mmNs4sluNEnqZcOXRouzSENMoCFu4uhTbfkIyvbzNADG8IXWdgHat8lwnP+wyDEfyu uJMlg6VFI7/NTA1om9p9qpRkIYy925wOano7sTlS7X48ZpYaMnvoSfg5zRe238H0W9jgMjqRF/t tHkBLNAN1m/43K2wiOXVuBC6YWEaoUJfd88kw+/wtP/0ph1iK05KwYIWsUofSCbR/68JiStLepE Fr6feQGPdMLJ6FLe+oW9xuTqHK8Fz3o/vKP7ikPWp/O6eaPBMC7s+XkWu/axJBY= X-Google-Smtp-Source: AGHT+IEaQFSfe998RNh/GX2oW6uSKwBMKS0GfhtC+bGSqB3Uxd3cB9CpaqjB5HySSx1hx08W5+cxyA== X-Received: by 2002:a05:6102:4a86:b0:4e6:f7e9:c4a5 with SMTP id ada2fe7eead31-4e7f6262edamr3329872137.22.1749929834074; Sat, 14 Jun 2025 12:37:14 -0700 (PDT) Received: from localhost ([2803:2d60:1107:87f:8278:bb4e:8f36:e943]) by smtp.gmail.com with ESMTPSA id ada2fe7eead31-4e7f2fbf5b9sm512467137.29.2025.06.14.12.37.13 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 14 Jun 2025 12:37:13 -0700 (PDT) From: Peter Sanchez To: ~netlandish/links-dev@lists.code.netlandish.com Cc: Peter Sanchez Subject: [PATCH links] Fixing bug where tag issues would fail due to stupid use of regexp. Instead we now parse the valid JSON and strip out null entries. Date: Sat, 14 Jun 2025 13:37:09 -0600 Message-ID: <20250614193711.19112-1-peter@netlandish.com> X-Mailer: git-send-email 2.47.2 MIME-Version: 1.0 Content-Transfer-Encoding: 8bit I don't really remember what lead us to use the regexp pattern to strip out null entries initially but it was a dumb decision. Changelog-fixed: No longer using regexp to parse null entries from json (no clue wtf we were thinking there) --- models/base_url.go | 13 +++++-------- models/link_short.go | 24 +++++++++--------------- models/listing.go | 34 ++++++++++++---------------------- models/org_link.go | 24 ++++++++---------------- models/utils.go | 23 +++++++++++++++++++++++ 5 files changed, 57 insertions(+), 61 deletions(-) diff --git a/models/base_url.go b/models/base_url.go index affaa04..18c6560 100644 --- a/models/base_url.go +++ b/models/base_url.go @@ -8,7 +8,6 @@ import ( "errors" "fmt" "net/url" - "regexp" "time" sq "github.com/Masterminds/squirrel" @@ -90,14 +89,12 @@ func GetBaseURLs(ctx context.Context, opts *database.FilterOptions) ([]*BaseURL, &url.LastParseAttempt, &url.CreatedOn, &tags); err != nil { return err } - re := regexp.MustCompile(`(,\s)?null,?`) - tags = re.ReplaceAllString(tags, "") - if tags != "[]" { - err = json.Unmarshal([]byte(tags), &url.Tags) - if err != nil { - return err - } + + url.Tags, err = BuildSliceFromJSONString[Tag](tags) + if err != nil { + return err } + err = url.ToLocalTZ(tz) if err != nil { return err diff --git a/models/link_short.go b/models/link_short.go index cda1a20..31a38b0 100644 --- a/models/link_short.go +++ b/models/link_short.go @@ -5,7 +5,6 @@ import ( "database/sql" "encoding/json" "fmt" - "regexp" "time" sq "github.com/Masterminds/squirrel" @@ -48,14 +47,11 @@ func GetLinkShorts(ctx context.Context, opts *database.FilterOptions) ([]*LinkSh &ls.OrgID, &ls.UserID, &ls.CreatedOn, &ls.UpdatedOn, &ls.LookupName, &tags); err != nil { return err } - re := regexp.MustCompile(`(,\s)?null,?`) - tags = re.ReplaceAllString(tags, "") - if tags != "[]" { - err = json.Unmarshal([]byte(tags), &ls.Tags) - if err != nil { - return err - } + ls.Tags, err = BuildSliceFromJSONString[Tag](tags) + if err != nil { + return err } + err = ls.ToLocalTZ(tz) if err != nil { return err @@ -263,14 +259,12 @@ func ExportLinkShorts(ctx context.Context, opts *database.FilterOptions) ([]*Exp if err = rows.Scan(&ls.ID, &ls.Title, &ls.URL, &ls.ShortCode, &ls.CreatedOn, &tags); err != nil { return err } - re := regexp.MustCompile(`(,\s)?null,?`) - tags = re.ReplaceAllString(tags, "") - if tags != "[]" { - err = json.Unmarshal([]byte(tags), &ls.Tags) - if err != nil { - return err - } + ls.Tags, err = BuildSliceFromJSONString[ExportTag](tags) + err = json.Unmarshal([]byte(tags), &ls.Tags) + if err != nil { + return err } + linkShorts = append(linkShorts, &ls) } return nil diff --git a/models/listing.go b/models/listing.go index fa3c778..7e58ce8 100644 --- a/models/listing.go +++ b/models/listing.go @@ -3,9 +3,7 @@ package models import ( "context" "database/sql" - "encoding/json" "fmt" - "regexp" "time" sq "github.com/Masterminds/squirrel" @@ -43,7 +41,6 @@ func GetListings(ctx context.Context, opts *database.FilterOptions) ([]*Listing, } defer rows.Close() - re := regexp.MustCompile(`(,\s)?null,?`) for rows.Next() { var ls Listing var tags string @@ -51,13 +48,12 @@ func GetListings(ctx context.Context, opts *database.FilterOptions) ([]*Listing, &ls.UserID, &ls.IsDefault, &ls.IsActive, &ls.CreatedOn, &ls.UpdatedOn, &ls.LookupName, &tags); err != nil { return err } - tags = re.ReplaceAllString(tags, "") - if tags != "[]" { - err = json.Unmarshal([]byte(tags), &ls.Tags) - if err != nil { - return err - } + + ls.Tags, err = BuildSliceFromJSONString[Tag](tags) + if err != nil { + return err } + err = ls.ToLocalTZ(tz) if err != nil { return err @@ -249,22 +245,16 @@ func ExportListings(ctx context.Context, opts *database.FilterOptions) ([]*Expor if err = rows.Scan(&ls.ID, &ls.Title, &ls.Slug, &ls.CreatedOn, &tags, &links); err != nil { return err } - re := regexp.MustCompile(`(,\s)?null,?`) - links = re.ReplaceAllString(links, "") - if links != "[]" { - err = json.Unmarshal([]byte(links), &ls.Links) - if err != nil { - return err - } + ls.Links, err = BuildSliceFromJSONString[ExportLink](links) + if err != nil { + return err } - tags = re.ReplaceAllString(tags, "") - if tags != "[]" { - err = json.Unmarshal([]byte(tags), &ls.Tags) - if err != nil { - return err - } + ls.Tags, err = BuildSliceFromJSONString[ExportTag](tags) + if err != nil { + return err } + listings = append(listings, &ls) } return nil diff --git a/models/org_link.go b/models/org_link.go index acf6da1..d46657c 100644 --- a/models/org_link.go +++ b/models/org_link.go @@ -3,10 +3,8 @@ package models import ( "context" "database/sql" - "encoding/json" "fmt" "net/url" - "regexp" "time" sq "github.com/Masterminds/squirrel" @@ -69,14 +67,11 @@ func GetOrgLinks(ctx context.Context, opts *database.FilterOptions) ([]*OrgLink, &o.BaseURLData, &o.BaseURLCounter, &o.BaseURLHash); err != nil { return err } - re := regexp.MustCompile(`(,\s)?null,?`) - tags = re.ReplaceAllString(tags, "") - if tags != "[]" { - err = json.Unmarshal([]byte(tags), &o.Tags) - if err != nil { - return err - } + o.Tags, err = BuildSliceFromJSONString[Tag](tags) + if err != nil { + return err } + err = o.ToLocalTZ(tz) if err != nil { return err @@ -349,14 +344,11 @@ func ExportOrgLinks(ctx context.Context, opts *database.FilterOptions) ([]*Expor &o.Unread, &o.Starred, &o.Hash, &o.CreatedOn, &tags); err != nil { return err } - re := regexp.MustCompile(`(,\s)?null,?`) - tags = re.ReplaceAllString(tags, "") - if tags != "[]" { - err = json.Unmarshal([]byte(tags), &o.Tags) - if err != nil { - return err - } + o.Tags, err = BuildSliceFromJSONString[ExportTag](tags) + if err != nil { + return err } + links = append(links, &o) } return nil diff --git a/models/utils.go b/models/utils.go index 0255066..f8d787e 100644 --- a/models/utils.go +++ b/models/utils.go @@ -3,6 +3,8 @@ package models import ( "context" "database/sql" + "encoding/json" + "fmt" mrand "math/rand" "strings" "time" @@ -96,3 +98,24 @@ func ShowLinkCounter(obj any) bool { return false } } + +// SliceNilRemover will remove any nil values from a given slice +func SliceNilRemover[T any](in []*T) []T { + out := make([]T, 0, len(in)) + for _, item := range in { + if item != nil { + out = append(out, *item) + } + } + return out +} + +// BuildSliceFromString will build slice from a json_agg PostgreSQL value. +func BuildSliceFromJSONString[T any](jsonInput string) ([]T, error) { + var dval []*T + err := json.Unmarshal([]byte(jsonInput), &dval) + if err != nil { + return nil, fmt.Errorf("invalid JSON array: %w", err) + } + return SliceNilRemover(dval), nil +} -- 2.47.2