2026-01-15 01:13:44 +00:00
|
|
|
<?php
|
|
|
|
|
|
refactor: settings redesign, Spanish translations, language system, strict_types
Settings pages:
- Redesign layout to match Sendkit (max-w-4xl, space-y-12, Separator sections)
- Merge Members page into Workspace settings with Table, invite Dialog, ConfirmDeleteModal
- Add workspace logo upload/delete routes and controller methods
- Translate all hardcoded strings in Workspace.vue modals
Language system:
- Drop languages table, replace language_id FK with locale string column on users
- Create config/languages.php for available languages and default locale
- Add Spanish (es) translations (13 files)
- Simplify HandleInertiaRequests, ProfileController, RegisteredUserController
Code quality:
- Add declare(strict_types=1) to all PHP files
- Fix MastodonPublisher using wrong attribute (filename -> original_filename)
- Fix HasMediaTest for new has_photo/photo_url accessors
- Fix PublishToSocialPlatformTest type error revealed by strict_types
- Remove orphaned Language model from AppServiceProvider morph map
- Update User TypeScript interface (has_photo, photo_url, locale)
- Eager load media relation on workspaces to prevent N+1
- Add 8 new tests for workspace logo upload/delete
- Update workspace settings test to assert members/invitations props
All 710 tests passing.
2026-03-30 03:20:43 +00:00
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
2026-01-15 01:13:44 +00:00
|
|
|
namespace App\Providers;
|
|
|
|
|
|
2026-01-17 02:46:30 +00:00
|
|
|
use App\Listeners\StripeEventListener;
|
2026-05-03 21:38:17 +00:00
|
|
|
use App\Models\AccessToken;
|
2026-04-15 01:22:04 +00:00
|
|
|
use App\Models\Account;
|
2026-04-16 00:06:06 +00:00
|
|
|
use App\Models\AiUsageLog;
|
2026-06-14 19:12:49 +00:00
|
|
|
use App\Models\Automation;
|
|
|
|
|
use App\Models\AutomationNodeRun;
|
|
|
|
|
use App\Models\AutomationNodeState;
|
|
|
|
|
use App\Models\AutomationRun;
|
|
|
|
|
use App\Models\AutomationTriggerItem;
|
2026-04-15 01:22:04 +00:00
|
|
|
use App\Models\Invite;
|
2026-01-18 19:46:27 +00:00
|
|
|
use App\Models\Media;
|
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\Models\Notification;
|
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\Models\NotificationPreference;
|
2026-04-14 20:50:41 +00:00
|
|
|
use App\Models\Plan;
|
2026-01-18 19:46:27 +00:00
|
|
|
use App\Models\Post;
|
2026-04-15 23:08:16 +00:00
|
|
|
use App\Models\PostComment;
|
2026-01-18 19:46:27 +00:00
|
|
|
use App\Models\PostPlatform;
|
|
|
|
|
use App\Models\SocialAccount;
|
2026-01-17 17:44:37 +00:00
|
|
|
use App\Models\Subscription;
|
|
|
|
|
use App\Models\SubscriptionItem;
|
2026-01-18 19:46:27 +00:00
|
|
|
use App\Models\User;
|
|
|
|
|
use App\Models\Workspace;
|
2026-05-03 19:52:28 +00:00
|
|
|
use App\Models\WorkspaceInvite;
|
2026-01-18 19:46:27 +00:00
|
|
|
use App\Models\WorkspaceLabel;
|
2026-05-03 18:23:30 +00:00
|
|
|
use App\Models\WorkspaceSignature;
|
2026-05-07 15:42:35 +00:00
|
|
|
use App\Services\PostHogService;
|
2026-05-03 16:44:13 +00:00
|
|
|
use App\Services\PostTemplate\Registry as PostTemplateRegistry;
|
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\Socialite\DiscordProvider;
|
2026-01-17 02:46:30 +00:00
|
|
|
use App\Socialite\InstagramProvider;
|
2026-01-15 17:24:39 +00:00
|
|
|
use App\Socialite\LinkedInPageExtendSocialite;
|
2026-01-15 01:13:44 +00:00
|
|
|
use Carbon\CarbonImmutable;
|
2026-01-18 19:46:27 +00:00
|
|
|
use Illuminate\Auth\Notifications\ResetPassword;
|
|
|
|
|
use Illuminate\Auth\Notifications\VerifyEmail;
|
2026-03-29 23:14:23 +00:00
|
|
|
use Illuminate\Cache\RateLimiting\Limit;
|
feat: implement MCP server with tools, Post API tests, auth middleware
- Create TryPostServer MCP server with 17 tools:
Post (List, Get, Create, Delete), Hashtag (List, Create, Update, Delete),
Label (List, Create, Update, Delete), Workspace (Get),
ApiKey (List, Create, Delete)
- Create AuthenticateMcpToken middleware (logs in workspace owner)
- Register mcp.auth middleware alias in bootstrap/app.php
- Create routes/ai.php with mcp.trypost.test subdomain
- Add PostApiTest with 6 tests (list, show, create, delete, isolation)
- Fix PostApiTest assertions for pagination/resource wrapping
- 704 tests passing, frontend build passing
2026-03-29 23:30:36 +00:00
|
|
|
use Illuminate\Database\Eloquent\Model;
|
2026-01-18 19:46:27 +00:00
|
|
|
use Illuminate\Database\Eloquent\Relations\Relation;
|
2026-03-29 23:14:23 +00:00
|
|
|
use Illuminate\Http\Request;
|
2026-01-18 19:46:27 +00:00
|
|
|
use Illuminate\Http\Resources\Json\JsonResource;
|
|
|
|
|
use Illuminate\Notifications\Messages\MailMessage;
|
2026-01-15 01:13:44 +00:00
|
|
|
use Illuminate\Support\Facades\Date;
|
|
|
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
|
use Illuminate\Support\Facades\Event;
|
2026-03-29 23:14:23 +00:00
|
|
|
use Illuminate\Support\Facades\RateLimiter;
|
2026-01-15 01:13:44 +00:00
|
|
|
use Illuminate\Support\ServiceProvider;
|
|
|
|
|
use Illuminate\Validation\Rules\Password;
|
2026-01-17 17:44:37 +00:00
|
|
|
use Laravel\Cashier\Cashier;
|
2026-01-17 02:46:30 +00:00
|
|
|
use Laravel\Cashier\Events\WebhookReceived;
|
2026-01-18 19:46:27 +00:00
|
|
|
use Laravel\Nightwatch\Facades\Nightwatch;
|
|
|
|
|
use Laravel\Nightwatch\Records\CacheEvent;
|
2026-05-03 21:38:17 +00:00
|
|
|
use Laravel\Passport\Passport;
|
2026-01-17 02:46:30 +00:00
|
|
|
use Laravel\Socialite\Facades\Socialite;
|
2026-03-31 00:18:07 +00:00
|
|
|
use Laravel\Socialite\Two\GoogleProvider;
|
|
|
|
|
use PostHog\PostHog;
|
2026-01-17 02:46:30 +00:00
|
|
|
use SocialiteProviders\Facebook\FacebookExtendSocialite;
|
2026-01-15 01:13:44 +00:00
|
|
|
use SocialiteProviders\LinkedIn\LinkedInExtendSocialite;
|
|
|
|
|
use SocialiteProviders\Manager\SocialiteWasCalled;
|
2026-01-18 16:10:01 +00:00
|
|
|
use SocialiteProviders\Pinterest\PinterestExtendSocialite;
|
2026-01-15 01:13:44 +00:00
|
|
|
use SocialiteProviders\TikTok\TikTokExtendSocialite;
|
|
|
|
|
|
|
|
|
|
class AppServiceProvider extends ServiceProvider
|
|
|
|
|
{
|
|
|
|
|
/**
|
|
|
|
|
* Register any application services.
|
|
|
|
|
*/
|
|
|
|
|
public function register(): void
|
|
|
|
|
{
|
2026-05-03 16:44:13 +00:00
|
|
|
$this->app->singleton(PostTemplateRegistry::class);
|
|
|
|
|
|
feat: implement MCP server with tools, Post API tests, auth middleware
- Create TryPostServer MCP server with 17 tools:
Post (List, Get, Create, Delete), Hashtag (List, Create, Update, Delete),
Label (List, Create, Update, Delete), Workspace (Get),
ApiKey (List, Create, Delete)
- Create AuthenticateMcpToken middleware (logs in workspace owner)
- Register mcp.auth middleware alias in bootstrap/app.php
- Create routes/ai.php with mcp.trypost.test subdomain
- Add PostApiTest with 6 tests (list, show, create, delete, isolation)
- Fix PostApiTest assertions for pagination/resource wrapping
- 704 tests passing, frontend build passing
2026-03-29 23:30:36 +00:00
|
|
|
if ($this->app->environment('local') && class_exists(\Laravel\Telescope\TelescopeServiceProvider::class)) {
|
|
|
|
|
$this->app->register(\Laravel\Telescope\TelescopeServiceProvider::class);
|
|
|
|
|
$this->app->register(TelescopeServiceProvider::class);
|
|
|
|
|
}
|
2026-01-15 01:13:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Bootstrap any application services.
|
|
|
|
|
*/
|
|
|
|
|
public function boot(): void
|
|
|
|
|
{
|
|
|
|
|
$this->configureDefaults();
|
2026-01-18 19:46:27 +00:00
|
|
|
$this->configureMorphMap();
|
2026-03-31 00:18:07 +00:00
|
|
|
$this->configurePostHog();
|
2026-03-29 23:14:23 +00:00
|
|
|
$this->configureRateLimiting();
|
2026-01-15 01:13:44 +00:00
|
|
|
$this->configureSocialite();
|
2026-01-17 02:46:30 +00:00
|
|
|
$this->configureStripeWebhooks();
|
2026-01-17 17:44:37 +00:00
|
|
|
|
2026-04-15 01:22:04 +00:00
|
|
|
Cashier::useCustomerModel(Account::class);
|
2026-01-17 17:44:37 +00:00
|
|
|
Cashier::useSubscriptionModel(Subscription::class);
|
|
|
|
|
Cashier::useSubscriptionItemModel(SubscriptionItem::class);
|
2026-06-14 18:46:17 +00:00
|
|
|
Cashier::keepPastDueSubscriptionsActive();
|
2026-04-14 21:44:47 +00:00
|
|
|
|
2026-05-03 21:38:17 +00:00
|
|
|
$this->configurePassport();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected function configurePassport(): void
|
|
|
|
|
{
|
|
|
|
|
Passport::useTokenModel(AccessToken::class);
|
|
|
|
|
|
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
|
|
|
// API keys may omit an application expiry ("never"). Passport still
|
|
|
|
|
// embeds a JWT `exp`, so keep that far ahead and enforce optional
|
|
|
|
|
// `oauth_access_tokens.expires_at` in LoadWorkspaceFromToken.
|
|
|
|
|
Passport::personalAccessTokensExpireIn(now()->addYears(100));
|
|
|
|
|
|
2026-05-03 21:38:17 +00:00
|
|
|
Passport::tokensCan([
|
|
|
|
|
'mcp:use' => 'Use MCP server',
|
|
|
|
|
]);
|
|
|
|
|
|
2026-05-03 23:26:09 +00:00
|
|
|
Passport::authorizationView('mcp.authorize');
|
2026-01-17 02:46:30 +00:00
|
|
|
}
|
|
|
|
|
|
2026-01-18 19:46:27 +00:00
|
|
|
protected function configureMorphMap(): void
|
|
|
|
|
{
|
|
|
|
|
Relation::enforceMorphMap([
|
2026-05-03 21:38:17 +00:00
|
|
|
'accessToken' => AccessToken::class,
|
2026-04-15 01:22:04 +00:00
|
|
|
'account' => Account::class,
|
2026-04-16 00:06:06 +00:00
|
|
|
'aiUsageLog' => AiUsageLog::class,
|
2026-06-14 19:12:49 +00:00
|
|
|
'automation' => Automation::class,
|
|
|
|
|
'automationNodeRun' => AutomationNodeRun::class,
|
|
|
|
|
'automationNodeState' => AutomationNodeState::class,
|
|
|
|
|
'automationRun' => AutomationRun::class,
|
|
|
|
|
'automationTriggerItem' => AutomationTriggerItem::class,
|
2026-04-15 01:22:04 +00:00
|
|
|
'invite' => Invite::class,
|
2026-01-18 19:46:27 +00:00
|
|
|
'media' => Media::class,
|
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
|
|
|
'notification' => Notification::class,
|
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
|
|
|
'notificationPreference' => NotificationPreference::class,
|
2026-05-03 19:52:28 +00:00
|
|
|
'plan' => Plan::class,
|
2026-01-18 19:46:27 +00:00
|
|
|
'post' => Post::class,
|
2026-04-15 23:08:16 +00:00
|
|
|
'postComment' => PostComment::class,
|
2026-01-18 19:46:27 +00:00
|
|
|
'postPlatform' => PostPlatform::class,
|
|
|
|
|
'socialAccount' => SocialAccount::class,
|
|
|
|
|
'subscription' => Subscription::class,
|
|
|
|
|
'subscriptionItem' => SubscriptionItem::class,
|
|
|
|
|
'user' => User::class,
|
|
|
|
|
'workspace' => Workspace::class,
|
2026-05-03 19:52:28 +00:00
|
|
|
'workspaceInvite' => WorkspaceInvite::class,
|
2026-01-18 19:46:27 +00:00
|
|
|
'workspaceLabel' => WorkspaceLabel::class,
|
2026-05-03 19:52:28 +00:00
|
|
|
'workspaceSignature' => WorkspaceSignature::class,
|
2026-01-18 19:46:27 +00:00
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-31 00:18:07 +00:00
|
|
|
protected function configurePostHog(): void
|
|
|
|
|
{
|
2026-05-07 15:42:35 +00:00
|
|
|
if (! PostHogService::isEnabled()) {
|
|
|
|
|
return;
|
2026-03-31 00:18:07 +00:00
|
|
|
}
|
2026-05-07 15:42:35 +00:00
|
|
|
|
|
|
|
|
PostHog::init(config('services.posthog.api_key'), [
|
|
|
|
|
'host' => config('services.posthog.host'),
|
|
|
|
|
]);
|
2026-03-31 00:18:07 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-29 23:14:23 +00:00
|
|
|
protected function configureRateLimiting(): void
|
|
|
|
|
{
|
|
|
|
|
RateLimiter::for('api', function (Request $request) {
|
|
|
|
|
if ($this->app->environment('local')) {
|
|
|
|
|
return Limit::none();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Limit::perMinute(60)->by($request->workspace?->id ?: $request->ip());
|
|
|
|
|
});
|
2026-07-25 00:21:39 +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
|
|
|
RateLimiter::for(
|
|
|
|
|
'mcp-oauth-registration',
|
|
|
|
|
fn (Request $request): Limit => Limit::perMinute(30)->by($request->ip()),
|
|
|
|
|
);
|
|
|
|
|
|
2026-07-25 01:49:04 +00:00
|
|
|
// Signed media uploads (api.uploads.store). MCP hosts share egress IPs
|
|
|
|
|
// across tenants — key by workspace_id from the signed URL, with a high
|
|
|
|
|
// IP backstop so one client cannot flood every workspace.
|
|
|
|
|
RateLimiter::for('signed-uploads', function (Request $request) {
|
|
|
|
|
$limits = [
|
|
|
|
|
Limit::perMinute((int) config('trypost.media.signed_upload_per_ip_per_minute'))
|
|
|
|
|
->by("ip:{$request->ip()}"),
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
$workspaceId = $request->query('workspace_id');
|
|
|
|
|
|
|
|
|
|
if (filled($workspaceId)) {
|
|
|
|
|
array_unshift(
|
|
|
|
|
$limits,
|
|
|
|
|
Limit::perMinute((int) config('trypost.media.signed_upload_per_workspace_per_minute'))
|
|
|
|
|
->by("workspace:{$workspaceId}"),
|
|
|
|
|
);
|
2026-07-25 01:37:57 +00:00
|
|
|
}
|
2026-07-25 00:21:39 +00:00
|
|
|
|
2026-07-25 01:49:04 +00:00
|
|
|
return $limits;
|
2026-07-25 00:21:39 +00:00
|
|
|
});
|
2026-03-29 23:14:23 +00:00
|
|
|
}
|
|
|
|
|
|
2026-01-17 02:46:30 +00:00
|
|
|
protected function configureStripeWebhooks(): void
|
|
|
|
|
{
|
|
|
|
|
Event::listen(WebhookReceived::class, StripeEventListener::class);
|
2026-01-15 01:13:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected function configureSocialite(): void
|
|
|
|
|
{
|
2026-03-31 00:18:07 +00:00
|
|
|
// Google Auth (login/signup) - separate from YouTube OAuth
|
|
|
|
|
Socialite::extend('google-auth', function ($app) {
|
|
|
|
|
$config = $app['config']['services.google-auth'];
|
|
|
|
|
|
|
|
|
|
return Socialite::buildProvider(GoogleProvider::class, $config);
|
|
|
|
|
});
|
|
|
|
|
|
2026-01-17 02:46:30 +00:00
|
|
|
// Instagram Business Login
|
|
|
|
|
Socialite::extend('instagram', function ($app) {
|
|
|
|
|
$config = $app['config']['services.instagram'];
|
|
|
|
|
|
|
|
|
|
return Socialite::buildProvider(InstagramProvider::class, $config);
|
|
|
|
|
});
|
|
|
|
|
|
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
|
|
|
Socialite::extend('discord', function ($app) {
|
|
|
|
|
$config = $app['config']['services.discord'];
|
|
|
|
|
|
|
|
|
|
return Socialite::buildProvider(DiscordProvider::class, $config);
|
|
|
|
|
});
|
|
|
|
|
|
2026-01-17 02:46:30 +00:00
|
|
|
Event::listen(SocialiteWasCalled::class, FacebookExtendSocialite::class);
|
2026-01-15 01:13:44 +00:00
|
|
|
Event::listen(SocialiteWasCalled::class, LinkedInExtendSocialite::class);
|
|
|
|
|
Event::listen(SocialiteWasCalled::class, LinkedInPageExtendSocialite::class);
|
2026-01-18 16:10:01 +00:00
|
|
|
Event::listen(SocialiteWasCalled::class, PinterestExtendSocialite::class);
|
2026-01-15 01:13:44 +00:00
|
|
|
Event::listen(SocialiteWasCalled::class, TikTokExtendSocialite::class);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected function configureDefaults(): void
|
|
|
|
|
{
|
|
|
|
|
Date::use(CarbonImmutable::class);
|
|
|
|
|
|
2026-01-18 19:46:27 +00:00
|
|
|
// Disable wrapping of JSON resources
|
|
|
|
|
JsonResource::withoutWrapping();
|
feat: implement MCP server with tools, Post API tests, auth middleware
- Create TryPostServer MCP server with 17 tools:
Post (List, Get, Create, Delete), Hashtag (List, Create, Update, Delete),
Label (List, Create, Update, Delete), Workspace (Get),
ApiKey (List, Create, Delete)
- Create AuthenticateMcpToken middleware (logs in workspace owner)
- Register mcp.auth middleware alias in bootstrap/app.php
- Create routes/ai.php with mcp.trypost.test subdomain
- Add PostApiTest with 6 tests (list, show, create, delete, isolation)
- Fix PostApiTest assertions for pagination/resource wrapping
- 704 tests passing, frontend build passing
2026-03-29 23:30:36 +00:00
|
|
|
Model::shouldBeStrict(! $this->app->isProduction());
|
2026-01-18 19:46:27 +00:00
|
|
|
|
2026-05-03 22:56:36 +00:00
|
|
|
DB::prohibitDestructiveCommands(
|
|
|
|
|
app()->isProduction(),
|
|
|
|
|
);
|
2026-01-15 01:13:44 +00:00
|
|
|
|
|
|
|
|
Password::defaults(fn (): ?Password => app()->isProduction()
|
|
|
|
|
? Password::min(12)
|
|
|
|
|
->mixedCase()
|
|
|
|
|
->letters()
|
|
|
|
|
->numbers()
|
|
|
|
|
->symbols()
|
|
|
|
|
->uncompromised()
|
|
|
|
|
: null
|
|
|
|
|
);
|
2026-01-18 19:46:27 +00:00
|
|
|
|
|
|
|
|
Nightwatch::rejectCacheEvents(function (CacheEvent $cacheEvent) {
|
|
|
|
|
return in_array($cacheEvent->key, [
|
|
|
|
|
'illuminate:foundation:down',
|
|
|
|
|
'illuminate:queue:restart',
|
|
|
|
|
'illuminate:schedule:interrupt',
|
|
|
|
|
]);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Custom email verification template
|
|
|
|
|
VerifyEmail::toMailUsing(function (User $user, string $url) {
|
|
|
|
|
return (new MailMessage)
|
|
|
|
|
->from(config('mail.from.address'), config('mail.from.name'))
|
2026-01-18 20:48:12 +00:00
|
|
|
->subject('Verify your email address')
|
2026-01-18 19:46:27 +00:00
|
|
|
->view('mail.email-verification', [
|
2026-01-18 20:48:12 +00:00
|
|
|
'title' => 'Verify your email address',
|
|
|
|
|
'previewText' => 'Please verify your email address.',
|
2026-01-18 19:46:27 +00:00
|
|
|
'user' => $user,
|
|
|
|
|
'url' => $url,
|
|
|
|
|
]);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Custom password reset template
|
|
|
|
|
ResetPassword::toMailUsing(function (User $user, string $token) {
|
|
|
|
|
$url = url(route('password.reset', [
|
|
|
|
|
'token' => $token,
|
|
|
|
|
'email' => $user->getEmailForPasswordReset(),
|
|
|
|
|
], false));
|
|
|
|
|
|
|
|
|
|
return (new MailMessage)
|
|
|
|
|
->from(config('mail.from.address'), config('mail.from.name'))
|
2026-01-18 20:48:12 +00:00
|
|
|
->subject('Reset your password')
|
2026-01-18 19:46:27 +00:00
|
|
|
->view('mail.password-reset', [
|
2026-01-18 20:48:12 +00:00
|
|
|
'title' => 'Reset your password',
|
|
|
|
|
'previewText' => 'Reset your password.',
|
2026-01-18 19:46:27 +00:00
|
|
|
'user' => $user,
|
|
|
|
|
'url' => $url,
|
|
|
|
|
]);
|
|
|
|
|
});
|
2026-01-15 01:13:44 +00:00
|
|
|
}
|
|
|
|
|
}
|