chore: commit remaining workspace changes
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
<?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('academy_lessons', function (Blueprint $table): void {
|
||||
$table->longText('content_markdown')->nullable()->after('content');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('academy_lessons', function (Blueprint $table): void {
|
||||
$table->dropColumn('content_markdown');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
<?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_lessons', function (Blueprint $table): void {
|
||||
$table->unsignedInteger('lesson_number')->nullable()->after('slug');
|
||||
$table->unsignedInteger('course_order')->nullable()->after('lesson_number');
|
||||
$table->string('series_name', 120)->nullable()->after('course_order');
|
||||
|
||||
$table->index(['series_name', 'course_order', 'lesson_number'], 'academy_lessons_series_course_order_index');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('academy_lessons', function (Blueprint $table): void {
|
||||
$table->dropIndex('academy_lessons_series_course_order_index');
|
||||
$table->dropColumn(['lesson_number', 'course_order', 'series_name']);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
<?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_lessons', function (Blueprint $table): void {
|
||||
$table->string('article_cover_image')->nullable()->after('cover_image');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('academy_lessons', function (Blueprint $table): void {
|
||||
$table->dropColumn('article_cover_image');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
<?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_lessons', function (Blueprint $table): void {
|
||||
$table->json('tags')->nullable()->after('article_cover_image');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('academy_lessons', function (Blueprint $table): void {
|
||||
$table->dropColumn('tags');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,47 @@
|
||||
<?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_courses', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->string('title');
|
||||
$table->string('slug')->unique();
|
||||
$table->string('subtitle')->nullable();
|
||||
$table->text('excerpt')->nullable();
|
||||
$table->longText('description')->nullable();
|
||||
$table->string('cover_image')->nullable();
|
||||
$table->string('teaser_image')->nullable();
|
||||
$table->string('access_level')->default('free');
|
||||
$table->string('difficulty')->default('beginner');
|
||||
$table->string('status')->default('draft');
|
||||
$table->boolean('is_featured')->default(false);
|
||||
$table->unsignedInteger('order_num')->default(0);
|
||||
$table->unsignedInteger('estimated_minutes')->nullable();
|
||||
$table->unsignedInteger('lessons_count_cache')->default(0);
|
||||
$table->timestamp('published_at')->nullable();
|
||||
$table->string('seo_title')->nullable();
|
||||
$table->text('seo_description')->nullable();
|
||||
$table->text('meta_keywords')->nullable();
|
||||
$table->string('og_title')->nullable();
|
||||
$table->text('og_description')->nullable();
|
||||
$table->string('og_image')->nullable();
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
|
||||
$table->index(['status', 'published_at']);
|
||||
$table->index(['is_featured', 'order_num']);
|
||||
$table->index(['access_level', 'difficulty']);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('academy_courses');
|
||||
}
|
||||
};
|
||||
@@ -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::create('academy_course_sections', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->foreignId('course_id')->constrained('academy_courses')->cascadeOnDelete();
|
||||
$table->string('title');
|
||||
$table->string('slug')->nullable();
|
||||
$table->text('description')->nullable();
|
||||
$table->unsignedInteger('order_num')->default(0);
|
||||
$table->boolean('is_visible')->default(true);
|
||||
$table->timestamps();
|
||||
|
||||
$table->index(['course_id', 'order_num']);
|
||||
$table->unique(['course_id', 'slug']);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('academy_course_sections');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
<?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_course_lessons', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->foreignId('course_id')->constrained('academy_courses')->cascadeOnDelete();
|
||||
$table->foreignId('section_id')->nullable()->constrained('academy_course_sections')->nullOnDelete();
|
||||
$table->foreignId('lesson_id')->constrained('academy_lessons')->cascadeOnDelete();
|
||||
$table->unsignedInteger('order_num')->default(0);
|
||||
$table->boolean('is_required')->default(true);
|
||||
$table->string('access_override')->nullable();
|
||||
$table->foreignId('unlock_after_lesson_id')->nullable()->constrained('academy_lessons')->nullOnDelete();
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['course_id', 'lesson_id']);
|
||||
$table->index(['course_id', 'section_id', 'order_num']);
|
||||
$table->index(['lesson_id']);
|
||||
$table->index(['is_required']);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('academy_course_lessons');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
<?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_course_enrollments', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
|
||||
$table->foreignId('course_id')->constrained('academy_courses')->cascadeOnDelete();
|
||||
$table->string('status')->default('active');
|
||||
$table->foreignId('last_lesson_id')->nullable()->constrained('academy_lessons')->nullOnDelete();
|
||||
$table->timestamp('started_at')->nullable();
|
||||
$table->timestamp('completed_at')->nullable();
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['user_id', 'course_id']);
|
||||
$table->index(['course_id', 'status']);
|
||||
$table->index(['user_id', 'status']);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('academy_course_enrollments');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
<?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_lesson_revisions', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->foreignId('lesson_id')->constrained('academy_lessons')->cascadeOnDelete();
|
||||
$table->foreignId('user_id')->nullable()->constrained()->nullOnDelete();
|
||||
$table->string('change_note', 255)->nullable();
|
||||
$table->json('snapshot_json');
|
||||
$table->timestamps();
|
||||
|
||||
$table->index(['lesson_id', 'created_at']);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('academy_lesson_revisions');
|
||||
}
|
||||
};
|
||||
@@ -28,8 +28,12 @@ class AcademyDemoSeeder extends Seeder
|
||||
'slug' => 'what-is-ai-assisted-digital-art',
|
||||
'category_slug' => 'prompting-basics',
|
||||
'title' => 'What Is AI-Assisted Digital Art?',
|
||||
'lesson_number' => 1,
|
||||
'course_order' => 1,
|
||||
'series_name' => 'AI Art Basics',
|
||||
'excerpt' => 'A grounded overview of how Skinbase creators can use AI as a creative assistant instead of a shortcut.',
|
||||
'content' => 'AI-assisted digital art combines prompt-driven ideation, composition decisions, and manual finishing into a single workflow that still depends on taste and intent.',
|
||||
'tags' => ['ai art', 'basics', 'workflow'],
|
||||
'difficulty' => 'beginner',
|
||||
'access_level' => 'free',
|
||||
'lesson_type' => 'article',
|
||||
@@ -39,19 +43,42 @@ class AcademyDemoSeeder extends Seeder
|
||||
'slug' => 'prompting-basics-for-skinbase-creators',
|
||||
'category_slug' => 'prompting-basics',
|
||||
'title' => 'Prompting Basics for Skinbase Creators',
|
||||
'lesson_number' => 2,
|
||||
'course_order' => 2,
|
||||
'series_name' => 'AI Art Basics',
|
||||
'excerpt' => 'Learn how to describe subject, mood, lighting, composition, and finish for Skinbase-native prompt workflows.',
|
||||
'content' => 'Start with a clear subject. Add mood and lighting. Then anchor the composition for the output you actually want to upload.',
|
||||
'tags' => ['prompting', 'composition', 'lighting'],
|
||||
'difficulty' => 'beginner',
|
||||
'access_level' => 'free',
|
||||
'lesson_type' => 'article',
|
||||
'featured' => true,
|
||||
],
|
||||
[
|
||||
'slug' => 'from-prompt-to-finished-artwork',
|
||||
'category_slug' => 'prompting-basics',
|
||||
'title' => 'From Prompt to Finished Artwork',
|
||||
'lesson_number' => 3,
|
||||
'course_order' => 3,
|
||||
'series_name' => 'AI Art Basics',
|
||||
'excerpt' => 'Move from raw generations to polished, upload-ready artwork with a clear finishing workflow.',
|
||||
'content' => 'Review the generation, choose the best candidate, clean up weak areas, and prepare the final upload with intentional metadata and presentation.',
|
||||
'tags' => ['editing', 'cleanup', 'publishing'],
|
||||
'difficulty' => 'beginner',
|
||||
'access_level' => 'free',
|
||||
'lesson_type' => 'workflow',
|
||||
'featured' => true,
|
||||
],
|
||||
[
|
||||
'slug' => 'ai-ethics-and-skinbase-upload-rules',
|
||||
'category_slug' => 'ai-ethics',
|
||||
'title' => 'AI Ethics and Skinbase Upload Rules',
|
||||
'lesson_number' => 7,
|
||||
'course_order' => 7,
|
||||
'series_name' => 'AI Art Basics',
|
||||
'excerpt' => 'Use AI responsibly, label your workflow clearly, and avoid imitation-based prompt packs.',
|
||||
'content' => 'Respect artists, disclose your workflow, and avoid packaging prompts around living-artist imitation or deceptive attribution.',
|
||||
'tags' => ['ethics', 'rules', 'publishing'],
|
||||
'difficulty' => 'beginner',
|
||||
'access_level' => 'free',
|
||||
'lesson_type' => 'ethics',
|
||||
@@ -61,8 +88,12 @@ class AcademyDemoSeeder extends Seeder
|
||||
'slug' => 'how-to-prepare-ai-artwork-for-upload',
|
||||
'category_slug' => 'wallpapers',
|
||||
'title' => 'How to Prepare AI Artwork for Upload',
|
||||
'lesson_number' => 8,
|
||||
'course_order' => 8,
|
||||
'series_name' => 'AI Art Basics',
|
||||
'excerpt' => 'A cleanup checklist for exporting Academy-ready artwork to Skinbase.',
|
||||
'content' => 'Check crop, upscale carefully, fix edge artifacts, and export a final image that fits the target category and composition.',
|
||||
'tags' => ['export', 'quality control', 'upload'],
|
||||
'difficulty' => 'beginner',
|
||||
'access_level' => 'free',
|
||||
'lesson_type' => 'workflow',
|
||||
@@ -74,6 +105,7 @@ class AcademyDemoSeeder extends Seeder
|
||||
'title' => 'Advanced Wallpaper Prompt Engineering',
|
||||
'excerpt' => 'Build prompts that control focal depth, desktop negative space, and repeatable atmosphere.',
|
||||
'content' => 'Use layered subject directives, desktop-safe composition notes, and finishing instructions to keep wallpapers clean and reusable.',
|
||||
'tags' => ['wallpapers', 'prompting', 'advanced'],
|
||||
'difficulty' => 'advanced',
|
||||
'access_level' => 'creator',
|
||||
'lesson_type' => 'workflow',
|
||||
@@ -85,6 +117,7 @@ class AcademyDemoSeeder extends Seeder
|
||||
'title' => 'Creating Consistent Robot Mascots',
|
||||
'excerpt' => 'Keep mascot silhouettes, palettes, and expression readable across multiple prompt iterations.',
|
||||
'content' => 'Define anchor traits first, then preserve them with repeated descriptors and a fixed framing recipe across generations.',
|
||||
'tags' => ['character design', 'consistency', 'iteration'],
|
||||
'difficulty' => 'advanced',
|
||||
'access_level' => 'creator',
|
||||
'lesson_type' => 'workflow',
|
||||
@@ -96,6 +129,7 @@ class AcademyDemoSeeder extends Seeder
|
||||
'title' => 'Building a Skinbase World From Scratch',
|
||||
'excerpt' => 'Plan a world cover, atmosphere kit, and visual language that feels coherent beyond one image.',
|
||||
'content' => 'Start from a world premise, define color anchors, then generate covers, teaser crops, and supporting scenes that share the same visual DNA.',
|
||||
'tags' => ['worldbuilding', 'visual language', 'series'],
|
||||
'difficulty' => 'pro',
|
||||
'access_level' => 'pro',
|
||||
'lesson_type' => 'workflow',
|
||||
@@ -107,6 +141,7 @@ class AcademyDemoSeeder extends Seeder
|
||||
'title' => 'Creating Editorial News Cover Images',
|
||||
'excerpt' => 'Design news cover art that reads fast, supports headlines, and avoids clutter.',
|
||||
'content' => 'Compose with headline space in mind, simplify the focal idea, and leave enough structure for editorial overlays to stay readable.',
|
||||
'tags' => ['covers', 'editorial', 'composition'],
|
||||
'difficulty' => 'pro',
|
||||
'access_level' => 'pro',
|
||||
'lesson_type' => 'workflow',
|
||||
@@ -120,8 +155,12 @@ class AcademyDemoSeeder extends Seeder
|
||||
[
|
||||
'category_id' => $categories->get($lesson['category_slug'])?->id,
|
||||
'title' => $lesson['title'],
|
||||
'lesson_number' => $lesson['lesson_number'] ?? null,
|
||||
'course_order' => $lesson['course_order'] ?? null,
|
||||
'series_name' => $lesson['series_name'] ?? null,
|
||||
'excerpt' => $lesson['excerpt'],
|
||||
'content' => $lesson['content'],
|
||||
'tags' => $lesson['tags'] ?? [],
|
||||
'difficulty' => $lesson['difficulty'],
|
||||
'access_level' => $lesson['access_level'],
|
||||
'lesson_type' => $lesson['lesson_type'],
|
||||
@@ -161,6 +200,38 @@ class AcademyDemoSeeder extends Seeder
|
||||
}
|
||||
}
|
||||
|
||||
$plannedLessonUpdates = [
|
||||
'writing-better-wallpaper-prompts' => [
|
||||
'lesson_number' => 4,
|
||||
'course_order' => 4,
|
||||
'series_name' => 'AI Art Basics',
|
||||
],
|
||||
'style-mood-lighting-and-composition' => [
|
||||
'lesson_number' => 5,
|
||||
'course_order' => 5,
|
||||
'series_name' => 'AI Art Basics',
|
||||
],
|
||||
'negative-prompts-and-quality-control' => [
|
||||
'lesson_number' => 6,
|
||||
'course_order' => 6,
|
||||
'series_name' => 'AI Art Basics',
|
||||
],
|
||||
'titles-tags-categories-and-discovery' => [
|
||||
'lesson_number' => 9,
|
||||
'course_order' => 9,
|
||||
'series_name' => 'AI Art Basics',
|
||||
],
|
||||
'from-idea-to-artwork-a-simple-skinbase-workflow' => [
|
||||
'lesson_number' => 10,
|
||||
'course_order' => 10,
|
||||
'series_name' => 'AI Art Basics',
|
||||
],
|
||||
];
|
||||
|
||||
foreach ($plannedLessonUpdates as $slug => $attributes) {
|
||||
AcademyLesson::query()->where('slug', $slug)->update($attributes);
|
||||
}
|
||||
|
||||
$prompts = [
|
||||
[
|
||||
'slug' => 'fantasy-floating-island-wallpaper',
|
||||
|
||||
Reference in New Issue
Block a user