Skip to content

Commit c489588

Browse files
committed
Move webmentions fetch to an Astro Action and fetch client-side instead of at build time
1 parent cc99e01 commit c489588

20 files changed

Lines changed: 1213 additions & 1204 deletions

File tree

‎.vscode/settings.json‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"AACN",
1111
"Abbrs",
1212
"Acronis",
13+
"addrs",
1314
"adtech",
1415
"agentic",
1516
"AIMD",
@@ -133,6 +134,7 @@
133134
"HMGET",
134135
"HMSET",
135136
"hocho",
137+
"Hrana",
136138
"HSTS",
137139
"htmlcsstoimage",
138140
"httpchk",
@@ -402,6 +404,7 @@
402404
"uppy",
403405
"upserts",
404406
"upstreamsvc",
407+
"urandom",
405408
"uuidv",
406409
"valyala",
407410
"VCALENDAR",

‎HUBSPOT.md‎

Lines changed: 0 additions & 477 deletions
This file was deleted.

‎TAGS.md‎

Lines changed: 0 additions & 11 deletions
This file was deleted.

‎_TODO.md‎

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -202,13 +202,8 @@ All code samples in this article are licensed under the MIT License. Feel free t
202202
- Re-enable link validator in `astro.config.ts` when pdf / downloads sorted out
203203

204204
- Uppy, Tus server, whatever other server needed for file upload on Contact Form component
205-
206-
- There's a pretty long delay when you push the Content Switcher to go from short to deep dive, what's causing it? It should be fast - maybe it's a prefetch issue and should prefetch on page load
207-
208205
- Contact page Uppy file upload not displaying. Submit button is huge on Contact page.
209206

210-
- Hero animation not loading on home page.
211-
212207
- Improve `<abbr>` styling: https://codepen.io/ire/pen/NoqWpm
213208

214209
## Header - "Squish" Effect
@@ -223,15 +218,4 @@ All code samples in this article are licensed under the MIT License. Feel free t
223218

224219
- Home page reorganization: move the "What I Deliver" box from the Hero into the Backstage image. Move the Backstage image / video to the hero.
225220

226-
- We need to check for short form and deep article articles where the deep-dive index.pdf has a non-featured tag like "argo-cd" only in the pdf.mdx. In those cases, we should make sure the callout for the deep dive includes the name of that non-featured (technology) tag
227-
228221
- Add a "Preview Special" item to our Download CTA that lets the user know the Deep Dive content can be previewed in HTML format, and offer a switch to it.
229-
230-
- Tags should break more evenly across two lines when there's a lot of them, instead of forcing the author name and date to break across two lines: platform-engineering-metrics-lead-time-developer-friction/index.mdx
231-
232-
17:15:36 [200] /articles/kubernetes-pod-disruption-budget-autoscaler-node-rotation 414ms
233-
[WebMentions] Failed to fetch mentions for https://www.webstackbuilders.com/articles/kubernetes-pod-disruption-budget-autoscaler-node-rotation. Will retry in 60s. DOMException [TimeoutError]: The operation was aborted due to timeout
234-
at node:internal/deps/undici/undici:15445:13
235-
at async requestWebmentions (/home/kevin/Repos/WebstackBuilders/CorporateWebsite/astro.webstackbuilders.com/src/components/WebMentions/server/index.ts:199:20)
236-
at async eval (/home/kevin/Repos/WebstackBuilders/CorporateWebsite/astro.webstackbuilders.com/src/components/WebMentions/index.astro:27:18)
237-
17:15:46 [200] /_vtbot_inspection_chamber.js 1ms
File renamed without changes.

‎package-lock.json‎

Lines changed: 17 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,6 @@
238238
"zod": "4.3.6"
239239
},
240240
"optionalDependencies": {
241-
"@rollup/rollup-linux-x64-gnu": "^4.59.0"
241+
"@rollup/rollup-linux-x64-gnu": "^4.60.0"
242242
}
243243
}

‎src/actions/index.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ import { downloads } from './downloads/action'
33
import { gdpr } from './gdpr/action'
44
import { newsletter } from './newsletter/action'
55
import { search } from './search/action'
6+
import { webmentions } from './webmentions/action'
67

