Add Video Projects schema and workflow engine core (tasks #31, #32)

Foundational layer for the YouTube Content Creation System project,
built as a fully independent domain per the earlier decision (task
#47) — no foreign keys into the existing projects/tasks/ideas tables.

Schema: video_projects, workflow_steps, assets, ai_providers,
workflow_templates (named to avoid colliding with the existing
Post-templates `templates` table).

WorkflowEngine (app/Support/VideoWorkflow/) drives a project's step
sequence: start/startFromTemplate, currentStep, completeStep (with
optional approval gating), approveStep/rejectStep, failStep/retryStep,
skipStep. It only manages state transitions — actual step execution
(AI calls, rendering, etc.) is out of scope here (tasks #33+).

Also fixes four Publisher classes (WordPress/Reddit/Twitter/LinkedIn)
that were missing a `use App\Models\SocialAccount;` import after an
earlier interface change, which broke the entire test suite at
bootstrap; and PostFactory, which still set the now-removed
`social_account_id` column instead of the SocialAccount pivot.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
m105
2026-08-06 20:38:52 +03:00
co-authored by Claude Sonnet 5
parent efdec9eabd
commit 6ff7f78c9c
25 changed files with 1049 additions and 25 deletions
@@ -0,0 +1,32 @@
<?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::create('workflow_templates', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->text('description')->nullable();
// Ordered array of step definitions, e.g.
// [{"type": "script_generation", "requires_approval": true}, ...]
$table->json('steps');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('workflow_templates');
}
};
@@ -0,0 +1,36 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* Deliberately independent of the existing projects/tasks/ideas tables —
* no foreign keys into them. See Task #47 on the "YouTube Content
* Creation System" project for why.
*/
public function up(): void
{
Schema::create('video_projects', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('description')->nullable();
$table->string('status')->default('draft');
$table->foreignId('workflow_template_id')->nullable()->constrained()->nullOnDelete();
$table->softDeletes();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('video_projects');
}
};
@@ -0,0 +1,39 @@
<?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::create('workflow_steps', function (Blueprint $table) {
$table->id();
$table->foreignId('video_project_id')->constrained()->cascadeOnDelete();
$table->string('type');
$table->unsignedInteger('order');
$table->string('status')->default('pending');
$table->boolean('requires_approval')->default(false);
$table->json('input')->nullable();
$table->json('output')->nullable();
$table->text('error')->nullable();
$table->timestamp('started_at')->nullable();
$table->timestamp('completed_at')->nullable();
$table->timestamps();
$table->unique(['video_project_id', 'order']);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('workflow_steps');
}
};
@@ -0,0 +1,35 @@
<?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::create('assets', function (Blueprint $table) {
$table->id();
$table->foreignId('video_project_id')->constrained()->cascadeOnDelete();
$table->foreignId('workflow_step_id')->nullable()->constrained()->nullOnDelete();
$table->string('type');
$table->string('disk_path');
$table->string('mime_type')->nullable();
$table->unsignedBigInteger('size_bytes')->nullable();
$table->unsignedInteger('version')->default(1);
$table->json('metadata')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('assets');
}
};
@@ -0,0 +1,32 @@
<?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::create('ai_providers', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('provider');
$table->string('api_key');
$table->json('config')->nullable();
$table->boolean('is_active')->default(true);
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('ai_providers');
}
};