Skip to content

feat: add endpoint-driven Alby Blog widget#2173

Draft
stackingsaunter wants to merge 5 commits intomasterfrom
feat/alby-blog-endpoint-widget
Draft

feat: add endpoint-driven Alby Blog widget#2173
stackingsaunter wants to merge 5 commits intomasterfrom
feat/alby-blog-endpoint-widget

Conversation

@stackingsaunter
Copy link
Copy Markdown
Member

@stackingsaunter stackingsaunter commented Mar 25, 2026

Summary

  • add a new Home AlbyBlogWidget driven by a future getalby.com blog endpoint
  • parse a flexible endpoint payload (array, { posts: [] }, or single object) and render the latest valid post
  • place the blog card above Stats for nerds in the right column
  • document optional env override in frontend/.env.local.example

Test plan

  • Run cd frontend && yarn tsc:compile
  • Verify Home layout placement (blog card above stats widget)
  • Verify widget hides cleanly when endpoint is unavailable or payload is invalid

getalby.com endpoint requirements

Endpoint: GET https://getalby.com/api/hub/blog/latest

The widget accepts any of:

  • BlogPost[]
  • { posts: BlogPost[] }
  • BlogPost

Required per post:

  • id or slug
  • title
  • one of lead / description / excerpt
  • url or link

Optional per post:

  • imageUrl (or image_url / coverImage / cover_image)
  • publishedAt (or published_at) for latest-post sorting

CORS: allow Hub frontend origins (dev and production) for GET.
No auth or secrets in the URL; response should be public-safe metadata only.

Made with Cursor

Summary by CodeRabbit

  • New Features

    • Added an Alby Blog widget to the Home page that surfaces the latest blog post with image/placeholder, title, description, and an external "read" link.
  • Chores

    • Updated development content-security policy for image sources.
    • Added an optional environment example entry for the Alby blog endpoint and fixed trailing-newline formatting.

Render Home blog content from a getalby.com endpoint.
Place the widget under Recently Used Apps in the right column.

Made-with: Cursor
Place the Alby Blog widget directly above Stats for nerds in the Home right column.

Made-with: Cursor
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 25, 2026

📝 Walkthrough

Walkthrough

Adds a new Alby Blog widget to Home that fetches the latest blog post via a new API route, adds backend service logic to retrieve and normalize the post from Alby, updates CSP img-src to include framerusercontent, and exposes an optional frontend env var for the Alby blog endpoint.

Changes

Cohort / File(s) Summary
Frontend: Blog widget & integration
frontend/src/components/home/widgets/AlbyBlogWidget.tsx, frontend/src/screens/Home.tsx
New AlbyBlogWidget React component using useAlbyBlog() to render latest post (image or gradient fallback) and an external link; widget added to Home right column before "Stats for nerds".
Frontend: Hook
frontend/src/hooks/useAlbyBlog.ts
New SWR hook useAlbyBlog to fetch /api/alby/blog/latest with 5-minute dedupe interval and typed BlogPost shape.
Frontend: Env example
frontend/.env.local.example
Added commented VITE_ALBY_BLOG_ENDPOINT example and fixed trailing newline formatting.
Frontend: Vite CSP
frontend/vite.config.ts
Development-injected CSP updated: removed https://getalby.com from img-src, added https://framerusercontent.com.
Backend: HTTP route & Wails handler
http/alby_http_service.go, wails/wails_handlers.go
New GET route /api/alby/blog/latest and corresponding Wails route handler returning latest blog post JSON or 500 on error.
Backend: Alby service & models
alby/alby_service.go, alby/models.go
Added GetLatestBlogPost(ctx) method on albyService, 10s timeout HTTP call to ${albyInternalAPIURL}/hub/blog/latest, JSON parsing, & normalization; added BlogPost type and interface method.
Backend: CSP middleware
http/http_service.go
Updated CSP middleware img-src: removed https://getalby.com, added https://framerusercontent.com.
Tests: Mocks
tests/mocks/AlbyService.go
Added mock implementation and expecter helpers for GetLatestBlogPost(ctx) to support tests.

Sequence Diagram

