2026-03-29 22:24:28 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
2026-04-02 20:57:06 +00:00
|
|
|
use App\Http\Controllers\App\AnalyticsController;
|
2026-03-29 22:24:28 +00:00
|
|
|
use App\Http\Controllers\App\ApiKeyController;
|
2026-04-15 13:20:37 +00:00
|
|
|
use App\Http\Controllers\App\AssetController;
|
2026-03-29 22:24:28 +00:00
|
|
|
use App\Http\Controllers\App\BillingController;
|
feat(channels): add Discord as a social channel
Connect a Discord server via OAuth (bot authorization) and schedule/publish
messages to its channels, with mentions and rich embeds.
- Connect: custom Socialite Discord provider (bot scope) maps the authorized
guild to a SocialAccount; throws if no server was authorized.
- Publish: DiscordPublisher posts via the global bot token, validates the chosen
channel belongs to the connected guild (anti cross-guild), optimizes media,
builds allowed_mentions only from explicit mention chips (no accidental pings),
and renders rich embeds.
- Compose: per-post channel picker (live lookup), mention autocomplete and an
embed editor, gated by a required-channel compliance rule; Discord post preview.
- Enum/config/content-type wiring, ConnectionVerifier health check, throttled
lookup endpoints, i18n (en/es/pt-BR), and tests.
Operators must create a Discord application and set DISCORD_CLIENT_ID,
DISCORD_CLIENT_SECRET, DISCORD_BOT_TOKEN and DISCORD_CLIENT_REDIRECT.
2026-06-16 17:44:00 +00:00
|
|
|
use App\Http\Controllers\App\DiscordController as AppDiscordController;
|
2026-04-15 14:02:10 +00:00
|
|
|
use App\Http\Controllers\App\GiphyController;
|
2026-07-17 17:32:40 +00:00
|
|
|
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;
|
feat: notification system with SendNotification job, dialog UI, tests
Backend:
- Create notifications table (user_id, workspace_id, type, channel,
title, body, data JSON, read_at, archived_at)
- Create Notification model with Type enum (post_failed,
account_disconnected, invite_received, member_joined, member_removed)
and Channel enum (email, in_app, both)
- Create SendNotification job: isolated from publish flow, handles
saving in-app notification and sending email independently
- NotificationController: index (excludes archived, scoped to workspace),
markAsRead, markAllAsRead, archiveAll
- Integrate with PublishToSocialPlatform (post failed/partial)
- Integrate with VerifyWorkspaceConnections (batch disconnection)
- Integrate with SocialAccount::markAsDisconnected (single disconnection)
- All use SendNotification::dispatch() instead of direct Mail::to()
Frontend:
- NotificationBell component in sidebar footer with unread badge
- Dialog with notification list, mark as read, mark all read, archive all
- Click navigates to relevant page (post edit, accounts)
- i18n for notifications UI (en, es, pt-BR)
Tests:
- 8 tests for NotificationController (auth, CRUD, workspace scoping)
- 4 tests for SendNotification job (channels, email, data storage)
All 745 tests passing.
2026-03-30 19:47:03 +00:00
|
|
|
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;
|
2026-05-03 12:36:50 +00:00
|
|
|
use App\Http\Controllers\App\PostAiCreateController;
|
|
|
|
|
use App\Http\Controllers\App\PostAiGenerateController;
|
2026-05-21 19:27:55 +00:00
|
|
|
use App\Http\Controllers\App\PostAiRegenerateMediaController;
|
2026-05-03 12:36:50 +00:00
|
|
|
use App\Http\Controllers\App\PostAiReviewController;
|
2026-04-15 23:10:45 +00:00
|
|
|
use App\Http\Controllers\App\PostCommentController;
|
2026-03-29 22:24:28 +00:00
|
|
|
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;
|
feat: redesign billing, onboarding, sidebar, and settings architecture
- Sidebar reorganized: Workspace group (connections, hashtags, labels,
API keys, settings) and Account group (settings, usage, billing)
- Account group only visible to owner and hidden in self-hosted mode
- Onboarding simplified: role -> account (connect socials) -> completed
-> redirect to /subscribe. Removed Subscription setup step.
- Subscribe page redesigned with 4 plan cards, monthly/yearly toggle,
trial info, and per-plan features list
- Billing page redesigned following Sendkit layout (sections with
sidebar labels)
- Processing page uses usePoll with immediate watch for subscription
activation
- Cancel URL redirects directly to /subscribe
- Account settings page with name and billing_email (syncs with Stripe)
- Usage page with ring meters for all plan limits
- Settings layout tabs only for user pages (profile, password,
notifications). Workspace/API keys/billing are standalone pages.
- GoogleAuthButton extracted as reusable component
- WorkspaceRole TypeScript enum for type-safe role checks in frontend
- Trial period changed to 7 days
- Fixed onboarding loop when user confirms email
- All 1101 tests passing
2026-04-15 03:33:38 +00:00
|
|
|
use App\Http\Controllers\App\Settings\AccountController;
|
2026-05-03 20:42:44 +00:00
|
|
|
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;
|
2026-03-29 22:24:28 +00:00
|
|
|
use App\Http\Controllers\App\Settings\ProfileController;
|
2026-05-03 18:54:26 +00:00
|
|
|
use App\Http\Controllers\App\Settings\SettingsController;
|
feat: redesign billing, onboarding, sidebar, and settings architecture
- Sidebar reorganized: Workspace group (connections, hashtags, labels,
API keys, settings) and Account group (settings, usage, billing)
- Account group only visible to owner and hidden in self-hosted mode
- Onboarding simplified: role -> account (connect socials) -> completed
-> redirect to /subscribe. Removed Subscription setup step.
- Subscribe page redesigned with 4 plan cards, monthly/yearly toggle,
trial info, and per-plan features list
- Billing page redesigned following Sendkit layout (sections with
sidebar labels)
- Processing page uses usePoll with immediate watch for subscription
activation
- Cancel URL redirects directly to /subscribe
- Account settings page with name and billing_email (syncs with Stripe)
- Usage page with ring meters for all plan limits
- Settings layout tabs only for user pages (profile, password,
notifications). Workspace/API keys/billing are standalone pages.
- GoogleAuthButton extracted as reusable component
- WorkspaceRole TypeScript enum for type-safe role checks in frontend
- Trial period changed to 7 days
- Fixed onboarding loop when user confirms email
- All 1101 tests passing
2026-04-15 03:33:38 +00:00
|
|
|
use App\Http\Controllers\App\Settings\UsageController;
|
2026-04-15 13:20:37 +00:00
|
|
|
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;
|
2026-08-06 14:34:50 +00:00
|
|
|
use App\Http\Controllers\App\WelcomeController;
|
2026-03-29 22:24:28 +00:00
|
|
|
use App\Http\Controllers\App\WorkspaceController;
|
|
|
|
|
use App\Http\Controllers\App\WorkspaceInviteController;
|
|
|
|
|
use App\Http\Controllers\App\WorkspaceLabelController;
|
2026-05-03 18:23:30 +00:00
|
|
|
use App\Http\Controllers\App\WorkspaceSignatureController;
|
2026-03-29 22:24:28 +00:00
|
|
|
use App\Http\Controllers\Auth\BlueskyController;
|
feat(channels): add Discord as a social channel
Connect a Discord server via OAuth (bot authorization) and schedule/publish
messages to its channels, with mentions and rich embeds.
- Connect: custom Socialite Discord provider (bot scope) maps the authorized
guild to a SocialAccount; throws if no server was authorized.
- Publish: DiscordPublisher posts via the global bot token, validates the chosen
channel belongs to the connected guild (anti cross-guild), optimizes media,
builds allowed_mentions only from explicit mention chips (no accidental pings),
and renders rich embeds.
- Compose: per-post channel picker (live lookup), mention autocomplete and an
embed editor, gated by a required-channel compliance rule; Discord post preview.
- Enum/config/content-type wiring, ConnectionVerifier health check, throttled
lookup endpoints, i18n (en/es/pt-BR), and tests.
Operators must create a Discord application and set DISCORD_CLIENT_ID,
DISCORD_CLIENT_SECRET, DISCORD_BOT_TOKEN and DISCORD_CLIENT_REDIRECT.
2026-06-16 17:44:00 +00:00
|
|
|
use App\Http\Controllers\Auth\DiscordController;
|
2026-03-29 22:24:28 +00:00
|
|
|
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;
|
2026-03-29 22:24:28 +00:00
|
|
|
use App\Http\Controllers\Auth\LinkedInController;
|
|
|
|
|
use App\Http\Controllers\Auth\MastodonController;
|
|
|
|
|
use App\Http\Controllers\Auth\PinterestController;
|
|
|
|
|
use App\Http\Controllers\Auth\SocialController;
|
Add Telegram connection flow (controller + webhook)
Connect a channel by issuing a one-time code the user posts as /connect <code>
in their channel. A secret-token-guarded webhook matches the code, links the
channel as a SocialAccount (chat_id in meta), and records it on the request so
the connect endpoint can poll for completion. Adds the TelegramConnectRequest
model + migration, the connect/status endpoints, the public webhook route (CSRF
exempt), a ConnectionVerifier branch (getChat liveness), and a telegram:set-webhook
command. Tests cover the code issue, webhook link, secret rejection, expired/
unknown codes, status polling, and the command.
2026-06-14 00:39:03 +00:00
|
|
|
use App\Http\Controllers\Auth\TelegramController;
|
2026-03-29 22:24:28 +00:00
|
|
|
use App\Http\Controllers\Auth\ThreadsController;
|
|
|
|
|
use App\Http\Controllers\Auth\TikTokController;
|
|
|
|
|
use App\Http\Controllers\Auth\XController;
|
|
|
|
|
use App\Http\Controllers\Auth\YouTubeController;
|
2026-04-17 02:05:51 +00:00
|
|
|
use App\Http\Middleware\App\EnsureAccountReady;
|
2026-06-25 17:30:15 +00:00
|
|
|
use App\Http\Middleware\App\EnsureHasWorkspace;
|
2026-03-29 22:24:28 +00:00
|
|
|
use Illuminate\Support\Facades\Route;
|
|
|
|
|
|
2026-03-31 14:35:49 +00:00
|
|
|
// Subscription selection (requires auth but not subscription)
|
2026-05-02 16:30:59 +00:00
|
|
|
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');
|
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');
|
|
|
|
|
|
2026-04-17 02:05:51 +00:00
|
|
|
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')
|
2026-04-17 02:05:51 +00:00
|
|
|
->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
|
2026-06-25 18:34:03 +00:00
|
|
|
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');
|
2026-06-25 00:05:09 +00:00
|
|
|
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');
|
Add Telegram connection flow (controller + webhook)
Connect a channel by issuing a one-time code the user posts as /connect <code>
in their channel. A secret-token-guarded webhook matches the code, links the
channel as a SocialAccount (chat_id in meta), and records it on the request so
the connect endpoint can poll for completion. Adds the TelegramConnectRequest
model + migration, the connect/status endpoints, the public webhook route (CSRF
exempt), a ConnectionVerifier branch (getChat liveness), and a telegram:set-webhook
command. Tests cover the code issue, webhook link, secret rejection, expired/
unknown codes, status polling, and the command.
2026-06-14 00:39:03 +00:00
|
|
|
|
feat(channels): add Discord as a social channel
Connect a Discord server via OAuth (bot authorization) and schedule/publish
messages to its channels, with mentions and rich embeds.
- Connect: custom Socialite Discord provider (bot scope) maps the authorized
guild to a SocialAccount; throws if no server was authorized.
- Publish: DiscordPublisher posts via the global bot token, validates the chosen
channel belongs to the connected guild (anti cross-guild), optimizes media,
builds allowed_mentions only from explicit mention chips (no accidental pings),
and renders rich embeds.
- Compose: per-post channel picker (live lookup), mention autocomplete and an
embed editor, gated by a required-channel compliance rule; Discord post preview.
- Enum/config/content-type wiring, ConnectionVerifier health check, throttled
lookup endpoints, i18n (en/es/pt-BR), and tests.
Operators must create a Discord application and set DISCORD_CLIENT_ID,
DISCORD_CLIENT_SECRET, DISCORD_BOT_TOKEN and DISCORD_CLIENT_REDIRECT.
2026-06-16 17:44:00 +00:00
|
|
|
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
|
2026-06-25 17:30:15 +00:00
|
|
|
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');
|
|
|
|
|
|
feat(channels): add Discord as a social channel
Connect a Discord server via OAuth (bot authorization) and schedule/publish
messages to its channels, with mentions and rich embeds.
- Connect: custom Socialite Discord provider (bot scope) maps the authorized
guild to a SocialAccount; throws if no server was authorized.
- Publish: DiscordPublisher posts via the global bot token, validates the chosen
channel belongs to the connected guild (anti cross-guild), optimizes media,
builds allowed_mentions only from explicit mention chips (no accidental pings),
and renders rich embeds.
- Compose: per-post channel picker (live lookup), mention autocomplete and an
embed editor, gated by a required-channel compliance rule; Discord post preview.
- Enum/config/content-type wiring, ConnectionVerifier health check, throttled
lookup endpoints, i18n (en/es/pt-BR), and tests.
Operators must create a Discord application and set DISCORD_CLIENT_ID,
DISCORD_CLIENT_SECRET, DISCORD_BOT_TOKEN and DISCORD_CLIENT_REDIRECT.
2026-06-16 17:44:00 +00:00
|
|
|
// 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');
|
|
|
|
|
|
2026-05-02 15:22:42 +00:00
|
|
|
// Brand settings
|
2026-05-03 16:44:13 +00:00
|
|
|
Route::get('settings/workspace/brand', [WorkspaceController::class, 'brandSettings'])->name('app.workspace.brand');
|
2026-05-02 15:22:42 +00:00
|
|
|
|
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');
|
2026-05-03 12:36:50 +00:00
|
|
|
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');
|
2026-05-02 15:22:42 +00:00
|
|
|
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');
|
2026-05-04 01:37:51 +00:00
|
|
|
Route::post('posts/{post}/duplicate', [PostController::class, 'duplicate'])->name('app.posts.duplicate');
|
2026-07-17 17:32:40 +00:00
|
|
|
Route::post('posts/link-preview', LinkPreviewController::class)
|
|
|
|
|
->middleware('throttle:30,1')
|
|
|
|
|
->name('app.posts.link-preview');
|
2026-03-31 14:35:49 +00:00
|
|
|
|
2026-05-03 12:36:50 +00:00
|
|
|
// Post AI
|
|
|
|
|
Route::post('posts/{post}/ai/generate', [PostAiGenerateController::class, 'generate'])->name('app.posts.ai.generate');
|
2026-05-21 19:27:55 +00:00
|
|
|
Route::post('posts/{post}/media/{mediaId}/ai/regenerate', [PostAiRegenerateMediaController::class, 'regenerate'])->name('app.posts.ai.regenerate-media');
|
2026-05-03 12:36:50 +00:00
|
|
|
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');
|
2026-05-08 21:30:11 +00:00
|
|
|
Route::get('posts/ai/{creationId}/loading', [PostAiCreateController::class, 'loading'])->name('app.posts.ai.loading')->whereUuid('creationId');
|
2026-05-03 12:36:50 +00:00
|
|
|
|
2026-04-15 23:10:45 +00:00
|
|
|
// 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
|
2026-05-03 16:44:13 +00:00
|
|
|
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
|
|
|
|
2026-05-03 18:23:30 +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
|
|
|
|
2026-04-15 13:20:37 +00:00
|
|
|
// Assets
|
|
|
|
|
Route::get('assets', [AssetController::class, 'index'])->name('app.assets.index');
|
feat: media gallery picker, custom emoji picker, preview tabs, real platform logos
- gallery: extract /assets tabs (uploads, Unsplash, Giphy) into shared
GalleryBrowser used by both /assets and a new MediaPickerDialog inside the
post editor; add JSON search endpoint for workspace assets with tests
- emoji: replace broken emoji-picker-element web component with a custom
EmojiPicker (full Unicode set, search, categories, recently-used,
light/dark, i18n)
- preview tab: platform selector pills, variant tabs (data-driven from
content_types map) so the user can switch Feed/Reel/Story etc. and have
it autosave through the same handler ScheduleTab uses
- platform logos: shared usePlatformLogo composable (logo + label + content
types); replaces inline maps across 5 components, fixes
instagram-facebook falling back to default.png
- tooltips: hover details (display_name · @username + platform label) on
platform avatars across editor, posts list and calendar
- settings cards: show ` · @username` in the title bar so multiple accounts
on the same network are distinguishable
- routes: drop the throttle:6,1 group middleware on social connect routes
(was 429ing legitimate OAuth retries) and rely on the default limiter
2026-05-01 17:53:49 +00:00
|
|
|
Route::get('assets/search', [AssetController::class, 'search'])->name('app.assets.search');
|
2026-04-15 13:20:37 +00:00
|
|
|
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');
|
2026-04-15 14:02:10 +00:00
|
|
|
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-04-15 13:20:37 +00:00
|
|
|
|
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
|
2026-05-03 16:44:13 +00:00
|
|
|
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');
|
2026-05-03 21:38:17 +00:00
|
|
|
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');
|
|
|
|
|
|
feat: redesign billing, onboarding, sidebar, and settings architecture
- Sidebar reorganized: Workspace group (connections, hashtags, labels,
API keys, settings) and Account group (settings, usage, billing)
- Account group only visible to owner and hidden in self-hosted mode
- Onboarding simplified: role -> account (connect socials) -> completed
-> redirect to /subscribe. Removed Subscription setup step.
- Subscribe page redesigned with 4 plan cards, monthly/yearly toggle,
trial info, and per-plan features list
- Billing page redesigned following Sendkit layout (sections with
sidebar labels)
- Processing page uses usePoll with immediate watch for subscription
activation
- Cancel URL redirects directly to /subscribe
- Account settings page with name and billing_email (syncs with Stripe)
- Usage page with ring meters for all plan limits
- Settings layout tabs only for user pages (profile, password,
notifications). Workspace/API keys/billing are standalone pages.
- GoogleAuthButton extracted as reusable component
- WorkspaceRole TypeScript enum for type-safe role checks in frontend
- Trial period changed to 7 days
- Fixed onboarding loop when user confirms email
- All 1101 tests passing
2026-04-15 03:33:38 +00:00
|
|
|
// Account Settings
|
|
|
|
|
Route::get('settings/account', [AccountController::class, 'edit'])->name('app.account.edit');
|
|
|
|
|
Route::put('settings/account', [AccountController::class, 'update'])->name('app.account.update');
|
2026-05-03 16:44:13 +00:00
|
|
|
Route::get('settings/account/usage', [UsageController::class, 'index'])->name('app.usage.index');
|
feat: redesign billing, onboarding, sidebar, and settings architecture
- Sidebar reorganized: Workspace group (connections, hashtags, labels,
API keys, settings) and Account group (settings, usage, billing)
- Account group only visible to owner and hidden in self-hosted mode
- Onboarding simplified: role -> account (connect socials) -> completed
-> redirect to /subscribe. Removed Subscription setup step.
- Subscribe page redesigned with 4 plan cards, monthly/yearly toggle,
trial info, and per-plan features list
- Billing page redesigned following Sendkit layout (sections with
sidebar labels)
- Processing page uses usePoll with immediate watch for subscription
activation
- Cancel URL redirects directly to /subscribe
- Account settings page with name and billing_email (syncs with Stripe)
- Usage page with ring meters for all plan limits
- Settings layout tabs only for user pages (profile, password,
notifications). Workspace/API keys/billing are standalone pages.
- GoogleAuthButton extracted as reusable component
- WorkspaceRole TypeScript enum for type-safe role checks in frontend
- Trial period changed to 7 days
- Fixed onboarding loop when user confirms email
- All 1101 tests passing
2026-04-15 03:33:38 +00:00
|
|
|
|
2026-03-31 14:35:49 +00:00
|
|
|
// Billing
|
2026-05-03 16:44:13 +00:00
|
|
|
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');
|
feat: per-workspace pricing, onboarding, and billing overhaul
Pricing
- Bill per workspace ($12/mo or $120/yr each); Stripe quantity tracks the
workspace count and syncs on workspace create/delete.
- 2,500 AI credits per workspace, pooled at the account level; monthly reset
on the billing anniversary, annual granted upfront (no rollover).
- One social account per network per workspace; remove all count-based limits
(workspace/social/member) and the legacy plan tiers (single Workspace plan).
Onboarding (cloud only: SELF_HOSTED=false + PostHog)
- Replace the /subscribe plan picker with /onboarding persona selection
(Creator/Freelancer/Startup/Agency/Small business/Other), saved on the user
(users.persona) and mirrored to PostHog, then Stripe Checkout on the monthly
price. 8-day trial so Stripe displays 7.
Billing screen
- Remove the Change Plan dialog (dead with a single plan); add an annual-upgrade
banner for monthly subscribers (swapToYearly).
- Current-plan card shows the workspace count instead of the plan name.
System AI
- Brand analyzer / workspace autofill is always allowed and never debits credits
(system feature, not the user's usage).
Self-hosted (SELF_HOSTED=true) bypasses all billing, credit, limit, network,
and onboarding logic.
2026-06-21 23:40:03 +00:00
|
|
|
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
|
|
|
|
fix: Stripe webhook setup completion, notifications routing, billing UI
- Fix StripeEventListener: mark setup as Completed when subscription.created webhook fires
- Fix notifications routes: move outside subscribed middleware (non-critical feature)
- Fix NotificationBell: guard response.ok before parsing JSON
- Fix StripeEventListener: use data_get() for payload access
- Rewrite billing page to use SettingsLayout with download buttons on invoices
- Remove duplicate Unit/Listeners/StripeEventListenerTest (consolidated into Feature)
- Add comprehensive Stripe webhook tests (14 scenarios: happy path, setup transitions, edge cases, error handling)
- Update billing i18n: remove "no credit card" text, add status key
- Move auth layout logo to top-left corner
- Redesign onboarding Role page with compact list layout
- Add persona i18n translations (EN, PT-BR, ES)
- Update README with API, MCP, tech stack, quick start
2026-03-31 16:52:36 +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 () {
|
2026-05-03 18:54:26 +00:00
|
|
|
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');
|
|
|
|
|
});
|
|
|
|
|
|
2026-05-02 16:30:59 +00:00
|
|
|
Route::middleware(['auth'])->group(function () {
|
2026-03-31 14:35:49 +00:00
|
|
|
Route::delete('settings/profile', [ProfileController::class, 'destroy'])->name('app.profile.destroy');
|
2026-05-03 20:42:44 +00:00
|
|
|
|
|
|
|
|
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')
|
2026-05-03 20:42:44 +00:00
|
|
|
->name('app.authentication.update-password');
|
|
|
|
|
Route::delete('settings/authentication/sessions', [AuthenticationController::class, 'destroyOtherSessions'])
|
|
|
|
|
->name('app.authentication.destroy-other-sessions');
|
2026-05-04 22:22:09 +00:00
|
|
|
Route::get('settings/authentication/providers/{provider}/connect', [AuthenticationController::class, 'connectProvider'])
|
|
|
|
|
->name('app.authentication.connect-provider');
|
2026-05-03 20:42:44 +00:00
|
|
|
Route::delete('settings/authentication/providers/{provider}', [AuthenticationController::class, 'disconnectProvider'])
|
|
|
|
|
->name('app.authentication.disconnect-provider');
|
|
|
|
|
|
2026-05-03 16:44:13 +00:00
|
|
|
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
|
|
|
});
|