Skip to content

Feat/port oss features#1

Open
lumizone wants to merge 65 commits into
mainfrom
feat/port-oss-features
Open

Feat/port oss features#1
lumizone wants to merge 65 commits into
mainfrom
feat/port-oss-features

Conversation

@lumizone

Copy link
Copy Markdown
Owner

No description provided.

Łukasz and others added 30 commits June 28, 2026 09:42
Port all 11 PostSider_OSS features onto the cloud app:
composer helpers (hashtag groups / caption templates / UTM),
posting queue (day-aware find-slot), bulk CSV import, approval
workflow, per-platform preview, AI caption rewrite, API request
generator, Post Checker, Evergreen, Smart Slots, first-comment.

- Schema: add PostApproval/ApprovalStatus, composer-helpers and
  Evergreen models, Post.{evergreen,lastRecycledAt,firstComment},
  CreationMethod.CSV. Shipped as a Prisma migration (server runs
  migrate deploy).
- AI: Post Checker + rewrite reuse cloud OpenaiService.complete()
  instead of the OSS BYO-key path; drop post-checker/providers.
- Queue: IntegrationService.findFreeDateTime returns {time,days?}[],
  PostsService recursive slot search with slotsForDay + 365d guard.
- First-comment: DTO + repo persistence + publish workflow v1.0.5.
- Evergreen: Temporal activity + workflow wired into orchestrator.
- Frontend: settings pages, csv-import + approval pages, preview +
  composer panels, API generator; composer + posts list integrated.

build:backend + build:orchestrator + frontend build green;
92 unit tests pass across 11 standalone jest configs.
Not yet live-e2e'd.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cloud reuses the platform OpenAI key, so /posts/check and /posts/rewrite
never return 409 and there is no /settings/post-checker page or endpoint.
Remove the unreachable "no key / Connect a key" states and the dead
links, and drop the unused getCheckerConfig/saveCheckerConfig/
deleteCheckerConfig client helpers. Also fix an en-dash in the checker
panel negatives list (brand: no en/em-dashes in rendered copy).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Add MINIO_* vars (MinIO storage provider)
- Add GOOGLE_GMB_*, INSTAGRAM_* (standalone), MEWE_*, WHOP_* OAuth vars
- Add TEMPORAL_* vars and RUN_CRON
- Add EMAIL_PROVIDER + full SMTP/Resend env block (was commented-out skeleton)
- Add DISABLE_REGISTRATION, NOT_SECURED, REQUIRE_EMAIL_ACTIVATION, DISALLOW_PLUS
- Add MAIN_URL, PORT, NODE_ENV, TZ, ORCHESTRATOR_PORT
- Add NEXT_PUBLIC_SELF_HOSTED, NEXT_PUBLIC_OVERRIDE_BACKEND_URL,
  NEXT_PUBLIC_UPLOAD_STATIC_DIRECTORY, NEXT_PUBLIC_SENTRY_DSN,
  NEXT_PUBLIC_FACEBOOK_PIXEL
- Add monitoring, short-link, cloud-only optional vars
- Add LISTMONK_WELCOME_TEMPLATE_ID, DISABLE_X_ANALYTICS, STRIP_LINKS_FROM_X_POSTS
- Fix Cloudflare R2 block comment (was misleadingly marked "required")
- comm check clean: all process.env.* reads in apps/libraries are now covered

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Update Self-Hosting section to reference docker-compose.production.yaml
  (the actual production compose, not a hypothetical docker-compose.yaml)
- Document that migrations run automatically via prisma migrate deploy at boot
- Add MinIO and DbGate to the service list (they are in the compose file)
- Describe the full 6-step run: copy .env.example, fill required vars,
  build (NEXT_PUBLIC_BACKEND_URL is a build-time ARG), up -d, bootstrap, logs
- Note: leave POLAR_ACCESS_TOKEN and OPENAI_API_KEY blank for self-host
  (billing unlimited, AI uses BYO keys)
- Fix pg_dump example to match actual container user/db defaults
- Correct updating steps to use production compose filename

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
….env.example

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… image/video gen, fal)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…blic API

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…stra_*) + dead enum values/flags via migration

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…streak/digest workflows; drop orphan spec

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…0 high), consolidate pnpm overrides into workspace

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…CODE_OF_CONDUCT/CI, fix env+docker config, drop stale .kiro specs

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Łukasz and others added 30 commits June 28, 2026 19:31
…s/mcp/jest.config.cjs + CI step)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…emoved, deps CVE overrides, null-guards fixed, pushed)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…build)

