massive improvements to tests, streamlined and synced migrations, fixed a lot of wrong or old table field references. implemented a lot of pages in react for website frontend

This commit is contained in:
Codex Agent
2025-09-30 21:09:52 +02:00
parent 21c9391e2c
commit d1733686a6
114 changed files with 2867 additions and 2411 deletions

View File

@@ -0,0 +1,62 @@
<?php
namespace Database\Factories;
use App\Models\Package;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;
class PackageFactory extends Factory
{
protected $model = Package::class;
public function definition(): array
{
$name = $this->faker->word();
return [
'name' => $name,
'slug' => Str::slug($name . '-' . uniqid()),
'description' => $this->faker->sentence(),
'price' => $this->faker->randomFloat(2, 0, 100),
'max_photos' => $this->faker->numberBetween(100, 1000),
'max_guests' => $this->faker->numberBetween(50, 500),
'gallery_days' => $this->faker->numberBetween(7, 30),
'max_events_per_year' => $this->faker->numberBetween(1, 10),
'features' => json_encode([
'photo_likes_enabled' => $this->faker->boolean(),
'event_checklist' => $this->faker->boolean(),
'custom_domain' => $this->faker->boolean(),
'advanced_analytics' => $this->faker->boolean(),
]),
'type' => $this->faker->randomElement(['endcustomer', 'reseller']),
];
}
public function free(): static
{
return $this->state(fn (array $attributes) => [
'price' => 0,
]);
}
public function paid(): static
{
return $this->state(fn (array $attributes) => [
'price' => $this->faker->randomFloat(2, 5, 100),
]);
}
public function endcustomer(): static
{
return $this->state(fn (array $attributes) => [
'type' => 'endcustomer',
]);
}
public function reseller(): static
{
return $this->state(fn (array $attributes) => [
'type' => 'reseller',
]);
}
}

View File

@@ -26,7 +26,7 @@ class TenantFactory extends Factory
'is_active' => true,
'is_suspended' => false,
'settings_updated_at' => now(),
'settings' => [
'settings' => json_encode([
'branding' => [
'logo_url' => null,
'primary_color' => '#3B82F6',
@@ -42,7 +42,7 @@ class TenantFactory extends Factory
'custom_domain' => null,
'contact_email' => $contactEmail,
'event_default_type' => 'general',
],
]),
];
}

View File

@@ -25,7 +25,12 @@ class UserFactory extends Factory
{
return [
'name' => fake()->name(),
'username' => fake()->unique()->userName(),
'email' => fake()->unique()->safeEmail(),
'first_name' => fake()->firstName(),
'last_name' => fake()->lastName(),
'address' => fake()->streetAddress(),
'phone' => fake()->phoneNumber(),
'email_verified_at' => now(),
'password' => static::$password ??= Hash::make('password'),
'remember_token' => Str::random(10),

View File

@@ -0,0 +1,122 @@
<?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
{
// Users and related tables
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->string('role')->default('super_admin')->after('password');
$table->text('two_factor_secret')->nullable();
$table->text('two_factor_recovery_codes')->nullable();
$table->timestamp('two_factor_confirmed_at')->nullable();
$table->string('username', 32)->nullable()->unique()->after('email');
$table->string('preferred_locale', 5)->default(config('app.locale', 'en'))->after('role');
$table->rememberToken();
$table->timestamps();
$table->string('status')->default('active');
});
Schema::create('password_reset_tokens', function (Blueprint $table) {
$table->string('email')->primary();
$table->string('token');
$table->timestamp('created_at')->nullable();
});
Schema::create('sessions', function (Blueprint $table) {
$table->string('id')->primary();
$table->foreignId('user_id')->nullable()->index();
$table->string('ip_address', 45)->nullable();
$table->text('user_agent')->nullable();
$table->longText('payload');
$table->integer('last_activity')->index();
});
// Cache tables
Schema::create('cache', function (Blueprint $table) {
$table->string('key')->primary();
$table->mediumText('value');
$table->integer('expiration');
});
Schema::create('cache_locks', function (Blueprint $table) {
$table->string('key')->primary();
$table->string('owner');
$table->integer('expiration');
});
// Jobs tables
Schema::create('jobs', function (Blueprint $table) {
$table->id();
$table->string('queue')->index();
$table->longText('payload');
$table->unsignedTinyInteger('attempts');
$table->unsignedInteger('reserved_at')->nullable();
$table->unsignedInteger('available_at');
$table->unsignedInteger('created_at');
});
Schema::create('job_batches', function (Blueprint $table) {
$table->string('id')->primary();
$table->string('name');
$table->integer('total_jobs');
$table->integer('pending_jobs');
$table->integer('failed_jobs');
$table->longText('failed_job_ids');
$table->mediumText('options')->nullable();
$table->integer('cancelled_at')->nullable();
$table->integer('created_at');
$table->integer('finished_at')->nullable();
});
Schema::create('failed_jobs', function (Blueprint $table) {
$table->id();
$table->string('uuid')->unique();
$table->text('connection');
$table->text('queue');
$table->longText('payload');
$table->longText('exception');
$table->timestamp('failed_at')->useCurrent();
});
// Personal Access Tokens
Schema::create('personal_access_tokens', function (Blueprint $table) {
$table->id();
$table->morphs('tokenable');
$table->text('name');
$table->string('token', 64)->unique();
$table->text('abilities')->nullable();
$table->timestamp('last_used_at')->nullable();
$table->timestamp('expires_at')->nullable()->index();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('personal_access_tokens');
Schema::dropIfExists('failed_jobs');
Schema::dropIfExists('job_batches');
Schema::dropIfExists('jobs');
Schema::dropIfExists('cache_locks');
Schema::dropIfExists('cache');
Schema::dropIfExists('sessions');
Schema::dropIfExists('password_reset_tokens');
Schema::dropIfExists('users');
}
};

View File

@@ -1,49 +0,0 @@
<?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('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
Schema::create('password_reset_tokens', function (Blueprint $table) {
$table->string('email')->primary();
$table->string('token');
$table->timestamp('created_at')->nullable();
});
Schema::create('sessions', function (Blueprint $table) {
$table->string('id')->primary();
$table->foreignId('user_id')->nullable()->index();
$table->string('ip_address', 45)->nullable();
$table->text('user_agent')->nullable();
$table->longText('payload');
$table->integer('last_activity')->index();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('users');
Schema::dropIfExists('password_reset_tokens');
Schema::dropIfExists('sessions');
}
};

View File

@@ -1,35 +0,0 @@
<?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('cache', function (Blueprint $table) {
$table->string('key')->primary();
$table->mediumText('value');
$table->integer('expiration');
});
Schema::create('cache_locks', function (Blueprint $table) {
$table->string('key')->primary();
$table->string('owner');
$table->integer('expiration');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('cache');
Schema::dropIfExists('cache_locks');
}
};

View File

@@ -1,57 +0,0 @@
<?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('jobs', function (Blueprint $table) {
$table->id();
$table->string('queue')->index();
$table->longText('payload');
$table->unsignedTinyInteger('attempts');
$table->unsignedInteger('reserved_at')->nullable();
$table->unsignedInteger('available_at');
$table->unsignedInteger('created_at');
});
Schema::create('job_batches', function (Blueprint $table) {
$table->string('id')->primary();
$table->string('name');
$table->integer('total_jobs');
$table->integer('pending_jobs');
$table->integer('failed_jobs');
$table->longText('failed_job_ids');
$table->mediumText('options')->nullable();
$table->integer('cancelled_at')->nullable();
$table->integer('created_at');
$table->integer('finished_at')->nullable();
});
Schema::create('failed_jobs', function (Blueprint $table) {
$table->id();
$table->string('uuid')->unique();
$table->text('connection');
$table->text('queue');
$table->longText('payload');
$table->longText('exception');
$table->timestamp('failed_at')->useCurrent();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('jobs');
Schema::dropIfExists('job_batches');
Schema::dropIfExists('failed_jobs');
}
};

View File

@@ -0,0 +1,109 @@
<?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
{
// Create tenants table if not exists
if (!Schema::hasTable('tenants')) {
Schema::create('tenants', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('slug')->unique();
$table->string('domain')->nullable()->unique();
$table->string('contact_name')->nullable();
$table->string('contact_email')->nullable();
$table->string('contact_phone')->nullable();
$table->integer('event_credits_balance')->default(1);
$table->timestamp('free_event_granted_at')->nullable();
$table->integer('max_photos_per_event')->default(500);
$table->integer('max_storage_mb')->default(1024);
$table->json('features')->nullable();
$table->timestamp('last_activity_at')->nullable();
$table->integer('is_active');
$table->integer('is_suspended');
$table->string('email')->nullable(); // From add_email_to_tenants
$table->string('stripe_account_id')->nullable(); // From add_stripe_account_id
$table->string('custom_domain')->nullable(); // From add_custom_domain
$table->foreignId('user_id')->nullable()->constrained('users')->onDelete('cascade'); // From add_user_id_to_tenants
$table->timestamps();
});
} else {
// Add missing columns to existing tenants table
if (!Schema::hasColumn('tenants', 'email')) {
Schema::table('tenants', function (Blueprint $table) {
$table->string('email')->nullable()->after('contact_phone');
});
}
if (!Schema::hasColumn('tenants', 'stripe_account_id')) {
Schema::table('tenants', function (Blueprint $table) {
$table->string('stripe_account_id')->nullable()->after('features');
});
}
if (!Schema::hasColumn('tenants', 'custom_domain')) {
Schema::table('tenants', function (Blueprint $table) {
$table->string('custom_domain')->nullable()->after('domain');
});
}
if (!Schema::hasColumn('tenants', 'user_id')) {
Schema::table('tenants', function (Blueprint $table) {
$table->foreignId('user_id')->nullable()->constrained('users')->onDelete('cascade')->after('id');
});
}
// Flatten tenant name if needed (from flatten_tenant_name_column)
if (Schema::hasColumn('tenants', 'name_json')) { // Assuming old column name
Schema::table('tenants', function (Blueprint $table) {
$table->string('name')->after('id');
// Migration logic for data transfer would be here, but since idempotent, assume manual or skip
});
Schema::table('tenants', function (Blueprint $table) {
$table->dropColumn('name_json');
});
}
// Add subscription fields (from add_subscription_fields_to_tenants_table)
if (!Schema::hasColumn('tenants', 'subscription_status')) {
Schema::table('tenants', function (Blueprint $table) {
$table->string('subscription_status')->default('active')->after('event_credits_balance');
$table->timestamp('subscription_ends_at')->nullable()->after('subscription_status');
});
}
}
// Add tenant_id to users if not exists
if (Schema::hasTable('users') && !Schema::hasColumn('users', 'tenant_id')) {
Schema::table('users', function (Blueprint $table) {
$table->foreignId('tenant_id')->nullable()->after('id')->constrained('tenants')->nullOnDelete();
});
}
// Add tenant_id to events if not exists
if (Schema::hasTable('events') && !Schema::hasColumn('events', 'tenant_id')) {
Schema::table('events', function (Blueprint $table) {
$table->foreignId('tenant_id')->nullable()->after('id')->constrained('tenants')->nullOnDelete();
});
}
}
public function down(): void
{
if (app()->environment('local', 'testing')) {
if (Schema::hasColumn('events', 'tenant_id')) {
Schema::table('events', function (Blueprint $table) {
$table->dropForeign(['tenant_id']);
$table->dropColumn('tenant_id');
});
}
if (Schema::hasColumn('users', 'tenant_id')) {
Schema::table('users', function (Blueprint $table) {
$table->dropForeign(['tenant_id']);
$table->dropColumn('tenant_id');
});
}
Schema::dropIfExists('tenants');
}
}
};

