Received: from mail.netlandish.com (mail.netlandish.com [174.136.98.166])
	by code.netlandish.com (Postfix) with ESMTP id BA1FC270
	for <~netlandish/links-dev@lists.code.netlandish.com>; Thu, 17 Apr 2025 00:48:11 +0000 (UTC)
Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=209.85.128.173; helo=mail-yw1-f173.google.com; envelope-from=peter@netlandish.com; receiver=<UNKNOWN> 
Authentication-Results: mail.netlandish.com;
	dkim=pass (1024-bit key; unprotected) header.d=netlandish.com header.i=@netlandish.com header.b=mGeRt27B
Received: from mail-yw1-f173.google.com (mail-yw1-f173.google.com [209.85.128.173])
	by mail.netlandish.com (Postfix) with ESMTP id 0D9291D642E
	for <~netlandish/links-dev@lists.code.netlandish.com>; Thu, 17 Apr 2025 00:48:21 +0000 (UTC)
Received: by mail-yw1-f173.google.com with SMTP id 00721157ae682-6fece18b3c8so1715037b3.3
        for <~netlandish/links-dev@lists.code.netlandish.com>; Wed, 16 Apr 2025 17:48:20 -0700 (PDT)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
        d=netlandish.com; s=google; t=1744850900; x=1745455700; 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=4xXO7SGzbV+DNxQ4BikYtFAgV1pkIhopFmYG1VvATro=;
        b=mGeRt27BKciV0vV+WNVaq/xDspJRseExJkr7R+w1HlPbGjehCd1poX3WJB0eXd1/Eo
         EiwN+FpMXttKSKnKwe9ahlabU/zYODnkUYkgBNzc6SVj+r15fKvfod9TKjwDkAjhEz2Y
         06Gaa4RKXVZzERLm2rIhP0OtJV9bkNCYzE1mI=
X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
        d=1e100.net; s=20230601; t=1744850900; x=1745455700;
        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=4xXO7SGzbV+DNxQ4BikYtFAgV1pkIhopFmYG1VvATro=;
        b=JFr1TWugC9ftM9a2It7FmMT+e/46r8Vdf3hd8PeiSlBbfU+7h71xkYGZyCVarebY/s
         c6wb0v8l4TBGFnVB6vYjEtbD9koFggyQCKmzIFOfmNXZyhUODPWOxUZPDcB6odsh7cU2
         QvVZ1i5Csb3lyO0IIzmAOJkusTAPgbPR3gvHLWjsHVjggF+fUOW6BEH0aal1OrX3npOP
         wfuO0iLv+STDfQ3j6Uy1d4IwY7/LRT/fEgHrq6x7wzrYUlV+KM85KsiBa5S+gQBuQ8+G
         Qwimo7EkNXN1e5aHrMwg5EVT2syzPzdeA0N59wVGSqQuW0r3CKklHz2lN5mZABw24oY0
         yi9Q==
X-Gm-Message-State: AOJu0YwRV2NGq3pdg4TU0dIoZNeDXpHvOf2Hcb9pKK7jHVbmAu/ST4ln
	udvoA2oGAtWMcwXxrfLJklEPITULmB63yoJylMNTcU3jt9VEXoBJvfW9wfWj0JsjgImn/4HHgHW
	if0g=
X-Gm-Gg: ASbGncuqc7ImKdJ4CUfiEPlvQRyXGvE9ykj4389JPdpMyjL9n/XSdethbHKLhHPi7Da
	PJ/0hEwiDReFrWuiXFb6dUvlOBqR1cN7P25PL+dEYAUV3nwwbKfHjFHcxGRn6X4ObbsyxZ21tmd
	duavoAfWJsymZHRW8vf2vGWsBjPeBjoy8iXlMzogsOpWxxCuh/q9PO7ZtzHR+MwBGLinpM1rTXc
	9e2QuR/lySCI8FPGgq70eUxf40O/xrTMY908Ud/7DJPT1KuSanwOUpoiGzHUhV7tfWfRkKbE+oZ
	TC862ywZJyHbwp5qYxYLP+Y+Wg1fPxLw4y1OaaA20X+aCzdEwP4=
X-Google-Smtp-Source: AGHT+IHs+wnQT4inLJXuCcfW6Vev1Um8VULpRonjYky3V98MXt7vbJE2ncgdqK4JOXK3d0NCxwDBCg==
X-Received: by 2002:a05:690c:3506:b0:6fe:c040:8eda with SMTP id 00721157ae682-706b325d56amr56999747b3.4.1744850899833;
        Wed, 16 Apr 2025 17:48:19 -0700 (PDT)
Received: from localhost ([2803:2d60:1118:5ee:cf90:91a0:674:a269])
        by smtp.gmail.com with ESMTPSA id 00721157ae682-7053e39d38csm44261697b3.116.2025.04.16.17.48.18
        (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);
        Wed, 16 Apr 2025 17:48:19 -0700 (PDT)
From: Peter Sanchez <peter@netlandish.com>
To: ~netlandish/links-dev@lists.code.netlandish.com
Cc: Peter Sanchez <peter@netlandish.com>
Subject: [PATCH links] Changing OrgLink.BaseURLID from sql.NullInt64 to just an int field.
Date: Wed, 16 Apr 2025 18:48:12 -0600
Message-ID: <20250417004816.30020-1-peter@netlandish.com>
X-Mailer: git-send-email 2.47.2
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Implements: https://todo.code.netlandish.com/~netlandish/links/105
Signed-off-by: Peter Sanchez <peter@netlandish.com>
---
 api/api_test.go               |  5 +--
 api/graph/generated.go        | 67 ++++++++---------------------------
 api/graph/schema.graphqls     |  2 +-
 api/graph/schema.resolvers.go | 15 +++-----
 client.go                     |  2 +-
 core/import.go                |  5 ++-
 core/routes_test.go           |  2 +-
 core/samples/create_link.json |  5 +--
 core/samples/detail_link.json |  6 +---
 core/samples/update_link.json |  5 +--
 models/models.go              | 30 ++++++++--------
 11 files changed, 43 insertions(+), 101 deletions(-)

diff --git a/api/api_test.go b/api/api_test.go
index 018aeb4..26cf221 100644
--- a/api/api_test.go
+++ b/api/api_test.go
@@ -661,10 +661,7 @@ func TestAPI(t *testing.T) {
 					id
 					title
 					url
-					baseUrlId {
-						valid
-						int64
-					}
+					baseUrlId
 					orgId
 					userId
 					visibility
diff --git a/api/graph/generated.go b/api/graph/generated.go
index ba2d17c..61befad 100644
--- a/api/graph/generated.go
+++ b/api/graph/generated.go
@@ -509,8 +509,6 @@ type MutationResolver interface {
 	SendRegisterInvitation(ctx context.Context, toEmail string) (*model.RegisterInvitation, error)
 }
 type OrgLinkResolver interface {
-	BaseURLID(ctx context.Context, obj *models.OrgLink) (*model.NullInt, error)
-
 	Visibility(ctx context.Context, obj *models.OrgLink) (model.LinkVisibility, error)
 
 	Type(ctx context.Context, obj *models.OrgLink) (model.LinkType, error)
@@ -14522,22 +14520,22 @@ func (ec *executionContext) _OrgLink_baseUrlId(ctx context.Context, field graphq
 	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
 		directive0 := func(rctx context.Context) (interface{}, error) {
 			ctx = rctx // use context from middleware stack in children
-			return ec.resolvers.OrgLink().BaseURLID(rctx, obj)
+			return obj.BaseURLID, nil
 		}
 
 		directive1 := func(ctx context.Context) (interface{}, error) {
 			scope, err := ec.unmarshalNAccessScope2linksᚋapiᚋgraphᚋmodelᚐAccessScope(ctx, "LINKS")
 			if err != nil {
-				var zeroVal *model.NullInt
+				var zeroVal int
 				return zeroVal, err
 			}
 			kind, err := ec.unmarshalNAccessKind2linksᚋapiᚋgraphᚋmodelᚐAccessKind(ctx, "RO")
 			if err != nil {
-				var zeroVal *model.NullInt
+				var zeroVal int
 				return zeroVal, err
 			}
 			if ec.directives.Access == nil {
-				var zeroVal *model.NullInt
+				var zeroVal int
 				return zeroVal, errors.New("directive access is not implemented")
 			}
 			return ec.directives.Access(ctx, obj, directive0, scope, kind)
@@ -14550,10 +14548,10 @@ func (ec *executionContext) _OrgLink_baseUrlId(ctx context.Context, field graphq
 		if tmp == nil {
 			return nil, nil
 		}
-		if data, ok := tmp.(*model.NullInt); ok {
+		if data, ok := tmp.(int); ok {
 			return data, nil
 		}
-		return nil, fmt.Errorf(`unexpected type %T from directive, should be *links/api/graph/model.NullInt`, tmp)
+		return nil, fmt.Errorf(`unexpected type %T from directive, should be int`, tmp)
 	})
 	if err != nil {
 		ec.Error(ctx, err)
@@ -14565,25 +14563,19 @@ func (ec *executionContext) _OrgLink_baseUrlId(ctx context.Context, field graphq
 		}
 		return graphql.Null
 	}
-	res := resTmp.(*model.NullInt)
+	res := resTmp.(int)
 	fc.Result = res
-	return ec.marshalNNullInt2ᚖlinksᚋapiᚋgraphᚋmodelᚐNullInt(ctx, field.Selections, res)
+	return ec.marshalNInt2int(ctx, field.Selections, res)
 }
 
 func (ec *executionContext) fieldContext_OrgLink_baseUrlId(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
 	fc = &graphql.FieldContext{
 		Object:     "OrgLink",
 		Field:      field,
-		IsMethod:   true,
-		IsResolver: true,
+		IsMethod:   false,
+		IsResolver: false,
 		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
-			switch field.Name {
-			case "int64":
-				return ec.fieldContext_NullInt_int64(ctx, field)
-			case "valid":
-				return ec.fieldContext_NullInt_valid(ctx, field)
-			}
-			return nil, fmt.Errorf("no field named %q was found under type NullInt", field.Name)
+			return nil, errors.New("field of type Int does not have child fields")
 		},
 	}
 	return fc, nil
@@ -28219,41 +28211,10 @@ func (ec *executionContext) _OrgLink(ctx context.Context, sel ast.SelectionSet,
 				atomic.AddUint32(&out.Invalids, 1)
 			}
 		case "baseUrlId":
-			field := field
-
-			innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) {
-				defer func() {
-					if r := recover(); r != nil {
-						ec.Error(ctx, ec.Recover(ctx, r))
-					}
-				}()
-				res = ec._OrgLink_baseUrlId(ctx, field, obj)
-				if res == graphql.Null {
-					atomic.AddUint32(&fs.Invalids, 1)
-				}
-				return res
+			out.Values[i] = ec._OrgLink_baseUrlId(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				atomic.AddUint32(&out.Invalids, 1)
 			}
-
-			if field.Deferrable != nil {
-				dfs, ok := deferred[field.Deferrable.Label]
-				di := 0
-				if ok {
-					dfs.AddField(field)
-					di = len(dfs.Values) - 1
-				} else {
-					dfs = graphql.NewFieldSet([]graphql.CollectedField{field})
-					deferred[field.Deferrable.Label] = dfs
-				}
-				dfs.Concurrently(di, func(ctx context.Context) graphql.Marshaler {
-					return innerFunc(ctx, dfs)
-				})
-
-				// don't run the out.Concurrently() call below
-				out.Values[i] = graphql.Null
-				continue
-			}
-
-			out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) })
 		case "orgId":
 			out.Values[i] = ec._OrgLink_orgId(ctx, field, obj)
 			if out.Values[i] == graphql.Null {
diff --git a/api/graph/schema.graphqls b/api/graph/schema.graphqls
index db6168a..5987562 100644
--- a/api/graph/schema.graphqls
+++ b/api/graph/schema.graphqls
@@ -230,7 +230,7 @@ type OrgLink {
     description: String!
     url: String!
     hash: String!
-    baseUrlId: NullInt! @access(scope: LINKS, kind: RO)
+    baseUrlId: Int! @access(scope: LINKS, kind: RO)
     orgId: Int! @access(scope: LINKS, kind: RO)
     userId: Int @access(scope: LINKS, kind: RO)
     visibility: LinkVisibility! @access(scope: LINKS, kind: RO)
diff --git a/api/graph/schema.resolvers.go b/api/graph/schema.resolvers.go
index 0ed7d79..bfab68a 100644
--- a/api/graph/schema.resolvers.go
+++ b/api/graph/schema.resolvers.go
@@ -525,7 +525,7 @@ func (r *mutationResolver) AddLink(ctx context.Context, input *model.LinkInput)
 	OrgLink := &models.OrgLink{
 		Title:      input.Title,
 		OrgID:      org.ID,
-		BaseURLID:  sql.NullInt64{Valid: true, Int64: int64(BaseURL.ID)},
+		BaseURLID:  BaseURL.ID,
 		Visibility: visibility,
 		Unread:     input.Unread,
 		Starred:    input.Starred,
@@ -681,7 +681,7 @@ func (r *mutationResolver) UpdateLink(ctx context.Context, input *model.UpdateLi
 			return nil, err
 		}
 
-		orgLink.BaseURLID = sql.NullInt64{Valid: true, Int64: int64(BaseURL.ID)}
+		orgLink.BaseURLID = BaseURL.ID
 		orgLink.URL = *input.URL
 		if input.Visibility != nil && string(*input.Visibility) == models.OrgLinkVisibilityPublic {
 			srv.QueueTask("general", core.ParseBaseURLTask(srv, BaseURL, nil))
@@ -861,7 +861,7 @@ func (r *mutationResolver) DeleteLink(ctx context.Context, hash string) (*model.
 
 	deletedID := link.Hash
 	visibility := link.Visibility
-	baseURLID := int(link.BaseURLID.Int64)
+	baseURLID := link.BaseURLID
 	err = link.Delete(ctx)
 	if err != nil {
 		return nil, err
@@ -1012,7 +1012,7 @@ func (r *mutationResolver) AddNote(ctx context.Context, input *model.NoteInput)
 		Title:       input.Title,
 		OrgID:       org.ID,
 		Description: gcore.StripHtmlTags(input.Description),
-		BaseURLID:   sql.NullInt64{Valid: true, Int64: int64(BaseURL.ID)},
+		BaseURLID:   BaseURL.ID,
 		Visibility:  string(input.Visibility),
 		Starred:     input.Starred,
 		URL:         noteURL,
@@ -1027,7 +1027,7 @@ func (r *mutationResolver) AddNote(ctx context.Context, input *model.NoteInput)
 	}
 
 	// We process the based link metadata after saving the current note
-	if OrgLinkNote.BaseURLID.Valid && OrgLinkNote.Visibility == models.OrgLinkVisibilityPublic {
+	if OrgLinkNote.Visibility == models.OrgLinkVisibilityPublic {
 		srv.QueueTask("general", core.ParseBaseURLTask(srv, BaseURL, nil))
 	}
 
@@ -4743,11 +4743,6 @@ func (r *mutationResolver) SendRegisterInvitation(ctx context.Context, toEmail s
 	return &model.RegisterInvitation{Email: toEmail}, nil
 }
 
-// BaseURLID is the resolver for the baseUrlId field.
-func (r *orgLinkResolver) BaseURLID(ctx context.Context, obj *models.OrgLink) (*model.NullInt, error) {
-	return &model.NullInt{Int64: int(obj.BaseURLID.Int64), Valid: obj.BaseURLID.Valid}, nil
-}
-
 // Visibility is the resolver for the visibility field.
 func (r *orgLinkResolver) Visibility(ctx context.Context, obj *models.OrgLink) (model.LinkVisibility, error) {
 	return model.LinkVisibility(obj.Visibility), nil
diff --git a/client.go b/client.go
index 3bc6a68..caeabcc 100644
--- a/client.go
+++ b/client.go
@@ -35,7 +35,7 @@ func (h *HTTPTransport) RoundTrip(req *http.Request) (*http.Response, error) {
 }
 
 // Execute ...
-func Execute(ctx context.Context, op *gqlclient.Operation, result interface{}) error {
+func Execute(ctx context.Context, op *gqlclient.Operation, result any) error {
 	var (
 		client     *gqlclient.Client
 		httpClient *http.Client
diff --git a/core/import.go b/core/import.go
index b1a1129..322bbc3 100644
--- a/core/import.go
+++ b/core/import.go
@@ -2,7 +2,6 @@ package core
 
 import (
 	"context"
-	"database/sql"
 	"encoding/json"
 	"fmt"
 	"io"
@@ -313,7 +312,7 @@ func processOrgLinks(obj importObj, baseURLMap map[string]int,
 		Title:       links.SanitizeUTF8(title),
 		URL:         obj.GetURL(),
 		Description: links.SanitizeUTF8(obj.GetDescription()),
-		BaseURLID:   sql.NullInt64{Valid: true, Int64: int64(baseID)},
+		BaseURLID:   baseID,
 		OrgID:       org.ID,
 		UserID:      int(user.ID),
 		Type:        linkType,
@@ -355,7 +354,7 @@ func importOrgLinks(ctx context.Context, objAdapter *importAdapter, baseURLMap m
 		nLinks := make([]*models.OrgLink, 0)
 
 		for _, ol := range orgLinks {
-			olId := fmt.Sprintf("%d:%d", ol.BaseURLID.Int64, ol.OrgID)
+			olId := fmt.Sprintf("%d:%d", ol.BaseURLID, ol.OrgID)
 			if _, ok := oMap[olId]; ok {
 				// Found a duplicate, continue
 				continue
diff --git a/core/routes_test.go b/core/routes_test.go
index 98bf6c0..2a9fcde 100644
--- a/core/routes_test.go
+++ b/core/routes_test.go
@@ -215,7 +215,7 @@ func TestHandlers(t *testing.T) {
 		orgLink := &models.OrgLink{
 			Title:      "link",
 			URL:        "http://foo.com",
-			BaseURLID:  sql.NullInt64{Int64: int64(baseURL.ID), Valid: true},
+			BaseURLID:  baseURL.ID,
 			OrgID:      1,
 			UserID:     1,
 			Visibility: models.OrgLinkVisibilityPublic,
diff --git a/core/samples/create_link.json b/core/samples/create_link.json
index 0d2b6d5..b023491 100644
--- a/core/samples/create_link.json
+++ b/core/samples/create_link.json
@@ -3,10 +3,7 @@
         "addLink": {
             "id": 81,
             "url": "http://foo.com",
-            "baseUrlId": {
-                "valid": true,
-                "int64": 26
-            },
+            "baseUrlId": 26,
             "orgId": 1,
             "userId": 1,
             "visibility": "PUBLIC",
diff --git a/core/samples/detail_link.json b/core/samples/detail_link.json
index 91dd593..e2090d8 100644
--- a/core/samples/detail_link.json
+++ b/core/samples/detail_link.json
@@ -5,11 +5,7 @@
 	    "hash": "abcdefg",
             "title": "Detail org",
             "url": "https://www.detail.org",
-            "baseUrlId": {
-                "valid": true,
-                "int64": 32
-
-            },
+            "baseUrlId": 32,
             "orgId": 1,
             "userId": 1,
             "visibility": "PUBLIC",
diff --git a/core/samples/update_link.json b/core/samples/update_link.json
index 358b454..1dfc216 100644
--- a/core/samples/update_link.json
+++ b/core/samples/update_link.json
@@ -3,10 +3,7 @@
         "updateLink": {
             "id": 3,
             "url": "https://edited.io/",
-            "baseUrlId": {
-                "valid": true,
-                "int64": 28
-            },
+            "baseUrlId": 28,
             "orgId": 1,
             "userId": 1,
             "visibility": "PUBLIC",
diff --git a/models/models.go b/models/models.go
index c80f62d..d343917 100644
--- a/models/models.go
+++ b/models/models.go
@@ -71,21 +71,21 @@ type BaseURL struct {
 
 // OrgLink ...
 type OrgLink struct {
-	ID          int           `db:"id" json:"id"`
-	Title       string        `db:"title" json:"title"`
-	Description string        `db:"description" json:"description"`
-	URL         string        `db:"url" json:"url"`
-	BaseURLID   sql.NullInt64 `db:"base_url_id" json:"baseURLId"`
-	OrgID       int           `db:"org_id" json:"orgId"`
-	UserID      int           `db:"user_id" json:"userId"`
-	Visibility  string        `db:"visibility" json:"visibility"`
-	Unread      bool          `db:"unread" json:"unread"`
-	Starred     bool          `db:"starred" json:"starred"`
-	ArchiveURL  string        `db:"archive_url" json:"archiveUrl"`
-	Type        string        `db:"type" json:"type"`
-	Hash        string        `db:"hash" json:"hash"`
-	CreatedOn   time.Time     `db:"created_on" json:"createdOn"`
-	UpdatedOn   time.Time     `db:"updated_on" json:"updatedOn"`
+	ID          int       `db:"id" json:"id"`
+	Title       string    `db:"title" json:"title"`
+	Description string    `db:"description" json:"description"`
+	URL         string    `db:"url" json:"url"`
+	BaseURLID   int       `db:"base_url_id" json:"baseUrlId"`
+	OrgID       int       `db:"org_id" json:"orgId"`
+	UserID      int       `db:"user_id" json:"userId"`
+	Visibility  string    `db:"visibility" json:"visibility"`
+	Unread      bool      `db:"unread" json:"unread"`
+	Starred     bool      `db:"starred" json:"starred"`
+	ArchiveURL  string    `db:"archive_url" json:"archiveUrl"`
+	Type        string    `db:"type" json:"type"`
+	Hash        string    `db:"hash" json:"hash"`
+	CreatedOn   time.Time `db:"created_on" json:"createdOn"`
+	UpdatedOn   time.Time `db:"updated_on" json:"updatedOn"`
 
 	OrgSlug     string        `db:"-" json:"orgSlug"`
 	BaseURLData BaseURLData   `db:"-" json:"baseUrlData"`
-- 
2.47.2