With node-linker=hoisted (.npmrc) and no workspace: deps in the frontend,
pnpm places all third-party deps in the ROOT node_modules; apps/frontend/
node_modules is never created, so the runtime-stage COPY failed with
'/build/apps/frontend/node_modules: not found' and aborted the image build.
next start resolves deps from the root via Node's upward module resolution.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The orchestrator bundles one Temporal worker per platform task-queue in a
single node process; startup peak is ~2.47GB (steady-state ~2.2GB). At 1280M
the container OOM-kills mid-boot and crash-loops, so scheduled posts never
publish (sit in QUEUE). 3072M clears the startup peak. The AI strip did not
reduce this — the workers are per-platform, not AI.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ng channels)

Three issues left OAuth channels dead until a manual reconnect:

1. refreshTokenWorkflow gave up forever when a token had ALREADY expired
   (`if (!minMax) return false`) — e.g. after the orchestrator was down past
   expiry (OOM) or a short-lived token missed a cycle. Now it refreshes
   immediately instead of exiting, with a 5-min pre-expiry buffer and a
   per-iteration floor so a failing refresh can't hot-loop.

2. Nothing re-armed refresh workflows after a redeploy. RefreshIntegrationService
   now re-arms on boot (onApplicationBootstrap), waiting for the Temporal client
   and using USE_EXISTING + ALLOW_DUPLICATE so it's idempotent and safe under
   double-fire.

3. The re-arm only covered providers with refreshCron=true (2 of them); most
   providers leave it false yet expose a working refreshToken(). Re-arm now
   targets any channel that has a refreshToken, so all refreshable integrations
   stay fresh.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…flag)

deploy.sh now touches the monitor's MAINTENANCE flag on start and removes it on
exit (trap), so a rebuild's container flap no longer fires false uptime alerts.
No-op when the monitor directory is absent.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…hop/YouTube fixes

- Telegram: add Postiz-style one-shared-bot connect flow (telegram-connect-modal
  with /connect <code> + Copy + polling of GET /integrations/telegram/updates;
  backend getBotId already existed). Document TELEGRAM_TOKEN/TELEGRAM_BOT_NAME in
  .env.production.example.
- Remove 8 social providers entirely (gmail, reddit, kick, rumble, vk, bear-blog,
  mewe, skool): provider + DTO files, AllProvidersSettings union/array, manager
  registration, connector catalog, add-channel/onboarding lists, icons, and the
  integrations/preview-families/provider-requirements/platform-icon keyed maps.
- Whop: fix the dependent forum dropdown — pass the chosen company id to the
  `experiences` remote-select (new field.remoteParam), param-aware cache key,
  clear the child field when the parent changes, and drop the cache key on a
  cancelled fetch so it never strands on "Loading…".
- YouTube: trim OAuth scopes to match the Google console (userinfo.profile/email,
  youtube.force-ssl/readonly/upload) — drops youtube, youtubepartner,
  yt-analytics.readonly that caused checkScopes mismatch on connect.
- Telegram modal: per-run cancellation flag + onConnectedRef so the poll loop
  starts once and cannot leak/duplicate-connect.
- Fix connector.catalog.spec to assert on a still-present connector.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Drop now-dead references to gmail/reddit/kick/rumble/vk/mewe/skool left after
the provider removal — no build/runtime impact, pure cleanup:
- integrations.controller.ts: getEnvMapping + getOAuthSetupFields +
  getDefaultCustomFields entries for the removed providers.
- provider-env.helper.ts: ENV_MAPPINGS entries (reddit/kick/vk/gmail).
- comment.capability.ts: drop kick/reddit/skool/vk from COMMENT_PROVIDERS.
- no.auth.integrations.controller.ts: remove dead `integration === 'skool'` branch.
- inbound.source.interface.ts: doc-comment example reddit -> discord.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Upgrade the Smart Slots ("best time to post") engine end to end:

- Heuristics: replace the binary peak/off-peak step with a smooth gradient
  (peak 1.0 tapering to a floor over ±3h, circular) and add tuned peak-hour +
  weekend-strength tables for EVERY registered platform (was 5). Far better,
  more natural ranking and tie-breaking.
- Personalization (activates the previously-dead clickHistogram path with real
  own data): blend in the channel's posting rhythm — a histogram of the local
  hours its past PUBLISHED posts went out — gated on >= 8 posts so it is a real
  signal, not noise. (Engagement-based optimization still needs persisted
  analytics; the histogram hook is ready for it.)
- Collision avoidance: drop candidate slots within 45 min of an already-QUEUED
  post for that channel, so we never suggest a time you are already posting.