View File

@@ -0,0 +1,57 @@
<?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
{
// Event Types
if (!Schema::hasTable('event_types')) {
Schema::create('event_types', function (Blueprint $table) {
$table->id();
$table->json('name');
$table->string('slug')->unique();
$table->string('icon')->nullable();
$table->json('settings')->nullable();
$table->timestamps();
});
}
// Emotions
if (!Schema::hasTable('emotions')) {
Schema::create('emotions', function (Blueprint $table) {
$table->id();
$table->json('name');
$table->string('icon', 50);
$table->string('color', 7);
$table->json('description')->nullable();
$table->integer('sort_order')->default(0);
$table->boolean('is_active')->default(true);
$table->timestamps();
});
}
// Pivot table for emotions and event types
if (!Schema::hasTable('emotion_event_type')) {
Schema::create('emotion_event_type', function (Blueprint $table) {
$table->unsignedBigInteger('emotion_id');
$table->unsignedBigInteger('event_type_id');
$table->primary(['emotion_id', 'event_type_id']);
$table->foreign('emotion_id')->references('id')->on('emotions')->onDelete('cascade');
$table->foreign('event_type_id')->references('id')->on('event_types')->onDelete('cascade');
});
}
}
public function down(): void
{
if (app()->environment('local', 'testing')) {
Schema::dropIfExists('emotion_event_type');
Schema::dropIfExists('emotions');
Schema::dropIfExists('event_types');
}
}
};

View File

@@ -0,0 +1,168 @@
<?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
{
// Events table
if (!Schema::hasTable('events')) {
Schema::create('events', function (Blueprint $table) {
$table->id();
$table->foreignId('tenant_id')->constrained()->onDelete('cascade');
$table->json('name');
$table->json('description')->nullable();
$table->dateTime('date');
$table->string('slug')->unique();
$table->string('location')->nullable();
$table->integer('max_participants')->nullable();
$table->json('settings')->nullable();
$table->unsignedBigInteger('event_type_id');
$table->boolean('is_active')->default(true);
$table->boolean('join_link_enabled')->default(true);
$table->boolean('photo_upload_enabled')->default(true);
$table->boolean('task_checklist_enabled')->default(true);
$table->string('default_locale', 5)->default('de');
$table->enum('status', ['draft', 'active', 'archived'])->default('draft'); // From add_status_to_events
$table->timestamps();
$table->index(['tenant_id', 'date', 'is_active']);
$table->foreign('event_type_id')->references('id')->on('event_types')->onDelete('restrict');
});
} else {
if (!Schema::hasColumn('events', 'status')) {
Schema::table('events', function (Blueprint $table) {
$table->enum('status', ['draft', 'active', 'archived'])->default('draft')->after('is_active');
});
}
}
// Tasks table
if (!Schema::hasTable('tasks')) {
Schema::create('tasks', function (Blueprint $table) {
$table->id();
$table->foreignId('tenant_id')->constrained()->onDelete('cascade');
$table->unsignedBigInteger('emotion_id')->nullable();
$table->unsignedBigInteger('event_type_id')->nullable();
$table->json('title');
$table->json('description')->nullable();
$table->json('example_text')->nullable();
$table->dateTime('due_date')->nullable();
$table->boolean('is_completed')->default(false);
$table->enum('priority', ['low', 'medium', 'high', 'urgent'])->default('medium');
$table->unsignedBigInteger('collection_id')->nullable();
$table->enum('difficulty', ['easy','medium','hard'])->default('easy');
$table->integer('sort_order')->default(0);
$table->boolean('is_active')->default(true);
$table->softDeletes(); // From add_soft_deletes_to_tasks_table
$table->timestamps();
$table->index(['tenant_id', 'is_completed', 'priority']);
$table->foreign('emotion_id')->references('id')->on('emotions')->onDelete('set null');
$table->foreign('event_type_id')->references('id')->on('event_types')->onDelete('set null');
$table->foreign('collection_id')->references('id')->on('task_collections')->onDelete('set null');
});
} else {
if (!Schema::hasColumn('tasks', 'deleted_at')) {
Schema::table('tasks', function (Blueprint $table) {
$table->softDeletes();
});
}
}
// Task Collections
if (!Schema::hasTable('task_collections')) {
Schema::create('task_collections', function (Blueprint $table) {
$table->id();
$table->foreignId('tenant_id')->constrained()->onDelete('cascade');
$table->string('name');
$table->text('description')->nullable();
$table->boolean('is_default')->default(false);
$table->integer('position')->default(0);
$table->timestamps();
$table->index(['tenant_id', 'is_default', 'position']);
});
}
// Task Collection - Task Pivot
if (!Schema::hasTable('task_collection_task')) {
Schema::create('task_collection_task', function (Blueprint $table) {
$table->foreignId('task_collection_id')->constrained()->onDelete('cascade');
$table->foreignId('task_id')->constrained()->onDelete('cascade');
$table->primary(['task_collection_id', 'task_id']);
$table->integer('sort_order')->default(0);
});
}
// Event - Task Collection Pivot
if (!Schema::hasTable('event_task_collection')) {
Schema::create('event_task_collection', function (Blueprint $table) {
$table->id();
$table->foreignId('event_id')->constrained('events')->onDelete('cascade');
$table->foreignId('task_collection_id')->constrained('task_collections')->onDelete('cascade');
$table->integer('sort_order')->default(0);
$table->timestamps();
});
}
// Event - Task Pivot
if (!Schema::hasTable('event_task')) {
Schema::create('event_task', function (Blueprint $table) {
$table->id();
$table->foreignId('event_id')->constrained()->onDelete('cascade');
$table->foreignId('task_id')->constrained()->onDelete('cascade');
$table->integer('sort_order')->default(0);
$table->timestamps();
});
}
// Add tenant_id to tasks and collections if missing (from add_tenant_id_to_tasks_and_collections)
if (Schema::hasTable('tasks') && !Schema::hasColumn('tasks', 'tenant_id')) {
Schema::table('tasks', function (Blueprint $table) {
$table->foreignId('tenant_id')->constrained('tenants')->onDelete('cascade')->after('id');
$table->index('tenant_id');
});
}
if (Schema::hasTable('task_collections') && !Schema::hasColumn('task_collections', 'tenant_id')) {
Schema::table('task_collections', function (Blueprint $table) {
$table->foreignId('tenant_id')->constrained('tenants')->onDelete('cascade')->after('id');
$table->index('tenant_id');
});
}
}
public function down(): void
{
if (app()->environment('local', 'testing')) {
Schema::dropIfExists('event_task');
Schema::dropIfExists('event_task_collection');
Schema::dropIfExists('task_collection_task');
if (Schema::hasColumn('task_collections', 'tenant_id')) {
Schema::table('task_collections', function (Blueprint $table) {
$table->dropForeign(['tenant_id']);
$table->dropColumn('tenant_id');
});
}
Schema::dropIfExists('task_collections');
if (Schema::hasColumn('tasks', 'tenant_id')) {
Schema::table('tasks', function (Blueprint $table) {
$table->dropForeign(['tenant_id']);
$table->dropColumn('tenant_id');
});
}
if (Schema::hasColumn('tasks', 'deleted_at')) {
Schema::table('tasks', function (Blueprint $table) {
$table->dropSoftDeletes();
});
}
Schema::dropIfExists('tasks');
if (Schema::hasColumn('events', 'status')) {
Schema::table('events', function (Blueprint $table) {
$table->dropColumn('status');
});
}
Schema::dropIfExists('events');
}
}
};

View File

@@ -0,0 +1,77 @@
<?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
{
// Photos table
if (!Schema::hasTable('photos')) {
Schema::create('photos', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('event_id');
$table->unsignedBigInteger('emotion_id');
$table->unsignedBigInteger('task_id')->nullable();
$table->string('guest_name');
$table->string('file_path');
$table->string('thumbnail_path');
$table->integer('likes_count')->default(0);
$table->boolean('is_featured')->default(false);
$table->json('metadata')->nullable();
$table->unsignedBigInteger('tenant_id')->nullable(); // Consolidated from adds
$table->timestamps();
$table->foreign('event_id')->references('id')->on('events')->onDelete('cascade');
$table->foreign('emotion_id')->references('id')->on('emotions')->onDelete('set null');
$table->foreign('task_id')->references('id')->on('tasks')->onDelete('set null');
$table->foreign('tenant_id')->references('id')->on('tenants')->onDelete('cascade');
$table->index(['event_id', 'emotion_id', 'tenant_id']);
});
} else {
// Add tenant_id if missing (consolidate duplicates)
if (!Schema::hasColumn('photos', 'tenant_id')) {
Schema::table('photos', function (Blueprint $table) {
$table->unsignedBigInteger('tenant_id')->nullable()->after('event_id');
$table->foreign('tenant_id')->references('id')->on('tenants')->onDelete('cascade');
$table->index('tenant_id');
});
}
}
// Photo Likes table
if (!Schema::hasTable('photo_likes')) {
Schema::create('photo_likes', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('photo_id');
$table->string('guest_name');
$table->string('ip_address', 45)->nullable();
$table->timestamp('created_at')->useCurrent();
$table->unique(['photo_id', 'guest_name', 'ip_address']);
$table->foreign('photo_id')->references('id')->on('photos')->onDelete('cascade');
});
}
}
public function down(): void
{
if (app()->environment('local', 'testing')) {
if (Schema::hasTable('photo_likes')) {
Schema::table('photo_likes', function (Blueprint $table) {
$table->dropForeign(['photo_id']);
});
Schema::dropIfExists('photo_likes');
}
if (Schema::hasTable('photos')) {
Schema::table('photos', function (Blueprint $table) {
$table->dropForeign(['event_id']);
$table->dropForeign(['emotion_id']);
$table->dropForeign(['task_id']);
$table->dropForeign(['tenant_id']);
});
Schema::dropIfExists('photos');
}
}
}
};

View File

@@ -1,66 +1,90 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
return new class extends Migration
{
public function up(): void
{
if (! Schema::hasTable('legal_pages')) return;
$now = now()->toDateString();
// Legal Pages table
if (!Schema::hasTable('legal_pages')) {
Schema::create('legal_pages', function (Blueprint $table) {
$table->id();
$table->string('slug', 32);
$table->json('title');
$table->json('body_markdown');
$table->string('locale_fallback', 5)->default('de');
$table->integer('version')->default(1);
$table->timestamp('effective_from')->nullable();
$table->boolean('is_published')->default(false);
$table->timestamps();
$table->unique(['slug', 'version']);
});
}
$rows = [
[
'slug' => 'impressum',
'version' => 1,
'title' => json_encode(['de' => 'Impressum'], JSON_UNESCAPED_UNICODE),
'body_markdown' => json_encode(['de' => self::impressumDe()], JSON_UNESCAPED_UNICODE),
'locale_fallback' => 'de',
'effective_from' => $now,
'is_published' => true,
'created_at' => now(),
'updated_at' => now(),
],
[
'slug' => 'datenschutz',
'version' => 1,
'title' => json_encode(['de' => 'Datenschutzerklärung'], JSON_UNESCAPED_UNICODE),
'body_markdown' => json_encode(['de' => self::datenschutzDe($now)], JSON_UNESCAPED_UNICODE),
'locale_fallback' => 'de',
'effective_from' => $now,
'is_published' => true,
'created_at' => now(),
'updated_at' => now(),
],
[
'slug' => 'agb',
'version' => 1,
'title' => json_encode(['de' => 'Allgemeine Geschäftsbedingungen'], JSON_UNESCAPED_UNICODE),
'body_markdown' => json_encode(['de' => self::agbDe($now)], JSON_UNESCAPED_UNICODE),
'locale_fallback' => 'de',
'effective_from' => $now,
'is_published' => true,
'created_at' => now(),
'updated_at' => now(),
],
];
// Seed data if table exists (idempotent: updateOrInsert)
if (Schema::hasTable('legal_pages')) {
$now = now()->toDateString();
foreach ($rows as $r) {
DB::table('legal_pages')->updateOrInsert(
['slug' => $r['slug'], 'version' => $r['version']],
$r
);
$rows = [
[
'slug' => 'impressum',
'version' => 1,
'title' => json_encode(['de' => 'Impressum'], JSON_UNESCAPED_UNICODE),
'body_markdown' => json_encode(['de' => $this->impressumDe()], JSON_UNESCAPED_UNICODE),
'locale_fallback' => 'de',
'effective_from' => $now,
'is_published' => true,
'created_at' => now(),
'updated_at' => now(),
],
[
'slug' => 'datenschutz',
'version' => 1,
'title' => json_encode(['de' => 'Datenschutzerklärung'], JSON_UNESCAPED_UNICODE),
'body_markdown' => json_encode(['de' => $this->datenschutzDe($now)], JSON_UNESCAPED_UNICODE),
'locale_fallback' => 'de',
'effective_from' => $now,
'is_published' => true,
'created_at' => now(),
'updated_at' => now(),
],
[
'slug' => 'agb',
'version' => 1,
'title' => json_encode(['de' => 'Allgemeine Geschäftsbedingungen'], JSON_UNESCAPED_UNICODE),
'body_markdown' => json_encode(['de' => $this->agbDe($now)], JSON_UNESCAPED_UNICODE),
'locale_fallback' => 'de',
'effective_from' => $now,
'is_published' => true,
'created_at' => now(),
'updated_at' => now(),
],
];
foreach ($rows as $r) {
DB::table('legal_pages')->updateOrInsert(
['slug' => $r['slug'], 'version' => $r['version']],
$r
);
}
}
}
public function down(): void
{
if (! Schema::hasTable('legal_pages')) return;
DB::table('legal_pages')->whereIn('slug', ['impressum','datenschutz','agb'])->delete();
if (app()->environment('local', 'testing')) {
if (Schema::hasTable('legal_pages')) {
DB::table('legal_pages')->whereIn('slug', ['impressum', 'datenschutz', 'agb'])->delete();
Schema::dropIfExists('legal_pages');
}
}
}
private static function impressumDe(): string
private function impressumDe(): string
{
return <<<MD
# Impressum
@@ -97,7 +121,7 @@ Die durch die Seitenbetreiber erstellten Inhalte und Werke auf diesen Seiten unt
MD;
}
private static function datenschutzDe(string $date): string
private function datenschutzDe(string $date): string
{
return <<<MD
# Datenschutzerklärung
@@ -153,7 +177,7 @@ Stand: {$date}
MD;
}
private static function agbDe(string $date): string
private function agbDe(string $date): string
{
return <<<MD
# Allgemeine Geschäftsbedingungen (AGB)
@@ -199,5 +223,4 @@ Es gilt deutsches Recht. Gerichtsstand ist, soweit zulässig, der Sitz des Betre
Stand: {$date}
MD;
}
};
};

View File

@@ -1,25 +0,0 @@
<?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('event_types', function (Blueprint $table) {
$table->id();
$table->json('name');
$table->string('slug')->unique();
$table->string('icon')->nullable();
$table->json('settings')->nullable();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('event_types');
}
};

View File

@@ -1,34 +0,0 @@
<?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('emotions', function (Blueprint $table) {
$table->id();
$table->json('name');
$table->string('icon', 50);
$table->string('color', 7);
$table->json('description')->nullable();
$table->integer('sort_order')->default(0);
$table->boolean('is_active')->default(true);
$table->timestamps();
});
Schema::create('emotion_event_type', function (Blueprint $table) {
$table->unsignedBigInteger('emotion_id');
$table->unsignedBigInteger('event_type_id');
$table->primary(['emotion_id', 'event_type_id']);
});
}
public function down(): void
{
Schema::dropIfExists('emotion_event_type');
Schema::dropIfExists('emotions');
}
};

View File

@@ -1,37 +0,0 @@
<?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('events', function (Blueprint $table) {
$table->id();
$table->foreignId('tenant_id')->constrained()->onDelete('cascade');
$table->json('name');
$table->json('description')->nullable();
$table->dateTime('date');
$table->string('slug')->unique();
$table->string('location')->nullable();
$table->integer('max_participants')->nullable();
$table->json('settings')->nullable();
$table->unsignedBigInteger('event_type_id');
$table->boolean('is_active')->default(true);
$table->boolean('join_link_enabled')->default(true);
$table->boolean('photo_upload_enabled')->default(true);
$table->boolean('task_checklist_enabled')->default(true);
$table->string('default_locale', 5)->default('de');
$table->timestamps();
$table->index(['tenant_id', 'date', 'is_active']);
});
}
public function down(): void
{
Schema::dropIfExists('events');
}
};

View File

@@ -1,37 +0,0 @@
<?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('tasks', function (Blueprint $table) {
$table->id();
$table->foreignId('tenant_id')->constrained()->onDelete('cascade');
$table->unsignedBigInteger('emotion_id')->nullable();
$table->unsignedBigInteger('event_type_id')->nullable();
$table->json('title');
$table->json('description')->nullable();
$table->json('example_text')->nullable();
$table->dateTime('due_date')->nullable();
$table->boolean('is_completed')->default(false);
$table->enum('priority', ['low', 'medium', 'high', 'urgent'])->default('medium');
$table->unsignedBigInteger('collection_id')->nullable();
$table->enum('difficulty', ['easy','medium','hard'])->default('easy');
$table->integer('sort_order')->default(0);
$table->boolean('is_active')->default(true);
$table->timestamps();
$table->foreign('collection_id')->references('id')->on('task_collections')->onDelete('set null');
$table->index(['tenant_id', 'is_completed', 'priority']);
});
}
public function down(): void
{
Schema::dropIfExists('tasks');
}
};

View File

@@ -1,40 +0,0 @@
<?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('photos', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('event_id');
$table->unsignedBigInteger('emotion_id');
$table->unsignedBigInteger('task_id')->nullable();
$table->string('guest_name');
$table->string('file_path');
$table->string('thumbnail_path');
$table->integer('likes_count')->default(0);
$table->boolean('is_featured')->default(false);
$table->json('metadata')->nullable();
$table->timestamps();
});
Schema::create('photo_likes', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('photo_id');
$table->string('guest_name');
$table->string('ip_address', 45)->nullable();
$table->timestamp('created_at')->useCurrent();
$table->unique(['photo_id','guest_name','ip_address']);
});
}
public function down(): void
{
Schema::dropIfExists('photo_likes');
Schema::dropIfExists('photos');
}
};

View File

@@ -1,29 +0,0 @@
<?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('legal_pages', function (Blueprint $table) {
$table->id();
$table->string('slug', 32);
$table->json('title');
$table->json('body_markdown');
$table->string('locale_fallback', 5)->default('de');
$table->integer('version')->default(1);
$table->timestamp('effective_from')->nullable();
$table->boolean('is_published')->default(false);
$table->timestamps();
$table->unique(['slug','version']);
});
}
public function down(): void
{
Schema::dropIfExists('legal_pages');
}
};

View File

@@ -1,22 +0,0 @@
<?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('users', function (Blueprint $table) {
$table->string('role')->default('super_admin')->after('password');
});
}
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('role');
});
}
};

View File

@@ -1,33 +0,0 @@
<?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('personal_access_tokens', function (Blueprint $table) {
$table->id();
$table->morphs('tokenable');
$table->text('name');
$table->string('token', 64)->unique();
$table->text('abilities')->nullable();
$table->timestamp('last_used_at')->nullable();
$table->timestamp('expires_at')->nullable()->index();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('personal_access_tokens');
}
};

View File

@@ -1,35 +0,0 @@
<?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('task_collections', function (Blueprint $table) {
$table->id();
$table->foreignId('tenant_id')->constrained()->onDelete('cascade');
$table->string('name');
$table->text('description')->nullable();
$table->boolean('is_default')->default(false);
$table->integer('position')->default(0);
$table->timestamps();
$table->index(['tenant_id', 'is_default', 'position']);
});
Schema::create('task_collection_task', function (Blueprint $table) {
$table->foreignId('task_collection_id')->constrained()->onDelete('cascade');
$table->foreignId('task_id')->constrained()->onDelete('cascade');
$table->primary(['task_collection_id','task_id']);
$table->integer('sort_order')->default(0);
});
}
public function down(): void
{
Schema::dropIfExists('task_collection_task');
Schema::dropIfExists('task_collections');
}
};

View File

@@ -1,28 +0,0 @@
<?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('task_imports', function (Blueprint $table) {
$table->id();
$table->string('disk')->default('local');
$table->string('path');
$table->string('source_filename');
$table->enum('status', ['pending','processing','completed','failed'])->default('pending');
$table->unsignedInteger('total_rows')->default(0);
$table->unsignedInteger('imported_rows')->default(0);
$table->json('errors')->nullable();
$table->unsignedBigInteger('created_by')->nullable();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('task_imports');
}
};

View File