sequenceDiagram
    participant Widget as AlbyBlogWidget
    participant FrontendAPI as Frontend (/api/alby/blog/latest)
    participant AlbySvc as AlbyService
    participant AlbyExternal as Alby Internal API

    Widget->>FrontendAPI: GET /api/alby/blog/latest
    FrontendAPI->>AlbySvc: GetLatestBlogPost(ctx)
    AlbySvc->>AlbyExternal: GET /hub/blog/latest (10s timeout)
    AlbyExternal-->>AlbySvc: JSON response (post)
    AlbySvc->>FrontendAPI: *BlogPost* (normalized)
    FrontendAPI-->>Widget: 200 OK with post JSON
    alt post present
        Widget->>Widget: derive theme from post.id, render image or gradient, title, description, external link
    else no post / error
        Widget-->>Widget: render nothing (null)
    end
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested Reviewers

  • reneaaron

Poem

🐰 I hopped to fetch the freshest feed,
A blog from Alby, swift indeed,
Gradients hum and images gleam,
Framer-hosted pics join the theme,
Hooray — the little widget's freed!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title clearly summarizes the main change: adding a new Alby Blog widget component to the Home page that fetches content from an endpoint.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/alby-blog-endpoint-widget

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

stackingsaunter and others added 2 commits March 26, 2026 16:34
Allow framerusercontent images in CSP.
Normalize HTML-escaped image query params so thumbnails render.

Made-with: Cursor
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
frontend/src/components/home/widgets/AlbyBlogWidget.tsx (2)

140-144: Consider adding loading="lazy" to the image.

Since this widget may be below the fold, lazy loading the image can improve initial page load performance.

🔧 Proposed fix
           <img
             src={post.imageUrl}
             alt={post.title}
+            loading="lazy"
             className="absolute inset-0 size-full object-cover"
           />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/home/widgets/AlbyBlogWidget.tsx` around lines 140 -
144, The <img> in the AlbyBlogWidget component is missing lazy loading; update
the JSX inside AlbyBlogWidget (the <img> using post.imageUrl and
alt={post.title}) to include loading="lazy" so the image is deferred until
needed (optionally also consider decoding="async" or adding fetchpriority when
appropriate).

84-100: Consider using SWR for fetching blog posts.

The component uses raw fetch() with useEffect for data fetching. Per coding guidelines, SWR should be used for server state management. SWR would provide automatic caching, deduplication, and revalidation.

That said, since this fetches from an external endpoint (not the Hub API), the request() helper may not apply directly. The current implementation is functional and handles errors gracefully.

♻️ Example SWR refactor
+import useSWR from "swr";
+
+const fetcher = async (url: string): Promise<BlogPost | null> => {
+  const response = await fetch(url);
+  if (!response.ok) return null;
+  const payload = (await response.json()) as unknown;
+  const candidates = Array.isArray(payload)
+    ? payload
+    : Array.isArray((payload as { posts?: unknown[] })?.posts)
+      ? (payload as { posts: unknown[] }).posts
+      : [payload];
+  const posts = candidates
+    .map(normalizePost)
+    .filter((post): post is BlogPost => !!post);
+  return posts.length > 0 ? pickLatestPost(posts) : null;
+};
+
 export function AlbyBlogWidget() {
-  const [post, setPost] = React.useState<BlogPost | null>(null);
-  const [themeClassName, setThemeClassName] = React.useState(fallbackThemes[0]);
-
-  React.useEffect(() => {
-    const loadPost = async () => {
-      try {
-        const posts = await fetchBlogPosts();
-        if (!posts.length) {
-          setPost(null);
-          return;
-        }
-        const latest = pickLatestPost(posts);
-        setPost(latest);
-        const themeIndex = Math.abs(
-          [...latest.id].reduce((sum, ch) => sum + ch.charCodeAt(0), 0)
-        );
-        setThemeClassName(fallbackThemes[themeIndex % fallbackThemes.length]);
-      } catch {
-        setPost(null);
-      }
-    };
-
-    void loadPost();
-  }, []);
+  const { data: post } = useSWR(ALBY_BLOG_ENDPOINT, fetcher);
+
+  const themeClassName = React.useMemo(() => {
+    if (!post) return fallbackThemes[0];
+    const themeIndex = Math.abs(
+      [...post.id].reduce((sum, ch) => sum + ch.charCodeAt(0), 0)
+    );
+    return fallbackThemes[themeIndex % fallbackThemes.length];
+  }, [post]);

As per coding guidelines: Use SWR for server state management.

Also applies to: 102-126

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/home/widgets/AlbyBlogWidget.tsx` around lines 84 -
100, The fetchBlogPosts function uses raw fetch() and should be refactored to
use SWR for server-state management; replace direct calls to ALBY_BLOG_ENDPOINT
inside fetchBlogPosts with an SWR data fetcher hooked into the component (useSWR
key = ALBY_BLOG_ENDPOINT) and move the existing response/error handling and
payload normalization (normalizePost, BlogPost) into the SWR fetcher function so
the component consumes data from useSWR (with loading/error states) enabling
caching, deduplication, and revalidation.
frontend/.env.local.example (1)

