Treat created_via as non-blocking analytics data.

Default to web when the value is missing, null, or invalid so post creation never fails over provenance.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Paulo Castellano 2026-07-24 11:34:24 -03:00
parent 459f4dd5a5
commit a545d29cfb
2 changed files with 46 additions and 16 deletions

View file

@ -12,7 +12,6 @@
use App\Models\Workspace;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use InvalidArgumentException;
class CreatePost
{
@ -29,37 +28,30 @@ class CreatePost
* 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.
* api, or automation). Analytical only — defaults to web when omitted
* or invalid, and never blocks creation.
*
* @param array{
* content?: ?string,
* media?: array<int, mixed>,
* date?: ?string,
* scheduled_at?: ?string,
* created_via: CreatedVia,
* created_via?: CreatedVia,
* platforms?: array<int, array{social_account_id: string, content_type?: string, meta?: array<string, mixed>}>,
* label_ids?: array<int, string>
* } $data
*
* @throws InvalidArgumentException when created_via is missing or not a CreatedVia case
*/
public static function execute(Workspace $workspace, User $user, array $data): Post
{
$createdVia = data_get($data, 'created_via');
if (! $createdVia instanceof CreatedVia) {
throw new InvalidArgumentException('created_via must be a CreatedVia enum case.');
}
$scheduledAt = self::resolveScheduledAt($data);
$post = DB::transaction(function () use ($workspace, $user, $data, $scheduledAt, $createdVia): Post {
$post = DB::transaction(function () use ($workspace, $user, $data, $scheduledAt): Post {
$post = $workspace->posts()->create([
'user_id' => $user->id,
'content' => data_get($data, 'content', ''),
'media' => data_get($data, 'media', []),
'status' => PostStatus::Draft,
'created_via' => $createdVia,
'created_via' => self::resolveCreatedVia($data),
'scheduled_at' => $scheduledAt,
]);
@ -105,6 +97,26 @@ public static function execute(Workspace $workspace, User $user, array $data): P
return $post;
}
/**
* Analytical only — never fail creation over a missing/invalid value.
*
* @param array<string, mixed> $data
*/
private static function resolveCreatedVia(array $data): CreatedVia
{
$value = data_get($data, 'created_via');
if ($value instanceof CreatedVia) {
return $value;
}
if (is_string($value)) {
return CreatedVia::tryFrom($value) ?? CreatedVia::Web;
}
return CreatedVia::Web;
}
/**
* @param array<string, mixed> $data
*/

View file

@ -44,11 +44,29 @@
'automation' => CreatedVia::Automation,
]);
test('execute requires created_via', function () {
test('execute defaults created_via to web when omitted', function () {
$user = User::factory()->create();
$workspace = Workspace::factory()->create(['user_id' => $user->id]);
CreatePost::execute($workspace, $user, [
$post = CreatePost::execute($workspace, $user, [
'content' => 'Hello world',
]);
})->throws(InvalidArgumentException::class, 'created_via must be a CreatedVia enum case.');
expect($post->fresh()->created_via)->toBe(CreatedVia::Web);
});
test('execute defaults created_via to web when null or invalid', function (mixed $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::Web);
})->with([
'null' => null,
'invalid string' => 'not-a-channel',
'integer' => 1,
]);