Commit graph

20 commits

Author SHA1 Message Date
Paulo Castellano
4be33d00d2
fix: stop reporting client-error OAuth exceptions to Nightwatch (#261)
League\OAuth2\Server\Exception\OAuthServerException with a status
below 500 (invalid/missing/expired bearer tokens, invalid_grant, etc.)
represents a client error, not an application failure, but Passport's
TokenGuard explicitly calls report() on every failed bearer-token
check. This was flooding Nightwatch with 401 noise from bots probing
the public MCP endpoint. Actual server_error (500) responses are still
reported.
2026-08-09 12:01:12 -03:00
Paulo Castellano
2ca5948309
Scope MCP OAuth tokens to user + workspace (#222) (#245)
* Scope MCP OAuth tokens to user + workspace

Bind authorization-code grants to the authorizing workspace (via auth codes),
inherit workspace on refresh, resolve MCP/API requests from the token instead
of current_workspace_id, backfill existing grants, and revoke workspace tokens
when a member is removed.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Add multi-workspace MCP OAuth coverage

Cover coexistence of the same client across workspaces, settings
list/disconnect scoped to the current workspace, and API key
controllers excluding workspace-bound MCP grants.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Use constrained foreignUuid for oauth_auth_codes.workspace_id

Match the project's UUID foreign-key convention instead of a separate
foreign() call.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Localize the MCP OAuth authorize consent screen

Wire authorize.blade.php to mcp.* translation keys (including the
workspace scope copy) and cover pt-BR rendering.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Fix invalid Mockery import in bind workspace test

CI treats the non-compound `use Mockery` as an ErrorException and
aborts the whole parallel suite.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Inline MCP OAuth workspace backfill into the migration

Move the one-shot backfill out of a dedicated Action and wrap it in an
explicit transaction so a failure rolls back partial binds/revokes.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Nest MCP authorize i18n keys and test backfill rollback

Group consent-screen copy under mcp.authorize.*, and assert the
workspace backfill migration rolls back binds when it fails before
commit.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Hardcode TryPost in the MCP authorize page title

Drop the config('app.name') interpolation from the consent screen title.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Add workspace picker to MCP OAuth consent screen

Let users choose which workspace to bind at authorize time instead of
always using current_workspace_id; silent re-consent still falls back.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Tighten MCP authorize workspace select spacing

Match NativeSelect styling and give the label, control, and helper text room to breathe.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Convert MCP OAuth consent screen to Inertia Vue

Reuse AuthCardLayout, Button, and NativeSelect so the authorize page
matches the app UI. Keep native form posts so Passport's external
redirect still works for MCP client popups.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Polish MCP authorize layout with logo and workspace combobox

Drop the shield and AuthCardLayout double-logo, put TryPost branding
at the top, and reuse the app Combobox pattern for workspace search.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Align MCP OAuth workspace backfill with mcpOAuth scope

Reuse AccessToken::mcpOAuth() so the migration only touches mcp:use
grants on non-PAT clients, matching the rest of the codebase.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Tighten MCP OAuth workspace backfill heuristics

Only touch connected MCP sessions, bind a sole membership or a valid
current workspace, and revoke ambiguous multi-workspace grants instead
of guessing the oldest workspace.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Drop Passport connection override from auth code migration

Always use the app default database connection from .env.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Bind MCP OAuth workspace in AccessTokenRepository

Replace the AccessTokenCreated listener with the same Passport repository
override pattern used for auth codes, so workspace_id is set at persist.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Simplify AccessTokenRepository workspace binding

Drop redundant string casts and the oldest-workspace fallback; keep a
small ownedWorkspace/payloadId helper surface instead.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Extract Passport MCP authorization view from AppServiceProvider

Keep configurePassport thin by moving the Inertia consent props into an
invokable App\Passport\AuthorizationView class.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Simplify AuthorizationView and cover it with direct tests

Use collection higher-order mapping for workspaces/scopes and add focused
tests for current-workspace selection and empty-user props.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Rename BindWorkspaceToAccessTokenTest after listener removal

The suite now covers AuthCodeRepository and AccessTokenRepository
workspace binding, not an AccessTokenCreated listener.

* Fail closed when auth code has no bindable workspace

Authorization-code grants no longer fall back to the user's current
workspace, so a token cannot be minted for a different tenant than consent.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Retrigger CI after GitHub Actions infrastructure failures

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore: retrigger CI

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: harden MCP OAuth workspace binding on refresh and backfill

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: always show MCP OAuth consent to pick a workspace

Disable Passport silent re-consent and require an explicit workspace_id
from the consent form, with Passport wiring moved to its own provider.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: sort MCP connected clients by last used

Show most recently used OAuth connections first on the workspace MCP settings page.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-06 21:59:34 -03:00
Paulo Castellano
a4cf8aa4ce 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-13 21:39:03 -03:00
Paulo Castellano
6d3a375add Add strict_types to automation files and AutomationNodeState factory 2026-06-10 17:26:50 -03:00
Paulo Castellano
b23ab0166e feat(automations): implement automation features and UI enhancements
- Added new automation-related routes and controllers for managing automations.
- Introduced automation nodes in the UI with distinct styles and interactions.
- Updated sidebar to include navigation for automations.
- Enhanced post creation logic to support automation metadata.
- Refactored content type and platform enums into types for better type safety.
- Added localization for automation-related terms in English, Spanish, and Portuguese.
- Improved error handling in various components to accommodate new features.
2026-05-24 09:17:19 -03:00
Paulo Castellano
74bd88d3d6 feat(auth): self-hosted registration gate + admin seeder (closes #46)
Self-hosted installs (SELF_HOSTED=true, the default) now close /register
to the public. Workspace invites still work — the AcceptInvite page
links into /register with ?invite={id}, the middleware persists that
into the session, and POST /register passes through.

- EnsureRegistrationEnabled middleware gates GET/POST /register.
  Accepts ?invite=… (URL) or pending_invite_id (session) as the pass.
- RegisteredUserController::store clears the marker after signup.
- AcceptInvite.vue passes invite.id in the register link's query string.
- Login.vue hides the "Sign up" link when self_hosted.
- UserSeeder bootstraps a single admin (admin@trypost.it / password).
  Idempotent; not wired into DatabaseSeeder — operator runs
  `php artisan db:seed --class=UserSeeder` per the install docs.
- Tests cover both flag values for every changed surface.

Docs PR: see trypost-docs self-hosting/installation.mdx step 3.
2026-05-19 11:45:16 -03:00
Paulo Castellano
acdc78f8bd refactor: standardize UI components, modernize localization structure, and remove legacy appearance management middleware. 2026-05-06 14:26:18 -03:00
Paulo Castellano
9a26e6d802 feat: complete create + publish post flow via MCP and REST API
Lets ChatGPT (MCP) and external clients (REST API) drive the full lifecycle of
a post — create with platform selection, attach media from URLs, schedule or
publish immediately, and fetch engagement metrics — without touching the web UI.

MCP tools added: UpdatePostTool, PublishPostTool, AttachMediaFromUrlTool,
ListContentTypesTool, GetPostMetricsTool, PreviewPostTool. CreatePostTool now
accepts platforms[] + scheduled_at + label_ids; ListPostsTool gains
status/search/limit filters.

REST endpoints added: POST /api/posts/{post}/media, GET /api/posts/{post}/metrics,
GET /api/posts/{post}/preview, GET /api/content-types.

Also fixes a silent CreatePost::execute bug — the action validated platforms[]
but ignored it, so REST callers never saw their selection persisted. Adds cross
validation rules (ContentTypeMatchesPlatform / ContentTypeMatchesPostPlatform)
so a LinkedIn account can't be saddled with x_post, and rejects inactive social
accounts during validation instead of failing silently downstream.

Shared services (PostMetricsFetcher, PostPreviewer, MediaAttacher) back both
MCP tools and REST controllers so behaviour stays aligned. New Resources
(PlatformContentTypesResource, PostMetricsResource, PostPreviewResource,
PostMediaAttachResource) keep controllers free of inline model mapping.

Suite: 1.332 passing, 0 failing — covers web (PostControllerTest), REST
(PostApiTest, PlatformApiTest, PostMediaApiTest), MCP (66 tool tests), and
the publish job (PublishToSocialPlatformTest).

Removes /docs from git tracking and TIKTOK_REVIEW_VIDEO_SCRIPT.md.
2026-05-04 08:12:28 -03:00
Paulo Castellano
20eeeaa493 feat: migrate custom API token implementation to Laravel Passport for authentication and token management 2026-05-03 18:38:17 -03:00
Paulo Castellano
b3b59b4d13 refactor: remove onboarding flow, implement brand analysis services, and replace setup middleware with account readiness checks 2026-04-16 23:05:51 -03:00
Paulo Castellano
f3f72afecd refactor: auth split layout, subscribe redesign, onboarding, i18n, cookie locale
Auth pages:
- Create AuthSplitLayout with animated feature slides (6 slides, 3 languages)
- All auth pages use split layout (form left, visual right)
- Add show/hide password toggle with tooltip on Register
- Legal footer only shown on Register via showLegal prop

Subscribe page:
- Redesign to match auth card pattern (centered, clean)
- Platform icons, feature checklist, dynamic trial days (trialDays - 1)
- Add "Switch workspace" link
- Full i18n (en, es, pt-BR)

Onboarding:
- Rename URLs: step1 -> role, step2 -> connect
- Add enforceStep() to prevent skipping/going back steps
- Redirect /onboarding to /onboarding/role
- Redesign Step2 with AuthSplitLayout and compact platform list
- 21 tests covering all step enforcement scenarios

Workspaces page:
- Redesign with AuthSplitLayout (list with avatars, current badge)

Language system:
- Move locale from DB to cookie (forever, unencrypted, session.domain)
- Create SetLocale middleware (sets cookie if missing, validates against config)
- Rename lang/pt-br to lang/pt-BR
- Add dayjs es locale

Other:
- Copy utils.ts from sendkit (formatNumber, formatMoney, copyToClipboard)
- ConfirmDeleteModal with text confirmation (sendkit pattern)
- i18n for ConfirmDeleteModal internal strings (common.php)
- EmptyState component for posts index
- Exact match for "All" posts in sidebar
- Posts breadcrumbs show current status filter
- DialogFooter buttons aligned left
- API Keys page redesign with Table, DropdownMenu, EmptyState
- Extract CreateApiKeyDialog and InviteMemberDialog to components
- Remove API Keys from sidebar
- DropdownMenuItem destructive variant for Remove action
2026-03-30 11:53:42 -03:00
Paulo Castellano
a926033d06 refactor: organize middleware/requests into App/ subdirs, add Resources, fix auth routes
- Move middleware to App/ subdir (HandleInertiaRequests, HandleAppearance,
  EnsureSubscribed, EnsureUserSetupIsComplete) matching Sendkit pattern
- Move all Form Requests into organized subdirs (App/Post, App/Workspace,
  App/Media, App/Invite, App/Settings, App/Auth)
- Create AuthUserResource and AuthWorkspaceResource for HandleInertiaRequests
  shared data (role inside currentWorkspace, matching Sendkit pattern)
- Split auth.php into 3 route groups (no middleware, guest, auth) matching
  Sendkit pattern exactly
- Fix UserFactory to include all nullable attributes (current_workspace_id,
  stripe_id, pm_type, pm_last_four, trial_ends_at)
- Fix SocialAccountResource (display_name not name)
- Update frontend for new auth prop structure
- 702 tests passing (2 pre-existing Mastodon failures)
2026-03-29 21:13:30 -03:00
Paulo Castellano
fabce14aad 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 20:30:36 -03:00
Paulo Castellano
039ed02b42 feat: rate limiting, cleanup duplicates, ApiKey app tests, bootstrap hardening
- Add API rate limiting (60/min per workspace) in AppServiceProvider
- Add throttle:api middleware to all API routes
- Add rate limit exception rendering for JSON responses
- Add middleware priority list (AuthenticateApiToken before ThrottleRequests)
- Add ApiKeyControllerTest (6 tests for App dashboard CRUD)
- Remove duplicate test files (Controllers/ dir duplicates)
- Remove old controllers and routes/settings.php
- Add routes/mcp.php placeholder
- Add API Keys nav item to settings layout
- Fix LazilyRefreshDatabase conflict in API tests
- 698 tests passing
2026-03-29 20:14:23 -03:00
Paulo Castellano
298360c305 feat: delete old controllers, add API tests, ApiKeys page, MCP route
- Remove old root-level controllers (moved to App/ namespace)
- Remove old routes/settings.php (merged into routes/app.php)
- Add routes/mcp.php placeholder for future MCP server
- Add settings/ApiKeys.vue page with create/delete/copy token
- Add API Keys nav item to settings layout
- Add 26 API endpoint tests (auth, hashtags, labels, api-keys, workspace)
- Add ApiToken factory
- All 710 tests passing
2026-03-29 19:59:17 -03:00
Paulo Castellano
8689e54e55 refactor: restructure to Actions, subdomain routes (app/api), API tokens
- Extract business logic from controllers into Action classes:
  Post/, Workspace/, Hashtag/, Label/, Invite/, ApiKey/
- Create subdomain routing: app.trypost.test (Inertia dashboard),
  api.trypost.test (REST API with token auth)
- Add ApiToken model with tp_ prefix, token_lookup/hash auth
- Add AuthenticateApiToken middleware for API authentication
- Create Api controllers with JSON Resources for all entities
- Create App controllers that use Actions + Inertia responses
- Organize Form Requests into Api/ and App/ directories
- Add api_tokens migration
- Update all route names with app. prefix
- Update all tests to use new route names (684 passing)
2026-03-29 19:24:28 -03:00
Paulo Castellano
1196551a26 feat: improvements on invites 2026-01-20 16:53:54 -03:00
Paulo Castellano
7c00c3387e feat: Implement user onboarding, subscription management, and refactor social integrations with new UI components and mail templates. 2026-01-16 23:46:30 -03:00
Paulo Castellano
7c80d717b1 chore: first commit 2026-01-15 14:24:39 -03:00
Paulo Castellano
6890147aa6 chore: first commit 2026-01-14 22:13:44 -03:00