From 7e21914d1775d6dd8b1c6193a8b6ac14e92833b1 Mon Sep 17 00:00:00 2001 From: Peter Sanchez Date: Thu, 16 May 2024 15:53:04 -0600 Subject: [PATCH] I think this is the last of the hash conversions. Implements: https://todo.code.netlandish.com/~netlandish/links/74 --- api/graph/generated.go | 61 ++++++++++++++++++++++++++++++++ api/graph/schema.graphqls | 1 + api/graph/schema.resolvers.go | 2 +- core/routes.go | 9 ++--- migrations/0001_initial.up.sql | 3 +- migrations/test_migration.up.sql | 10 +++--- models/base_url.go | 23 +++++++----- models/models.go | 1 + models/schema.sql | 5 ++- 9 files changed, 93 insertions(+), 22 deletions(-) diff --git a/api/graph/generated.go b/api/graph/generated.go index ebb41d9..a2b7643 100644 --- a/api/graph/generated.go +++ b/api/graph/generated.go @@ -92,6 +92,7 @@ type ComplexityRoot struct { Counter func(childComplexity int) int CreatedOn func(childComplexity int) int Data func(childComplexity int) int + Hash func(childComplexity int) int ID func(childComplexity int) int PublicReady func(childComplexity int) int Tags func(childComplexity int) int @@ -679,6 +680,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.BaseURL.Data(childComplexity), true + case "BaseURL.hash": + if e.complexity.BaseURL.Hash == nil { + break + } + + return e.complexity.BaseURL.Hash(childComplexity), true + case "BaseURL.id": if e.complexity.BaseURL.ID == nil { break @@ -4854,6 +4862,50 @@ func (ec *executionContext) fieldContext_BaseURL_publicReady(ctx context.Context return fc, nil } +func (ec *executionContext) _BaseURL_hash(ctx context.Context, field graphql.CollectedField, obj *models.BaseURL) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_BaseURL_hash(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Hash, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_BaseURL_hash(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "BaseURL", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + func (ec *executionContext) _BaseURL_data(ctx context.Context, field graphql.CollectedField, obj *models.BaseURL) (ret graphql.Marshaler) { fc, err := ec.fieldContext_BaseURL_data(ctx, field) if err != nil { @@ -16241,6 +16293,8 @@ func (ec *executionContext) fieldContext_Query_getPopularLinks(ctx context.Conte return ec.fieldContext_BaseURL_tags(ctx, field) case "publicReady": return ec.fieldContext_BaseURL_publicReady(ctx, field) + case "hash": + return ec.fieldContext_BaseURL_hash(ctx, field) case "data": return ec.fieldContext_BaseURL_data(ctx, field) case "createdOn": @@ -23581,6 +23635,13 @@ func (ec *executionContext) _BaseURL(ctx context.Context, sel ast.SelectionSet, out.Values[i] = ec._BaseURL_publicReady(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } + case "hash": + + out.Values[i] = ec._BaseURL_hash(ctx, field, obj) + if out.Values[i] == graphql.Null { invalids++ } diff --git a/api/graph/schema.graphqls b/api/graph/schema.graphqls index 5a891d1..094004c 100644 --- a/api/graph/schema.graphqls +++ b/api/graph/schema.graphqls @@ -146,6 +146,7 @@ type BaseURL { counter: Int! tags: [Tag]! publicReady: Boolean! @internal + hash: String! data: BaseURLData! createdOn: Time! updatedOn: Time! diff --git a/api/graph/schema.resolvers.go b/api/graph/schema.resolvers.go index 1c1ca89..c38dc9f 100644 --- a/api/graph/schema.resolvers.go +++ b/api/graph/schema.resolvers.go @@ -39,7 +39,7 @@ import ( "golang.org/x/image/draw" "golang.org/x/net/idna" "netlandish.com/x/gobwebs" - oauth2 "netlandish.com/x/gobwebs-oauth2" + "netlandish.com/x/gobwebs-oauth2" gaccounts "netlandish.com/x/gobwebs/accounts" "netlandish.com/x/gobwebs/database" "netlandish.com/x/gobwebs/email" diff --git a/core/routes.go b/core/routes.go index bf95e01..ffa3073 100644 --- a/core/routes.go +++ b/core/routes.go @@ -1356,11 +1356,11 @@ func (s *Service) OrgLinksCreate(c echo.Context) error { } // BaseURL - id, err := strconv.Atoi(c.QueryParam("burlid")) - if err == nil { + hash := c.QueryParam("burlid") + if hash != "" { opts := &database.FilterOptions{ Filter: sq.And{ - sq.Eq{"b.id": id}, + sq.Eq{"b.hash": hash}, sq.Eq{"b.public_ready": true}, }, } @@ -1382,7 +1382,7 @@ func (s *Service) OrgLinksCreate(c echo.Context) error { } } else { // OrgLink - hash := c.QueryParam("linkid") + hash = c.QueryParam("linkid") if hash != "" { type GraphQLResponse struct { Link models.OrgLink `json:"getOrgLink"` @@ -1441,6 +1441,7 @@ func (s *Service) PopularLinkList(c echo.Context) error { id title url + hash counter tags { id diff --git a/migrations/0001_initial.up.sql b/migrations/0001_initial.up.sql index 352d3a3..f44fac5 100644 --- a/migrations/0001_initial.up.sql +++ b/migrations/0001_initial.up.sql @@ -85,6 +85,7 @@ CREATE TABLE base_urls ( title VARCHAR ( 150 ) DEFAULT '', url TEXT UNIQUE NOT NULL, public_ready BOOLEAN DEFAULT FALSE, + hash VARCHAR(128) UNIQUE NOT NULL, data JSONB DEFAULT '{}', counter INT DEFAULT 0, created_on TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, @@ -100,7 +101,7 @@ CREATE TABLE org_links ( url TEXT NOT NULL, description TEXT DEFAULT '', "type" INT NOT NULL DEFAULT 0, - hash VARCHAR(128) UNIQUE NOT NULL DEFAULT substr(encode(sha256(random()::text::bytea), 'hex'), 0, 27), + hash VARCHAR(128) UNIQUE NOT NULL, base_url_id INT REFERENCES base_urls (id) ON DELETE CASCADE, org_id INT REFERENCES organizations (id) ON DELETE CASCADE NOT NULL, user_id INT REFERENCES users (id) ON DELETE SET NULL, diff --git a/migrations/test_migration.up.sql b/migrations/test_migration.up.sql index 9879d36..d954a89 100644 --- a/migrations/test_migration.up.sql +++ b/migrations/test_migration.up.sql @@ -7,13 +7,13 @@ INSERT INTO organizations (owner_id, name, slug, org_type) VALUES (1, 'business INSERT INTO organizations (owner_id, name, slug) VALUES (2, 'api test org', 'api-test-org'); -INSERT INTO base_urls (url) VALUES ('http://base.com'); +INSERT INTO base_urls (url, hash) VALUES ('http://base.com', 'abcdefg'); -INSERT INTO org_links (title, url, base_url_id, user_id, org_id, visibility) VALUES - ('Public Business url', 'http://base.com?vis=public', 1, 1, 2, 0); +INSERT INTO org_links (title, url, base_url_id, user_id, org_id, visibility, hash) VALUES + ('Public Business url', 'http://base.com?vis=public', 1, 1, 2, 0, 'hijklmn'); -INSERT INTO org_links (title, url, base_url_id, user_id, org_id, visibility) VALUES - ('Private Business url', 'http://base.com?vis=private', 1, 1, 2, 1); +INSERT INTO org_links (title, url, base_url_id, user_id, org_id, visibility, hash) VALUES + ('Private Business url', 'http://base.com?vis=private', 1, 1, 2, 1, 'opqrstu'); INSERT INTO domains (name, lookup_name, org_id, service, status) VALUES ('short domain', 'short.domain.org', 1, 1, 1); INSERT INTO domains (name, lookup_name, org_id, service, status, level) VALUES ('listing domain', 'list.domain.org', 1, 2, 1, 1); diff --git a/models/base_url.go b/models/base_url.go index 8105b60..a70ea87 100644 --- a/models/base_url.go +++ b/models/base_url.go @@ -12,6 +12,7 @@ import ( "time" sq "github.com/Masterminds/squirrel" + "github.com/segmentio/ksuid" "netlandish.com/x/gobwebs/database" "netlandish.com/x/gobwebs/timezone" ) @@ -53,7 +54,8 @@ func GetBaseURLs(ctx context.Context, opts *database.FilterOptions) ([]*BaseURL, if err := database.WithTx(ctx, database.TxOptionsRO, func(tx *sql.Tx) error { q := opts.GetBuilder(nil) rows, err := q. - Columns("b.id", "b.url", "b.title", "b.counter", "b.data", "b.public_ready", "b.created_on", "json_agg(t)::jsonb"). + Columns("b.id", "b.url", "b.title", "b.counter", "b.data", "b.public_ready", "b.hash", + "b.created_on", "json_agg(t)::jsonb"). From("base_urls b"). LeftJoin("org_links ol ON ol.base_url_id = b.id"). LeftJoin("tag_links tl ON tl.org_link_id = ol.id"). @@ -75,7 +77,7 @@ func GetBaseURLs(ctx context.Context, opts *database.FilterOptions) ([]*BaseURL, var url BaseURL var tags string if err = rows.Scan(&url.ID, &url.URL, &url.Title, &url.Counter, - &url.Data, &url.PublicReady, &url.CreatedOn, &tags); err != nil { + &url.Data, &url.PublicReady, &url.Hash, &url.CreatedOn, &tags); err != nil { return err } re := regexp.MustCompile(`(,\s)?null,?`) @@ -114,12 +116,13 @@ func (b *BaseURL) Load(ctx context.Context) error { tz := timezone.ForContext(ctx) err := database.WithTx(ctx, database.TxOptionsRO, func(tx *sql.Tx) error { err := sq. - Select("id", "title", "url", "counter", "data", "public_ready", "created_on"). + Select("id", "title", "url", "counter", "data", "public_ready", "hash", "created_on"). From("base_urls"). Where("id = ?", b.ID). PlaceholderFormat(sq.Dollar). RunWith(tx). - ScanContext(ctx, &b.ID, &b.Title, &b.URL, &b.Counter, &b.Data, &b.PublicReady, &b.CreatedOn) + ScanContext(ctx, &b.ID, &b.Title, &b.URL, &b.Counter, &b.Data, + &b.PublicReady, &b.Hash, &b.CreatedOn) if err != nil { if err == sql.ErrNoRows { return nil @@ -137,10 +140,13 @@ func (b *BaseURL) Store(ctx context.Context) error { err := database.WithTx(ctx, nil, func(tx *sql.Tx) error { var err error if b.ID == 0 { + if b.Hash == "" { + b.Hash = ksuid.New().String() + } row := tx.QueryRowContext(ctx, ` WITH ins AS ( - INSERT INTO base_urls(url) - VALUES ($1) + INSERT INTO base_urls (url, hash) + VALUES ($1, $2) ON CONFLICT (url) DO UPDATE SET url = NULL WHERE FALSE @@ -150,7 +156,7 @@ func (b *BaseURL) Store(ctx context.Context) error { UNION ALL SELECT id, url, created_on FROM base_urls WHERE url = $1 - LIMIT 1;`, b.URL) + LIMIT 1;`, b.URL, b.Hash) err := row.Scan(&b.ID, &b.URL, &b.CreatedOn) if err != nil { return err @@ -161,6 +167,7 @@ func (b *BaseURL) Store(ctx context.Context) error { Set("title", b.Title). Set("data", b.Data). Set("public_ready", b.PublicReady). + Set("hash", b.Hash). Where("id = ?", b.ID). Suffix(`RETURNING (updated_on)`). PlaceholderFormat(sq.Dollar). @@ -228,6 +235,6 @@ func (b *BaseURL) TagsToString() string { // to a users bookmarks func (b *BaseURL) QueryParams() url.Values { qs := url.Values{} - qs.Set("burlid", fmt.Sprint(b.ID)) + qs.Set("burlid", b.Hash) return qs } diff --git a/models/models.go b/models/models.go index f198860..0d6c4ef 100644 --- a/models/models.go +++ b/models/models.go @@ -70,6 +70,7 @@ type BaseURL struct { Counter int `db:"counter"` Data BaseURLData `db:"data"` PublicReady bool `db:"public_ready"` + Hash string `db:"hash" json:"hash"` CreatedOn time.Time `db:"created_on"` UpdatedOn time.Time `db:"updated_on"` diff --git a/models/schema.sql b/models/schema.sql index 73cc957..f44fac5 100644 --- a/models/schema.sql +++ b/models/schema.sql @@ -1,5 +1,3 @@ -CREATE EXTENSION IF NOT EXISTS pgcrypto; - CREATE OR REPLACE FUNCTION update_updated_on_column() RETURNS TRIGGER AS $$ BEGIN @@ -87,6 +85,7 @@ CREATE TABLE base_urls ( title VARCHAR ( 150 ) DEFAULT '', url TEXT UNIQUE NOT NULL, public_ready BOOLEAN DEFAULT FALSE, + hash VARCHAR(128) UNIQUE NOT NULL, data JSONB DEFAULT '{}', counter INT DEFAULT 0, created_on TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, @@ -102,7 +101,7 @@ CREATE TABLE org_links ( url TEXT NOT NULL, description TEXT DEFAULT '', "type" INT NOT NULL DEFAULT 0, - hash VARCHAR(128) UNIQUE NOT NULL DEFAULT substr(encode(sha256(random()::text::bytea), 'hex'), 0, 27), + hash VARCHAR(128) UNIQUE NOT NULL, base_url_id INT REFERENCES base_urls (id) ON DELETE CASCADE, org_id INT REFERENCES organizations (id) ON DELETE CASCADE NOT NULL, user_id INT REFERENCES users (id) ON DELETE SET NULL, -- 2.45.2