Received: from mail.netlandish.com (mail.netlandish.com [174.136.98.166])
	by code.netlandish.com (Postfix) with ESMTP id DB5B6210
	for <~netlandish/links-dev@lists.code.netlandish.com>; Wed, 12 Nov 2025 13:44:15 +0000 (UTC)
Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=209.85.128.179; helo=mail-yw1-f179.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=YvsZ5Fx7
Received: from mail-yw1-f179.google.com (mail-yw1-f179.google.com [209.85.128.179])
	by mail.netlandish.com (Postfix) with ESMTP id 76E7B1D640A
	for <~netlandish/links-dev@lists.code.netlandish.com>; Wed, 12 Nov 2025 13:45:42 +0000 (UTC)
Received: by mail-yw1-f179.google.com with SMTP id 00721157ae682-7866bca6765so7541557b3.1
        for <~netlandish/links-dev@lists.code.netlandish.com>; Wed, 12 Nov 2025 05:45:42 -0800 (PST)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
        d=netlandish.com; s=google; t=1762955142; x=1763559942; 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=j9JJMdjfX4tbuoM1cXsDr5WKVgV7sRv1rDqLSG05AEI=;
        b=YvsZ5Fx7Ug7WDnIy3ofTBE/4BHYYdaZA8j4KC7G2jLCFC9uXBLSVfwtXGgOh6g5E/3
         reSmLalCdF9wpV8i2tIAs3hoNuPDAZ/ShI3vaj+GMd+NEzel3jfLUTNcd1IFHDPfjW/8
         SZCBYnYyA+XPagR4HV5zew1y9GB3fL00v1iwY=
X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
        d=1e100.net; s=20230601; t=1762955142; x=1763559942;
        h=content-transfer-encoding:mime-version:message-id:date:subject:cc
         :to:from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date
         :message-id:reply-to;
        bh=j9JJMdjfX4tbuoM1cXsDr5WKVgV7sRv1rDqLSG05AEI=;
        b=jqJmjQUGJEpUEczIoaOcgzlhrTFuNo8fS87w12hSAI93MfDrsWoMQZckcKojdc2Rgr
         HHLibPdzbYh0IbGLokEH7FjpEpvaxIi4EvSujtRhdCgyCQwvZt3Rdu6mXXwkse1EAxcu
         EH+OPSwyMyCFbz8McXDhpLyQGQvHrKLqIp7QDVrD93ZPNDVkxFbEgXgSOp2NWB2pHBoS
         MwfyLVBRbY8DmLF7w+bByaVmlfdGn6zOsHBvUB2WHML4HMmBZyrbWaasxYXMphRGEBGW
         GHGXyyNisNPKcr7058YYHcx4LQ34Ypb/4zVMN3HmW5eXSDAnTD7rWhGRec6YNlP6sLk9
         Zu/g==
X-Gm-Message-State: AOJu0Yy7RWhJGQDkOJRgu2PimDRB5f9ROesrur/Ku6g709B0SoBVPeTY
	+X+pAs53djcnOxQcZCdubXujzLQclJWjEmaCs9lqq9p7j7LA/dQzhleBXa1RySNrgvLfUMGGQCj
	m+czQGAs=
X-Gm-Gg: ASbGncuJHZXykPSJ6CQ8QmZQFQV4NjyKPY1Odcxiou6GPe90V6iI4FHsDJCF9hgeDK7
	QGjo77ckW8mp3RwTAaJSzVj0hVYJJCrsE1xEerhyvAo8eZUliiru9CnIir6zzwGyAZyG2nMKcCW
	tuXa9Q4DmTsC2zOdIq7AWJFs9/5K6Sn7hKB2+ypP2L2oZzlGomH3u32EkokHKm48ECm1Pe6ysle
	KckOxLFUdNaHA7Oy59KNXFHVvF1AMQe+cBd8XZf7uVauASMRjZY9AA/5xmb7ci2DIOCuOYhHMSk
	kwHiDF1unAvnQ/r3MNn80WC8zs1FFP8DxHjL3LoEnh088q8ScvD+FeOeOMSuvteBuK14lC0O9tO
	NC4r/ZtSaZheklzIOyoZDIicCjrPNc2NhFD3ePunrSkJaa/fndHEUPC/lVfIu3W7vq1Uo9y8E8s
	Wt
