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:
2025-09-17 19:56:54 +02:00
parent 5fbb9cb240
commit 42d6e98dff
84 changed files with 6125 additions and 155 deletions

View File

@@ -0,0 +1,224 @@
<?php
namespace App\Http\Controllers\Api\Tenant;
use App\Http\Controllers\Controller;
use App\Http\Requests\Tenant\EventStoreRequest;
use Illuminate\Support\Str;
use App\Http\Resources\Tenant\EventResource;
use App\Models\Event;
use App\Models\Tenant;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Gate;
use Illuminate\Validation\ValidationException;
class EventController extends Controller
{
/**
* Display a listing of the tenant's events.
*/
public function index(Request $request): AnonymousResourceCollection
{
$tenantId = $request->attributes->get('tenant_id');
if (!$tenantId) {
throw ValidationException::withMessages([
'tenant_id' => 'Tenant ID not found in request context.',
]);
}
$query = Event::where('tenant_id', $tenantId)
->with(['eventType', 'photos'])
->orderBy('created_at', 'desc');
// Apply filters
if ($request->has('status')) {
$query->where('status', $request->status);
}
if ($request->has('type_id')) {
$query->where('event_type_id', $request->type_id);
}
// Pagination
$perPage = $request->get('per_page', 15);
$events = $query->paginate($perPage);
return EventResource::collection($events);
}
/**
* Store a newly created event in storage.
*/
public function store(EventStoreRequest $request): JsonResponse
{
$tenantId = $request->attributes->get('tenant_id');
// Check credits balance
$tenant = Tenant::findOrFail($tenantId);
if ($tenant->event_credits_balance <= 0) {
return response()->json([
'error' => 'Insufficient event credits. Please purchase more credits.',
], 402);
}
$validated = $request->validated();
$event = Event::create(array_merge($validated, [
'tenant_id' => $tenantId,
'status' => 'draft', // Default status
'slug' => $this->generateUniqueSlug($validated['name'], $tenantId),
]));
// Decrement credits
$tenant->decrement('event_credits_balance', 1);
$event->load(['eventType', 'tenant']);
return response()->json([
'message' => 'Event created successfully',
'data' => new EventResource($event),
], 201);
}
/**
* Display the specified event.
*/
public function show(Request $request, Event $event): JsonResponse
{
$tenantId = $request->attributes->get('tenant_id');
// Ensure event belongs to tenant
if ($event->tenant_id !== $tenantId) {
return response()->json(['error' => 'Event not found'], 404);
}
$event->load([
'eventType',
'photos' => fn ($query) => $query->with('likes')->latest(),
'tasks',
'tenant' => fn ($query) => $query->select('id', 'name', 'event_credits_balance')
]);
return response()->json([
'data' => new EventResource($event),
]);
}
/**
* Update the specified event in storage.
*/
public function update(EventStoreRequest $request, Event $event): JsonResponse
{
$tenantId = $request->attributes->get('tenant_id');
// Ensure event belongs to tenant
if ($event->tenant_id !== $tenantId) {
return response()->json(['error' => 'Event not found'], 404);
}
$validated = $request->validated();
// Update slug if name changed
if ($validated['name'] !== $event->name) {
$validated['slug'] = $this->generateUniqueSlug($validated['name'], $tenantId, $event->id);
}
$event->update($validated);
$event->load(['eventType', 'tenant']);
return response()->json([
'message' => 'Event updated successfully',
'data' => new EventResource($event),
]);
}
/**
* Remove the specified event from storage.
*/
public function destroy(Request $request, Event $event): JsonResponse
{
$tenantId = $request->attributes->get('tenant_id');
// Ensure event belongs to tenant
if ($event->tenant_id !== $tenantId) {
return response()->json(['error' => 'Event not found'], 404);
}
// Soft delete
$event->delete();
return response()->json([
'message' => 'Event deleted successfully',
]);
}
/**
* Bulk update event status (publish/unpublish)
*/
public function bulkUpdateStatus(Request $request): JsonResponse
{
$tenantId = $request->attributes->get('tenant_id');
$validated = $request->validate([
'event_ids' => 'required|array',
'event_ids.*' => 'exists:events,id',
'status' => 'required|in:draft,published,archived',
]);
$updatedCount = Event::whereIn('id', $validated['event_ids'])
->where('tenant_id', $tenantId)
->update(['status' => $validated['status']]);
return response()->json([
'message' => "{$updatedCount} events updated successfully",
'updated_count' => $updatedCount,
]);
}
/**
* Generate unique slug for event name
*/
private function generateUniqueSlug(string $name, int $tenantId, ?int $excludeId = null): string
{
$slug = Str::slug($name);
$originalSlug = $slug;
$counter = 1;
while (Event::where('slug', $slug)
->where('tenant_id', $tenantId)
->where('id', '!=', $excludeId)
->exists()) {
$slug = $originalSlug . '-' . $counter;
$counter++;
}
return $slug;
}
/**
* Search events by name or description
*/
public function search(Request $request): AnonymousResourceCollection
{
$tenantId = $request->attributes->get('tenant_id');
$query = $request->get('q', '');
if (strlen($query) < 2) {
return EventResource::collection(collect([]));
}
$events = Event::where('tenant_id', $tenantId)
->where(function ($q) use ($query) {
$q->where('name', 'like', "%{$query}%")
->orWhere('description', 'like', "%{$query}%");
})
->with('eventType')
->limit(10)
->get();
return EventResource::collection($events);
}
}