getSession()->get('console_logged_in')) { return new JsonResponse(['error' => 'Unauthorized'], Response::HTTP_UNAUTHORIZED); } return null; } // Credentials Management public function credentials(Request $request): JsonResponse { if ($resp = $this->checkAuth($request)) { return $resp; } if ($request->getMethod() === 'GET') { $credentials = $this->entityManager->getRepository(S3Credential::class)->findAll(); return new JsonResponse([ 'credentials' => array_map(function($cred) { return [ 'id' => $cred->getId(), 'access_key' => $cred->getAccessKey(), 'user_name' => $cred->getUserName(), 'is_active' => $cred->isActive(), 'created_at' => $cred->getCreatedAt()->format('Y-m-d H:i:s'), 'bucket_count' => $cred->getBuckets()->count() ]; }, $credentials) ]); } if ($request->getMethod() === 'POST') { $data = json_decode($request->getContent(), true); $accessKey = $data['access_key'] ?? 'AKIA' . strtoupper(bin2hex(random_bytes(10))); $secretKey = $data['secret_key'] ?? base64_encode(random_bytes(30)); $userName = $data['user_name'] ?? null; $credential = $this->s3Service->createCredential($accessKey, $secretKey, $userName); return new JsonResponse([ 'id' => $credential->getId(), 'access_key' => $credential->getAccessKey(), 'secret_key' => $credential->getSecretKey(), 'user_name' => $credential->getUserName(), 'is_active' => $credential->isActive(), 'created_at' => $credential->getCreatedAt()->format('Y-m-d H:i:s') ], 201); } return new JsonResponse(['error' => 'Method not allowed'], 405); } public function credentialDetail(int $id, Request $request): JsonResponse { if ($resp = $this->checkAuth($request)) { return $resp; } $credential = $this->entityManager->getRepository(S3Credential::class)->find($id); if (!$credential) { return new JsonResponse(['error' => 'Credential not found'], 404); } if ($request->getMethod() === 'GET') { return new JsonResponse([ 'id' => $credential->getId(), 'access_key' => $credential->getAccessKey(), 'secret_key' => $credential->getSecretKey(), 'user_name' => $credential->getUserName(), 'is_active' => $credential->isActive(), 'created_at' => $credential->getCreatedAt()->format('Y-m-d H:i:s'), 'buckets' => array_map(function($bucket) { return [ 'name' => $bucket->getName(), 'region' => $bucket->getRegion(), 'created_at' => $bucket->getCreatedAt()->format('Y-m-d H:i:s') ]; }, $credential->getBuckets()->toArray()) ]); } if ($request->getMethod() === 'PUT') { $data = json_decode($request->getContent(), true); if (isset($data['user_name'])) { $credential->setUserName($data['user_name']); } if (isset($data['is_active'])) { $credential->setIsActive($data['is_active']); } $this->entityManager->flush(); return new JsonResponse(['message' => 'Credential updated']); } if ($request->getMethod() === 'DELETE') { $this->entityManager->remove($credential); $this->entityManager->flush(); return new JsonResponse(['message' => 'Credential deleted']); } return new JsonResponse(['error' => 'Method not allowed'], 405); } // Buckets Management public function buckets(Request $request): JsonResponse { if ($resp = $this->checkAuth($request)) { return $resp; } if ($request->getMethod() === 'GET') { $buckets = $this->entityManager->getRepository(S3Bucket::class)->findAll(); return new JsonResponse([ 'buckets' => array_map(function($bucket) { $objectCount = $this->entityManager->getRepository(S3Object::class)->count(['bucket' => $bucket]); $totalSize = $this->entityManager->createQueryBuilder() ->select('SUM(o.size)') ->from(S3Object::class, 'o') ->where('o.bucket = :bucket') ->setParameter('bucket', $bucket) ->getQuery() ->getSingleScalarResult() ?: 0; return [ 'name' => $bucket->getName(), 'region' => $bucket->getRegion(), 'owner' => $bucket->getOwner()->getUserName() ?: $bucket->getOwner()->getAccessKey(), 'created_at' => $bucket->getCreatedAt()->format('Y-m-d H:i:s'), 'object_count' => $objectCount, 'total_size' => $totalSize, 'total_size_human' => $this->formatBytes($totalSize) ]; }, $buckets) ]); } if ($request->getMethod() === 'POST') { $data = json_decode($request->getContent(), true); $bucketName = $data['name'] ?? null; $ownerId = $data['owner_id'] ?? null; $region = $data['region'] ?? 'us-east-1'; if (!$bucketName || !$ownerId) { return new JsonResponse(['error' => 'Missing bucket name or owner'], 400); } $owner = $this->entityManager->getRepository(S3Credential::class)->find($ownerId); if (!$owner) { return new JsonResponse(['error' => 'Owner not found'], 404); } try { $bucket = $this->s3Service->createBucket($bucketName, $owner, $region); return new JsonResponse([ 'name' => $bucket->getName(), 'region' => $bucket->getRegion(), 'owner' => $bucket->getOwner()->getUserName() ?: $bucket->getOwner()->getAccessKey(), 'created_at' => $bucket->getCreatedAt()->format('Y-m-d H:i:s') ], 201); } catch (\Exception $e) { return new JsonResponse(['error' => $e->getMessage()], 400); } } return new JsonResponse(['error' => 'Method not allowed'], 405); } public function bucketDetail(string $name, Request $request): JsonResponse { if ($resp = $this->checkAuth($request)) { return $resp; } $bucket = $this->s3Service->findBucketByName($name); if (!$bucket) { return new JsonResponse(['error' => 'Bucket not found'], 404); } if ($request->getMethod() === 'GET') { $objects = $this->s3Service->listObjects($bucket); $totalSize = array_sum(array_map(fn($obj) => $obj->getSize(), $objects)); return new JsonResponse([ 'name' => $bucket->getName(), 'region' => $bucket->getRegion(), 'owner' => $bucket->getOwner()->getUserName() ?: $bucket->getOwner()->getAccessKey(), 'created_at' => $bucket->getCreatedAt()->format('Y-m-d H:i:s'), 'object_count' => count($objects), 'total_size' => $totalSize, 'total_size_human' => $this->formatBytes($totalSize), 'objects' => array_map(function($obj) { return [ 'key' => $obj->getObjectKey(), 'size' => $obj->getSize(), 'size_human' => $this->formatBytes($obj->getSize()), 'content_type' => $obj->getContentType(), 'etag' => $obj->getEtag(), 'is_multipart' => $obj->isMultipart(), 'created_at' => $obj->getCreatedAt()->format('Y-m-d H:i:s') ]; }, $objects) ]); } if ($request->getMethod() === 'DELETE') { try { $this->s3Service->deleteBucket($bucket); return new JsonResponse(['message' => 'Bucket deleted']); } catch (\Exception $e) { return new JsonResponse(['error' => $e->getMessage()], 400); } } return new JsonResponse(['error' => 'Method not allowed'], 405); } // Objects Management public function objects(string $bucketName, Request $request): JsonResponse { if ($resp = $this->checkAuth($request)) { return $resp; } $bucket = $this->s3Service->findBucketByName($bucketName); if (!$bucket) { return new JsonResponse(['error' => 'Bucket not found'], 404); } if ($request->getMethod() === 'GET') { $prefix = $request->query->get('prefix', ''); $objects = $this->s3Service->listObjects($bucket, $prefix); return new JsonResponse([ 'objects' => array_map(function($obj) { return [ 'key' => $obj->getObjectKey(), 'size' => $obj->getSize(), 'size_human' => $this->formatBytes($obj->getSize()), 'content_type' => $obj->getContentType(), 'etag' => $obj->getEtag(), 'is_multipart' => $obj->isMultipart(), 'created_at' => $obj->getCreatedAt()->format('Y-m-d H:i:s') ]; }, $objects) ]); } if ($request->getMethod() === 'DELETE') { $data = json_decode($request->getContent(), true); $keys = $data['keys'] ?? []; $deleted = []; foreach ($keys as $key) { $object = $this->s3Service->findObject($bucket, $key); if ($object) { $this->s3Service->deleteObject($object); $deleted[] = $key; } } return new JsonResponse(['deleted' => $deleted]); } return new JsonResponse(['error' => 'Method not allowed'], 405); } public function objectDetail(string $bucketName, string $objectKey, Request $request): JsonResponse { if ($resp = $this->checkAuth($request)) { return $resp; } $bucket = $this->s3Service->findBucketByName($bucketName); if (!$bucket) { return new JsonResponse(['error' => 'Bucket not found'], 404); } $object = $this->s3Service->findObject($bucket, $objectKey); if (!$object) { return new JsonResponse(['error' => 'Object not found'], 404); } if ($request->getMethod() === 'GET') { return new JsonResponse([ 'key' => $object->getObjectKey(), 'size' => $object->getSize(), 'size_human' => $this->formatBytes($object->getSize()), 'content_type' => $object->getContentType(), 'etag' => $object->getEtag(), 'is_multipart' => $object->isMultipart(), 'part_count' => $object->getPartCount(), 'metadata' => $object->getMetadata(), 'storage_path' => $object->getStoragePath(), 'created_at' => $object->getCreatedAt()->format('Y-m-d H:i:s'), 'updated_at' => $object->getUpdatedAt()->format('Y-m-d H:i:s') ]); } if ($request->getMethod() === 'DELETE') { $this->s3Service->deleteObject($object); return new JsonResponse(['message' => 'Object deleted']); } return new JsonResponse(['error' => 'Method not allowed'], 405); } // Multipart Uploads public function multipartUploads(string $bucketName, Request $request): JsonResponse { if ($resp = $this->checkAuth($request)) { return $resp; } $bucket = $this->s3Service->findBucketByName($bucketName); if (!$bucket) { return new JsonResponse(['error' => 'Bucket not found'], 404); } $uploads = $this->s3Service->listMultipartUploads($bucket); return new JsonResponse([ 'uploads' => array_map(function($upload) { $parts = $this->s3Service->listParts($upload); $totalSize = array_sum(array_map(fn($part) => $part->getSize(), $parts)); return [ 'upload_id' => $upload->getUploadId(), 'object_key' => $upload->getObjectKey(), 'initiated_by' => $upload->getInitiatedBy()->getUserName() ?: $upload->getInitiatedBy()->getAccessKey(), 'content_type' => $upload->getContentType(), 'initiated_at' => $upload->getInitiatedAt()->format('Y-m-d H:i:s'), 'part_count' => count($parts), 'total_size' => $totalSize, 'total_size_human' => $this->formatBytes($totalSize) ]; }, $uploads) ]); } // Presigned URLs public function presignedUrls(Request $request): JsonResponse { if ($resp = $this->checkAuth($request)) { return $resp; } if ($request->getMethod() === 'GET') { $urls = $this->entityManager->getRepository(\App\Entity\S3PresignedUrl::class) ->createQueryBuilder('p') ->where('p.expiresAt > :now') ->setParameter('now', new \DateTime()) ->orderBy('p.createdAt', 'DESC') ->setMaxResults(100) ->getQuery() ->getResult(); return new JsonResponse([ 'urls' => array_map(function($url) { return [ 'bucket_name' => $url->getBucketName(), 'object_key' => $url->getObjectKey(), 'method' => $url->getMethod(), 'access_key' => $url->getAccessKey(), 'expires_at' => $url->getExpiresAt()->format('Y-m-d H:i:s'), 'created_at' => $url->getCreatedAt()->format('Y-m-d H:i:s'), 'url' => $url->getUrl() ]; }, $urls) ]); } if ($request->getMethod() === 'POST') { $data = json_decode($request->getContent(), true); $bucketName = $data['bucket_name'] ?? null; $objectKey = $data['object_key'] ?? null; $method = $data['method'] ?? 'GET'; $expiresIn = $data['expires_in'] ?? 3600; $accessKey = $data['access_key'] ?? null; if (!$bucketName || !$objectKey || !$accessKey) { return new JsonResponse(['error' => 'Missing required fields'], 400); } $credential = $this->s3Service->findCredentialByAccessKey($accessKey); if (!$credential) { return new JsonResponse(['error' => 'Invalid access key'], 404); } $url = $this->s3Service->generatePresignedUrl($bucketName, $objectKey, $credential, $method, $expiresIn); return new JsonResponse(['url' => $url], 201); } return new JsonResponse(['error' => 'Method not allowed'], 405); } // Statistics public function stats(Request $request): JsonResponse { if ($resp = $this->checkAuth($request)) { return $resp; } $credentialCount = $this->entityManager->getRepository(S3Credential::class)->count([]); $bucketCount = $this->entityManager->getRepository(S3Bucket::class)->count([]); $objectCount = $this->entityManager->getRepository(S3Object::class)->count([]); $totalSize = $this->entityManager->createQueryBuilder() ->select('SUM(o.size)') ->from(S3Object::class, 'o') ->getQuery() ->getSingleScalarResult() ?: 0; $multipartUploads = $this->entityManager->getRepository(\App\Entity\S3MultipartUpload::class)->count([]); $activePresignedUrls = $this->entityManager->getRepository(\App\Entity\S3PresignedUrl::class) ->createQueryBuilder('p') ->select('COUNT(p.id)') ->where('p.expiresAt > :now') ->setParameter('now', new \DateTime()) ->getQuery() ->getSingleScalarResult(); return new JsonResponse([ 'credentials' => $credentialCount, 'buckets' => $bucketCount, 'objects' => $objectCount, 'total_storage' => $totalSize, 'total_storage_human' => $this->formatBytes($totalSize), 'active_multipart_uploads' => $multipartUploads, 'active_presigned_urls' => $activePresignedUrls ]); } private function formatBytes(int $bytes): string { if ($bytes === 0) return '0 B'; $units = ['B', 'KB', 'MB', 'GB', 'TB']; $i = floor(log($bytes, 1024)); return round($bytes / pow(1024, $i), 2) . ' ' . $units[$i]; } }