5-5: Add a trailing newline.

The file is missing a trailing newline at the end. Some tools and editors expect files to end with a newline.

🔧 Proposed fix
 # optional blog endpoint used by Home Alby Blog widget
 `#VITE_ALBY_BLOG_ENDPOINT`=https://getalby.com/api/hub/blog/latest
+
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/.env.local.example` at line 5, Add a trailing newline to the end of
the file so the final line
("#VITE_ALBY_BLOG_ENDPOINT=https://getalby.com/api/hub/blog/latest") ends with a
newline character; simply ensure the file terminates with a newline (LF) so
editors and tools recognize the last line correctly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@frontend/.env.local.example`:
- Line 5: Add a trailing newline to the end of the file so the final line
("#VITE_ALBY_BLOG_ENDPOINT=https://getalby.com/api/hub/blog/latest") ends with a
newline character; simply ensure the file terminates with a newline (LF) so
editors and tools recognize the last line correctly.

In `@frontend/src/components/home/widgets/AlbyBlogWidget.tsx`:
- Around line 140-144: The <img> in the AlbyBlogWidget component is missing lazy
loading; update the JSX inside AlbyBlogWidget (the <img> using post.imageUrl and
alt={post.title}) to include loading="lazy" so the image is deferred until
needed (optionally also consider decoding="async" or adding fetchpriority when
appropriate).
- Around line 84-100: The fetchBlogPosts function uses raw fetch() and should be
refactored to use SWR for server-state management; replace direct calls to
ALBY_BLOG_ENDPOINT inside fetchBlogPosts with an SWR data fetcher hooked into
the component (useSWR key = ALBY_BLOG_ENDPOINT) and move the existing
response/error handling and payload normalization (normalizePost, BlogPost) into
the SWR fetcher function so the component consumes data from useSWR (with
loading/error states) enabling caching, deduplication, and revalidation.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9fa94d79-505c-4a15-a35a-da92e8edd5d5

📥 Commits

Reviewing files that changed from the base of the PR and between 56a8df5 and d88c40e.

📒 Files selected for processing (5)
  • frontend/.env.local.example
  • frontend/src/components/home/widgets/AlbyBlogWidget.tsx
  • frontend/src/screens/Home.tsx
  • frontend/vite.config.ts
  • http/http_service.go

Copy link
Copy Markdown
Member

@pavanjoshi914 pavanjoshi914 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there are few issues with how this feature is implemented added comments here

publishedAt?: string;
};

const ALBY_BLOG_ENDPOINT =
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we shouldn't do fetching in the view itself. its not consistent. we use swr for api requests and api requests shall be done on go backend and repsonse should be passed on frontend


const ALBY_BLOG_ENDPOINT =
import.meta.env.VITE_ALBY_BLOG_ENDPOINT ||
"https://getalby.com/api/hub/blog/latest";
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it uses this single api endpoint. then where this api/hub/blog/feed is used. i see it added in other pr

@@ -1,2 +1,5 @@
# set a custom messageboard wallet (should be a sub-wallet with only make invoice and list transactions permissions)
#VITE_LIGHTNING_MESSAGEBOARD_NWC_URL="nostr+walletconnect://5f8e7c098137ccca853327be44a9b2e956cf79a8e2336e27a4f27b3fb55325b6?relay=wss://relay.getalby.com&relay=wss://relay2.getalby.com&secret=ace5c4b9e08138a2ef91b4ccf1379952c77c651866b29f5872b5165134417894" No newline at end of file
#VITE_LIGHTNING_MESSAGEBOARD_NWC_URL="nostr+walletconnect://5f8e7c098137ccca853327be44a9b2e956cf79a8e2336e27a4f27b3fb55325b6?relay=wss://relay.getalby.com&relay=wss://relay2.getalby.com&secret=ace5c4b9e08138a2ef91b4ccf1379952c77c651866b29f5872b5165134417894"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need of env variables once we shift api fetching to backend. we have alby internal api endpoints already defined in backend

…nd wails handlers

