trypost/routes/app.php

318 lines
22 KiB
PHP
Raw Permalink Normal View History

<?php
declare(strict_types=1);
2026-04-02 20:57:06 +00:00
use App\Http\Controllers\App\AnalyticsController;
use App\Http\Controllers\App\ApiKeyController;
use App\Http\Controllers\App\AssetController;
use App\Http\Controllers\App\BillingController;
use App\Http\Controllers\App\DiscordController as AppDiscordController;
use App\Http\Controllers\App\GiphyController;
use App\Http\Controllers\App\LinkPreviewController;
MCP: workspace settings, viewer read access, and token access (#241) * Add workspace MCP settings and token access controls. Ship MCP settings UI, OAuth revoke/list helpers, Passport deploy wiring, and workspace.token:mcp gating so assistants can connect without pulling in welcome/onboarding from the parent epic. Co-authored-by: Cursor <cursoragent@cursor.com> * Type MCP client config shapes instead of string checks. Encode http/config-root on each advanced client and tighten primary client ids so snippet generation does not branch on magic strings. Co-authored-by: Cursor <cursoragent@cursor.com> * Polish MCP settings follow-ups from review. Translate Ukrainian MCP copy, deep-link ChatGPT into connector creation, drop an unused asset and revoke arg, and assert PATs are rejected on the MCP endpoint. Co-authored-by: Cursor <cursoragent@cursor.com> * Harden MCP connected clients, revoke scope, and OAuth consent. List recoverable sessions with live refresh tokens, revoke only PATs, throttle registration alone, and block viewers from authorizing MCP. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify MCP OAuth route throttling to a single middleware group. Co-authored-by: Cursor <cursoragent@cursor.com> * Allow workspace viewers read-only MCP access with web policy writes. Mirror the web app: MCP connects on view + OAuth mcp:use, write tools enforce createPost/update/delete/manageAccounts/manageTeam, and demotion to Viewer keeps grants. Cover role denials, consent, and disconnect. Co-authored-by: Cursor <cursoragent@cursor.com> * Harden MCP tool authz with shared workspace helpers. Route ApiKey tools through AuthorizesMcpTool, fail closed on null user or policy argument, and resolve the current workspace before mutating. Co-authored-by: Cursor <cursoragent@cursor.com> * Drop redundant string casts on validated request data. Enum::from and validated() fields are already strings, so the casts add noise without changing behavior. Co-authored-by: Cursor <cursoragent@cursor.com> * Show only the current user's MCP connections in settings. Match API keys privacy: list and disconnect your own OAuth clients, not teammates' across the account. Co-authored-by: Cursor <cursoragent@cursor.com> * Cover LoadWorkspaceFromToken gaps and harden AuthorizesMcpTool tests. Co-authored-by: Cursor <cursoragent@cursor.com> * Drop redundant is_string guard before UpdatePostTool find. Co-authored-by: Cursor <cursoragent@cursor.com> * Refactor AppSidebar to always show MCP link and simplify route middleware definition in ai.php. The MCP link is now consistently displayed regardless of the current workspace state, and the route middleware syntax has been streamlined. * Refresh MCP connected clients with Inertia usePoll. Co-authored-by: Cursor <cursoragent@cursor.com> * Bump laravel/mcp to 0.9.1 and add the TryPost server icon. Requires laravel/boost 2.5 for the Icon attribute; expose images/trypost/icon.png on TryPostServer. Co-authored-by: Cursor <cursoragent@cursor.com> * Drop no-op ReflectionClass import in TryPostServerTest. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-06 12:54:51 +00:00
use App\Http\Controllers\App\McpSettingsController;
use App\Http\Controllers\App\NotificationController;
Activation checklist + MCP OAuth authorize UX (#239) (#250) * Wire onboarding activation into Account, observers, and shared Inertia data Add onboarding casts/hasFinishedOnboarding, AccessToken ObservedBy, Platform::connectableOptions, Post/SocialAccount onboarding broadcast hooks, and lazy onboardingResidual share + SharedData types. * Register onboarding routes and post-checkout activation redirects. Wire billing processing and the sidebar checklist so owners land on activation after subscribe, with locale sidebar/uk onboarding strings. * Align MCP grant usability with onboarding activation checks Unbound MCP tokens fall back to the user's current workspace and require createPost so viewer/unscoped grants neither unlock the checklist nor broadcast onboarding status. * Require bound MCP workspace for onboarding activation. Drop current-workspace fallback from usable MCP grants so checklist detection and broadcasts match Passport token scoping; viewers still cannot unlock the MCP step. Co-authored-by: Cursor <cursoragent@cursor.com> * Harden onboarding review findings and tighten locale strings. Fix Welcome/Persona/TrackPost suites broken by the activation route reuse and PostObserver analytics side effects, restore Echo poll fallbacks, reject unbound MCP grants in tests, and drop unused onboarding.mcp keys. Co-authored-by: Cursor <cursoragent@cursor.com> * Remove unused sidebar and MCP authorization locale keys. Drop dead sidebar menu/theme strings (including the overwritten workspace label and api_keys nav entry) and unused MCP authorize app_title/approving copy across all locales. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix SetLocale crashing on Passport Symfony OAuth responses. OAuth errors return a raw Symfony Response without withCookie(); attach the default locale cookie via headers so authorize no longer 500s. Co-authored-by: Cursor <cursoragent@cursor.com> * Prompt OAuth guests to log in before rejecting unknown clients. MCP Inspector often reuses a stale client_id; validateAuthorizationRequest was returning invalid_client JSON before the login redirect. Guests now hit /login first, then client validation runs after authentication. Co-authored-by: Cursor <cursoragent@cursor.com> * Render Inertia OAuth authorize errors for browser logins. After login, Inertia follows the intended authorize URL; raw invalid_client JSON broke that visit. HTML/Inertia requests now get mcp/AuthorizeError while API JSON clients still receive the OAuth error payload. Co-authored-by: Cursor <cursoragent@cursor.com> * Detect Inertia OAuth error pages via Request::inertia(). Use the framework helper so post-login authorize failures keep returning an Inertia page instead of raw OAuth JSON. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify OAuth authorize error page detection to expectsJson. Drop the X-Inertia header sniff; browser and Inertia visits already do not expectsJson, while API clients still receive the OAuth JSON payload. Co-authored-by: Cursor <cursoragent@cursor.com> * Share MCP authorize layout and drop the error close button. Keep authorize and authorize-error on the same centered card shell instead of the auth split layout. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify onboarding activation for reviewability and safety. Use an exists-based MCP check, keep GETs read-only, move sync into syncAndNotify, clear MCP skips on connect, restrict complete to owners, and share Echo/poll via one composable. Co-authored-by: Cursor <cursoragent@cursor.com> * Move MCP OAuth authorize UX out of the onboarding PR. Keep the activation checklist focused; OAuth guest/error-page work now lives on fix/mcp-oauth-authorize-ux. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix corrupted French MCP locale after OAuth key cleanup. Co-authored-by: Cursor <cursoragent@cursor.com> * Restore MCP OAuth authorize UX onto the onboarding branch. Keep authorize error page, guest login-before-client validation, and SetLocale Symfony cookie fix in #250. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix OAuth prompt=none redirects and harden onboarding tests. Keep login_required/consent_required as redirects instead of Inertia, add regression coverage for owner-only activation, require invite email confirmation, and align MCP connected apps with the sessions list UI. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify onboarding guards and dedupe viewed analytics. Introduce isOnboardingOpen / belongsToAccount helpers, collapse duplicated sync/dispatch paths, and capture onboarding.viewed once per account. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify onboarding event, observers, and status helpers. Tighten Account onboarding predicates, drop nullable broadcast/dispatch APIs, and collapse repeated observer/controller guards. Co-authored-by: Cursor <cursoragent@cursor.com> * Treat in-app users as always having an account. Add resolveAccount(), tighten belongsToAccount to string ids, and fold guest residual handling into ResolveOnboardingStatus. Co-authored-by: Cursor <cursoragent@cursor.com> * Rename onboarding residual share test to progress. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify onboarding status and rename residual to progress. Use accountOrFail, extract MCP onboarding scope, auto-leave the ready screen, and send non-onboarding checkout back to accounts. Co-authored-by: Cursor <cursoragent@cursor.com> * Extract HasAccount and prefer data_get in onboarding flows. Move account helpers off User, drop nullable sidebarProgress, and read OAuth/onboarding payloads with data_get. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify onboarding checks and extract HasOnboarding. Use Eloquent + policies for MCP/backfill paths, and move account onboarding helpers into a dedicated trait. Co-authored-by: Cursor <cursoragent@cursor.com> * Add trait tests and tidy onboarding imports. Cover HasAccount and HasOnboarding under Models/Traits, prefer filled() for checkout session ids, and import Throwable instead of FQCN. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify checkout session_id and OAuth error props. Read session_id via request->string(), and take OAuth error details from the League exception instead of decoding the response body. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify PostObserver onboarding notify path. Share one otherPosts check for first-create and last-delete instead of separate callbacks. Co-authored-by: Cursor <cursoragent@cursor.com> * Use post author as onboarding sync actor. Drop Auth::user() preference in PostObserver; checklist sync attributes to $post->user. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify SocialAccountObserver and OAuth authorize flow. Share create/delete onboarding notify, drop Auth actor fallback to owner, and inline Passport Inertia error handling. Co-authored-by: Cursor <cursoragent@cursor.com> * Use lazy Inertia props for onboarding partial reloads. Drop partial-header branching; wrap page props in closures and always redirect completed/dismissed accounts to the calendar. Co-authored-by: Cursor <cursoragent@cursor.com> * Defer sidebar onboarding progress and stamp completion as owner-only. Skip the MCP checklist work on full Inertia visits via deferred shared props, early-exit token scans, and keep account completion stamps owner-gated. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify deferred onboarding progress share via canShowProgress. Co-authored-by: Cursor <cursoragent@cursor.com> * Add User firstName for shared auth and simplify onboarding page. Co-authored-by: Cursor <cursoragent@cursor.com> * Move User firstName coverage into UserTest. Co-authored-by: Cursor <cursoragent@cursor.com> * Use first_name directly without empty-name fallbacks. Co-authored-by: Cursor <cursoragent@cursor.com> * Resolve onboarding sample prompt on the frontend via i18n. Co-authored-by: Cursor <cursoragent@cursor.com> * Stamp onboarding completion via the account owner after teammate unlocks. Co-authored-by: Cursor <cursoragent@cursor.com> * Count only the account owner MCP grant toward onboarding activation. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix OAuth consent auth-token mismatch for mid-activation owners. Skip deferred onboardingProgress on Passport authorize so Inertia does not rotate the session authToken, cover happy and stale-token paths in tests, and polish MCP setup copy plus sidebar/onboarding layout. Co-authored-by: Cursor <cursoragent@cursor.com> * Keep users on onboarding after activation completes. Stamp completion and re-render the finished checklist instead of redirecting to the calendar so owners can review the done state. Co-authored-by: Cursor <cursoragent@cursor.com> * Clarify Passport consent-view opt-out and guard app-route deferral. Rename the authorize-only route check and assert onboardingProgress still defers on calendar, onboarding, and MCP settings. Co-authored-by: Cursor <cursoragent@cursor.com> * Harden onboarding completion and MCP consent workspace binding. Reject OAuth approve without a workspace, retry auto-complete until stamped, send dismissed complete straight to calendar, and cover the device consent defer opt-out. Co-authored-by: Cursor <cursoragent@cursor.com> * Enable activation checklist for self-hosted installs. Remove the self-hosted onboarding redirects, keep the SaaS-only dismiss backfill, and cover subscription-less owners plus skip/complete destinations. Co-authored-by: Cursor <cursoragent@cursor.com> * Add GitHub, Hacker News, and directories referral sources. Expand the welcome referral step with open-source and directory discovery channels. Co-authored-by: Cursor <cursoragent@cursor.com> * Refine welcome referral sources and labels. Split Instagram/Threads, add Founder, and shorten Google, GitHub, AI, and blog option labels. Co-authored-by: Cursor <cursoragent@cursor.com> * Sort accounts platforms alphabetically and drop connect hover plus. Reuse connectableOptions for the accounts index and remove the unused plus badge on disconnected cards. Co-authored-by: Cursor <cursoragent@cursor.com> * Centralize PostHog once-capture so disabled installs don't burn dedupe keys. Move isEnabled + Cache::add into PostHogService::captureOnce and route onboarding viewed/step events through it. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify onboarding backfill to complete every existing open account. Drop self-hosted and subscription filters; down clears completed_at again. Co-authored-by: Cursor <cursoragent@cursor.com> * Drop PostHog captureOnce and use plain capture for onboarding. Remove cache-based event dedupe; callers rely on PostHogService::capture gating. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-07 23:34:43 +00:00
use App\Http\Controllers\App\OnboardingController;
use App\Http\Controllers\App\PostAiCreateController;
use App\Http\Controllers\App\PostAiGenerateController;
use App\Http\Controllers\App\PostAiRegenerateMediaController;
use App\Http\Controllers\App\PostAiReviewController;
use App\Http\Controllers\App\PostCommentController;
use App\Http\Controllers\App\PostController;
feat: @mentions in comments, AI Action layer + MCP tools, settings tabs Mentions in post comments - @mention autocomplete (workspace members, current user excluded) with marker syntax @[uuid] persisted, display names rendered via CommentBody chips; live edit replaces markers with names and converts back on save. - NotifyMentions action with workspace-scoped membership check, dedupes same user, only newly-added mentions on update. - Email + in-app via SendNotification job, respecting per-user notification_preferences.mentioned_in_comment. - Heartbeat-based presence (Cache, 60s TTL, 30s ping) so online recipients get only the in-app notification — no email noise. - Real-time bell on workspace.{id}.user.{id} private channel (NotificationCreated event), scoped channel name avoids client-side filtering and lays out a convention for future workspace channels. - Mailable localized via lang/{en,es,pt-BR}/mail.php; Maizzle source template for the email is committed and built into resources/views/mail. AI generation refactor (Action layer + MCP) - Extracted Actions/Ai/Generate{Image,Video} with QuotaExhaustedException so agent tools and MCP tools share a single domain entry point. - Mcp/Tools/Ai/Generate{Image,Video}Tool registered in TryPostServer; both return MediaResource payloads. - Orientation::imageApiSize maps non-OpenAI ratios to 1:1/2:3/3:2. - config/ai.php is now the single source of truth driven by env, removing the trypost.ai shim. Default text/image providers flipped to OpenAI. Settings/UX - /settings/workspace split into shadcn Tabs (Workspace / Brand / Users) with three components. - /assets and the in-editor MediaPicker open the ImagePreviewDialog lightbox on image click while preserving action button behaviour. - Comments tab landed via ?tab=comments&comment=<id> from notification click (scroll-to + temporary highlight). - Mention autocomplete popover flips above when near the viewport bottom. - Real social platform PNGs replace Tabler brand glyphs in schedule pills and post list, with hover tooltip carrying display_name + handle. Bug fixes - AcceptInvite: controller now passes workspace + role payload that the Vue page expects; login/register CTAs preselect the invite email. - WorkspaceInvite mailable: stopped referencing nonexistent $invite->workspace and $invite->role; column added to the migration, Invite model casts role to WorkspaceRole, CreateInvite persists it. - PostCommentCreated: added broadcastAs so .PostCommentCreated actually matches the Echo listener; payload now includes mentioned_users so receivers render the chip correctly without a refetch. - Preview components for X/Pinterest/Threads/Bluesky/LinkedIn/Mastodon/ TikTok/YouTube switched from item.type === 'image' to !isVideoMedia(item) so media without a persisted type still renders. - UpdatePostRequest now accepts media.*.{type,mime_type,size,...} so the posts.media JSON keeps the metadata that the previews need. - Removed throttle:6,1 from social connect routes (was 429ing legitimate OAuth retries). - Used MediaType enum cases instead of literal 'image'/'video' strings when creating media rows. Tests - MentionParser unit tests, NotifyMentions feature tests including online/offline channel selection and preference gating, MCP AI tool happy paths, MentionedInComment mailable rendering, AcceptInvite + search-members + index mentioned_users path. 1229 passing.
2026-05-01 23:59:03 +00:00
use App\Http\Controllers\App\PresenceController;
use App\Http\Controllers\App\Settings\AccountController;
use App\Http\Controllers\App\Settings\AuthenticationController;
refactor: notification preferences, header slots, calendar layout, UI polish Notification preferences: - Create notification_preferences table (post_published, post_failed, account_disconnected booleans per user) - NotificationPreferenceController with firstOrCreate on first visit - SendNotification job respects email preferences before sending - Settings page with toggle switches, i18n in 3 languages - 8 new tests for preferences (controller + wantsEmailFor + job integration) Post published notification: - PostPublished mail + maizzle template - Notify owner on successful publish via SendNotification job - PostPublished type added to notification enum Header & Layout: - Rename AppSidebarHeader to AppHeader with left/center/right slots - showSidebarTrigger prop to hide sidebar toggle - Calendar: controls in header (left: nav, center: date, right: tabs + new post) - Fixed header with scrollable content (flex h-screen pattern) - fullWidth pages use overflow-y-auto (fixes month view scroll) UI improvements: - Action buttons moved to header-right: posts, hashtags, labels - Settings breadcrumbs: "Settings > Profile" pattern - Calendar: remove duplicate New Post button from day view - Remove size="sm" from Schedule/Publish buttons - Remove bg-background from header (inherits from SidebarInset) - Add Cancel button to labels and hashtags create/edit dialogs - Add common.cancel i18n key - Clean up orphaned Calendar breadcrumbs - Fix SocialAccountsGrid buttons to use shadcn Button ghost All 753 tests passing.
2026-03-30 21:18:17 +00:00
use App\Http\Controllers\App\Settings\NotificationPreferenceController;
use App\Http\Controllers\App\Settings\ProfileController;
use App\Http\Controllers\App\Settings\SettingsController;
use App\Http\Controllers\App\Settings\UsageController;
use App\Http\Controllers\App\UnsplashController;
Add workspace webhooks and drop the unused automation webhook node (#326) * Add workspace webhooks and drop the unused automation webhook node. Give workspaces HMAC-signed outgoing webhooks for the post lifecycle, with retry, auto-pause, replay, and live logs, and keep HTTP Request as the only outbound automation node. * Tighten webhook controller and validation after review. Drop the redundant workspace redirects, prune logs without counting, and validate events/status with Rule::enum. * Move leftover webhook UI copy behind i18n. HTTP status phrases, delete-cancel, and validation attribute names were still English literals. * Build the webhook-paused email through Maizzle. The hand-written Blade skipped the shared layout, header, and footer used by the other mail templates. * Cover real webhook dispatch paths and restyle the webhook pages. * Ask for the shared delete keyword when confirming a webhook delete. The endpoint URL is a poor confirm string; posts and assets already use the common "delete" keyword. * Fix webhook review blockers so CI can go green. Drop leftover French automation keys, stop mutating Inertia log props, and show delivered_at instead of created_at. * Close the remaining webhook review gaps. Keep Echo log updates across infinite scroll, align the channel with the policy, persist log ids across retries, and fail unknown automation nodes without throwing. * Stop webhook delivery after disable and record last sent only on success. Queued jobs now skip paused or disabled endpoints unless the user replays, and changing the URL re-pings it first. * Limit webhooks to owners and admins, and encrypt signing secrets. Members can no longer create or inspect outgoing integrations, and secrets stay encrypted at rest. * Cover webhook secret hiding, skip-ping, and failed-delivery edges. * Send the full post on webhooks after labels and platforms are saved. * Fix webhook payloads for integer media ids and type webhook status. * Split the webhook show page into focused components. * Reset live webhook logs when switching endpoints. * Keep the newest webhook logs at the top after live merges. * Cast media item ids to string without the extra scalar check. * Add post.unscheduled webhooks and put the log id on the envelope. Unscheduling is now a first-class event, and receivers can send the delivery id back so we can find the matching log. * Translate webhook event names in the UI. * Make the webhook show page full-width and stop stacking flash toasts. * Translate remaining webhook UI copy in every locale. * Sign webhook pings and drop author email from the payload. * Send signed webhook tests after create instead of pinging on save. Create and update only block private URLs so the receiver can copy the secret first. The show page then sends a signed webhook.test with an object data envelope. * Polish webhook test UX and always mint the dispatch log id in the job. Keep send-test in the actions menu (its own group) and drop the leftover constructor param so retries reuse the serialized id instead of a caller-supplied one.
2026-09-04 12:43:29 +00:00
use App\Http\Controllers\App\WebhookController;
Welcome: pre-subscription funnel and member subscription-required screen (#243) * Rename pre-subscription onboarding funnel to Welcome. Move the ICP steps to /welcome, drop the social-connect checkout gate, hold unpaid members on a subscription-required screen, and keep legacy /onboarding URLs working until the post-subscription checklist lands. Closes #237 Co-authored-by: Cursor <cursoragent@cursor.com> * Drop legacy /onboarding ICP URL aliases. Unfinished users re-enter Welcome via EnsureAccountReady on next login; /onboarding stays free for the post-subscription checklist. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify Welcome PostHog event names. Use welcome.persona/goals/referral and drop the unused checkout case — begin checkout stays on the frontend as checkout.started / begin_checkout. Co-authored-by: Cursor <cursoragent@cursor.com> * Slim welcome goal options to match the #204 set. Drop team_collaboration, automate_api, and track_performance so the goals step stays at nine choices. Co-authored-by: Cursor <cursoragent@cursor.com> * Split Welcome AI goals into TryPost AI and MCP assistants. Rewrite ai_content for in-app generation and add use_mcp so Claude/ChatGPT/Cursor intent is captured separately across locales. Co-authored-by: Cursor <cursoragent@cursor.com> * Harden Welcome goals gate and drop dead checkout UI. Treat removed goal values as incomplete so mid-funnel users re-select, remove the unused canCheckout branch, and fix the pt-BR welcome progress label. Co-authored-by: Cursor <cursoragent@cursor.com> * Add password visibility toggle to the login form. Match the register eye control so users can reveal their password while signing in. Co-authored-by: Cursor <cursoragent@cursor.com> * Point the sidebar community link to Discord. Replace the X stay-updated entry with Join Discord and the trypost.it/discord invite. Co-authored-by: Cursor <cursoragent@cursor.com> * Rename the sidebar Discord link to Discord community. Softer label that matches the other support nav items. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix broken Turkish Discord community translation. An unescaped apostrophe left a parse error in lang/tr/sidebar.php. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-06 14:34:50 +00:00
use App\Http\Controllers\App\WelcomeController;
use App\Http\Controllers\App\WorkspaceController;
use App\Http\Controllers\App\WorkspaceInviteController;
use App\Http\Controllers\App\WorkspaceLabelController;
use App\Http\Controllers\App\WorkspaceSignatureController;
use App\Http\Controllers\Auth\BlueskyController;
use App\Http\Controllers\Auth\DiscordController;
use App\Http\Controllers\Auth\FacebookController;
use App\Http\Controllers\Auth\InstagramController;
2026-04-02 20:57:06 +00:00
use App\Http\Controllers\Auth\InstagramFacebookController;
use App\Http\Controllers\Auth\LinkedInController;
use App\Http\Controllers\Auth\MastodonController;
use App\Http\Controllers\Auth\PinterestController;
use App\Http\Controllers\Auth\SocialController;
use App\Http\Controllers\Auth\TelegramController;
use App\Http\Controllers\Auth\ThreadsController;
use App\Http\Controllers\Auth\TikTokController;
use App\Http\Controllers\Auth\XController;
use App\Http\Controllers\Auth\YouTubeController;
use App\Http\Middleware\App\EnsureAccountReady;
use App\Http\Middleware\App\EnsureHasWorkspace;
use Illuminate\Support\Facades\Route;
2026-03-31 14:35:49 +00:00
// Subscription selection (requires auth but not subscription)
Route::middleware(['auth'])->group(function () {
Route::get('/', function () {
return redirect()->route('app.calendar');
})->name('app.home');
2026-03-31 14:35:49 +00:00
Route::get('subscribe', [BillingController::class, 'subscribe'])->name('app.subscribe');
Welcome: pre-subscription funnel and member subscription-required screen (#243) * Rename pre-subscription onboarding funnel to Welcome. Move the ICP steps to /welcome, drop the social-connect checkout gate, hold unpaid members on a subscription-required screen, and keep legacy /onboarding URLs working until the post-subscription checklist lands. Closes #237 Co-authored-by: Cursor <cursoragent@cursor.com> * Drop legacy /onboarding ICP URL aliases. Unfinished users re-enter Welcome via EnsureAccountReady on next login; /onboarding stays free for the post-subscription checklist. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify Welcome PostHog event names. Use welcome.persona/goals/referral and drop the unused checkout case — begin checkout stays on the frontend as checkout.started / begin_checkout. Co-authored-by: Cursor <cursoragent@cursor.com> * Slim welcome goal options to match the #204 set. Drop team_collaboration, automate_api, and track_performance so the goals step stays at nine choices. Co-authored-by: Cursor <cursoragent@cursor.com> * Split Welcome AI goals into TryPost AI and MCP assistants. Rewrite ai_content for in-app generation and add use_mcp so Claude/ChatGPT/Cursor intent is captured separately across locales. Co-authored-by: Cursor <cursoragent@cursor.com> * Harden Welcome goals gate and drop dead checkout UI. Treat removed goal values as incomplete so mid-funnel users re-select, remove the unused canCheckout branch, and fix the pt-BR welcome progress label. Co-authored-by: Cursor <cursoragent@cursor.com> * Add password visibility toggle to the login form. Match the register eye control so users can reveal their password while signing in. Co-authored-by: Cursor <cursoragent@cursor.com> * Point the sidebar community link to Discord. Replace the X stay-updated entry with Join Discord and the trypost.it/discord invite. Co-authored-by: Cursor <cursoragent@cursor.com> * Rename the sidebar Discord link to Discord community. Softer label that matches the other support nav items. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix broken Turkish Discord community translation. An unescaped apostrophe left a parse error in lang/tr/sidebar.php. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-06 14:34:50 +00:00
Route::get('welcome', fn () => redirect()->route('app.welcome.persona'))->name('app.welcome');
Route::get('welcome/persona', [WelcomeController::class, 'persona'])->name('app.welcome.persona');
Route::post('welcome/persona', [WelcomeController::class, 'storePersona'])->name('app.welcome.persona.store');
Route::get('welcome/goals', [WelcomeController::class, 'goals'])->name('app.welcome.goals');
Route::post('welcome/goals', [WelcomeController::class, 'storeGoals'])->name('app.welcome.goals.store');
Route::get('welcome/referral-source', [WelcomeController::class, 'referralSource'])->name('app.welcome.referral-source');
Route::post('welcome/referral-source', [WelcomeController::class, 'storeReferralSource'])
->middleware('throttle:6,1')
->name('app.welcome.referral-source.store');
Add social connect step to welcome before Stripe (#293) * feat: add social connect step to welcome before Stripe checkout Ask new owners to connect a network after referral source so we can track welcome.connect in PostHog and still let them continue to checkout without a connection. Co-authored-by: Cursor <cursoragent@cursor.com> * Nest welcome connect copy under a connect array. Co-authored-by: Cursor <cursoragent@cursor.com> * Require a connected social account before welcome checkout. Skip is no longer allowed, and the welcome layout takes a Tailwind size so the connect grid can sit two rows of six. Co-authored-by: Cursor <cursoragent@cursor.com> * Harden the welcome connect step after review. Track connect only after Stripe creates a session, restore a missing workspace before showing networks, and cover the remaining checkout and analytics cases. Co-authored-by: Cursor <cursoragent@cursor.com> * Refactor social account status handling across components Updated the SocialAccountsGrid, NetworkConnectGrid, onboarding, and welcome connect components to utilize the new SocialAccountStatus enum for improved clarity and maintainability. This change replaces string literals for account statuses with the enum values, enhancing type safety and consistency throughout the application. Co-authored-by: Cursor <cursoragent@cursor.com> * Refactor workspace resolution in WelcomeController and StoreWelcomeConnectRequest Updated the WelcomeController and StoreWelcomeConnectRequest to directly access the user's current workspace, simplifying the code by removing the resolveCurrentWorkspace method. This change enhances readability and maintains functionality by ensuring the current workspace is correctly utilized in the connection process. Additionally, removed outdated test cases related to workspace restoration. * Inline welcome connect PostHog platforms from the current workspace. Drop the extra helper — the grid already loads accounts the same way as onboarding and accounts. Co-authored-by: Cursor <cursoragent@cursor.com> * Inline Stripe checkout into the welcome connect store. startCheckout was a one-caller wrapper; storeConnect now matches the other welcome steps. Co-authored-by: Cursor <cursoragent@cursor.com> * Move welcome connect validation into the controller. The FormRequest had no input to validate and duplicated step-gating. Require a connected account in storeConnect, and drop the dead owner abort plus the always-true PostHog connected flag. Co-authored-by: Cursor <cursoragent@cursor.com> * Show welcome toasts and cover remaining connect cases. Mount the app Toast host on WelcomeLayout so OAuth, Telegram, and disconnect feedback is visible. Add tests for stale goals, an empty workspace grid, accounts on another workspace, and skipped identify when Stripe fails. Co-authored-by: Cursor <cursoragent@cursor.com> * Assume a welcome workspace, validate connect in the FormRequest, and add browser tests. Co-authored-by: Cursor <cursoragent@cursor.com> * Rename WelcomeEvent::dashboardFunnel() to funnel(). Co-authored-by: Cursor <cursoragent@cursor.com> * Identify connected platforms from the social account observer. Co-authored-by: Cursor <cursoragent@cursor.com> * Queue connected-platform identify on the posthog queue. Co-authored-by: Cursor <cursoragent@cursor.com> * Harden welcome connect: 404 without a workspace, and keep step redirects ahead of connect validation. Co-authored-by: Cursor <cursoragent@cursor.com> * Identify connected platforms on workspace and account groups, and keep the account union on the owner. Co-authored-by: Cursor <cursoragent@cursor.com> * Share hasCurrentGoals on User and keep Stripe checkout when PostHog capture fails. Co-authored-by: Cursor <cursoragent@cursor.com> * Skip welcome connect validation when the controller would redirect the user away. Co-authored-by: Cursor <cursoragent@cursor.com> * Move current-goal membership onto the Goal enum. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-17 22:37:43 +00:00
Route::get('welcome/connect', [WelcomeController::class, 'connect'])->name('app.welcome.connect');
Route::post('welcome/connect', [WelcomeController::class, 'storeConnect'])
->middleware('throttle:6,1')
->name('app.welcome.connect.store');
Route::get('welcome/subscription-required', [WelcomeController::class, 'subscriptionRequired'])->name('app.welcome.subscription-required');
2026-03-31 14:35:49 +00:00
Route::get('billing/processing', [BillingController::class, 'processing'])->name('app.billing.processing');
Route::get('workspaces/create', [WorkspaceController::class, 'create'])->name('app.workspaces.create');
Route::post('workspaces', [WorkspaceController::class, 'store'])->name('app.workspaces.store');
Route::post('workspaces/autofill', [WorkspaceController::class, 'autofillBrand'])
feat: add brand autofill from website URL in onboarding Users on the Brand step can type their website URL and click 'Preencher' (Portuguese) / 'Autofill' (English). The backend fetches their homepage, parses standard meta tags, and returns: - name ← og:site_name | title (suffix stripped at ' | ', ' - ') - description ← meta[name=description] | og:description - language ← <html lang> mapped to en / pt-BR / es - logo ← apple-touch-icon | largest link[rel*=icon] | og:image The logo is downloaded, validated (mime whitelist, 2MB max), and attached to the workspace's 'logo' media collection so the avatar updates immediately. Zero LLM calls, zero external APIs. Uses symfony/dom-crawler + symfony/css-selector (newly required) for meta extraction. Everything else (Http client, workspace media, Intervention) was already in the project. Security: - SSRF guardrail: resolves the host, rejects private / loopback / link-local ranges, enforces http(s) scheme on both the initial page fetch and the logo download. - Rate limited at 10 req/min per user via the throttle middleware alias on the route. - Logo content-type must be one of the allowed image mimes; wrong types are silently dropped so users never see broken images. UX: - 'Autofill' button next to the website input, disabled until there is a URL; shows a spinner while running. - If a logo was captured, a small preview appears below the input so users can see what was pulled before saving. - Success and error paths both surface as vue-sonner toasts, with translations in en / pt-BR / es. - Failures leave the form untouched — nothing is destructively overwritten if parsing gave us nothing. Tests (16 new): action-level coverage for happy path, title-suffix fallback, language code normalization across 6 locales, scheme rejection, private-range SSRF rejection, implicit https prefixing, empty sites, upstream errors, and wrong-mime logo rejection. Plus two controller-level tests for the autofill endpoint.
2026-04-16 14:13:54 +00:00
->middleware('throttle:10,1')
->name('app.workspaces.autofill');
feat: @mentions in comments, AI Action layer + MCP tools, settings tabs Mentions in post comments - @mention autocomplete (workspace members, current user excluded) with marker syntax @[uuid] persisted, display names rendered via CommentBody chips; live edit replaces markers with names and converts back on save. - NotifyMentions action with workspace-scoped membership check, dedupes same user, only newly-added mentions on update. - Email + in-app via SendNotification job, respecting per-user notification_preferences.mentioned_in_comment. - Heartbeat-based presence (Cache, 60s TTL, 30s ping) so online recipients get only the in-app notification — no email noise. - Real-time bell on workspace.{id}.user.{id} private channel (NotificationCreated event), scoped channel name avoids client-side filtering and lays out a convention for future workspace channels. - Mailable localized via lang/{en,es,pt-BR}/mail.php; Maizzle source template for the email is committed and built into resources/views/mail. AI generation refactor (Action layer + MCP) - Extracted Actions/Ai/Generate{Image,Video} with QuotaExhaustedException so agent tools and MCP tools share a single domain entry point. - Mcp/Tools/Ai/Generate{Image,Video}Tool registered in TryPostServer; both return MediaResource payloads. - Orientation::imageApiSize maps non-OpenAI ratios to 1:1/2:3/3:2. - config/ai.php is now the single source of truth driven by env, removing the trypost.ai shim. Default text/image providers flipped to OpenAI. Settings/UX - /settings/workspace split into shadcn Tabs (Workspace / Brand / Users) with three components. - /assets and the in-editor MediaPicker open the ImagePreviewDialog lightbox on image click while preserving action button behaviour. - Comments tab landed via ?tab=comments&comment=<id> from notification click (scroll-to + temporary highlight). - Mention autocomplete popover flips above when near the viewport bottom. - Real social platform PNGs replace Tabler brand glyphs in schedule pills and post list, with hover tooltip carrying display_name + handle. Bug fixes - AcceptInvite: controller now passes workspace + role payload that the Vue page expects; login/register CTAs preselect the invite email. - WorkspaceInvite mailable: stopped referencing nonexistent $invite->workspace and $invite->role; column added to the migration, Invite model casts role to WorkspaceRole, CreateInvite persists it. - PostCommentCreated: added broadcastAs so .PostCommentCreated actually matches the Echo listener; payload now includes mentioned_users so receivers render the chip correctly without a refetch. - Preview components for X/Pinterest/Threads/Bluesky/LinkedIn/Mastodon/ TikTok/YouTube switched from item.type === 'image' to !isVideoMedia(item) so media without a persisted type still renders. - UpdatePostRequest now accepts media.*.{type,mime_type,size,...} so the posts.media JSON keeps the metadata that the previews need. - Removed throttle:6,1 from social connect routes (was 429ing legitimate OAuth retries). - Used MediaType enum cases instead of literal 'image'/'video' strings when creating media rows. Tests - MentionParser unit tests, NotifyMentions feature tests including online/offline channel selection and preference gating, MCP AI tool happy paths, MentionedInComment mailable rendering, AcceptInvite + search-members + index mentioned_users path. 1229 passing.
2026-05-01 23:59:03 +00:00
Route::get('workspace/members/search', [WorkspaceController::class, 'searchMembers'])
->middleware('throttle:60,1')
->name('app.workspace.members.search');
Route::post('presence/heartbeat', [PresenceController::class, 'heartbeat'])
->name('app.presence.heartbeat');
2026-03-31 14:35:49 +00:00
});
// Social Connect routes
Route::middleware(['auth'])->group(function () {
// Starting a connection reads the user's current workspace, so these require
// one — during onboarding they redirect to workspace creation. Disconnecting
// lives here too (and not behind EnsureAccountReady) so it works before a
// subscription exists; the controller still authorizes workspace ownership.
Route::middleware(EnsureHasWorkspace::class)->group(function () {
Route::get('connect/linkedin', [LinkedInController::class, 'connect'])->name('app.social.linkedin.connect');
Route::get('connect/x', [XController::class, 'connect'])->name('app.social.x.connect');
Route::get('connect/tiktok', [TikTokController::class, 'connect'])->name('app.social.tiktok.connect');
Route::get('connect/youtube', [YouTubeController::class, 'connect'])->name('app.social.youtube.connect');
Route::get('connect/facebook', [FacebookController::class, 'connect'])->name('app.social.facebook.connect');
Route::get('connect/instagram', [InstagramController::class, 'connect'])->name('app.social.instagram.connect');
Route::get('connect/instagram-facebook', [InstagramFacebookController::class, 'connect'])->name('app.social.instagram-facebook.connect');
Route::get('connect/threads', [ThreadsController::class, 'connect'])->name('app.social.threads.connect');
Route::get('connect/pinterest', [PinterestController::class, 'connect'])->name('app.social.pinterest.connect');
Route::get('connect/bluesky', [BlueskyController::class, 'connect'])->name('app.social.bluesky.connect');
Route::post('connect/bluesky', [BlueskyController::class, 'store'])->name('app.social.bluesky.store');
Route::get('connect/mastodon', [MastodonController::class, 'connect'])->name('app.social.mastodon.connect');
Route::post('connect/mastodon', [MastodonController::class, 'authorizeInstance'])->name('app.social.mastodon.authorize');
Route::post('connect/telegram', [TelegramController::class, 'connect'])->name('app.social.telegram.connect');
Route::get('connect/discord', [DiscordController::class, 'connect'])->name('app.social.discord.connect');
Route::delete('accounts/{account}', [SocialController::class, 'disconnect'])->name('app.accounts.disconnect');
});
// OAuth callbacks and identity selection resolve their workspace from the
// session set when the flow started, then self-close the popup. They run
// without the current-workspace gate so a momentarily missing current
// workspace can't HTML-redirect the popup instead of closing it cleanly.
2026-03-31 14:35:49 +00:00
Route::get('accounts/linkedin/callback', [LinkedInController::class, 'callback'])->name('app.social.linkedin.callback');
Route::get('accounts/linkedin/select', [LinkedInController::class, 'selectIdentity'])->name('app.social.linkedin.select-identity');
Route::post('accounts/linkedin/select', [LinkedInController::class, 'select'])->name('app.social.linkedin.select');
2026-03-31 14:35:49 +00:00
Route::get('accounts/x/callback', [XController::class, 'callback'])->name('app.social.x.callback');
Route::get('accounts/tiktok/callback', [TikTokController::class, 'callback'])->name('app.social.tiktok.callback');
Route::get('accounts/youtube/callback', [YouTubeController::class, 'callback'])->name('app.social.youtube.callback');
Route::get('accounts/facebook/callback', [FacebookController::class, 'callback'])->name('app.social.facebook.callback');
Route::get('accounts/facebook/select', [FacebookController::class, 'selectPage'])->name('app.social.facebook.select-page');
Route::post('accounts/facebook/select', [FacebookController::class, 'select'])->name('app.social.facebook.select');
Route::get('accounts/instagram/callback', [InstagramController::class, 'callback'])->name('app.social.instagram.callback');
Route::get('accounts/instagram/select', [InstagramController::class, 'selectAccount'])->name('app.social.instagram.select-account');
Route::post('accounts/instagram/select', [InstagramController::class, 'select'])->name('app.social.instagram.select');
2026-04-02 20:57:06 +00:00
Route::get('accounts/instagram-facebook/callback', [InstagramFacebookController::class, 'callback'])->name('app.social.instagram-facebook.callback');
Route::get('accounts/instagram-facebook/select-page', [InstagramFacebookController::class, 'selectPage'])->name('app.social.instagram-facebook.select-page');
Route::post('accounts/instagram-facebook/select', [InstagramFacebookController::class, 'select'])->name('app.social.instagram-facebook.select');
2026-03-31 14:35:49 +00:00
Route::get('accounts/threads/callback', [ThreadsController::class, 'callback'])->name('app.social.threads.callback');
Route::get('accounts/pinterest/callback', [PinterestController::class, 'callback'])->name('app.social.pinterest.callback');
Route::get('accounts/mastodon/callback', [MastodonController::class, 'callback'])->name('app.social.mastodon.callback');
Route::get('accounts/discord/callback', [DiscordController::class, 'callback'])->name('app.social.discord.callback');
2026-03-31 14:35:49 +00:00
});
Activation checklist + MCP OAuth authorize UX (#239) (#250) * Wire onboarding activation into Account, observers, and shared Inertia data Add onboarding casts/hasFinishedOnboarding, AccessToken ObservedBy, Platform::connectableOptions, Post/SocialAccount onboarding broadcast hooks, and lazy onboardingResidual share + SharedData types. * Register onboarding routes and post-checkout activation redirects. Wire billing processing and the sidebar checklist so owners land on activation after subscribe, with locale sidebar/uk onboarding strings. * Align MCP grant usability with onboarding activation checks Unbound MCP tokens fall back to the user's current workspace and require createPost so viewer/unscoped grants neither unlock the checklist nor broadcast onboarding status. * Require bound MCP workspace for onboarding activation. Drop current-workspace fallback from usable MCP grants so checklist detection and broadcasts match Passport token scoping; viewers still cannot unlock the MCP step. Co-authored-by: Cursor <cursoragent@cursor.com> * Harden onboarding review findings and tighten locale strings. Fix Welcome/Persona/TrackPost suites broken by the activation route reuse and PostObserver analytics side effects, restore Echo poll fallbacks, reject unbound MCP grants in tests, and drop unused onboarding.mcp keys. Co-authored-by: Cursor <cursoragent@cursor.com> * Remove unused sidebar and MCP authorization locale keys. Drop dead sidebar menu/theme strings (including the overwritten workspace label and api_keys nav entry) and unused MCP authorize app_title/approving copy across all locales. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix SetLocale crashing on Passport Symfony OAuth responses. OAuth errors return a raw Symfony Response without withCookie(); attach the default locale cookie via headers so authorize no longer 500s. Co-authored-by: Cursor <cursoragent@cursor.com> * Prompt OAuth guests to log in before rejecting unknown clients. MCP Inspector often reuses a stale client_id; validateAuthorizationRequest was returning invalid_client JSON before the login redirect. Guests now hit /login first, then client validation runs after authentication. Co-authored-by: Cursor <cursoragent@cursor.com> * Render Inertia OAuth authorize errors for browser logins. After login, Inertia follows the intended authorize URL; raw invalid_client JSON broke that visit. HTML/Inertia requests now get mcp/AuthorizeError while API JSON clients still receive the OAuth error payload. Co-authored-by: Cursor <cursoragent@cursor.com> * Detect Inertia OAuth error pages via Request::inertia(). Use the framework helper so post-login authorize failures keep returning an Inertia page instead of raw OAuth JSON. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify OAuth authorize error page detection to expectsJson. Drop the X-Inertia header sniff; browser and Inertia visits already do not expectsJson, while API clients still receive the OAuth JSON payload. Co-authored-by: Cursor <cursoragent@cursor.com> * Share MCP authorize layout and drop the error close button. Keep authorize and authorize-error on the same centered card shell instead of the auth split layout. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify onboarding activation for reviewability and safety. Use an exists-based MCP check, keep GETs read-only, move sync into syncAndNotify, clear MCP skips on connect, restrict complete to owners, and share Echo/poll via one composable. Co-authored-by: Cursor <cursoragent@cursor.com> * Move MCP OAuth authorize UX out of the onboarding PR. Keep the activation checklist focused; OAuth guest/error-page work now lives on fix/mcp-oauth-authorize-ux. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix corrupted French MCP locale after OAuth key cleanup. Co-authored-by: Cursor <cursoragent@cursor.com> * Restore MCP OAuth authorize UX onto the onboarding branch. Keep authorize error page, guest login-before-client validation, and SetLocale Symfony cookie fix in #250. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix OAuth prompt=none redirects and harden onboarding tests. Keep login_required/consent_required as redirects instead of Inertia, add regression coverage for owner-only activation, require invite email confirmation, and align MCP connected apps with the sessions list UI. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify onboarding guards and dedupe viewed analytics. Introduce isOnboardingOpen / belongsToAccount helpers, collapse duplicated sync/dispatch paths, and capture onboarding.viewed once per account. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify onboarding event, observers, and status helpers. Tighten Account onboarding predicates, drop nullable broadcast/dispatch APIs, and collapse repeated observer/controller guards. Co-authored-by: Cursor <cursoragent@cursor.com> * Treat in-app users as always having an account. Add resolveAccount(), tighten belongsToAccount to string ids, and fold guest residual handling into ResolveOnboardingStatus. Co-authored-by: Cursor <cursoragent@cursor.com> * Rename onboarding residual share test to progress. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify onboarding status and rename residual to progress. Use accountOrFail, extract MCP onboarding scope, auto-leave the ready screen, and send non-onboarding checkout back to accounts. Co-authored-by: Cursor <cursoragent@cursor.com> * Extract HasAccount and prefer data_get in onboarding flows. Move account helpers off User, drop nullable sidebarProgress, and read OAuth/onboarding payloads with data_get. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify onboarding checks and extract HasOnboarding. Use Eloquent + policies for MCP/backfill paths, and move account onboarding helpers into a dedicated trait. Co-authored-by: Cursor <cursoragent@cursor.com> * Add trait tests and tidy onboarding imports. Cover HasAccount and HasOnboarding under Models/Traits, prefer filled() for checkout session ids, and import Throwable instead of FQCN. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify checkout session_id and OAuth error props. Read session_id via request->string(), and take OAuth error details from the League exception instead of decoding the response body. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify PostObserver onboarding notify path. Share one otherPosts check for first-create and last-delete instead of separate callbacks. Co-authored-by: Cursor <cursoragent@cursor.com> * Use post author as onboarding sync actor. Drop Auth::user() preference in PostObserver; checklist sync attributes to $post->user. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify SocialAccountObserver and OAuth authorize flow. Share create/delete onboarding notify, drop Auth actor fallback to owner, and inline Passport Inertia error handling. Co-authored-by: Cursor <cursoragent@cursor.com> * Use lazy Inertia props for onboarding partial reloads. Drop partial-header branching; wrap page props in closures and always redirect completed/dismissed accounts to the calendar. Co-authored-by: Cursor <cursoragent@cursor.com> * Defer sidebar onboarding progress and stamp completion as owner-only. Skip the MCP checklist work on full Inertia visits via deferred shared props, early-exit token scans, and keep account completion stamps owner-gated. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify deferred onboarding progress share via canShowProgress. Co-authored-by: Cursor <cursoragent@cursor.com> * Add User firstName for shared auth and simplify onboarding page. Co-authored-by: Cursor <cursoragent@cursor.com> * Move User firstName coverage into UserTest. Co-authored-by: Cursor <cursoragent@cursor.com> * Use first_name directly without empty-name fallbacks. Co-authored-by: Cursor <cursoragent@cursor.com> * Resolve onboarding sample prompt on the frontend via i18n. Co-authored-by: Cursor <cursoragent@cursor.com> * Stamp onboarding completion via the account owner after teammate unlocks. Co-authored-by: Cursor <cursoragent@cursor.com> * Count only the account owner MCP grant toward onboarding activation. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix OAuth consent auth-token mismatch for mid-activation owners. Skip deferred onboardingProgress on Passport authorize so Inertia does not rotate the session authToken, cover happy and stale-token paths in tests, and polish MCP setup copy plus sidebar/onboarding layout. Co-authored-by: Cursor <cursoragent@cursor.com> * Keep users on onboarding after activation completes. Stamp completion and re-render the finished checklist instead of redirecting to the calendar so owners can review the done state. Co-authored-by: Cursor <cursoragent@cursor.com> * Clarify Passport consent-view opt-out and guard app-route deferral. Rename the authorize-only route check and assert onboardingProgress still defers on calendar, onboarding, and MCP settings. Co-authored-by: Cursor <cursoragent@cursor.com> * Harden onboarding completion and MCP consent workspace binding. Reject OAuth approve without a workspace, retry auto-complete until stamped, send dismissed complete straight to calendar, and cover the device consent defer opt-out. Co-authored-by: Cursor <cursoragent@cursor.com> * Enable activation checklist for self-hosted installs. Remove the self-hosted onboarding redirects, keep the SaaS-only dismiss backfill, and cover subscription-less owners plus skip/complete destinations. Co-authored-by: Cursor <cursoragent@cursor.com> * Add GitHub, Hacker News, and directories referral sources. Expand the welcome referral step with open-source and directory discovery channels. Co-authored-by: Cursor <cursoragent@cursor.com> * Refine welcome referral sources and labels. Split Instagram/Threads, add Founder, and shorten Google, GitHub, AI, and blog option labels. Co-authored-by: Cursor <cursoragent@cursor.com> * Sort accounts platforms alphabetically and drop connect hover plus. Reuse connectableOptions for the accounts index and remove the unused plus badge on disconnected cards. Co-authored-by: Cursor <cursoragent@cursor.com> * Centralize PostHog once-capture so disabled installs don't burn dedupe keys. Move isEnabled + Cache::add into PostHogService::captureOnce and route onboarding viewed/step events through it. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify onboarding backfill to complete every existing open account. Drop self-hosted and subscription filters; down clears completed_at again. Co-authored-by: Cursor <cursoragent@cursor.com> * Drop PostHog captureOnce and use plain capture for onboarding. Remove cache-based event dedupe; callers rely on PostHogService::capture gating. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-07 23:34:43 +00:00
// Routes that require account access and a current workspace
Route::middleware(['auth', EnsureAccountReady::class, EnsureHasWorkspace::class])->group(function () {
Activation checklist + MCP OAuth authorize UX (#239) (#250) * Wire onboarding activation into Account, observers, and shared Inertia data Add onboarding casts/hasFinishedOnboarding, AccessToken ObservedBy, Platform::connectableOptions, Post/SocialAccount onboarding broadcast hooks, and lazy onboardingResidual share + SharedData types. * Register onboarding routes and post-checkout activation redirects. Wire billing processing and the sidebar checklist so owners land on activation after subscribe, with locale sidebar/uk onboarding strings. * Align MCP grant usability with onboarding activation checks Unbound MCP tokens fall back to the user's current workspace and require createPost so viewer/unscoped grants neither unlock the checklist nor broadcast onboarding status. * Require bound MCP workspace for onboarding activation. Drop current-workspace fallback from usable MCP grants so checklist detection and broadcasts match Passport token scoping; viewers still cannot unlock the MCP step. Co-authored-by: Cursor <cursoragent@cursor.com> * Harden onboarding review findings and tighten locale strings. Fix Welcome/Persona/TrackPost suites broken by the activation route reuse and PostObserver analytics side effects, restore Echo poll fallbacks, reject unbound MCP grants in tests, and drop unused onboarding.mcp keys. Co-authored-by: Cursor <cursoragent@cursor.com> * Remove unused sidebar and MCP authorization locale keys. Drop dead sidebar menu/theme strings (including the overwritten workspace label and api_keys nav entry) and unused MCP authorize app_title/approving copy across all locales. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix SetLocale crashing on Passport Symfony OAuth responses. OAuth errors return a raw Symfony Response without withCookie(); attach the default locale cookie via headers so authorize no longer 500s. Co-authored-by: Cursor <cursoragent@cursor.com> * Prompt OAuth guests to log in before rejecting unknown clients. MCP Inspector often reuses a stale client_id; validateAuthorizationRequest was returning invalid_client JSON before the login redirect. Guests now hit /login first, then client validation runs after authentication. Co-authored-by: Cursor <cursoragent@cursor.com> * Render Inertia OAuth authorize errors for browser logins. After login, Inertia follows the intended authorize URL; raw invalid_client JSON broke that visit. HTML/Inertia requests now get mcp/AuthorizeError while API JSON clients still receive the OAuth error payload. Co-authored-by: Cursor <cursoragent@cursor.com> * Detect Inertia OAuth error pages via Request::inertia(). Use the framework helper so post-login authorize failures keep returning an Inertia page instead of raw OAuth JSON. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify OAuth authorize error page detection to expectsJson. Drop the X-Inertia header sniff; browser and Inertia visits already do not expectsJson, while API clients still receive the OAuth JSON payload. Co-authored-by: Cursor <cursoragent@cursor.com> * Share MCP authorize layout and drop the error close button. Keep authorize and authorize-error on the same centered card shell instead of the auth split layout. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify onboarding activation for reviewability and safety. Use an exists-based MCP check, keep GETs read-only, move sync into syncAndNotify, clear MCP skips on connect, restrict complete to owners, and share Echo/poll via one composable. Co-authored-by: Cursor <cursoragent@cursor.com> * Move MCP OAuth authorize UX out of the onboarding PR. Keep the activation checklist focused; OAuth guest/error-page work now lives on fix/mcp-oauth-authorize-ux. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix corrupted French MCP locale after OAuth key cleanup. Co-authored-by: Cursor <cursoragent@cursor.com> * Restore MCP OAuth authorize UX onto the onboarding branch. Keep authorize error page, guest login-before-client validation, and SetLocale Symfony cookie fix in #250. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix OAuth prompt=none redirects and harden onboarding tests. Keep login_required/consent_required as redirects instead of Inertia, add regression coverage for owner-only activation, require invite email confirmation, and align MCP connected apps with the sessions list UI. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify onboarding guards and dedupe viewed analytics. Introduce isOnboardingOpen / belongsToAccount helpers, collapse duplicated sync/dispatch paths, and capture onboarding.viewed once per account. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify onboarding event, observers, and status helpers. Tighten Account onboarding predicates, drop nullable broadcast/dispatch APIs, and collapse repeated observer/controller guards. Co-authored-by: Cursor <cursoragent@cursor.com> * Treat in-app users as always having an account. Add resolveAccount(), tighten belongsToAccount to string ids, and fold guest residual handling into ResolveOnboardingStatus. Co-authored-by: Cursor <cursoragent@cursor.com> * Rename onboarding residual share test to progress. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify onboarding status and rename residual to progress. Use accountOrFail, extract MCP onboarding scope, auto-leave the ready screen, and send non-onboarding checkout back to accounts. Co-authored-by: Cursor <cursoragent@cursor.com> * Extract HasAccount and prefer data_get in onboarding flows. Move account helpers off User, drop nullable sidebarProgress, and read OAuth/onboarding payloads with data_get. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify onboarding checks and extract HasOnboarding. Use Eloquent + policies for MCP/backfill paths, and move account onboarding helpers into a dedicated trait. Co-authored-by: Cursor <cursoragent@cursor.com> * Add trait tests and tidy onboarding imports. Cover HasAccount and HasOnboarding under Models/Traits, prefer filled() for checkout session ids, and import Throwable instead of FQCN. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify checkout session_id and OAuth error props. Read session_id via request->string(), and take OAuth error details from the League exception instead of decoding the response body. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify PostObserver onboarding notify path. Share one otherPosts check for first-create and last-delete instead of separate callbacks. Co-authored-by: Cursor <cursoragent@cursor.com> * Use post author as onboarding sync actor. Drop Auth::user() preference in PostObserver; checklist sync attributes to $post->user. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify SocialAccountObserver and OAuth authorize flow. Share create/delete onboarding notify, drop Auth actor fallback to owner, and inline Passport Inertia error handling. Co-authored-by: Cursor <cursoragent@cursor.com> * Use lazy Inertia props for onboarding partial reloads. Drop partial-header branching; wrap page props in closures and always redirect completed/dismissed accounts to the calendar. Co-authored-by: Cursor <cursoragent@cursor.com> * Defer sidebar onboarding progress and stamp completion as owner-only. Skip the MCP checklist work on full Inertia visits via deferred shared props, early-exit token scans, and keep account completion stamps owner-gated. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify deferred onboarding progress share via canShowProgress. Co-authored-by: Cursor <cursoragent@cursor.com> * Add User firstName for shared auth and simplify onboarding page. Co-authored-by: Cursor <cursoragent@cursor.com> * Move User firstName coverage into UserTest. Co-authored-by: Cursor <cursoragent@cursor.com> * Use first_name directly without empty-name fallbacks. Co-authored-by: Cursor <cursoragent@cursor.com> * Resolve onboarding sample prompt on the frontend via i18n. Co-authored-by: Cursor <cursoragent@cursor.com> * Stamp onboarding completion via the account owner after teammate unlocks. Co-authored-by: Cursor <cursoragent@cursor.com> * Count only the account owner MCP grant toward onboarding activation. Co-authored-by: Cursor <cursoragent@cursor.com> * Fix OAuth consent auth-token mismatch for mid-activation owners. Skip deferred onboardingProgress on Passport authorize so Inertia does not rotate the session authToken, cover happy and stale-token paths in tests, and polish MCP setup copy plus sidebar/onboarding layout. Co-authored-by: Cursor <cursoragent@cursor.com> * Keep users on onboarding after activation completes. Stamp completion and re-render the finished checklist instead of redirecting to the calendar so owners can review the done state. Co-authored-by: Cursor <cursoragent@cursor.com> * Clarify Passport consent-view opt-out and guard app-route deferral. Rename the authorize-only route check and assert onboardingProgress still defers on calendar, onboarding, and MCP settings. Co-authored-by: Cursor <cursoragent@cursor.com> * Harden onboarding completion and MCP consent workspace binding. Reject OAuth approve without a workspace, retry auto-complete until stamped, send dismissed complete straight to calendar, and cover the device consent defer opt-out. Co-authored-by: Cursor <cursoragent@cursor.com> * Enable activation checklist for self-hosted installs. Remove the self-hosted onboarding redirects, keep the SaaS-only dismiss backfill, and cover subscription-less owners plus skip/complete destinations. Co-authored-by: Cursor <cursoragent@cursor.com> * Add GitHub, Hacker News, and directories referral sources. Expand the welcome referral step with open-source and directory discovery channels. Co-authored-by: Cursor <cursoragent@cursor.com> * Refine welcome referral sources and labels. Split Instagram/Threads, add Founder, and shorten Google, GitHub, AI, and blog option labels. Co-authored-by: Cursor <cursoragent@cursor.com> * Sort accounts platforms alphabetically and drop connect hover plus. Reuse connectableOptions for the accounts index and remove the unused plus badge on disconnected cards. Co-authored-by: Cursor <cursoragent@cursor.com> * Centralize PostHog once-capture so disabled installs don't burn dedupe keys. Move isEnabled + Cache::add into PostHogService::captureOnce and route onboarding viewed/step events through it. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify onboarding backfill to complete every existing open account. Drop self-hosted and subscription filters; down clears completed_at again. Co-authored-by: Cursor <cursoragent@cursor.com> * Drop PostHog captureOnce and use plain capture for onboarding. Remove cache-based event dedupe; callers rely on PostHogService::capture gating. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-07 23:34:43 +00:00
Route::get('onboarding', [OnboardingController::class, 'index'])->name('app.onboarding');
Route::post('onboarding/mcp/skip', [OnboardingController::class, 'skipMcp'])->name('app.onboarding.mcp.skip');
Route::post('onboarding/complete', [OnboardingController::class, 'complete'])->name('app.onboarding.complete');
// Discord — live lookups for the composer (channel picker + mention autocomplete).
// Throttled because they proxy the shared bot's (rate-limited) Discord API.
Route::get('discord/accounts/{account}/channels', [AppDiscordController::class, 'channels'])
->middleware('throttle:60,1')
->name('app.discord.channels');
Route::get('discord/accounts/{account}/mentions', [AppDiscordController::class, 'mentions'])
->middleware('throttle:60,1')
->name('app.discord.mentions');
2026-03-31 14:35:49 +00:00
// Workspaces
Route::get('workspaces', [WorkspaceController::class, 'index'])->name('app.workspaces.index');
Route::post('workspaces/{workspace}/switch', [WorkspaceController::class, 'switch'])->name('app.workspaces.switch');
Route::delete('workspaces/{workspace}', [WorkspaceController::class, 'destroy'])->name('app.workspaces.destroy');
// Workspace settings
Route::get('settings/workspace', [WorkspaceController::class, 'settings'])->name('app.workspace.settings');
Route::put('settings/workspace', [WorkspaceController::class, 'updateSettings'])->name('app.workspace.settings.update');
Route::post('settings/workspace/logo', [WorkspaceController::class, 'uploadLogo'])->name('app.workspace.upload-logo');
Route::delete('settings/workspace/logo', [WorkspaceController::class, 'deleteLogo'])->name('app.workspace.delete-logo');
// Brand settings
Route::get('settings/workspace/brand', [WorkspaceController::class, 'brandSettings'])->name('app.workspace.brand');
2026-03-31 14:35:49 +00:00
// Social Accounts
Route::get('accounts', [SocialController::class, 'index'])->name('app.accounts');
Route::put('accounts/{account}/toggle', [SocialController::class, 'toggleActive'])->name('app.accounts.toggle');
2026-04-02 20:57:06 +00:00
// Analytics
Route::get('analytics', [AnalyticsController::class, 'index'])->name('app.analytics');
Route::get('analytics/{account}', [AnalyticsController::class, 'show'])->name('app.analytics.show');
2026-03-31 14:35:49 +00:00
// Calendar
Route::get('calendar', [PostController::class, 'calendar'])->name('app.calendar');
// Posts
Route::get('posts/{status?}', [PostController::class, 'index'])->name('app.posts.index')->where('status', 'draft|scheduled|published');
Route::get('posts/create', [PostController::class, 'create'])->name('app.posts.create');
2026-03-31 14:35:49 +00:00
Route::post('posts', [PostController::class, 'store'])->name('app.posts.store');
Route::get('posts/{post}/edit', [PostController::class, 'edit'])->name('app.posts.edit');
Route::get('posts/{post}', [PostController::class, 'show'])->name('app.posts.show');
Route::get('posts/{post}/platforms/{postPlatform}/metrics', [PostController::class, 'platformMetrics'])->name('app.posts.platforms.metrics');
2026-03-31 14:35:49 +00:00
Route::put('posts/{post}', [PostController::class, 'update'])->name('app.posts.update');
Route::delete('posts/{post}', [PostController::class, 'destroy'])->name('app.posts.destroy');
Route::post('posts/{post}/duplicate', [PostController::class, 'duplicate'])->name('app.posts.duplicate');
Route::post('posts/link-preview', LinkPreviewController::class)
->middleware('throttle:30,1')
->name('app.posts.link-preview');
2026-03-31 14:35:49 +00:00
// Post AI
Route::post('posts/{post}/ai/generate', [PostAiGenerateController::class, 'generate'])->name('app.posts.ai.generate');
Route::post('posts/{post}/media/{mediaId}/ai/regenerate', [PostAiRegenerateMediaController::class, 'regenerate'])->name('app.posts.ai.regenerate-media');
Route::post('posts/{post}/ai/review', [PostAiReviewController::class, 'review'])->name('app.posts.ai.review');
Route::post('posts/ai/create', [PostAiCreateController::class, 'start'])->name('app.posts.ai.create');
Route::get('posts/ai/{creationId}/loading', [PostAiCreateController::class, 'loading'])->name('app.posts.ai.loading')->whereUuid('creationId');
// Post Comments
Route::get('posts/{post}/comments', [PostCommentController::class, 'index'])->name('app.posts.comments.index');
Route::post('posts/{post}/comments', [PostCommentController::class, 'store'])->name('app.posts.comments.store');
Route::put('posts/{post}/comments/{comment}', [PostCommentController::class, 'update'])->name('app.posts.comments.update');
Route::delete('posts/{post}/comments/{comment}', [PostCommentController::class, 'destroy'])->name('app.posts.comments.destroy');
Route::post('posts/{post}/comments/{comment}/react', [PostCommentController::class, 'react'])->name('app.posts.comments.react');
2026-03-31 14:35:49 +00:00
// Members
Route::get('settings/workspace/members', [WorkspaceInviteController::class, 'index'])->name('app.members');
Route::post('settings/workspace/members/invites', [WorkspaceInviteController::class, 'store'])->name('app.invites.store');
Route::delete('settings/workspace/members/invites/{invite}', [WorkspaceInviteController::class, 'destroy'])->name('app.invites.destroy');
Route::delete('settings/workspace/members/{user}', [WorkspaceInviteController::class, 'removeMember'])->name('app.members.remove');
Route::put('settings/workspace/members/{user}/role', [WorkspaceInviteController::class, 'updateRole'])->name('app.members.update-role');
2026-03-31 14:35:49 +00:00
// Signatures
Route::get('signatures', [WorkspaceSignatureController::class, 'index'])->name('app.signatures.index');
Route::post('signatures', [WorkspaceSignatureController::class, 'store'])->name('app.signatures.store');
Route::put('signatures/{signature}', [WorkspaceSignatureController::class, 'update'])->name('app.signatures.update');
Route::delete('signatures/{signature}', [WorkspaceSignatureController::class, 'destroy'])->name('app.signatures.destroy');
2026-03-31 14:35:49 +00:00
// Assets
Route::get('assets', [AssetController::class, 'index'])->name('app.assets.index');
Route::get('assets/search', [AssetController::class, 'search'])->name('app.assets.search');
Route::post('assets', [AssetController::class, 'store'])->name('app.assets.store');
Route::post('assets/chunked', [AssetController::class, 'storeChunked'])->name('app.assets.store-chunked');
Route::post('assets/from-url', [AssetController::class, 'storeFromUrl'])->name('app.assets.store-from-url');
Route::delete('assets/{media}', [AssetController::class, 'destroy'])->name('app.assets.destroy');
Route::get('assets/unsplash/search', [UnsplashController::class, 'search'])->name('app.assets.unsplash.search');
Route::get('assets/unsplash/trending', [UnsplashController::class, 'trending'])->name('app.assets.unsplash.trending');
Route::get('assets/giphy/search', [GiphyController::class, 'search'])->name('app.assets.giphy.search');
Route::get('assets/giphy/trending', [GiphyController::class, 'trending'])->name('app.assets.giphy.trending');
2026-03-31 14:35:49 +00:00
// Labels
Route::get('labels', [WorkspaceLabelController::class, 'index'])->name('app.labels.index');
Route::post('labels', [WorkspaceLabelController::class, 'store'])->name('app.labels.store');
Route::put('labels/{label}', [WorkspaceLabelController::class, 'update'])->name('app.labels.update');
Route::delete('labels/{label}', [WorkspaceLabelController::class, 'destroy'])->name('app.labels.destroy');
// API Keys
Route::get('settings/workspace/api-keys', [ApiKeyController::class, 'index'])->name('app.api-keys.index');
Route::post('settings/workspace/api-keys', [ApiKeyController::class, 'store'])->name('app.api-keys.store');
Route::delete('settings/workspace/api-keys/{tokenId}', [ApiKeyController::class, 'destroy'])->name('app.api-keys.destroy');
2026-03-31 14:35:49 +00:00
MCP: workspace settings, viewer read access, and token access (#241) * Add workspace MCP settings and token access controls. Ship MCP settings UI, OAuth revoke/list helpers, Passport deploy wiring, and workspace.token:mcp gating so assistants can connect without pulling in welcome/onboarding from the parent epic. Co-authored-by: Cursor <cursoragent@cursor.com> * Type MCP client config shapes instead of string checks. Encode http/config-root on each advanced client and tighten primary client ids so snippet generation does not branch on magic strings. Co-authored-by: Cursor <cursoragent@cursor.com> * Polish MCP settings follow-ups from review. Translate Ukrainian MCP copy, deep-link ChatGPT into connector creation, drop an unused asset and revoke arg, and assert PATs are rejected on the MCP endpoint. Co-authored-by: Cursor <cursoragent@cursor.com> * Harden MCP connected clients, revoke scope, and OAuth consent. List recoverable sessions with live refresh tokens, revoke only PATs, throttle registration alone, and block viewers from authorizing MCP. Co-authored-by: Cursor <cursoragent@cursor.com> * Simplify MCP OAuth route throttling to a single middleware group. Co-authored-by: Cursor <cursoragent@cursor.com> * Allow workspace viewers read-only MCP access with web policy writes. Mirror the web app: MCP connects on view + OAuth mcp:use, write tools enforce createPost/update/delete/manageAccounts/manageTeam, and demotion to Viewer keeps grants. Cover role denials, consent, and disconnect. Co-authored-by: Cursor <cursoragent@cursor.com> * Harden MCP tool authz with shared workspace helpers. Route ApiKey tools through AuthorizesMcpTool, fail closed on null user or policy argument, and resolve the current workspace before mutating. Co-authored-by: Cursor <cursoragent@cursor.com> * Drop redundant string casts on validated request data. Enum::from and validated() fields are already strings, so the casts add noise without changing behavior. Co-authored-by: Cursor <cursoragent@cursor.com> * Show only the current user's MCP connections in settings. Match API keys privacy: list and disconnect your own OAuth clients, not teammates' across the account. Co-authored-by: Cursor <cursoragent@cursor.com> * Cover LoadWorkspaceFromToken gaps and harden AuthorizesMcpTool tests. Co-authored-by: Cursor <cursoragent@cursor.com> * Drop redundant is_string guard before UpdatePostTool find. Co-authored-by: Cursor <cursoragent@cursor.com> * Refactor AppSidebar to always show MCP link and simplify route middleware definition in ai.php. The MCP link is now consistently displayed regardless of the current workspace state, and the route middleware syntax has been streamlined. * Refresh MCP connected clients with Inertia usePoll. Co-authored-by: Cursor <cursoragent@cursor.com> * Bump laravel/mcp to 0.9.1 and add the TryPost server icon. Requires laravel/boost 2.5 for the Icon attribute; expose images/trypost/icon.png on TryPostServer. Co-authored-by: Cursor <cursoragent@cursor.com> * Drop no-op ReflectionClass import in TryPostServerTest. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-06 12:54:51 +00:00
// MCP
Route::get('settings/workspace/mcp', [McpSettingsController::class, 'index'])->name('app.mcp.index');
Route::delete('settings/workspace/mcp/{client}', [McpSettingsController::class, 'disconnect'])->name('app.mcp.disconnect');
Add workspace webhooks and drop the unused automation webhook node (#326) * Add workspace webhooks and drop the unused automation webhook node. Give workspaces HMAC-signed outgoing webhooks for the post lifecycle, with retry, auto-pause, replay, and live logs, and keep HTTP Request as the only outbound automation node. * Tighten webhook controller and validation after review. Drop the redundant workspace redirects, prune logs without counting, and validate events/status with Rule::enum. * Move leftover webhook UI copy behind i18n. HTTP status phrases, delete-cancel, and validation attribute names were still English literals. * Build the webhook-paused email through Maizzle. The hand-written Blade skipped the shared layout, header, and footer used by the other mail templates. * Cover real webhook dispatch paths and restyle the webhook pages. * Ask for the shared delete keyword when confirming a webhook delete. The endpoint URL is a poor confirm string; posts and assets already use the common "delete" keyword. * Fix webhook review blockers so CI can go green. Drop leftover French automation keys, stop mutating Inertia log props, and show delivered_at instead of created_at. * Close the remaining webhook review gaps. Keep Echo log updates across infinite scroll, align the channel with the policy, persist log ids across retries, and fail unknown automation nodes without throwing. * Stop webhook delivery after disable and record last sent only on success. Queued jobs now skip paused or disabled endpoints unless the user replays, and changing the URL re-pings it first. * Limit webhooks to owners and admins, and encrypt signing secrets. Members can no longer create or inspect outgoing integrations, and secrets stay encrypted at rest. * Cover webhook secret hiding, skip-ping, and failed-delivery edges. * Send the full post on webhooks after labels and platforms are saved. * Fix webhook payloads for integer media ids and type webhook status. * Split the webhook show page into focused components. * Reset live webhook logs when switching endpoints. * Keep the newest webhook logs at the top after live merges. * Cast media item ids to string without the extra scalar check. * Add post.unscheduled webhooks and put the log id on the envelope. Unscheduling is now a first-class event, and receivers can send the delivery id back so we can find the matching log. * Translate webhook event names in the UI. * Make the webhook show page full-width and stop stacking flash toasts. * Translate remaining webhook UI copy in every locale. * Sign webhook pings and drop author email from the payload. * Send signed webhook tests after create instead of pinging on save. Create and update only block private URLs so the receiver can copy the secret first. The show page then sends a signed webhook.test with an object data envelope. * Polish webhook test UX and always mint the dispatch log id in the job. Keep send-test in the actions menu (its own group) and drop the leftover constructor param so retries reuse the serialized id instead of a caller-supplied one.
2026-09-04 12:43:29 +00:00
// Webhooks
Route::get('webhooks', [WebhookController::class, 'index'])->name('app.webhooks.index');
Route::post('webhooks', [WebhookController::class, 'store'])->name('app.webhooks.store');
Route::get('webhooks/{webhook}', [WebhookController::class, 'show'])->name('app.webhooks.show');
Route::put('webhooks/{webhook}', [WebhookController::class, 'update'])->name('app.webhooks.update');
Route::post('webhooks/{webhook}/send-test', [WebhookController::class, 'sendTest'])->name('app.webhooks.send-test');
Route::post('webhooks/{webhook}/rotate-secret', [WebhookController::class, 'rotateSecret'])->name('app.webhooks.rotate-secret');
Route::post('webhooks/{webhook}/logs/{webhookLog}/replay', [WebhookController::class, 'replay'])->name('app.webhooks.replay');
Route::delete('webhooks/{webhook}', [WebhookController::class, 'destroy'])->name('app.webhooks.destroy');
// Account Settings
Route::get('settings/account', [AccountController::class, 'edit'])->name('app.account.edit');
Route::put('settings/account', [AccountController::class, 'update'])->name('app.account.update');
Route::get('settings/account/usage', [UsageController::class, 'index'])->name('app.usage.index');
2026-03-31 14:35:49 +00:00
// Billing
Route::get('settings/account/billing', [BillingController::class, 'index'])->name('app.billing.index');
Route::get('settings/account/billing/portal', [BillingController::class, 'portal'])->name('app.billing.portal');
Route::post('settings/account/billing/swap-to-yearly', [BillingController::class, 'swapToYearly'])->name('app.billing.swap-to-yearly');
2026-03-31 14:35:49 +00:00
});
// Notifications (auth only, no subscription required)
Route::middleware(['auth'])->group(function () {
2026-03-31 14:35:49 +00:00
Route::get('notifications', [NotificationController::class, 'index'])->name('app.notifications.index');
Route::put('notifications/{notification}/read', [NotificationController::class, 'markAsRead'])->name('app.notifications.read');
Route::post('notifications/read-all', [NotificationController::class, 'markAllAsRead'])->name('app.notifications.read-all');
Route::post('notifications/archive-all', [NotificationController::class, 'archiveAll'])->name('app.notifications.archive-all');
});
// Settings (auth required)
Route::middleware(['auth'])->group(function () {
Route::get('settings', [SettingsController::class, 'index'])->name('app.settings');
2026-03-31 14:35:49 +00:00
Route::get('settings/profile', [ProfileController::class, 'edit'])->name('app.profile.edit');
Route::put('settings/profile', [ProfileController::class, 'update'])->name('app.profile.update');
Route::post('settings/profile/photo', [ProfileController::class, 'uploadPhoto'])->name('app.profile.upload-photo');
Route::delete('settings/profile/photo', [ProfileController::class, 'deletePhoto'])->name('app.profile.delete-photo');
Route::put('settings/language', [ProfileController::class, 'updateLanguage'])->name('app.profile.language');
});
Route::middleware(['auth'])->group(function () {
2026-03-31 14:35:49 +00:00
Route::delete('settings/profile', [ProfileController::class, 'destroy'])->name('app.profile.destroy');
Route::get('settings/authentication', [AuthenticationController::class, 'edit'])->name('app.authentication.edit');
Route::put('settings/authentication/password', [AuthenticationController::class, 'updatePassword'])
2026-03-31 14:35:49 +00:00
->middleware('throttle:6,1')
->name('app.authentication.update-password');
Route::delete('settings/authentication/sessions', [AuthenticationController::class, 'destroyOtherSessions'])
->name('app.authentication.destroy-other-sessions');
Route::get('settings/authentication/providers/{provider}/connect', [AuthenticationController::class, 'connectProvider'])
->name('app.authentication.connect-provider');
Route::delete('settings/authentication/providers/{provider}', [AuthenticationController::class, 'disconnectProvider'])
->name('app.authentication.disconnect-provider');
Route::get('settings/profile/notifications', [NotificationPreferenceController::class, 'edit'])->name('app.notifications.preferences');
Route::put('settings/profile/notifications', [NotificationPreferenceController::class, 'update'])->name('app.notifications.preferences.update');
2026-03-31 14:35:49 +00:00
});