Feat/port oss features#1
Open
lumizone wants to merge 65 commits into
Open
Conversation
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>
…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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.