diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml
new file mode 100644
index 0000000..47d95c4
--- /dev/null
+++ b/.github/workflows/cd.yml
@@ -0,0 +1,17 @@
+name: Laravel.php Setting Menu
+
+on: push
+
+jobs:
+ cd:
+ runs-on: ubuntu-latest
+ steps:
+ - name: cd
+ uses: tripteki/cd-package@1.0.0
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+ repotoken: ${{ secrets.REPOSITORY_TOKEN }}
+ repouser: tripteki
+ repository: https://packagist.org
+ language: php
+ artifact: composer.json
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..061b3a5
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2022 Trip Teknologi
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..293d45b
--- /dev/null
+++ b/README.md
@@ -0,0 +1,90 @@
+
Setting Menu
+
+This package provides implementation of setting menu in repository pattern for Lumen and Laravel besides REST API starterpack of admin management with no intervention to codebase and keep clean.
+
+Getting Started
+---
+
+Installation :
+
+```
+$ composer require tripteki/laravelphp-setting-menu
+```
+
+How to use it :
+
+- Put `Tripteki\SettingMenu\Providers\SettingMenuServiceProvider` to service provider configuration list.
+
+- Put `Tripteki\SettingMenu\Providers\SettingMenuServiceProvider::ignoreMigrations()` into `register` provider, then publish migrations file into your project's directory with running (optionally) :
+
+```
+php artisan vendor:publish --tag=tripteki-laravelphp-setting-menu-migrations
+```
+
+- Migrate.
+
+```
+$ php artisan migrate
+```
+
+- Publish tests file into your project's directory with running (optionally) :
+
+```
+php artisan vendor:publish --tag=tripteki-laravelphp-setting-menu-tests
+```
+
+- Sample :
+
+```php
+use Tripteki\SettingMenu\Contracts\Repository\Admin\ISettingMenuDetailRepository;
+use Tripteki\SettingMenu\Contracts\Repository\ISettingMenuRepository;
+
+$menuRepository = app(ISettingMenuDetailRepository::class);
+
+// $menuRepository->create("headernavbar", "home", [ "category" => "base", "icon" => "md-home", "title" => "Home", "description" => "Home Page", ]); //
+// $menuRepository->delete("headernavbar", "home"); //
+// $menuRepository->update("headernavbar", "home", [ "icon" => "fa-home", ]); //
+// $menuRepository->get("headernavbar", "home"); //
+// $menuRepository->all("headernavbar"); //
+
+$repository = app(ISettingMenuRepository::class);
+// $repository->setUser(...); //
+// $repository->getUser(); //
+
+// $repository->move(null, "dashboard", "sidenavbar"); //
+// $repository->move(null, "home", "headernavbar"); //
+// $repository->move(null, "profile", "headernavbar"); //
+// $repository->move(null, "about", "headernavbar"); //
+// $repository->move(null, "media", "sidenavbar"); //
+// $repository->move("media", null, "sidenavbar"); //
+// $repository->move("about", null, "headernavbar"); //
+// $repository->move("profile", null, "headernavbar"); //
+// $repository->move("home", null, "headernavbar"); //
+// $repository->move("dashboard", null, "sidenavbar"); //
+// $repository->move("media", "dashboard", "sidenavbar"); //
+// $repository->move("profile", "home", "headernavbar"); //
+// $repository->move("about", "home.profile", "headernavbar"); //
+// $repository->move("home.profile.about", "home", "headernavbar"); //
+// $repository->all(); //
+```
+
+- Generate swagger files into your project's directory with putting this into your annotation configuration (optionally) :
+
+```
+base_path("app/Http/Controllers/SettingMenu")
+```
+
+```
+base_path("app/Http/Controllers/Admin/SettingMenu")
+```
+
+Usage
+---
+
+`php artisan adminer:install:setting:menu`
+
+Author
+---
+
+- Trip Teknologi ([@tripteki](https://linkedin.com/company/tripteki))
+- Hasby Maulana ([@hsbmaulana](https://linkedin.com/in/hsbmaulana))
diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000..8a39991
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,58 @@
+{
+ "name": "tripteki/laravelphp-setting-menu",
+ "version": "1.0.0",
+ "description": "Trip Teknologi's Laravel.php Setting Menu",
+
+ "readme": "README.md",
+ "license": "MIT",
+ "authors": [ { "name": "Trip Teknologi", "email": "tripteki.company@gmail.com" } ],
+ "homepage": "https://github.com/tripteki/laravelphp-setting-menu",
+ "support": { "issues": "https://github.com/tripteki/laravelphp-setting-menu/issues" },
+
+ "require": {
+
+ "php": "^8.0.2",
+
+ "tripteki/laravelphp-repository": "^1.0.0",
+ "tripteki/laravelphp-helpers": "^1.0.0",
+ "tripteki/laravelphp-adminer": "^1.0.0",
+ "tripteki/laravelphp-setting": "^1.0.0",
+ "tripteki/laravelphp-import-export": "^1.0.0",
+ "tripteki/laravelphp-request-response-query": "^1.0.0",
+
+ "awobaz/compoships": "^2.0"
+ },
+
+ "require-dev": {},
+
+ "suggest": {
+
+ "laravel/lumen-framework": "Required when using lumen framework (^9.0).",
+ "laravel/framework": "Required when using laravel framework (^9.0)."
+ },
+
+ "autoload": {
+
+ "psr-4": {
+
+ "Tripteki\\SettingMenu\\": "src/"
+ }
+ },
+
+ "autoload-dev": {},
+
+ "extra": {
+
+ "laravel": {
+
+ "dont-discover": [],
+
+ "providers": [
+
+ "Tripteki\\SettingMenu\\Providers\\SettingMenuServiceProvider"
+ ],
+
+ "aliases": []
+ }
+ }
+}
diff --git a/database/migrations/2023_01_15_000000_create_menus_table.php b/database/migrations/2023_01_15_000000_create_menus_table.php
new file mode 100644
index 0000000..018c484
--- /dev/null
+++ b/database/migrations/2023_01_15_000000_create_menus_table.php
@@ -0,0 +1,35 @@
+string("menuable_type");
+ $table->string("menuable_id");
+
+ $table->string("category");
+ $table->string("icon");
+ $table->string("title");
+ $table->text("description");
+
+ $table->primary([ "menuable_type", "menuable_id", ]);
+ });
+ }
+
+ /**
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists("menus");
+ }
+};
diff --git a/src/Console/Commands/InstallCommand.php b/src/Console/Commands/InstallCommand.php
new file mode 100644
index 0000000..deb87be
--- /dev/null
+++ b/src/Console/Commands/InstallCommand.php
@@ -0,0 +1,79 @@
+helper = $helper;
+ }
+
+ /**
+ * @return int
+ */
+ public function handle()
+ {
+ $this->call("adminer:install:setting");
+ $this->installStack();
+
+ return 0;
+ }
+
+ /**
+ * @return int|null
+ */
+ protected function installStack()
+ {
+ (new Filesystem)->ensureDirectoryExists(base_path("routes/user"));
+ (new Filesystem)->ensureDirectoryExists(base_path("routes/admin"));
+ (new Filesystem)->copy(__DIR__."/../../../stubs/routes/user/setting/menu.php", base_path("routes/user/setting/menu.php"));
+ (new Filesystem)->copy(__DIR__."/../../../stubs/routes/admin/setting/menu.php", base_path("routes/admin/setting/menu.php"));
+ $this->helper->putRoute("api.php", "user/setting/menu.php");
+ $this->helper->putRoute("api.php", "admin/setting/menu.php");
+
+ (new Filesystem)->ensureDirectoryExists(app_path("Http/Controllers/Setting/Menu"));
+ (new Filesystem)->copyDirectory(__DIR__."/../../../stubs/app/Http/Controllers/Setting/Menu", app_path("Http/Controllers/Setting/Menu"));
+ (new Filesystem)->ensureDirectoryExists(app_path("Http/Requests/Settings/Menus"));
+ (new Filesystem)->copyDirectory(__DIR__."/../../../stubs/app/Http/Requests/Settings/Menus", app_path("Http/Requests/Settings/Menus"));
+ (new Filesystem)->ensureDirectoryExists(app_path("Http/Controllers/Admin/Setting/Menu"));
+ (new Filesystem)->copyDirectory(__DIR__."/../../../stubs/app/Http/Controllers/Admin/Setting/Menu", app_path("Http/Controllers/Admin/Setting/Menu"));
+ (new Filesystem)->ensureDirectoryExists(app_path("Imports/Settings/Menus"));
+ (new Filesystem)->copyDirectory(__DIR__."/../../../stubs/app/Imports/Settings/Menus", app_path("Imports/Settings/Menus"));
+ (new Filesystem)->ensureDirectoryExists(app_path("Exports/Settings/Menus"));
+ (new Filesystem)->copyDirectory(__DIR__."/../../../stubs/app/Exports/Settings/Menus", app_path("Exports/Settings/Menus"));
+ (new Filesystem)->ensureDirectoryExists(app_path("Http/Requests/Admin/Settings/Menus"));
+ (new Filesystem)->copyDirectory(__DIR__."/../../../stubs/app/Http/Requests/Admin/Settings/Menus", app_path("Http/Requests/Admin/Settings/Menus"));
+ (new Filesystem)->ensureDirectoryExists(app_path("Http/Responses"));
+
+ $this->helper->putTrait($this->helper->classToFile(get_class(app(AuthModelContract::class))), \Tripteki\SettingMenu\Traits\MenuTrait::class);
+
+ $this->info("Adminer Setting Menu scaffolding installed successfully.");
+ }
+};
diff --git a/src/Contracts/Repository/Admin/ISettingMenuDetailRepository.php b/src/Contracts/Repository/Admin/ISettingMenuDetailRepository.php
new file mode 100644
index 0000000..4783312
--- /dev/null
+++ b/src/Contracts/Repository/Admin/ISettingMenuDetailRepository.php
@@ -0,0 +1,14 @@
+data = $data;
+ }
+};
diff --git a/src/Events/Placing.php b/src/Events/Placing.php
new file mode 100644
index 0000000..80704b1
--- /dev/null
+++ b/src/Events/Placing.php
@@ -0,0 +1,24 @@
+data = $data;
+ }
+};
diff --git a/src/Events/Unplaced.php b/src/Events/Unplaced.php
new file mode 100644
index 0000000..941873b
--- /dev/null
+++ b/src/Events/Unplaced.php
@@ -0,0 +1,24 @@
+data = $data;
+ }
+};
diff --git a/src/Events/Unplacing.php b/src/Events/Unplacing.php
new file mode 100644
index 0000000..d9f8e15
--- /dev/null
+++ b/src/Events/Unplacing.php
@@ -0,0 +1,24 @@
+data = $data;
+ }
+};
diff --git a/src/Http/Resources/MenuResource.php b/src/Http/Resources/MenuResource.php
new file mode 100644
index 0000000..63a88a6
--- /dev/null
+++ b/src/Http/Resources/MenuResource.php
@@ -0,0 +1,25 @@
+ $this->whenCounted("menusHeadernavbar"),
+ "menus_sidenavbar_count" => $this->whenCounted("menusSidenavbar"),
+ "menus_headernavbar" => $this->whenLoaded("menusHeadernavbar"),
+ "menus_sidenavbar" => $this->whenLoaded("menusSidenavbar"),
+ ];
+ }
+};
diff --git a/src/Listeners/.gitkeep b/src/Listeners/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/src/Models/AbstractMenu.php b/src/Models/AbstractMenu.php
new file mode 100644
index 0000000..21009fc
--- /dev/null
+++ b/src/Models/AbstractMenu.php
@@ -0,0 +1,92 @@
+key, MenuStrictScope::space(null)), ".").".".Str::afterLast($this->key, "."));
+ }
+
+ /**
+ * @return int
+ */
+ public function getMenusCountAttribute()
+ {
+ return $this->menus()->count();
+ }
+
+ /**
+ * @return \Illuminate\Database\Eloquent\Relations\Relation
+ */
+ public function menus()
+ {
+ $key = foreignKeyName(app(AuthModelContract::class));
+
+ return $this->hasMany(static::class, [ "value", $key, ], [ "key", $key, ])->withoutGlobalScope(MenuStrictScope::class)->with("menus");
+ }
+};
diff --git a/src/Models/Admin/Detailmenu.php b/src/Models/Admin/Detailmenu.php
new file mode 100644
index 0000000..a10ac53
--- /dev/null
+++ b/src/Models/Admin/Detailmenu.php
@@ -0,0 +1,72 @@
+menuable_type, "\\"));
+ }
+
+ /**
+ * @return string
+ */
+ public function getMenuAttribute()
+ {
+ return Str::after(Str::replaceFirst(MenuStrictScope::space(null), "", $this->menuable_id), ".");
+ }
+
+ /**
+ * @return \Illuminate\Database\Eloquent\Relations\Relation
+ */
+ public function menuable()
+ {
+ return $this->morphTo();
+ }
+};
diff --git a/src/Models/Headernavbar.php b/src/Models/Headernavbar.php
new file mode 100644
index 0000000..22ac816
--- /dev/null
+++ b/src/Models/Headernavbar.php
@@ -0,0 +1,21 @@
+morphOne(Detailmenu::class, "menuable", null, null, "menus_id");
+ }
+};
diff --git a/src/Models/Sidenavbar.php b/src/Models/Sidenavbar.php
new file mode 100644
index 0000000..e507b70
--- /dev/null
+++ b/src/Models/Sidenavbar.php
@@ -0,0 +1,21 @@
+morphOne(Detailmenu::class, "menuable", null, null, "menus_id");
+ }
+};
diff --git a/src/Providers/SettingMenuServiceProvider.php b/src/Providers/SettingMenuServiceProvider.php
new file mode 100644
index 0000000..69dcde0
--- /dev/null
+++ b/src/Providers/SettingMenuServiceProvider.php
@@ -0,0 +1,99 @@
+ \Tripteki\SettingMenu\Repositories\Eloquent\Admin\SettingMenuDetailRepository::class,
+ \Tripteki\SettingMenu\Contracts\Repository\ISettingMenuRepository::class => \Tripteki\SettingMenu\Repositories\Eloquent\SettingMenuRepository::class,
+ ];
+
+ /**
+ * @var bool
+ */
+ public static $runsMigrations = true;
+
+ /**
+ * @return bool
+ */
+ public static function shouldRunMigrations()
+ {
+ return static::$runsMigrations;
+ }
+
+ /**
+ * @return void
+ */
+ public static function ignoreMigrations()
+ {
+ static::$runsMigrations = false;
+ }
+
+ /**
+ * @return void
+ */
+ public function boot()
+ {
+ parent::boot();
+
+ $this->registerPublishers();
+ $this->registerCommands();
+ $this->registerMigrations();
+ }
+
+ /**
+ * @return void
+ */
+ protected function registerCommands()
+ {
+ if (! $this->app->isProduction() && $this->app->runningInConsole()) {
+
+ $this->commands(
+ [
+ InstallCommand::class,
+ ]);
+ }
+ }
+
+ /**
+ * @return void
+ */
+ protected function registerMigrations()
+ {
+ if ($this->app->runningInConsole() && static::shouldRunMigrations()) {
+
+ $this->loadMigrationsFrom(__DIR__."/../../database/migrations");
+ }
+ }
+
+ /**
+ * @return void
+ */
+ protected function registerPublishers()
+ {
+ if (! static::shouldRunMigrations()) {
+
+ $this->publishes(
+ [
+ __DIR__."/../../database/migrations" => database_path("migrations"),
+ ],
+
+ "tripteki-laravelphp-setting-menu-migrations");
+ }
+
+ $this->publishes(
+ [
+ __DIR__."/../../stubs/tests/Feature/Setting/Menu/MenuTest.stub" => base_path("tests/Feature/Setting/Menu/MenuTest.php"),
+ ],
+
+ "tripteki-laravelphp-setting-menu-tests");
+ }
+};
diff --git a/src/Repositories/Eloquent/Admin/SettingMenuDetailRepository.php b/src/Repositories/Eloquent/Admin/SettingMenuDetailRepository.php
new file mode 100644
index 0000000..061f157
--- /dev/null
+++ b/src/Repositories/Eloquent/Admin/SettingMenuDetailRepository.php
@@ -0,0 +1,178 @@
+space) => Headernavbar::class,
+ (app(Sidenavbar::class)->space) => Sidenavbar::class,
+ ];
+
+ if ($menu) {
+
+ foreach ($contexts as $key => $value) {
+
+ $contexts[$key] = MenuStrictScope::space($key);
+ }
+ }
+
+ $type = @$contexts[$context] ?: null;
+
+ if ($type && ! $menu) {
+
+ return $type;
+ }
+ if ($type && $menu) {
+
+ return $type.".".$menu;
+
+ } else {
+
+ throw new \InvalidArgumentException("Menu type is not valid.");
+ }
+ }
+
+ /**
+ * @param int|string $type
+ * @param array $querystring
+ * @return mixed
+ */
+ public function all($type, $querystring = [])
+ {
+ $type = $this->type($type);
+ $querystringed =
+ [
+ "limit" => $querystring["limit"] ?? request()->query("limit", 10),
+ "current_page" => $querystring["current_page"] ?? request()->query("current_page", 1),
+ ];
+ extract($querystringed);
+
+ $content = QueryBuilder::for(Detailmenu::where("menuable_type", $type))->
+ defaultSort("category")->
+ allowedSorts([ "category", "icon", "title", "description", ])->
+ allowedFilters([ "category", "icon", "title", "description", ])->
+ paginate($limit, "*", "current_page", $current_page)->appends(empty($querystring) ? request()->query() : $querystringed);
+
+ return $content;
+ }
+
+ /**
+ * @param int|string $type
+ * @param int|string $identifier
+ * @param array $querystring
+ * @return mixed
+ */
+ public function get($type, $identifier, $querystring = [])
+ {
+ $identifier = $this->type($type, $identifier);
+ $type = $this->type($type);
+ $content = Detailmenu::where([ "menuable_type" => $type, "menuable_id" => $identifier, ])->firstOrFail();
+
+ return $content;
+ }
+
+ /**
+ * @param int|string $type
+ * @param int|string $identifier
+ * @param array $data
+ * @return mixed
+ */
+ public function update($type, $identifier, $data)
+ {
+ $identifier = $this->type($type, $identifier);
+ $type = $this->type($type);
+ $content = Detailmenu::where([ "menuable_type" => $type, "menuable_id" => $identifier, ])->firstOrFail();
+
+ DB::beginTransaction();
+
+ try {
+
+ $content->fill($data);
+
+ $content->save();
+
+ DB::commit();
+
+ } catch (Exception $exception) {
+
+ DB::rollback();
+ }
+
+ return $content;
+ }
+
+ /**
+ * @param int|string $type
+ * @param int|string $identifier
+ * @param array $data
+ * @return mixed
+ */
+ public function create($type, $identifier, $data)
+ {
+ $identifier = $this->type($type, $identifier);
+ $type = $this->type($type);
+ $content = null;
+
+ DB::beginTransaction();
+
+ try {
+
+ $content = Detailmenu::firstOrCreate(array_merge($data, [ "menuable_type" => $type, "menuable_id" => $identifier, ]));
+
+ DB::commit();
+
+ } catch (Exception $exception) {
+
+ DB::rollback();
+ }
+
+ return $content;
+ }
+
+ /**
+ * @param int|string $type
+ * @param int|string $identifier
+ * @return mixed
+ */
+ public function delete($type, $identifier)
+ {
+ $identifier = $this->type($type, $identifier);
+ $type = $this->type($type);
+ $content = Detailmenu::where([ "menuable_type" => $type, "menuable_id" => $identifier, ])->firstOrFail();
+
+ DB::beginTransaction();
+
+ try {
+
+ $content->delete();
+
+ DB::commit();
+
+ } catch (Exception $exception) {
+
+ DB::rollback();
+ }
+
+ return $content;
+ }
+};
diff --git a/src/Repositories/Eloquent/SettingMenuRepository.php b/src/Repositories/Eloquent/SettingMenuRepository.php
new file mode 100644
index 0000000..4e9fcbd
--- /dev/null
+++ b/src/Repositories/Eloquent/SettingMenuRepository.php
@@ -0,0 +1,203 @@
+setting = $setting;
+ $this->menudetail = $menudetail;
+ }
+
+ /**
+ * @return void
+ */
+ public function __destruct()
+ {}
+
+ /**
+ * @param string $type
+ * @param string|null $menu
+ * @return string
+ */
+ protected function menu($type, $menu = null)
+ {
+ $menu = $menu ? $this->menudetail->type($type, $menu) : $menu;
+
+ if ($menu) {
+
+ return $menu;
+
+ } else {
+
+ return null;
+ }
+ }
+
+ /**
+ * @param array $querystring
+ * @return mixed
+ */
+ public function all($querystring = [])
+ {
+ $user = $this->user; $content = null;
+
+ $content = $user->load([ "menusHeadernavbar", "menusSidenavbar", ])->loadCount([ "menusHeadernavbar", "menusSidenavbar", ]);
+
+ return new MenuResource($content);
+ }
+
+ /**
+ * @param string $type
+ * @param int|string $oldparent
+ * @param int|string $oldchild
+ * @param int|string $newparent
+ * @param int|string $newchild
+ * @return mixed
+ */
+ public function update($type, $oldparent, $oldchild, $newparent, $newchild)
+ {
+ $oldparent = $this->menu($type, $oldparent);
+ $oldchild = $this->menu($type, $oldchild);
+ $newparent = $this->menu($type, $newparent);
+ $newchild = $this->menu($type, $newchild);
+
+ $type = $this->menudetail->type($type);
+ $content = null;
+
+ DB::beginTransaction();
+
+ try {
+
+ $content = [ "user_id" => $this->user->id, ];
+
+ call_user_func($type."::"."withoutGlobalScopes")->where(array_merge($content, [ "key" => $oldchild, "value" => $oldparent, ]))->delete();
+ $content = call_user_func($type."::"."forceCreate", array_merge($content, [ "key" => $newchild, "value" => $newparent, ]));
+
+ DB::commit();
+
+ event(new Placed($content));
+
+ } catch (Exception $exception) {
+
+ DB::rollback();
+ }
+
+ return $content;
+ }
+
+ /**
+ * @param int|string $type
+ * @param int|string $identifier
+ * @return mixed
+ */
+ public function delete($type, $identifier)
+ {
+ $this->setting->setUser($this->getUser());
+
+ $identifier = $this->menudetail->type($type, $identifier);
+ $type = $this->menudetail->type($type);
+ $content = null;
+
+ DB::beginTransaction();
+
+ try {
+
+ $content = $this->setting->setdown($identifier);
+ $content->forceDelete();
+
+ DB::commit();
+
+ event(new Unplaced($content));
+
+ } catch (Exception $exception) {
+
+ DB::rollback();
+ }
+
+ return $content;
+ }
+
+ /**
+ * @param string $menu
+ * @param string $element
+ * @return mixed
+ */
+ public function attach($menu, $element)
+ {
+ $parentmenu = Str::replace(".".Str::afterLast($menu, "."), "", $menu);
+ $childmenu = $menu;
+ $content = null;
+
+ if ($parentmenu === $childmenu) $content = $this->update($element, null, null, null, $childmenu);
+ else $content = $this->update($element, $parentmenu, null, $parentmenu, $childmenu);
+
+ return $content;
+ }
+
+ /**
+ * @param string $menu
+ * @param string $element
+ * @return mixed
+ */
+ public function detach($menu, $element)
+ {
+ return $content = $this->delete($element, $menu);
+ }
+
+ /**
+ * @param string|null $from
+ * @param string|null $to
+ * @param string $element
+ * @return mixed
+ */
+ public function move(string|null $from, string|null $to, string $element)
+ {
+ $content = null;
+
+ if ($from && ! $to) {
+
+ return $content = $this->detach($from, $element);
+ }
+
+ if ($from && $to) {
+
+ $this->detach($from, $element);
+
+ $to = $to.".".Str::afterLast($from, ".");
+ }
+
+ return $content = $this->attach($to, $element);
+ }
+};
diff --git a/src/Repositories/QueryBuilder/.gitkeep b/src/Repositories/QueryBuilder/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/src/Scopes/MenuStrictScope.php b/src/Scopes/MenuStrictScope.php
new file mode 100644
index 0000000..608fb63
--- /dev/null
+++ b/src/Scopes/MenuStrictScope.php
@@ -0,0 +1,37 @@
+space."."."%");
+
+ $builder->where("key", "like", $space)->whereNull("value");
+ }
+};
diff --git a/src/Traits/MenuTrait.php b/src/Traits/MenuTrait.php
new file mode 100644
index 0000000..3f43265
--- /dev/null
+++ b/src/Traits/MenuTrait.php
@@ -0,0 +1,25 @@
+hasMany(Headernavbar::class);
+ }
+
+ /**
+ * @return \Illuminate\Database\Eloquent\Relations\Relation
+ */
+ public function menusSidenavbar()
+ {
+ return $this->hasMany(Sidenavbar::class);
+ }
+};
diff --git a/stubs/app/Exports/Settings/Menus/.gitkeep b/stubs/app/Exports/Settings/Menus/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/stubs/app/Http/Controllers/Admin/Setting/Menu/MenuAdminController.php b/stubs/app/Http/Controllers/Admin/Setting/Menu/MenuAdminController.php
new file mode 100644
index 0000000..f254660
--- /dev/null
+++ b/stubs/app/Http/Controllers/Admin/Setting/Menu/MenuAdminController.php
@@ -0,0 +1,339 @@
+menuRepository = $menuRepository;
+ }
+
+ /**
+ * @OA\Get(
+ * path="/admin/bars/{bar}/menus",
+ * tags={"Admin Menu"},
+ * summary="Index",
+ * @OA\Parameter(
+ * required=true,
+ * in="path",
+ * name="bar",
+ * schema={"type": "string", "enum": {"headernavbar", "sidenavbar"}},
+ * description="Menu's Bar."
+ * ),
+ * @OA\Parameter(
+ * required=false,
+ * in="query",
+ * name="limit",
+ * description="Menu's Pagination Limit."
+ * ),
+ * @OA\Parameter(
+ * required=false,
+ * in="query",
+ * name="current_page",
+ * description="Menu's Pagination Current Page."
+ * ),
+ * @OA\Parameter(
+ * required=false,
+ * in="query",
+ * name="order",
+ * description="Menu's Pagination Order."
+ * ),
+ * @OA\Parameter(
+ * required=false,
+ * in="query",
+ * name="filter[]",
+ * description="Menu's Pagination Filter."
+ * ),
+ * @OA\Response(
+ * response=200,
+ * description="Success."
+ * )
+ * )
+ *
+ * @param \App\Http\Requests\Admin\Settings\Menus\MenuIndexValidation $request
+ * @param string $bar
+ * @return \Illuminate\Http\JsonResponse
+ */
+ public function index(MenuIndexValidation $request, $bar)
+ {
+ $form = $request->validated();
+ $data = [];
+ $statecode = 200;
+
+ $data = $this->menuRepository->all($bar);
+
+ return iresponse($data, $statecode);
+ }
+
+ /**
+ * @OA\Get(
+ * path="/admin/bars/{bar}/menus/{menu}",
+ * tags={"Admin Menu"},
+ * summary="Show",
+ * @OA\Parameter(
+ * required=true,
+ * in="path",
+ * name="bar",
+ * schema={"type": "string", "enum": {"headernavbar", "sidenavbar"}},
+ * description="Menu's Bar."
+ * ),
+ * @OA\Parameter(
+ * required=true,
+ * in="path",
+ * name="menu",
+ * description="Menu's Menu."
+ * ),
+ * @OA\Response(
+ * response=200,
+ * description="Success."
+ * ),
+ * @OA\Response(
+ * response=404,
+ * description="Not Found."
+ * )
+ * )
+ *
+ * @param \App\Http\Requests\Admin\Settings\Menus\MenuShowValidation $request
+ * @param string $bar
+ * @param string $menu
+ * @return \Illuminate\Http\JsonResponse
+ */
+ public function show(MenuShowValidation $request, $bar, $menu)
+ {
+ $form = $request->validated();
+ $data = [];
+ $statecode = 200;
+
+ $data = $this->menuRepository->get($bar, $menu);
+
+ return iresponse($data, $statecode);
+ }
+
+ /**
+ * @OA\Post(
+ * path="/admin/bars/{bar}/menus",
+ * tags={"Admin Menu"},
+ * summary="Store",
+ * @OA\Parameter(
+ * required=true,
+ * in="path",
+ * name="bar",
+ * schema={"type": "string", "enum": {"headernavbar", "sidenavbar"}},
+ * description="Menu's Bar."
+ * ),
+ * @OA\RequestBody(
+ * @OA\MediaType(
+ * mediaType="application/x-www-form-urlencoded",
+ * @OA\Schema(
+ * @OA\Property(
+ * property="menu",
+ * type="string",
+ * description="Menu's Menu."
+ * ),
+ * @OA\Property(
+ * property="category",
+ * type="string",
+ * description="Menu's Category."
+ * ),
+ * @OA\Property(
+ * property="icon",
+ * type="string",
+ * description="Menu's Icon."
+ * ),
+ * @OA\Property(
+ * property="title",
+ * type="string",
+ * description="Menu's Title."
+ * ),
+ * @OA\Property(
+ * property="description",
+ * type="string",
+ * description="Menu's Description."
+ * )
+ * )
+ * )
+ * ),
+ * @OA\Response(
+ * response=201,
+ * description="Created."
+ * ),
+ * @OA\Response(
+ * response=422,
+ * description="Unprocessable Entity."
+ * )
+ * )
+ *
+ * @param \App\Http\Requests\Admin\Settings\Menus\MenuStoreValidation $request
+ * @param string $bar
+ * @return \Illuminate\Http\JsonResponse
+ */
+ public function store(MenuStoreValidation $request, $bar)
+ {
+ $form = $request->validated(); $menu = $form["menu"]; $form = collect($form)->except([ "bar", "menu", ])->toArray();
+ $data = [];
+ $statecode = 202;
+
+ $data = $this->menuRepository->create($bar, $menu, $form);
+
+ if ($data) {
+
+ $statecode = 201;
+ }
+
+ return iresponse($data, $statecode);
+ }
+
+ /**
+ * @OA\Put(
+ * path="/admin/bars/{bar}/menus/{menu}",
+ * tags={"Admin Menu"},
+ * summary="Update",
+ * @OA\Parameter(
+ * required=true,
+ * in="path",
+ * name="bar",
+ * schema={"type": "string", "enum": {"headernavbar", "sidenavbar"}},
+ * description="Menu's Bar."
+ * ),
+ * @OA\Parameter(
+ * required=true,
+ * in="path",
+ * name="menu",
+ * description="Menu's Menu."
+ * ),
+ * @OA\RequestBody(
+ * @OA\MediaType(
+ * mediaType="application/x-www-form-urlencoded",
+ * @OA\Schema(
+ * @OA\Property(
+ * property="category",
+ * type="string",
+ * description="Menu's Category."
+ * ),
+ * @OA\Property(
+ * property="icon",
+ * type="string",
+ * description="Menu's Icon."
+ * ),
+ * @OA\Property(
+ * property="title",
+ * type="string",
+ * description="Menu's Title."
+ * ),
+ * @OA\Property(
+ * property="description",
+ * type="string",
+ * description="Menu's Description."
+ * )
+ * )
+ * )
+ * ),
+ * @OA\Response(
+ * response=201,
+ * description="Created."
+ * ),
+ * @OA\Response(
+ * response=422,
+ * description="Unprocessable Entity."
+ * ),
+ * @OA\Response(
+ * response=404,
+ * description="Not Found."
+ * )
+ * )
+ *
+ * @param \App\Http\Requests\Admin\Settings\Menus\MenuUpdateValidation $request
+ * @param string $bar
+ * @param string $menu
+ * @return \Illuminate\Http\JsonResponse
+ */
+ public function update(MenuUpdateValidation $request, $bar, $menu)
+ {
+ $form = collect($request->validated())->except([ "bar", "menu", ])->toArray();
+ $data = [];
+ $statecode = 202;
+
+ $data = $this->menuRepository->update($bar, $menu, $form);
+
+ if ($data) {
+
+ $statecode = 201;
+ }
+
+ return iresponse($data, $statecode);
+ }
+
+ /**
+ * @OA\Delete(
+ * path="/admin/bars/{bar}/menus/{menu}",
+ * tags={"Admin Menu"},
+ * summary="Destroy",
+ * @OA\Parameter(
+ * required=true,
+ * in="path",
+ * name="bar",
+ * schema={"type": "string", "enum": {"headernavbar", "sidenavbar"}},
+ * description="Menu's Bar."
+ * ),
+ * @OA\Parameter(
+ * required=true,
+ * in="path",
+ * name="menu",
+ * description="Menu's Menu."
+ * ),
+ * @OA\Response(
+ * response=200,
+ * description="Success."
+ * ),
+ * @OA\Response(
+ * response=422,
+ * description="Unprocessable Entity."
+ * ),
+ * @OA\Response(
+ * response=404,
+ * description="Not Found."
+ * )
+ * )
+ *
+ * @param \App\Http\Requests\Admin\Settings\Menus\MenuDestroyValidation $request
+ * @param string $bar
+ * @param string $menu
+ * @return \Illuminate\Http\JsonResponse
+ */
+ public function destroy(MenuDestroyValidation $request, $bar, $menu)
+ {
+ $form = $request->validated();
+ $data = [];
+ $statecode = 202;
+
+ $data = $this->menuRepository->delete($bar, $menu);
+
+ if ($data) {
+
+ $statecode = 200;
+ }
+
+ return iresponse($data, $statecode);
+ }
+};
diff --git a/stubs/app/Http/Controllers/Setting/Menu/MenuController.php b/stubs/app/Http/Controllers/Setting/Menu/MenuController.php
new file mode 100644
index 0000000..f80a2de
--- /dev/null
+++ b/stubs/app/Http/Controllers/Setting/Menu/MenuController.php
@@ -0,0 +1,240 @@
+menuRepository = $menuRepository;
+ }
+
+ /**
+ * @OA\Get(
+ * path="/menus",
+ * tags={"Menus"},
+ * summary="Index",
+ * security={{ "bearerAuth": {} }},
+ * @OA\Response(
+ * response=200,
+ * description="Success."
+ * )
+ * )
+ *
+ * @param \Illuminate\Http\Request $request
+ * @return \Illuminate\Http\JsonResponse
+ */
+ public function index(Request $request)
+ {
+ $data = [];
+ $statecode = 200;
+
+ $this->menuRepository->setUser($request->user());
+
+ $data = $this->menuRepository->all();
+
+ return iresponse($data, $statecode);
+ }
+
+ /**
+ * @OA\Post(
+ * path="/menus",
+ * tags={"Menus"},
+ * summary="Store",
+ * security={{ "bearerAuth": {} }},
+ * @OA\RequestBody(
+ * @OA\MediaType(
+ * mediaType="application/x-www-form-urlencoded",
+ * @OA\Schema(
+ * @OA\Property(
+ * property="bar",
+ * type="string",
+ * schema={"type": "string", "enum": {"headernavbar", "sidenavbar"}},
+ * description="Menu's Bar."
+ * ),
+ * @OA\Property(
+ * property="menu",
+ * type="string",
+ * description="Menu's Menu."
+ * )
+ * )
+ * )
+ * ),
+ * @OA\Response(
+ * response=201,
+ * description="Created."
+ * ),
+ * @OA\Response(
+ * response=422,
+ * description="Unprocessable Entity."
+ * )
+ * )
+ *
+ * @param \App\Http\Requests\Settings\Menus\MenuStoreValidation $request
+ * @return \Illuminate\Http\JsonResponse
+ */
+ public function store(MenuStoreValidation $request)
+ {
+ $form = $request->validated();
+ $data = [];
+ $statecode = 202;
+
+ $this->menuRepository->setUser($request->user());
+
+ if ($this->menuRepository->getUser()) {
+
+ $data = $this->menuRepository->move(null, $form["menu"], $form["bar"]);
+
+ if ($data) {
+
+ $statecode = 201;
+ }
+ }
+
+ return iresponse($data, $statecode);
+ }
+
+ /**
+ * @OA\Put(
+ * path="/menus",
+ * tags={"Menus"},
+ * summary="Update",
+ * security={{ "bearerAuth": {} }},
+ * @OA\RequestBody(
+ * @OA\MediaType(
+ * mediaType="application/x-www-form-urlencoded",
+ * @OA\Schema(
+ * @OA\Property(
+ * property="bar",
+ * type="string",
+ * schema={"type": "string", "enum": {"headernavbar", "sidenavbar"}},
+ * description="Menu's Bar."
+ * ),
+ * @OA\Property(
+ * property="menu_from",
+ * type="string",
+ * description="Menu's Menu From."
+ * ),
+ * @OA\Property(
+ * property="menu_to",
+ * type="string",
+ * description="Menu's Menu To."
+ * )
+ * )
+ * )
+ * ),
+ * @OA\Response(
+ * response=201,
+ * description="Created."
+ * ),
+ * @OA\Response(
+ * response=422,
+ * description="Unprocessable Entity."
+ * ),
+ * @OA\Response(
+ * response=404,
+ * description="Not Found."
+ * )
+ * )
+ *
+ * @param \App\Http\Requests\Settings\Menus\MenuUpdateValidation $request
+ * @return \Illuminate\Http\JsonResponse
+ */
+ public function update(MenuUpdateValidation $request)
+ {
+ $form = $request->validated();
+ $data = [];
+ $statecode = 202;
+
+ $this->menuRepository->setUser($request->user());
+
+ if ($this->menuRepository->getUser()) {
+
+ $data = $this->menuRepository->move($form["menu_from"], $form["menu_to"], $form["bar"]);
+
+ if ($data) {
+
+ $statecode = 201;
+ }
+ }
+
+ return iresponse($data, $statecode);
+ }
+
+ /**
+ * @OA\Delete(
+ * path="/menus/{bar}/{menu}",
+ * tags={"Menus"},
+ * summary="Destroy",
+ * security={{ "bearerAuth": {} }},
+ * @OA\Parameter(
+ * required=true,
+ * in="path",
+ * name="bar",
+ * schema={"type": "string", "enum": {"headernavbar", "sidenavbar"}},
+ * description="Menu's Bar."
+ * ),
+ * @OA\Parameter(
+ * required=true,
+ * in="path",
+ * name="menu",
+ * description="Menu's Menu."
+ * ),
+ * @OA\Response(
+ * response=200,
+ * description="Success."
+ * ),
+ * @OA\Response(
+ * response=422,
+ * description="Unprocessable Entity."
+ * ),
+ * @OA\Response(
+ * response=404,
+ * description="Not Found."
+ * )
+ * )
+ *
+ * @param \App\Http\Requests\Settings\Menus\MenuDestroyValidation $request
+ * @param string $bar
+ * @param string $menu
+ * @return \Illuminate\Http\JsonResponse
+ */
+ public function destroy(MenuDestroyValidation $request, $bar, $menu)
+ {
+ $form = $request->validated();
+ $data = [];
+ $statecode = 202;
+
+ $this->menuRepository->setUser($request->user());
+
+ if ($this->menuRepository->getUser()) {
+
+ $data = $this->menuRepository->move($menu, null, $bar);
+
+ if ($data) {
+
+ $statecode = 200;
+ }
+ }
+
+ return iresponse($data, $statecode);
+ }
+};
diff --git a/stubs/app/Http/Requests/Admin/Settings/Menus/MenuDestroyValidation.php b/stubs/app/Http/Requests/Admin/Settings/Menus/MenuDestroyValidation.php
new file mode 100644
index 0000000..da3eb95
--- /dev/null
+++ b/stubs/app/Http/Requests/Admin/Settings/Menus/MenuDestroyValidation.php
@@ -0,0 +1,61 @@
+ $this->route("bar"),
+ "menu" => MenuStrictScope::space($this->route("bar").".".$this->route("menu")),
+ ];
+ }
+
+ /**
+ * @return void
+ */
+ protected function postValidation()
+ {
+ return [
+
+ "menu" => Str::replaceFirst(MenuStrictScope::space($this->route("bar")."."), "", $this->route("menu")),
+ ];
+ }
+
+ /**
+ * @return bool
+ */
+ public function authorize()
+ {
+ return true;
+ }
+
+ /**
+ * @return array
+ */
+ public function rules()
+ {
+ return [
+
+ "bar" => "required|string|in:headernavbar,sidenavbar",
+
+ "menu" => [
+
+ "required",
+ "string",
+ Rule::exists(Detailmenu::class, "menuable_id"),
+ ],
+ ];
+ }
+};
diff --git a/stubs/app/Http/Requests/Admin/Settings/Menus/MenuIndexValidation.php b/stubs/app/Http/Requests/Admin/Settings/Menus/MenuIndexValidation.php
new file mode 100644
index 0000000..290884c
--- /dev/null
+++ b/stubs/app/Http/Requests/Admin/Settings/Menus/MenuIndexValidation.php
@@ -0,0 +1,38 @@
+ $this->route("bar"),
+ ];
+ }
+
+ /**
+ * @return bool
+ */
+ public function authorize()
+ {
+ return true;
+ }
+
+ /**
+ * @return array
+ */
+ public function rules()
+ {
+ return [
+
+ "bar" => "required|string|in:headernavbar,sidenavbar",
+ ];
+ }
+};
diff --git a/stubs/app/Http/Requests/Admin/Settings/Menus/MenuShowValidation.php b/stubs/app/Http/Requests/Admin/Settings/Menus/MenuShowValidation.php
new file mode 100644
index 0000000..64cf8c1
--- /dev/null
+++ b/stubs/app/Http/Requests/Admin/Settings/Menus/MenuShowValidation.php
@@ -0,0 +1,61 @@
+ $this->route("bar"),
+ "menu" => MenuStrictScope::space($this->route("bar").".".$this->route("menu")),
+ ];
+ }
+
+ /**
+ * @return void
+ */
+ protected function postValidation()
+ {
+ return [
+
+ "menu" => Str::replaceFirst(MenuStrictScope::space($this->route("bar")."."), "", $this->route("menu")),
+ ];
+ }
+
+ /**
+ * @return bool
+ */
+ public function authorize()
+ {
+ return true;
+ }
+
+ /**
+ * @return array
+ */
+ public function rules()
+ {
+ return [
+
+ "bar" => "required|string|in:headernavbar,sidenavbar",
+
+ "menu" => [
+
+ "required",
+ "string",
+ Rule::exists(Detailmenu::class, "menuable_id"),
+ ],
+ ];
+ }
+};
diff --git a/stubs/app/Http/Requests/Admin/Settings/Menus/MenuStoreValidation.php b/stubs/app/Http/Requests/Admin/Settings/Menus/MenuStoreValidation.php
new file mode 100644
index 0000000..0802a02
--- /dev/null
+++ b/stubs/app/Http/Requests/Admin/Settings/Menus/MenuStoreValidation.php
@@ -0,0 +1,68 @@
+ $this->route("bar"),
+ "menu" => MenuStrictScope::space($this->route("bar").".".$this->input("menu")),
+ ];
+ }
+
+ /**
+ * @return void
+ */
+ protected function postValidation()
+ {
+ return [
+
+ "menu" => Str::replaceFirst(MenuStrictScope::space($this->route("bar")."."), "", $this->input("menu")),
+ ];
+ }
+
+ /**
+ * @return bool
+ */
+ public function authorize()
+ {
+ return true;
+ }
+
+ /**
+ * @return array
+ */
+ public function rules()
+ {
+ return [
+
+ "bar" => "required|string|in:headernavbar,sidenavbar",
+
+ "menu" => [
+
+ "required",
+ "string",
+ "lowercase",
+ "max:127",
+ Rule::unique(Detailmenu::class, "menuable_id"),
+ ],
+
+ "category" => "required|string|max:127",
+ "icon" => "required|string|lowercase|max:127|regex:/^[a-z\-]+$/",
+ "title" => "required|string|max:127",
+ "description" => "required|string|max:65535",
+ ];
+ }
+};
diff --git a/stubs/app/Http/Requests/Admin/Settings/Menus/MenuUpdateValidation.php b/stubs/app/Http/Requests/Admin/Settings/Menus/MenuUpdateValidation.php
new file mode 100644
index 0000000..9d86484
--- /dev/null
+++ b/stubs/app/Http/Requests/Admin/Settings/Menus/MenuUpdateValidation.php
@@ -0,0 +1,68 @@
+ $this->route("bar"),
+ "menu" => MenuStrictScope::space($this->route("bar").".".$this->route("menu")),
+ ];
+ }
+
+ /**
+ * @return void
+ */
+ protected function postValidation()
+ {
+ return [
+
+ "menu" => Str::replaceFirst(MenuStrictScope::space($this->route("bar")."."), "", $this->route("menu")),
+ ];
+ }
+
+ /**
+ * @return bool
+ */
+ public function authorize()
+ {
+ return true;
+ }
+
+ /**
+ * @return array
+ */
+ public function rules()
+ {
+ return [
+
+ "bar" => "required|string|in:headernavbar,sidenavbar",
+
+ "menu" => [
+
+ "required",
+ "string",
+ "lowercase",
+ "max:127",
+ Rule::exists(Detailmenu::class, "menuable_id"),
+ ],
+
+ "category" => "required|string|max:127",
+ "icon" => "required|string|lowercase|max:127|regex:/^[a-z\-]+$/",
+ "title" => "required|string|max:127",
+ "description" => "required|string|max:65535",
+ ];
+ }
+};
diff --git a/stubs/app/Http/Requests/Settings/Menus/MenuDestroyValidation.php b/stubs/app/Http/Requests/Settings/Menus/MenuDestroyValidation.php
new file mode 100644
index 0000000..a5dc13a
--- /dev/null
+++ b/stubs/app/Http/Requests/Settings/Menus/MenuDestroyValidation.php
@@ -0,0 +1,60 @@
+ $this->route("bar"),
+ "menu" => Str::of($this->route("menu"))->explode(".")->map(fn ($menu, $key) => MenuStrictScope::space($this->route("bar").".".$menu))->toArray(),
+ ];
+ }
+
+ /**
+ * @return void
+ */
+ protected function postValidation()
+ {
+ return [
+
+ "menu" => collect($this->route("menu"))->map(fn ($menu, $key) => Str::replaceFirst(MenuStrictScope::space($this->route("bar")."."), "", $menu))->implode("."),
+ ];
+ }
+
+ /**
+ * @return bool
+ */
+ public function authorize()
+ {
+ return true;
+ }
+
+ /**
+ * @return array
+ */
+ public function rules()
+ {
+ return [
+
+ "bar" => "required|string|in:headernavbar,sidenavbar",
+
+ "menu" => "required|array",
+ "menu.*" => [
+
+ Rule::exists(Detailmenu::class, "menuable_id"),
+ ],
+ ];
+ }
+};
diff --git a/stubs/app/Http/Requests/Settings/Menus/MenuStoreValidation.php b/stubs/app/Http/Requests/Settings/Menus/MenuStoreValidation.php
new file mode 100644
index 0000000..4364918
--- /dev/null
+++ b/stubs/app/Http/Requests/Settings/Menus/MenuStoreValidation.php
@@ -0,0 +1,59 @@
+ Str::of($this->input("menu"))->explode(".")->map(fn ($menu, $key) => MenuStrictScope::space($this->input("bar").".".$menu))->toArray(),
+ ];
+ }
+
+ /**
+ * @return void
+ */
+ protected function postValidation()
+ {
+ return [
+
+ "menu" => collect($this->input("menu"))->map(fn ($menu, $key) => Str::replaceFirst(MenuStrictScope::space($this->input("bar")."."), "", $menu))->implode("."),
+ ];
+ }
+
+ /**
+ * @return bool
+ */
+ public function authorize()
+ {
+ return true;
+ }
+
+ /**
+ * @return array
+ */
+ public function rules()
+ {
+ return [
+
+ "bar" => "required|string|in:headernavbar,sidenavbar",
+
+ "menu" => "required|array",
+ "menu.*" => [
+
+ Rule::exists(Detailmenu::class, "menuable_id"),
+ ],
+ ];
+ }
+};
diff --git a/stubs/app/Http/Requests/Settings/Menus/MenuUpdateValidation.php b/stubs/app/Http/Requests/Settings/Menus/MenuUpdateValidation.php
new file mode 100644
index 0000000..61deefd
--- /dev/null
+++ b/stubs/app/Http/Requests/Settings/Menus/MenuUpdateValidation.php
@@ -0,0 +1,67 @@
+ Str::of($this->input("menu_from"))->explode(".")->map(fn ($menu, $key) => MenuStrictScope::space($this->input("bar").".".$menu))->toArray(),
+ "menu_to" => Str::of($this->input("menu_to"))->explode(".")->map(fn ($menu, $key) => MenuStrictScope::space($this->input("bar").".".$menu))->toArray(),
+ ];
+ }
+
+ /**
+ * @return void
+ */
+ protected function postValidation()
+ {
+ return [
+
+ "menu_from" => collect($this->input("menu_from"))->map(fn ($menu, $key) => Str::replaceFirst(MenuStrictScope::space($this->input("bar")."."), "", $menu))->implode("."),
+ "menu_to" => collect($this->input("menu_to"))->map(fn ($menu, $key) => Str::replaceFirst(MenuStrictScope::space($this->input("bar")."."), "", $menu))->implode("."),
+ ];
+ }
+
+ /**
+ * @return bool
+ */
+ public function authorize()
+ {
+ return true;
+ }
+
+ /**
+ * @return array
+ */
+ public function rules()
+ {
+ return [
+
+ "bar" => "required|string|in:headernavbar,sidenavbar",
+
+ "menu_from" => "required|array",
+ "menu_from.*" => [
+
+ Rule::exists(Detailmenu::class, "menuable_id"),
+ ],
+
+ "menu_to" => "required|array",
+ "menu_to.*" => [
+
+ Rule::exists(Detailmenu::class, "menuable_id"),
+ ],
+ ];
+ }
+};
diff --git a/stubs/app/Http/Responses/.gitkeep b/stubs/app/Http/Responses/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/stubs/app/Imports/Settings/Menus/.gitkeep b/stubs/app/Imports/Settings/Menus/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/stubs/routes/admin/setting/menu.php b/stubs/routes/admin/setting/menu.php
new file mode 100644
index 0000000..996f22e
--- /dev/null
+++ b/stubs/routes/admin/setting/menu.php
@@ -0,0 +1,12 @@
+middleware(config("adminer.middleware.admin"))->group(function () {
+
+ /**
+ * Settings Menus.
+ */
+ Route::apiResource("bars.menus", MenuAdminController::class)->parameters([ "bars" => "bar", "menus" => "menu", ]);
+});
diff --git a/stubs/routes/user/setting/menu.php b/stubs/routes/user/setting/menu.php
new file mode 100644
index 0000000..a87eaf6
--- /dev/null
+++ b/stubs/routes/user/setting/menu.php
@@ -0,0 +1,15 @@
+middleware(config("adminer.middleware.user"))->group(function () {
+
+ /**
+ * Settings Menus.
+ */
+ Route::get("menus", [ MenuController::class, "index", ]);
+ Route::post("menus", [ MenuController::class, "store", ]);
+ Route::put("menus", [ MenuController::class, "update", ]);
+ Route::delete("menus/{bar}/{menu}", [ MenuController::class, "destroy", ]);
+});
diff --git a/stubs/tests/Feature/Setting/Menu/MenuTest.stub b/stubs/tests/Feature/Setting/Menu/MenuTest.stub
new file mode 100644
index 0000000..bd70ba5
--- /dev/null
+++ b/stubs/tests/Feature/Setting/Menu/MenuTest.stub
@@ -0,0 +1,83 @@
+user();
+ $this->actingAs($user);
+
+ $this->test_users_can_update_menu();
+
+ $data = $this->get(config("adminer.route.user") ?? "api"."/menus");
+ $data->assertStatus(200);
+ }
+
+ /**
+ * @return void
+ */
+ public function test_users_can_update_menu()
+ {
+ $this->post(config("adminer.route.admin") ?? "api/admin"."/bars/headernavbar/menus", [
+
+ "menu" => "home",
+ "category" => "base",
+ "icon" => "md-home",
+ "title" => "Home",
+ "description" => "Home Page",
+ ]);
+ $this->post(config("adminer.route.admin") ?? "api/admin"."/bars/headernavbar/menus", [
+
+ "menu" => "profile",
+ "category" => "base",
+ "icon" => "md-profile",
+ "title" => "Profile",
+ "description" => "Profile Page",
+ ]);
+
+ $user = $this->user();
+ $this->actingAs($user);
+
+ $this->post(config("adminer.route.user") ?? "api"."/menus", [
+
+ "bar" => "headernavbar",
+ "menu" => "home",
+ ]);
+ $this->delete(config("adminer.route.user") ?? "api"."/menus/headernavbar/home");
+ $this->post(config("adminer.route.user") ?? "api"."/menus", [
+
+ "bar" => "headernavbar",
+ "menu" => "profile",
+ ]);
+ $this->post(config("adminer.route.user") ?? "api"."/menus", [
+
+ "bar" => "headernavbar",
+ "menu" => "profile.home",
+ ]);
+ $this->delete(config("adminer.route.user") ?? "api"."/menus/headernavbar/profile.home");
+
+ $this->post(config("adminer.route.user") ?? "api"."/menus", [
+
+ "bar" => "headernavbar",
+ "menu" => "home",
+ ]);
+ $data = $this->put(config("adminer.route.user") ?? "api"."/menus", [
+
+ "bar" => "headernavbar",
+ "menu_from" => "home",
+ "menu_to" => "profile",
+ ]);
+ $data->assertStatus(201);
+ }
+};