ReviewOS

also looking at this

stacks/postline

feat: crosspost to LinkedIn and Instagram

#1
Merged chris wants to merge feat/crosspost-linkedin-instagram into main
31 files +2335 -87
app/Actions/Postline/CrosspostPublishAction.tsadded+52-0
Changes to app/Actions/Postline/CrosspostPublishAction.ts
@@ -0,0 +1,52 @@
1import type { SocialProvider } from '../../Support/Social/types'
2import type { RequestInstance } from '@stacksjs/types'
3import { Action } from '@stacksjs/actions'
4import { response } from '@stacksjs/router'
5import { crosspost, crosspostProviders } from '../../Services/Social/CrosspostService'
6
7export default new Action({
8 name: 'Postline Crosspost Publish',
9 description: 'Publish a post to one or more connected providers at once.',
10 method: 'POST',
11
12 async handle(request: RequestInstance) {
13 const text = String(request.get('text') || request.get('body') || '').trim()
14
15 const available = new Set<string>(crosspostProviders())
16 const providers = String(request.get('providers') || request.get('provider') || '')
17 .split(',')
18 .map(value => value.trim().toLowerCase())
19 .filter(value => available.has(value)) as SocialProvider[]
20
21 const externalUri = String(request.get('external_uri') || '').trim()
22 const externalTitle = String(request.get('external_title') || '').trim()
23 const externalDescription = String(request.get('external_description') || '').trim()
24 const external = externalUri && externalTitle
25 ? {
26 uri: externalUri,
27 title: externalTitle,
28 description: externalDescription || undefined,
29 }
30 : undefined
31
32 const imageUrl = String(request.get('image_url') || '').trim()
33 const imageAlt = String(request.get('image_alt') || '').trim()
34 const media = imageUrl
35 ? [{ url: imageUrl, altText: imageAlt || undefined }]
36 : undefined
37
38 try {
39 const data = await crosspost.publish(text, providers, { external, media })
40 // Surface a partial-failure (some targets failed) as a 207-style payload
41 // while still returning 200 so the client can report per-provider state.
42 const allFailed = data.results.length > 0 && data.results.every(result => !result.ok)
43 return response.json({ ok: !allFailed, data }, { status: allFailed ? 422 : 200 })
44 }
45 catch (error) {
46 return response.json({
47 ok: false,
48 error: error instanceof Error ? error.message : String(error),
49 }, { status: 422 })
50 }
51 },
52})