X-Google-Smtp-Source: AGHT+IGj8EBPLI5Xrxf3EZE/Ag+ON4R9QEK6XuC4VfprCuvvOEvl9F2TI42Ao0PpicnnNq+bNJ3X+A==
X-Received: by 2002:a05:690c:7005:b0:784:8673:6f6f with SMTP id 00721157ae682-788136e5db4mr22354727b3.58.1762955141639;
        Wed, 12 Nov 2025 05:45:41 -0800 (PST)
Received: from localhost ([2803:2d60:1107:87f:e68a:2c54:8a71:5fe9])
        by smtp.gmail.com with UTF8SMTPSA id 00721157ae682-7880e1b68a9sm11152227b3.57.2025.11.12.05.45.40
        (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);
        Wed, 12 Nov 2025 05:45:41 -0800 (PST)
From: Peter Sanchez <peter@netlandish.com>
To: ~netlandish/links-dev@lists.code.netlandish.com
Cc: Peter Sanchez <peter@netlandish.com>
Subject: [PATCH links] Adding `imageUrl` to relevant GraphQL types to make it easier to fetch images from their individual hosting environments.
Date: Wed, 12 Nov 2025 07:45:31 -0600
Message-ID: <20251112134538.26395-1-peter@netlandish.com>
X-Mailer: git-send-email 2.49.1
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Changelog-added: `imageUrl` field to relevant GraphQL types
---
 api/gqlgen.yml                |  12 ++
 api/graph/generated.go        | 289 +++++++++++++++++++++++++++++++---
 api/graph/schema.graphqls     |   5 +-
 api/graph/schema.resolvers.go |  41 ++++-
 4 files changed, 321 insertions(+), 26 deletions(-)

diff --git a/api/gqlgen.yml b/api/gqlgen.yml
index 090a18f..6f9e4a4 100644
--- a/api/gqlgen.yml
+++ b/api/gqlgen.yml
@@ -67,3 +67,15 @@ models:
   AuditLog:
     model:
       - netlandish.com/x/gobwebs-auditlog.AuditLog
