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:
70
app/Http/Requests/Tenant/EventStoreRequest.php
Normal file
70
app/Http/Requests/Tenant/EventStoreRequest.php
Normal file
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Tenant;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class EventStoreRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true; // Authorization handled by middleware
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$tenantId = request()->attributes->get('tenant_id');
|
||||
|
||||
return [
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'description' => ['nullable', 'string'],
|
||||
'event_date' => ['required', 'date', 'after_or_equal:today'],
|
||||
'location' => ['nullable', 'string', 'max:255'],
|
||||
'event_type_id' => ['required', 'exists:event_types,id'],
|
||||
'max_participants' => ['nullable', 'integer', 'min:1', 'max:10000'],
|
||||
'public_url' => ['nullable', 'url', 'max:500'],
|
||||
'custom_domain' => ['nullable', 'string', 'max:255'],
|
||||
'theme_color' => ['nullable', 'string', 'regex:/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/'],
|
||||
'logo_image' => ['nullable', 'image', 'max:2048'], // 2MB
|
||||
'cover_image' => ['nullable', 'image', 'max:5120'], // 5MB
|
||||
'password_protected' => ['nullable', 'boolean'],
|
||||
'password' => ['required_if:password_protected,true', 'string', 'min:6', 'confirmed'],
|
||||
'status' => ['nullable', Rule::in(['draft', 'published', 'archived'])],
|
||||
'features' => ['nullable', 'array'],
|
||||
'features.*' => ['string'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom validation messages.
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'event_date.after_or_equal' => 'Das Event-Datum darf nicht in der Vergangenheit liegen.',
|
||||
'password.confirmed' => 'Die Passwortbestätigung stimmt nicht überein.',
|
||||
'logo_image.image' => 'Das Logo muss ein Bild sein.',
|
||||
'cover_image.image' => 'Das Cover-Bild muss ein Bild sein.',
|
||||
'theme_color.regex' => 'Die Farbe muss im Hex-Format angegeben werden (z.B. #FF0000).',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
*/
|
||||
protected function prepareForValidation()
|
||||
{
|
||||
$this->merge([
|
||||
'password_protected' => $this->boolean('password_protected'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
62
app/Http/Requests/Tenant/PhotoStoreRequest.php
Normal file
62
app/Http/Requests/Tenant/PhotoStoreRequest.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Tenant;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class PhotoStoreRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true; // Authorization handled by middleware
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'photo' => [
|
||||
'required',
|
||||
'image',
|
||||
'mimes:jpeg,png,webp',
|
||||
'max:10240', // 10MB
|
||||
],
|
||||
'caption' => ['nullable', 'string', 'max:500'],
|
||||
'alt_text' => ['nullable', 'string', 'max:255'],
|
||||
'tags' => ['nullable', 'array', 'max:10'],
|
||||
'tags.*' => ['string', 'max:50'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom validation messages.
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'photo.required' => 'Ein Foto muss hochgeladen werden.',
|
||||
'photo.image' => 'Die Datei muss ein Bild sein.',
|
||||
'photo.mimes' => 'Nur JPEG, PNG und WebP Formate sind erlaubt.',
|
||||
'photo.max' => 'Das Foto darf maximal 10MB groß sein.',
|
||||
'caption.max' => 'Die Bildunterschrift darf maximal 500 Zeichen haben.',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
*/
|
||||
protected function prepareForValidation()
|
||||
{
|
||||
$this->merge([
|
||||
'tags' => $this->tags ? explode(',', $this->tags) : [],
|
||||
]);
|
||||
}
|
||||
}
|
||||
66
app/Http/Requests/Tenant/SettingsStoreRequest.php
Normal file
66
app/Http/Requests/Tenant/SettingsStoreRequest.php
Normal file
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Tenant;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class SettingsStoreRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'settings' => ['required', 'array'],
|
||||
'settings.branding' => ['sometimes', 'array'],
|
||||
'settings.branding.logo_url' => ['nullable', 'url', 'max:500'],
|
||||
'settings.branding.primary_color' => ['nullable', 'regex:/^#[0-9A-Fa-f]{6}$/'],
|
||||
'settings.branding.secondary_color' => ['nullable', 'regex:/^#[0-9A-Fa-f]{6}$/'],
|
||||
'settings.branding.font_family' => ['nullable', 'string', 'max:100'],
|
||||
'settings.features' => ['sometimes', 'array'],
|
||||
'settings.features.photo_likes_enabled' => ['nullable', 'boolean'],
|
||||
'settings.features.event_checklist' => ['nullable', 'boolean'],
|
||||
'settings.features.custom_domain' => ['nullable', 'boolean'],
|
||||
'settings.features.advanced_analytics' => ['nullable', 'boolean'],
|
||||
'settings.custom_domain' => ['nullable', 'string', 'max:255', 'regex:/^[a-zA-Z0-9][a-zA-Z0-9-]{1,61}[a-zA-Z0-9]\.[a-zA-Z]{2,}$/'],
|
||||
'settings.contact_email' => ['nullable', 'email', 'max:255'],
|
||||
'settings.event_default_type' => ['nullable', 'string', 'max:50'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom messages for validator errors.
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'settings.required' => 'Settings-Daten sind erforderlich.',
|
||||
'settings.branding.logo_url.url' => 'Die Logo-URL muss eine gültige URL sein.',
|
||||
'settings.branding.primary_color.regex' => 'Die Primärfarbe muss ein gültiges Hex-Format (#RRGGBB) haben.',
|
||||
'settings.branding.secondary_color.regex' => 'Die Sekundärfarbe muss ein gültiges Hex-Format (#RRGGBB) haben.',
|
||||
'settings.custom_domain.regex' => 'Das Custom Domain muss ein gültiges Domain-Format haben.',
|
||||
'settings.contact_email.email' => 'Die Kontakt-E-Mail muss eine gültige E-Mail-Adresse sein.',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
*/
|
||||
protected function prepareForValidation()
|
||||
{
|
||||
$this->merge([
|
||||
'settings' => $this->input('settings', []),
|
||||
]);
|
||||
}
|
||||
}
|
||||
60
app/Http/Requests/Tenant/TaskStoreRequest.php
Normal file
60
app/Http/Requests/Tenant/TaskStoreRequest.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Tenant;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class TaskStoreRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'title' => ['required', 'string', 'max:255'],
|
||||
'description' => ['nullable', 'string'],
|
||||
'collection_id' => ['nullable', 'exists:task_collections,id', function ($attribute, $value, $fail) {
|
||||
$tenantId = request()->tenant?->id;
|
||||
if ($tenantId && !\App\Models\TaskCollection::where('id', $value)->where('tenant_id', $tenantId)->exists()) {
|
||||
$fail('Die TaskCollection gehört nicht zu diesem Tenant.');
|
||||
}
|
||||
}],
|
||||
'priority' => ['nullable', Rule::in(['low', 'medium', 'high', 'urgent'])],
|
||||
'due_date' => ['nullable', 'date', 'after:now'],
|
||||
'is_completed' => ['nullable', 'boolean'],
|
||||
'assigned_to' => ['nullable', 'exists:users,id', function ($attribute, $value, $fail) {
|
||||
$tenantId = request()->tenant?->id;
|
||||
if ($tenantId && !\App\Models\User::where('id', $value)->where('tenant_id', $tenantId)->exists()) {
|
||||
$fail('Der Benutzer gehört nicht zu diesem Tenant.');
|
||||
}
|
||||
}],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom messages for validator errors.
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'title.required' => 'Der Task-Titel ist erforderlich.',
|
||||
'title.max' => 'Der Task-Titel darf maximal 255 Zeichen haben.',
|
||||
'collection_id.exists' => 'Die ausgewählte TaskCollection existiert nicht.',
|
||||
'priority.in' => 'Die Priorität muss low, medium, high oder urgent sein.',
|
||||
'due_date.after' => 'Das Fälligkeitsdatum muss in der Zukunft liegen.',
|
||||
'assigned_to.exists' => 'Der zugewiesene Benutzer existiert nicht.',
|
||||
];
|
||||
}
|
||||
}
|
||||
59
app/Http/Requests/Tenant/TaskUpdateRequest.php
Normal file
59
app/Http/Requests/Tenant/TaskUpdateRequest.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Tenant;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class TaskUpdateRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'title' => ['sometimes', 'required', 'string', 'max:255'],
|
||||
'description' => ['sometimes', 'nullable', 'string'],
|
||||
'collection_id' => ['sometimes', 'nullable', 'exists:task_collections,id', function ($attribute, $value, $fail) {
|
||||
$tenantId = request()->tenant?->id;
|
||||
if ($tenantId && !\App\Models\TaskCollection::where('id', $value)->where('tenant_id', $tenantId)->exists()) {
|
||||
$fail('Die TaskCollection gehört nicht zu diesem Tenant.');
|
||||
}
|
||||
}],
|
||||
'priority' => ['sometimes', 'nullable', Rule::in(['low', 'medium', 'high', 'urgent'])],
|
||||
'due_date' => ['sometimes', 'nullable', 'date'],
|
||||
'is_completed' => ['sometimes', 'boolean'],
|
||||
'assigned_to' => ['sometimes', 'nullable', 'exists:users,id', function ($attribute, $value, $fail) {
|
||||
$tenantId = request()->tenant?->id;
|
||||
if ($tenantId && !\App\Models\User::where('id', $value)->where('tenant_id', $tenantId)->exists()) {
|
||||
$fail('Der Benutzer gehört nicht zu diesem Tenant.');
|
||||
}
|
||||
}],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom messages for validator errors.
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'title.required' => 'Der Task-Titel ist erforderlich.',
|
||||
'title.max' => 'Der Task-Titel darf maximal 255 Zeichen haben.',
|
||||
'collection_id.exists' => 'Die ausgewählte TaskCollection existiert nicht.',
|
||||
'priority.in' => 'Die Priorität muss low, medium, high oder urgent sein.',
|
||||
'assigned_to.exists' => 'Der zugewiesene Benutzer existiert nicht.',
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user