Add BlogPost type and GetLatestBlogPost to alby service interface
Replace direct frontend fetch with useAlbyBlog SWR hook for consistent data fetching
Simplify AlbyBlogWidget to match codebase widget patterns
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@alby/alby_service.go`:
- Around line 246-262: The decoded temporary struct "post" may miss upstream
variants (slug, link, description, excerpt, image) and the code only validates
Title and URL; update the normalization and validation after json.Unmarshal in
alby_service.go so you map fallback keys into the canonical fields (e.g., use
slug -> ID, link -> URL, description/excerpt -> Lead/description, alternate
image keys -> ImageURL) and then enforce required fields (ID, Title, URL) before
returning; if any required canonical field is still empty return an error ("no
blog post found" or similar) and log the detailed decode/normalization failure
via logger.Logger.WithError.
- Around line 219-236: In GetLatestBlogPost wrap returned errors with fmt.Errorf
to preserve error chains and add context: wherever the code currently logs and
returns err (the request creation error after
logger.Logger.WithError(err).Error("Error creating request to blog endpoint"),
the client.Do error logged as "Failed to fetch blog endpoint", and the
io.ReadAll error logged as "Failed to read response body"), return
fmt.Errorf("creating blog request: %w", err), fmt.Errorf("fetching blog
endpoint: %w", err), and fmt.Errorf("reading blog response body: %w", err)
respectively (keep logging calls but change the returned errors), ensuring
imports include fmt and references to GetLatestBlogPost,
setDefaultRequestHeaders, client.Do, and io.ReadAll to locate the spots.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e236b837-894c-41c8-bd6b-39cb9a0b2f63

📥 Commits

Reviewing files that changed from the base of the PR and between d88c40e and fb24c64.

📒 Files selected for processing (7)
  • alby/alby_service.go
  • alby/models.go
  • frontend/src/components/home/widgets/AlbyBlogWidget.tsx
  • frontend/src/hooks/useAlbyBlog.ts
  • http/alby_http_service.go
  • tests/mocks/AlbyService.go
  • wails/wails_handlers.go
✅ Files skipped from review due to trivial changes (1)
  • tests/mocks/AlbyService.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/src/components/home/widgets/AlbyBlogWidget.tsx

Comment thread alby/alby_service.go
Comment on lines +219 to +236
if err != nil {
logger.Logger.WithError(err).Error("Error creating request to blog endpoint")
return nil, err
}
setDefaultRequestHeaders(req)

res, err := client.Do(req)
if err != nil {
logger.Logger.WithError(err).Error("Failed to fetch blog endpoint")
return nil, err
}
defer res.Body.Close()

body, err := io.ReadAll(res.Body)
if err != nil {
logger.Logger.WithError(err).Error("Failed to read response body")
return nil, errors.New("failed to read response body")
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify unwrapped errors in the new method.
rg -n 'GetLatestBlogPost|return nil, err|errors.New\("failed to read response body"\)' alby/alby_service.go

Repository: getAlby/hub

Length of output: 696


🏁 Script executed:

# Read the GetLatestBlogPost method to see context
sed -n '213,270p' alby/alby_service.go

Repository: getAlby/hub

Length of output: 1763


🏁 Script executed:

# Check imports to verify fmt and errors are available
head -30 alby/alby_service.go

Repository: getAlby/hub

Length of output: 475


🏁 Script executed:

# Confirm the exact line numbers and error returns in the method
sed -n '213,265p' alby/alby_service.go | cat -n

Repository: getAlby/hub

Length of output: 1984


Wrap errors with context in GetLatestBlogPost to aid debugging.

Lines 221, 228, 235, and 257 return raw or opaque errors, losing caller context. Use fmt.Errorf("context: %w", err) to preserve error chains for diagnosis across stack layers.

Suggested error-wrapping patch
 	req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
 	if err != nil {
 		logger.Logger.WithError(err).Error("Error creating request to blog endpoint")
-		return nil, err
+		return nil, fmt.Errorf("create blog request: %w", err)
 	}
 
 	res, err := client.Do(req)
 	if err != nil {
 		logger.Logger.WithError(err).Error("Failed to fetch blog endpoint")
-		return nil, err
+		return nil, fmt.Errorf("fetch blog endpoint: %w", err)
 	}
 	defer res.Body.Close()
 
 	body, err := io.ReadAll(res.Body)
 	if err != nil {
 		logger.Logger.WithError(err).Error("Failed to read response body")
-		return nil, errors.New("failed to read response body")
+		return nil, fmt.Errorf("read blog response body: %w", err)
 	}
 
 	err = json.Unmarshal(body, &post)
 	if err != nil {
 		logger.Logger.WithError(err).Error("Failed to decode blog API response")
-		return nil, err
+		return nil, fmt.Errorf("decode blog API response: %w", err)
 	}

Per coding guidelines for Go: "Use error wrapping with fmt.Errorf("context: %w", err) for debugging".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@alby/alby_service.go` around lines 219 - 236, In GetLatestBlogPost wrap
returned errors with fmt.Errorf to preserve error chains and add context:
wherever the code currently logs and returns err (the request creation error
after logger.Logger.WithError(err).Error("Error creating request to blog
endpoint"), the client.Do error logged as "Failed to fetch blog endpoint", and
the io.ReadAll error logged as "Failed to read response body"), return
fmt.Errorf("creating blog request: %w", err), fmt.Errorf("fetching blog
endpoint: %w", err), and fmt.Errorf("reading blog response body: %w", err)
respectively (keep logging calls but change the returned errors), ensuring
imports include fmt and references to GetLatestBlogPost,
setDefaultRequestHeaders, client.Do, and io.ReadAll to locate the spots.

Comment thread alby/alby_service.go
Comment on lines +246 to +262
var post struct {
ID string `json:"id"`
Title string `json:"title"`
Lead string `json:"lead"`
URL string `json:"url"`
ImageURL string `json:"imageUrl"`
PublishedAt string `json:"publishedAt"`
}
err = json.Unmarshal(body, &post)
if err != nil {
logger.Logger.WithError(err).Error("Failed to decode blog API response")
return nil, err
}

if post.Title == "" || post.URL == "" {
return nil, errors.New("no blog post found")
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Handle fallback keys and enforce required fields before returning.

Line 246 only decodes lead/url/imageUrl, and Line 260 validates only title/url. If upstream sends slug, description/excerpt, or link, this can return an incomplete BlogPost (empty id/description) instead of treating it as invalid.

Suggested normalization + validation patch
 func (svc *albyService) GetLatestBlogPost(ctx context.Context) (*BlogPost, error) {
@@
 	var post struct {
-		ID          string `json:"id"`
-		Title       string `json:"title"`
-		Lead        string `json:"lead"`
-		URL         string `json:"url"`
-		ImageURL    string `json:"imageUrl"`
-		PublishedAt string `json:"publishedAt"`
+		ID          string `json:"id"`
+		Slug        string `json:"slug"`
+		Title       string `json:"title"`
+		Lead        string `json:"lead"`
+		Description string `json:"description"`
+		Excerpt     string `json:"excerpt"`
+		URL         string `json:"url"`
+		Link        string `json:"link"`
+		ImageURL    string `json:"imageUrl"`
+		ImageURLAlt string `json:"image_url"`
+		CoverImage  string `json:"coverImage"`
+		CoverImage2 string `json:"cover_image"`
 	}
@@
-	if post.Title == "" || post.URL == "" {
+	firstNonEmpty := func(values ...string) string {
+		for _, v := range values {
+			if v != "" {
+				return v
+			}
+		}
+		return ""
+	}
+
+	id := firstNonEmpty(post.ID, post.Slug)
+	description := firstNonEmpty(post.Lead, post.Description, post.Excerpt)
+	url := firstNonEmpty(post.URL, post.Link)
+	imageURL := firstNonEmpty(post.ImageURL, post.ImageURLAlt, post.CoverImage, post.CoverImage2)
+
+	if id == "" || post.Title == "" || description == "" || url == "" {
 		return nil, errors.New("no blog post found")
 	}
 
 	return &BlogPost{
-		ID:          post.ID,
+		ID:          id,
 		Title:       post.Title,
-		Description: post.Lead,
-		URL:         post.URL,
-		ImageURL:    strings.ReplaceAll(post.ImageURL, "&amp;", "&"),
+		Description: description,
+		URL:         url,
+		ImageURL:    strings.ReplaceAll(imageURL, "&amp;", "&"),
 	}, nil
 }

Also applies to: 264-270

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@alby/alby_service.go` around lines 246 - 262, The decoded temporary struct
"post" may miss upstream variants (slug, link, description, excerpt, image) and
the code only validates Title and URL; update the normalization and validation
after json.Unmarshal in alby_service.go so you map fallback keys into the
canonical fields (e.g., use slug -> ID, link -> URL, description/excerpt ->
Lead/description, alternate image keys -> ImageURL) and then enforce required
fields (ID, Title, URL) before returning; if any required canonical field is
still empty return an error ("no blog post found" or similar) and log the
detailed decode/normalization failure via logger.Logger.WithError.

@reneaaron reneaaron marked this pull request as draft April 14, 2026 09:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants