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); + } +};