From 21ac4621c3230f9868bf6c6dc98f6e6b700ad596 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?D=2E=20Nagy=20Gerg=C5=91?= <hello@iamgergo.com>
Date: Sun, 13 Oct 2024 15:53:53 +0200
Subject: [PATCH] settings

---
 database/factories/SettingFactory.php  |  3 +-
 routes/web.php                         |  5 --
 src/Interfaces/Settings/Registry.php   |  5 +-
 src/Interfaces/Settings/Repository.php | 57 ++++++++++++++++++++-
 src/Models/Setting.php                 | 10 ++--
 src/Settings/Registry.php              |  8 +++
 src/Settings/Repository.php            | 33 +++++++++---
 tests/Settings/SettingsTest.php        | 70 ++++++++++++++++++++++++++
 8 files changed, 171 insertions(+), 20 deletions(-)
 create mode 100644 tests/Settings/SettingsTest.php

diff --git a/database/factories/SettingFactory.php b/database/factories/SettingFactory.php
index 2f60e75f..69db050a 100644
--- a/database/factories/SettingFactory.php
+++ b/database/factories/SettingFactory.php
@@ -20,7 +20,8 @@ class SettingFactory extends Factory
     public function definition(): array
     {
         return [
-            //
+            'key' => $this->faker->slug(1),
+            'value' => mt_rand(10, 1000),
         ];
     }
 }
diff --git a/routes/web.php b/routes/web.php
index 53c9c9ce..9c884374 100644
--- a/routes/web.php
+++ b/routes/web.php
@@ -3,7 +3,6 @@
 use Cone\Root\Http\Controllers\DashboardController;
 use Cone\Root\Http\Controllers\DownloadController;
 use Cone\Root\Http\Controllers\ResourceController;
-use Cone\Root\Http\Controllers\SettingController;
 use Illuminate\Support\Facades\Route;
 
 // Dashboard
@@ -12,10 +11,6 @@
 // Download
 Route::get('/download/{medium:uuid}', DownloadController::class)->name('download');
 
-// Settings
-Route::get('/settings/{group}', [SettingController::class, 'show'])->name('settings.show');
-Route::patch('/settings/{group}', [SettingController::class, 'update'])->name('settings.update');
-
 // Resource
 Route::get('/{resource}', [ResourceController::class, 'index'])->name('resource.index');
 Route::get('/{resource}/create', [ResourceController::class, 'create'])->name('resource.create');
diff --git a/src/Interfaces/Settings/Registry.php b/src/Interfaces/Settings/Registry.php
index 33b3ab10..ac10d0fc 100644
--- a/src/Interfaces/Settings/Registry.php
+++ b/src/Interfaces/Settings/Registry.php
@@ -4,5 +4,8 @@
 
 interface Registry
 {
-    //
+    /**
+     * Get the repository instance.
+     */
+    public function getRepository(): Repository;
 }
diff --git a/src/Interfaces/Settings/Repository.php b/src/Interfaces/Settings/Repository.php
index 2ece89dd..f616d6e3 100644
--- a/src/Interfaces/Settings/Repository.php
+++ b/src/Interfaces/Settings/Repository.php
@@ -2,7 +2,62 @@
 
 namespace Cone\Root\Interfaces\Settings;
 
+use Cone\Root\Models\Setting;
+
 interface Repository
 {
-    //
+    /**
+     * Get the setting model.
+     */
+    public function model(): Setting;
+
+    /**
+     * Set the value cast.
+     */
+    public function cast(string $key, string $type): void;
+
+    /**
+     * Merge the casts.
+     */
+    public function mergeCasts(array $casts): void;
+
+    /**
+     * Remove the given casts.
+     */
+    public function removeCasts(string|array $keys): void;
+
+    /**
+     * Remove the given casts.
+     */
+    public function clearCasts(): void;
+
+    /**
+     * Get the value casts.
+     */
+    public function getCasts(): array;
+
+    /**
+     * Get the value for the given key.
+     */
+    public function get(string $key, mixed $default = null, bool $fresh = false): mixed;
+
+    /**
+     * Set the value for the given key.
+     */
+    public function set(string $key, mixed $value): mixed;
+
+    /**
+     * Delete the given keys.
+     */
+    public function delete(string|array $keys): void;
+
+    /**
+     * Flush the cache.
+     */
+    public function flush(): void;
+
+    /**
+     * Get all the settings.
+     */
+    public function all(): array;
 }
diff --git a/src/Models/Setting.php b/src/Models/Setting.php
index d6cd7fc5..9d852f5f 100644
--- a/src/Models/Setting.php
+++ b/src/Models/Setting.php
@@ -49,12 +49,14 @@ protected static function newFactory(): SettingFactory
     /**
      * Cast the value attribute to the given type.
      */
-    public function castValue(?string $type = null): void
+    public function castValue(?string $type = null): static
     {
-        if (is_null($type)) {
-            unset($this->casts['value']);
-        } else {
+        if (! is_null($type)) {
             $this->casts['value'] = $type;
+        } else {
+            unset($this->casts['value']);
         }
+
+        return $this;
     }
 }
diff --git a/src/Settings/Registry.php b/src/Settings/Registry.php
index 63600bd6..39db241a 100644
--- a/src/Settings/Registry.php
+++ b/src/Settings/Registry.php
@@ -20,6 +20,14 @@ public function __construct(Repository $repository)
         $this->repository = $repository;
     }
 
+    /**
+     * Get the repository instance.
+     */
+    public function getRepository(): Repository
+    {
+        return $this->repository;
+    }
+
     /**
      * Dynamically call the given method.
      */
diff --git a/src/Settings/Repository.php b/src/Settings/Repository.php
index fea0572f..018011b8 100644
--- a/src/Settings/Repository.php
+++ b/src/Settings/Repository.php
@@ -6,6 +6,7 @@
 use Cone\Root\Interfaces\Settings\Repository as Contract;
 use Cone\Root\Models\Setting;
 use Illuminate\Contracts\Support\Arrayable;
+use Illuminate\Database\Eloquent\Builder;
 
 class Repository implements Arrayable, ArrayAccess, Contract
 {
@@ -27,6 +28,14 @@ public function model(): Setting
         return Setting::proxy();
     }
 
+    /**
+     * Get the base query for the repository.
+     */
+    public function query(): Builder
+    {
+        return $this->model()->newQuery();
+    }
+
     /**
      * Set the value cast.
      */
@@ -78,15 +87,23 @@ public function get(string $key, mixed $default = null, bool $fresh = false): mi
             return $this->offsetGet($key);
         }
 
-        $model = $this->model()->newQuery()->firstWhere('key', '=', $key);
+        return $this->refresh($key, $default);
+    }
 
-        if (! is_null($model)) {
-            $model->castValue($this->casts[$key] ?? null);
+    /**
+     * Refresh the given key.
+     */
+    public function refresh(string $key, mixed $default = null): mixed
+    {
+        $model = $this->query()->firstWhere('key', '=', $key);
 
-            $this->offsetSet($key, $model->value);
-        }
+        $value = is_null($model)
+            ? $default
+            : $model->castValue($this->casts[$key] ?? null)->value;
+
+        $this->offsetSet($key, $value);
 
-        return $this->cache[$key] ?? $default;
+        return $value;
     }
 
     /**
@@ -94,7 +111,7 @@ public function get(string $key, mixed $default = null, bool $fresh = false): mi
      */
     public function set(string $key, mixed $value): mixed
     {
-        $model = $this->model()->newQuery()->firstOrNew(['key' => $key]);
+        $model = $this->query()->firstOrNew(['key' => $key]);
 
         $model->castValue($this->casts[$key] ?? null);
 
@@ -116,7 +133,7 @@ public function delete(string|array $keys): void
             $this->offsetUnset($key);
         }
 
-        $this->model()->newQuery()->whereIn('key', (array) $keys)->delete();
+        $this->query()->whereIn('key', (array) $keys)->delete();
     }
 
     /**
diff --git a/tests/Settings/SettingsTest.php b/tests/Settings/SettingsTest.php
new file mode 100644
index 00000000..03f03673
--- /dev/null
+++ b/tests/Settings/SettingsTest.php
@@ -0,0 +1,70 @@
+<?php
+
+namespace Cone\Root\Tests\Settings;
+
+use Cone\Root\Settings\Registry;
+use Cone\Root\Settings\Repository;
+use Cone\Root\Tests\TestCase;
+use Illuminate\Support\Facades\Date;
+
+class SettingsTest extends TestCase
+{
+    protected Registry $registry;
+
+    public function setUp(): void
+    {
+        parent::setUp();
+
+        $this->registry = new Registry(new Repository);
+    }
+
+    public function test_setting_can_be_set(): void
+    {
+        $value = $this->registry->set('foo', 'bar');
+        $this->assertSame('bar', $value);
+
+        $this->assertDatabaseHas('root_settings', ['key' => 'foo', 'value' => 'bar']);
+    }
+
+    public function test_setting_value_with_cast(): void
+    {
+        $this->registry->cast('ran_at', 'datetime');
+
+        $value = $this->registry->get('ran_at');
+        $this->assertNull($value);
+
+        $value = $this->registry->set('ran_at', $now = Date::now());
+        $this->assertSame(
+            $now->__toString(),
+            $this->registry->query()->firstWhere('key', 'ran_at')->value
+        );
+
+        $this->assertSame(
+            $now->__toString(),
+            $this->registry->get('ran_at')->__toString()
+        );
+    }
+
+    public function test_setting_can_be_get(): void
+    {
+        $value = $this->registry->get('foo');
+        $this->assertNull($value);
+
+        $value = $this->registry->get('foo', 'bar');
+        $this->assertSame('bar', $value);
+    }
+
+    public function test_setting_can_be_deleted(): void
+    {
+        $value = $this->registry->set('foo', 'bar');
+        $this->assertSame('bar', $value);
+
+        $value = $this->registry->get('foo');
+        $this->assertSame('bar', $value);
+
+        $this->registry->delete('foo');
+        $this->assertNull($this->registry->get('foo'));
+
+        $this->assertDatabaseMissing('root_settings', ['key' => 'foo']);
+    }
+}