From c0e2411722b4d95c9e2e8ee073944ca8c8c1ff06 Mon Sep 17 00:00:00 2001 From: saunter <68239231+stackingsaunter@users.noreply.github.com> Date: Wed, 25 Mar 2026 02:36:23 +0100 Subject: [PATCH 1/4] feat: add endpoint-driven Alby Blog widget Render Home blog content from a getalby.com endpoint. Place the widget under Recently Used Apps in the right column. Made-with: Cursor --- frontend/.env.local.example | 5 +- .../home/widgets/AlbyBlogWidget.tsx | 176 ++++++++++++++++++ frontend/src/screens/Home.tsx | 2 + 3 files changed, 182 insertions(+), 1 deletion(-) create mode 100644 frontend/src/components/home/widgets/AlbyBlogWidget.tsx diff --git a/frontend/.env.local.example b/frontend/.env.local.example index 4bc19c193..bc4cf7d48 100644 --- a/frontend/.env.local.example +++ b/frontend/.env.local.example @@ -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" + +# optional blog endpoint used by Home Alby Blog widget +#VITE_ALBY_BLOG_ENDPOINT=https://getalby.com/api/hub/blog/latest \ No newline at end of file diff --git a/frontend/src/components/home/widgets/AlbyBlogWidget.tsx b/frontend/src/components/home/widgets/AlbyBlogWidget.tsx new file mode 100644 index 000000000..1f2c69576 --- /dev/null +++ b/frontend/src/components/home/widgets/AlbyBlogWidget.tsx @@ -0,0 +1,176 @@ +import { SquareArrowOutUpRightIcon } from "lucide-react"; +import React from "react"; +import { + Card, + CardContent, + CardFooter, + CardHeader, + CardTitle, +} from "src/components/ui/card"; +import { ExternalLinkButton } from "src/components/ui/custom/external-link-button"; +import { cn } from "src/lib/utils"; + +type BlogPost = { + id: string; + title: string; + description: string; + url: string; + imageUrl?: string; + publishedAt?: string; +}; + +const ALBY_BLOG_ENDPOINT = + import.meta.env.VITE_ALBY_BLOG_ENDPOINT || + "https://getalby.com/api/hub/blog/latest"; + +const fallbackThemes = [ + "from-emerald-200 via-cyan-200 to-yellow-200", + "from-orange-200 via-amber-100 to-pink-100", + "from-slate-200 via-zinc-100 to-lime-100", + "from-sky-200 via-indigo-100 to-violet-100", +]; + +function toStringValue(value: unknown): string | undefined { + return typeof value === "string" && value.trim() ? value.trim() : undefined; +} + +function normalizePost(input: unknown): BlogPost | null { + if (!input || typeof input !== "object") { + return null; + } + const item = input as Record; + const id = toStringValue(item.id) || toStringValue(item.slug) || "latest"; + const title = toStringValue(item.title); + const description = + toStringValue(item.lead) || + toStringValue(item.description) || + toStringValue(item.excerpt); + const url = toStringValue(item.url) || toStringValue(item.link); + const imageUrl = + toStringValue(item.imageUrl) || + toStringValue(item.image_url) || + toStringValue(item.coverImage) || + toStringValue(item.cover_image); + const publishedAt = + toStringValue(item.publishedAt) || toStringValue(item.published_at); + + if (!title || !url || !description) { + return null; + } + + return { + id, + title, + description, + url, + imageUrl, + publishedAt, + }; +} + +function pickLatestPost(posts: BlogPost[]): BlogPost { + const dated = posts.filter((p) => p.publishedAt); + if (dated.length > 0) { + return [...dated].sort( + (a, b) => + new Date(b.publishedAt || "").getTime() - + new Date(a.publishedAt || "").getTime() + )[0]; + } + return posts[0]; +} + +async function fetchBlogPosts(): Promise { + const response = await fetch(ALBY_BLOG_ENDPOINT); + if (!response.ok) { + throw new Error(`Failed to fetch blog posts: ${response.status}`); + } + + 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]; + + return candidates + .map(normalizePost) + .filter((post): post is BlogPost => !!post); +} + +export function AlbyBlogWidget() { + const [post, setPost] = React.useState(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(); + }, []); + + if (!post) { + return null; + } + + return ( + + + Alby Blog + + +
+ {post.imageUrl ? ( + {post.title} + ) : ( +
+
+
+
+
+ )} +
+ + +
+

