2026-03-29 22:24:28 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
|
|
use App\Http\Controllers\Api\ApiKeyController;
|
feat: Asset Library list, preview, and attach via API and MCP (#282)
* feat: list, preview, and attach Asset Library media via API and MCP
Let API and MCP clients reuse workspace assets instead of re-uploading, sharing the same scoped query, signed preview, and idempotent attach path.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Align Asset Library API and MCP with main media patterns.
Drop the signed-preview stack, return Storage URLs and PostResource like existing attach flows, and query medias by morph owner instead of getMedia().
Co-authored-by: Cursor <cursoragent@cursor.com>
* Paginate workspace assets with the app default page size.
Keep list pagination in the action via config('app.pagination.default') instead of a hardcoded API page size.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Move asset API and MCP input rules into FormRequests.
Keep controllers and tools free of inline field validation; MCP tools reuse the request rule definitions.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Document asset MCP tools with explicit parameters and constraints.
Spell out workspace scope, return fields, sibling tools, and rejection cases so agents can call list/get/attach without guessing.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Harden asset attach against races and keep library metadata on the post.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Relock the library asset on attach so a deleted file cannot land on the post.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Document that omitting alt on attach keeps the library alt text.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-16 20:08:20 +00:00
|
|
|
use App\Http\Controllers\Api\AssetController;
|
2026-03-29 22:24:28 +00:00
|
|
|
use App\Http\Controllers\Api\LabelController;
|
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 11:12:28 +00:00
|
|
|
use App\Http\Controllers\Api\PlatformController;
|
2026-03-29 22:24:28 +00:00
|
|
|
use App\Http\Controllers\Api\PostController;
|
2026-05-03 18:23:30 +00:00
|
|
|
use App\Http\Controllers\Api\SignatureController;
|
feat: social account toggle action, API, MCP + full test coverage
- Extract ToggleSocialAccount action from SocialController
- Add API endpoints: GET /social-accounts, PUT /social-accounts/{id}/toggle
- Add MCP tools: ListSocialAccountsTool, ToggleSocialAccountTool
- Fix all MCP tools: findOrFail → find + Response::error for graceful errors
- Fix MCP tools using $request->validated() without validate() call
- Fix return types to Response|ResponseFactory for error paths
- Add SocialAccountResource is_active/status fields (no tokens exposed)
- Add 43 MCP tests covering all 18 tools (CRUD, validation, cross-workspace)
- Add API response structure tests for posts, hashtags, labels, workspace
- Add API validation tests for post create/update, api-key expiry, label color
- Add API cross-workspace delete tests for hashtags and labels
- Add app validation tests for hashtag/label update, invite fields, password
- Add auth required tests for notifications, profile delete, api-keys index
- Add media reorder validation tests
2026-03-31 04:42:39 +00:00
|
|
|
use App\Http\Controllers\Api\SocialAccountController;
|
2026-05-15 19:10:21 +00:00
|
|
|
use App\Http\Controllers\Api\UploadController;
|
2026-09-04 15:00:32 +00:00
|
|
|
use App\Http\Controllers\Api\WebhookController;
|
2026-03-29 22:24:28 +00:00
|
|
|
use App\Http\Controllers\Api\WorkspaceController;
|
|
|
|
|
use Illuminate\Support\Facades\Route;
|
|
|
|
|
|
2026-05-15 19:10:21 +00:00
|
|
|
Route::post('/uploads/{token}', [UploadController::class, 'store'])
|
2026-07-25 01:49:04 +00:00
|
|
|
->middleware(['signed', 'throttle:signed-uploads'])
|
|
|
|
|
->whereUuid('token')
|
2026-05-15 19:10:21 +00:00
|
|
|
->name('api.uploads.store');
|
|
|
|
|
|
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 11:12:28 +00:00
|
|
|
Route::middleware(['auth:api', 'workspace.token', 'throttle:api'])->group(function () {
|
2026-03-31 14:35:49 +00:00
|
|
|
// Posts
|
|
|
|
|
Route::get('/posts', [PostController::class, 'index'])->name('api.posts.index');
|
|
|
|
|
Route::post('/posts', [PostController::class, 'store'])->name('api.posts.store');
|
|
|
|
|
Route::get('/posts/{post}', [PostController::class, 'show'])->name('api.posts.show');
|
|
|
|
|
Route::put('/posts/{post}', [PostController::class, 'update'])->name('api.posts.update');
|
|
|
|
|
Route::delete('/posts/{post}', [PostController::class, 'destroy'])->name('api.posts.destroy');
|
2026-05-04 21:00:03 +00:00
|
|
|
Route::post('/posts/{post}/media', [PostController::class, 'storeMedia'])->name('api.posts.store-media');
|
|
|
|
|
Route::post('/posts/{post}/media/from-url', [PostController::class, 'attachMediaFromUrl'])->name('api.posts.attach-media-from-url');
|
feat: Asset Library list, preview, and attach via API and MCP (#282)
* feat: list, preview, and attach Asset Library media via API and MCP
Let API and MCP clients reuse workspace assets instead of re-uploading, sharing the same scoped query, signed preview, and idempotent attach path.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Align Asset Library API and MCP with main media patterns.
Drop the signed-preview stack, return Storage URLs and PostResource like existing attach flows, and query medias by morph owner instead of getMedia().
Co-authored-by: Cursor <cursoragent@cursor.com>
* Paginate workspace assets with the app default page size.
Keep list pagination in the action via config('app.pagination.default') instead of a hardcoded API page size.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Move asset API and MCP input rules into FormRequests.
Keep controllers and tools free of inline field validation; MCP tools reuse the request rule definitions.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Document asset MCP tools with explicit parameters and constraints.
Spell out workspace scope, return fields, sibling tools, and rejection cases so agents can call list/get/attach without guessing.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Harden asset attach against races and keep library metadata on the post.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Relock the library asset on attach so a deleted file cannot land on the post.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Document that omitting alt on attach keeps the library alt text.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-16 20:08:20 +00:00
|
|
|
Route::post('/posts/{post}/media/from-asset', [PostController::class, 'attachExistingAsset'])->name('api.posts.attach-existing-asset');
|
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 11:12:28 +00:00
|
|
|
Route::get('/posts/{post}/metrics', [PostController::class, 'metrics'])->name('api.posts.metrics');
|
|
|
|
|
Route::get('/posts/{post}/preview', [PostController::class, 'preview'])->name('api.posts.preview');
|
|
|
|
|
|
|
|
|
|
// Platforms (read-only metadata)
|
|
|
|
|
Route::get('/content-types', [PlatformController::class, 'contentTypes'])->name('api.content-types');
|
2026-03-31 14:35:49 +00:00
|
|
|
|
|
|
|
|
// Workspace
|
|
|
|
|
Route::get('/workspace', [WorkspaceController::class, 'show'])->name('api.workspace.show');
|
|
|
|
|
|
2026-05-03 18:23:30 +00:00
|
|
|
// Signatures
|
|
|
|
|
Route::get('/signatures', [SignatureController::class, 'index'])->name('api.signatures.index');
|
|
|
|
|
Route::post('/signatures', [SignatureController::class, 'store'])->name('api.signatures.store');
|
|
|
|
|
Route::put('/signatures/{signature}', [SignatureController::class, 'update'])->name('api.signatures.update');
|
|
|
|
|
Route::delete('/signatures/{signature}', [SignatureController::class, 'destroy'])->name('api.signatures.destroy');
|
2026-03-31 14:35:49 +00:00
|
|
|
|
feat: Asset Library list, preview, and attach via API and MCP (#282)
* feat: list, preview, and attach Asset Library media via API and MCP
Let API and MCP clients reuse workspace assets instead of re-uploading, sharing the same scoped query, signed preview, and idempotent attach path.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Align Asset Library API and MCP with main media patterns.
Drop the signed-preview stack, return Storage URLs and PostResource like existing attach flows, and query medias by morph owner instead of getMedia().
Co-authored-by: Cursor <cursoragent@cursor.com>
* Paginate workspace assets with the app default page size.
Keep list pagination in the action via config('app.pagination.default') instead of a hardcoded API page size.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Move asset API and MCP input rules into FormRequests.
Keep controllers and tools free of inline field validation; MCP tools reuse the request rule definitions.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Document asset MCP tools with explicit parameters and constraints.
Spell out workspace scope, return fields, sibling tools, and rejection cases so agents can call list/get/attach without guessing.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Harden asset attach against races and keep library metadata on the post.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Relock the library asset on attach so a deleted file cannot land on the post.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Document that omitting alt on attach keeps the library alt text.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-16 20:08:20 +00:00
|
|
|
// Assets
|
|
|
|
|
Route::get('/assets', [AssetController::class, 'index'])->name('api.assets.index');
|
|
|
|
|
Route::get('/assets/{media}', [AssetController::class, 'show'])->name('api.assets.show');
|
|
|
|
|
|
2026-03-31 14:35:49 +00:00
|
|
|
// Labels
|
|
|
|
|
Route::get('/labels', [LabelController::class, 'index'])->name('api.labels.index');
|
|
|
|
|
Route::post('/labels', [LabelController::class, 'store'])->name('api.labels.store');
|
|
|
|
|
Route::put('/labels/{label}', [LabelController::class, 'update'])->name('api.labels.update');
|
|
|
|
|
Route::delete('/labels/{label}', [LabelController::class, 'destroy'])->name('api.labels.destroy');
|
|
|
|
|
|
|
|
|
|
// Social Accounts
|
|
|
|
|
Route::get('/social-accounts', [SocialAccountController::class, 'index'])->name('api.social-accounts.index');
|
|
|
|
|
Route::put('/social-accounts/{account}/toggle', [SocialAccountController::class, 'toggle'])->name('api.social-accounts.toggle');
|
2026-07-25 00:47:51 +00:00
|
|
|
Route::get('/social-accounts/{account}/boards', [SocialAccountController::class, 'boards'])
|
|
|
|
|
->middleware('throttle:60,1')
|
|
|
|
|
->name('api.social-accounts.boards');
|
2026-07-25 01:00:07 +00:00
|
|
|
Route::get('/social-accounts/{account}/channels', [SocialAccountController::class, 'channels'])
|
|
|
|
|
->middleware('throttle:60,1')
|
|
|
|
|
->name('api.social-accounts.channels');
|
2026-03-31 14:35:49 +00:00
|
|
|
|
2026-09-04 15:00:32 +00:00
|
|
|
// Webhooks
|
|
|
|
|
Route::get('/webhooks', [WebhookController::class, 'index'])->name('api.webhooks.index');
|
|
|
|
|
Route::post('/webhooks', [WebhookController::class, 'store'])->name('api.webhooks.store');
|
|
|
|
|
Route::get('/webhooks/{webhook}', [WebhookController::class, 'show'])->name('api.webhooks.show');
|
|
|
|
|
Route::put('/webhooks/{webhook}', [WebhookController::class, 'update'])->name('api.webhooks.update');
|
|
|
|
|
Route::post('/webhooks/{webhook}/send-test', [WebhookController::class, 'sendTest'])->name('api.webhooks.send-test');
|
|
|
|
|
Route::post('/webhooks/{webhook}/rotate-secret', [WebhookController::class, 'rotateSecret'])->name('api.webhooks.rotate-secret');
|
|
|
|
|
Route::get('/webhooks/{webhook}/logs', [WebhookController::class, 'logs'])->name('api.webhooks.logs');
|
|
|
|
|
Route::post('/webhooks/{webhook}/logs/{webhookLog}/replay', [WebhookController::class, 'replay'])->name('api.webhooks.replay');
|
|
|
|
|
Route::delete('/webhooks/{webhook}', [WebhookController::class, 'destroy'])->name('api.webhooks.destroy');
|
|
|
|
|
|
2026-03-31 14:35:49 +00:00
|
|
|
// API Keys
|
|
|
|
|
Route::get('/api-keys', [ApiKeyController::class, 'index'])->name('api.api-keys.index');
|
|
|
|
|
Route::post('/api-keys', [ApiKeyController::class, 'store'])->name('api.api-keys.store');
|
|
|
|
|
Route::delete('/api-keys/{apiToken}', [ApiKeyController::class, 'destroy'])->name('api.api-keys.destroy');
|
|
|
|
|
});
|