die tenant admin oauth authentifizierung wurde implementiert und funktioniert jetzt. Zudem wurde das marketing frontend dashboard implementiert.

This commit is contained in:
Codex Agent
2025-11-04 16:14:17 +01:00
parent 92e64c361a
commit fe380689fb
63 changed files with 4239 additions and 1142 deletions

View File

@@ -3,13 +3,11 @@
namespace App\Http\Controllers\Api\Tenant;
use App\Http\Controllers\Controller;
use App\Models\Event;
use App\Models\Photo;
use App\Models\Tenant;
use App\Services\Tenant\DashboardSummaryService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Support\Carbon;
class DashboardController extends Controller
{
@@ -32,52 +30,8 @@ class DashboardController extends Controller
], 401);
}
$eventsQuery = Event::query()
->where('tenant_id', $tenant->getKey());
$summary = app(DashboardSummaryService::class)->build($tenant);
$activeEvents = (clone $eventsQuery)
->where(fn ($query) => $query
->where('is_active', true)
->orWhere('status', 'published'))
->count();
$upcomingEvents = (clone $eventsQuery)
->whereDate('date', '>=', Carbon::now()->startOfDay())
->count();
$eventsWithTasks = (clone $eventsQuery)
->whereHas('tasks')
->count();
$totalEvents = (clone $eventsQuery)->count();
$taskProgress = $totalEvents > 0
? (int) round(($eventsWithTasks / $totalEvents) * 100)
: 0;
$newPhotos = Photo::query()
->whereHas('event', fn ($query) => $query->where('tenant_id', $tenant->getKey()))
->where('created_at', '>=', Carbon::now()->subDays(7))
->count();
$activePackage = $tenant->tenantPackages()
->with('package')
->where('active', true)
->orderByDesc('expires_at')
->orderByDesc('purchased_at')
->first();
return response()->json([
'active_events' => $activeEvents,
'new_photos' => $newPhotos,
'task_progress' => $taskProgress,
'credit_balance' => $tenant->event_credits_balance ?? null,
'upcoming_events' => $upcomingEvents,
'active_package' => $activePackage ? [
'name' => $activePackage->package?->getNameForLocale(app()->getLocale()) ?? $activePackage->package?->name ?? '',
'expires_at' => optional($activePackage->expires_at)->toIso8601String(),
'remaining_events' => $activePackage->remaining_events ?? null,
] : null,
]);
return response()->json($summary);
}
}

View File

@@ -0,0 +1,134 @@
<?php
namespace App\Http\Controllers\Api\Tenant;
use App\Http\Controllers\Controller;
use App\Http\Requests\Tenant\ProfileUpdateRequest;
use App\Models\User;
use App\Support\ApiError;
use App\Support\TenantAuth;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Symfony\Component\HttpFoundation\Response;
class ProfileController extends Controller
{
public function show(Request $request): JsonResponse
{
try {
$user = TenantAuth::resolveAdminUser($request);
} catch (\Throwable $exception) {
Log::warning('[TenantProfile] Unable to resolve user for profile show', [
'tenant_id' => $request->attributes->get('tenant_id'),
'error' => $exception->getMessage(),
]);
return ApiError::response(
'profile_user_missing',
'Profil nicht verfügbar',
'Für diesen Tenant konnte kein Account gefunden werden.',
Response::HTTP_NOT_FOUND
);
}
return response()->json([
'data' => $this->transformUser($user),
]);
}
public function update(ProfileUpdateRequest $request): JsonResponse
{
try {
$user = TenantAuth::resolveAdminUser($request);
} catch (\Throwable $exception) {
Log::warning('[TenantProfile] Unable to resolve user for profile update', [
'tenant_id' => $request->attributes->get('tenant_id'),
'error' => $exception->getMessage(),
]);
return ApiError::response(
'profile_user_missing',
'Profil nicht verfügbar',
'Für diesen Tenant konnte kein Account gefunden werden.',
Response::HTTP_NOT_FOUND
);
}
$data = $request->validated();
$updates = [];
$emailChanged = false;
if (isset($data['name']) && $data['name'] !== $user->name) {
$updates['name'] = $data['name'];
}
if (array_key_exists('preferred_locale', $data) && $data['preferred_locale'] !== $user->preferred_locale) {
$updates['preferred_locale'] = $data['preferred_locale'] ? Str::lower($data['preferred_locale']) : null;
}
if (isset($data['email']) && Str::lower($data['email']) !== Str::lower((string) $user->email)) {
$updates['email'] = $data['email'];
$updates['email_verified_at'] = null;
$emailChanged = true;
}
if ($request->filled('password')) {
$currentPassword = (string) $request->input('current_password');
if (! $request->filled('current_password') || ! Hash::check($currentPassword, $user->password)) {
return ApiError::response(
'profile.invalid_current_password',
'Aktuelles Passwort ungültig',
'Das aktuelle Passwort stimmt nicht.',
Response::HTTP_UNPROCESSABLE_ENTITY,
[
'errors' => [
'current_password' => ['Das aktuelle Passwort stimmt nicht.'],
],
]
);
}
$updates['password'] = $request->input('password');
}
if ($updates !== []) {
$user->forceFill($updates);
$user->save();
if ($emailChanged) {
try {
$user->sendEmailVerificationNotification();
} catch (\Throwable $exception) {
Log::error('[TenantProfile] Failed to send verification email after profile update', [
'user_id' => $user->getKey(),
'tenant_id' => $request->attributes->get('tenant_id'),
'error' => $exception->getMessage(),
]);
}
}
}
return response()->json([
'message' => 'Profil erfolgreich aktualisiert.',
'data' => $this->transformUser($user->fresh()),
]);
}
/**
* @return array<string, mixed>
*/
private function transformUser(User $user): array
{
return [
'id' => $user->getKey(),
'name' => $user->name,
'email' => $user->email,
'preferred_locale' => $user->preferred_locale,
'email_verified' => $user->email_verified_at !== null,
'email_verified_at' => $user->email_verified_at?->toIso8601String(),
];
}
}