+ {post.title} +

+

+ {post.description} +

+
+
+ + Read on Alby Blog + + +
+
+ + ); +} diff --git a/frontend/src/screens/Home.tsx b/frontend/src/screens/Home.tsx index e62e3480a..eff133d64 100644 --- a/frontend/src/screens/Home.tsx +++ b/frontend/src/screens/Home.tsx @@ -22,6 +22,7 @@ import React from "react"; import albyGo from "src/assets/suggested-apps/alby-go.png"; import zapplanner from "src/assets/suggested-apps/zapplanner.png"; import { AppOfTheDayWidget } from "src/components/home/widgets/AppOfTheDayWidget"; +import { AlbyBlogWidget } from "src/components/home/widgets/AlbyBlogWidget"; import { BlockHeightWidget } from "src/components/home/widgets/BlockHeightWidget"; import { ForwardsWidget } from "src/components/home/widgets/ForwardsWidget"; import { LatestUsedAppsWidget } from "src/components/home/widgets/LatestUsedAppsWidget"; @@ -162,6 +163,7 @@ function Home() { {/* RIGHT */}
+ From 70bdaf6c9f49b1fd3f94e881fd0ea4de0e80a98b Mon Sep 17 00:00:00 2001 From: saunter <68239231+stackingsaunter@users.noreply.github.com> Date: Wed, 25 Mar 2026 02:39:49 +0100 Subject: [PATCH 2/4] fix: move Alby Blog card above stats widget Place the Alby Blog widget directly above Stats for nerds in the Home right column. Made-with: Cursor --- frontend/src/screens/Home.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/screens/Home.tsx b/frontend/src/screens/Home.tsx index eff133d64..c06a2c983 100644 --- a/frontend/src/screens/Home.tsx +++ b/frontend/src/screens/Home.tsx @@ -163,7 +163,6 @@ function Home() { {/* RIGHT */}
- @@ -195,6 +194,8 @@ function Home() { + +
From 405af5390982143af44abe005fd769809315d7ef Mon Sep 17 00:00:00 2001 From: saunter <68239231+stackingsaunter@users.noreply.github.com> Date: Thu, 26 Mar 2026 16:34:23 +0100 Subject: [PATCH 3/4] fix: allow Alby Blog thumbnails from Framer CDN Allow framerusercontent images in CSP. Normalize HTML-escaped image query params so thumbnails render. Made-with: Cursor --- frontend/src/components/home/widgets/AlbyBlogWidget.tsx | 3 ++- frontend/vite.config.ts | 2 +- http/http_service.go | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/home/widgets/AlbyBlogWidget.tsx b/frontend/src/components/home/widgets/AlbyBlogWidget.tsx index 1f2c69576..c0549f1d6 100644 --- a/frontend/src/components/home/widgets/AlbyBlogWidget.tsx +++ b/frontend/src/components/home/widgets/AlbyBlogWidget.tsx @@ -46,11 +46,12 @@ function normalizePost(input: unknown): BlogPost | null { toStringValue(item.description) || toStringValue(item.excerpt); const url = toStringValue(item.url) || toStringValue(item.link); - const imageUrl = + const imageUrlValue = toStringValue(item.imageUrl) || toStringValue(item.image_url) || toStringValue(item.coverImage) || toStringValue(item.cover_image); + const imageUrl = imageUrlValue?.replace(/&/g, "&"); const publishedAt = toStringValue(item.publishedAt) || toStringValue(item.published_at); diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 42589f100..37eec3d3a 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -98,7 +98,7 @@ const insertDevCSPPlugin: Plugin = { "", ` - ` + ` ); }, }, diff --git a/http/http_service.go b/http/http_service.go index 7d798bd5d..3d292b194 100644 --- a/http/http_service.go +++ b/http/http_service.go @@ -66,7 +66,7 @@ func (httpSvc *HttpService) RegisterSharedRoutes(e *echo.Echo) { e.Use(middleware.SecureWithConfig(middleware.SecureConfig{ ContentTypeNosniff: "nosniff", XFrameOptions: "DENY", - ContentSecurityPolicy: "default-src 'self'; img-src 'self' https://uploads.getalby-assets.com https://getalby.com; connect-src 'self' https://api.getalby.com https://getalby.com https://zapplanner.albylabs.com wss://relay.getalby.com wss://relay2.getalby.com; frame-src https://embed.bitrefill.com", + ContentSecurityPolicy: "default-src 'self'; img-src 'self' https://uploads.getalby-assets.com https://getalby.com https://framerusercontent.com; connect-src 'self' https://api.getalby.com https://getalby.com https://zapplanner.albylabs.com wss://relay.getalby.com wss://relay2.getalby.com; frame-src https://embed.bitrefill.com", ReferrerPolicy: "no-referrer", })) e.Use(middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{ From fb24c641c2a90474e492601102ac94760d31dea3 Mon Sep 17 00:00:00 2001 From: pavanjoshi914 Date: Tue, 31 Mar 2026 13:59:08 +0530 Subject: [PATCH 4/4] feat: add Go backend proxy endpoint /api/alby/blog/latest with HTTP and 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 --- alby/alby_service.go | 61 ++++++++ alby/models.go | 9 ++ .../home/widgets/AlbyBlogWidget.tsx | 137 ++---------------- frontend/src/hooks/useAlbyBlog.ts | 17 +++ http/alby_http_service.go | 12 ++ tests/mocks/AlbyService.go | 61 ++++++++ wails/wails_handlers.go | 11 ++ 7 files changed, 187 insertions(+), 121 deletions(-) create mode 100644 frontend/src/hooks/useAlbyBlog.ts diff --git a/alby/alby_service.go b/alby/alby_service.go index 04e9f7edf..ba95be7f8 100644 --- a/alby/alby_service.go +++ b/alby/alby_service.go @@ -7,6 +7,7 @@ import ( "fmt" "io" "net/http" + "strings" "time" "github.com/getAlby/hub/config" @@ -208,3 +209,63 @@ func (svc *albyService) GetInfo(ctx context.Context) (*AlbyInfo, error) { Incidents: incidents, }, nil } + +func (svc *albyService) GetLatestBlogPost(ctx context.Context) (*BlogPost, error) { + client := &http.Client{Timeout: 10 * time.Second} + + url := fmt.Sprintf("%s/hub/blog/latest", albyInternalAPIURL) + + 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 + } + 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") + } + + if res.StatusCode >= 300 { + logger.Logger.WithFields(logrus.Fields{ + "body": string(body), + "status_code": res.StatusCode, + }).Error("Blog endpoint returned non-success code") + return nil, fmt.Errorf("blog endpoint returned non-success code: %s", string(body)) + } + + 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") + } + + return &BlogPost{ + ID: post.ID, + Title: post.Title, + Description: post.Lead, + URL: post.URL, + ImageURL: strings.ReplaceAll(post.ImageURL, "&", "&"), + }, nil +} diff --git a/alby/models.go b/alby/models.go index e366050aa..4f7fcccb6 100644 --- a/alby/models.go +++ b/alby/models.go @@ -11,6 +11,15 @@ type AlbyService interface { GetInfo(ctx context.Context) (*AlbyInfo, error) GetBitcoinRate(ctx context.Context) (*BitcoinRate, error) GetChannelPeerSuggestions(ctx context.Context) ([]ChannelPeerSuggestion, error) + GetLatestBlogPost(ctx context.Context) (*BlogPost, error) +} + +type BlogPost struct { + ID string `json:"id"` + Title string `json:"title"` + Description string `json:"description"` + URL string `json:"url"` + ImageURL string `json:"imageUrl,omitempty"` } type AlbyOAuthService interface { diff --git a/frontend/src/components/home/widgets/AlbyBlogWidget.tsx b/frontend/src/components/home/widgets/AlbyBlogWidget.tsx index c0549f1d6..f5f3a7f45 100644 --- a/frontend/src/components/home/widgets/AlbyBlogWidget.tsx +++ b/frontend/src/components/home/widgets/AlbyBlogWidget.tsx @@ -1,28 +1,16 @@ import { SquareArrowOutUpRightIcon } from "lucide-react"; -import React from "react"; import { Card, CardContent, + CardDescription, CardFooter, CardHeader, CardTitle, } from "src/components/ui/card"; import { ExternalLinkButton } from "src/components/ui/custom/external-link-button"; +import { useAlbyBlog } from "src/hooks/useAlbyBlog"; import { cn } from "src/lib/utils"; -type BlogPost = { - id: string; - title: string; - description: string; - url: string; - imageUrl?: string; - publishedAt?: string; -}; - -const ALBY_BLOG_ENDPOINT = - import.meta.env.VITE_ALBY_BLOG_ENDPOINT || - "https://getalby.com/api/hub/blog/latest"; - const fallbackThemes = [ "from-emerald-200 via-cyan-200 to-yellow-200", "from-orange-200 via-amber-100 to-pink-100", @@ -30,111 +18,25 @@ const fallbackThemes = [ "from-sky-200 via-indigo-100 to-violet-100", ]; -function toStringValue(value: unknown): string | undefined { - return typeof value === "string" && value.trim() ? value.trim() : undefined; -} - -function normalizePost(input: unknown): BlogPost | null { - if (!input || typeof input !== "object") { - return null; - } - const item = input as Record; - const id = toStringValue(item.id) || toStringValue(item.slug) || "latest"; - const title = toStringValue(item.title); - const description = - toStringValue(item.lead) || - toStringValue(item.description) || - toStringValue(item.excerpt); - const url = toStringValue(item.url) || toStringValue(item.link); - const imageUrlValue = - toStringValue(item.imageUrl) || - toStringValue(item.image_url) || - toStringValue(item.coverImage) || - toStringValue(item.cover_image); - const imageUrl = imageUrlValue?.replace(/&/g, "&"); - const publishedAt = - toStringValue(item.publishedAt) || toStringValue(item.published_at); - - if (!title || !url || !description) { - return null; - } - - return { - id, - title, - description, - url, - imageUrl, - publishedAt, - }; -} - -function pickLatestPost(posts: BlogPost[]): BlogPost { - const dated = posts.filter((p) => p.publishedAt); - if (dated.length > 0) { - return [...dated].sort( - (a, b) => - new Date(b.publishedAt || "").getTime() - - new Date(a.publishedAt || "").getTime() - )[0]; - } - return posts[0]; -} - -async function fetchBlogPosts(): Promise { - const response = await fetch(ALBY_BLOG_ENDPOINT); - if (!response.ok) { - throw new Error(`Failed to fetch blog posts: ${response.status}`); - } - - 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]; - - return candidates - .map(normalizePost) - .filter((post): post is BlogPost => !!post); +function hashString(str: string): number { + return Math.abs([...str].reduce((sum, ch) => sum + ch.charCodeAt(0), 0)); } export function AlbyBlogWidget() { - const [post, setPost] = React.useState(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 } = useAlbyBlog(); if (!post) { return null; } + const theme = fallbackThemes[hashString(post.id) % fallbackThemes.length]; + return ( - - - Alby Blog + + + Alby Blog - +
{post.imageUrl ? ( ) : ( -
+
@@ -156,14 +53,12 @@ export function AlbyBlogWidget() { )}
- +
-

- {post.title} -

-

+ {post.title} + {post.description} -

+
diff --git a/frontend/src/hooks/useAlbyBlog.ts b/frontend/src/hooks/useAlbyBlog.ts new file mode 100644 index 000000000..7fbac7570 --- /dev/null +++ b/frontend/src/hooks/useAlbyBlog.ts @@ -0,0 +1,17 @@ +import useSWR from "swr"; + +import { swrFetcher } from "src/utils/swr"; + +type BlogPost = { + id: string; + title: string; + description: string; + url: string; + imageUrl?: string; +}; + +export function useAlbyBlog() { + return useSWR("/api/alby/blog/latest", swrFetcher, { + dedupingInterval: 5 * 60 * 1000, // 5 minutes + }); +} diff --git a/http/alby_http_service.go b/http/alby_http_service.go index 1c88adebc..8dd24f17a 100644 --- a/http/alby_http_service.go +++ b/http/alby_http_service.go @@ -32,6 +32,7 @@ func (albyHttpSvc *AlbyHttpService) RegisterSharedRoutes(readOnlyApiGroup *echo. e.GET("/api/alby/callback", albyHttpSvc.albyCallbackHandler) e.GET("/api/alby/info", albyHttpSvc.albyInfoHandler) e.GET("/api/alby/rates", albyHttpSvc.albyBitcoinRateHandler) + e.GET("/api/alby/blog/latest", albyHttpSvc.albyBlogLatestHandler) readOnlyApiGroup.GET("/alby/me", albyHttpSvc.albyMeHandler) fullAccessApiGroup.POST("/alby/link-account", albyHttpSvc.albyLinkAccountHandler) fullAccessApiGroup.POST("/alby/auto-channel", albyHttpSvc.autoChannelHandler) @@ -96,6 +97,17 @@ func (albyHttpSvc *AlbyHttpService) albyBitcoinRateHandler(c echo.Context) error return c.JSON(http.StatusOK, rate) } +func (albyHttpSvc *AlbyHttpService) albyBlogLatestHandler(c echo.Context) error { + post, err := albyHttpSvc.albySvc.GetLatestBlogPost(c.Request().Context()) + if err != nil { + logger.Logger.WithError(err).Error("Failed to get latest blog post") + return c.JSON(http.StatusInternalServerError, ErrorResponse{ + Message: fmt.Sprintf("Failed to get latest blog post: %s", err.Error()), + }) + } + return c.JSON(http.StatusOK, post) +} + func (albyHttpSvc *AlbyHttpService) albyCallbackHandler(c echo.Context) error { code := c.QueryParam("code") diff --git a/tests/mocks/AlbyService.go b/tests/mocks/AlbyService.go index d4a127949..94bbca8a9 100644 --- a/tests/mocks/AlbyService.go +++ b/tests/mocks/AlbyService.go @@ -162,6 +162,67 @@ func (_c *MockAlbyService_GetChannelPeerSuggestions_Call) RunAndReturn(run func( return _c } +func (_mock *MockAlbyService) GetLatestBlogPost(ctx context.Context) (*alby.BlogPost, error) { + ret := _mock.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for GetLatestBlogPost") + } + + var r0 *alby.BlogPost + var r1 error + if returnFunc, ok := ret.Get(0).(func(context.Context) (*alby.BlogPost, error)); ok { + return returnFunc(ctx) + } + if returnFunc, ok := ret.Get(0).(func(context.Context) *alby.BlogPost); ok { + r0 = returnFunc(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*alby.BlogPost) + } + } + if returnFunc, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = returnFunc(ctx) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// MockAlbyService_GetLatestBlogPost_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetLatestBlogPost' +type MockAlbyService_GetLatestBlogPost_Call struct { + *mock.Call +} + +// GetLatestBlogPost is a helper method to define mock.On call +// - ctx context.Context +func (_e *MockAlbyService_Expecter) GetLatestBlogPost(ctx interface{}) *MockAlbyService_GetLatestBlogPost_Call { + return &MockAlbyService_GetLatestBlogPost_Call{Call: _e.mock.On("GetLatestBlogPost", ctx)} +} + +func (_c *MockAlbyService_GetLatestBlogPost_Call) Run(run func(ctx context.Context)) *MockAlbyService_GetLatestBlogPost_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 context.Context + if args[0] != nil { + arg0 = args[0].(context.Context) + } + run( + arg0, + ) + }) + return _c +} + +func (_c *MockAlbyService_GetLatestBlogPost_Call) Return(blogPost *alby.BlogPost, err error) *MockAlbyService_GetLatestBlogPost_Call { + _c.Call.Return(blogPost, err) + return _c +} + +func (_c *MockAlbyService_GetLatestBlogPost_Call) RunAndReturn(run func(ctx context.Context) (*alby.BlogPost, error)) *MockAlbyService_GetLatestBlogPost_Call { + _c.Call.Return(run) + return _c +} + // GetInfo provides a mock function for the type MockAlbyService func (_mock *MockAlbyService) GetInfo(ctx context.Context) (*alby.AlbyInfo, error) { ret := _mock.Called(ctx) diff --git a/wails/wails_handlers.go b/wails/wails_handlers.go index f96697de6..8005a5062 100644 --- a/wails/wails_handlers.go +++ b/wails/wails_handlers.go @@ -409,6 +409,17 @@ func (app *WailsApp) WailsRequestRouter(route string, method string, body string return WailsRequestRouterResponse{Body: nil, Error: err.Error()} } return WailsRequestRouterResponse{Body: rate, Error: ""} + case "/api/alby/blog/latest": + post, err := app.svc.GetAlbySvc().GetLatestBlogPost(ctx) + if err != nil { + logger.Logger.WithFields(logrus.Fields{ + "route": route, + "method": method, + "body": body, + }).WithError(err).Error("Failed to get latest blog post") + return WailsRequestRouterResponse{Body: nil, Error: err.Error()} + } + return WailsRequestRouterResponse{Body: post, Error: ""} case "/api/apps": switch method { case "POST":