+  Organization:
+    fields:
+      imageUrl:
+        resolver: true
+  Listing:
+    fields:
+      imageUrl:
+        resolver: true
+  QRCode:
+    fields:
+      imageUrl:
+        resolver: true
diff --git a/api/graph/generated.go b/api/graph/generated.go
index b82dd28..1d880ed 100644
--- a/api/graph/generated.go
+++ b/api/graph/generated.go
@@ -46,6 +46,7 @@ type ResolverRoot interface {
 	BaseURL() BaseURLResolver
 	BillingSettings() BillingSettingsResolver
 	Domain() DomainResolver
+	Listing() ListingResolver
 	Mutation() MutationResolver
 	OrgLink() OrgLinkResolver
 	Organization() OrganizationResolver
@@ -192,6 +193,7 @@ type ComplexityRoot struct {
 		DomainID   func(childComplexity int) int
 		ID         func(childComplexity int) int
 		Image      func(childComplexity int) int
+		ImageURL   func(childComplexity int) int
 		IsActive   func(childComplexity int) int
 		IsDefault  func(childComplexity int) int
 		LookupName func(childComplexity int) int
@@ -319,6 +321,7 @@ type ComplexityRoot struct {
 		CreatedOn  func(childComplexity int) int
 		ID         func(childComplexity int) int
 		Image      func(childComplexity int) int
+		ImageURL   func(childComplexity int) int
 		IsActive   func(childComplexity int) int
 		Name       func(childComplexity int) int
 		OrgType    func(childComplexity int) int
@@ -384,6 +387,7 @@ type ComplexityRoot struct {
 		HashID    func(childComplexity int) int
 		ID        func(childComplexity int) int
 		ImagePath func(childComplexity int) int
+		ImageURL  func(childComplexity int) int
 		OrgID     func(childComplexity int) int
 		Title     func(childComplexity int) int
 		URL       func(childComplexity int) int
@@ -496,6 +500,9 @@ type DomainResolver interface {
 	Level(ctx context.Context, obj *models.Domain) (model.DomainLevel, error)
 	Status(ctx context.Context, obj *models.Domain) (model.DomainStatus, error)
 }
+type ListingResolver interface {
+	ImageURL(ctx context.Context, obj *models.Listing) (*string, error)
+}
 type MutationResolver interface {
 	AddOrganization(ctx context.Context, input model.OrganizationInput) (*models.Organization, error)
 	UpdateOrganization(ctx context.Context, input *model.UpdateOrganizationInput) (*models.Organization, error)
@@ -540,7 +547,7 @@ type OrgLinkResolver interface {
 type OrganizationResolver interface {
 	OrgType(ctx context.Context, obj *models.Organization) (model.OrgType, error)
 
-	Image(ctx context.Context, obj *models.Organization) (*graphql.Upload, error)
+	ImageURL(ctx context.Context, obj *models.Organization) (*string, error)
 
 	Visibility(ctx context.Context, obj *models.Organization) (model.PublicVisibility, error)
 }
@@ -549,6 +556,8 @@ type OrganizationSettingsResolver interface {
 }
 type QRCodeResolver interface {
 	CodeType(ctx context.Context, obj *models.QRCode) (model.QRCodeType, error)
+
+	ImageURL(ctx context.Context, obj *models.QRCode) (*string, error)
 }
 type QueryResolver interface {
 	Version(ctx context.Context) (*model.Version, error)
@@ -1179,6 +1188,13 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
 
 		return e.complexity.Listing.Image(childComplexity), true
 
+	case "Listing.imageUrl":
+		if e.complexity.Listing.ImageURL == nil {
+			break
+		}
+
+		return e.complexity.Listing.ImageURL(childComplexity), true
+
 	case "Listing.isActive":
 		if e.complexity.Listing.IsActive == nil {
 			break
@@ -2007,6 +2023,13 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
 
 		return e.complexity.Organization.Image(childComplexity), true
 
+	case "Organization.imageUrl":
+		if e.complexity.Organization.ImageURL == nil {
+			break
+		}
+
+		return e.complexity.Organization.ImageURL(childComplexity), true
+
 	case "Organization.isActive":
 		if e.complexity.Organization.IsActive == nil {
 			break
@@ -2294,6 +2317,13 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
 
 		return e.complexity.QRCode.ImagePath(childComplexity), true
 
+	case "QRCode.imageUrl":
+		if e.complexity.QRCode.ImageURL == nil {
+			break
+		}
+
+		return e.complexity.QRCode.ImageURL(childComplexity), true
+
 	case "QRCode.orgId":
 		if e.complexity.QRCode.OrgID == nil {
 			break
@@ -4703,6 +4733,8 @@ func (ec *executionContext) fieldContext_Analytics_qrList(_ context.Context, fie
 				return ec.fieldContext_QRCode_url(ctx, field)
 			case "imagePath":
 				return ec.fieldContext_QRCode_imagePath(ctx, field)
+			case "imageUrl":
+				return ec.fieldContext_QRCode_imageUrl(ctx, field)
 			case "hashId":
 				return ec.fieldContext_QRCode_hashId(ctx, field)
 			case "createdOn":
@@ -8033,6 +8065,47 @@ func (ec *executionContext) fieldContext_Listing_image(_ context.Context, field
 	return fc, nil
 }
 
+func (ec *executionContext) _Listing_imageUrl(ctx context.Context, field graphql.CollectedField, obj *models.Listing) (ret graphql.Marshaler) {
+	fc, err := ec.fieldContext_Listing_imageUrl(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) (any, error) {
+		ctx = rctx // use context from middleware stack in children
+		return ec.resolvers.Listing().ImageURL(rctx, obj)
+	})
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	if resTmp == nil {
+		return graphql.Null
+	}
+	res := resTmp.(*string)
+	fc.Result = res
+	return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_Listing_imageUrl(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+	fc = &graphql.FieldContext{
+		Object:     "Listing",
+		Field:      field,
+		IsMethod:   true,
+		IsResolver: true,
+		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) _Listing_metadata(ctx context.Context, field graphql.CollectedField, obj *models.Listing) (ret graphql.Marshaler) {
 	fc, err := ec.fieldContext_Listing_metadata(ctx, field)
 	if err != nil {
@@ -8792,6 +8865,8 @@ func (ec *executionContext) fieldContext_ListingCursor_result(_ context.Context,
 				return ec.fieldContext_Listing_slug(ctx, field)
 			case "image":
 				return ec.fieldContext_Listing_image(ctx, field)
+			case "imageUrl":
+				return ec.fieldContext_Listing_imageUrl(ctx, field)
 			case "metadata":
 				return ec.fieldContext_Listing_metadata(ctx, field)
 			case "isActive":
@@ -9480,6 +9555,8 @@ func (ec *executionContext) fieldContext_ListingLinkCursor_result(_ context.Cont
 				return ec.fieldContext_Listing_slug(ctx, field)
 			case "image":
 				return ec.fieldContext_Listing_image(ctx, field)
+			case "imageUrl":
+				return ec.fieldContext_Listing_imageUrl(ctx, field)
 			case "metadata":
 				return ec.fieldContext_Listing_metadata(ctx, field)
 			case "isActive":
@@ -9765,6 +9842,8 @@ func (ec *executionContext) fieldContext_Mutation_addOrganization(ctx context.Co
 				return ec.fieldContext_Organization_slug(ctx, field)
 			case "image":
 				return ec.fieldContext_Organization_image(ctx, field)
+			case "imageUrl":
+				return ec.fieldContext_Organization_imageUrl(ctx, field)
 			case "timezone":
 				return ec.fieldContext_Organization_timezone(ctx, field)
 			case "settings":
@@ -9880,6 +9959,8 @@ func (ec *executionContext) fieldContext_Mutation_updateOrganization(ctx context
 				return ec.fieldContext_Organization_slug(ctx, field)
 			case "image":
 				return ec.fieldContext_Organization_image(ctx, field)
+			case "imageUrl":
+				return ec.fieldContext_Organization_imageUrl(ctx, field)
 			case "timezone":
 				return ec.fieldContext_Organization_timezone(ctx, field)
 			case "settings":
@@ -11580,6 +11661,8 @@ func (ec *executionContext) fieldContext_Mutation_addListing(ctx context.Context
 				return ec.fieldContext_Listing_slug(ctx, field)
 			case "image":
 				return ec.fieldContext_Listing_image(ctx, field)
+			case "imageUrl":
+				return ec.fieldContext_Listing_imageUrl(ctx, field)
 			case "metadata":
 				return ec.fieldContext_Listing_metadata(ctx, field)
 			case "isActive":
@@ -11806,6 +11889,8 @@ func (ec *executionContext) fieldContext_Mutation_updateListing(ctx context.Cont
 				return ec.fieldContext_Listing_slug(ctx, field)
 			case "image":
 				return ec.fieldContext_Listing_image(ctx, field)
+			case "imageUrl":
+				return ec.fieldContext_Listing_imageUrl(ctx, field)
 			case "metadata":
 				return ec.fieldContext_Listing_metadata(ctx, field)
 			case "isActive":
@@ -12226,6 +12311,8 @@ func (ec *executionContext) fieldContext_Mutation_addQRCode(ctx context.Context,
 				return ec.fieldContext_QRCode_url(ctx, field)
 			case "imagePath":
 				return ec.fieldContext_QRCode_imagePath(ctx, field)
+			case "imageUrl":
+				return ec.fieldContext_QRCode_imageUrl(ctx, field)
 			case "hashId":
 				return ec.fieldContext_QRCode_hashId(ctx, field)
 			case "createdOn":
@@ -12792,6 +12879,8 @@ func (ec *executionContext) fieldContext_Mutation_updateAdminOrg(ctx context.Con
 				return ec.fieldContext_Organization_slug(ctx, field)
 			case "image":
 				return ec.fieldContext_Organization_image(ctx, field)
+			case "imageUrl":
+				return ec.fieldContext_Organization_imageUrl(ctx, field)
 			case "timezone":
 				return ec.fieldContext_Organization_timezone(ctx, field)
 			case "settings":
@@ -15122,7 +15211,7 @@ func (ec *executionContext) _Organization_image(ctx context.Context, field graph
 	}()
 	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) {
 		ctx = rctx // use context from middleware stack in children
-		return ec.resolvers.Organization().Image(rctx, obj)
+		return obj.Image, nil
 	})
 	if err != nil {
 		ec.Error(ctx, err)
@@ -15131,19 +15220,60 @@ func (ec *executionContext) _Organization_image(ctx context.Context, field graph
 	if resTmp == nil {
 		return graphql.Null
 	}
-	res := resTmp.(*graphql.Upload)
+	res := resTmp.(string)
 	fc.Result = res
-	return ec.marshalOUpload2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚐUpload(ctx, field.Selections, res)
+	return ec.marshalOString2string(ctx, field.Selections, res)
 }
 
 func (ec *executionContext) fieldContext_Organization_image(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+	fc = &graphql.FieldContext{
+		Object:     "Organization",
+		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) _Organization_imageUrl(ctx context.Context, field graphql.CollectedField, obj *models.Organization) (ret graphql.Marshaler) {
+	fc, err := ec.fieldContext_Organization_imageUrl(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) (any, error) {
+		ctx = rctx // use context from middleware stack in children
+		return ec.resolvers.Organization().ImageURL(rctx, obj)
+	})
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	if resTmp == nil {
+		return graphql.Null
+	}
+	res := resTmp.(*string)
+	fc.Result = res
+	return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_Organization_imageUrl(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
 	fc = &graphql.FieldContext{
 		Object:     "Organization",
 		Field:      field,
 		IsMethod:   true,
 		IsResolver: true,
 		Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
-			return nil, errors.New("field of type Upload does not have child fields")
+			return nil, errors.New("field of type String does not have child fields")
 		},
 	}
 	return fc, nil
@@ -15644,6 +15774,8 @@ func (ec *executionContext) fieldContext_OrganizationCursor_result(_ context.Con
 				return ec.fieldContext_Organization_slug(ctx, field)
 			case "image":
 				return ec.fieldContext_Organization_image(ctx, field)
+			case "imageUrl":
+				return ec.fieldContext_Organization_imageUrl(ctx, field)
 			case "timezone":
 				return ec.fieldContext_Organization_timezone(ctx, field)
 			case "settings":
@@ -17156,6 +17288,47 @@ func (ec *executionContext) fieldContext_QRCode_imagePath(_ context.Context, fie
 	return fc, nil
 }
 
+func (ec *executionContext) _QRCode_imageUrl(ctx context.Context, field graphql.CollectedField, obj *models.QRCode) (ret graphql.Marshaler) {
+	fc, err := ec.fieldContext_QRCode_imageUrl(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) (any, error) {
+		ctx = rctx // use context from middleware stack in children
+		return ec.resolvers.QRCode().ImageURL(rctx, obj)
+	})
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	if resTmp == nil {
+		return graphql.Null
+	}
+	res := resTmp.(*string)
+	fc.Result = res
+	return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_QRCode_imageUrl(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+	fc = &graphql.FieldContext{
+		Object:     "QRCode",
+		Field:      field,
+		IsMethod:   true,
+		IsResolver: true,
+		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) _QRCode_hashId(ctx context.Context, field graphql.CollectedField, obj *models.QRCode) (ret graphql.Marshaler) {
 	fc, err := ec.fieldContext_QRCode_hashId(ctx, field)
 	if err != nil {
@@ -17865,6 +18038,8 @@ func (ec *executionContext) fieldContext_Query_getOrganizations(ctx context.Cont
 				return ec.fieldContext_Organization_slug(ctx, field)
 			case "image":
 				return ec.fieldContext_Organization_image(ctx, field)
+			case "imageUrl":
+				return ec.fieldContext_Organization_imageUrl(ctx, field)
 			case "timezone":
 				return ec.fieldContext_Organization_timezone(ctx, field)
 			case "settings":
@@ -17977,6 +18152,8 @@ func (ec *executionContext) fieldContext_Query_getOrganization(ctx context.Conte
 				return ec.fieldContext_Organization_slug(ctx, field)
 			case "image":
 				return ec.fieldContext_Organization_image(ctx, field)
+			case "imageUrl":
+				return ec.fieldContext_Organization_imageUrl(ctx, field)
 			case "timezone":
 				return ec.fieldContext_Organization_timezone(ctx, field)
 			case "settings":
@@ -19622,6 +19799,8 @@ func (ec *executionContext) fieldContext_Query_getQRDetail(ctx context.Context,
 				return ec.fieldContext_QRCode_url(ctx, field)
 			case "imagePath":
 				return ec.fieldContext_QRCode_imagePath(ctx, field)
+			case "imageUrl":
+				return ec.fieldContext_QRCode_imageUrl(ctx, field)
 			case "hashId":
 				return ec.fieldContext_QRCode_hashId(ctx, field)
 			case "createdOn":
@@ -19931,6 +20110,8 @@ func (ec *executionContext) fieldContext_Query_getFeedFollowing(_ context.Contex
 				return ec.fieldContext_Organization_slug(ctx, field)
 			case "image":
 				return ec.fieldContext_Organization_image(ctx, field)
+			case "imageUrl":
+				return ec.fieldContext_Organization_imageUrl(ctx, field)
 			case "timezone":
 				return ec.fieldContext_Organization_timezone(ctx, field)
 			case "settings":
@@ -27577,72 +27758,105 @@ func (ec *executionContext) _Listing(ctx context.Context, sel ast.SelectionSet,
 		case "id":
 			out.Values[i] = ec._Listing_id(ctx, field, obj)
 			if out.Values[i] == graphql.Null {
-				out.Invalids++
+				atomic.AddUint32(&out.Invalids, 1)
 			}
 		case "title":
 			out.Values[i] = ec._Listing_title(ctx, field, obj)
 			if out.Values[i] == graphql.Null {
-				out.Invalids++
+				atomic.AddUint32(&out.Invalids, 1)
 			}
 		case "slug":
 			out.Values[i] = ec._Listing_slug(ctx, field, obj)
 			if out.Values[i] == graphql.Null {
-				out.Invalids++
+				atomic.AddUint32(&out.Invalids, 1)
 			}
 		case "image":
 			out.Values[i] = ec._Listing_image(ctx, field, obj)
 			if out.Values[i] == graphql.Null {
-				out.Invalids++
+				atomic.AddUint32(&out.Invalids, 1)
 			}
+		case "imageUrl":
+			field := field
+
+			innerFunc := func(ctx context.Context, _ *graphql.FieldSet) (res graphql.Marshaler) {
+				defer func() {
+					if r := recover(); r != nil {
+						ec.Error(ctx, ec.Recover(ctx, r))
+					}
+				}()
+				res = ec._Listing_imageUrl(ctx, field, obj)
+				return res
+			}
+
+			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 "metadata":
 			out.Values[i] = ec._Listing_metadata(ctx, field, obj)
 			if out.Values[i] == graphql.Null {
-				out.Invalids++
+				atomic.AddUint32(&out.Invalids, 1)
 			}
 		case "isActive":
 			out.Values[i] = ec._Listing_isActive(ctx, field, obj)
 			if out.Values[i] == graphql.Null {
-				out.Invalids++
+				atomic.AddUint32(&out.Invalids, 1)
 			}
 		case "isDefault":
 			out.Values[i] = ec._Listing_isDefault(ctx, field, obj)
 			if out.Values[i] == graphql.Null {
-				out.Invalids++
+				atomic.AddUint32(&out.Invalids, 1)
 			}
 		case "tags":
 			out.Values[i] = ec._Listing_tags(ctx, field, obj)
 			if out.Values[i] == graphql.Null {
-				out.Invalids++
+				atomic.AddUint32(&out.Invalids, 1)
 			}
 		case "userId":
 			out.Values[i] = ec._Listing_userId(ctx, field, obj)
 			if out.Values[i] == graphql.Null {
-				out.Invalids++
+				atomic.AddUint32(&out.Invalids, 1)
 			}
 		case "orgId":
 			out.Values[i] = ec._Listing_orgId(ctx, field, obj)
 			if out.Values[i] == graphql.Null {
-				out.Invalids++
+				atomic.AddUint32(&out.Invalids, 1)
 			}
 		case "domainId":
 			out.Values[i] = ec._Listing_domainId(ctx, field, obj)
 			if out.Values[i] == graphql.Null {
-				out.Invalids++
+				atomic.AddUint32(&out.Invalids, 1)
 			}
 		case "lookupName":
 			out.Values[i] = ec._Listing_lookupName(ctx, field, obj)
 			if out.Values[i] == graphql.Null {
-				out.Invalids++
+				atomic.AddUint32(&out.Invalids, 1)
 			}
 		case "createdOn":
 			out.Values[i] = ec._Listing_createdOn(ctx, field, obj)
 			if out.Values[i] == graphql.Null {
-				out.Invalids++
+				atomic.AddUint32(&out.Invalids, 1)
 			}
 		case "updatedOn":
 			out.Values[i] = ec._Listing_updatedOn(ctx, field, obj)
 			if out.Values[i] == graphql.Null {
-				out.Invalids++
+				atomic.AddUint32(&out.Invalids, 1)
 			}
 		default:
 			panic("unknown field " + strconv.Quote(field.Name))
@@ -28597,6 +28811,8 @@ func (ec *executionContext) _Organization(ctx context.Context, sel ast.Selection
 				atomic.AddUint32(&out.Invalids, 1)
 			}
 		case "image":
+			out.Values[i] = ec._Organization_image(ctx, field, obj)
+		case "imageUrl":
 			field := field
 
 			innerFunc := func(ctx context.Context, _ *graphql.FieldSet) (res graphql.Marshaler) {
@@ -28605,7 +28821,7 @@ func (ec *executionContext) _Organization(ctx context.Context, sel ast.Selection
 						ec.Error(ctx, ec.Recover(ctx, r))
 					}
 				}()
-				res = ec._Organization_image(ctx, field, obj)
+				res = ec._Organization_imageUrl(ctx, field, obj)
 				return res
 			}
 
@@ -29169,6 +29385,39 @@ func (ec *executionContext) _QRCode(ctx context.Context, sel ast.SelectionSet, o
 			if out.Values[i] == graphql.Null {
 				atomic.AddUint32(&out.Invalids, 1)
 			}
+		case "imageUrl":
+			field := field
+
+			innerFunc := func(ctx context.Context, _ *graphql.FieldSet) (res graphql.Marshaler) {
+				defer func() {
+					if r := recover(); r != nil {
+						ec.Error(ctx, ec.Recover(ctx, r))
+					}
+				}()
+				res = ec._QRCode_imageUrl(ctx, field, obj)
+				return res
+			}
+
+			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 "hashId":
 			out.Values[i] = ec._QRCode_hashId(ctx, field, obj)
 			if out.Values[i] == graphql.Null {
diff --git a/api/graph/schema.graphqls b/api/graph/schema.graphqls
index afd08d5..9288fb7 100644
--- a/api/graph/schema.graphqls
+++ b/api/graph/schema.graphqls
@@ -182,7 +182,8 @@ type Organization {
     orgType: OrgType!
     name: String!
     slug: String!
-    image: Upload
+    image: String
+    imageUrl: String
     timezone: String! @access(scope: ORGS, kind: RO)
     settings: OrganizationSettings! @access(scope: ORGS, kind: RO)
     isActive: Boolean! @access(scope: ORGS, kind: RO)
@@ -295,6 +296,7 @@ type Listing {
     title: String!
     slug: String!
     image: String!
+    imageUrl: String
     metadata: Metadata!
     isActive: Boolean! @access(scope: LISTS, kind: RO)
     isDefault: Boolean! @access(scope: LISTS, kind: RO)
@@ -460,6 +462,7 @@ type QRCode {
     codeType: QRCodeType!
     url: String!
     imagePath: String!
+    imageUrl: String
     hashId: String! @access(scope: QRCODES, kind: RO)
     createdOn: Time!
     clicks: Int!
diff --git a/api/graph/schema.resolvers.go b/api/graph/schema.resolvers.go
index b49dcde..a1584ca 100644
--- a/api/graph/schema.resolvers.go
+++ b/api/graph/schema.resolvers.go
@@ -32,7 +32,6 @@ import (
 	"strings"
 	"time"
 
-	"github.com/99designs/gqlgen/graphql"
 	sq "github.com/Masterminds/squirrel"
 	"github.com/segmentio/ksuid"
 	qrcode "github.com/yeqown/go-qrcode/v2"
@@ -93,6 +92,16 @@ func (r *domainResolver) Status(ctx context.Context, obj *models.Domain) (model.
 	return model.DomainStatus(obj.Status), nil
 }
 
+// ImageURL is the resolver for the imageUrl field.
+func (r *listingResolver) ImageURL(ctx context.Context, obj *models.Listing) (*string, error) {
+	if obj.Image == "" {
+		return nil, nil
+	}
+	srv := server.ForContext(ctx)
+	fullURL, _ := url.JoinPath(srv.Config.MediaURL, obj.Image)
+	return &fullURL, nil
+}
+
 // AddOrganization is the resolver for the addOrganization field.
 func (r *mutationResolver) AddOrganization(ctx context.Context, input model.OrganizationInput) (*models.Organization, error) {
 	tokenUser := oauth2.ForContext(ctx)
@@ -4987,9 +4996,14 @@ func (r *organizationResolver) OrgType(ctx context.Context, obj *models.Organiza
 	return model.OrgType(obj.OrgType), nil
 }
 
-// Image is the resolver for the image field.
-func (r *organizationResolver) Image(ctx context.Context, obj *models.Organization) (*graphql.Upload, error) {
-	panic(fmt.Errorf("not implemented: Image - image"))
+// ImageURL is the resolver for the imageUrl field.
+func (r *organizationResolver) ImageURL(ctx context.Context, obj *models.Organization) (*string, error) {
+	if obj.Image == "" {
+		return nil, nil
+	}
+	srv := server.ForContext(ctx)
+	fullURL, _ := url.JoinPath(srv.Config.MediaURL, obj.Image)
+	return &fullURL, nil
 }
 
 // Visibility is the resolver for the visibility field.
@@ -5007,11 +5021,21 @@ func (r *qRCodeResolver) CodeType(ctx context.Context, obj *models.QRCode) (mode
 	return model.QRCodeType(obj.CodeType), nil
 }
 
+// ImageURL is the resolver for the imageUrl field.
+func (r *qRCodeResolver) ImageURL(ctx context.Context, obj *models.QRCode) (*string, error) {
+	if obj.ImagePath == "" {
+		return nil, nil
+	}
+	srv := server.ForContext(ctx)
+	fullURL, _ := url.JoinPath(srv.Config.MediaURL, obj.ImagePath)
+	return &fullURL, nil
+}
+
 // Version is the resolver for the version field.
 func (r *queryResolver) Version(ctx context.Context) (*model.Version, error) {
 	return &model.Version{
 		Major:           0,
-		Minor:           8,
+		Minor:           9,
 		Patch:           0,
 		DeprecationDate: nil,
 	}, nil
@@ -5026,6 +5050,9 @@ func (r *queryResolver) Me(ctx context.Context) (*models.User, error) {
 
 // GetOrganizations is the resolver for the getOrganizations field.
 func (r *queryResolver) GetOrganizations(ctx context.Context, input *model.GetOrganizationsInput) ([]*models.Organization, error) {
+	if input == nil {
+		input = &model.GetOrganizationsInput{}
+	}
 	tokenUser := oauth2.ForContext(ctx)
 	if tokenUser == nil {
 		return nil, valid.ErrAuthorization
@@ -7391,6 +7418,9 @@ func (r *Resolver) BillingSettings() BillingSettingsResolver { return &billingSe
 // Domain returns DomainResolver implementation.
 func (r *Resolver) Domain() DomainResolver { return &domainResolver{r} }
 
+// Listing returns ListingResolver implementation.
+func (r *Resolver) Listing() ListingResolver { return &listingResolver{r} }
+
 // Mutation returns MutationResolver implementation.
 func (r *Resolver) Mutation() MutationResolver { return &mutationResolver{r} }
 
@@ -7418,6 +7448,7 @@ type auditLogResolver struct{ *Resolver }
 type baseURLResolver struct{ *Resolver }
 type billingSettingsResolver struct{ *Resolver }
 type domainResolver struct{ *Resolver }
+type listingResolver struct{ *Resolver }
 type mutationResolver struct{ *Resolver }
 type orgLinkResolver struct{ *Resolver }
 type organizationResolver struct{ *Resolver }
-- 
2.49.1