- Diversity: return a spread (at most one suggestion per local day, >= 3h apart)
  instead of a cluster of adjacent peak hours.
- Denser candidate grid + 30-min lead; fail-soft DB queries.
- Lock the gradient behaviour with a new scoring spec case.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NEYNAR_SECRET_KEY (Neynar API key) + NEYNAR_CLIENT_ID (Sign In With Neynar
client id) power the Farcaster/wrapcast provider. Real values live only in the
gitignored .env.production.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the broken manual signer-UUID form with the official Sign In With
Neynar (SIWN) widget, so Farcaster connects via a proper login that auto-creates
and authorizes a signer.

- Backend: the get-oauth-url response for wrapcast now includes neynarClientId
  (the SIWN client id); the validated `state` is already returned as `url`.
- Frontend: new FarcasterConnectModal loads the official SIWN script
  (neynarxyz.github.io/siwn), renders the "Sign in with Neynar" button, and on
  success flattens { signer_uuid, fid, user.* } into the flat snake_case payload
  the backend authenticate() expects, then finalizes connectIntegration('wrapcast').
- calendar.tsx routes wrapcast to the SIWN modal (no extra npm dependency; the
  widget script is loaded from the CDN at runtime).

Requires the app origin (app.postsider.com) to be an authorized origin in the
Neynar developer portal.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…flows, Smart Slots, YouTube scopes

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…mplate

From the high-effort code review:
- Orphaned-provider crashes (from removing 8 providers): getIntegrationList
  now skips Integration rows whose provider is gone (was a 500 that broke the
  whole channel list), and the publish activity throws a clean per-post error
  instead of crashing the worker on undefined.
- Smart Slots no longer falls back to the unfiltered candidate grid when few
  collision-free slots remain — prefer free slots (even if fewer than `count`)
  so it never suggests a time on top of an already-queued post.
- .env.production.example: comment out NOT_SECURED (even "false" is truthy and
  enables insecure mode -> login fails); fix the misleading comment.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…een, UTM, threads)

- find-slot: a channel with no posting times no longer 400s — falls back to
  default hours so quick-scheduling always returns a slot (365-day guard kept).
- refresh-token workflow: guard a null tokenExpiration (non-expiring token) so
  it re-checks daily instead of hammering the provider's token endpoint every
  60s (new Date(null) is epoch 0).
- evergreen: recycle into a slot from the post's OWN channel (pass integrationId
  to findFreeDateTime) instead of the org default.
- UTM: keep a balanced trailing ')' that is part of the URL path (wikipedia-
  style links) and only strip an unbalanced ')' as punctuation (+ tests).
- threads: re-add the threads_manage_replies scope so first-comment/reply works
  on newly-connected accounts (provider does no checkScopes, so it is safe;
  requires the Meta app to have the permission approved).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ipts

var/send-test-email.sh and var/send-all-templates.sh hardcoded a Resend API key
(the one that leaked, now rotated/dead) and a personal recipient address. Read
both from the environment instead (RESEND_API_KEY, TO_EMAIL) with a fail-fast
guard, so a live key can never be committed again.

Note: the old key is still in git history but is revoked; no history rewrite.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New orgs (first signup per email, billing enabled) now get an actual STANDARD
plan trial instead of just an isTrailing flag with no channels:

- organization.repository.createOrgAndUser creates a nested trial Subscription
  (STANDARD tier, 5 channels, identifier="trial", cancelAt = now + 7 days).
  Gated on isBillingEnabled() so self-host (already unlimited) is untouched;
  the existing one-trial-per-email TrialUsage guard still applies.
- subscription.service.getSubscriptionByOrganizationId now returns null once a
  trial subscription's cancelAt has passed, so the org cleanly drops to FREE
  when the 7 days end. Scoped to identifier="trial" so Polar subscriptions are
  left entirely to Polar's webhook lifecycle.

