authorize($request); $query = Organization::query() ->withCount('users') ->with(['subscriptions' => fn ($subscription) => $subscription->latest('starts_at')->limit(1)]) ->addSelect(['last_activity_at' => DB::table('audit_logs') ->select('created_at') ->whereColumn('organization_id', 'organizations.id') ->latest('created_at') ->limit(1)]); if ($search = trim((string) $request->query('search', ''))) { $query->where(fn ($builder) => $builder->where('name', 'like', "%{$search}%")->orWhere('slug', 'like', "%{$search}%")); } if ($status = $request->query('status')) { $query->where('status', $status); } if ($plan = $request->query('plan')) { $query->whereHas('subscriptions', fn ($subscription) => $subscription->where('plan_key', $plan)); } $sort = (string) $request->query('sort', 'name'); $direction = strtolower((string) $request->query('direction', 'asc')) === 'desc' ? 'desc' : 'asc'; $column = $sort === 'users' ? 'users_count' : (in_array($sort, ['name', 'created_at'], true) ? $sort : 'name'); $organizations = $query->orderBy($column, $direction)->paginate(min(max($request->integer('perPage', 20), 1), 100)); return response()->json(['data' => $organizations->getCollection()->map($this->payload(...)), 'meta' => ['currentPage' => $organizations->currentPage(), 'lastPage' => $organizations->lastPage(), 'perPage' => $organizations->perPage(), 'total' => $organizations->total()]]); } public function store(StoreOrganizationRequest $request): JsonResponse { $this->authorize($request); $data = $request->validated(); $organization = Organization::query()->create(['name' => $data['name'], 'slug' => $data['slug'], 'status' => $data['status'] ?? 'active', 'default_locale' => $data['defaultLocale'], 'timezone' => $data['timezone']]); $this->audit($request, 'organization.created', $organization); return response()->json(['data' => $this->payload($organization)], 201); } public function show(Request $request, string $organization): JsonResponse { $this->authorize($request); $model = Organization::query()->withCount('users')->findOrFail($organization); $subscription = $model->subscriptions()->latest('starts_at')->first(); $recentUsers = $model->users()->latest('created_at')->limit(5)->get(['id', 'name', 'email', 'role', 'status']); $recentActivity = DB::table('audit_logs')->where('organization_id', $model->getKey())->latest('created_at')->limit(10)->get(['id', 'action', 'entity_type as entityType', 'entity_id as entityId', 'created_at as createdAt']); return response()->json(['data' => array_merge($this->payload($model), ['subscription' => $subscription ? $this->subscriptionPayload($subscription) : null, 'storageUsedBytes' => $subscription?->storage_used_bytes ?? 0, 'aiJobsCount' => DB::table('ai_jobs')->where('organization_id', $model->getKey())->count(), 'failedAiJobsCount' => DB::table('ai_jobs')->where('organization_id', $model->getKey())->where('status', 'failed')->count(), 'recentUsers' => $recentUsers, 'recentActivity' => $recentActivity])]); } public function update(UpdateOrganizationRequest $request, string $organization): JsonResponse { $this->authorize($request); $model = Organization::query()->findOrFail($organization); $data = $request->validated(); $model->update(array_filter(['name' => $data['name'] ?? null, 'status' => $data['status'] ?? null, 'default_locale' => $data['defaultLocale'] ?? null, 'timezone' => $data['timezone'] ?? null], fn ($value) => $value !== null)); $this->audit($request, 'organization.updated', $model); return response()->json(['data' => $this->payload($model->refresh())]); } private function authorize(Request $request): void { abort_unless($this->deployment->supportsMultipleOrganizations(), 404); abort_unless($this->permissions->allows($request->user(), Permission::PlatformManageOrganizations), 403); } private function payload(Organization $organization): array { $subscription = $organization->relationLoaded('subscriptions') ? $organization->subscriptions->first() : $organization->subscriptions()->latest('starts_at')->first(); return ['id' => $organization->getKey(), 'name' => $organization->name, 'slug' => $organization->slug, 'status' => $organization->status, 'defaultLocale' => $organization->default_locale, 'timezone' => $organization->timezone, 'usersCount' => $organization->users_count ?? $organization->users()->count(), 'createdAt' => $organization->created_at?->toISOString(), 'lastActivityAt' => $organization->last_activity_at ? Carbon::parse($organization->last_activity_at)->toISOString() : null, 'planKey' => $subscription?->plan_key, 'subscriptionStatus' => $subscription?->status, 'subscriptionExpiresAt' => $subscription?->expires_at?->toISOString(), 'storageUsedBytes' => $subscription?->storage_used_bytes ?? 0, 'storageQuotaBytes' => $subscription?->storage_quota_bytes, 'aiCreditsUsed' => $subscription?->ai_credits_used ?? 0]; } private function subscriptionPayload($subscription): array { return ['id' => $subscription->getKey(), 'planKey' => $subscription->plan_key, 'status' => $subscription->status, 'startsAt' => $subscription->starts_at?->toISOString(), 'expiresAt' => $subscription->expires_at?->toISOString(), 'seatLimit' => $subscription->seat_limit, 'storageQuotaBytes' => $subscription->storage_quota_bytes, 'storageUsedBytes' => $subscription->storage_used_bytes, 'aiCreditQuota' => $subscription->ai_credit_quota, 'aiCreditsUsed' => $subscription->ai_credits_used, 'enabledFeatures' => $subscription->enabled_features ?? []]; } private function audit(Request $request, string $action, Organization $organization): void { DB::table('audit_logs')->insert(['id' => (string) str()->ulid(), 'organization_id' => $organization->getKey(), 'actor_id' => $request->user()->getKey(), 'action' => $action, 'entity_type' => 'organization', 'entity_id' => $organization->getKey(), 'metadata' => json_encode(['schemaVersion' => 1]), 'ip_address' => $request->ip(), 'created_at' => now()]); } }