also looking at this
feat: crosspost to LinkedIn and Instagram
#1
31 files
+2335
-87
| @@ -0,0 +1,52 @@ | ||
| 1 | import type { SocialProvider } from '../../Support/Social/types' | |
| 2 | import type { RequestInstance } from '@stacksjs/types' | |
| 3 | import { Action } from '@stacksjs/actions' | |
| 4 | import { response } from '@stacksjs/router' | |
| 5 | import { crosspost, crosspostProviders } from '../../Services/Social/CrosspostService' | |
| 6 | ||
| 7 | export 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 | }) | |