Implement academy analytics, billing, and web stories updates
This commit is contained in:
67
database/factories/WorldWebStoryFactory.php
Normal file
67
database/factories/WorldWebStoryFactory.php
Normal file
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Models\World;
|
||||
use App\Models\WorldWebStory;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* @extends Factory<WorldWebStory>
|
||||
*/
|
||||
class WorldWebStoryFactory extends Factory
|
||||
{
|
||||
protected $model = WorldWebStory::class;
|
||||
|
||||
public function definition(): array
|
||||
{
|
||||
$title = Str::title($this->faker->unique()->words(3, true));
|
||||
$slug = Str::slug($title);
|
||||
|
||||
return [
|
||||
'world_id' => World::factory(),
|
||||
'slug' => $slug,
|
||||
'title' => $title,
|
||||
'subtitle' => $this->faker->sentence(6),
|
||||
'excerpt' => $this->faker->sentence(14),
|
||||
'description' => $this->faker->paragraph(),
|
||||
'seo_title' => $title . ' – Skinbase Web Story',
|
||||
'seo_description' => $this->faker->sentence(18),
|
||||
'poster_portrait_path' => 'web-stories/worlds/' . $slug . '/poster-portrait.webp',
|
||||
'poster_square_path' => 'web-stories/worlds/' . $slug . '/poster-square.webp',
|
||||
'publisher_logo_path' => 'images/skinbase_logo_96.webp',
|
||||
'status' => WorldWebStory::STATUS_DRAFT,
|
||||
'featured' => false,
|
||||
'active' => true,
|
||||
'noindex' => false,
|
||||
'published_at' => null,
|
||||
'starts_at' => null,
|
||||
'ends_at' => null,
|
||||
'created_by' => User::factory(),
|
||||
'updated_by' => User::factory(),
|
||||
];
|
||||
}
|
||||
|
||||
public function published(): self
|
||||
{
|
||||
return $this->state(fn (): array => [
|
||||
'status' => WorldWebStory::STATUS_PUBLISHED,
|
||||
'published_at' => Carbon::now()->subHour(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function visible(): self
|
||||
{
|
||||
return $this->published()->state(fn (): array => [
|
||||
'active' => true,
|
||||
'noindex' => false,
|
||||
'starts_at' => Carbon::now()->subDay(),
|
||||
'ends_at' => Carbon::now()->addDay(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
50
database/factories/WorldWebStoryPageFactory.php
Normal file
50
database/factories/WorldWebStoryPageFactory.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\Artwork;
|
||||
use App\Models\WorldWebStory;
|
||||
use App\Models\WorldWebStoryPage;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
* @extends Factory<WorldWebStoryPage>
|
||||
*/
|
||||
class WorldWebStoryPageFactory extends Factory
|
||||
{
|
||||
protected $model = WorldWebStoryPage::class;
|
||||
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'story_id' => WorldWebStory::factory(),
|
||||
'artwork_id' => null,
|
||||
'position' => 1,
|
||||
'layout' => WorldWebStoryPage::LAYOUT_ARTWORK,
|
||||
'background_type' => WorldWebStoryPage::BACKGROUND_IMAGE,
|
||||
'background_path' => 'web-stories/worlds/example/pages/page-01.webp',
|
||||
'background_mobile_path' => 'web-stories/worlds/example/pages/page-01.webp',
|
||||
'headline' => 'Story headline',
|
||||
'body' => 'Short supporting copy for this world web story page.',
|
||||
'cta_label' => null,
|
||||
'cta_url' => null,
|
||||
'alt_text' => 'World story background',
|
||||
'caption' => 'Skinbase World',
|
||||
'credit_text' => null,
|
||||
'text_position' => 'bottom',
|
||||
'overlay_strength' => 35,
|
||||
'animation' => 'fade-in',
|
||||
'active' => true,
|
||||
];
|
||||
}
|
||||
|
||||
public function withArtwork(): self
|
||||
{
|
||||
return $this->state(fn (): array => [
|
||||
'artwork_id' => Artwork::factory(),
|
||||
'layout' => WorldWebStoryPage::LAYOUT_ARTWORK,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('world_web_stories', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->foreignId('world_id')->nullable()->constrained('worlds')->nullOnDelete();
|
||||
$table->string('slug')->unique();
|
||||
$table->string('title');
|
||||
$table->string('subtitle')->nullable();
|
||||
$table->text('excerpt')->nullable();
|
||||
$table->text('description')->nullable();
|
||||
$table->string('seo_title')->nullable();
|
||||
$table->text('seo_description')->nullable();
|
||||
$table->string('poster_portrait_path')->nullable();
|
||||
$table->string('poster_square_path')->nullable();
|
||||
$table->string('publisher_logo_path')->nullable();
|
||||
$table->enum('status', ['draft', 'published', 'archived'])->default('draft');
|
||||
$table->boolean('featured')->default(false);
|
||||
$table->boolean('active')->default(true);
|
||||
$table->boolean('noindex')->default(false);
|
||||
$table->timestamp('published_at')->nullable();
|
||||
$table->timestamp('starts_at')->nullable();
|
||||
$table->timestamp('ends_at')->nullable();
|
||||
$table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete();
|
||||
$table->foreignId('updated_by')->nullable()->constrained('users')->nullOnDelete();
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
|
||||
$table->index('world_id');
|
||||
$table->index('slug');
|
||||
$table->index(['status', 'active', 'published_at']);
|
||||
$table->index(['featured', 'status', 'active']);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('world_web_stories');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('world_web_story_pages', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->foreignId('story_id')->constrained('world_web_stories')->cascadeOnDelete();
|
||||
$table->foreignId('artwork_id')->nullable()->constrained('artworks')->nullOnDelete();
|
||||
$table->unsignedInteger('position');
|
||||
$table->enum('layout', ['cover', 'artwork', 'creator', 'mood', 'collection', 'cta']);
|
||||
$table->enum('background_type', ['image', 'video', 'gradient']);
|
||||
$table->string('background_path')->nullable();
|
||||
$table->string('background_mobile_path')->nullable();
|
||||
$table->string('headline')->nullable();
|
||||
$table->text('body')->nullable();
|
||||
$table->string('cta_label')->nullable();
|
||||
$table->string('cta_url')->nullable();
|
||||
$table->text('alt_text')->nullable();
|
||||
$table->string('caption')->nullable();
|
||||
$table->string('credit_text')->nullable();
|
||||
$table->enum('text_position', ['top', 'center', 'bottom'])->default('bottom');
|
||||
$table->unsignedTinyInteger('overlay_strength')->default(35);
|
||||
$table->enum('animation', ['fade-in', 'fly-in-bottom', 'pulse', 'pan-left', 'pan-right'])->nullable();
|
||||
$table->boolean('active')->default(true);
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
|
||||
$table->index(['story_id', 'position']);
|
||||
$table->index(['story_id', 'active']);
|
||||
$table->index('artwork_id');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('world_web_story_pages');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('academy_events', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->string('event_type')->index();
|
||||
$table->string('content_type')->nullable()->index();
|
||||
$table->unsignedBigInteger('content_id')->nullable()->index();
|
||||
$table->foreignId('user_id')->nullable()->constrained()->nullOnDelete();
|
||||
$table->string('visitor_id', 120)->nullable()->index();
|
||||
$table->string('session_id', 120)->nullable()->index();
|
||||
$table->text('url')->nullable();
|
||||
$table->string('route_name')->nullable()->index();
|
||||
$table->text('referrer')->nullable();
|
||||
$table->string('utm_source')->nullable()->index();
|
||||
$table->string('utm_medium')->nullable()->index();
|
||||
$table->string('utm_campaign')->nullable()->index();
|
||||
$table->string('device_type')->nullable()->index();
|
||||
$table->string('browser')->nullable();
|
||||
$table->string('platform')->nullable();
|
||||
$table->string('country_code', 8)->nullable()->index();
|
||||
$table->boolean('is_logged_in')->default(false)->index();
|
||||
$table->boolean('is_subscriber')->default(false)->index();
|
||||
$table->boolean('is_admin')->default(false)->index();
|
||||
$table->boolean('is_bot')->default(false)->index();
|
||||
$table->boolean('is_crawler')->default(false)->index();
|
||||
$table->boolean('is_suspicious')->default(false)->index();
|
||||
$table->json('metadata')->nullable();
|
||||
$table->timestamp('occurred_at')->index();
|
||||
$table->timestamps();
|
||||
|
||||
$table->index(['event_type', 'occurred_at']);
|
||||
$table->index(['content_type', 'content_id', 'occurred_at'], 'academy_events_content_occurred_idx');
|
||||
$table->index(['user_id', 'occurred_at']);
|
||||
$table->index(['visitor_id', 'occurred_at']);
|
||||
$table->index(['is_bot', 'is_admin', 'occurred_at'], 'academy_events_bot_admin_occurred_idx');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('academy_events');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('academy_content_metrics_daily', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->date('date')->index();
|
||||
$table->string('content_type')->index();
|
||||
$table->unsignedBigInteger('content_id')->nullable()->index();
|
||||
$table->unsignedInteger('views')->default(0);
|
||||
$table->unsignedInteger('unique_visitors')->default(0);
|
||||
$table->unsignedInteger('guest_views')->default(0);
|
||||
$table->unsignedInteger('user_views')->default(0);
|
||||
$table->unsignedInteger('subscriber_views')->default(0);
|
||||
$table->unsignedInteger('engaged_views')->default(0);
|
||||
$table->unsignedInteger('scroll_50')->default(0);
|
||||
$table->unsignedInteger('scroll_75')->default(0);
|
||||
$table->unsignedInteger('scroll_100')->default(0);
|
||||
$table->unsignedInteger('likes')->default(0);
|
||||
$table->unsignedInteger('saves')->default(0);
|
||||
$table->unsignedInteger('prompt_copies')->default(0);
|
||||
$table->unsignedInteger('negative_prompt_copies')->default(0);
|
||||
$table->unsignedInteger('starts')->default(0);
|
||||
$table->unsignedInteger('completions')->default(0);
|
||||
$table->unsignedInteger('upgrade_clicks')->default(0);
|
||||
$table->unsignedInteger('premium_preview_views')->default(0);
|
||||
$table->unsignedInteger('search_impressions')->default(0);
|
||||
$table->unsignedInteger('search_clicks')->default(0);
|
||||
$table->unsignedInteger('bounce_count')->default(0);
|
||||
$table->unsignedInteger('avg_engaged_seconds')->nullable();
|
||||
$table->decimal('popularity_score', 12, 2)->default(0);
|
||||
$table->decimal('conversion_score', 12, 2)->default(0);
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['date', 'content_type', 'content_id'], 'academy_metrics_daily_unique');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('academy_content_metrics_daily');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('academy_likes', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
|
||||
$table->string('content_type')->index();
|
||||
$table->unsignedBigInteger('content_id')->index();
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['user_id', 'content_type', 'content_id'], 'academy_likes_unique');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('academy_likes');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('academy_saves', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
|
||||
$table->string('content_type')->index();
|
||||
$table->unsignedBigInteger('content_id')->index();
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['user_id', 'content_type', 'content_id'], 'academy_saves_unique');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('academy_saves');
|
||||
}
|
||||
};
|
||||
@@ -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
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('academy_user_progress', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
|
||||
$table->foreignId('course_id')->nullable()->constrained('academy_courses')->nullOnDelete();
|
||||
$table->foreignId('lesson_id')->nullable()->constrained('academy_lessons')->nullOnDelete();
|
||||
$table->string('status')->index();
|
||||
$table->unsignedTinyInteger('progress_percent')->default(0);
|
||||
$table->timestamp('started_at')->nullable();
|
||||
$table->timestamp('completed_at')->nullable();
|
||||
$table->timestamp('last_seen_at')->nullable();
|
||||
$table->json('metadata')->nullable();
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['user_id', 'course_id', 'lesson_id'], 'academy_user_progress_unique');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('academy_user_progress');
|
||||
}
|
||||
};
|
||||
@@ -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
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('academy_search_logs', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->foreignId('user_id')->nullable()->constrained()->nullOnDelete();
|
||||
$table->string('visitor_id', 120)->nullable()->index();
|
||||
$table->string('query')->index();
|
||||
$table->string('normalized_query')->index();
|
||||
$table->unsignedInteger('results_count')->default(0)->index();
|
||||
$table->string('clicked_content_type')->nullable()->index();
|
||||
$table->unsignedBigInteger('clicked_content_id')->nullable()->index();
|
||||
$table->json('filters')->nullable();
|
||||
$table->boolean('is_logged_in')->default(false)->index();
|
||||
$table->boolean('is_subscriber')->default(false)->index();
|
||||
$table->boolean('is_bot')->default(false)->index();
|
||||
$table->timestamps();
|
||||
|
||||
$table->index(['normalized_query', 'created_at']);
|
||||
$table->index(['results_count', 'created_at']);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('academy_search_logs');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('academy_prompt_templates', function (Blueprint $table): void {
|
||||
if (! Schema::hasColumn('academy_prompt_templates', 'documentation')) {
|
||||
$table->json('documentation')->nullable()->after('workflow_notes');
|
||||
}
|
||||
|
||||
if (! Schema::hasColumn('academy_prompt_templates', 'placeholders')) {
|
||||
$table->json('placeholders')->nullable()->after('documentation');
|
||||
}
|
||||
|
||||
if (! Schema::hasColumn('academy_prompt_templates', 'helper_prompts')) {
|
||||
$table->json('helper_prompts')->nullable()->after('placeholders');
|
||||
}
|
||||
|
||||
if (! Schema::hasColumn('academy_prompt_templates', 'prompt_variants')) {
|
||||
$table->json('prompt_variants')->nullable()->after('helper_prompts');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('academy_prompt_templates', function (Blueprint $table): void {
|
||||
$columns = array_values(array_filter([
|
||||
Schema::hasColumn('academy_prompt_templates', 'documentation') ? 'documentation' : null,
|
||||
Schema::hasColumn('academy_prompt_templates', 'placeholders') ? 'placeholders' : null,
|
||||
Schema::hasColumn('academy_prompt_templates', 'helper_prompts') ? 'helper_prompts' : null,
|
||||
Schema::hasColumn('academy_prompt_templates', 'prompt_variants') ? 'prompt_variants' : null,
|
||||
]));
|
||||
|
||||
if ($columns !== []) {
|
||||
$table->dropColumn($columns);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('comment_reactions', function (Blueprint $table): void {
|
||||
$table->index(['created_at', 'id'], 'idx_comment_reactions_created_at');
|
||||
});
|
||||
|
||||
Schema::table('user_mentions', function (Blueprint $table): void {
|
||||
$table->index(['created_at', 'id'], 'idx_user_mentions_created_at');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('user_mentions', function (Blueprint $table): void {
|
||||
$table->dropIndex('idx_user_mentions_created_at');
|
||||
});
|
||||
|
||||
Schema::table('comment_reactions', function (Blueprint $table): void {
|
||||
$table->dropIndex('idx_comment_reactions_created_at');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
<?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('users', function (Blueprint $table) {
|
||||
$table->string('stripe_id')->nullable()->index();
|
||||
$table->string('pm_type')->nullable();
|
||||
$table->string('pm_last_four', 4)->nullable();
|
||||
$table->timestamp('trial_ends_at')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->dropIndex([
|
||||
'stripe_id',
|
||||
]);
|
||||
|
||||
$table->dropColumn([
|
||||
'stripe_id',
|
||||
'pm_type',
|
||||
'pm_last_four',
|
||||
'trial_ends_at',
|
||||
]);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
<?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('subscriptions', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('user_id');
|
||||
$table->string('type');
|
||||
$table->string('stripe_id')->unique();
|
||||
$table->string('stripe_status');
|
||||
$table->string('stripe_price')->nullable();
|
||||
$table->integer('quantity')->nullable();
|
||||
$table->timestamp('trial_ends_at')->nullable();
|
||||
$table->timestamp('ends_at')->nullable();
|
||||
$table->timestamps();
|
||||
|
||||
$table->index(['user_id', 'stripe_status']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('subscriptions');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
<?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('subscription_items', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('subscription_id');
|
||||
$table->string('stripe_id')->unique();
|
||||
$table->string('stripe_product');
|
||||
$table->string('stripe_price');
|
||||
$table->integer('quantity')->nullable();
|
||||
$table->timestamps();
|
||||
|
||||
$table->index(['subscription_id', 'stripe_price']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('subscription_items');
|
||||
}
|
||||
};
|
||||
@@ -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('subscription_items', function (Blueprint $table) {
|
||||
$table->string('meter_id')->nullable()->after('stripe_price');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('subscription_items', function (Blueprint $table) {
|
||||
$table->dropColumn('meter_id');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -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('subscription_items', function (Blueprint $table) {
|
||||
$table->string('meter_event_name')->nullable()->after('quantity');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('subscription_items', function (Blueprint $table) {
|
||||
$table->dropColumn('meter_event_name');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('academy_billing_events', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->foreignId('user_id')->nullable()->constrained()->nullOnDelete();
|
||||
$table->string('stripe_event_id')->nullable()->unique();
|
||||
$table->string('stripe_customer_id')->nullable()->index();
|
||||
$table->string('stripe_subscription_id')->nullable()->index();
|
||||
$table->string('event_type');
|
||||
$table->string('academy_tier')->nullable();
|
||||
$table->string('academy_plan')->nullable();
|
||||
$table->json('payload_summary')->nullable();
|
||||
$table->timestamp('processed_at')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('academy_billing_events');
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user