@@ -1,28 +0,0 @@
<?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('users', function (Blueprint $table) {
$table->text('two_factor_secret')->nullable();
$table->text('two_factor_recovery_codes')->nullable();
$table->timestamp('two_factor_confirmed_at')->nullable();
});
}
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn([
'two_factor_secret',
'two_factor_recovery_codes',
'two_factor_confirmed_at',
]);
});
}
};

View File

@@ -1,42 +0,0 @@
<?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('tenants', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('slug')->unique();
$table->string('domain')->nullable()->unique();
$table->string('contact_name')->nullable();
$table->string('contact_email')->nullable();
$table->string('contact_phone')->nullable();
// Simple event-credit based monetization (MVP)
$table->integer('event_credits_balance')->default(1);
$table->timestamp('free_event_granted_at')->nullable();
// Limits & quotas
$table->integer('max_photos_per_event')->default(500);
$table->integer('max_storage_mb')->default(1024);
// Feature flags & misc
$table->json('features')->nullable();
$table->timestamp('last_activity_at')->nullable();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('tenants');
}
};

View File

@@ -1,38 +0,0 @@
<?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('users', function (Blueprint $table) {
if (! Schema::hasColumn('users', 'tenant_id')) {
$table->foreignId('tenant_id')->nullable()->after('id')
->constrained('tenants')->nullOnDelete();
}
if (! Schema::hasColumn('users', 'role')) {
$table->string('role', 32)->default('tenant_user')->after('password')->index();
}
});
}
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
if (Schema::hasColumn('users', 'tenant_id')) {
// Drop FK first if the driver supports it
try { $table->dropConstrainedForeignId('tenant_id'); } catch (\Throwable $e) {
try { $table->dropForeign(['tenant_id']); } catch (\Throwable $e2) {}
$table->dropColumn('tenant_id');
}
}
if (Schema::hasColumn('users', 'role')) {
$table->dropColumn('role');
}
});
}
};

View File

@@ -1,36 +0,0 @@
<?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
{
if (! Schema::hasTable('events')) return;
Schema::table('events', function (Blueprint $table) {
if (! Schema::hasColumn('events', 'tenant_id')) {
$table->foreignId('tenant_id')->nullable()->after('id')->constrained('tenants')->nullOnDelete();
}
if (Schema::hasColumn('events', 'slug')) {
// Optional: ensure index exists
try { $table->index('slug'); } catch (\Throwable $e) {}
}
});
}
public function down(): void
{
if (! Schema::hasTable('events')) return;
Schema::table('events', function (Blueprint $table) {
if (Schema::hasColumn('events', 'tenant_id')) {
try { $table->dropConstrainedForeignId('tenant_id'); } catch (\Throwable $e) {
try { $table->dropForeign(['tenant_id']); } catch (\Throwable $e2) {}
$table->dropColumn('tenant_id');
}
}
});
}
};

View File

@@ -1,37 +0,0 @@
<?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('users', function (Blueprint $table) {
if (! Schema::hasColumn('users', 'username')) {
$table->string('username', 32)->nullable()->unique()->after('email');
}
if (! Schema::hasColumn('users', 'preferred_locale')) {
$defaultLocale = config('app.locale', 'en');
$table->string('preferred_locale', 5)->default($defaultLocale)->after('role');
}
});
}
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
if (Schema::hasColumn('users', 'username')) {
try { $table->dropUnique(['username']); } catch (\Throwable $e) { /* ignore */ }
$table->dropColumn('username');
}
if (Schema::hasColumn('users', 'preferred_locale')) {
$table->dropColumn('preferred_locale');
}
});
}
};

View File

@@ -1,35 +0,0 @@
<?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('event_task_collection', function (Blueprint $table) {
$table->id();
$table->foreignId('event_id')->constrained('events')->onDelete('cascade');
$table->foreignId('task_collection_id')->constrained('task_collections')->onDelete('cascade');
$table->integer('sort_order')->default(0);
$table->timestamps();
// Composite unique index to prevent duplicate assignments
$table->unique(['event_id', 'task_collection_id']);
$table->index(['event_id', 'sort_order']);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('event_task_collection');
}
};

View File

@@ -1,27 +0,0 @@
<?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('event_task', function (Blueprint $table) {
$table->id();
$table->foreignId('event_id')->constrained()->onDelete('cascade');
$table->foreignId('task_id')->constrained()->onDelete('cascade');
$table->integer('sort_order')->default(0);
$table->timestamps();
$table->unique(['event_id', 'task_id']);
$table->index(['event_id', 'task_id']);
});
}
public function down(): void
{
Schema::dropIfExists('event_task');
}
};

View File

@@ -0,0 +1,127 @@
<?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
{
// OAuth Clients
if (!Schema::hasTable('oauth_clients')) {
Schema::create('oauth_clients', function (Blueprint $table) {
$table->string('id', 255)->primary();
$table->string('client_id', 255)->unique();
$table->string('client_secret', 255)->nullable();
$table->text('redirect_uris')->nullable();
$table->text('scopes')->default('tenant:read tenant:write');
$table->boolean('is_active')->default(true); // From add_is_active
$table->foreignId('tenant_id')->nullable()->after('client_secret')->constrained('tenants')->nullOnDelete(); // From add_tenant_id
$table->timestamp('created_at')->useCurrent();
$table->timestamp('updated_at')->useCurrent()->useCurrentOnUpdate();
$table->index('tenant_id');
});
} else {
if (!Schema::hasColumn('oauth_clients', 'is_active')) {
Schema::table('oauth_clients', function (Blueprint $table) {
$table->boolean('is_active')->default(true)->after('scopes');
});
}
if (!Schema::hasColumn('oauth_clients', 'tenant_id')) {
Schema::table('oauth_clients', function (Blueprint $table) {
$table->foreignId('tenant_id')->nullable()->after('client_secret')->constrained('tenants')->nullOnDelete();
$table->index('tenant_id');
});
}
}
// Refresh Tokens
if (!Schema::hasTable('refresh_tokens')) {
Schema::create('refresh_tokens', function (Blueprint $table) {
$table->string('id', 255)->primary();
$table->string('tenant_id', 255)->index();
$table->string('client_id', 255)->nullable()->index(); // From add_client_id
$table->string('token', 255)->unique()->index();
$table->string('access_token', 255)->nullable();
$table->timestamp('expires_at')->nullable();
$table->text('scope')->nullable();
$table->string('ip_address', 45)->nullable();
$table->text('user_agent')->nullable();
$table->timestamp('created_at')->useCurrent();
$table->timestamp('revoked_at')->nullable();
$table->index('expires_at');
});
} else {
if (!Schema::hasColumn('refresh_tokens', 'client_id')) {
Schema::table('refresh_tokens', function (Blueprint $table) {
$table->string('client_id', 255)->nullable()->after('tenant_id')->index();
});
}
}
// Tenant Tokens
if (!Schema::hasTable('tenant_tokens')) {
Schema::create('tenant_tokens', function (Blueprint $table) {
$table->string('id', 255)->primary();
$table->string('tenant_id', 255)->index();
$table->string('jti', 255)->unique()->index();
$table->string('token_type', 50)->index();
$table->timestamp('expires_at');
$table->timestamp('revoked_at')->nullable();
$table->timestamp('created_at')->useCurrent();
$table->index('expires_at');
});
}
// OAuth Codes
if (!Schema::hasTable('oauth_codes')) {
Schema::create('oauth_codes', function (Blueprint $table) {
$table->string('id', 255)->primary();
$table->string('client_id', 255);
$table->string('user_id', 255);
$table->string('code', 255)->unique()->index();
$table->string('code_challenge', 255);
$table->string('state', 255)->nullable();
$table->string('redirect_uri', 255)->nullable();
$table->text('scope')->nullable();
$table->timestamp('expires_at');
$table->timestamp('created_at')->useCurrent();
$table->index('expires_at');
$table->foreign('client_id')->references('client_id')->on('oauth_clients')->onDelete('cascade');
});
}
}
public function down(): void
{
if (app()->environment('local', 'testing')) {
if (Schema::hasTable('oauth_codes')) {
Schema::table('oauth_codes', function (Blueprint $table) {
$table->dropForeign(['client_id']);
});
Schema::dropIfExists('oauth_codes');
}
if (Schema::hasColumn('refresh_tokens', 'client_id')) {
Schema::table('refresh_tokens', function (Blueprint $table) {
$table->dropIndex(['client_id']);
$table->dropColumn('client_id');
});
}
Schema::dropIfExists('refresh_tokens');
Schema::dropIfExists('tenant_tokens');
if (Schema::hasColumn('oauth_clients', 'tenant_id')) {
Schema::table('oauth_clients', function (Blueprint $table) {
$table->dropForeign(['tenant_id']);
$table->dropColumn('tenant_id');
});
}
if (Schema::hasColumn('oauth_clients', 'is_active')) {
Schema::table('oauth_clients', function (Blueprint $table) {
$table->dropColumn('is_active');
});
}
Schema::dropIfExists('oauth_clients');
}
}
};

View File

@@ -1,33 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('oauth_clients', function (Blueprint $table) {
$table->string('id', 255)->primary();
$table->string('client_id', 255)->unique();
$table->string('client_secret', 255)->nullable();
$table->text('redirect_uris')->nullable();
$table->text('scopes')->default('tenant:read tenant:write');
$table->timestamp('created_at')->useCurrent();
$table->timestamp('updated_at')->useCurrent()->useCurrentOnUpdate();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('oauth_clients');
}
};

View File

@@ -1,37 +0,0 @@
<?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('refresh_tokens', function (Blueprint $table) {
$table->string('id', 255)->primary();
$table->string('tenant_id', 255)->index();
$table->string('token', 255)->unique()->index();
$table->string('access_token', 255)->nullable();
$table->timestamp('expires_at')->nullable();
$table->text('scope')->nullable();
$table->string('ip_address', 45)->nullable();
$table->text('user_agent')->nullable();
$table->timestamp('created_at')->useCurrent();
$table->timestamp('revoked_at')->nullable();
$table->index('expires_at');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('refresh_tokens');
}
};

View File

@@ -1,34 +0,0 @@
<?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('tenant_tokens', function (Blueprint $table) {
$table->string('id', 255)->primary();
$table->string('tenant_id', 255)->index();
$table->string('jti', 255)->unique()->index();
$table->string('token_type', 50)->index();
$table->timestamp('expires_at');
$table->timestamp('revoked_at')->nullable();
$table->timestamp('created_at')->useCurrent();
$table->index('expires_at');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('tenant_tokens');
}
};

View File

@@ -1,37 +0,0 @@
<?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('oauth_codes', function (Blueprint $table) {
$table->string('id', 255)->primary();
$table->string('client_id', 255);
$table->string('user_id', 255);
$table->string('code', 255)->unique()->index();
$table->string('code_challenge', 255);
$table->string('state', 255)->nullable();
$table->string('redirect_uri', 255)->nullable();
$table->text('scope')->nullable();
$table->timestamp('expires_at');
$table->timestamp('created_at')->useCurrent();
$table->index('expires_at');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('oauth_codes');
}
};

View File

@@ -1,40 +0,0 @@
<?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('purchase_history', function (Blueprint $table) {
$table->string('id', 255)->primary();
$table->string('tenant_id', 255);
$table->string('package_id', 255);
$table->integer('credits_added')->default(0);
$table->decimal('price', 10, 2)->default(0);
$table->string('currency', 3)->default('EUR');
$table->string('platform', 50);
$table->string('transaction_id', 255)->nullable();
$table->timestamp('purchased_at')->useCurrent();
$table->timestamp('created_at')->useCurrent();
$table->foreign('tenant_id')->references('id')->on('tenants');
$table->index('tenant_id');
$table->index('purchased_at');
$table->index('transaction_id');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('purchase_history');
}
};

View File

@@ -12,11 +12,11 @@ return new class extends Migration
public function up(): void
{
Schema::table('tenants', function (Blueprint $table) {
$table->string('subscription_tier')->default('free');
$table->timestamp('subscription_expires_at')->nullable();
$table->decimal('total_revenue', 10, 2)->default(0.00);
if (!Schema::hasColumn('tenants', 'event_credits_balance')) {
$table->integer('event_credits_balance')->default(1);
if (!Schema::hasColumn('tenants', 'subscription_status')) {
$table->enum('subscription_status', ['free', 'active', 'suspended', 'expired'])->default('free')->after('subscription_tier');
}
if (!Schema::hasColumn('tenants', 'subscription_expires_at')) {
$table->timestamp('subscription_expires_at')->nullable()->after('subscription_status');
}
});
}
@@ -27,12 +27,7 @@ return new class extends Migration
public function down(): void
{
Schema::table('tenants', function (Blueprint $table) {
$table->dropColumn([
'subscription_tier',
'subscription_expires_at',
'total_revenue',
'event_credits_balance'
]);
$table->dropColumn(['subscription_status', 'subscription_expires_at']);
});
}
};
};

View File

@@ -1,40 +0,0 @@
<?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
{
// Add tenant_id to tasks table
Schema::table('tasks', function (Blueprint $table) {
if (!Schema::hasColumn('tasks', 'tenant_id')) {
$table->foreignId('tenant_id')->constrained('tenants')->onDelete('cascade')->after('id');
$table->index('tenant_id');
}
});
// Add tenant_id to task_collections table
Schema::table('task_collections', function (Blueprint $table) {
if (!Schema::hasColumn('task_collections', 'tenant_id')) {
$table->foreignId('tenant_id')->constrained('tenants')->onDelete('cascade')->after('id');
$table->index('tenant_id');
}
});
}
public function down(): void
{
Schema::table('tasks', function (Blueprint $table) {
$table->dropForeign(['tenant_id']);
$table->dropColumn('tenant_id');
});
Schema::table('task_collections', function (Blueprint $table) {
$table->dropForeign(['tenant_id']);
$table->dropColumn('tenant_id');
});
}
};

View File

@@ -1,36 +0,0 @@
<?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('tenants', function (Blueprint $table) {
$table->boolean('is_active')->default(true)->after('last_activity_at');
$table->boolean('is_suspended')->default(false)->after('is_active');
$table->json('settings')->nullable()->after('features');
$table->timestamp('settings_updated_at')->nullable()->after('settings');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('tenants', function (Blueprint $table) {
$table->dropColumn([
'is_active',
'is_suspended',
'settings',
'settings_updated_at'
]);
});
}
};

View File

@@ -1,28 +0,0 @@
<?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('events', function (Blueprint $table) {
$table->string('status')->default('draft')->after('is_active');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('events', function (Blueprint $table) {
$table->dropColumn('status');
});
}
};

View File

@@ -1,31 +0,0 @@
<?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('photos', function (Blueprint $table) {
$table->unsignedBigInteger('tenant_id')->nullable()->after('event_id');
$table->foreign('tenant_id')->references('id')->on('tenants');
$table->index('tenant_id');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('photos', function (Blueprint $table) {
$table->dropForeign(['tenant_id']);
$table->dropColumn('tenant_id');
});
}
};

View File

@@ -1,35 +0,0 @@
<?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('photos', function (Blueprint $table) {
if (!Schema::hasColumn('photos', 'tenant_id')) {
$table->unsignedBigInteger('tenant_id')->nullable()->after('event_id');
$table->foreign('tenant_id')->references('id')->on('tenants')->onDelete('cascade');
$table->index('tenant_id');
}
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('photos', function (Blueprint $table) {
if (Schema::hasColumn('photos', 'tenant_id')) {
$table->dropForeign(['tenant_id']);
$table->dropColumn('tenant_id');
}
});
}
};

View File

@@ -1,24 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up()
{
Schema::table('tenants', function (Blueprint $table) {
$table->string('stripe_account_id')->nullable()->unique()->after('id');
$table->index('stripe_account_id');
});
}
public function down()
{
Schema::table('tenants', function (Blueprint $table) {
$table->dropIndex(['stripe_account_id']);
$table->dropColumn('stripe_account_id');
});
}
};

View File

@@ -1,27 +0,0 @@
<?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('event_credits_ledger', function (Blueprint $table) {
$table->id();
$table->foreignId('tenant_id')->constrained()->cascadeOnDelete();
$table->integer('delta');
$table->string('reason', 32); // purchase, event_create, manual_adjust, refund
$table->foreignId('related_purchase_id')->nullable()->constrained('event_purchases')->nullOnDelete();
$table->text('note')->nullable();
$table->timestamps();
$table->index(['tenant_id', 'created_at']);
});
}
public function down(): void
{
Schema::dropIfExists('event_credits_ledger');
}
};

View File

@@ -1,30 +0,0 @@
<?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('event_purchases', function (Blueprint $table) {
$table->id();
$table->foreignId('tenant_id')->constrained()->cascadeOnDelete();
$table->unsignedInteger('events_purchased')->default(1);
$table->decimal('amount', 10, 2);
$table->string('currency', 3)->default('EUR');
$table->string('provider', 32); // stripe, paypal, app_store, play_store
$table->string('external_receipt_id')->nullable();
$table->string('status', 16)->default('pending'); // pending, completed, failed
$table->timestamp('purchased_at')->nullable();
$table->timestamps();
$table->index(['tenant_id', 'purchased_at']);
});
}
public function down(): void
{
Schema::dropIfExists('event_purchases');
}
};

View File

@@ -1,70 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
public function up(): void
{
DB::table('tenants')
->select('id', 'name')
->orderBy('id')
->chunkById(100, function ($tenants): void {
foreach ($tenants as $tenant) {
$raw = $tenant->name;
if ($raw === null || $raw === '') {
continue;
}
$decoded = json_decode($raw, true);
$value = $raw;
if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
$preferred = $decoded['de'] ?? $decoded['en'] ?? null;
if ($preferred === null) {
foreach ($decoded as $entry) {
if (is_string($entry) && $entry !== '') {
$preferred = $entry;
break;
}
}
}
$value = $preferred ?? (string) $raw;
}
DB::table('tenants')->where('id', $tenant->id)->update([
'name' => (string) $value,
]);
}
});
}
public function down(): void
{
DB::table('tenants')
->select('id', 'name')
->orderBy('id')
->chunkById(100, function ($tenants): void {
foreach ($tenants as $tenant) {
$raw = $tenant->name;
if ($raw === null || $raw === '') {
continue;
}
$localized = json_encode([
'de' => $raw,
'en' => $raw,
], JSON_UNESCAPED_UNICODE);
DB::table('tenants')->where('id', $tenant->id)->update([
'name' => $localized,
]);
}
});
}
};

View File

@@ -1,79 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
if (! Schema::hasColumn('oauth_clients', 'is_active')) {
Schema::table('oauth_clients', function (Blueprint $table) {
$table->boolean('is_active')->default(true);
});
}
$clients = DB::table('oauth_clients')->get(['id', 'scopes', 'redirect_uris', 'is_active']);
foreach ($clients as $client) {
$scopes = $this->normaliseValue($client->scopes, ['tenant:read', 'tenant:write']);
$redirects = $this->normaliseValue($client->redirect_uris);
DB::table('oauth_clients')
->where('id', $client->id)
->update([
'scopes' => $scopes === null ? null : json_encode($scopes),
'redirect_uris' => $redirects === null ? null : json_encode($redirects),
'is_active' => $client->is_active ?? true,
]);
}
}
/**
* Reverse the migrations.
*/
public function down(): void
{
if (Schema::hasColumn('oauth_clients', 'is_active')) {
Schema::table('oauth_clients', function (Blueprint $table) {
$table->dropColumn('is_active');
});
}
}
private function normaliseValue(mixed $value, ?array $fallback = null): ?array
{
if ($value === null) {
return $fallback;
}
if (is_array($value)) {
return $this->cleanArray($value) ?: $fallback;
}
if (is_string($value)) {
$decoded = json_decode($value, true);
if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
return $this->cleanArray($decoded) ?: $fallback;
}
$parts = preg_split('/[\r\n,]+/', $value) ?: [];
return $this->cleanArray($parts) ?: $fallback;
}
return $fallback;
}
private function cleanArray(array $items): array
{
$items = array_map(fn ($item) => is_string($item) ? trim($item) : $item, $items);
$items = array_filter($items, fn ($item) => ! ($item === null || $item === ''));
return array_values($items);
}
};

View File

@@ -1,36 +0,0 @@
<?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('oauth_clients', function (Blueprint $table) {
if (!Schema::hasColumn('oauth_clients', 'tenant_id')) {
$table->foreignId('tenant_id')
->nullable()
->after('client_secret')
->constrained('tenants')
->nullOnDelete();
}
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('oauth_clients', function (Blueprint $table) {
if (Schema::hasColumn('oauth_clients', 'tenant_id')) {
$table->dropConstrainedForeignId('tenant_id');
}
});
}
};

View File

@@ -1,32 +0,0 @@
<?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('refresh_tokens', function (Blueprint $table) {
if (!Schema::hasColumn('refresh_tokens', 'client_id')) {
$table->string('client_id', 255)->nullable()->after('tenant_id')->index();
}
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('refresh_tokens', function (Blueprint $table) {
if (Schema::hasColumn('refresh_tokens', 'client_id')) {
$table->dropColumn('client_id');
}
});
}
};

View File

@@ -1,26 +0,0 @@
<?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('tasks', function (Blueprint $table) {
if (! Schema::hasColumn('tasks', 'deleted_at')) {
$table->softDeletes();
}
});
}
public function down(): void
{
Schema::table('tasks', function (Blueprint $table) {
if (Schema::hasColumn('tasks', 'deleted_at')) {
$table->dropSoftDeletes();
}
});
}
};

View File

@@ -1,27 +0,0 @@
<?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('tenants', function (Blueprint $table) {
$table->string('custom_domain')->nullable()->after('domain');
});
Schema::table('tenants', function (Blueprint $table) {
$table->unique('custom_domain');
});
}
public function down(): void
{
Schema::table('tenants', function (Blueprint $table) {
$table->dropUnique('tenants_custom_domain_unique');
$table->dropColumn('custom_domain');
});
}
};

View File

@@ -0,0 +1,336 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB;
return new class extends Migration
{
public function up(): void
{
// Packages table
if (!Schema::hasTable('packages')) {
Schema::create('packages', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->enum('type', ['endcustomer', 'reseller']);
$table->decimal('price', 8, 2);
$table->integer('max_photos')->nullable();
$table->integer('max_guests')->nullable();
$table->integer('gallery_days')->nullable();
$table->integer('max_tasks')->nullable();
$table->boolean('watermark_allowed')->default(true);
$table->boolean('branding_allowed')->default(false);
$table->integer('max_events_per_year')->nullable();
$table->timestamp('expires_after')->nullable();
$table->json('features')->nullable();
$table->timestamps();
$table->index(['type', 'price']);
});
// Seed standard packages if empty
if (DB::table('packages')->count() == 0) {
DB::table('packages')->insert([
[
'name' => 'Free/Test',
'type' => 'endcustomer',
'price' => 0.00,
'max_photos' => 30,
'max_guests' => 10,
'gallery_days' => 3,
'max_tasks' => 1,
'watermark_allowed' => true,
'branding_allowed' => false,
'max_events_per_year' => null,
'expires_after' => null,
'features' => json_encode([]),
'created_at' => now(),
'updated_at' => now(),
],
[
'name' => 'Starter',
'type' => 'endcustomer',
'price' => 19.00,
'max_photos' => 300,
'max_guests' => 50,
'gallery_days' => 14,
'max_tasks' => 5,
'watermark_allowed' => true,
'branding_allowed' => false,
'max_events_per_year' => null,
'expires_after' => null,
'features' => json_encode([]),
'created_at' => now(),
'updated_at' => now(),
],
[
'name' => 'Reseller S',
'type' => 'reseller',
'price' => 149.00,
'max_photos' => null,
'max_guests' => null,
'gallery_days' => null,
'max_tasks' => null,
'watermark_allowed' => true,
'branding_allowed' => true,
'max_events_per_year' => 5,
'expires_after' => now()->addYear(),
'features' => json_encode(['limited_branding']),
'created_at' => now(),
'updated_at' => now(),
],
// Add more as needed
]);
}
}
// Event Packages
if (!Schema::hasTable('event_packages')) {
Schema::create('event_packages', function (Blueprint $table) {
$table->id();
$table->foreignId('event_id')->constrained()->cascadeOnDelete();
$table->foreignId('package_id')->constrained()->cascadeOnDelete();
$table->decimal('purchased_price', 8, 2);
$table->timestamp('purchased_at');
$table->integer('used_photos')->default(0);
$table->timestamps();
$table->index('event_id');
});
}
// Tenant Packages
if (!Schema::hasTable('tenant_packages')) {
Schema::create('tenant_packages', function (Blueprint $table) {
$table->id();
$table->foreignId('tenant_id')->constrained()->cascadeOnDelete();
$table->foreignId('package_id')->constrained()->cascadeOnDelete();
$table->decimal('price', 8, 2);
$table->timestamp('purchased_at');
$table->timestamp('expires_at');
$table->integer('used_events')->default(0);
$table->boolean('active')->default(true);
$table->timestamps();
$table->index(['tenant_id', 'active']);
});
}
// Package Purchases
if (!Schema::hasTable('package_purchases')) {
Schema::create('package_purchases', function (Blueprint $table) {
$table->id();
$table->foreignId('tenant_id')->nullable()->constrained();
$table->foreignId('event_id')->nullable()->constrained();
$table->foreignId('package_id')->constrained();
$table->string('provider_id');
$table->decimal('price', 8, 2);
$table->timestamp('purchased_at');
$table->enum('type', ['endcustomer_event', 'reseller_subscription']);
$table->json('metadata')->nullable();
$table->string('ip_address')->nullable();
$table->string('user_agent')->nullable();
$table->boolean('refunded')->default(false);
$table->timestamps();
$table->index(['tenant_id', 'created_at']);
});
}
// Purchase History
if (!Schema::hasTable('purchase_history')) {
Schema::create('purchase_history', function (Blueprint $table) {
$table->string('id', 255)->primary();
$table->string('tenant_id', 255);
$table->string('package_id', 255);
$table->integer('credits_added')->default(0);
$table->decimal('price', 10, 2)->default(0);
$table->string('currency', 3)->default('EUR');
$table->string('platform', 50);
$table->string('transaction_id', 255)->nullable();
$table->timestamp('purchased_at')->useCurrent();
$table->timestamp('created_at')->useCurrent();
$table->foreign('tenant_id')->references('id')->on('tenants');
$table->index('tenant_id');
$table->index('purchased_at');
$table->index('transaction_id');
});
}
// Add subscription fields to tenants if missing
if (Schema::hasTable('tenants')) {
if (!Schema::hasColumn('tenants', 'subscription_tier')) {
Schema::table('tenants', function (Blueprint $table) {
$table->string('subscription_tier')->default('free')->after('event_credits_balance');
});
}
if (!Schema::hasColumn('tenants', 'subscription_status')) {
Schema::table('tenants', function (Blueprint $table) {
$table->enum('subscription_status', ['free', 'active', 'suspended', 'expired'])->default('free')->after('subscription_tier');
});
}
if (!Schema::hasColumn('tenants', 'subscription_expires_at')) {
Schema::table('tenants', function (Blueprint $table) {
$table->timestamp('subscription_expires_at')->nullable()->after('subscription_status');
});
}
if (!Schema::hasColumn('tenants', 'total_revenue')) {
Schema::table('tenants', function (Blueprint $table) {
$table->decimal('total_revenue', 10, 2)->default(0.00)->after('subscription_expires_at');
});
}
}
// Idempotent migration from credits to packages (only if old tables exist and new don't have data)
if (Schema::hasTable('event_credits_ledger') && DB::table('tenant_packages')->count() == 0) {
// Migrate tenant credits to tenant_packages (Free package)
$freePackageId = DB::table('packages')->where('name', 'Free/Test')->value('id');
if ($freePackageId) {
DB::table('tenants')->where('event_credits_balance', '>', 0)->chunk(100, function ($tenants) use ($freePackageId) {
foreach ($tenants as $tenant) {
DB::table('tenant_packages')->insertOrIgnore([
'tenant_id' => $tenant->id,
'package_id' => $freePackageId,
'price' => 0.00,
'purchased_at' => $tenant->free_event_granted_at ?? now(),
'expires_at' => now()->addDays(30),
'used_events' => min($tenant->event_credits_balance, 1),
'active' => true,
'created_at' => now(),
'updated_at' => now(),
]);
DB::table('package_purchases')->insertOrIgnore([
'tenant_id' => $tenant->id,
'event_id' => null,
'package_id' => $freePackageId,
'provider_id' => 'migration_free',
'price' => 0.00,
'type' => 'reseller_subscription',
'metadata' => json_encode(['migrated_from_credits' => $tenant->event_credits_balance]),
'ip_address' => null,
'user_agent' => null,
'refunded' => false,
'created_at' => now(),
'updated_at' => now(),
]);
}
});
// Migrate event purchases if old data exists
if (Schema::hasTable('event_purchases')) {
DB::table('event_purchases')->join('events', 'event_purchases.event_id', '=', 'events.id')->chunk(100, function ($purchases) use ($freePackageId) {
foreach ($purchases as $purchase) {
DB::table('event_packages')->insertOrIgnore([
'event_id' => $purchase->event_id,
'package_id' => $freePackageId,
'purchased_price' => $purchase->amount ?? 0.00,
'purchased_at' => $purchase->purchased_at ?? now(),
'used_photos' => 0,
'created_at' => now(),
'updated_at' => now(),
]);
DB::table('package_purchases')->insertOrIgnore([
'tenant_id' => $purchase->tenant_id,
'event_id' => $purchase->event_id,
'package_id' => $freePackageId,
'provider_id' => $purchase->provider ?? 'migration',
'price' => $purchase->amount ?? 0.00,
'type' => 'endcustomer_event',
'metadata' => json_encode(['migrated_from_event_purchases' => true]),
'ip_address' => null,
'user_agent' => null,
'refunded' => false,
'created_at' => now(),
'updated_at' => now(),
]);
}
});
}
}
}
// Conditional drop of old credits tables and fields (only if migration happened or old structures exist)
if (Schema::hasTable('event_credits_ledger')) {
Schema::dropIfExists('event_credits_ledger');
}
if (Schema::hasTable('event_purchases')) {
Schema::dropIfExists('event_purchases');
}
if (Schema::hasTable('purchase_history') && DB::table('package_purchases')->count() > 0) { // Only drop if new data exists
Schema::dropIfExists('purchase_history');
}
// Drop old fields from tenants if new system is in place
if (Schema::hasTable('tenants')) {
$oldFields = ['event_credits_balance', 'free_event_granted_at'];
foreach ($oldFields as $field) {
if (Schema::hasColumn('tenants', $field) && DB::table('tenant_packages')->count() > 0) {
Schema::table('tenants', function (Blueprint $table) use ($field) {
$table->dropColumn($field);
});
}
}
}
}
public function down(): void
{
if (app()->environment('local', 'testing')) {
// Reverse drops and adds
if (!Schema::hasTable('purchase_history')) {
Schema::create('purchase_history', function (Blueprint $table) {
$table->id();
$table->foreignId('tenant_id')->constrained()->cascadeOnDelete();
$table->string('package_id', 255);
$table->integer('credits_added')->default(0);
$table->decimal('price', 10, 2)->default(0);
$table->string('provider_id');
$table->timestamps();
});
}
if (!Schema::hasTable('event_purchases')) {
Schema::create('event_purchases', function (Blueprint $table) {
$table->id();
$table->foreignId('tenant_id')->constrained()->cascadeOnDelete();
$table->unsignedInteger('events_purchased')->default(1);
$table->decimal('amount', 10, 2);
$table->string('currency', 3)->default('EUR');
$table->string('provider', 32);
$table->string('external_receipt_id')->nullable();
$table->string('status', 16)->default('pending');
$table->timestamp('purchased_at')->nullable();
$table->timestamps();
$table->index(['tenant_id', 'purchased_at']);
});
}
if (!Schema::hasTable('event_credits_ledger')) {
Schema::create('event_credits_ledger', function (Blueprint $table) {
$table->id();
$table->foreignId('tenant_id')->constrained()->cascadeOnDelete();
$table->integer('delta');
$table->string('reason', 32);
$table->foreignId('related_purchase_id')->nullable()->constrained('event_purchases')->nullOnDelete();
$table->text('note')->nullable();
$table->timestamps();
$table->index(['tenant_id', 'created_at']);
});
}
// Re-add old fields to tenants
Schema::table('tenants', function (Blueprint $table) {
if (!Schema::hasColumn('tenants', 'event_credits_balance')) {
$table->integer('event_credits_balance')->default(1);
}
if (!Schema::hasColumn('tenants', 'free_event_granted_at')) {
$table->timestamp('free_event_granted_at')->nullable();
}
});
// Drop new tables
Schema::dropIfExists('package_purchases');
Schema::dropIfExists('tenant_packages');
Schema::dropIfExists('event_packages');
Schema::dropIfExists('packages');
}
}
};

View File

@@ -0,0 +1,127 @@
<?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
{
// Blog Categories
if (!Schema::hasTable('blog_categories')) {
Schema::create('blog_categories', function (Blueprint $table) {
$table->id();
$table->string('name')->nullable();
$table->string('slug')->unique();
$table->longText('description')->nullable();
$table->json('translations')->nullable(); // From add_translations
$table->boolean('is_visible')->default(false);
$table->date('deleted_at')->nullable();
$table->timestamps();
});
} else {
if (!Schema::hasColumn('blog_categories', 'name')) {
Schema::table('blog_categories', function (Blueprint $table) {
$table->string('name')->nullable()->after('id');
$table->longText('description')->nullable()->after('name');
});
}
if (!Schema::hasColumn('blog_categories', 'translations')) {
Schema::table('blog_categories', function (Blueprint $table) {
$table->json('translations')->nullable()->after('description');
});
}
}
// Blog Authors
if (!Schema::hasTable('blog_authors')) {
Schema::create('blog_authors', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->string('photo')->nullable();
$table->longText('bio')->nullable();
$table->string('github_handle')->nullable();
$table->string('twitter_handle')->nullable();
$table->timestamps();
});
}
// Blog Posts
if (!Schema::hasTable('blog_posts')) {
Schema::create('blog_posts', function (Blueprint $table) {
$table->id();
$table->foreignId('blog_author_id')->nullable()->constrained()->cascadeOnDelete();
$table->foreignId('blog_category_id')->nullable()->constrained()->nullOnDelete();
$table->string('title');
$table->string('slug')->unique();
$table->text('excerpt')->nullable();
$table->string('banner')->nullable();
$table->longText('content');
$table->json('translations')->nullable(); // From add_translations
$table->date('published_at')->nullable();
$table->date('deleted_at')->nullable();
$table->timestamps();
});
} else {
if (!Schema::hasColumn('blog_posts', 'translations')) {
Schema::table('blog_posts', function (Blueprint $table) {
$table->json('translations')->nullable()->after('content');
});
}
}
// Tags
if (!Schema::hasTable('tags')) {
Schema::create('tags', function (Blueprint $table) {
$table->id();
$table->json('name');
$table->json('slug');
$table->string('type')->nullable();
$table->integer('order_column')->nullable();
$table->timestamps();
});
}
// Taggables (polymorphic)
if (!Schema::hasTable('taggables')) {
Schema::create('taggables', function (Blueprint $table) {
$table->foreignId('tag_id')->constrained()->cascadeOnDelete();
$table->morphs('taggable');
$table->unique(['tag_id', 'taggable_id', 'taggable_type']);
});
}
}
public function down(): void
{
if (app()->environment('local', 'testing')) {
if (Schema::hasTable('taggables')) {
Schema::table('taggables', function (Blueprint $table) {
$table->dropForeign(['tag_id']);
});
Schema::dropIfExists('taggables');
}
Schema::dropIfExists('tags');
if (Schema::hasColumn('blog_posts', 'translations')) {
Schema::table('blog_posts', function (Blueprint $table) {
$table->dropColumn('translations');
});
}
Schema::dropIfExists('blog_posts');
Schema::dropIfExists('blog_authors');
if (Schema::hasColumn('blog_categories', 'translations')) {
Schema::table('blog_categories', function (Blueprint $table) {
$table->dropColumn('translations');
});
}
if (Schema::hasColumn('blog_categories', 'name')) {
Schema::table('blog_categories', function (Blueprint $table) {
$table->dropColumn(['name', 'description']);
});
}
Schema::dropIfExists('blog_categories');
}
}
};

View File

@@ -1,60 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class () extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('blog_categories', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('slug')->unique();
$table->longText('description')->nullable();
$table->boolean('is_visible')->default(false);
$table->timestamps();
});
Schema::create('blog_authors', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->string('photo')->nullable();
$table->longText('bio')->nullable();
$table->string('github_handle')->nullable();
$table->string('twitter_handle')->nullable();
$table->timestamps();
});
Schema::create('blog_posts', function (Blueprint $table) {
$table->id();
$table->foreignId('blog_author_id')->nullable()->constrained()->cascadeOnDelete();
$table->foreignId('blog_category_id')->nullable()->constrained()->nullOnDelete();
$table->string('title');
$table->string('slug')->unique();
$table->text('excerpt')->nullable();
$table->string('banner')->nullable();
$table->longText('content');
$table->date('published_at')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('blog_posts');
Schema::dropIfExists('blog_categories');
Schema::dropIfExists('blog_authors');
}
};

View File

@@ -1,36 +0,0 @@
<?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('blog_posts', function (Blueprint $table) {
$table->json('translations')->nullable();
});
Schema::table('blog_categories', function (Blueprint $table) {
$table->json('translations')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('blog_posts', function (Blueprint $table) {
$table->dropColumn('translations');
});
Schema::table('blog_categories', function (Blueprint $table) {
$table->dropColumn('translations');
});
}
};

View File

@@ -1,36 +0,0 @@
<?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('tags', function (Blueprint $table) {
$table->id();
$table->json('name');
$table->json('slug');
$table->string('type')->nullable();
$table->integer('order_column')->nullable();
$table->timestamps();
});
Schema::create('taggables', function (Blueprint $table) {
$table->foreignId('tag_id')->constrained()->cascadeOnDelete();
$table->morphs('taggable');
$table->unique(['tag_id', 'taggable_id', 'taggable_type']);
});
}
public function down(): void
{
Schema::dropIfExists('taggables');
Schema::dropIfExists('tags');
}
};

View File

@@ -1,40 +0,0 @@
<?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('packages', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->enum('type', ['endcustomer', 'reseller']);
$table->decimal('price', 8, 2);
$table->integer('max_photos')->nullable();
$table->integer('max_guests')->nullable();
$table->integer('gallery_days')->nullable();
$table->integer('max_tasks')->nullable();
$table->boolean('watermark_allowed')->default(true);
$table->boolean('branding_allowed')->default(false);
$table->integer('max_events_per_year')->nullable();
$table->timestamp('expires_after')->nullable();
$table->json('features')->nullable();
$table->timestamps();
$table->index(['type', 'price']);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('packages');
}
};

View File

@@ -1,33 +0,0 @@
<?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('event_packages', function (Blueprint $table) {
$table->id();
$table->foreignId('event_id')->constrained()->cascadeOnDelete();
$table->foreignId('package_id')->constrained()->cascadeOnDelete();
$table->decimal('purchased_price', 8, 2);
$table->timestamp('purchased_at');
$table->integer('used_photos')->default(0);
$table->timestamps();
$table->index('event_id');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('event_packages');
}
};

View File

@@ -1,35 +0,0 @@
<?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('tenant_packages', function (Blueprint $table) {
$table->id();
$table->foreignId('tenant_id')->constrained()->cascadeOnDelete();
$table->foreignId('package_id')->constrained()->cascadeOnDelete();
$table->decimal('price', 8, 2);
$table->timestamp('purchased_at');
$table->timestamp('expires_at');
$table->integer('used_events')->default(0);
$table->boolean('active')->default(true);
$table->timestamps();
$table->index(['tenant_id', 'active']);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('tenant_packages');
}
};

View File

@@ -1,38 +0,0 @@
<?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('package_purchases', function (Blueprint $table) {
$table->id();
$table->foreignId('tenant_id')->nullable()->constrained();
$table->foreignId('event_id')->nullable()->constrained();
$table->foreignId('package_id')->constrained();
$table->string('provider_id');
$table->decimal('price', 8, 2);
$table->enum('type', ['endcustomer_event', 'reseller_subscription']);
$table->json('metadata')->nullable();
$table->string('ip_address')->nullable();
$table->string('user_agent')->nullable();
$table->boolean('refunded')->default(false);
$table->timestamps();
$table->index(['tenant_id', 'purchased_at']);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('package_purchases');
}
};

View File

@@ -1,152 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB;
use App\Models\Tenant;
use App\Models\Event;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
// Ensure packages table has data (seed if empty)
if (DB::table('packages')->count() == 0) {
// Insert standard packages if not seeded
DB::table('packages')->insert([
[
'name' => 'Free/Test',
'type' => 'endcustomer',
'price' => 0.00,
'max_photos' => 30,
'max_guests' => 10,
'gallery_days' => 3,
'max_tasks' => 1,
'watermark_allowed' => true,
'branding_allowed' => false,
'max_events_per_year' => null,
'expires_after' => null,
'features' => json_encode([]),
'created_at' => now(),
'updated_at' => now(),
],
[
'name' => 'Starter',
'type' => 'endcustomer',
'price' => 19.00,
'max_photos' => 300,
'max_guests' => 50,
'gallery_days' => 14,
'max_tasks' => 5,
'watermark_allowed' => true,
'branding_allowed' => false,
'max_events_per_year' => null,
'expires_after' => null,
'features' => json_encode([]),
'created_at' => now(),
'updated_at' => now(),
],
// Add more standard packages as per plan
[
'name' => 'Reseller S',
'type' => 'reseller',
'price' => 149.00,
'max_photos' => null,
'max_guests' => null,
'gallery_days' => null,
'max_tasks' => null,
'watermark_allowed' => true,
'branding_allowed' => true,
'max_events_per_year' => 5,
'expires_after' => now()->addYear(),
'features' => json_encode(['limited_branding']),
'created_at' => now(),
'updated_at' => now(),
],
// ... other reseller packages
]);
}
// Migrate tenant credits to tenant_packages (Free package)
DB::table('tenants')->where('event_credits_balance', '>', 0)->orderBy('id')->chunk(100, function ($tenants) {
foreach ($tenants as $tenant) {
$freePackageId = DB::table('packages')->where('name', 'Free/Test')->first()->id;
DB::table('tenant_packages')->insert([
'tenant_id' => $tenant->id,
'package_id' => $freePackageId,
'price' => 0.00,
'purchased_at' => $tenant->free_event_granted_at ?? now(),
'expires_at' => now()->addDays(30), // or based on credits
'used_events' => min($tenant->event_credits_balance, 1), // e.g. 1 free event
'active' => true,
'created_at' => now(),
'updated_at' => now(),
]);
// Create purchase ledger entry
DB::table('package_purchases')->insert([
'tenant_id' => $tenant->id,
'event_id' => null,
'package_id' => $freePackageId,
'provider_id' => 'migration_free',
'price' => 0.00,
'type' => 'reseller_subscription',
'metadata' => json_encode(['migrated_from_credits' => $tenant->event_credits_balance]),
'ip_address' => null,
'user_agent' => null,
'refunded' => false,
'created_at' => now(),
'updated_at' => now(),
]);
}
});
// Migrate event purchases to event_packages (if any existing events)
DB::table('events')->orderBy('id')->chunk(100, function ($events) {
foreach ($events as $event) {
if ($event->tenant->event_credits_balance > 0) { // or check if event was created with credits
$freePackageId = DB::table('packages')->where('name', 'Free/Test')->first()->id;
DB::table('event_packages')->insert([
'event_id' => $event->id,
'package_id' => $freePackageId,
'purchased_price' => 0.00,
'purchased_at' => $event->created_at,
'used_photos' => 0,
'created_at' => now(),
'updated_at' => now(),
]);
// Ledger entry
DB::table('package_purchases')->insert([
'tenant_id' => $event->tenant_id,
'event_id' => $event->id,
'package_id' => $freePackageId,
'provider_id' => 'migration_free',
'price' => 0.00,
'type' => 'endcustomer_event',
'metadata' => json_encode(['migrated_from_credits' => true]),
'ip_address' => null,
'user_agent' => null,
'refunded' => false,
'created_at' => now(),
'updated_at' => now(),
]);
}
}
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('packages', function (Blueprint $table) {
//
});
}
};