Fixes: new users hitting "channel limit" immediately (FREE tier = 0 channels)
because the trial was never actually granted. Verified end to end: a fresh
signup gets STANDARD / trial / 5 channels / expiry +7d.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
BloggerProvider is registered and connectable, but 'blogger' was never in
all.providers.settings.ts, so the EmptySettings @isin validation rejected
`__type: "blogger"` and every Blogger publish failed with
'Posts.0.settings."__type" must be ...'. Blogger has no post-level settings,
so it joins the None/setEmpty group like threads/nostr. (Cross-checked the full
registered-provider list vs settings: blogger was the only registered provider
missing; mastodon-custom is disabled in the manager.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…essages

The submit-error banner referenced .submitError/.submitErrorIcon/.submitErrorClose
classes that were never defined in create-post-modal.module.css, so publish
errors rendered as an unstyled, broken-looking block. Add a proper red error
banner (icon + message + dismiss), matching the app's error style.

Also humanize raw NestJS/class-validator messages: drop the `Posts.0.settings.`
field path, Title-case the field, and replace the internal `__type` discriminator
error (which dumped the entire provider list at the user) with a friendly line.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Storage page: the endpoint required a platform superadmin unconditionally, so
  an org admin on a self-hosted instance got 403 — which the api client turns
  into a global logout. Gate the superadmin check behind managed/SaaS mode
  (mirroring the frontend nav's NEXT_PUBLIC_SELF_HOSTED flag), and make the
  page's fetch `silent` so any permission error shows as a message, never a
  logout.
- Users/Team tab: GET /settings/team required the paid TEAM_MEMBERS feature, so
  the whole tab 402'd on plans without team seats. Listing now needs only org
  ADMIN (you can always see your own team); only INVITING stays gated. The page
  also renders a friendly plan message instead of a raw "HTTP 402".
- Composer error banner: make the dismiss (x) button render reliably (match the
  header close-button sizing).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Settings -> API request generator was a single-column form. Rebuild it to
mirror the calendar composer: a two-column layout (Compose on the left, live
request on the right) with a dedicated CSS module, responsive stacking, a proper
post-content editor, and a sticky curl/JSON output panel — the same composing
experience, but it emits the ready-to-send POST /public/v1/posts request instead
of a Publish button.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ngs panel)

create-post-modal.tsx referenced .validationSummary/.validationItem/.provider*/
.field* classes that were never defined in its CSS module, so the "Almost there —
a few things to finish" validation summary and the entire per-channel provider
settings panel (Whop forum, TikTok privacy, etc.) rendered completely unstyled.
Add proper styling for all 19: an amber validation card, a bordered settings
panel with hint/warning callouts and a char counter, and the checkbox/select/
input field controls. (Found via a repo-wide scan for styles.X referenced in TSX
but absent from the sibling .module.css — 0 remain across the frontend.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The api client logged the user out on ANY 401 OR 403. A 403 is a
permission/plan gate on an already-authenticated user (superadmin-only storage,
admin-only api-keys list, paid-feature routes), so hitting such a page kicked
them to /login. Only 401 (no/expired session) now logs out; 403 throws so the
page renders the error. Session revocation still surfaces as a 401 next request.
This is the systemic version of the earlier per-page storage fix.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the simplified single-channel form with the actual calendar composer
experience: a multi-select channel strip using the same ChannelAvatar chips
(profile picture + platform badge) as the calendar, the content editor, date &
time, post type and Insert media — the only difference from publishing is the
output. Selecting N channels emits one posts[] entry per channel (each with its
own provider default settings) via the new buildMultiPostBody, rendered live as
curl/JSON. buildPostBody now delegates to buildMultiPostBody so existing
single-channel behaviour (and its spec) is unchanged; added multi-channel specs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The selected channel in the new-post composer had a single dark ring
(box-shadow 2.5px --fg). Add a white gap ring hugging the avatar before the dark
ring so selection reads as a two-tone halo. Both colors use --bg/--fg so they
invert correctly with the theme; the per-channel "active" target keeps its faint
outer ring on top.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The request generator's channel chips used a bordered-card selected state.
Switch to the calendar composer's look: borderless avatar chips with the white +
dark halo ring on the selected avatar and the name going muted -> --fg on
select, so the two forms are visually identical.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a fixed top-of-screen private-beta notice on the auth pages (AuthShell gains
an optional `banner`): sign-ups are closed, registration won't work, join the
whitelist at postsider.com. The login footer now links to the whitelist instead
of the (now disabled) /register route. Registration itself is turned off via
DISABLE_REGISTRATION on the server (env, not committed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Switch the login private-beta strip from amber to a solid black bar with white
text (var(--fg)/var(--bg) so it inverts cleanly in dark mode).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New sign-ups are closed, so "Sign in with Google" would only work for
pre-existing Google accounts and just confuses new visitors. Gate the button and
its "or" divider behind a SHOW_GOOGLE flag (currently false); flip to restore it
when the beta opens.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…sion

Add a 2026-07-02 Status bullet (real 7-day STANDARD trial, blogger __type fix,
20 missing composer CSS classes, api-client 401-only logout + storage/team gate
fixes, request generator rebuilt as the calendar compose form, private-beta login
banner + disabled registration). Note the trial in the billing section and the
api-client 401-only-logout rule in the frontend conventions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant