diff --git a/app/Config/Routes.php b/app/Config/Routes.php index f0dd58b0..cba9bf1a 100644 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -60,6 +60,11 @@ $routes->get('themes/colors', 'ColorsController', [ 'as' => 'themes-colors-css', ]); +// health check +$routes->get('/health', 'HomeController::health', [ + 'as' => 'health', +]); + // We get a performance increase by specifying the default // route since we don't have to scan directories. $routes->get('/', 'HomeController', [ diff --git a/app/Controllers/HomeController.php b/app/Controllers/HomeController.php index 68816c0c..130d1e3a 100644 --- a/app/Controllers/HomeController.php +++ b/app/Controllers/HomeController.php @@ -11,8 +11,11 @@ declare(strict_types=1); namespace App\Controllers; use App\Models\PodcastModel; +use CodeIgniter\Database\Exceptions\DatabaseException; use CodeIgniter\HTTP\RedirectResponse; +use CodeIgniter\HTTP\ResponseInterface; use Config\Services; +use Modules\Media\FileManagers\FileManagerInterface; class HomeController extends BaseController { @@ -48,4 +51,43 @@ class HomeController extends BaseController return view('home', $data); } + + public function health(): ResponseInterface + { + $errors = []; + + try { + db_connect(); + } catch (DatabaseException) { + $errors[] = 'Unable to connect to the database.'; + } + + // --- Can Castopod connect to the cache handler + if (config('Cache')->handler !== 'dummy' && cache()->getCacheInfo() === null) { + $errors[] = 'Unable connect to the cache handler.'; + } + + // --- Can Castopod write to storage? + + /** @var FileManagerInterface $fileManager */ + $fileManager = service('file_manager', false); + + if (! $fileManager->isHealthy()) { + $errors[] = 'Problem with file manager.'; + } + + if ($errors !== []) { + return $this->response->setStatusCode(503, 'Problem with cache handler.') + ->setJSON([ + 'code' => 503, + 'errors' => $errors, + ]); + } + + return $this->response->setStatusCode(200) + ->setJSON([ + 'code' => 200, + 'message' => '✨ All good!', + ]); + } } diff --git a/modules/Media/FileManagers/FS.php b/modules/Media/FileManagers/FS.php index 80c03547..f37792a7 100644 --- a/modules/Media/FileManagers/FS.php +++ b/modules/Media/FileManagers/FS.php @@ -132,4 +132,11 @@ class FS implements FileManagerInterface return true; } + + public function isHealthy(): bool + { + helper('media'); + + return is_really_writable(ROOTPATH . 'public/' . media_path()); + } } diff --git a/modules/Media/FileManagers/FileManagerInterface.php b/modules/Media/FileManagers/FileManagerInterface.php index 3b53afa7..39093441 100644 --- a/modules/Media/FileManagers/FileManagerInterface.php +++ b/modules/Media/FileManagers/FileManagerInterface.php @@ -23,4 +23,6 @@ interface FileManagerInterface public function deletePodcastImageSizes(string $podcastHandle): bool; public function deletePersonImagesSizes(): bool; + + public function isHealthy(): bool; } diff --git a/modules/Media/FileManagers/S3.php b/modules/Media/FileManagers/S3.php index eef77fe1..d905945d 100644 --- a/modules/Media/FileManagers/S3.php +++ b/modules/Media/FileManagers/S3.php @@ -27,15 +27,19 @@ class S3 implements FileManagerInterface 'use_path_style_endpoint' => $config->s3['path_style_endpoint'], ]); - // create bucket if it does not already exist - if (! $this->s3->doesBucketExist((string) $this->config->s3['bucket'])) { - try { - $this->s3->createBucket([ - 'Bucket' => $this->config->s3['bucket'], - ]); - } catch (Exception $exception) { - log_message('critical', $exception->getMessage()); + try { + // create bucket if it does not already exist + if (! $this->s3->doesBucketExist((string) $this->config->s3['bucket'])) { + try { + $this->s3->createBucket([ + 'Bucket' => $this->config->s3['bucket'], + ]); + } catch (Exception $exception) { + log_message('critical', $exception->getMessage()); + } } + } catch (Exception $exception) { + throw new Exception($exception->getMessage(), $exception->getCode(), $exception); } } @@ -171,4 +175,18 @@ class S3 implements FileManagerInterface $podcastImageKeys = preg_grep("~^persons\/.*_.*.\.(jpg|png|webp)$~", $objectsKeys); return (bool) $podcastImageKeys; } + + public function isHealthy(): bool + { + try { + // ok if bucket exists and you have permission to access it + $this->s3->headBucket([ + 'Bucket' => $this->config->s3['bucket'], + ]); + } catch (Exception) { + return false; + } + + return true; + } }