Track post creation origin via created_via.
Persist whether a post was created through web, MCP, API, or automation so we can attribute entry points without guessing from request context. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
e2c43339e2
commit
06f83a6571
14 changed files with 93 additions and 2 deletions
|
|
@ -12,6 +12,7 @@
|
|||
use App\DataTransferObjects\Automation\NodeRunResult;
|
||||
use App\Enums\Ai\ContentStyle;
|
||||
use App\Enums\Ai\GeneratorFormat;
|
||||
use App\Enums\Post\CreatedVia;
|
||||
use App\Enums\PostPlatform\ContentType;
|
||||
use App\Models\AutomationRun;
|
||||
use App\Models\SocialAccount;
|
||||
|
|
@ -145,6 +146,7 @@ public function __invoke(AutomationRun $run, array $config): NodeRunResult
|
|||
'content' => $generated->content,
|
||||
'media' => $generated->media,
|
||||
'platforms' => $platforms,
|
||||
'created_via' => CreatedVia::Automation,
|
||||
]);
|
||||
|
||||
$run->update(['generated_post_id' => $post->id]);
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
namespace App\Actions\Post;
|
||||
|
||||
use App\Enums\Post\CreatedVia;
|
||||
use App\Enums\Post\Status as PostStatus;
|
||||
use App\Events\PostCreated;
|
||||
use App\Models\Post;
|
||||
|
|
@ -26,11 +27,15 @@ class CreatePost
|
|||
* `label_ids[]` are attached after creation so the same set of UUIDs
|
||||
* works for REST, MCP, and web callers.
|
||||
*
|
||||
* `created_via` records which entry point created the post (web, mcp,
|
||||
* api, or automation). Callers must set it explicitly.
|
||||
*
|
||||
* @param array{
|
||||
* content?: ?string,
|
||||
* media?: array<int, mixed>,
|
||||
* date?: ?string,
|
||||
* scheduled_at?: ?string,
|
||||
* created_via: CreatedVia,
|
||||
* platforms?: array<int, array{social_account_id: string, content_type?: string, meta?: array<string, mixed>}>,
|
||||
* label_ids?: array<int, string>
|
||||
* } $data
|
||||
|
|
@ -45,6 +50,7 @@ public static function execute(Workspace $workspace, User $user, array $data): P
|
|||
'content' => data_get($data, 'content', ''),
|
||||
'media' => data_get($data, 'media', []),
|
||||
'status' => PostStatus::Draft,
|
||||
'created_via' => data_get($data, 'created_via'),
|
||||
'scheduled_at' => $scheduledAt,
|
||||
]);
|
||||
|
||||
|
|
|
|||
13
app/Enums/Post/CreatedVia.php
Normal file
13
app/Enums/Post/CreatedVia.php
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Enums\Post;
|
||||
|
||||
enum CreatedVia: string
|
||||
{
|
||||
case Web = 'web';
|
||||
case Mcp = 'mcp';
|
||||
case Api = 'api';
|
||||
case Automation = 'automation';
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@
|
|||
use App\Actions\Post\UpdatePost;
|
||||
use App\Enums\Media\Type as MediaType;
|
||||
use App\Enums\Post\Action as PostAction;
|
||||
use App\Enums\Post\CreatedVia;
|
||||
use App\Http\Requests\Api\Post\AttachMediaFromUrlRequest;
|
||||
use App\Http\Requests\Api\Post\StoreMediaRequest;
|
||||
use App\Http\Requests\Api\Post\StorePostRequest;
|
||||
|
|
@ -61,6 +62,8 @@ public function store(StorePostRequest $request): JsonResponse
|
|||
);
|
||||
}
|
||||
|
||||
$data['created_via'] = CreatedVia::Api;
|
||||
|
||||
$post = CreatePost::execute($workspace, $workspace->owner, $data);
|
||||
|
||||
$post->load(['postPlatforms.socialAccount']);
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
use App\Ai\Templates\AiContentTemplate;
|
||||
use App\Ai\Templates\AiTemplateRegistry;
|
||||
use App\Enums\Post\Action as PostAction;
|
||||
use App\Enums\Post\CreatedVia;
|
||||
use App\Enums\Post\Status as PostStatus;
|
||||
use App\Enums\SocialAccount\Platform;
|
||||
use App\Http\Requests\App\Post\StorePostRequest;
|
||||
|
|
@ -189,6 +190,7 @@ public function store(StorePostRequest $request): RedirectResponse|\Symfony\Comp
|
|||
$post = CreatePost::execute($workspace, $request->user(), [
|
||||
'date' => $request->input('date'),
|
||||
'media' => $request->input('media', []),
|
||||
'created_via' => CreatedVia::Web,
|
||||
]);
|
||||
|
||||
return Inertia::location(route('app.posts.edit', $post));
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
use App\Actions\Post\CreatePost;
|
||||
use App\Enums\Media\Type as MediaType;
|
||||
use App\Enums\Post\CreatedVia;
|
||||
use App\Http\Requests\App\PostTemplate\ApplyPostTemplateRequest;
|
||||
use App\Http\Requests\App\PostTemplate\IndexPostTemplateRequest;
|
||||
use App\Http\Resources\App\PostTemplateResource;
|
||||
|
|
@ -89,6 +90,7 @@ public function apply(ApplyPostTemplateRequest $request, string $slug, TemplateI
|
|||
'content' => $content,
|
||||
'media' => $media,
|
||||
'date' => $request->input('date'),
|
||||
'created_via' => CreatedVia::Web,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
use App\Enums\Ai\GeneratorFormat;
|
||||
use App\Enums\Notification\Channel as NotificationChannel;
|
||||
use App\Enums\Notification\Type as NotificationType;
|
||||
use App\Enums\Post\CreatedVia;
|
||||
use App\Enums\PostPlatform\ContentType;
|
||||
use App\Events\Ai\PostCreationReady;
|
||||
use App\Jobs\SendNotification;
|
||||
|
|
@ -195,6 +196,7 @@ private function createPostFromGenerated(Workspace $workspace, GeneratedPost $ge
|
|||
'content' => $generated->content,
|
||||
'media' => $generated->media,
|
||||
'date' => $this->date,
|
||||
'created_via' => CreatedVia::Web,
|
||||
]);
|
||||
|
||||
if ($generated->contentType && $socialAccount) {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
namespace App\Mcp\Tools\Post;
|
||||
|
||||
use App\Actions\Post\CreatePost;
|
||||
use App\Enums\Post\CreatedVia;
|
||||
use App\Enums\PostPlatform\ContentType;
|
||||
use App\Http\Resources\Api\PostResource;
|
||||
use App\Rules\ContentTypeMatchesPlatform;
|
||||
|
|
@ -41,6 +42,8 @@ public function handle(Request $request): ResponseFactory
|
|||
...PostPlatformMetaRules::rules(),
|
||||
]);
|
||||
|
||||
$validated['created_via'] = CreatedVia::Mcp;
|
||||
|
||||
$post = CreatePost::execute($workspace, $request->user(), $validated);
|
||||
|
||||
$post->load(['postPlatforms.socialAccount', 'labels']);
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
use App\DataTransferObjects\MediaItem;
|
||||
use App\Enums\Media\Type;
|
||||
use App\Enums\Post\CreatedVia;
|
||||
use App\Enums\Post\Status as PostStatus;
|
||||
use App\Enums\SocialAccount\Platform;
|
||||
use App\Observers\PostObserver;
|
||||
|
|
@ -34,6 +35,7 @@ class Post extends Model
|
|||
'content',
|
||||
'media',
|
||||
'status',
|
||||
'created_via',
|
||||
'scheduled_at',
|
||||
'published_at',
|
||||
];
|
||||
|
|
@ -42,6 +44,7 @@ protected function casts(): array
|
|||
{
|
||||
return [
|
||||
'status' => PostStatus::class,
|
||||
'created_via' => CreatedVia::class,
|
||||
'media' => 'array',
|
||||
'scheduled_at' => 'datetime',
|
||||
'published_at' => 'datetime',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('posts', function (Blueprint $table) {
|
||||
$table->string('created_via')->nullable()->after('status');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('posts', function (Blueprint $table) {
|
||||
$table->dropColumn('created_via');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
declare(strict_types=1);
|
||||
|
||||
use App\Actions\Post\CreatePost;
|
||||
use App\Enums\Post\CreatedVia;
|
||||
use App\Events\PostCreated;
|
||||
use App\Models\User;
|
||||
use App\Models\Workspace;
|
||||
|
|
@ -16,6 +17,7 @@
|
|||
|
||||
$post = CreatePost::execute($workspace, $user, [
|
||||
'content' => 'Hello world',
|
||||
'created_via' => CreatedVia::Web,
|
||||
]);
|
||||
|
||||
Event::assertDispatched(
|
||||
|
|
@ -24,3 +26,20 @@
|
|||
&& $event->post->workspace_id === $workspace->id,
|
||||
);
|
||||
});
|
||||
|
||||
test('execute persists created_via for each entry point', function (CreatedVia $createdVia) {
|
||||
$user = User::factory()->create();
|
||||
$workspace = Workspace::factory()->create(['user_id' => $user->id]);
|
||||
|
||||
$post = CreatePost::execute($workspace, $user, [
|
||||
'content' => 'Hello world',
|
||||
'created_via' => $createdVia,
|
||||
]);
|
||||
|
||||
expect($post->fresh()->created_via)->toBe($createdVia);
|
||||
})->with([
|
||||
'web' => CreatedVia::Web,
|
||||
'mcp' => CreatedVia::Mcp,
|
||||
'api' => CreatedVia::Api,
|
||||
'automation' => CreatedVia::Automation,
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Enums\Post\CreatedVia;
|
||||
use App\Enums\Post\Status as PostStatus;
|
||||
use App\Enums\PostPlatform\ContentType;
|
||||
use App\Enums\SocialAccount\Platform;
|
||||
|
|
@ -72,7 +73,9 @@
|
|||
->assertCreated()
|
||||
->assertJsonPath('status', PostStatus::Draft->value);
|
||||
|
||||
expect(Post::where('workspace_id', $this->workspace->id)->count())->toBe(1);
|
||||
$post = Post::where('workspace_id', $this->workspace->id)->first();
|
||||
expect($post)->not->toBeNull();
|
||||
expect($post->created_via)->toBe(CreatedVia::Api);
|
||||
});
|
||||
|
||||
it('creates a post with content, media, and labels', function () {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Enums\Post\CreatedVia;
|
||||
use App\Enums\SocialAccount\Platform;
|
||||
use App\Enums\UserWorkspace\Role;
|
||||
use App\Mcp\Servers\TryPostServer;
|
||||
|
|
@ -109,7 +110,9 @@
|
|||
->etc();
|
||||
});
|
||||
|
||||
expect(Post::where('workspace_id', $this->workspace->id)->count())->toBe(1);
|
||||
$post = Post::where('workspace_id', $this->workspace->id)->first();
|
||||
expect($post)->not->toBeNull();
|
||||
expect($post->created_via)->toBe(CreatedVia::Mcp);
|
||||
});
|
||||
|
||||
test('create post with platforms enables only those', function () {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Enums\Post\CreatedVia;
|
||||
use App\Enums\Post\Status as PostStatus;
|
||||
use App\Enums\PostPlatform\ContentType;
|
||||
use App\Enums\PostPlatform\Status;
|
||||
|
|
@ -257,6 +258,7 @@
|
|||
$post = Post::where('workspace_id', $this->workspace->id)->first();
|
||||
expect($post)->not->toBeNull();
|
||||
expect($post->status)->toBe(PostStatus::Draft);
|
||||
expect($post->created_via)->toBe(CreatedVia::Web);
|
||||
expect($post->postPlatforms)->toHaveCount(1);
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue