Implement multi-tenancy support with OAuth2 authentication for tenant admins, Stripe integration for event purchases and credits ledger, new Filament resources for event purchases, updated API routes and middleware for tenant isolation and token guarding, added factories/seeders/migrations for new models (Tenant, EventPurchase, OAuth entities, etc.), enhanced tests, and documentation updates. Removed outdated DemoAchievementsSeeder.
This commit is contained in:
@@ -9,15 +9,23 @@ return new class extends Migration {
|
||||
{
|
||||
Schema::create('events', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->json('name');
|
||||
$table->json('description')->nullable();
|
||||
$table->date('date');
|
||||
$table->foreignId('tenant_id')->constrained()->onDelete('cascade');
|
||||
$table->string('name');
|
||||
$table->text('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']);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -9,15 +9,23 @@ return new class extends Migration {
|
||||
{
|
||||
Schema::create('tasks', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('emotion_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');
|
||||
$table->json('example_text')->nullable();
|
||||
$table->string('title');
|
||||
$table->text('description')->nullable();
|
||||
$table->text('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']);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -9,15 +9,21 @@ return new class extends Migration {
|
||||
{
|
||||
Schema::create('task_collections', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->json('name');
|
||||
$table->json('description')->nullable();
|
||||
$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->unsignedBigInteger('task_collection_id');
|
||||
$table->unsignedBigInteger('task_id');
|
||||
$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);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
<?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');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
<?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');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('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');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('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');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('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');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::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');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
<?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->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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('tenants', function (Blueprint $table) {
|
||||
$table->dropColumn([
|
||||
'subscription_tier',
|
||||
'subscription_expires_at',
|
||||
'total_revenue',
|
||||
'event_credits_balance'
|
||||
]);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
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');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -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) {
|
||||
$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'
|
||||
]);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,24 @@
|
||||
<?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');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
<?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');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('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');
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user