104 lines
4.9 KiB
Markdown
104 lines
4.9 KiB
Markdown
# postiz-poster
|
|
|
|
A Saltcorn plugin that publishes table rows to social networks through a
|
|
[Postiz](https://github.com/gitroomhq/postiz-app) instance -- self-hosted or
|
|
Postiz Cloud. Postiz handles the per-platform fan-out (Mastodon, Bluesky, X,
|
|
LinkedIn, Facebook, Instagram, Threads, Telegram, and more); this plugin just
|
|
maps a Saltcorn row into a Postiz post and calls Postiz's public API.
|
|
|
|
## How it works
|
|
|
|
- **Plugin config** holds the Postiz connection: a base URL and a public API
|
|
key. Self-hosted and cloud use the *same* `/public/v1` API, so switching
|
|
between them is only a base-URL change -- no code change.
|
|
- **One action**, `post_to_postiz`, is attached to any trigger (row Insert /
|
|
Update, or a button). When it fires it reads the post text (and an optional
|
|
media URL) from the row, optionally uploads the media to Postiz, and creates
|
|
the post against the channels you selected.
|
|
- The channel picker in the action config is **populated from the live Postiz
|
|
instance** (`GET /public/v1/integrations`), so admins choose real connected
|
|
accounts instead of pasting ids. If Postiz is unreachable at config time the
|
|
field degrades to a free-text id list.
|
|
|
|
## Layout
|
|
|
|
```
|
|
postiz/
|
|
index.js plugin entry: wires config workflow + the action
|
|
lib/
|
|
constants.js plugin name/version, Postiz endpoints, post types
|
|
postizClient.js the only module that speaks HTTP to Postiz
|
|
config.js configuration_workflow (base URL + API key)
|
|
action.js post_to_postiz: configFields, payload builder, run
|
|
secretsAtRest.js AES-256-GCM seal/open for the API key (shared helper)
|
|
test/
|
|
e2e.js payload + secrets gates, plus optional live gate
|
|
```
|
|
|
|
## Secrets at rest
|
|
|
|
The Postiz API key is configured through the normal plugin UI but is **not**
|
|
left plaintext in the database. Saltcorn stores plugin configuration unencrypted
|
|
in `_sc_plugins.configuration`, which would otherwise expose the key to DB
|
|
backups, admin reads, and dev-deploy's journal sync. Instead:
|
|
|
|
- `lib/secretsAtRest.js` seals the key with AES-256-GCM under a key derived
|
|
(HKDF-SHA256) from Saltcorn's `session_secret` -- which lives in the env /
|
|
config file, not the database. Ciphertext in the DB is useless without it.
|
|
- Because Saltcorn's config form autosaves raw values, `onLoad` re-seals any
|
|
plaintext key the moment the plugin (re)loads after a save; the action
|
|
decrypts only at the point of use.
|
|
- **Threat model:** this protects DB-only exposure (backups, sync, config-table
|
|
reads). It does not protect a host that leaks both the database and the env /
|
|
config file. **Rotating `session_secret` invalidates the sealed key** -- you
|
|
must re-enter it in the plugin config (the action raises a clear error saying
|
|
so).
|
|
- For the same reason, the sealed key is environment-specific: it will not
|
|
decrypt on a peer with a different `session_secret`, so exclude it from any
|
|
dev-deploy sync and set it per environment.
|
|
|
|
## Configuring
|
|
|
|
1. In Postiz, connect your social accounts, then create a key under
|
|
**Settings -> Developers -> Public API**.
|
|
2. In Saltcorn: **Settings -> Plugins -> postiz-poster**, set:
|
|
- **Postiz base URL** -- self-hosted `https://postiz.example.com`, or cloud
|
|
`https://api.postiz.com`.
|
|
- **Public API key**.
|
|
3. Create a trigger on the table you want to publish from, choose action
|
|
`post_to_postiz`, then pick the content field, optional media field, target
|
|
channels, and timing (now / schedule / draft).
|
|
|
|
## Testing
|
|
|
|
```
|
|
node test/e2e.js # pure payload gate only
|
|
POSTIZ_BASE_URL=http://localhost:5000 \
|
|
POSTIZ_API_KEY=xxxxxxxx node test/e2e.js # + live gate vs a real Postiz
|
|
```
|
|
|
|
The live gate lists integrations and creates a **draft** (safe to delete) using
|
|
the first connected channel. With no env vars it is skipped, not failed.
|
|
|
|
## To confirm before production
|
|
|
|
These are sketched against the documented Postiz API and isolated to one or two
|
|
spots each, but verify against [docs.postiz.com/public-api](https://docs.postiz.com/public-api),
|
|
which has changed across Postiz's weekly releases:
|
|
|
|
- **Create-post body shape** (`type` / `date` / `posts[].integration` /
|
|
`value[].content` / `value[].image`) -- see `buildPostPayload` in
|
|
`lib/action.js`.
|
|
- **Auth header format** -- bare key vs `Bearer` (`AUTH_HEADER` in
|
|
`lib/constants.js`).
|
|
- **Media upload** -- whether `/upload` accepts a URL fetch or wants multipart,
|
|
and the id/path field it returns (`uploadMedia` in `lib/postizClient.js`).
|
|
|
|
## Notes
|
|
|
|
- Postiz removes the *subscription* cost (self-hosted) but not the *gatekeeping*:
|
|
you still register your own developer apps with each walled-garden platform
|
|
(Meta App Review, LinkedIn Community Management API, separate Threads app) and
|
|
own the OAuth token upkeep. Mastodon / Bluesky / X are straightforward.
|
|
- Underlying platform fees still pass through (e.g. X per-post pricing). Postiz
|
|
brokers the call; it does not make any platform's API free.
|