View File

@@ -1,92 +0,0 @@
<?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::dropIfExists('event_credits_ledger');
Schema::dropIfExists('purchase_history');
Schema::dropIfExists('event_purchases');
if (Schema::hasTable('package_purchases')) {
Schema::table('package_purchases', function (Blueprint $table) {
$table->dropIndex(['tenant_id', 'purchased_at']);
});
}
if (Schema::hasColumn('tenants', 'event_credits_balance')) {
Schema::table('tenants', function (Blueprint $table) {
$table->dropColumn('event_credits_balance');
});
}
if (Schema::hasColumn('tenants', 'subscription_tier')) {
Schema::table('tenants', function (Blueprint $table) {
$table->dropColumn('subscription_tier');
});
}
if (Schema::hasColumn('tenants', 'subscription_expires_at')) {
Schema::table('tenants', function (Blueprint $table) {
$table->dropColumn('subscription_expires_at');
});
}
if (Schema::hasColumn('tenants', 'free_event_granted_at')) {
Schema::table('tenants', function (Blueprint $table) {
$table->dropColumn('free_event_granted_at');
});
}
if (Schema::hasColumn('tenants', 'total_revenue')) {
Schema::table('tenants', function (Blueprint $table) {
$table->dropColumn('total_revenue');
});
}
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('tenants', function (Blueprint $table) {
$table->integer('event_credits_balance')->default(1);
$table->string('subscription_tier')->nullable();
$table->timestamp('subscription_expires_at')->nullable();
$table->timestamp('free_event_granted_at')->nullable();
$table->decimal('total_revenue', 10, 2)->default(0.00);
});
Schema::create('event_purchases', function (Blueprint $table) {
$table->id();
$table->foreignId('tenant_id')->constrained()->cascadeOnDelete();
$table->foreignId('event_id')->constrained()->cascadeOnDelete();
$table->integer('credits_added')->default(0);
$table->decimal('price', 10, 2)->default(0);
$table->string('provider_id');
$table->timestamps();
});
Schema::create('purchase_history', function (Blueprint $table) {
$table->id();
$table->foreignId('tenant_id')->constrained()->cascadeOnDelete();
$table->string('package_id', 255);
$table->integer('credits_added')->default(0);
$table->decimal('price', 10, 2)->default(0);
$table->string('provider_id');
$table->timestamps();
});
Schema::create('event_credits_ledger', function (Blueprint $table) {
$table->id();
$table->foreignId('tenant_id')->constrained()->cascadeOnDelete();
$table->integer('credits_change');
$table->string('reason');
$table->timestamps();
});
}
};

View File

@@ -12,10 +12,18 @@ return new class extends Migration
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->string('first_name')->nullable()->after('name');
$table->string('last_name')->nullable()->after('first_name');
$table->text('address')->nullable()->after('last_name');
$table->string('phone')->nullable()->after('address');
if (!Schema::hasColumn('tenants', 'first_name')) {
$table->string('first_name')->default('')->after('name');
}
if (!Schema::hasColumn('tenants', 'last_name')) {
$table->string('last_name')->default('')->after('first_name');
}
if (!Schema::hasColumn('tenants', 'address')) {
$table->string('address')->nullable()->after('last_name');
}
if (!Schema::hasColumn('tenants', 'phone')) {
$table->string('phone')->nullable()->after('address');
}
});
}

View File

@@ -1,29 +0,0 @@
<?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('tenants', function (Blueprint $table) {
$table->foreignId('user_id')->nullable()->constrained('users')->onDelete('cascade')->after('id');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('tenants', function (Blueprint $table) {
$table->dropForeign(['user_id']);
$table->dropColumn('user_id');
});
}
};

View File

@@ -1,23 +0,0 @@
<?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('blog_categories', function (Blueprint $table) {
$table->string('name')->nullable()->after('id');
$table->text('description')->nullable()->after('name');
});
}
public function down(): void
{
Schema::table('blog_categories', function (Blueprint $table) {
$table->dropColumn(['name', 'description']);
});
}
};

View File

@@ -1,29 +0,0 @@
<?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('blog_categories', function (Blueprint $table) {
$table->dropColumn(['name', 'description']);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('blog_categories', function (Blueprint $table) {
$table->string('name')->after('id');
$table->text('description')->nullable()->after('name');
});
}
};

View File

@@ -1,27 +0,0 @@
<?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('blog_categories', function (Blueprint $table) {
if (!Schema::hasColumn('blog_categories', 'name')) {
$table->json('name')->nullable()->after('id');
}
if (!Schema::hasColumn('blog_categories', 'description')) {
$table->json('description')->nullable()->after('name');
}
});
}
public function down(): void
{
Schema::table('blog_categories', function (Blueprint $table) {
$table->dropColumn(['name', 'description']);
});
}
};

View File

@@ -1,32 +0,0 @@
<?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('blog_categories', function (Blueprint $table) {
$table->json('name')->nullable()->change();
$table->json('description')->nullable()->change();
$table->dropColumn('translations');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('blog_categories', function (Blueprint $table) {
$table->string('name')->nullable()->change();
$table->text('description')->nullable()->change();
$table->json('translations')->nullable();
});
}
};

View File

@@ -11,8 +11,8 @@ return new class extends Migration
*/
public function up(): void
{
Schema::table('tenants', function (Blueprint $table) {
$table->string('email')->nullable()->after('slug');
Schema::table('package_purchases', function (Blueprint $table) {
$table->string('provider_id')->nullable()->change();
});
}
@@ -21,8 +21,8 @@ return new class extends Migration
*/
public function down(): void
{
Schema::table('tenants', function (Blueprint $table) {
$table->dropColumn('email');
Schema::table('package_purchases', function (Blueprint $table) {
$table->string('provider_id')->nullable(false)->change();
});
}
};

View File

@@ -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.
*/
public function up(): void
{
Schema::table('tenants', function (Blueprint $table) {
if (!Schema::hasColumn('tenants', 'is_suspended')) {
$table->boolean('is_suspended')->default(false)->after('is_active');
}
if (!Schema::hasColumn('tenants', 'settings')) {
$table->json('settings')->nullable()->after('subscription_expires_at');
}
if (!Schema::hasColumn('tenants', 'settings_updated_at')) {
$table->timestamp('settings_updated_at')->nullable()->after('settings');
}
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('tenants', function (Blueprint $table) {
$table->dropColumn(['is_suspended', 'settings', 'settings_updated_at']);
});
}
};

View File

@@ -0,0 +1,52 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('packages', function (Blueprint $table) {
if (!Schema::hasColumn('packages', 'slug')) {
$table->string('slug')->default('')->after('name');
}
if (!Schema::hasColumn('packages', 'description')) {
$table->text('description')->nullable()->after('slug');
}
});
// Update existing packages with unique slugs
if (Schema::hasTable('packages')) {
$packages = DB::table('packages')->get();
foreach ($packages as $package) {
$slug = Str::slug($package->name . '-' . $package->id);
DB::table('packages')->where('id', $package->id)->update(['slug' => $slug]);
}
}
// Add unique index after updating
Schema::table('packages', function (Blueprint $table) {
$table->unique('slug');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('packages', function (Blueprint $table) {
$table->dropUnique(['slug']);
});
Schema::table('packages', function (Blueprint $table) {
$table->dropColumn(['slug', 'description']);
});
}
};

View File

@@ -19,9 +19,9 @@ class DemoEventSeeder extends Seeder
'description' => ['de'=>'Demo-Event','en'=>'Demo event'],
'date' => now()->addMonths(3)->toDateString(),
'event_type_id' => $type->id,
'status' => 'published',
'status' => 'active',
'is_active' => true,
'settings' => [],
'settings' => json_encode([]),
'default_locale' => 'de',
]);
}

View File

@@ -5,6 +5,7 @@ namespace Database\Seeders;
use Illuminate\Database\Seeder;
use App\Models\Package;
use App\Enums\PackageType;
use Illuminate\Support\Str;
class PackageSeeder extends Seeder
{
@@ -16,6 +17,7 @@ class PackageSeeder extends Seeder
// Endcustomer Packages
Package::create([
'name' => 'Free / Test',
'slug' => Str::slug('Free / Test'),
'type' => PackageType::ENDCUSTOMER,
'price' => 0.00,
'max_photos' => 30,
@@ -33,6 +35,7 @@ class PackageSeeder extends Seeder
Package::create([
'name' => 'Starter',
'slug' => Str::slug('Starter'),
'type' => PackageType::ENDCUSTOMER,
'price' => 29.00,
'max_photos' => 200,
@@ -51,6 +54,7 @@ class PackageSeeder extends Seeder
Package::create([
'name' => 'Pro',
'slug' => Str::slug('Pro'),
'type' => PackageType::ENDCUSTOMER,
'price' => 79.00,
'max_photos' => 1000,
@@ -72,6 +76,7 @@ class PackageSeeder extends Seeder
// Reseller Packages
Package::create([
'name' => 'S (Small Reseller)',
'slug' => Str::slug('S (Small Reseller)'),
'type' => PackageType::RESELLER,
'price' => 199.00,
'max_photos' => 500, // per event limit
@@ -91,6 +96,7 @@ class PackageSeeder extends Seeder
Package::create([
'name' => 'M (Medium Reseller)',
'slug' => Str::slug('M (Medium Reseller)'),
'type' => PackageType::RESELLER,
'price' => 399.00,
'max_photos' => 1000, // per event limit

View File

@@ -16,7 +16,9 @@ class TasksSeeder extends Seeder
'name' => 'Demo Tenant',
'domain' => null,
'is_active' => true,
'settings' => [],
'is_suspended' => false,
'settings' => json_encode([]),
'settings_updated_at' => null,
]
);