78
export const server = {
89
contact,
910
downloads,
1011
gdpr,
1112
newsletter,
1213
search,
14+
webmentions,
1315
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
export interface WebmentionDisplayItem {
2+
authorName: string
3+
authorUrl: string
4+
avatarUrl: string
5+
contentHtml: string
6+
id: string
7+
published: string
8+
sourceUrl: string
9+
}
10+
11+
export interface WebmentionsListResult {
12+
likesCount: number
13+
mentions: WebmentionDisplayItem[]
14+
repostsCount: number
15+
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import { beforeEach, describe, expect, it, vi } from 'vitest'
2+
3+
type MockedAction<Input, Output> = {
4+
handler: (input: Input) => Promise<Output>
5+
}
6+
7+
const throwActionErrorMock = vi.fn()
8+
9+
vi.mock('astro:actions', () => ({
10+
defineAction: (config: unknown) => config,
11+
}))
12+
13+
vi.mock('@actions/utils/errors', () => ({
14+
throwActionError: throwActionErrorMock,
15+
}))
16+
17+
vi.mock('@components/WebMentions/server', () => ({
18+
fetchWebmentions: vi.fn(),
19+
}))
20+
21+
describe('webmentions actions', () => {
22+
beforeEach(() => {
23+
throwActionErrorMock.mockReset()
24+
vi.resetModules()
25+
})
26+
27+
it('returns normalized mention display data and interaction counts', async () => {
28+
const { fetchWebmentions } = await import('@components/WebMentions/server')
29+
const fetchWebmentionsMock = vi.mocked(fetchWebmentions)
30+
31+
fetchWebmentionsMock.mockResolvedValue([
32+
{
33+
'wm-id': 'mention-1',
34+
'wm-target': 'https://example.com/post',
35+
'wm-source': 'https://elsewhere.example.com/1',
36+
'wm-property': 'mention-of',
37+
published: '2024-01-01T00:00:00.000Z',
38+
author: {
39+
name: 'Alice',
40+
photo: 'https://elsewhere.example.com/alice.jpg',
41+
url: 'https://elsewhere.example.com/alice',
42+
},
43+
content: {
44+
value: '<p>Nice post</p>',
45+
},
46+
},
47+
{
48+
'wm-id': 'like-1',
49+
'wm-target': 'https://example.com/post',
50+
'wm-source': 'https://elsewhere.example.com/2',
51+
'wm-property': 'like-of',
52+
published: '2024-01-02T00:00:00.000Z',
53+
author: {
54+
name: 'Bob',
55+
url: 'https://elsewhere.example.com/bob',
56+
},
57+
},
58+
{
59+
'wm-id': 'repost-1',
60+
'wm-target': 'https://example.com/post',
61+
'wm-source': 'https://elsewhere.example.com/3',
62+
'wm-property': 'repost-of',
63+
published: '2024-01-03T00:00:00.000Z',
64+
author: {
65+
name: 'Charlie',
66+
url: 'https://elsewhere.example.com/charlie',
67+
},
68+
},
69+
])
70+
71+
const { webmentions } = await import('../action')
72+
const listAction = webmentions.list as unknown as MockedAction<
73+
{ url: string },
74+
{
75+
likesCount: number
76+
mentions: Array<{
77+
authorName: string
78+
authorUrl: string
79+
avatarUrl: string
80+
contentHtml: string
81+
id: string
82+
published: string
83+
sourceUrl: string
84+
}>
85+
repostsCount: number
86+
}
87+
>
88+
const result = await listAction.handler({ url: 'https://example.com/post' })
89+
90+
expect(fetchWebmentionsMock).toHaveBeenCalledWith('https://example.com/post')
91+
expect(result.likesCount).toBe(1)
92+
expect(result.repostsCount).toBe(1)
93+
expect(result.mentions).toHaveLength(1)
94+
expect(result.mentions[0]).toMatchObject({
95+
authorName: 'Alice',
96+
authorUrl: 'https://elsewhere.example.com/alice',
97+
avatarUrl: 'https://elsewhere.example.com/alice.jpg',
98+
contentHtml: '<p>Nice post</p>',
99+
id: 'mention-1',
100+
published: '2024-01-01T00:00:00.000Z',
101+
sourceUrl: 'https://elsewhere.example.com/1',
102+
})
103+
})
104+
105+
it('reports unexpected handler failures through the shared action error helper', async () => {
106+
const failure = new Error('Unexpected parse failure')
107+
const { fetchWebmentions } = await import('@components/WebMentions/server')
108+
vi.mocked(fetchWebmentions).mockRejectedValue(failure)
109+
throwActionErrorMock.mockImplementation(() => {
110+
throw failure
111+
})
112+
113+
const { webmentions } = await import('../action')
114+
const listAction = webmentions.list as unknown as MockedAction<{ url: string }, unknown>
115+
116+
await expect(listAction.handler({ url: 'https://example.com/post' })).rejects.toThrow(failure)
117+
expect(throwActionErrorMock).toHaveBeenCalledWith(
118+
failure,
119+
{ route: '/_actions/webmentions/list', operation: 'list' },
120+
{ fallbackMessage: 'Webmentions could not be loaded.' }
121+
)
122+
})
123+
})

0 commit comments

Comments
 (0)