Ver Fonte

first commit

chenlong há 4 anos atrás
commit
6e9fbeeb3c
100 ficheiros alterados com 6178 adições e 0 exclusões
  1. 82 0
      .env.example
  2. 45 0
      .env.testing
  3. 5 0
      .gitattributes
  4. 29 0
      .gitignore
  5. 20 0
      .travis.yml
  6. 46 0
      CODE_OF_CONDUCT.md
  7. 10 0
      CONTRIBUTING.md
  8. 21 0
      LICENSE.md
  9. 3 0
      PULL_REQUEST_TEMPLATE.md
  10. 31 0
      README.md
  11. 1 0
      _config.yml
  12. 124 0
      app/Console/Commands/LaraStructure.php
  13. 40 0
      app/Console/Kernel.php
  14. 24 0
      app/Console/Stubs/DummyModel.stub
  15. 92 0
      app/Console/Stubs/DummyRepository.stub
  16. 20 0
      app/Console/Stubs/DummyRepositoryInterface.stub
  17. 40 0
      app/Events/OrderCreateEvent.php
  18. 84 0
      app/Exceptions/Handler.php
  19. 87 0
      app/Helpers/helper.php
  20. 181 0
      app/Http/Controllers/Admin/Addresses/AddressController.php
  21. 125 0
      app/Http/Controllers/Admin/Attributes/AttributeController.php
  22. 78 0
      app/Http/Controllers/Admin/Attributes/AttributeValueController.php
  23. 101 0
      app/Http/Controllers/Admin/Brands/BrandController.php
  24. 145 0
      app/Http/Controllers/Admin/Categories/CategoryController.php
  25. 20 0
      app/Http/Controllers/Admin/Categories/Requests/CreateCategoryRequest.php
  26. 31 0
      app/Http/Controllers/Admin/Categories/Requests/UpdateCategoryRequest.php
  27. 66 0
      app/Http/Controllers/Admin/Cities/CityController.php
  28. 79 0
      app/Http/Controllers/Admin/Countries/CountryController.php
  29. 33 0
      app/Http/Controllers/Admin/Countries/Requests/UpdateCountryRequest.php
  30. 106 0
      app/Http/Controllers/Admin/Couriers/CourierController.php
  31. 76 0
      app/Http/Controllers/Admin/Customers/CustomerAddressController.php
  32. 145 0
      app/Http/Controllers/Admin/Customers/CustomerController.php
  33. 19 0
      app/Http/Controllers/Admin/DashboardController.php
  34. 199 0
      app/Http/Controllers/Admin/EmployeeController.php
  35. 69 0
      app/Http/Controllers/Admin/LoginController.php
  36. 193 0
      app/Http/Controllers/Admin/Orders/OrderController.php
  37. 98 0
      app/Http/Controllers/Admin/Orders/OrderStatusController.php
  38. 36 0
      app/Http/Controllers/Admin/Permissions/PermissionController.php
  39. 388 0
      app/Http/Controllers/Admin/Products/ProductController.php
  40. 70 0
      app/Http/Controllers/Admin/Provinces/ProvinceController.php
  41. 111 0
      app/Http/Controllers/Admin/Roles/RoleController.php
  42. 78 0
      app/Http/Controllers/Auth/CartLoginController.php
  43. 32 0
      app/Http/Controllers/Auth/ForgotPasswordController.php
  44. 79 0
      app/Http/Controllers/Auth/LoginController.php
  45. 71 0
      app/Http/Controllers/Auth/RegisterController.php
  46. 38 0
      app/Http/Controllers/Auth/ResetPasswordController.php
  47. 18 0
      app/Http/Controllers/Controller.php
  48. 61 0
      app/Http/Controllers/Front/AccountsController.php
  49. 37 0
      app/Http/Controllers/Front/Addresses/CountryStateController.php
  50. 37 0
      app/Http/Controllers/Front/Addresses/StateCityController.php
  51. 146 0
      app/Http/Controllers/Front/CartController.php
  52. 45 0
      app/Http/Controllers/Front/CategoryController.php
  53. 253 0
      app/Http/Controllers/Front/CheckoutController.php
  54. 154 0
      app/Http/Controllers/Front/CustomerAddressController.php
  55. 33 0
      app/Http/Controllers/Front/HomeController.php
  56. 155 0
      app/Http/Controllers/Front/Payments/BankTransferController.php
  57. 68 0
      app/Http/Controllers/Front/ProductController.php
  58. 64 0
      app/Http/Kernel.php
  59. 17 0
      app/Http/Middleware/EncryptCookies.php
  60. 23 0
      app/Http/Middleware/RedirectIfAuthenticated.php
  61. 26 0
      app/Http/Middleware/RedirectIfNotCustomer.php
  62. 26 0
      app/Http/Middleware/RedirectIfNotEmployee.php
  63. 18 0
      app/Http/Middleware/TrimStrings.php
  64. 23 0
      app/Http/Middleware/TrustProxies.php
  65. 17 0
      app/Http/Middleware/VerifyCsrfToken.php
  66. 36 0
      app/Listeners/OrderCreateEventListener.php
  67. 47 0
      app/Mail/SendOrderToCustomerMailable.php
  68. 46 0
      app/Mail/sendEmailNotificationToAdminMailable.php
  69. 29 0
      app/Providers/AppServiceProvider.php
  70. 31 0
      app/Providers/AuthServiceProvider.php
  71. 26 0
      app/Providers/BroadcastServiceProvider.php
  72. 32 0
      app/Providers/EventServiceProvider.php
  73. 100 0
      app/Providers/GlobalTemplateServiceProvider.php
  74. 151 0
      app/Providers/RepositoryServiceProvider.php
  75. 73 0
      app/Providers/RouteServiceProvider.php
  76. 105 0
      app/Shop/Addresses/Address.php
  77. 7 0
      app/Shop/Addresses/Exceptions/AddressNotFoundException.php
  78. 7 0
      app/Shop/Addresses/Exceptions/CreateAddressErrorException.php
  79. 183 0
      app/Shop/Addresses/Repositories/AddressRepository.php
  80. 36 0
      app/Shop/Addresses/Repositories/Interfaces/AddressRepositoryInterface.php
  81. 21 0
      app/Shop/Addresses/Requests/CreateAddressRequest.php
  82. 21 0
      app/Shop/Addresses/Requests/UpdateAddressRequest.php
  83. 54 0
      app/Shop/Addresses/Transformations/AddressTransformable.php
  84. 33 0
      app/Shop/Admins/Requests/CreateEmployeeRequest.php
  85. 31 0
      app/Shop/Admins/Requests/LoginRequest.php
  86. 32 0
      app/Shop/Admins/Requests/UpdateEmployeeRequest.php
  87. 30 0
      app/Shop/AttributeValues/AttributeValue.php
  88. 63 0
      app/Shop/AttributeValues/Repositories/AttributeValueRepository.php
  89. 19 0
      app/Shop/AttributeValues/Repositories/AttributeValueRepositoryInterface.php
  90. 20 0
      app/Shop/AttributeValues/Requests/CreateAttributeValueRequest.php
  91. 21 0
      app/Shop/Attributes/Attribute.php
  92. 7 0
      app/Shop/Attributes/Exceptions/AttributeNotFoundException.php
  93. 7 0
      app/Shop/Attributes/Exceptions/CreateAttributeErrorException.php
  94. 7 0
      app/Shop/Attributes/Exceptions/UpdateAttributeErrorException.php
  95. 111 0
      app/Shop/Attributes/Repositories/AttributeRepository.php
  96. 25 0
      app/Shop/Attributes/Repositories/AttributeRepositoryInterface.php
  97. 20 0
      app/Shop/Attributes/Requests/CreateAttributeRequest.php
  98. 18 0
      app/Shop/Attributes/Requests/UpdateAttributeRequest.php
  99. 18 0
      app/Shop/Base/BaseFormRequest.php
  100. 19 0
      app/Shop/Brands/Brand.php

+ 82 - 0
.env.example

@@ -0,0 +1,82 @@
+APP_NAME=Laravel
+APP_ENV=local
+APP_KEY=base64:85SiSOVH7/2NVYP5/DBrtBKbL51Nw0+6lF7tx2+jo8o=
+APP_DEBUG=true
+APP_URL=http://localhost
+NODE_ENV=development
+TIMEZONE=UTC
+
+LOG_CHANNEL=stack
+
+DB_CONNECTION=mysql
+DB_HOST=192.168.10.10
+DB_PORT=3306
+DB_DATABASE=homestead
+DB_USERNAME=homestead
+DB_PASSWORD=secret
+
+BROADCAST_DRIVER=log
+CACHE_DRIVER=file
+SESSION_DRIVER=file
+QUEUE_DRIVER=sync
+
+REDIS_HOST=127.0.0.1
+REDIS_PASSWORD=null
+REDIS_PORT=6379
+
+MAIL_DRIVER=log
+MAIL_HOST=smtp.mailtrap.io
+MAIL_PORT=2525
+MAIL_USERNAME=null
+MAIL_PASSWORD=null
+MAIL_ENCRYPTION=null
+
+PUSHER_APP_ID=
+PUSHER_APP_KEY=
+PUSHER_APP_SECRET=
+
+SHIPPING_COST=0
+TAX_RATE=0
+DEFAULT_CURRENCY=USD
+CURRENCY_SYMBOL=$
+
+MAILCHIMP_API_KEY=
+MAILCHIMP_LIST_ID=
+
+GOOGLE_ANALYTICS=
+
+PAYMENT_METHODS=paypal,stripe,bank-transfer
+
+PP_ACCOUNT_ID=your.email-facilitator@yahoo.com
+PP_CLIENT_ID=
+PP_CLIENT_SECRET=
+PP_API_URL=https://api.sandbox.paypal.com
+PP_REDIRECT_URL=http://localhost/execute
+PP_CANCEL_URL=http://localhost/success
+PP_FAILED_URL=http://localhost/failed
+PP_MODE=sandbox
+
+STRIPE_KEY=
+STRIPE_SECRET=
+STRIPE_REDIRECT_URL=
+STRIPE_CANCEL_URL=
+STRIPE_FAILED_URL=
+
+SHOP_NAME=
+SHOP_COUNTRY_ISO=
+SHOP_COUNTRY_ID=226
+# options - gms, kgs, oz, lbs
+SHOP_WEIGHT=lbs
+SHOP_EMAIL=janedoe@mailinator.com
+
+BANK_TRANSFER_NAME=
+BANK_TRANSFER_ACCOUNT_TYPE=
+BANK_TRANSFER_ACCOUNT_NAME=
+BANK_TRANSFER_ACCOUNT_NUMBER=
+BANK_TRANSFER_SWIFT_CODE=
+BANK_TRANSFER_SWIFT_NOTE=
+
+ACTIVATE_SHIPPING=0
+SHIPPING_API_TOKEN=shippo_test_9a71d35b5bb461660008294c1d7734284a82dd77
+
+LARECIPE_DOCS_CACHED=false

+ 45 - 0
.env.testing

@@ -0,0 +1,45 @@
+APP_NAME=Laravel
+APP_ENV=testing
+APP_KEY=base64:85SiSOVH7/2NVYP5/DBrtBKbL51Nw0+6lF7tx2+jo8o=
+APP_DEBUG=true
+APP_LOG_LEVEL=debug
+APP_URL=http://localhost
+
+DB_CONNECTION=sqlite
+DB_HOST=192.168.10.10
+DB_PORT=3306
+DB_DATABASE=:memory:
+DB_USERNAME=homestead
+DB_PASSWORD=secret
+
+BROADCAST_DRIVER=log
+CACHE_DRIVER=file
+SESSION_DRIVER=file
+QUEUE_DRIVER=sync
+
+REDIS_HOST=127.0.0.1
+REDIS_PASSWORD=null
+REDIS_PORT=6379
+
+MAIL_DRIVER=smtp
+MAIL_HOST=smtp.mailtrap.io
+MAIL_PORT=2525
+MAIL_USERNAME=null
+MAIL_PASSWORD=null
+MAIL_ENCRYPTION=null
+
+PUSHER_APP_ID=
+PUSHER_APP_KEY=
+PUSHER_APP_SECRET=
+
+PAYPAL_ACCOUNT_ID=
+PAYPAL_CLIENT_ID=
+PAYPAL_CLIENT_SECRET=
+PAYPAL_URL=
+PAYPAL_MODE=sandbox
+SHIPPING_COST=0
+
+DEPLOY_SERVER=
+INQUIRY_MAIL=your@email.com
+
+COUNTRY_ID=169

+ 5 - 0
.gitattributes

@@ -0,0 +1,5 @@
+* text=auto
+*.css linguist-vendored
+*.scss linguist-vendored
+*.js linguist-vendored
+CHANGELOG.md export-ignore

+ 29 - 0
.gitignore

@@ -0,0 +1,29 @@
+/node_modules/
+/public/hot
+/public/storage
+/public/fonts
+/public/img
+/public/images
+/public/fonts/vendor
+/public/css/app.css
+/public/js/app.js
+/storage/*.key
+/vendor
+/.idea
+/.vagrant
+Homestead.json
+Homestead.yaml
+npm-debug.log
+yarn-error.log
+.env
+.DS_Store
+/public/.DS_Store
+yarn.lock
+package-lock.json
+/build
+Envoy.blade.php
+/coverage
+storage/app/public/storage
+\.php_cs\.cache
+public/css
+public/js

+ 20 - 0
.travis.yml

@@ -0,0 +1,20 @@
+language: php
+
+jsdecena:
+  - php72-composer:latest
+
+sudo: false
+
+cache:
+  directories:
+    - $HOME/.composer/cache
+
+before_script:
+  - cp .env.testing .env
+  - composer install --no-interaction
+  - composer dump-autoload
+
+script:
+  - vendor/bin/phpunit --coverage-clover=coverage.xml
+after_success:
+  - bash <(curl -s https://codecov.io/bash)

+ 46 - 0
CODE_OF_CONDUCT.md

@@ -0,0 +1,46 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual attention or advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at jeff.decena@yahoo.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
+
+Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
+
+[homepage]: http://contributor-covenant.org
+[version]: http://contributor-covenant.org/version/1/4/

+ 10 - 0
CONTRIBUTING.md

@@ -0,0 +1,10 @@
+# Submitting Guidelines
+
+## Test Case required
+
+- Frontend and Backend testing are required to ensure the integrity of the application
+- Frontend tests are in `/tests/Feature` while Backend tests in `tests/Unit`
+- Frontend tests are divided in `Admin` and `Front`, put the test in correct folder
+- Issues/Features should be submitted with test cases to demonstrate the fix/intent
+ 
+### * Without a test case, the issue / feature may be closed by the admin

+ 21 - 0
LICENSE.md

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2017 Jeff Simons Decena
+
+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.

+ 3 - 0
PULL_REQUEST_TEMPLATE.md

@@ -0,0 +1,3 @@
+# Title of the PR
+
+## Description of the PR with the link on the issue trying to solve

+ 31 - 0
README.md

@@ -0,0 +1,31 @@
+[![Build Status](https://travis-ci.org/jsdecena/laracom.svg?branch=master)](https://travis-ci.org/jsdecena/laracom)
+[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/Laracommerce/laracom/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/Laracommerce/laracom/?branch=master)
+[![Code Intelligence Status](https://scrutinizer-ci.com/g/Laracommerce/laracom/badges/code-intelligence.svg?b=master)](https://scrutinizer-ci.com/code-intelligence)
+[![codecov](https://codecov.io/gh/jsdecena/laracom/branch/master/graph/badge.svg)](https://codecov.io/gh/jsdecena/laracom)
+[![Fork Status](https://img.shields.io/github/forks/jsdecena/laracom.svg)](https://github.com/jsdecena/laracom)
+[![Star Status](https://img.shields.io/github/stars/jsdecena/laracom.svg)](https://github.com/jsdecena/laracom)
+[![Gitter chat](https://badges.gitter.im/gitterHQ/gitter.png)](https://gitter.im/larac0m/Lobby)
+[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2FLaracommerce%2Flaracom.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2FLaracommerce%2Flaracom?ref=badge_shield)
+
+# Laravel FREE E-Commerce Software
+
+- See full [documentation](https://shop.laracom.net/docs)
+
+# Setup
+* `composer install`
+* `php artisan migrate --seed`
+* `npm install`
+* `npm run dev`
+* `php artisan storage:link`
+
+# Contributors
+[Jeff Simons Decena](https://jsdecena.me) - Author
+
+[Contributors](https://github.com/Laracommerce/laracom/graphs/contributors)
+
+# Get discount on Digital Ocean
+Sign-up with [Digital Ocean and get $10 discount](https://m.do.co/c/bce94237de96)!
+
+
+## License
+[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2FLaracommerce%2Flaracom.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2FLaracommerce%2Flaracom?ref=badge_large)

+ 1 - 0
_config.yml

@@ -0,0 +1 @@
+theme: jekyll-theme-minimal

+ 124 - 0
app/Console/Commands/LaraStructure.php

@@ -0,0 +1,124 @@
+<?php
+
+namespace App\Console\Commands;
+
+use Illuminate\Console\Command;
+use Illuminate\Filesystem\Filesystem;
+
+class LaraStructure extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'make:structure {model} {--m|migration}';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = 'This command generate model and a base repository out of the box.';
+
+    /**
+     * Filesystem instance
+     * 
+     * @var string
+     */
+    protected $filesystem;
+
+    /**
+     * Default laracom folder
+     * 
+     * @var string
+     */
+    protected $folder;
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct(Filesystem $filesystem)
+    {
+        parent::__construct();
+        $this->filesystem = $filesystem;
+    }
+
+    /**
+     * Execute the console command.
+     *
+     * @return mixed
+     */
+    public function handle()
+    {
+        // Get model name from developer
+        $this->model = ucfirst($this->argument('model'));
+
+        // Get plural name for the given model
+        $pluralModel = str_plural($this->model);
+
+        // Check if the model already created
+        if ( $this->filesystem->exists(app_path("Shop/{$pluralModel}/{$this->model}.php")) ){
+            return $this->error("The given model already exists!");
+        }
+
+        // create all structured folders
+        $this->createFolders('Shop');
+
+        $this->createFile(
+            app_path('Console/Stubs/DummyRepository.stub'),
+            app_path("Shop/{$pluralModel}/Repositories/{$this->model}Repository.php")
+        );
+
+        $this->createFile(
+            app_path('Console/Stubs/DummyRepositoryInterface.stub'),
+            app_path("Shop/{$pluralModel}/Repositories/Interfaces/{$this->model}RepositoryInterface.php")
+        );
+
+        $this->info('File structure for ' . $this->model . ' created.');
+        
+        // Create Model under default instalation folder
+        $this->call('make:model', [
+            'name' => 'Shop/' . $pluralModel . '/' .$this->model,
+            '--migration' => $this->option('migration'),
+        ]);
+    }
+
+    /**
+     * Create source from dummy model name
+     * 
+     * @param  string $dummy        
+     * @param  string $destinationPath
+     * @return void
+     */
+    protected function createFile($dummySource, $destinationPath)
+    {
+        $pluralModel = str_plural($this->model);
+        $dummyRepository = $this->filesystem->get($dummySource);
+        $repositoryContent = str_replace(['Dummy', 'Dummies'], [$this->model, $pluralModel], $dummyRepository);
+        $this->filesystem->put($dummySource, $repositoryContent);
+        $this->filesystem->copy($dummySource, $destinationPath);
+        $this->filesystem->put($dummySource, $dummyRepository);
+    }
+
+    /**
+     * Create all required folders
+     * 
+     * @return void
+     */
+    protected function createFolders($baseFolder)
+    {
+        // get plural from model name
+        $pluralModel = str_plural($this->model);
+         // create container folder
+        $this->filesystem->makeDirectory(app_path($baseFolder."/{$pluralModel}"));
+         // add requests folder
+        $this->filesystem->makeDirectory(app_path($baseFolder."/{$pluralModel}/Requests"));
+         // add repositories folder
+        $this->filesystem->makeDirectory(app_path($baseFolder."/{$pluralModel}/Repositories/"));
+         // add Interfaces folder
+        $this->filesystem->makeDirectory(app_path($baseFolder."/{$pluralModel}/Repositories/Interfaces"));
+    }
+}

+ 40 - 0
app/Console/Kernel.php

@@ -0,0 +1,40 @@
+<?php
+
+namespace App\Console;
+
+use Illuminate\Console\Scheduling\Schedule;
+use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
+
+class Kernel extends ConsoleKernel
+{
+    /**
+     * The Artisan commands provided by your application.
+     *
+     * @var array
+     */
+    protected $commands = [
+        Commands\LaraStructure::class
+    ];
+
+    /**
+     * Define the application's command schedule.
+     *
+     * @param  \Illuminate\Console\Scheduling\Schedule  $schedule
+     * @return void
+     */
+    protected function schedule(Schedule $schedule)
+    {
+        // $schedule->command('inspire')
+        //          ->hourly();
+    }
+
+    /**
+     * Register the Closure based commands for the application.
+     *
+     * @return void
+     */
+    protected function commands()
+    {
+        require base_path('routes/console.php');
+    }
+}

+ 24 - 0
app/Console/Stubs/DummyModel.stub

@@ -0,0 +1,24 @@
+<?php
+
+namespace App\Shop\Dummies;
+
+class Dummy extends Model
+{
+    /**
+     * The attributes that are mass assignable.
+     *
+     * @var array
+     */
+    protected $fillable = [
+    	// 
+    ];
+
+    /**
+     * The attributes that should be hidden for arrays.
+     *
+     * @var array
+     */
+    protected $hidden = [
+    	// 
+    ];
+}

+ 92 - 0
app/Console/Stubs/DummyRepository.stub

@@ -0,0 +1,92 @@
+<?php
+
+namespace App\Shop\Dummies\Repositories;
+
+use App\Shop\Dummies\Dummy;
+use Illuminate\Support\Collection;
+use Jsdecena\Baserepo\BaseRepository;
+use Illuminate\Database\QueryException;
+use Illuminate\Database\Eloquent\ModelNotFoundException;
+use App\Shop\Dummies\Repositories\Interfaces\DummyRepositoryInterface;
+
+class DummyRepository extends BaseRepository implements DummyRepositoryInterface
+{
+	/**
+     * DummyRepository constructor.
+     * 
+     * @param Dummy $dummy
+     */
+    public function __construct(Dummy $dummy)
+    {
+        parent::__construct($dummy);
+        $this->model = $dummy;
+    }
+
+    /**
+     * List all the Dummies
+     *
+     * @param string $order
+     * @param string $sort
+     * @param array $except
+     * @return \Illuminate\Support\Collection
+     */
+    public function listDummies(string $order = 'id', string $sort = 'desc', $except = []) : Collection
+    {
+        return $this->model->orderBy($order, $sort)->get()->except($except);
+    }
+
+    /**
+     * Create Dummy
+     *
+     * @param array $params
+     *
+     * @return Dummy
+     * @throws InvalidArgumentException
+     */
+    public function createDummy(array $params) : Dummy
+    {
+        try {
+        	return Dummy::create($params);
+        } catch (QueryException $e) {
+            throw new InvalidArgumentException($e->getMessage());
+        }
+    }
+
+    /**
+     * Update the dummy
+     *
+     * @param array $params
+     * @return Dummy
+     */
+    public function updateDummy(array $params) : Dummy
+    {
+        $dummy = $this->findDummyById($this->model->id);
+        $dummy->update($params);
+        return $dummy;
+    }
+
+    /**
+     * @param int $id
+     * 
+     * @return Dummy
+     * @throws ModelNotFoundException
+     */
+    public function findDummyById(int $id) : Dummy
+    {
+        try {
+            return $this->findOneOrFail($id);
+        } catch (ModelNotFoundException $e) {
+            throw new ModelNotFoundException($e->getMessage());
+        }
+    }
+
+    /**
+     * Delete a dummy
+     *
+     * @return bool
+     */
+    public function deleteDummy() : bool
+    {
+        return $this->model->delete();
+    }
+}

+ 20 - 0
app/Console/Stubs/DummyRepositoryInterface.stub

@@ -0,0 +1,20 @@
+<?php
+
+namespace App\Shop\Dummies\Repositories\Interfaces;
+
+use App\Shop\Dummies\Dummy;
+use Illuminate\Support\Collection;
+use Jsdecena\Baserepo\BaseRepositoryInterface;
+
+interface DummyRepositoryInterface extends BaseRepositoryInterface
+{
+    public function listDummies(string $order = 'id', string $sort = 'desc', $except = []) : Collection;
+
+    public function createDummy(array $params) : Dummy;
+
+    public function updateDummy(array $params) : Dummy;
+
+    public function findDummyById(int $id) : Dummy;
+    
+    public function deleteDummy() : bool;
+}

+ 40 - 0
app/Events/OrderCreateEvent.php

@@ -0,0 +1,40 @@
+<?php
+
+namespace App\Events;
+
+use App\Shop\Orders\Order;
+use Illuminate\Broadcasting\Channel;
+use Illuminate\Queue\SerializesModels;
+use Illuminate\Broadcasting\PrivateChannel;
+use Illuminate\Broadcasting\PresenceChannel;
+use Illuminate\Foundation\Events\Dispatchable;
+use Illuminate\Broadcasting\InteractsWithSockets;
+use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
+
+class OrderCreateEvent
+{
+    use Dispatchable, InteractsWithSockets, SerializesModels;
+
+    public $order;
+
+    /**
+     * Create a new event instance.
+     *
+     * @param Order $order
+     */
+    public function __construct(Order $order)
+    {
+        $this->order = $order;
+    }
+
+    /**
+     * Get the channels the event should broadcast on.
+     *
+     * @return Channel|array
+     * @codeCoverageIgnore
+     */
+    public function broadcastOn()
+    {
+        return new PrivateChannel('channel-name');
+    }
+}

+ 84 - 0
app/Exceptions/Handler.php

@@ -0,0 +1,84 @@
+<?php
+
+namespace App\Exceptions;
+
+use Exception;
+use Illuminate\Auth\AuthenticationException;
+use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
+use Illuminate\Support\Facades\Log;
+use Symfony\Component\HttpKernel\Exception\HttpException;
+use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
+
+/**
+ * @codeCoverageIgnore
+ */
+class Handler extends ExceptionHandler
+{
+    /**
+     * A list of the exception types that should not be reported.
+     *
+     * @var array
+     */
+    protected $dontReport = [
+        \Illuminate\Auth\AuthenticationException::class,
+        \Illuminate\Auth\Access\AuthorizationException::class,
+        \Symfony\Component\HttpKernel\Exception\HttpException::class,
+        \Illuminate\Database\Eloquent\ModelNotFoundException::class,
+        \Illuminate\Session\TokenMismatchException::class,
+        \Illuminate\Validation\ValidationException::class,
+    ];
+
+    /**
+     * Report or log an exception.
+     *
+     * This is a great spot to send exceptions to Sentry, Bugsnag, etc.
+     *
+     * @param  \Exception  $exception
+     * @return void
+     */
+    public function report(Exception $exception)
+    {
+        parent::report($exception);
+    }
+
+    /**
+     * Render an exception into an HTTP response.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  \Exception  $exception
+     * @return \Illuminate\Http\Response
+     */
+    public function render($request, Exception $exception)
+    {
+        if ($exception instanceof NotFoundHttpException) {
+            return response()->view('layouts.errors.404', [], 404);
+        } elseif ($exception instanceof HttpException && $exception->getStatusCode() == 403) {
+            return response()->view(
+                'layouts.errors.403',
+                ['error' => 'Sorry, this page is restricted to authorized users only.'],
+                403
+            );
+        } elseif ($exception instanceof HttpException) {
+            Log::info($exception->getMessage());
+            return response()->view('layouts.errors.503', ['error' => $exception->getTrace()], 500);
+        }
+
+        return parent::render($request, $exception);
+    }
+
+    /**
+     * Convert an authentication exception into an unauthenticated response.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  \Illuminate\Auth\AuthenticationException  $exception
+     * @return \Illuminate\Http\Response
+     */
+    protected function unauthenticated($request, AuthenticationException $exception)
+    {
+        if ($request->expectsJson()) {
+            return response()->json(['error' => 'Unauthenticated.'], 401);
+        }
+
+        return redirect()->guest(route('login'));
+    }
+}

+ 87 - 0
app/Helpers/helper.php

@@ -0,0 +1,87 @@
+<?php
+/**
+ * Created by PhpStorm.
+ * User: shehbaz
+ * Date: 1/21/19
+ * Time: 12:19 PM
+ */
+
+
+use Illuminate\Support\Facades\Validator;
+
+if (!function_exists("helper_test")) {
+    function helper_test()
+    {
+        echo "it is working";
+    }
+}
+
+if (!function_exists("populate_breadcumb")) {
+    /**
+     * popular data to layouts.admin.app when send from controller
+     *
+     *<h1> controller example </h1>
+     * <pre>
+     *  $data = [
+     * ["name" => "Dashboard1", "url" => route("admin.dashboard")],
+     * ["name" => "Products1", "url" => request()->fullUrl()]
+     * ];
+     *
+     * populate_breadcumb($data)
+     * </pre>
+     *
+     * @param $data
+     * @return void
+     */
+    function populate_breadcumb($data)
+    {
+        $validated = validate_breadcumb($data);
+        if ($validated["valid"] === true) {
+            view()->composer([
+                "layouts.admin.app"
+            ], function ($view) use ($data) {
+                $view->with(
+                    [
+                        "breadcumbs" => $data
+                    ]
+                );
+            });
+        }
+
+    }
+
+}
+
+if (!function_exists('validate_breadcumb')) {
+
+    /**
+     * validate breadcumb data
+     * @param $data
+     * @return array
+     */
+    function validate_breadcumb($data)
+    {
+        $validated = false;
+        $errors = [];
+        foreach ($data as $key => $item) {
+            $messages = [
+                'required' => "The :attribute field is required at index: $key.",
+                "url" => "The :attribute format is invalid at index: $key"
+
+            ];
+            $validator = Validator::make($item, [
+                'name' => 'required',
+                'url' => "required|url",
+//                "icon" => ""
+            ], $messages);
+            if ($validator->fails()) {
+                $validated = false;
+                $errors[] = $validator->errors();
+
+            } else {
+                $validated = true;
+            }
+        }
+        return ["errors" => $errors, "valid" => $validated];
+    }
+}

+ 181 - 0
app/Http/Controllers/Admin/Addresses/AddressController.php

@@ -0,0 +1,181 @@
+<?php
+
+namespace App\Http\Controllers\Admin\Addresses;
+
+use App\Shop\Addresses\Address;
+use App\Shop\Addresses\Repositories\AddressRepository;
+use App\Shop\Addresses\Repositories\Interfaces\AddressRepositoryInterface;
+use App\Shop\Addresses\Requests\CreateAddressRequest;
+use App\Shop\Addresses\Requests\UpdateAddressRequest;
+use App\Shop\Addresses\Transformations\AddressTransformable;
+use App\Shop\Cities\City;
+use App\Shop\Cities\Repositories\Interfaces\CityRepositoryInterface;
+use App\Shop\Countries\Country;
+use App\Shop\Countries\Repositories\CountryRepository;
+use App\Shop\Countries\Repositories\Interfaces\CountryRepositoryInterface;
+use App\Shop\Customers\Repositories\Interfaces\CustomerRepositoryInterface;
+use App\Http\Controllers\Controller;
+use App\Shop\Provinces\Repositories\Interfaces\ProvinceRepositoryInterface;
+use Illuminate\Http\Request;
+
+class AddressController extends Controller
+{
+    use AddressTransformable;
+
+    private $addressRepo;
+    private $customerRepo;
+    private $countryRepo;
+    private $provinceRepo;
+    private $cityRepo;
+
+    public function __construct(
+        AddressRepositoryInterface $addressRepository,
+        CustomerRepositoryInterface $customerRepository,
+        CountryRepositoryInterface $countryRepository,
+        ProvinceRepositoryInterface $provinceRepository,
+        CityRepositoryInterface $cityRepository
+    ) {
+        $this->addressRepo = $addressRepository;
+        $this->customerRepo = $customerRepository;
+        $this->countryRepo = $countryRepository;
+        $this->provinceRepo = $provinceRepository;
+        $this->cityRepo = $cityRepository;
+    }
+
+    /**
+     * Display a listing of the resource.
+     *
+     * @param Request $request
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function index(Request $request)
+    {
+        $list = $this->addressRepo->listAddress('created_at', 'desc');
+
+        if ($request->has('q')) {
+            $list = $this->addressRepo->searchAddress($request->input('q'));
+        }
+
+        $addresses = $list->map(function (Address $address) {
+            return $this->transformAddress($address);
+        })->all();
+
+        return view('admin.addresses.list', ['addresses' => $this->addressRepo->paginateArrayResults($addresses)]);
+    }
+
+    /**
+     * Show the form for creating a new resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function create()
+    {
+        $countries = $this->countryRepo->listCountries();
+        $country = $this->countryRepo->findCountryById(1);
+
+        $customers = $this->customerRepo->listCustomers();
+
+        return view('admin.addresses.create', [
+            'customers' => $customers,
+            'countries' => $countries,
+            'provinces' => $country->provinces,
+            'cities' => City::all()
+        ]);
+    }
+
+    /**
+     * Store a newly created resource in storage.
+     *
+     * @param  CreateAddressRequest $request
+     * @return \Illuminate\Http\Response
+     */
+    public function store(CreateAddressRequest $request)
+    {
+        $this->addressRepo->createAddress($request->except('_token', '_method'));
+
+        $request->session()->flash('message', 'Creation successful');
+        return redirect()->route('admin.addresses.index');
+    }
+
+    /**
+     * Display the specified resource.
+     *
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function show(int $id)
+    {
+        return view('admin.addresses.show', ['address' => $this->addressRepo->findAddressById($id)]);
+    }
+
+    /**
+     * Show the form for editing the specified resource.
+     *
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function edit(int $id)
+    {
+        $countries = $this->countryRepo->listCountries();
+
+        $country = $countries->filter(function ($country) {
+            return $country == env('SHOP_COUNTRY_ID', '1');
+        })->first();
+
+        $countryRepo = new CountryRepository(new Country);
+        if (!empty($country)) {
+            $countryRepo = new CountryRepository($country);
+        }
+
+        $address = $this->addressRepo->findAddressById($id);
+        $addressRepo = new AddressRepository($address);
+        $customer = $addressRepo->findCustomer();
+
+        return view('admin.addresses.edit', [
+            'address' => $address,
+            'countries' => $countries,
+            'countryId' => $address->country->id,
+            'provinces' => $countryRepo->findProvinces(),
+            'provinceId' => $address->province->id,
+            'cities' => $this->cityRepo->listCities(),
+            'cityId' => $address->city_id,
+            'customers' => $this->customerRepo->listCustomers(),
+            'customerId' => $customer->id
+        ]);
+    }
+
+    /**
+     * Update the specified resource in storage.
+     *
+     * @param  UpdateAddressRequest $request
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function update(UpdateAddressRequest $request, $id)
+    {
+        $address = $this->addressRepo->findAddressById($id);
+
+        $update = new AddressRepository($address);
+        $update->updateAddress($request->except('_method', '_token'));
+
+        $request->session()->flash('message', 'Update successful');
+        return redirect()->route('admin.addresses.edit', $id);
+    }
+
+    /**
+     * Remove the specified resource from storage.
+     *
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function destroy($id)
+    {
+        $address = $this->addressRepo->findAddressById($id);
+        $delete = new AddressRepository($address);
+        $delete->deleteAddress();
+
+        request()->session()->flash('message', 'Delete successful');
+        return redirect()->route('admin.addresses.index');
+    }
+}

+ 125 - 0
app/Http/Controllers/Admin/Attributes/AttributeController.php

@@ -0,0 +1,125 @@
+<?php
+
+namespace App\Http\Controllers\Admin\Attributes;
+
+use App\Http\Controllers\Controller;
+use App\Shop\Attributes\Exceptions\AttributeNotFoundException;
+use App\Shop\Attributes\Exceptions\CreateAttributeErrorException;
+use App\Shop\Attributes\Exceptions\UpdateAttributeErrorException;
+use App\Shop\Attributes\Repositories\AttributeRepository;
+use App\Shop\Attributes\Repositories\AttributeRepositoryInterface;
+use App\Shop\Attributes\Requests\CreateAttributeRequest;
+use App\Shop\Attributes\Requests\UpdateAttributeRequest;
+
+class AttributeController extends Controller
+{
+    private $attributeRepo;
+
+    /**
+     * AttributeController constructor.
+     * @param AttributeRepositoryInterface $attributeRepository
+     */
+    public function __construct(AttributeRepositoryInterface $attributeRepository)
+    {
+        $this->attributeRepo = $attributeRepository;
+    }
+
+    /**
+     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
+     */
+    public function index()
+    {
+        $results = $this->attributeRepo->listAttributes();
+        $attributes = $this->attributeRepo->paginateArrayResults($results->all());
+
+        return view('admin.attributes.list', compact('attributes'));
+    }
+
+    /**
+     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
+     */
+    public function create()
+    {
+        return view('admin.attributes.create');
+    }
+
+    /**
+     * @param CreateAttributeRequest $request
+     * @return \Illuminate\Http\RedirectResponse
+     */
+    public function store(CreateAttributeRequest $request)
+    {
+        $attribute = $this->attributeRepo->createAttribute($request->except('_token'));
+        $request->session()->flash('message', 'Create attribute successful!');
+
+        return redirect()->route('admin.attributes.edit', $attribute->id);
+    }
+
+    /**
+     * @param $id
+     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
+     */
+    public function show($id)
+    {
+        try {
+            $attribute = $this->attributeRepo->findAttributeById($id);
+            $attributeRepo = new AttributeRepository($attribute);
+
+            return view('admin.attributes.show', [
+                'attribute' => $attribute,
+                'values' => $attributeRepo->listAttributeValues()
+            ]);
+        } catch (AttributeNotFoundException $e) {
+            request()->session()->flash('error', 'The attribute you are looking for is not found.');
+
+            return redirect()->route('admin.attributes.index');
+        }
+    }
+
+    /**
+     * @param int $id
+     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
+     */
+    public function edit($id)
+    {
+        $attribute = $this->attributeRepo->findAttributeById($id);
+
+        return view('admin.attributes.edit', compact('attribute'));
+    }
+
+    /**
+     * @param UpdateAttributeRequest $request
+     * @param $id
+     * @return \Illuminate\Http\RedirectResponse
+     */
+    public function update(UpdateAttributeRequest $request, $id)
+    {
+        try {
+            $attribute = $this->attributeRepo->findAttributeById($id);
+
+            $attributeRepo = new AttributeRepository($attribute);
+            $attributeRepo->updateAttribute($request->except('_token'));
+
+            $request->session()->flash('message', 'Attribute update successful!');
+
+            return redirect()->route('admin.attributes.edit', $attribute->id);
+        } catch (UpdateAttributeErrorException $e) {
+            $request->session()->flash('error', $e->getMessage());
+
+            return redirect()->route('admin.attributes.edit', $id)->withInput();
+        }
+    }
+
+    /**
+     * @param $id
+     * @return bool|null
+     */
+    public function destroy($id)
+    {
+        $this->attributeRepo->findAttributeById($id)->delete();
+
+        request()->session()->flash('message', 'Attribute deleted successfully!');
+
+        return redirect()->route('admin.attributes.index');
+    }
+}

+ 78 - 0
app/Http/Controllers/Admin/Attributes/AttributeValueController.php

@@ -0,0 +1,78 @@
+<?php
+
+namespace App\Http\Controllers\Admin\Attributes;
+
+use App\Http\Controllers\Controller;
+use App\Shop\Attributes\Repositories\AttributeRepositoryInterface;
+use App\Shop\AttributeValues\AttributeValue;
+use App\Shop\AttributeValues\Repositories\AttributeValueRepository;
+use App\Shop\AttributeValues\Repositories\AttributeValueRepositoryInterface;
+use App\Shop\AttributeValues\Requests\CreateAttributeValueRequest;
+
+class AttributeValueController extends Controller
+{
+    /**
+     * @var AttributeRepositoryInterface
+     */
+    private $attributeRepo;
+
+    /**
+     * @var AttributeValueRepositoryInterface
+     */
+    private $attributeValueRepo;
+
+    /**
+     * AttributeValueController constructor.
+     * @param AttributeRepositoryInterface $attributeRepository
+     * @param AttributeValueRepositoryInterface $attributeValueRepository
+     */
+    public function __construct(
+        AttributeRepositoryInterface $attributeRepository,
+        AttributeValueRepositoryInterface $attributeValueRepository
+    ) {
+        $this->attributeRepo = $attributeRepository;
+        $this->attributeValueRepo = $attributeValueRepository;
+    }
+
+    public function create($id)
+    {
+        return view('admin.attribute-values.create', [
+            'attribute' => $this->attributeRepo->findAttributeById($id)
+        ]);
+    }
+
+    /**
+     * @param CreateAttributeValueRequest $request
+     * @param $id
+     * @return \Illuminate\Http\RedirectResponse
+     */
+    public function store(CreateAttributeValueRequest $request, $id)
+    {
+        $attribute = $this->attributeRepo->findAttributeById($id);
+
+        $attributeValue = new AttributeValue($request->except('_token'));
+        $attributeValueRepo = new AttributeValueRepository($attributeValue);
+
+        $attributeValueRepo->associateToAttribute($attribute);
+
+        $request->session()->flash('message', 'Attribute value created');
+
+        return redirect()->route('admin.attributes.show', $attribute->id);
+    }
+
+    /**
+     * @param $attributeId
+     * @param $attributeValueId
+     * @return \Illuminate\Http\RedirectResponse
+     */
+    public function destroy($attributeId, $attributeValueId)
+    {
+        $attributeValue = $this->attributeValueRepo->findOneOrFail($attributeValueId);
+
+        $attributeValueRepo = new AttributeValueRepository($attributeValue);
+        $attributeValueRepo->dissociateFromAttribute();
+
+        request()->session()->flash('message', 'Attribute value removed!');
+        return redirect()->route('admin.attributes.show', $attributeId);
+    }
+}

+ 101 - 0
app/Http/Controllers/Admin/Brands/BrandController.php

@@ -0,0 +1,101 @@
+<?php
+
+namespace App\Http\Controllers\Admin\Brands;
+
+use App\Http\Controllers\Controller;
+use App\Shop\Brands\Repositories\BrandRepository;
+use App\Shop\Brands\Repositories\BrandRepositoryInterface;
+use App\Shop\Brands\Requests\CreateBrandRequest;
+use App\Shop\Brands\Requests\UpdateBrandRequest;
+
+class BrandController extends Controller
+{
+    /**
+     * @var BrandRepositoryInterface
+     */
+    private $brandRepo;
+
+    /**
+     * BrandController constructor.
+     *
+     * @param BrandRepositoryInterface $brandRepository
+     */
+    public function __construct(BrandRepositoryInterface $brandRepository)
+    {
+        $this->brandRepo = $brandRepository;
+    }
+
+    /**
+     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
+     */
+    public function index()
+    {
+        $data = $this->brandRepo->paginateArrayResults($this->brandRepo->listBrands(['*'], 'name', 'asc')->all());
+
+        return view('admin.brands.list', ['brands' => $data]);
+    }
+
+    /**
+     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
+     */
+    public function create()
+    {
+        return view('admin.brands.create');
+    }
+
+    /**
+     * @param CreateBrandRequest $request
+     *
+     * @return \Illuminate\Http\RedirectResponse
+     */
+    public function store(CreateBrandRequest $request)
+    {
+        $this->brandRepo->createBrand($request->all());
+
+        return redirect()->route('admin.brands.index')->with('message', 'Create brand successful!');
+    }
+
+    /**
+     * @param $id
+     *
+     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
+     */
+    public function edit($id)
+    {
+        return view('admin.brands.edit', ['brand' => $this->brandRepo->findBrandById($id)]);
+    }
+
+    /**
+     * @param UpdateBrandRequest $request
+     * @param $id
+     *
+     * @return \Illuminate\Http\RedirectResponse
+     * @throws \App\Shop\Brands\Exceptions\UpdateBrandErrorException
+     */
+    public function update(UpdateBrandRequest $request, $id)
+    {
+        $brand = $this->brandRepo->findBrandById($id);
+
+        $brandRepo = new BrandRepository($brand);
+        $brandRepo->updateBrand($request->all());
+
+        return redirect()->route('admin.brands.edit', $id)->with('message', 'Update successful!');
+    }
+
+    /**
+     * @param $id
+     *
+     * @return \Illuminate\Http\RedirectResponse
+     * @throws \Exception
+     */
+    public function destroy($id)
+    {
+        $brand = $this->brandRepo->findBrandById($id);
+
+        $brandRepo = new BrandRepository($brand);
+        $brandRepo->dissociateProducts();
+        $brandRepo->deleteBrand();
+
+        return redirect()->route('admin.brands.index')->with('message', 'Delete successful!');
+    }
+}

+ 145 - 0
app/Http/Controllers/Admin/Categories/CategoryController.php

@@ -0,0 +1,145 @@
+<?php
+
+namespace App\Http\Controllers\Admin\Categories;
+
+use App\Shop\Categories\Repositories\CategoryRepository;
+use App\Shop\Categories\Repositories\Interfaces\CategoryRepositoryInterface;
+use App\Shop\Categories\Requests\CreateCategoryRequest;
+use App\Shop\Categories\Requests\UpdateCategoryRequest;
+use App\Http\Controllers\Controller;
+use Illuminate\Http\Request;
+
+class CategoryController extends Controller
+{
+    /**
+     * @var CategoryRepositoryInterface
+     */
+    private $categoryRepo;
+
+    /**
+     * CategoryController constructor.
+     *
+     * @param CategoryRepositoryInterface $categoryRepository
+     */
+    public function __construct(CategoryRepositoryInterface $categoryRepository)
+    {
+        $this->categoryRepo = $categoryRepository;
+    }
+
+    /**
+     * Display a listing of the resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function index()
+    {
+        $list = $this->categoryRepo->rootCategories('created_at', 'desc');
+
+        return view('admin.categories.list', [
+            'categories' => $this->categoryRepo->paginateArrayResults($list->all())
+        ]);
+    }
+
+    /**
+     * Show the form for creating a new resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function create()
+    {
+        return view('admin.categories.create', [
+            'categories' => $this->categoryRepo->listCategories('name', 'asc')
+        ]);
+    }
+
+    /**
+     * Store a newly created resource in storage.
+     *
+     * @param  CreateCategoryRequest  $request
+     * @return \Illuminate\Http\Response
+     */
+    public function store(CreateCategoryRequest $request)
+    {
+        $this->categoryRepo->createCategory($request->except('_token', '_method'));
+
+        return redirect()->route('admin.categories.index')->with('message', 'Category created');
+    }
+
+    /**
+     * Display the specified resource.
+     *
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function show($id)
+    {
+        $category = $this->categoryRepo->findCategoryById($id);
+
+        $cat = new CategoryRepository($category);
+
+        return view('admin.categories.show', [
+            'category' => $category,
+            'categories' => $category->children,
+            'products' => $cat->findProducts()
+        ]);
+    }
+
+    /**
+     * Show the form for editing the specified resource.
+     *
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function edit($id)
+    {
+        return view('admin.categories.edit', [
+            'categories' => $this->categoryRepo->listCategories('name', 'asc', $id),
+            'category' => $this->categoryRepo->findCategoryById($id)
+        ]);
+    }
+
+    /**
+     * Update the specified resource in storage.
+     *
+     * @param  UpdateCategoryRequest $request
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function update(UpdateCategoryRequest $request, $id)
+    {
+        $category = $this->categoryRepo->findCategoryById($id);
+
+        $update = new CategoryRepository($category);
+        $update->updateCategory($request->except('_token', '_method'));
+
+        $request->session()->flash('message', 'Update successful');
+        return redirect()->route('admin.categories.edit', $id);
+    }
+
+    /**
+     * Remove the specified resource from storage.
+     *
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function destroy(int $id)
+    {
+        $category = $this->categoryRepo->findCategoryById($id);
+        $category->products()->sync([]);
+        $category->delete();
+
+        request()->session()->flash('message', 'Delete successful');
+        return redirect()->route('admin.categories.index');
+    }
+
+    /**
+     * @param Request $request
+     * @return \Illuminate\Http\RedirectResponse
+     */
+    public function removeImage(Request $request)
+    {
+        $this->categoryRepo->deleteFile($request->only('category'));
+        request()->session()->flash('message', 'Image delete successful');
+        return redirect()->route('admin.categories.edit', $request->input('category'));
+    }
+}

+ 20 - 0
app/Http/Controllers/Admin/Categories/Requests/CreateCategoryRequest.php

@@ -0,0 +1,20 @@
+<?php
+
+namespace App\Shop\Categories\Requests;
+
+use App\Shop\Base\BaseFormRequest;
+
+class CreateCategoryRequest extends BaseFormRequest
+{
+    /**
+     * Get the validation rules that apply to the request.
+     *
+     * @return array
+     */
+    public function rules()
+    {
+        return [
+            'name' => ['required', 'unique:categories']
+        ];
+    }
+}

+ 31 - 0
app/Http/Controllers/Admin/Categories/Requests/UpdateCategoryRequest.php

@@ -0,0 +1,31 @@
+<?php
+
+namespace App\Shop\Categories\Requests;
+
+use Illuminate\Foundation\Http\FormRequest;
+use Illuminate\Validation\Rule;
+
+class UpdateCategoryRequest extends FormRequest
+{
+    /**
+     * Determine if the user is authorized to make this request.
+     *
+     * @return bool
+     */
+    public function authorize()
+    {
+        return true;
+    }
+
+    /**
+     * Get the validation rules that apply to the request.
+     *
+     * @return array
+     */
+    public function rules()
+    {
+        return [
+            'name' => ['required', Rule::unique('categories')->ignore(request()->segment(3))]
+        ];
+    }
+}

+ 66 - 0
app/Http/Controllers/Admin/Cities/CityController.php

@@ -0,0 +1,66 @@
+<?php
+
+namespace App\Http\Controllers\Admin\Cities;
+
+use App\Shop\Cities\Repositories\CityRepository;
+use App\Shop\Cities\Repositories\Interfaces\CityRepositoryInterface;
+use App\Shop\Cities\Requests\UpdateCityRequest;
+use App\Http\Controllers\Controller;
+
+class CityController extends Controller
+{
+    /**
+     * @var CityRepositoryInterface
+     */
+    private $cityRepo;
+
+    /**
+     * CityController constructor.
+     *
+     * @param CityRepositoryInterface $cityRepository
+     */
+    public function __construct(CityRepositoryInterface $cityRepository)
+    {
+        $this->cityRepo = $cityRepository;
+    }
+
+    /**
+     * Show the edit form
+     *
+     * @param int $countryId
+     * @param int $provinceId
+     * @param string $city
+     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
+     */
+    public function edit($countryId, $provinceId, $city)
+    {
+        $city = $this->cityRepo->findCityByName($city);
+
+        return view('admin.cities.edit', [
+            'countryId' => $countryId,
+            'provinceId' => $provinceId,
+            'city' => $city
+        ]);
+    }
+
+    /**
+     * Update the city
+     *
+     * @param UpdateCityRequest $request
+     * @param int $countryId
+     * @param int $provinceId
+     * @param $city
+     * @return \Illuminate\Http\RedirectResponse
+     */
+    public function update(UpdateCityRequest $request, $countryId, $provinceId, $city)
+    {
+        $city = $this->cityRepo->findCityByName($city);
+
+        $update = new CityRepository($city);
+        $update->updateCity($request->only('name'));
+
+        return redirect()
+            ->route('admin.countries.provinces.cities.edit', [$countryId, $provinceId, $city])
+            ->with('message', 'Update successful');
+    }
+}

+ 79 - 0
app/Http/Controllers/Admin/Countries/CountryController.php

@@ -0,0 +1,79 @@
+<?php
+
+namespace App\Http\Controllers\Admin\Countries;
+
+use App\Shop\Countries\Repositories\CountryRepository;
+use App\Shop\Countries\Repositories\Interfaces\CountryRepositoryInterface;
+use App\Shop\Countries\Requests\UpdateCountryRequest;
+use App\Http\Controllers\Controller;
+
+class CountryController extends Controller
+{
+    private $countryRepo;
+
+    public function __construct(CountryRepositoryInterface $countryRepository)
+    {
+        $this->countryRepo = $countryRepository;
+    }
+
+    /**
+     * Display a listing of the resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function index()
+    {
+        $list = $this->countryRepo->listCountries('created_at', 'desc');
+
+        return view('admin.countries.list', [
+            'countries' => $this->countryRepo->paginateArrayResults($list->all(), 10)
+        ]);
+    }
+
+    /**
+     * Display the specified resource.
+     *
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function show(int $id)
+    {
+        $country = $this->countryRepo->findCountryById($id);
+        $countryRepo = new CountryRepository($country);
+        $provinces = $countryRepo->findProvinces();
+
+        return view('admin.countries.show', [
+            'country' => $country,
+            'provinces' => $this->countryRepo->paginateArrayResults($provinces->toArray())
+        ]);
+    }
+
+    /**
+     * Show the form for editing the specified resource.
+     *
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function edit($id)
+    {
+        return view('admin.countries.edit', ['country' => $this->countryRepo->findCountryById($id)]);
+    }
+
+    /**
+     * Update the specified resource in storage.
+     *
+     * @param  UpdateCountryRequest $request
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function update(UpdateCountryRequest $request, $id)
+    {
+        $country = $this->countryRepo->findCountryById($id);
+
+        $update = new CountryRepository($country);
+        $update->updateCountry($request->except('_method', '_token'));
+
+        $request->session()->flash('message', 'Update successful');
+        return redirect()->route('admin.countries.edit', $id);
+    }
+}

+ 33 - 0
app/Http/Controllers/Admin/Countries/Requests/UpdateCountryRequest.php

@@ -0,0 +1,33 @@
+<?php
+
+namespace App\Shop\Countries\Requests;
+
+use Illuminate\Foundation\Http\FormRequest;
+
+class UpdateCountryRequest extends FormRequest
+{
+    /**
+     * Determine if the user is authorized to make this request.
+     *
+     * @return bool
+     */
+    public function authorize()
+    {
+        return true;
+    }
+
+    /**
+     * Get the validation rules that apply to the request.
+     *
+     * @return array
+     */
+    public function rules()
+    {
+        return [
+            'iso' => ['max:2'],
+            'iso3' => ['max:3'],
+            'numcode' => ['numeric'],
+            'phonecode' => ['numeric']
+        ];
+    }
+}

+ 106 - 0
app/Http/Controllers/Admin/Couriers/CourierController.php

@@ -0,0 +1,106 @@
+<?php
+
+namespace App\Http\Controllers\Admin\Couriers;
+
+use App\Shop\Couriers\Repositories\CourierRepository;
+use App\Shop\Couriers\Repositories\Interfaces\CourierRepositoryInterface;
+use App\Shop\Couriers\Requests\CreateCourierRequest;
+use App\Shop\Couriers\Requests\UpdateCourierRequest;
+use App\Http\Controllers\Controller;
+
+class CourierController extends Controller
+{
+    /**
+     * @var CourierRepositoryInterface
+     */
+    private $courierRepo;
+
+    /**
+     * CourierController constructor.
+     * @param CourierRepositoryInterface $courierRepository
+     */
+    public function __construct(CourierRepositoryInterface $courierRepository)
+    {
+        $this->courierRepo = $courierRepository;
+    }
+
+    /**
+     * Display a listing of the resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function index()
+    {
+        return view('admin.couriers.list', ['couriers' => $this->courierRepo->listCouriers('name', 'asc')]);
+    }
+
+    /**
+     * Show the form for creating a new resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function create()
+    {
+        return view('admin.couriers.create');
+    }
+
+    /**
+     * Store a newly created resource in storage.
+     *
+     * @param  CreateCourierRequest $request
+     * @return \Illuminate\Http\Response
+     */
+    public function store(CreateCourierRequest $request)
+    {
+        $this->courierRepo->createCourier($request->all());
+
+        $request->session()->flash('message', 'Create successful');
+        return redirect()->route('admin.couriers.index');
+    }
+
+    /**
+     * Show the form for editing the specified resource.
+     *
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function edit(int $id)
+    {
+        return view('admin.couriers.edit', ['courier' => $this->courierRepo->findCourierById($id)]);
+    }
+
+    /**
+     * Update the specified resource in storage.
+     *
+     * @param  UpdateCourierRequest  $request
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function update(UpdateCourierRequest $request, $id)
+    {
+        $courier = $this->courierRepo->findCourierById($id);
+
+        $update = new CourierRepository($courier);
+        $update->updateCourier($request->all());
+
+        $request->session()->flash('message', 'Update successful');
+        return redirect()->route('admin.couriers.edit', $id);
+    }
+
+    /**
+     * Remove the specified resource from storage.
+     *
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function destroy(int $id)
+    {
+        $courier = $this->courierRepo->findCourierById($id);
+
+        $courierRepo = new CourierRepository($courier);
+        $courierRepo->delete();
+
+        request()->session()->flash('message', 'Delete successful');
+        return redirect()->route('admin.couriers.index');
+    }
+}

+ 76 - 0
app/Http/Controllers/Admin/Customers/CustomerAddressController.php

@@ -0,0 +1,76 @@
+<?php
+
+namespace App\Http\Controllers\Admin\Customers;
+
+use App\Shop\Addresses\Repositories\Interfaces\AddressRepositoryInterface;
+use App\Shop\Countries\Repositories\Interfaces\CountryRepositoryInterface;
+use App\Http\Controllers\Controller;
+use App\Shop\Provinces\Repositories\Interfaces\ProvinceRepositoryInterface;
+
+class CustomerAddressController extends Controller
+{
+    /**
+     * @var AddressRepositoryInterface
+     */
+    private $addressRepo;
+    /**
+     * @var CountryRepositoryInterface
+     */
+    private $countryRepo;
+    /**
+     * @var ProvinceRepositoryInterface
+     */
+    private $provinceRepo;
+
+    /**
+     * CustomerAddressController constructor.
+     * @param AddressRepositoryInterface $addressRepository
+     * @param CountryRepositoryInterface $countryRepository
+     * @param ProvinceRepositoryInterface $provinceRepository
+     */
+    public function __construct(
+        AddressRepositoryInterface $addressRepository,
+        CountryRepositoryInterface $countryRepository,
+        ProvinceRepositoryInterface $provinceRepository
+    ) {
+        $this->addressRepo = $addressRepository;
+        $this->countryRepo = $countryRepository;
+        $this->provinceRepo = $provinceRepository;
+    }
+
+    /**
+     * Show the customer's address
+     *
+     * @param int $customerId
+     * @param int $addressId
+     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
+     */
+    public function show(int $customerId, int $addressId)
+    {
+        return view('admin.addresses.customers.show', [
+            'address' => $this->addressRepo->findAddressById($addressId),
+            'customerId' => $customerId
+        ]);
+    }
+
+    /**
+     * Show the edit form
+     *
+     * @param int $customerId
+     * @param int $addressId
+     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
+     */
+    public function edit(int $customerId, int $addressId)
+    {
+        $this->countryRepo->findCountryById(env('COUNTRY_ID', 1));
+        $province = $this->provinceRepo->findProvinceById(1);
+
+        return view('admin.addresses.customers.edit', [
+            'address' => $this->addressRepo->findAddressById($addressId),
+            'countries' => $this->countryRepo->listCountries(),
+            'provinces' => $this->countryRepo->findProvinces(),
+            'cities' => $this->provinceRepo->listCities($province->id),
+            'customerId' => $customerId
+        ]);
+    }
+}

+ 145 - 0
app/Http/Controllers/Admin/Customers/CustomerController.php

@@ -0,0 +1,145 @@
+<?php
+
+namespace App\Http\Controllers\Admin\Customers;
+
+use App\Shop\Customers\Customer;
+use App\Shop\Customers\Repositories\CustomerRepository;
+use App\Shop\Customers\Repositories\Interfaces\CustomerRepositoryInterface;
+use App\Shop\Customers\Requests\CreateCustomerRequest;
+use App\Shop\Customers\Requests\UpdateCustomerRequest;
+use App\Shop\Customers\Transformations\CustomerTransformable;
+use App\Http\Controllers\Controller;
+
+class CustomerController extends Controller
+{
+    use CustomerTransformable;
+
+    /**
+     * @var CustomerRepositoryInterface
+     */
+    private $customerRepo;
+
+    /**
+     * CustomerController constructor.
+     * @param CustomerRepositoryInterface $customerRepository
+     */
+    public function __construct(CustomerRepositoryInterface $customerRepository)
+    {
+        $this->customerRepo = $customerRepository;
+    }
+
+    /**
+     * Display a listing of the resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function index()
+    {
+        $list = $this->customerRepo->listCustomers('created_at', 'desc');
+
+        if (request()->has('q')) {
+            $list = $this->customerRepo->searchCustomer(request()->input('q'));
+        }
+
+        $customers = $list->map(function (Customer $customer) {
+            return $this->transformCustomer($customer);
+        })->all();
+
+
+        return view('admin.customers.list', [
+            'customers' => $this->customerRepo->paginateArrayResults($customers)
+        ]);
+    }
+
+    /**
+     * Show the form for creating a new resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function create()
+    {
+        return view('admin.customers.create');
+    }
+
+    /**
+     * Store a newly created resource in storage.
+     *
+     * @param  CreateCustomerRequest $request
+     * @return \Illuminate\Http\Response
+     */
+    public function store(CreateCustomerRequest $request)
+    {
+        $this->customerRepo->createCustomer($request->except('_token', '_method'));
+
+        return redirect()->route('admin.customers.index');
+    }
+
+    /**
+     * Display the specified resource.
+     *
+     * @param  int $id
+     * @return \Illuminate\Http\Response
+     */
+    public function show(int $id)
+    {
+        $customer = $this->customerRepo->findCustomerById($id);
+        
+        return view('admin.customers.show', [
+            'customer' => $customer,
+            'addresses' => $customer->addresses
+        ]);
+    }
+
+    /**
+     * Show the form for editing the specified resource.
+     *
+     * @param  int $id
+     * @return \Illuminate\Http\Response
+     */
+    public function edit($id)
+    {
+        return view('admin.customers.edit', ['customer' => $this->customerRepo->findCustomerById($id)]);
+    }
+
+    /**
+     * Update the specified resource in storage.
+     *
+     * @param  UpdateCustomerRequest $request
+     * @param  int $id
+     * @return \Illuminate\Http\Response
+     */
+    public function update(UpdateCustomerRequest $request, $id)
+    {
+        $customer = $this->customerRepo->findCustomerById($id);
+
+        $update = new CustomerRepository($customer);
+        $data = $request->except('_method', '_token', 'password');
+
+        if ($request->has('password')) {
+            $data['password'] = bcrypt($request->input('password'));
+        }
+
+        $update->updateCustomer($data);
+
+        $request->session()->flash('message', 'Update successful');
+        return redirect()->route('admin.customers.edit', $id);
+    }
+
+    /**
+     * Remove the specified resource from storage.
+     *
+     * @param  int $id
+     *
+     * @return \Illuminate\Http\Response
+     * @throws \Exception
+     */
+    public function destroy($id)
+    {
+        $customer = $this->customerRepo->findCustomerById($id);
+
+        $customerRepo = new CustomerRepository($customer);
+        $customerRepo->deleteCustomer();
+
+        return redirect()->route('admin.customers.index')->with('message', 'Delete successful');
+    }
+}

+ 19 - 0
app/Http/Controllers/Admin/DashboardController.php

@@ -0,0 +1,19 @@
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+use App\Http\Controllers\Controller;
+
+class DashboardController extends Controller
+{
+    public function index()
+    {
+        $breadcumb = [
+            ["name" => "Dashboard", "url" => route("admin.dashboard"), "icon" => "fa fa-dashboard"],
+            ["name" => "Home", "url" => route("admin.dashboard"), "icon" => "fa fa-home"],
+
+        ];
+        populate_breadcumb($breadcumb);
+        return view('admin.dashboard');
+    }
+}

+ 199 - 0
app/Http/Controllers/Admin/EmployeeController.php

@@ -0,0 +1,199 @@
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+use App\Shop\Admins\Requests\CreateEmployeeRequest;
+use App\Shop\Admins\Requests\UpdateEmployeeRequest;
+use App\Shop\Employees\Repositories\EmployeeRepository;
+use App\Shop\Employees\Repositories\Interfaces\EmployeeRepositoryInterface;
+use App\Shop\Roles\Repositories\RoleRepositoryInterface;
+use App\Http\Controllers\Controller;
+use Illuminate\Support\Facades\Hash;
+
+class EmployeeController extends Controller
+{
+    /**
+     * @var EmployeeRepositoryInterface
+     */
+    private $employeeRepo;
+    /**
+     * @var RoleRepositoryInterface
+     */
+    private $roleRepo;
+
+    /**
+     * EmployeeController constructor.
+     *
+     * @param EmployeeRepositoryInterface $employeeRepository
+     * @param RoleRepositoryInterface $roleRepository
+     */
+    public function __construct(
+        EmployeeRepositoryInterface $employeeRepository,
+        RoleRepositoryInterface $roleRepository
+    ) {
+        $this->employeeRepo = $employeeRepository;
+        $this->roleRepo = $roleRepository;
+    }
+
+    /**
+     * Display a listing of the resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function index()
+    {
+        $list = $this->employeeRepo->listEmployees('created_at', 'desc');
+
+        return view('admin.employees.list', [
+            'employees' => $this->employeeRepo->paginateArrayResults($list->all())
+        ]);
+    }
+
+    /**
+     * Show the form for creating a new resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function create()
+    {
+        $roles = $this->roleRepo->listRoles();
+
+        return view('admin.employees.create', compact('roles'));
+    }
+
+    /**
+     * Store a newly created resource in storage.
+     *
+     * @param  CreateEmployeeRequest $request
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function store(CreateEmployeeRequest $request)
+    {
+        $employee = $this->employeeRepo->createEmployee($request->all());
+
+        if ($request->has('role')) {
+            $employeeRepo = new EmployeeRepository($employee);
+            $employeeRepo->syncRoles([$request->input('role')]);
+        }
+
+        return redirect()->route('admin.employees.index');
+    }
+
+    /**
+     * Display the specified resource.
+     *
+     * @param  int $id
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function show(int $id)
+    {
+        $employee = $this->employeeRepo->findEmployeeById($id);
+        return view('admin.employees.show', ['employee' => $employee]);
+    }
+
+    /**
+     * Show the form for editing the specified resource.
+     *
+     * @param  int $id
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function edit(int $id)
+    {
+        $employee = $this->employeeRepo->findEmployeeById($id);
+        $roles = $this->roleRepo->listRoles('created_at', 'desc');
+        $isCurrentUser = $this->employeeRepo->isAuthUser($employee);
+
+        return view(
+            'admin.employees.edit',
+            [
+                'employee' => $employee,
+                'roles' => $roles,
+                'isCurrentUser' => $isCurrentUser,
+                'selectedIds' => $employee->roles()->pluck('role_id')->all()
+            ]
+        );
+    }
+
+    /**
+     * Update the specified resource in storage.
+     *
+     * @param UpdateEmployeeRequest $request
+     * @param  int $id
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function update(UpdateEmployeeRequest $request, $id)
+    {
+        $employee = $this->employeeRepo->findEmployeeById($id);
+        $isCurrentUser = $this->employeeRepo->isAuthUser($employee);
+
+        $empRepo = new EmployeeRepository($employee);
+        $empRepo->updateEmployee($request->except('_token', '_method', 'password'));
+
+        if ($request->has('password') && !empty($request->input('password'))) {
+            $employee->password = Hash::make($request->input('password'));
+            $employee->save();
+        }
+
+        if ($request->has('roles') and !$isCurrentUser) {
+            $employee->roles()->sync($request->input('roles'));
+        } elseif (!$isCurrentUser) {
+            $employee->roles()->detach();
+        }
+
+        return redirect()->route('admin.employees.edit', $id)
+            ->with('message', 'Update successful');
+    }
+
+    /**
+     * Remove the specified resource from storage.
+     *
+     * @param  int $id
+     *
+     * @return \Illuminate\Http\Response
+     * @throws \Exception
+     */
+    public function destroy(int $id)
+    {
+        $employee = $this->employeeRepo->findEmployeeById($id);
+        $employeeRepo = new EmployeeRepository($employee);
+        $employeeRepo->deleteEmployee();
+
+        return redirect()->route('admin.employees.index')->with('message', 'Delete successful');
+    }
+
+    /**
+     * @param $id
+     *
+     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
+     */
+    public function getProfile($id)
+    {
+        $employee = $this->employeeRepo->findEmployeeById($id);
+        return view('admin.employees.profile', ['employee' => $employee]);
+    }
+
+    /**
+     * @param UpdateEmployeeRequest $request
+     * @param $id
+     *
+     * @return \Illuminate\Http\RedirectResponse
+     */
+    public function updateProfile(UpdateEmployeeRequest $request, $id)
+    {
+        $employee = $this->employeeRepo->findEmployeeById($id);
+
+        $update = new EmployeeRepository($employee);
+        $update->updateEmployee($request->except('_token', '_method', 'password'));
+
+        if ($request->has('password') && $request->input('password') != '') {
+            $update->updateEmployee($request->only('password'));
+        }
+
+        return redirect()->route('admin.employee.profile', $id)
+            ->with('message', 'Update successful');
+    }
+}

+ 69 - 0
app/Http/Controllers/Admin/LoginController.php

@@ -0,0 +1,69 @@
+<?php
+
+namespace App\Http\Controllers\Admin;
+
+use App\Shop\Admins\Requests\LoginRequest;
+use App\Http\Controllers\Controller;
+use Illuminate\Foundation\Auth\AuthenticatesUsers;
+
+class LoginController extends Controller
+{
+    use AuthenticatesUsers;
+
+    /**
+     * Where to redirect users after login.
+     *
+     * @var string
+     */
+    protected $redirectTo = '/admin';
+
+
+    /**
+     * Shows the admin login form
+     *
+     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
+     */
+    public function showLoginForm()
+    {
+        if (auth()->guard('employee')->check()) {
+            return redirect()->route('admin.dashboard');
+        }
+
+        return view('auth.admin.login');
+    }
+
+    /**
+     * Login the employee
+     *
+     * @param LoginRequest $request
+     *
+     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\Response
+     * @throws \Illuminate\Validation\ValidationException
+     */
+    public function login(LoginRequest $request)
+    {
+        $this->validateLogin($request);
+
+        // If the class is using the ThrottlesLogins trait, we can automatically throttle
+        // the login attempts for this application. We'll key this by the username and
+        // the IP address of the client making these requests into this application.
+        if ($this->hasTooManyLoginAttempts($request)) {
+            $this->fireLockoutEvent($request);
+
+            return $this->sendLockoutResponse($request);
+        }
+
+        $details = $request->only('email', 'password');
+        $details['status'] = 1;
+        if (auth()->guard('employee')->attempt($details)) {
+            return $this->sendLoginResponse($request);
+        }
+
+        // If the login attempt was unsuccessful we will increment the number of attempts
+        // to login and redirect the user back to the login form. Of course, when this
+        // user surpasses their maximum number of attempts they will get locked out.
+        $this->incrementLoginAttempts($request);
+
+        return $this->sendFailedLoginResponse($request);
+    }
+}

+ 193 - 0
app/Http/Controllers/Admin/Orders/OrderController.php

@@ -0,0 +1,193 @@
+<?php
+
+namespace App\Http\Controllers\Admin\Orders;
+
+use App\Shop\Addresses\Repositories\Interfaces\AddressRepositoryInterface;
+use App\Shop\Addresses\Transformations\AddressTransformable;
+use App\Shop\Couriers\Courier;
+use App\Shop\Couriers\Repositories\CourierRepository;
+use App\Shop\Couriers\Repositories\Interfaces\CourierRepositoryInterface;
+use App\Shop\Customers\Customer;
+use App\Shop\Customers\Repositories\CustomerRepository;
+use App\Shop\Customers\Repositories\Interfaces\CustomerRepositoryInterface;
+use App\Shop\Orders\Order;
+use App\Shop\Orders\Repositories\Interfaces\OrderRepositoryInterface;
+use App\Shop\Orders\Repositories\OrderRepository;
+use App\Shop\OrderStatuses\OrderStatus;
+use App\Shop\OrderStatuses\Repositories\Interfaces\OrderStatusRepositoryInterface;
+use App\Shop\OrderStatuses\Repositories\OrderStatusRepository;
+use App\Http\Controllers\Controller;
+use Illuminate\Http\Request;
+use Illuminate\Support\Collection;
+
+class OrderController extends Controller
+{
+    use AddressTransformable;
+
+    /**
+     * @var OrderRepositoryInterface
+     */
+    private $orderRepo;
+
+    /**
+     * @var CourierRepositoryInterface
+     */
+    private $courierRepo;
+
+    /**
+     * @var CustomerRepositoryInterface
+     */
+    private $customerRepo;
+
+    /**
+     * @var OrderStatusRepositoryInterface
+     */
+    private $orderStatusRepo;
+
+    public function __construct(
+        OrderRepositoryInterface $orderRepository,
+        CourierRepositoryInterface $courierRepository,
+        CustomerRepositoryInterface $customerRepository,
+        OrderStatusRepositoryInterface $orderStatusRepository
+    ) {
+        $this->orderRepo = $orderRepository;
+        $this->courierRepo = $courierRepository;
+        $this->customerRepo = $customerRepository;
+        $this->orderStatusRepo = $orderStatusRepository;
+
+        $this->middleware(['permission:update-order, guard:employee'], ['only' => ['edit', 'update']]);
+    }
+
+    /**
+     * Display a listing of the resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function index()
+    {
+        $list = $this->orderRepo->listOrders('created_at', 'desc');
+
+        if (request()->has('q')) {
+            $list = $this->orderRepo->searchOrder(request()->input('q') ?? '');
+        }
+
+        $orders = $this->orderRepo->paginateArrayResults($this->transFormOrder($list), 10);
+
+        return view('admin.orders.list', ['orders' => $orders]);
+    }
+
+    /**
+     * Display the specified resource.
+     *
+     * @param  int $orderId
+     * @return \Illuminate\Http\Response
+     */
+    public function show($orderId)
+    {
+        $order = $this->orderRepo->findOrderById($orderId);
+
+        $orderRepo = new OrderRepository($order);
+        $order->courier = $orderRepo->getCouriers()->first();
+        $order->address = $orderRepo->getAddresses()->first();
+        $items = $orderRepo->listOrderedProducts();
+
+        return view('admin.orders.show', [
+            'order' => $order,
+            'items' => $items,
+            'customer' => $this->customerRepo->findCustomerById($order->customer_id),
+            'currentStatus' => $this->orderStatusRepo->findOrderStatusById($order->order_status_id),
+            'payment' => $order->payment,
+            'user' => auth()->guard('employee')->user()
+        ]);
+    }
+
+    /**
+     * @param $orderId
+     *
+     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
+     */
+    public function edit($orderId)
+    {
+        $order = $this->orderRepo->findOrderById($orderId);
+
+        $orderRepo = new OrderRepository($order);
+        $order->courier = $orderRepo->getCouriers()->first();
+        $order->address = $orderRepo->getAddresses()->first();
+        $items = $orderRepo->listOrderedProducts();
+
+        return view('admin.orders.edit', [
+            'statuses' => $this->orderStatusRepo->listOrderStatuses(),
+            'order' => $order,
+            'items' => $items,
+            'customer' => $this->customerRepo->findCustomerById($order->customer_id),
+            'currentStatus' => $this->orderStatusRepo->findOrderStatusById($order->order_status_id),
+            'payment' => $order->payment,
+            'user' => auth()->guard('employee')->user()
+        ]);
+    }
+
+    /**
+     * @param Request $request
+     * @param $orderId
+     *
+     * @return \Illuminate\Http\RedirectResponse
+     */
+    public function update(Request $request, $orderId)
+    {
+        $order = $this->orderRepo->findOrderById($orderId);
+        $orderRepo = new OrderRepository($order);
+
+        if ($request->has('total_paid') && $request->input('total_paid') != null) {
+            $orderData = $request->except('_method', '_token');
+        } else {
+            $orderData = $request->except('_method', '_token', 'total_paid');
+        }
+
+        $orderRepo->updateOrder($orderData);
+
+        return redirect()->route('admin.orders.edit', $orderId);
+    }
+
+    /**
+     * Generate order invoice
+     *
+     * @param int $id
+     * @return mixed
+     */
+    public function generateInvoice(int $id)
+    {
+        $order = $this->orderRepo->findOrderById($id);
+
+        $data = [
+            'order' => $order,
+            'products' => $order->products,
+            'customer' => $order->customer,
+            'courier' => $order->courier,
+            'address' => $this->transformAddress($order->address),
+            'status' => $order->orderStatus,
+            'payment' => $order->paymentMethod
+        ];
+
+        $pdf = app()->make('dompdf.wrapper');
+        $pdf->loadView('invoices.orders', $data)->stream();
+        return $pdf->stream();
+    }
+
+    /**
+     * @param Collection $list
+     * @return array
+     */
+    private function transFormOrder(Collection $list)
+    {
+        $courierRepo = new CourierRepository(new Courier());
+        $customerRepo = new CustomerRepository(new Customer());
+        $orderStatusRepo = new OrderStatusRepository(new OrderStatus());
+
+        return $list->transform(function (Order $order) use ($courierRepo, $customerRepo, $orderStatusRepo) {
+            $order->courier = $courierRepo->findCourierById($order->courier_id);
+            $order->customer = $customerRepo->findCustomerById($order->customer_id);
+            $order->status = $orderStatusRepo->findOrderStatusById($order->order_status_id);
+            return $order;
+        })->all();
+    }
+}

+ 98 - 0
app/Http/Controllers/Admin/Orders/OrderStatusController.php

@@ -0,0 +1,98 @@
+<?php
+
+namespace App\Http\Controllers\Admin\Orders;
+
+use App\Shop\OrderStatuses\Repositories\Interfaces\OrderStatusRepositoryInterface;
+use App\Shop\OrderStatuses\Repositories\OrderStatusRepository;
+use App\Shop\OrderStatuses\Requests\CreateOrderStatusRequest;
+use App\Shop\OrderStatuses\Requests\UpdateOrderStatusRequest;
+use Illuminate\Database\QueryException;
+use Illuminate\Http\Request;
+use App\Http\Controllers\Controller;
+
+class OrderStatusController extends Controller
+{
+    private $orderStatuses;
+
+
+    public function __construct(OrderStatusRepositoryInterface $orderStatusRepository)
+    {
+        $this->orderStatuses = $orderStatusRepository;
+    }
+
+    /**
+     * Display a listing of the resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function index()
+    {
+        return view('admin.order-statuses.list', ['orderStatuses' => $this->orderStatuses->listOrderStatuses()]);
+    }
+
+    /**
+     * Show the form for creating a new resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function create()
+    {
+        return view('admin.order-statuses.create');
+    }
+
+    /**
+     * Store a newly created resource in storage.
+     *
+     * @param  CreateOrderStatusRequest $request
+     * @return \Illuminate\Http\Response
+     */
+    public function store(CreateOrderStatusRequest $request)
+    {
+        $this->orderStatuses->createOrderStatus($request->except('_token', '_method'));
+        $request->session()->flash('message', 'Create successful');
+        return redirect()->route('admin.order-statuses.index');
+    }
+
+    /**
+     * Show the form for editing the specified resource.
+     *
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function edit(int $id)
+    {
+        return view('admin.order-statuses.edit', ['orderStatus' => $this->orderStatuses->findOrderStatusById($id)]);
+    }
+
+    /**
+     * Update the specified resource in storage.
+     *
+     * @param  UpdateOrderStatusRequest $request
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function update(UpdateOrderStatusRequest $request, int $id)
+    {
+        $orderStatus = $this->orderStatuses->findOrderStatusById($id);
+
+        $update = new OrderStatusRepository($orderStatus);
+        $update->updateOrderStatus($request->all());
+
+        $request->session()->flash('message', 'Update successful');
+        return redirect()->route('admin.order-statuses.edit', $id);
+    }
+
+    /**
+     * Remove the specified resource from storage.
+     *
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function destroy(int $id)
+    {
+        $this->orderStatuses->findOrderStatusById($id)->delete();
+
+        request()->session()->flash('message', 'Delete successful');
+        return redirect()->route('admin.order-statuses.index');
+    }
+}

+ 36 - 0
app/Http/Controllers/Admin/Permissions/PermissionController.php

@@ -0,0 +1,36 @@
+<?php
+
+namespace App\Http\Controllers\Admin\Permissions;
+
+use App\Http\Controllers\Controller;
+use App\Shop\Permissions\Repositories\PermissionRepository;
+
+class PermissionController extends Controller
+{
+    /**
+     * @var PermissionRepository
+     */
+    private $permRepo;
+
+    /**
+     * PermissionController constructor.
+     *
+     * @param PermissionRepository $permissionRepository
+     */
+    public function __construct(PermissionRepository $permissionRepository)
+    {
+        $this->permRepo = $permissionRepository;
+    }
+
+    /**
+     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
+     */
+    public function index()
+    {
+        $list = $this->permRepo->listPermissions();
+
+        $permissions = $this->permRepo->paginateArrayResults($list->all());
+
+        return view('admin.permissions.list', compact('permissions'));
+    }
+}

+ 388 - 0
app/Http/Controllers/Admin/Products/ProductController.php

@@ -0,0 +1,388 @@
+<?php
+
+namespace App\Http\Controllers\Admin\Products;
+
+use App\Shop\Attributes\Repositories\AttributeRepositoryInterface;
+use App\Shop\AttributeValues\Repositories\AttributeValueRepositoryInterface;
+use App\Shop\Brands\Repositories\BrandRepositoryInterface;
+use App\Shop\Categories\Repositories\Interfaces\CategoryRepositoryInterface;
+use App\Shop\ProductAttributes\ProductAttribute;
+use App\Shop\Products\Exceptions\ProductInvalidArgumentException;
+use App\Shop\Products\Exceptions\ProductNotFoundException;
+use App\Shop\Products\Product;
+use App\Shop\Products\Repositories\Interfaces\ProductRepositoryInterface;
+use App\Shop\Products\Repositories\ProductRepository;
+use App\Shop\Products\Requests\CreateProductRequest;
+use App\Shop\Products\Requests\UpdateProductRequest;
+use App\Http\Controllers\Controller;
+use App\Shop\Products\Transformations\ProductTransformable;
+use App\Shop\Tools\UploadableTrait;
+use Illuminate\Http\Request;
+use Illuminate\Http\UploadedFile;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
+use Illuminate\Support\Facades\Validator;
+
+class ProductController extends Controller
+{
+    use ProductTransformable, UploadableTrait;
+
+    /**
+     * @var ProductRepositoryInterface
+     */
+    private $productRepo;
+
+    /**
+     * @var CategoryRepositoryInterface
+     */
+    private $categoryRepo;
+
+    /**
+     * @var AttributeRepositoryInterface
+     */
+    private $attributeRepo;
+
+    /**
+     * @var AttributeValueRepositoryInterface
+     */
+    private $attributeValueRepository;
+
+    /**
+     * @var ProductAttribute
+     */
+    private $productAttribute;
+
+    /**
+     * @var BrandRepositoryInterface
+     */
+    private $brandRepo;
+
+    /**
+     * ProductController constructor.
+     *
+     * @param ProductRepositoryInterface $productRepository
+     * @param CategoryRepositoryInterface $categoryRepository
+     * @param AttributeRepositoryInterface $attributeRepository
+     * @param AttributeValueRepositoryInterface $attributeValueRepository
+     * @param ProductAttribute $productAttribute
+     * @param BrandRepositoryInterface $brandRepository
+     */
+    public function __construct(
+        ProductRepositoryInterface $productRepository,
+        CategoryRepositoryInterface $categoryRepository,
+        AttributeRepositoryInterface $attributeRepository,
+        AttributeValueRepositoryInterface $attributeValueRepository,
+        ProductAttribute $productAttribute,
+        BrandRepositoryInterface $brandRepository
+    ) {
+        $this->productRepo = $productRepository;
+        $this->categoryRepo = $categoryRepository;
+        $this->attributeRepo = $attributeRepository;
+        $this->attributeValueRepository = $attributeValueRepository;
+        $this->productAttribute = $productAttribute;
+        $this->brandRepo = $brandRepository;
+
+        $this->middleware(['permission:create-product, guard:employee'], ['only' => ['create', 'store']]);
+        $this->middleware(['permission:update-product, guard:employee'], ['only' => ['edit', 'update']]);
+        $this->middleware(['permission:delete-product, guard:employee'], ['only' => ['destroy']]);
+        $this->middleware(['permission:view-product, guard:employee'], ['only' => ['index', 'show']]);
+    }
+
+    /**
+     * Display a listing of the resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function index()
+    {
+        $list = $this->productRepo->listProducts('id');
+
+        if (request()->has('q') && request()->input('q') != '') {
+            $list = $this->productRepo->searchProduct(request()->input('q'));
+        }
+
+        $products = $list->map(function (Product $item) {
+            return $this->transformProduct($item);
+        })->all();
+
+        return view('admin.products.list', [
+            'products' => $this->productRepo->paginateArrayResults($products, 25)
+        ]);
+    }
+
+    /**
+     * Show the form for creating a new resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function create()
+    {
+        $categories = $this->categoryRepo->listCategories('name', 'asc');
+
+        return view('admin.products.create', [
+            'categories' => $categories,
+            'brands' => $this->brandRepo->listBrands(['*'], 'name', 'asc'),
+            'default_weight' => env('SHOP_WEIGHT'),
+            'weight_units' => Product::MASS_UNIT,
+            'product' => new Product
+        ]);
+    }
+
+    /**
+     * Store a newly created resource in storage.
+     *
+     * @param  CreateProductRequest $request
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function store(CreateProductRequest $request)
+    {
+        $data = $request->except('_token', '_method');
+        $data['slug'] = str_slug($request->input('name'));
+
+        if ($request->hasFile('cover') && $request->file('cover') instanceof UploadedFile) {
+            $data['cover'] = $this->productRepo->saveCoverImage($request->file('cover'));
+        }
+
+        $product = $this->productRepo->createProduct($data);
+
+        $productRepo = new ProductRepository($product);
+
+        if ($request->hasFile('image')) {
+            $productRepo->saveProductImages(collect($request->file('image')));
+        }
+
+        if ($request->has('categories')) {
+            $productRepo->syncCategories($request->input('categories'));
+        } else {
+            $productRepo->detachCategories();
+        }
+
+        return redirect()->route('admin.products.edit', $product->id)->with('message', 'Create successful');
+    }
+
+    /**
+     * Display the specified resource.
+     *
+     * @param  int $id
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function show(int $id)
+    {
+        $product = $this->productRepo->findProductById($id);
+        return view('admin.products.show', compact('product'));
+    }
+
+    /**
+     * Show the form for editing the specified resource.
+     *
+     * @param  int $id
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function edit(int $id)
+    {
+        $product = $this->productRepo->findProductById($id);
+        $productAttributes = $product->attributes()->get();
+
+        $qty = $productAttributes->map(function ($item) {
+            return $item->quantity;
+        })->sum();
+
+        if (request()->has('delete') && request()->has('pa')) {
+            $pa = $productAttributes->where('id', request()->input('pa'))->first();
+            $pa->attributesValues()->detach();
+            $pa->delete();
+
+            request()->session()->flash('message', 'Delete successful');
+            return redirect()->route('admin.products.edit', [$product->id, 'combination' => 1]);
+        }
+
+        $categories = $this->categoryRepo->listCategories('name', 'asc')->toTree();
+	
+        return view('admin.products.edit', [
+            'product' => $product,
+            'images' => $product->images()->get(['src']),
+            'categories' => $categories,
+            'selectedIds' => $product->categories()->pluck('category_id')->all(),
+            'attributes' => $this->attributeRepo->listAttributes(),
+            'productAttributes' => $productAttributes,
+            'qty' => $qty,
+            'brands' => $this->brandRepo->listBrands(['*'], 'name', 'asc'),
+            'weight' => $product->weight,
+            'default_weight' => $product->mass_unit,
+            'weight_units' => Product::MASS_UNIT
+        ]);
+    }
+
+    /**
+     * Update the specified resource in storage.
+     *
+     * @param  UpdateProductRequest $request
+     * @param  int $id
+     *
+     * @return \Illuminate\Http\Response
+     * @throws \App\Shop\Products\Exceptions\ProductUpdateErrorException
+     */
+    public function update(UpdateProductRequest $request, int $id)
+    {
+        $product = $this->productRepo->findProductById($id);
+        $productRepo = new ProductRepository($product);
+
+        if ($request->has('attributeValue')) {
+            $this->saveProductCombinations($request, $product);
+            return redirect()->route('admin.products.edit', [$id, 'combination' => 1])
+                ->with('message', 'Attribute combination created successful');
+        }
+
+        $data = $request->except(
+            'categories',
+            '_token',
+            '_method',
+            'default',
+            'image',
+            'productAttributeQuantity',
+            'productAttributePrice',
+            'attributeValue',
+            'combination'
+        );
+
+        $data['slug'] = str_slug($request->input('name'));
+
+        if ($request->hasFile('cover')) {
+            $data['cover'] = $productRepo->saveCoverImage($request->file('cover'));
+        }
+
+        if ($request->hasFile('image')) {
+            $productRepo->saveProductImages(collect($request->file('image')));
+        }
+
+        if ($request->has('categories')) {
+            $productRepo->syncCategories($request->input('categories'));
+        } else {
+            $productRepo->detachCategories();
+        }
+
+        $productRepo->updateProduct($data);
+
+        return redirect()->route('admin.products.edit', $id)
+            ->with('message', 'Update successful');
+    }
+
+    /**
+     * Remove the specified resource from storage.
+     *
+     * @param  int $id
+     *
+     * @return \Illuminate\Http\Response
+     * @throws \Exception
+     */
+    public function destroy($id)
+    {
+        $product = $this->productRepo->findProductById($id);
+        $product->categories()->sync([]);
+        $productAttr = $product->attributes();
+
+        $productAttr->each(function ($pa) {
+            DB::table('attribute_value_product_attribute')->where('product_attribute_id', $pa->id)->delete();
+        });
+
+        $productAttr->where('product_id', $product->id)->delete();
+
+        $productRepo = new ProductRepository($product);
+        $productRepo->removeProduct();
+
+        return redirect()->route('admin.products.index')->with('message', 'Delete successful');
+    }
+
+    /**
+     * @param Request $request
+     *
+     * @return \Illuminate\Http\RedirectResponse
+     */
+    public function removeImage(Request $request)
+    {
+        $this->productRepo->deleteFile($request->only('product', 'image'), 'uploads');
+        return redirect()->back()->with('message', 'Image delete successful');
+    }
+
+    /**
+     * @param Request $request
+     *
+     * @return \Illuminate\Http\RedirectResponse
+     */
+    public function removeThumbnail(Request $request)
+    {
+        $this->productRepo->deleteThumb($request->input('src'));
+        return redirect()->back()->with('message', 'Image delete successful');
+    }
+
+    /**
+     * @param Request $request
+     * @param Product $product
+     * @return boolean
+     */
+    private function saveProductCombinations(Request $request, Product $product): bool
+    {
+        $fields = $request->only(
+            'productAttributeQuantity',
+            'productAttributePrice',
+            'sale_price',
+            'default'
+        );
+
+        if ($errors = $this->validateFields($fields)) {
+            return redirect()->route('admin.products.edit', [$product->id, 'combination' => 1])
+                ->withErrors($errors);
+        }
+
+        $quantity = $fields['productAttributeQuantity'];
+        $price = $fields['productAttributePrice'];
+
+        $sale_price = null;
+        if (isset($fields['sale_price'])) {
+            $sale_price = $fields['sale_price'];
+        }
+
+        $attributeValues = $request->input('attributeValue');
+        $productRepo = new ProductRepository($product);
+
+        $hasDefault = $productRepo->listProductAttributes()->where('default', 1)->count();
+
+        $default = 0;
+        if ($request->has('default')) {
+            $default = $fields['default'];
+        }
+
+        if ($default == 1 && $hasDefault > 0) {
+            $default = 0;
+        }
+
+        $productAttribute = $productRepo->saveProductAttributes(
+            new ProductAttribute(compact('quantity', 'price', 'sale_price', 'default'))
+        );
+
+        // save the combinations
+        return collect($attributeValues)->each(function ($attributeValueId) use ($productRepo, $productAttribute) {
+            $attribute = $this->attributeValueRepository->find($attributeValueId);
+            return $productRepo->saveCombination($productAttribute, $attribute);
+        })->count();
+    }
+
+    /**
+     * @param array $data
+     *
+     * @return
+     */
+    private function validateFields(array $data)
+    {
+        $validator = Validator::make($data, [
+            'productAttributeQuantity' => 'required'
+        ]);
+
+        if ($validator->fails()) {
+            return $validator;
+        }
+    }
+}

+ 70 - 0
app/Http/Controllers/Admin/Provinces/ProvinceController.php

@@ -0,0 +1,70 @@
+<?php
+
+namespace App\Http\Controllers\Admin\Provinces;
+
+use App\Shop\Provinces\Repositories\Interfaces\ProvinceRepositoryInterface;
+use App\Http\Controllers\Controller;
+use App\Shop\Provinces\Repositories\ProvinceRepository;
+use Illuminate\Http\Request;
+
+class ProvinceController extends Controller
+{
+    protected $provinceRepo;
+
+    public function __construct(ProvinceRepositoryInterface $provinceRepository)
+    {
+        $this->provinceRepo = $provinceRepository;
+    }
+
+    /**
+     * Display the specified resource.
+     *
+     * @param int $provinceId
+     * @param int $countryId
+     * @return \Illuminate\Http\Response
+     */
+    public function show(int $countryId, int $provinceId)
+    {
+        $province = $this->provinceRepo->findProvinceById($provinceId);
+        $cities = $this->provinceRepo->listCities($provinceId);
+
+        return view('admin.provinces.show', [
+            'province' => $province,
+            'countryId' => $countryId,
+            'cities' => $this->provinceRepo->paginateArrayResults(collect($cities)->toArray())
+        ]);
+    }
+
+    /**
+     * Show the form for editing the specified resource.
+     *
+     * @param int $provinceId
+     * @param int $countryId
+     * @return \Illuminate\Http\Response
+     */
+    public function edit(int $countryId, int $provinceId)
+    {
+        return view('admin.provinces.edit', [
+            'province' => $this->provinceRepo->findProvinceById($provinceId),
+            'countryId' => $countryId
+        ]);
+    }
+
+    /**
+     * Update the specified resource in storage.
+     *
+     * @param  Request $request
+     * @param int $provinceId
+     * @param int $countryId
+     * @return \Illuminate\Http\Response
+     */
+    public function update(Request $request, int $countryId, int $provinceId)
+    {
+        $province = $this->provinceRepo->findProvinceById($provinceId);
+        $update = new ProvinceRepository($province);
+        $update->updateProvince($request->except('_method', '_token'));
+
+        $request->session()->flash('message', 'Update successful');
+        return redirect()->route('admin.countries.provinces.edit', [$countryId, $provinceId]);
+    }
+}

+ 111 - 0
app/Http/Controllers/Admin/Roles/RoleController.php

@@ -0,0 +1,111 @@
+<?php
+
+namespace App\Http\Controllers\Admin\Roles;
+
+use App\Http\Controllers\Controller;
+use App\Shop\Permissions\Repositories\Interfaces\PermissionRepositoryInterface;
+use App\Shop\Roles\Repositories\RoleRepository;
+use App\Shop\Roles\Repositories\RoleRepositoryInterface;
+use App\Shop\Roles\Requests\CreateRoleRequest;
+use App\Shop\Roles\Requests\UpdateRoleRequest;
+
+class RoleController extends Controller
+{
+    /**
+     * @var RoleRepositoryInterface
+     */
+    private $roleRepo;
+
+    /**
+     * @var PermissionRepositoryInterface
+     */
+    private $permissionRepository;
+
+    /**
+     * RoleController constructor.
+     *
+     * @param RoleRepositoryInterface $roleRepository
+     * @param PermissionRepositoryInterface $permissionRepository
+     */
+    public function __construct(
+        RoleRepositoryInterface $roleRepository,
+        PermissionRepositoryInterface $permissionRepository
+    ) {
+        $this->roleRepo = $roleRepository;
+        $this->permissionRepository = $permissionRepository;
+    }
+
+    /**
+     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
+     */
+    public function index()
+    {
+        $list = $this->roleRepo->listRoles('name', 'asc')->all();
+
+        $roles = $this->roleRepo->paginateArrayResults($list);
+
+        return view('admin.roles.list', compact('roles'));
+    }
+
+    /**
+     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
+     */
+    public function create()
+    {
+        return view('admin.roles.create');
+    }
+
+    /**
+     * @param CreateRoleRequest $request
+     *
+     * @return \Illuminate\Http\RedirectResponse
+     */
+    public function store(CreateRoleRequest $request)
+    {
+        $this->roleRepo->createRole($request->except('_method', '_token'));
+
+        return redirect()->route('admin.roles.index')
+            ->with('message', 'Create role successful!');
+    }
+
+    /**
+     * @param $id
+     *
+     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
+     */
+    public function edit($id)
+    {
+        $role = $this->roleRepo->findRoleById($id);
+
+        $roleRepo = new RoleRepository($role);
+        $attachedPermissionsArrayIds = $roleRepo->listPermissions()->pluck('id')->all();
+        $permissions = $this->permissionRepository->listPermissions(['*'], 'name', 'asc');
+
+        return view('admin.roles.edit', compact(
+            'role',
+            'permissions',
+            'attachedPermissionsArrayIds'
+        ));
+    }
+
+    /**
+     * @param UpdateRoleRequest $request
+     * @param $id
+     *
+     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
+     */
+    public function update(UpdateRoleRequest $request, $id)
+    {
+        $role = $this->roleRepo->findRoleById($id);
+
+        if ($request->has('permissions')) {
+            $roleRepo = new RoleRepository($role);
+            $roleRepo->syncPermissions($request->input('permissions'));
+        }
+
+        $this->roleRepo->updateRole($request->except('_method', '_token'), $id);
+
+        return redirect()->route('admin.roles.edit', $id)
+            ->with('message', 'Update role successful!');
+    }
+}

+ 78 - 0
app/Http/Controllers/Auth/CartLoginController.php

@@ -0,0 +1,78 @@
+<?php
+
+namespace App\Http\Controllers\Auth;
+
+use App\Http\Controllers\Controller;
+use App\Shop\Admins\Requests\LoginRequest;
+use Illuminate\Foundation\Auth\AuthenticatesUsers;
+use Illuminate\Support\Facades\Auth;
+
+class CartLoginController extends Controller
+{
+    /*
+    |--------------------------------------------------------------------------
+    | Login Controller
+    |--------------------------------------------------------------------------
+    |
+    | This controller handles authenticating users for the application and
+    | redirecting them to your home screen. The controller uses a trait
+    | to conveniently provide its functionality to your applications.
+    |
+    */
+
+    use AuthenticatesUsers;
+
+    /**
+     * Where to redirect users after login.
+     *
+     * @var string
+     */
+    protected $redirectTo = '/checkout';
+
+    /**
+     * Create a new controller instance.
+     *
+     */
+    public function __construct()
+    {
+        $this->middleware('guest')->except('logout');
+    }
+
+    public function showLoginForm()
+    {
+        return view('front.carts.login');
+    }
+
+    /**
+     * Login the customer
+     *
+     * @param LoginRequest $request
+     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\Response
+     */
+    public function login(LoginRequest $request)
+    {
+        $this->validateLogin($request);
+
+        // If the class is using the ThrottlesLogins trait, we can automatically throttle
+        // the login attempts for this application. We'll key this by the username and
+        // the IP address of the client making these requests into this application.
+        if ($this->hasTooManyLoginAttempts($request)) {
+            $this->fireLockoutEvent($request);
+
+            return $this->sendLockoutResponse($request);
+        }
+
+        $details = $request->only('email', 'password');
+        $details['status'] = 1;
+        if (auth()->attempt($details)) {
+            return $this->sendLoginResponse($request);
+        }
+
+        // If the login attempt was unsuccessful we will increment the number of attempts
+        // to login and redirect the user back to the login form. Of course, when this
+        // user surpasses their maximum number of attempts they will get locked out.
+        $this->incrementLoginAttempts($request);
+
+        return $this->sendFailedLoginResponse($request);
+    }
+}

+ 32 - 0
app/Http/Controllers/Auth/ForgotPasswordController.php

@@ -0,0 +1,32 @@
+<?php
+
+namespace App\Http\Controllers\Auth;
+
+use App\Http\Controllers\Controller;
+use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
+
+class ForgotPasswordController extends Controller
+{
+    /*
+    |--------------------------------------------------------------------------
+    | Password Reset Controller
+    |--------------------------------------------------------------------------
+    |
+    | This controller is responsible for handling password reset emails and
+    | includes a trait which assists in sending these notifications from
+    | your application to your users. Feel free to explore this trait.
+    |
+    */
+
+    use SendsPasswordResetEmails;
+
+    /**
+     * Create a new controller instance.
+     *
+     * @return void
+     */
+    public function __construct()
+    {
+        $this->middleware('guest');
+    }
+}

+ 79 - 0
app/Http/Controllers/Auth/LoginController.php

@@ -0,0 +1,79 @@
+<?php
+
+namespace App\Http\Controllers\Auth;
+
+use App\Shop\Admins\Requests\LoginRequest;
+use App\Http\Controllers\Controller;
+use Illuminate\Foundation\Auth\AuthenticatesUsers;
+
+class LoginController extends Controller
+{
+    /*
+    |--------------------------------------------------------------------------
+    | Login Controller
+    |--------------------------------------------------------------------------
+    |
+    | This controller handles authenticating users for the application and
+    | redirecting them to your home screen. The controller uses a trait
+    | to conveniently provide its functionality to your applications.
+    |
+    */
+
+    use AuthenticatesUsers;
+
+    /**
+     * Where to redirect users after login.
+     *
+     * @var string
+     */
+    protected $redirectTo = '/accounts?tab=profile';
+
+    /**
+     * Create a new controller instance.
+     *
+     */
+    public function __construct()
+    {
+        $this->middleware('guest')->except('logout');
+    }
+
+    public function showLoginForm()
+    {
+        return view('auth.login');
+    }
+
+    /**
+     * Login the admin
+     *
+     * @param LoginRequest $request
+     *
+     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\Response
+     * @throws \Illuminate\Validation\ValidationException
+     */
+    public function login(LoginRequest $request)
+    {
+        $this->validateLogin($request);
+
+        // If the class is using the ThrottlesLogins trait, we can automatically throttle
+        // the login attempts for this application. We'll key this by the username and
+        // the IP address of the client making these requests into this application.
+        if ($this->hasTooManyLoginAttempts($request)) {
+            $this->fireLockoutEvent($request);
+
+            return $this->sendLockoutResponse($request);
+        }
+
+        $details = $request->only('email', 'password');
+        $details['status'] = 1;
+        if (auth()->attempt($details)) {
+            return $this->sendLoginResponse($request);
+        }
+
+        // If the login attempt was unsuccessful we will increment the number of attempts
+        // to login and redirect the user back to the login form. Of course, when this
+        // user surpasses their maximum number of attempts they will get locked out.
+        $this->incrementLoginAttempts($request);
+
+        return $this->sendFailedLoginResponse($request);
+    }
+}

+ 71 - 0
app/Http/Controllers/Auth/RegisterController.php

@@ -0,0 +1,71 @@
+<?php
+
+namespace App\Http\Controllers\Auth;
+
+use App\Shop\Customers\Customer;
+use App\Http\Controllers\Controller;
+use App\Shop\Customers\Repositories\Interfaces\CustomerRepositoryInterface;
+use App\Shop\Customers\Requests\CreateCustomerRequest;
+use App\Shop\Customers\Requests\RegisterCustomerRequest;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Auth;
+use Illuminate\Support\Facades\Validator;
+use Illuminate\Foundation\Auth\RegistersUsers;
+
+class RegisterController extends Controller
+{
+    /*
+    |--------------------------------------------------------------------------
+    | Register Controller
+    |--------------------------------------------------------------------------
+    |
+    | This controller handles the registration of new users as well as their
+    | validation and creation. By default this controller uses a trait to
+    | provide this functionality without requiring any additional code.
+    |
+    */
+
+    use RegistersUsers;
+
+    /**
+     * Where to redirect users after registration.
+     *
+     * @var string
+     */
+    protected $redirectTo = '/accounts';
+
+    private $customerRepo;
+
+    /**
+     * Create a new controller instance.
+     * @param CustomerRepositoryInterface $customerRepository
+     */
+    public function __construct(CustomerRepositoryInterface $customerRepository)
+    {
+        $this->middleware('guest');
+        $this->customerRepo = $customerRepository;
+    }
+
+    /**
+     * Create a new user instance after a valid registration.
+     *
+     * @param  array  $data
+     * @return Customer
+     */
+    protected function create(array $data)
+    {
+        return $this->customerRepo->createCustomer($data);
+    }
+
+    /**
+     * @param RegisterCustomerRequest $request
+     * @return \Illuminate\Http\RedirectResponse
+     */
+    public function register(RegisterCustomerRequest $request)
+    {
+        $customer = $this->create($request->except('_method', '_token'));
+        Auth::login($customer);
+
+        return redirect()->route('accounts');
+    }
+}

+ 38 - 0
app/Http/Controllers/Auth/ResetPasswordController.php

@@ -0,0 +1,38 @@
+<?php
+
+namespace App\Http\Controllers\Auth;
+
+use App\Http\Controllers\Controller;
+use Illuminate\Foundation\Auth\ResetsPasswords;
+
+class ResetPasswordController extends Controller
+{
+    /*
+    |--------------------------------------------------------------------------
+    | Password Reset Controller
+    |--------------------------------------------------------------------------
+    |
+    | This controller is responsible for handling password reset requests
+    | and uses a simple trait to include this behavior. You're free to
+    | explore this trait and override any methods you wish to tweak.
+    |
+    */
+
+    use ResetsPasswords;
+
+    /**
+     * Where to redirect users after resetting their password.
+     *
+     * @var string
+     */
+    protected $redirectTo = '/accounts';
+
+    /**
+     * Create a new controller instance.
+     *
+     */
+    public function __construct()
+    {
+        $this->middleware('guest');
+    }
+}

+ 18 - 0
app/Http/Controllers/Controller.php

@@ -0,0 +1,18 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use Illuminate\Foundation\Bus\DispatchesJobs;
+use Illuminate\Routing\Controller as BaseController;
+use Illuminate\Foundation\Validation\ValidatesRequests;
+use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
+
+class Controller extends BaseController
+{
+    use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
+
+    protected function loggedUser()
+    {
+        return auth()->user();
+    }
+}

+ 61 - 0
app/Http/Controllers/Front/AccountsController.php

@@ -0,0 +1,61 @@
+<?php
+
+namespace App\Http\Controllers\Front;
+
+use App\Shop\Couriers\Repositories\Interfaces\CourierRepositoryInterface;
+use App\Shop\Customers\Repositories\CustomerRepository;
+use App\Shop\Customers\Repositories\Interfaces\CustomerRepositoryInterface;
+use App\Http\Controllers\Controller;
+use App\Shop\Orders\Order;
+use App\Shop\Orders\Transformers\OrderTransformable;
+
+class AccountsController extends Controller
+{
+    use OrderTransformable;
+
+    /**
+     * @var CustomerRepositoryInterface
+     */
+    private $customerRepo;
+
+    /**
+     * @var CourierRepositoryInterface
+     */
+    private $courierRepo;
+
+    /**
+     * AccountsController constructor.
+     *
+     * @param CourierRepositoryInterface $courierRepository
+     * @param CustomerRepositoryInterface $customerRepository
+     */
+    public function __construct(
+        CourierRepositoryInterface $courierRepository,
+        CustomerRepositoryInterface $customerRepository
+    ) {
+        $this->customerRepo = $customerRepository;
+        $this->courierRepo = $courierRepository;
+    }
+
+    public function index()
+    {
+        $customer = $this->customerRepo->findCustomerById(auth()->user()->id);
+
+        $customerRepo = new CustomerRepository($customer);
+        $orders = $customerRepo->findOrders(['*'], 'created_at');
+
+        $orders->transform(function (Order $order) {
+            return $this->transformOrder($order);
+        });
+
+        $orders->load('products');
+
+        $addresses = $customerRepo->findAddresses();
+
+        return view('front.accounts', [
+            'customer' => $customer,
+            'orders' => $this->customerRepo->paginateArrayResults($orders->toArray(), 15),
+            'addresses' => $addresses
+        ]);
+    }
+}

+ 37 - 0
app/Http/Controllers/Front/Addresses/CountryStateController.php

@@ -0,0 +1,37 @@
+<?php
+
+namespace App\Http\Controllers\Front\Addresses;
+
+use App\Http\Controllers\Controller;
+use App\Shop\Countries\Repositories\CountryRepository;
+use App\Shop\Countries\Repositories\Interfaces\CountryRepositoryInterface;
+
+class CountryStateController extends Controller
+{
+    private $countryRepo;
+
+    /**
+     * CountryStateController constructor.
+     *
+     * @param CountryRepositoryInterface $countryRepository
+     */
+    public function __construct(CountryRepositoryInterface $countryRepository)
+    {
+        $this->countryRepo = $countryRepository;
+    }
+
+    /**
+     * @param $countryId
+     *
+     * @return \Illuminate\Http\JsonResponse
+     */
+    public function index($countryId)
+    {
+        $country = $this->countryRepo->findCountryById($countryId);
+
+        $countryRepo = new CountryRepository($country);
+        $data = $countryRepo->listStates();
+
+        return response()->json(compact('data'));
+    }
+}

+ 37 - 0
app/Http/Controllers/Front/Addresses/StateCityController.php

@@ -0,0 +1,37 @@
+<?php
+
+namespace App\Http\Controllers\Front\Addresses;
+
+use App\Http\Controllers\Controller;
+use App\Shop\States\Repositories\StateRepository;
+use App\Shop\States\Repositories\StateRepositoryInterface;
+
+class StateCityController extends Controller
+{
+    private $stateRepo;
+
+    /**
+     * StateCityController constructor.
+     *
+     * @param StateRepositoryInterface $stateRepository
+     */
+    public function __construct(StateRepositoryInterface $stateRepository)
+    {
+        $this->stateRepo = $stateRepository;
+    }
+
+    /**
+     * @param $state_code
+     *
+     * @return \Illuminate\Http\JsonResponse
+     */
+    public function index($state_code)
+    {
+        $state = $this->stateRepo->findOneBy(compact('state_code'));
+
+        $stateRepo = new StateRepository($state);
+        $data = $stateRepo->listCities();
+
+        return response()->json(compact('data'));
+    }
+}

+ 146 - 0
app/Http/Controllers/Front/CartController.php

@@ -0,0 +1,146 @@
+<?php
+
+namespace App\Http\Controllers\Front;
+
+use App\Shop\Carts\Requests\AddToCartRequest;
+use App\Shop\Carts\Requests\UpdateCartRequest;
+use App\Shop\Carts\Repositories\Interfaces\CartRepositoryInterface;
+use App\Shop\Couriers\Repositories\Interfaces\CourierRepositoryInterface;
+use App\Shop\ProductAttributes\Repositories\ProductAttributeRepositoryInterface;
+use App\Shop\Products\Product;
+use App\Shop\Products\Repositories\Interfaces\ProductRepositoryInterface;
+use App\Shop\Products\Repositories\ProductRepository;
+use App\Shop\Products\Transformations\ProductTransformable;
+use Gloudemans\Shoppingcart\CartItem;
+use Illuminate\Http\Request;
+use App\Http\Controllers\Controller;
+
+class CartController extends Controller
+{
+    use ProductTransformable;
+
+    /**
+     * @var CartRepositoryInterface
+     */
+    private $cartRepo;
+
+    /**
+     * @var ProductRepositoryInterface
+     */
+    private $productRepo;
+
+    /**
+     * @var CourierRepositoryInterface
+     */
+    private $courierRepo;
+
+    /**
+     * @var ProductAttributeRepositoryInterface
+     */
+    private $productAttributeRepo;
+
+    /**
+     * CartController constructor.
+     * @param CartRepositoryInterface $cartRepository
+     * @param ProductRepositoryInterface $productRepository
+     * @param CourierRepositoryInterface $courierRepository
+     * @param ProductAttributeRepositoryInterface $productAttributeRepository
+     */
+    public function __construct(
+        CartRepositoryInterface $cartRepository,
+        ProductRepositoryInterface $productRepository,
+        CourierRepositoryInterface $courierRepository,
+        ProductAttributeRepositoryInterface $productAttributeRepository
+    ) {
+        $this->cartRepo = $cartRepository;
+        $this->productRepo = $productRepository;
+        $this->courierRepo = $courierRepository;
+        $this->productAttributeRepo = $productAttributeRepository;
+    }
+
+    /**
+     * Display a listing of the resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function index()
+    {
+        $courier = $this->courierRepo->findCourierById(request()->session()->get('courierId', 1));
+        $shippingFee = $this->cartRepo->getShippingFee($courier);
+
+        return view('front.carts.cart', [
+            'cartItems' => $this->cartRepo->getCartItemsTransformed(),
+            'subtotal' => $this->cartRepo->getSubTotal(),
+            'tax' => $this->cartRepo->getTax(),
+            'shippingFee' => $shippingFee,
+            'total' => $this->cartRepo->getTotal(2, $shippingFee)
+        ]);
+    }
+
+    /**
+     * Store a newly created resource in storage.
+     *
+     * @param  AddToCartRequest $request
+     * @return \Illuminate\Http\Response
+     */
+    public function store(AddToCartRequest $request)
+    {
+        $product = $this->productRepo->findProductById($request->input('product'));
+
+        if ($product->attributes()->count() > 0) {
+            $productAttr = $product->attributes()->where('default', 1)->first();
+
+            if (isset($productAttr->sale_price)) {
+                $product->price = $productAttr->price;
+
+                if (!is_null($productAttr->sale_price)) {
+                    $product->price = $productAttr->sale_price;
+                }
+            }
+        }
+
+        $options = [];
+        if ($request->has('productAttribute')) {
+
+            $attr = $this->productAttributeRepo->findProductAttributeById($request->input('productAttribute'));
+            $product->price = $attr->price;
+
+            $options['product_attribute_id'] = $request->input('productAttribute');
+            $options['combination'] = $attr->attributesValues->toArray();
+        }
+
+        $this->cartRepo->addToCart($product, $request->input('quantity'), $options);
+
+        return redirect()->route('cart.index')
+            ->with('message', 'Add to cart successful');
+    }
+
+    /**
+     * Update the specified resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function update(UpdateCartRequest $request, $id)
+    {
+        $this->cartRepo->updateQuantityInCart($id, $request->input('quantity'));
+
+        request()->session()->flash('message', 'Update cart successful');
+        return redirect()->route('cart.index');
+    }
+
+    /**
+     * Remove the specified resource from storage.
+     *
+     * @param  int  $id
+     * @return \Illuminate\Http\Response
+     */
+    public function destroy($id)
+    {
+        $this->cartRepo->removeToCart($id);
+
+        request()->session()->flash('message', 'Removed to cart successful');
+        return redirect()->route('cart.index');
+    }
+}

+ 45 - 0
app/Http/Controllers/Front/CategoryController.php

@@ -0,0 +1,45 @@
+<?php
+
+namespace App\Http\Controllers\Front;
+
+use App\Shop\Categories\Repositories\CategoryRepository;
+use App\Shop\Categories\Repositories\Interfaces\CategoryRepositoryInterface;
+use App\Http\Controllers\Controller;
+
+class CategoryController extends Controller
+{
+    /**
+     * @var CategoryRepositoryInterface
+     */
+    private $categoryRepo;
+
+    /**
+     * CategoryController constructor.
+     *
+     * @param CategoryRepositoryInterface $categoryRepository
+     */
+    public function __construct(CategoryRepositoryInterface $categoryRepository)
+    {
+        $this->categoryRepo = $categoryRepository;
+    }
+
+    /**
+     * Find the category via the slug
+     *
+     * @param string $slug
+     * @return \App\Shop\Categories\Category
+     */
+    public function getCategory(string $slug)
+    {
+        $category = $this->categoryRepo->findCategoryBySlug(['slug' => $slug]);
+
+        $repo = new CategoryRepository($category);
+
+        $products = $repo->findProducts()->where('status', 1)->all();
+
+        return view('front.categories.category', [
+            'category' => $category,
+            'products' => $repo->paginateArrayResults($products, 20)
+        ]);
+    }
+}

+ 253 - 0
app/Http/Controllers/Front/CheckoutController.php

@@ -0,0 +1,253 @@
+<?php
+
+namespace App\Http\Controllers\Front;
+
+use App\Shop\Addresses\Repositories\Interfaces\AddressRepositoryInterface;
+use App\Shop\Cart\Requests\CartCheckoutRequest;
+use App\Shop\Carts\Repositories\Interfaces\CartRepositoryInterface;
+use App\Shop\Carts\Requests\PayPalCheckoutExecutionRequest;
+use App\Shop\Carts\Requests\StripeExecutionRequest;
+use App\Shop\Couriers\Repositories\Interfaces\CourierRepositoryInterface;
+use App\Shop\Customers\Customer;
+use App\Shop\Customers\Repositories\CustomerRepository;
+use App\Shop\Customers\Repositories\Interfaces\CustomerRepositoryInterface;
+use App\Shop\Orders\Repositories\Interfaces\OrderRepositoryInterface;
+use App\Shop\PaymentMethods\Paypal\Exceptions\PaypalRequestError;
+use App\Shop\PaymentMethods\Paypal\Repositories\PayPalExpressCheckoutRepository;
+use App\Shop\PaymentMethods\Stripe\Exceptions\StripeChargingErrorException;
+use App\Shop\PaymentMethods\Stripe\StripeRepository;
+use App\Shop\Products\Repositories\Interfaces\ProductRepositoryInterface;
+use App\Shop\Products\Transformations\ProductTransformable;
+use App\Shop\Shipping\ShippingInterface;
+use Exception;
+use App\Http\Controllers\Controller;
+use Gloudemans\Shoppingcart\Facades\Cart;
+use Illuminate\Http\Request;
+use Illuminate\Support\Collection;
+use Illuminate\Support\Facades\Log;
+use PayPal\Exception\PayPalConnectionException;
+
+class CheckoutController extends Controller
+{
+    use ProductTransformable;
+
+    /**
+     * @var CartRepositoryInterface
+     */
+    private $cartRepo;
+
+    /**
+     * @var CourierRepositoryInterface
+     */
+    private $courierRepo;
+
+    /**
+     * @var AddressRepositoryInterface
+     */
+    private $addressRepo;
+
+    /**
+     * @var CustomerRepositoryInterface
+     */
+    private $customerRepo;
+
+    /**
+     * @var ProductRepositoryInterface
+     */
+    private $productRepo;
+
+    /**
+     * @var OrderRepositoryInterface
+     */
+    private $orderRepo;
+
+    /**
+     * @var PayPalExpressCheckoutRepository
+     */
+    private $payPal;
+
+    /**
+     * @var ShippingInterface
+     */
+    private $shippingRepo;
+
+    public function __construct(
+        CartRepositoryInterface $cartRepository,
+        CourierRepositoryInterface $courierRepository,
+        AddressRepositoryInterface $addressRepository,
+        CustomerRepositoryInterface $customerRepository,
+        ProductRepositoryInterface $productRepository,
+        OrderRepositoryInterface $orderRepository,
+        ShippingInterface $shipping
+    ) {
+        $this->cartRepo = $cartRepository;
+        $this->courierRepo = $courierRepository;
+        $this->addressRepo = $addressRepository;
+        $this->customerRepo = $customerRepository;
+        $this->productRepo = $productRepository;
+        $this->orderRepo = $orderRepository;
+        $this->payPal = new PayPalExpressCheckoutRepository;
+        $this->shippingRepo = $shipping;
+    }
+
+    /**
+     * Display a listing of the resource.
+     *
+     * @param Request $request
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function index(Request $request)
+    {
+        $products = $this->cartRepo->getCartItems();
+        $customer = $request->user();
+        $rates = null;
+        $shipment_object_id = null;
+
+        if (env('ACTIVATE_SHIPPING') == 1) {
+            $shipment = $this->createShippingProcess($customer, $products);
+            if (!is_null($shipment)) {
+                $shipment_object_id = $shipment->object_id;
+                $rates = $shipment->rates;
+            }
+        }
+
+        // Get payment gateways
+        $paymentGateways = collect(explode(',', config('payees.name')))->transform(function ($name) {
+            return config($name);
+        })->all();
+
+        $billingAddress = $customer->addresses()->first();
+
+        return view('front.checkout', [
+            'customer' => $customer,
+            'billingAddress' => $billingAddress,
+            'addresses' => $customer->addresses()->get(),
+            'products' => $this->cartRepo->getCartItems(),
+            'subtotal' => $this->cartRepo->getSubTotal(),
+            'tax' => $this->cartRepo->getTax(),
+            'total' => $this->cartRepo->getTotal(2),
+            'payments' => $paymentGateways,
+            'cartItems' => $this->cartRepo->getCartItemsTransformed(),
+            'shipment_object_id' => $shipment_object_id,
+            'rates' => $rates
+        ]);
+    }
+
+    /**
+     * Checkout the items
+     *
+     * @param CartCheckoutRequest $request
+     *
+     * @return \Illuminate\Http\RedirectResponse
+     * @throws \App\Shop\Addresses\Exceptions\AddressNotFoundException
+     * @throws \App\Shop\Customers\Exceptions\CustomerPaymentChargingErrorException
+     * @codeCoverageIgnore
+     */
+    public function store(CartCheckoutRequest $request)
+    {
+        $shippingFee = 0;
+
+        switch ($request->input('payment')) {
+            case 'paypal':
+                return $this->payPal->process($shippingFee, $request);
+                break;
+            case 'stripe':
+
+                $details = [
+                    'description' => 'Stripe payment',
+                    'metadata' => $this->cartRepo->getCartItems()->all()
+                ];
+
+                $customer = $this->customerRepo->findCustomerById(auth()->id());
+                $customerRepo = new CustomerRepository($customer);
+                $customerRepo->charge($this->cartRepo->getTotal(2, $shippingFee), $details);
+                break;
+            default:
+        }
+    }
+
+    /**
+     * Execute the PayPal payment
+     *
+     * @param PayPalCheckoutExecutionRequest $request
+     * @return \Illuminate\Http\RedirectResponse
+     */
+    public function executePayPalPayment(PayPalCheckoutExecutionRequest $request)
+    {
+        try {
+            $this->payPal->execute($request);
+            $this->cartRepo->clearCart();
+
+            return redirect()->route('checkout.success');
+        } catch (PayPalConnectionException $e) {
+            throw new PaypalRequestError($e->getData());
+        } catch (Exception $e) {
+            throw new PaypalRequestError($e->getMessage());
+        }
+    }
+
+    /**
+     * @param StripeExecutionRequest $request
+     * @return \Stripe\Charge
+     */
+    public function charge(StripeExecutionRequest $request)
+    {
+        try {
+            $customer = $this->customerRepo->findCustomerById(auth()->id());
+            $stripeRepo = new StripeRepository($customer);
+
+            $stripeRepo->execute(
+                $request->all(),
+                Cart::total(),
+                Cart::tax()
+            );
+            return redirect()->route('checkout.success')->with('message', 'Stripe payment successful!');
+        } catch (StripeChargingErrorException $e) {
+            Log::info($e->getMessage());
+            return redirect()->route('checkout.index')->with('error', 'There is a problem processing your request.');
+        }
+    }
+
+    /**
+     * Cancel page
+     *
+     * @param Request $request
+     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
+     */
+    public function cancel(Request $request)
+    {
+        return view('front.checkout-cancel', ['data' => $request->all()]);
+    }
+
+    /**
+     * Success page
+     *
+     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
+     */
+    public function success()
+    {
+        return view('front.checkout-success');
+    }
+
+    /**
+     * @param Customer $customer
+     * @param Collection $products
+     *
+     * @return mixed
+     */
+    private function createShippingProcess(Customer $customer, Collection $products)
+    {
+        $customerRepo = new CustomerRepository($customer);
+
+        if ($customerRepo->findAddresses()->count() > 0 && $products->count() > 0) {
+
+            $this->shippingRepo->setPickupAddress();
+            $deliveryAddress = $customerRepo->findAddresses()->first();
+            $this->shippingRepo->setDeliveryAddress($deliveryAddress);
+            $this->shippingRepo->readyParcel($this->cartRepo->getCartItems());
+
+            return $this->shippingRepo->readyShipment();
+        }
+    }
+}

+ 154 - 0
app/Http/Controllers/Front/CustomerAddressController.php

@@ -0,0 +1,154 @@
+<?php
+
+namespace App\Http\Controllers\Front;
+
+use App\Http\Controllers\Controller;
+use App\Shop\Addresses\Requests\CreateAddressRequest;
+use App\Shop\Addresses\Requests\UpdateAddressRequest;
+use App\Shop\Addresses\Repositories\AddressRepository;
+use App\Shop\Cities\Repositories\Interfaces\CityRepositoryInterface;
+use App\Shop\Addresses\Repositories\Interfaces\AddressRepositoryInterface;
+use App\Shop\Countries\Repositories\Interfaces\CountryRepositoryInterface;
+use App\Shop\Provinces\Repositories\Interfaces\ProvinceRepositoryInterface;
+
+class CustomerAddressController extends Controller
+{
+    /**
+     * @var AddressRepositoryInterface
+     */
+    private $addressRepo;
+
+    /**
+     * @var CountryRepositoryInterface
+     */
+    private $countryRepo;
+
+    /**
+     * @var CityRepositoryInterface
+     */
+    private $cityRepo;
+
+    /**
+     * @var ProvinceRepositoryInterface
+     */
+    private $provinceRepo;
+
+
+    /**
+     * @param AddressRepositoryInterface  $addressRepository
+     * @param CountryRepositoryInterface  $countryRepository
+     * @param CityRepositoryInterface     $cityRepository
+     * @param ProvinceRepositoryInterface $provinceRepository
+     */
+    public function __construct(
+        AddressRepositoryInterface $addressRepository,
+        CountryRepositoryInterface $countryRepository,
+        CityRepositoryInterface $cityRepository,
+        ProvinceRepositoryInterface $provinceRepository
+    ) {
+        $this->addressRepo = $addressRepository;
+        $this->countryRepo = $countryRepository;
+        $this->provinceRepo = $provinceRepository;
+        $this->cityRepo = $cityRepository;
+    }
+
+    /**
+     * @return \Illuminate\Http\RedirectResponse
+     */
+    public function index()
+    {
+
+        return redirect()->route('accounts', ['tab' => 'address']);
+    }
+
+    /**
+     * @param  $request
+     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
+     */
+    public function create()
+    {
+        $customer = auth()->user();
+
+        return view('front.customers.addresses.create', [
+            'customer' => $customer,
+            'countries' => $this->countryRepo->listCountries(),
+            'cities' => $this->cityRepo->listCities(),
+            'provinces' => $this->provinceRepo->listProvinces()
+        ]);
+    }
+
+    /**
+     * @param CreateAddressRequest $request
+     * @return \Illuminate\Http\RedirectResponse
+     */
+    public function store(CreateAddressRequest $request)
+    {
+        $request['customer_id'] = auth()->user()->id;
+
+        $this->addressRepo->createAddress($request->except('_token', '_method'));
+
+        return redirect()->route('accounts', ['tab' => 'address'])
+            ->with('message', 'Address creation successful');
+    }
+
+    /**
+     * @param $addressId
+     *
+     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
+     */
+    public function edit($customerId, $addressId)
+    {
+        $countries = $this->countryRepo->listCountries();
+
+        $address = $this->addressRepo->findCustomerAddressById($addressId, auth()->user());
+
+        return view('front.customers.addresses.edit', [
+            'customer' => auth()->user(),
+            'address' => $address,
+            'countries' => $countries,
+            'cities' => $this->cityRepo->listCities(),
+            'provinces' => $this->provinceRepo->listProvinces()
+        ]);
+    }
+
+    /**
+     * @param UpdateAddressRequest $request
+     * @param $addressId
+     *
+     * @return \Illuminate\Http\RedirectResponse
+     */
+    public function update(UpdateAddressRequest $request, $customerId, $addressId)
+    {
+        $address = $this->addressRepo->findCustomerAddressById($addressId, auth()->user());
+
+        $request = $request->except('_token', '_method');
+        $request['customer_id'] = auth()->user()->id;
+
+        $addressRepo = new AddressRepository($address);
+        $addressRepo->updateAddress($request);
+
+        return redirect()->route('accounts', ['tab' => 'address'])
+            ->with('message', 'Address update successful');
+    }
+
+    /**
+     * @param $addressId
+     *
+     * @return \Illuminate\Http\RedirectResponse
+     * @throws \Exception
+     */
+    public function destroy($customerId, $addressId)
+    {
+        $address = $this->addressRepo->findCustomerAddressById($addressId, auth()->user());
+
+       if ($address->orders()->exists()) {
+             $address->status=0;
+             $address->save();
+       }
+       else {
+             $address->delete();
+       }
+        return redirect()->route('accounts', ['tab' => 'address'])
+            ->with('message', 'Address delete successful');
+    }
+}

+ 33 - 0
app/Http/Controllers/Front/HomeController.php

@@ -0,0 +1,33 @@
+<?php
+
+namespace App\Http\Controllers\Front;
+
+use App\Shop\Categories\Repositories\Interfaces\CategoryRepositoryInterface;
+
+class HomeController
+{
+    /**
+     * @var CategoryRepositoryInterface
+     */
+    private $categoryRepo;
+
+    /**
+     * HomeController constructor.
+     * @param CategoryRepositoryInterface $categoryRepository
+     */
+    public function __construct(CategoryRepositoryInterface $categoryRepository)
+    {
+        $this->categoryRepo = $categoryRepository;
+    }
+
+    /**
+     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
+     */
+    public function index()
+    {
+        $cat1 = $this->categoryRepo->findCategoryById(2);
+        $cat2 = $this->categoryRepo->findCategoryById(3);
+
+        return view('front.index', compact('cat1', 'cat2'));
+    }
+}

+ 155 - 0
app/Http/Controllers/Front/Payments/BankTransferController.php

@@ -0,0 +1,155 @@
+<?php
+
+namespace App\Http\Controllers\Front\Payments;
+
+use App\Http\Controllers\Controller;
+use App\Shop\Carts\Repositories\Interfaces\CartRepositoryInterface;
+use App\Shop\Checkout\CheckoutRepository;
+use App\Shop\Orders\Repositories\OrderRepository;
+use App\Shop\OrderStatuses\OrderStatus;
+use App\Shop\OrderStatuses\Repositories\OrderStatusRepository;
+use App\Shop\Shipping\ShippingInterface;
+use Gloudemans\Shoppingcart\Facades\Cart;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Log;
+use Ramsey\Uuid\Uuid;
+use Shippo_Shipment;
+use Shippo_Transaction;
+
+class BankTransferController extends Controller
+{
+    /**
+     * @var CartRepositoryInterface
+     */
+    private $cartRepo;
+
+    /**
+     * @var int $shipping
+     */
+    private $shippingFee;
+
+    private $rateObjectId;
+
+    private $shipmentObjId;
+
+    private $billingAddress;
+
+    private $carrier;
+
+    /**
+     * BankTransferController constructor.
+     *
+     * @param Request $request
+     * @param CartRepositoryInterface $cartRepository
+     * @param ShippingInterface $shippingRepo
+     */
+    public function __construct(
+        Request $request,
+        CartRepositoryInterface $cartRepository,
+        ShippingInterface $shippingRepo
+    )
+    {
+        $this->cartRepo = $cartRepository;
+        $fee = 0;
+        $rateObjId = null;
+        $shipmentObjId = null;
+        $billingAddress = $request->input('billing_address');
+
+        if ($request->has('rate')) {
+            if ($request->input('rate') != '') {
+
+                $rate_id = $request->input('rate');
+                $rates = $shippingRepo->getRates($request->input('shipment_obj_id'));
+                $rate = collect($rates->results)->filter(function ($rate) use ($rate_id) {
+                    return $rate->object_id == $rate_id;
+                })->first();
+
+                $fee = $rate->amount;
+                $rateObjId = $rate->object_id;
+                $shipmentObjId = $request->input('shipment_obj_id');
+                $this->carrier = $rate;
+            }
+        }
+
+        $this->shippingFee = $fee;
+        $this->rateObjectId = $rateObjId;
+        $this->shipmentObjId = $shipmentObjId;
+        $this->billingAddress = $billingAddress;
+    }
+
+    /**
+     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
+     */
+    public function index()
+    {
+        return view('front.bank-transfer-redirect', [
+            'subtotal' => $this->cartRepo->getSubTotal(),
+            'shipping' => $this->shippingFee,
+            'tax' => $this->cartRepo->getTax(),
+            'total' => $this->cartRepo->getTotal(2, $this->shippingFee),
+            'rateObjectId' => $this->rateObjectId,
+            'shipmentObjId' => $this->shipmentObjId,
+            'billingAddress' => $this->billingAddress
+        ]);
+    }
+
+    /**
+     * @param Request $request
+     *
+     * @return \Illuminate\Http\RedirectResponse
+     * @throws \Exception
+     */
+    public function store(Request $request)
+    {
+        $checkoutRepo = new CheckoutRepository;
+        $orderStatusRepo = new OrderStatusRepository(new OrderStatus);
+        $os = $orderStatusRepo->findByName('ordered');
+
+        $order = $checkoutRepo->buildCheckoutItems([
+            'reference' => Uuid::uuid4()->toString(),
+            'courier_id' => 1, // @deprecated
+            'customer_id' => $request->user()->id,
+            'address_id' => $request->input('billing_address'),
+            'order_status_id' => $os->id,
+            'payment' => strtolower(config('bank-transfer.name')),
+            'discounts' => 0,
+            'total_products' => $this->cartRepo->getSubTotal(),
+            'total' => $this->cartRepo->getTotal(2, $this->shippingFee),
+            'total_shipping' => $this->shippingFee,
+            'total_paid' => 0,
+            'tax' => $this->cartRepo->getTax()
+        ]);
+
+        if (env('ACTIVATE_SHIPPING') == 1) {
+            $shipment = Shippo_Shipment::retrieve($this->shipmentObjId);
+
+            $details = [
+                'shipment' => [
+                    'address_to' => json_decode($shipment->address_to, true),
+                    'address_from' => json_decode($shipment->address_from, true),
+                    'parcels' => [json_decode($shipment->parcels[0], true)]
+                ],
+                'carrier_account' => $this->carrier->carrier_account,
+                'servicelevel_token' => $this->carrier->servicelevel->token
+            ];
+
+            $transaction = Shippo_Transaction::create($details);
+
+            if ($transaction['status'] != 'SUCCESS'){
+                Log::error($transaction['messages']);
+                return redirect()->route('checkout.index')->with('error', 'There is an error in the shipment details. Check logs.');
+            }
+
+            $orderRepo = new OrderRepository($order);
+            $orderRepo->updateOrder([
+                'courier' => $this->carrier->provider,
+                'label_url' => $transaction['label_url'],
+                'tracking_number' => $transaction['tracking_number']
+            ]);
+        }
+
+        Cart::destroy();
+
+        return redirect()->route('accounts', ['tab' => 'orders'])->with('message', 'Order successful!');
+    }
+}

+ 68 - 0
app/Http/Controllers/Front/ProductController.php

@@ -0,0 +1,68 @@
+<?php
+
+namespace App\Http\Controllers\Front;
+
+use App\Shop\Products\Product;
+use App\Shop\Products\Repositories\Interfaces\ProductRepositoryInterface;
+use App\Http\Controllers\Controller;
+use App\Shop\Products\Transformations\ProductTransformable;
+
+class ProductController extends Controller
+{
+    use ProductTransformable;
+
+    /**
+     * @var ProductRepositoryInterface
+     */
+    private $productRepo;
+
+    /**
+     * ProductController constructor.
+     * @param ProductRepositoryInterface $productRepository
+     */
+    public function __construct(ProductRepositoryInterface $productRepository)
+    {
+        $this->productRepo = $productRepository;
+    }
+
+    /**
+     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
+     */
+    public function search()
+    {
+        if (request()->has('q') && request()->input('q') != '') {
+            $list = $this->productRepo->searchProduct(request()->input('q'));
+        } else {
+            $list = $this->productRepo->listProducts();
+        }
+
+        $products = $list->where('status', 1)->map(function (Product $item) {
+            return $this->transformProduct($item);
+        });
+
+        return view('front.products.product-search', [
+            'products' => $this->productRepo->paginateArrayResults($products->all(), 10)
+        ]);
+    }
+
+    /**
+     * Get the product
+     *
+     * @param string $slug
+     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
+     */
+    public function show(string $slug)
+    {
+        $product = $this->productRepo->findProductBySlug(['slug' => $slug]);
+        $images = $product->images()->get();
+        $category = $product->categories()->first();
+        $productAttributes = $product->attributes;
+
+        return view('front.products.product', compact(
+            'product',
+            'images',
+            'productAttributes',
+            'category'
+        ));
+    }
+}

+ 64 - 0
app/Http/Kernel.php

@@ -0,0 +1,64 @@
+<?php
+
+namespace App\Http;
+
+use Illuminate\Foundation\Http\Kernel as HttpKernel;
+
+class Kernel extends HttpKernel
+{
+    /**
+     * The application's global HTTP middleware stack.
+     *
+     * These middleware are run during every request to your application.
+     *
+     * @var array
+     */
+    protected $middleware = [
+        \Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
+        \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
+        \App\Http\Middleware\TrimStrings::class,
+        \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
+        \App\Http\Middleware\TrustProxies::class,
+    ];
+
+    /**
+     * The application's route middleware groups.
+     *
+     * @var array
+     */
+    protected $middlewareGroups = [
+        'web' => [
+            \App\Http\Middleware\EncryptCookies::class,
+            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
+            \Illuminate\Session\Middleware\StartSession::class,
+            // \Illuminate\Session\Middleware\AuthenticateSession::class,
+            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
+            \App\Http\Middleware\VerifyCsrfToken::class,
+            \Illuminate\Routing\Middleware\SubstituteBindings::class,
+        ],
+
+        'api' => [
+            'throttle:60,1',
+            'bindings',
+        ],
+    ];
+
+    /**
+     * The application's route middleware.
+     *
+     * These middleware may be assigned to groups or used individually.
+     *
+     * @var array
+     */
+    protected $routeMiddleware = [
+        'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
+        'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
+        'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
+        'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
+        'can' => \Illuminate\Auth\Middleware\Authorize::class,
+        'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
+        'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
+        'employee' => \App\Http\Middleware\RedirectIfNotEmployee::class,
+        'checkout' => \App\Http\Middleware\RedirectIfNotCustomer::class,
+    ];
+}

+ 17 - 0
app/Http/Middleware/EncryptCookies.php

@@ -0,0 +1,17 @@
+<?php
+
+namespace App\Http\Middleware;
+
+use Illuminate\Cookie\Middleware\EncryptCookies as BaseEncrypter;
+
+class EncryptCookies extends BaseEncrypter
+{
+    /**
+     * The names of the cookies that should not be encrypted.
+     *
+     * @var array
+     */
+    protected $except = [
+        //
+    ];
+}

+ 23 - 0
app/Http/Middleware/RedirectIfAuthenticated.php

@@ -0,0 +1,23 @@
+<?php
+
+namespace App\Http\Middleware;
+
+use Closure;
+
+class RedirectIfAuthenticated
+{
+    /**
+     * Handle an incoming request.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  \Closure  $next
+     * @param  string|null  $guard
+     * @return mixed
+     */
+    public function handle($request, Closure $next, $guard = null)
+    {
+        auth()->guard($guard)->check();
+
+        return $next($request);
+    }
+}

+ 26 - 0
app/Http/Middleware/RedirectIfNotCustomer.php

@@ -0,0 +1,26 @@
+<?php
+
+namespace App\Http\Middleware;
+
+use Closure;
+
+class RedirectIfNotCustomer
+{
+    /**
+     * Handle an incoming request.
+     *
+     * @param  \Illuminate\Http\Request $request
+     * @param  \Closure $next
+     * @param string $guard
+     * @return mixed
+     */
+    public function handle($request, Closure $next, $guard = 'checkout')
+    {
+        if (!auth()->guard($guard)->check()) {
+            $request->session()->flash('error', 'You must be an employee to see this page');
+            return redirect(route('admin.login'));
+        }
+
+        return $next($request);
+    }
+}

+ 26 - 0
app/Http/Middleware/RedirectIfNotEmployee.php

@@ -0,0 +1,26 @@
+<?php
+
+namespace App\Http\Middleware;
+
+use Closure;
+
+class RedirectIfNotEmployee
+{
+    /**
+     * Handle an incoming request.
+     *
+     * @param  \Illuminate\Http\Request $request
+     * @param  \Closure $next
+     * @param string $guard
+     * @return mixed
+     */
+    public function handle($request, Closure $next, $guard = 'employee')
+    {
+        if (!auth()->guard($guard)->check()) {
+            $request->session()->flash('error', 'You must be an employee to see this page');
+            return redirect(route('admin.login'));
+        }
+
+        return $next($request);
+    }
+}

+ 18 - 0
app/Http/Middleware/TrimStrings.php

@@ -0,0 +1,18 @@
+<?php
+
+namespace App\Http\Middleware;
+
+use Illuminate\Foundation\Http\Middleware\TrimStrings as BaseTrimmer;
+
+class TrimStrings extends BaseTrimmer
+{
+    /**
+     * The names of the attributes that should not be trimmed.
+     *
+     * @var array
+     */
+    protected $except = [
+        'password',
+        'password_confirmation',
+    ];
+}

+ 23 - 0
app/Http/Middleware/TrustProxies.php

@@ -0,0 +1,23 @@
+<?php
+
+namespace App\Http\Middleware;
+
+use Fideloper\Proxy\TrustProxies as Middleware;
+use Illuminate\Http\Request;
+
+class TrustProxies extends Middleware
+{
+    /**
+     * The trusted proxies for this application.
+     *
+     * @var array
+     */
+    protected $proxies;
+
+    /**
+     * The headers that should be used to detect proxies.
+     *
+     * @var string
+     */
+    protected $headers = Request::HEADER_X_FORWARDED_ALL;
+}

+ 17 - 0
app/Http/Middleware/VerifyCsrfToken.php

@@ -0,0 +1,17 @@
+<?php
+
+namespace App\Http\Middleware;
+
+use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as BaseVerifier;
+
+class VerifyCsrfToken extends BaseVerifier
+{
+    /**
+     * The URIs that should be excluded from CSRF verification.
+     *
+     * @var array
+     */
+    protected $except = [
+        //
+    ];
+}

+ 36 - 0
app/Listeners/OrderCreateEventListener.php

@@ -0,0 +1,36 @@
+<?php
+
+namespace App\Listeners;
+
+use App\Events\OrderCreateEvent;
+use App\Shop\Orders\Repositories\OrderRepository;
+use Illuminate\Queue\InteractsWithQueue;
+use Illuminate\Contracts\Queue\ShouldQueue;
+
+class OrderCreateEventListener
+{
+    /**
+     * Create the event listener.
+     *
+     */
+    public function __construct()
+    {
+        //
+    }
+
+    /**
+     * Handle the event.
+     *
+     * @param  OrderCreateEvent  $event
+     * @return void
+     */
+    public function handle(OrderCreateEvent $event)
+    {
+        // send email to customer
+        $orderRepo = new OrderRepository($event->order);
+        $orderRepo->sendEmailToCustomer();
+
+        $orderRepo = new OrderRepository($event->order);
+        $orderRepo->sendEmailNotificationToAdmin();
+    }
+}

+ 47 - 0
app/Mail/SendOrderToCustomerMailable.php

@@ -0,0 +1,47 @@
+<?php
+
+namespace App\Mail;
+
+use App\Shop\Addresses\Transformations\AddressTransformable;
+use App\Shop\Orders\Order;
+use Illuminate\Bus\Queueable;
+use Illuminate\Mail\Mailable;
+use Illuminate\Queue\SerializesModels;
+use Illuminate\Contracts\Queue\ShouldQueue;
+
+class SendOrderToCustomerMailable extends Mailable
+{
+    use Queueable, SerializesModels, AddressTransformable;
+
+    public $order;
+
+    /**
+     * Create a new message instance.
+     *
+     * @param Order $order
+     */
+    public function __construct(Order $order)
+    {
+        $this->order = $order;
+    }
+
+    /**
+     * Build the message.
+     *
+     * @return $this
+     */
+    public function build()
+    {
+        $data = [
+            'order' => $this->order,
+            'products' => $this->order->products,
+            'customer' => $this->order->customer,
+            'courier' => $this->order->courier,
+            'address' => $this->order->address,
+            'status' => $this->order->orderStatus,
+            'payment' => $this->order->paymentMethod
+        ];
+
+        return $this->view('emails.customer.sendOrderDetailsToCustomer', $data);
+    }
+}

+ 46 - 0
app/Mail/sendEmailNotificationToAdminMailable.php

@@ -0,0 +1,46 @@
+<?php
+
+namespace App\Mail;
+
+use App\Shop\Addresses\Transformations\AddressTransformable;
+use App\Shop\Orders\Order;
+use Illuminate\Bus\Queueable;
+use Illuminate\Mail\Mailable;
+use Illuminate\Queue\SerializesModels;
+use Illuminate\Contracts\Queue\ShouldQueue;
+
+class sendEmailNotificationToAdminMailable extends Mailable
+{
+    use Queueable, SerializesModels, AddressTransformable;
+
+    public $order;
+
+    /**
+     * Create a new message instance.
+     * @param Order $order
+     */
+    public function __construct(Order $order)
+    {
+        $this->order = $order;
+    }
+
+    /**
+     * Build the message.
+     *
+     * @return $this
+     */
+    public function build()
+    {
+        $data = [
+            'order' => $this->order,
+            'products' => $this->order->products,
+            'customer' => $this->order->customer,
+            'courier' => $this->order->courier,
+            'address' => $this->order->address,
+            'status' => $this->order->orderStatus,
+            'payment' => $this->order->paymentMethod
+        ];
+
+        return $this->view('emails.admin.OrderNotificationEmail', $data);
+    }
+}

+ 29 - 0
app/Providers/AppServiceProvider.php

@@ -0,0 +1,29 @@
+<?php
+
+namespace App\Providers;
+
+use Illuminate\Support\ServiceProvider;
+use Laravel\Cashier\Cashier;
+
+class AppServiceProvider extends ServiceProvider
+{
+    /**
+     * Bootstrap any application services.
+     *
+     * @return void
+     */
+    public function boot()
+    {
+        Cashier::useCurrency(config('cart.currency'), config('cart.currency_symbol'));
+    }
+
+    /**
+     * Register any application services.
+     *
+     * @return void
+     */
+    public function register()
+    {
+        //
+    }
+}

+ 31 - 0
app/Providers/AuthServiceProvider.php

@@ -0,0 +1,31 @@
+<?php
+
+namespace App\Providers;
+
+use Illuminate\Support\Facades\Auth;
+use Illuminate\Support\Facades\Gate;
+use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
+
+class AuthServiceProvider extends ServiceProvider
+{
+    /**
+     * The policy mappings for the application.
+     *
+     * @var array
+     */
+    protected $policies = [
+        'App\Model' => 'App\Policies\ModelPolicy',
+    ];
+
+    /**
+     * Register any authentication / authorization services.
+     *
+     * @return void
+     */
+    public function boot()
+    {
+        $this->registerPolicies();
+
+        //
+    }
+}

+ 26 - 0
app/Providers/BroadcastServiceProvider.php

@@ -0,0 +1,26 @@
+<?php
+
+namespace App\Providers;
+
+use Illuminate\Support\ServiceProvider;
+use Illuminate\Support\Facades\Broadcast;
+
+/**
+ * Class BroadcastServiceProvider
+ * @package App\Providers
+ * @codeCoverageIgnore
+ */
+class BroadcastServiceProvider extends ServiceProvider
+{
+    /**
+     * Bootstrap any application services.
+     *
+     * @return void
+     */
+    public function boot()
+    {
+        Broadcast::routes();
+
+        require base_path('routes/channels.php');
+    }
+}

+ 32 - 0
app/Providers/EventServiceProvider.php

@@ -0,0 +1,32 @@
+<?php
+
+namespace App\Providers;
+
+use Illuminate\Support\Facades\Event;
+use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
+
+class EventServiceProvider extends ServiceProvider
+{
+    /**
+     * The event listener mappings for the application.
+     *
+     * @var array
+     */
+    protected $listen = [
+        'App\Events\OrderCreateEvent' => [
+            'App\Listeners\OrderCreateEventListener',
+        ],
+    ];
+
+    /**
+     * Register any events for your application.
+     *
+     * @return void
+     */
+    public function boot()
+    {
+        parent::boot();
+
+        //
+    }
+}

+ 100 - 0
app/Providers/GlobalTemplateServiceProvider.php

@@ -0,0 +1,100 @@
+<?php
+
+namespace App\Providers;
+
+use App\Shop\Carts\Repositories\CartRepository;
+use App\Shop\Carts\ShoppingCart;
+use App\Shop\Categories\Category;
+use App\Shop\Categories\Repositories\CategoryRepository;
+use App\Shop\Employees\Employee;
+use App\Shop\Employees\Repositories\EmployeeRepository;
+use Illuminate\Support\Facades\Auth;
+use Illuminate\Support\ServiceProvider;
+
+/**
+ * Class GlobalTemplateServiceProvider
+ * @package App\Providers
+ * @codeCoverageIgnore
+ */
+class GlobalTemplateServiceProvider extends ServiceProvider
+{
+    /**
+     * Register bindings in the container.
+     *
+     * @return void
+     */
+    public function boot()
+    {
+        view()->composer([
+            'layouts.admin.app',
+            'layouts.admin.sidebar',
+            'admin.shared.products'
+        ], function ($view) {
+            $view->with('admin', Auth::guard('employee')->user());
+        });
+
+        view()->composer(['layouts.front.app', 'front.categories.sidebar-category'], function ($view) {
+            $view->with('categories', $this->getCategories());
+            $view->with('cartCount', $this->getCartCount());
+        });
+
+        /**
+         * breadcumb
+         */
+        view()->composer([
+            "layouts.admin.app"
+        ], function ($view) {
+            $breadcumb = [
+                ["name" => "Dashboard", "url" => route("admin.dashboard"), "icon" => "fa fa-dashboard"],
+            ];
+            $paths = request()->segments();
+            if (count($paths) > 1) {
+                foreach ($paths as $key => $pah) {
+                    if ($key == 1)
+                        $breadcumb[] = ["name" => ucfirst($pah), "url" => request()->getBaseUrl() . "/" . $paths[0] . "/" . $paths[$key], 'icon' => config("module.admin." . $pah . ".icon")];
+                    elseif ($key == 2)
+                        $breadcumb[] = ["name" => ucfirst($pah), "url" => request()->getBaseUrl() . "/" . $paths[0] . "/" . $paths[1] . "/" . $paths[$key], 'icon' => config("module.admin." . $pah . ".icon")];
+                }
+            }
+            $view->with(
+                [
+                    "breadcumbs" => $breadcumb
+                ]
+            );
+        });
+
+
+        view()->composer(['layouts.front.category-nav'], function ($view) {
+            $view->with('categories', $this->getCategories());
+        });
+    }
+
+    /**
+     * Get all the categories
+     *
+     */
+    private function getCategories()
+    {
+        $categoryRepo = new CategoryRepository(new Category);
+        return $categoryRepo->listCategories('name', 'asc', 1)->whereIn('parent_id', [1]);
+    }
+
+    /**
+     * @return int
+     */
+    private function getCartCount()
+    {
+        $cartRepo = new CartRepository(new ShoppingCart);
+        return $cartRepo->countItems();
+    }
+
+    /**
+     * @param Employee $employee
+     * @return bool
+     */
+    private function isAdmin(Employee $employee)
+    {
+        $employeeRepo = new EmployeeRepository($employee);
+        return $employeeRepo->hasRole('admin');
+    }
+}

+ 151 - 0
app/Providers/RepositoryServiceProvider.php

@@ -0,0 +1,151 @@
+<?php
+
+namespace App\Providers;
+
+use App\Shop\Addresses\Repositories\AddressRepository;
+use App\Shop\Addresses\Repositories\Interfaces\AddressRepositoryInterface;
+use App\Shop\Attributes\Repositories\AttributeRepository;
+use App\Shop\Attributes\Repositories\AttributeRepositoryInterface;
+use App\Shop\AttributeValues\Repositories\AttributeValueRepository;
+use App\Shop\AttributeValues\Repositories\AttributeValueRepositoryInterface;
+use App\Shop\Brands\Repositories\BrandRepository;
+use App\Shop\Brands\Repositories\BrandRepositoryInterface;
+use App\Shop\Carts\Repositories\CartRepository;
+use App\Shop\Carts\Repositories\Interfaces\CartRepositoryInterface;
+use App\Shop\Categories\Repositories\CategoryRepository;
+use App\Shop\Categories\Repositories\Interfaces\CategoryRepositoryInterface;
+use App\Shop\Cities\Repositories\CityRepository;
+use App\Shop\Cities\Repositories\Interfaces\CityRepositoryInterface;
+use App\Shop\Countries\Repositories\CountryRepository;
+use App\Shop\Countries\Repositories\Interfaces\CountryRepositoryInterface;
+use App\Shop\Couriers\Repositories\CourierRepository;
+use App\Shop\Couriers\Repositories\Interfaces\CourierRepositoryInterface;
+use App\Shop\Customers\Repositories\CustomerRepository;
+use App\Shop\Customers\Repositories\Interfaces\CustomerRepositoryInterface;
+use App\Shop\Employees\Repositories\EmployeeRepository;
+use App\Shop\Employees\Repositories\Interfaces\EmployeeRepositoryInterface;
+use App\Shop\Orders\Repositories\Interfaces\OrderRepositoryInterface;
+use App\Shop\Orders\Repositories\OrderRepository;
+use App\Shop\OrderStatuses\Repositories\Interfaces\OrderStatusRepositoryInterface;
+use App\Shop\OrderStatuses\Repositories\OrderStatusRepository;
+use App\Shop\Permissions\Repositories\PermissionRepository;
+use App\Shop\Permissions\Repositories\Interfaces\PermissionRepositoryInterface;
+use App\Shop\ProductAttributes\Repositories\ProductAttributeRepository;
+use App\Shop\ProductAttributes\Repositories\ProductAttributeRepositoryInterface;
+use App\Shop\Products\Repositories\Interfaces\ProductRepositoryInterface;
+use App\Shop\Products\Repositories\ProductRepository;
+use App\Shop\Provinces\Repositories\Interfaces\ProvinceRepositoryInterface;
+use App\Shop\Provinces\Repositories\ProvinceRepository;
+use App\Shop\Roles\Repositories\RoleRepository;
+use App\Shop\Roles\Repositories\RoleRepositoryInterface;
+use App\Shop\Shipping\ShippingInterface;
+use App\Shop\Shipping\Shippo\ShippoShipmentRepository;
+use App\Shop\States\Repositories\StateRepository;
+use App\Shop\States\Repositories\StateRepositoryInterface;
+use Illuminate\Support\ServiceProvider;
+
+class RepositoryServiceProvider extends ServiceProvider
+{
+    public function register()
+    {
+        $this->app->bind(
+            StateRepositoryInterface::class,
+            StateRepository::class
+        );
+
+        $this->app->bind(
+            ShippingInterface::class,
+            ShippoShipmentRepository::class
+        );
+
+        $this->app->bind(
+            BrandRepositoryInterface::class,
+            BrandRepository::class
+        );
+
+        $this->app->bind(
+            ProductAttributeRepositoryInterface::class,
+            ProductAttributeRepository::class
+        );
+
+        $this->app->bind(
+            AttributeValueRepositoryInterface::class,
+            AttributeValueRepository::class
+        );
+
+        $this->app->bind(
+            AttributeRepositoryInterface::class,
+            AttributeRepository::class
+        );
+
+        $this->app->bind(
+            EmployeeRepositoryInterface::class,
+            EmployeeRepository::class
+        );
+
+        $this->app->bind(
+            CustomerRepositoryInterface::class,
+            CustomerRepository::class
+        );
+
+        $this->app->bind(
+            ProductRepositoryInterface::class,
+            ProductRepository::class
+        );
+
+        $this->app->bind(
+            CategoryRepositoryInterface::class,
+            CategoryRepository::class
+        );
+
+        $this->app->bind(
+            AddressRepositoryInterface::class,
+            AddressRepository::class
+        );
+
+        $this->app->bind(
+            CountryRepositoryInterface::class,
+            CountryRepository::class
+        );
+
+        $this->app->bind(
+            ProvinceRepositoryInterface::class,
+            ProvinceRepository::class
+        );
+
+        $this->app->bind(
+            CityRepositoryInterface::class,
+            CityRepository::class
+        );
+
+        $this->app->bind(
+            OrderRepositoryInterface::class,
+            OrderRepository::class
+        );
+
+        $this->app->bind(
+            OrderStatusRepositoryInterface::class,
+            OrderStatusRepository::class
+        );
+
+        $this->app->bind(
+            CourierRepositoryInterface::class,
+            CourierRepository::class
+        );
+
+        $this->app->bind(
+            CartRepositoryInterface::class,
+            CartRepository::class
+        );
+
+        $this->app->bind(
+            RoleRepositoryInterface::class,
+            RoleRepository::class
+        );
+
+        $this->app->bind(
+            PermissionRepositoryInterface::class,
+            PermissionRepository::class
+        );
+    }
+}

+ 73 - 0
app/Providers/RouteServiceProvider.php

@@ -0,0 +1,73 @@
+<?php
+
+namespace App\Providers;
+
+use Illuminate\Support\Facades\Route;
+use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
+
+class RouteServiceProvider extends ServiceProvider
+{
+    /**
+     * This namespace is applied to your controller routes.
+     *
+     * In addition, it is set as the URL generator's root namespace.
+     *
+     * @var string
+     */
+    protected $namespace = 'App\Http\Controllers';
+
+    /**
+     * Define your route model bindings, pattern filters, etc.
+     *
+     * @return void
+     */
+    public function boot()
+    {
+        //
+
+        parent::boot();
+    }
+
+    /**
+     * Define the routes for the application.
+     *
+     * @return void
+     */
+    public function map()
+    {
+        $this->mapApiRoutes();
+
+        $this->mapWebRoutes();
+
+        //
+    }
+
+    /**
+     * Define the "web" routes for the application.
+     *
+     * These routes all receive session state, CSRF protection, etc.
+     *
+     * @return void
+     */
+    protected function mapWebRoutes()
+    {
+        Route::middleware('web')
+             ->namespace($this->namespace)
+             ->group(base_path('routes/web.php'));
+    }
+
+    /**
+     * Define the "api" routes for the application.
+     *
+     * These routes are typically stateless.
+     *
+     * @return void
+     */
+    protected function mapApiRoutes()
+    {
+        Route::prefix('api')
+             ->middleware('api')
+             ->namespace($this->namespace)
+             ->group(base_path('routes/api.php'));
+    }
+}

+ 105 - 0
app/Shop/Addresses/Address.php

@@ -0,0 +1,105 @@
+<?php
+
+namespace App\Shop\Addresses;
+
+use App\Shop\Customers\Customer;
+use App\Shop\Orders\Order;
+use App\Shop\Provinces\Province;
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\SoftDeletes;
+use App\Shop\Cities\City;
+use App\Shop\Countries\Country;
+use Nicolaslopezj\Searchable\SearchableTrait;
+
+class Address extends Model
+{
+    use SoftDeletes, SearchableTrait;
+
+    /**
+     * The attributes that are mass assignable.
+     *
+     * @var array
+     */
+    public $fillable = [
+        'alias',
+        'address_1',
+        'address_2',
+        'zip',
+        'city',
+        'state_code',
+        'province_id',
+        'country_id',
+        'customer_id',
+        'status',
+        'phone'
+    ];
+
+    /**
+     * The attributes that should be hidden for arrays.
+     *
+     * @var array
+     */
+    protected $hidden = [];
+
+    protected $dates = ['deleted_at'];
+
+    /**
+     * Searchable rules.
+     *
+     * @var array
+     */
+    protected $searchable = [
+        'columns' => [
+            'alias' => 5,
+            'address_1' => 10,
+            'address_2' => 5,
+            'zip' => 5,
+            'city' => 10,
+            'state_code' => 10,
+            'phone' => 5
+        ]
+    ];
+
+    public function customer()
+    {
+        return $this->belongsTo(Customer::class);
+    }
+
+    public function country()
+    {
+        return $this->belongsTo(Country::class);
+    }
+
+    public function province()
+    {
+        return $this->belongsTo(Province::class);
+    }
+
+    /**
+     * @deprecated
+     *
+     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
+     */
+    public function city()
+    {
+        return $this->belongsTo(City::class, 'city');
+    }
+
+    /**
+     * @return \Illuminate\Database\Eloquent\Relations\HasMany
+     */
+    public function orders()
+    {
+        return $this->hasMany(Order::class);
+    }
+
+    /**
+     * @param $term
+     *
+     * @return mixed
+     */
+    public function searchAddress($term)
+    {
+        return self::search($term);
+    }
+}

+ 7 - 0
app/Shop/Addresses/Exceptions/AddressNotFoundException.php

@@ -0,0 +1,7 @@
+<?php
+
+namespace App\Shop\Addresses\Exceptions;
+
+class AddressNotFoundException extends \Exception
+{
+}

+ 7 - 0
app/Shop/Addresses/Exceptions/CreateAddressErrorException.php

@@ -0,0 +1,7 @@
+<?php
+
+namespace App\Shop\Addresses\Exceptions;
+
+class CreateAddressErrorException extends \Exception
+{
+}

+ 183 - 0
app/Shop/Addresses/Repositories/AddressRepository.php

@@ -0,0 +1,183 @@
+<?php
+
+namespace App\Shop\Addresses\Repositories;
+
+use App\Shop\Addresses\Address;
+use App\Shop\Addresses\Exceptions\CreateAddressErrorException;
+use App\Shop\Addresses\Exceptions\AddressNotFoundException;
+use App\Shop\Addresses\Repositories\Interfaces\AddressRepositoryInterface;
+use App\Shop\Addresses\Transformations\AddressTransformable;
+use App\Shop\Cities\City;
+use App\Shop\Countries\Country;
+use App\Shop\Customers\Customer;
+use App\Shop\Provinces\Province;
+use Illuminate\Database\Eloquent\ModelNotFoundException;
+use Illuminate\Database\QueryException;
+use Illuminate\Support\Collection;
+use Jsdecena\Baserepo\BaseRepository;
+
+class AddressRepository extends BaseRepository implements AddressRepositoryInterface
+{
+    use AddressTransformable;
+
+    /**
+     * AddressRepository constructor.
+     * @param Address $address
+     */
+    public function __construct(Address $address)
+    {
+        parent::__construct($address);
+        $this->model = $address;
+    }
+
+    /**
+     * Create the address
+     *
+     * @param array $data
+     *
+     * @return Address
+     * @throws CreateAddressErrorException
+     */
+    public function createAddress(array $data) : Address
+    {
+        try {
+            return $this->create($data);
+        } catch (QueryException $e) {
+            throw new CreateAddressErrorException('Address creation error');
+        }
+    }
+
+    /**
+     * Attach the customer to the address
+     *
+     * @param Address $address
+     * @param Customer $customer
+     */
+    public function attachToCustomer(Address $address, Customer $customer)
+    {
+        $customer->addresses()->save($address);
+    }
+
+    /**
+     * @param array $data
+     * @return bool
+     */
+    public function updateAddress(array $data): bool
+    {
+        return $this->update($data);
+    }
+
+    /**
+     * Soft delete the address
+     *
+     */
+    public function deleteAddress()
+    {
+        $this->model->customer()->dissociate();
+        return $this->model->delete();
+    }
+
+    /**
+     * List all the address
+     *
+     * @param string $order
+     * @param string $sort
+     * @param array $columns
+     * @return array|Collection
+     */
+    public function listAddress(string $order = 'id', string $sort = 'desc', array $columns = ['*']) : Collection
+    {
+        return $this->all($columns, $order, $sort);
+    }
+
+    /**
+     * Return the address
+     *
+     * @param int $id
+     *
+     * @return Address
+     * @throws AddressNotFoundException
+     */
+    public function findAddressById(int $id) : Address
+    {
+        try {
+            return $this->findOneOrFail($id);
+        } catch (ModelNotFoundException $e) {
+            throw new AddressNotFoundException('Address not found.');
+        }
+    }
+
+    /**
+     * Return the address
+     *
+     * @param int $id
+     *
+     * @return Address
+     * @throws AddressNotFoundException
+     */
+    public function findCustomerAddressById(int $id, Customer $customer) : Address
+    {
+        try 
+        {
+            return $customer
+                        ->addresses()
+                        ->whereId($id)
+                        ->firstOrFail();
+        } 
+        catch (ModelNotFoundException $e) 
+        {
+            throw new AddressNotFoundException('Address not found.');
+        }
+    }
+
+    /**
+     * Return the customer owner of the address
+     *
+     * @return Customer
+     */
+    public function findCustomer() : Customer
+    {
+        return $this->model->customer;
+    }
+
+    /**
+     * @param string $text
+     * @return mixed
+     */
+    public function searchAddress(string $text = null) : Collection
+    {
+        if (is_null($text)) {
+            return $this->all(['*'], 'address_1', 'asc');
+        }
+        return $this->model->searchAddress($text)->get();
+    }
+
+    /**
+     * @return Country
+     */
+    public function findCountry() : Country
+    {
+        return $this->model->country;
+    }
+
+    /**
+     * @return Province
+     */
+    public function findProvince() : Province
+    {
+        return $this->model->province;
+    }
+
+    public function findCity() : City
+    {
+        return $this->model->city;
+    }
+
+    /**
+     * @return Collection
+     */
+    public function findOrders() : Collection
+    {
+        return $this->model->orders()->get();
+    }
+}

+ 36 - 0
app/Shop/Addresses/Repositories/Interfaces/AddressRepositoryInterface.php

@@ -0,0 +1,36 @@
+<?php
+
+namespace App\Shop\Addresses\Repositories\Interfaces;
+
+use App\Shop\Addresses\Address;
+use App\Shop\Cities\City;
+use App\Shop\Countries\Country;
+use App\Shop\Customers\Customer;
+use App\Shop\Provinces\Province;
+use Illuminate\Support\Collection;
+use Jsdecena\Baserepo\BaseRepositoryInterface;
+
+interface AddressRepositoryInterface extends BaseRepositoryInterface
+{
+    public function createAddress(array $params) : Address;
+
+    public function attachToCustomer(Address $address, Customer $customer);
+
+    public function updateAddress(array $update): bool;
+
+    public function deleteAddress();
+
+    public function listAddress(string $order = 'id', string $sort = 'desc', array $columns = ['*']) : Collection;
+
+    public function findAddressById(int $id) : Address;
+
+    public function findCustomer() : Customer;
+
+    public function searchAddress(string $text) : Collection;
+
+    public function findCountry() : Country;
+
+    public function findProvince() : Province;
+
+    public function findCity() : City;
+}

+ 21 - 0
app/Shop/Addresses/Requests/CreateAddressRequest.php

@@ -0,0 +1,21 @@
+<?php
+
+namespace App\Shop\Addresses\Requests;
+
+use App\Shop\Base\BaseFormRequest;
+
+class CreateAddressRequest extends BaseFormRequest
+{
+    /**
+     * Get the validation rules that apply to the request.
+     *
+     * @return array
+     */
+    public function rules()
+    {
+        return [
+            'alias' => ['required'],
+            'address_1' => ['required']
+        ];
+    }
+}

+ 21 - 0
app/Shop/Addresses/Requests/UpdateAddressRequest.php

@@ -0,0 +1,21 @@
+<?php
+
+namespace App\Shop\Addresses\Requests;
+
+use App\Shop\Base\BaseFormRequest;
+
+class UpdateAddressRequest extends BaseFormRequest
+{
+    /**
+     * Get the validation rules that apply to the request.
+     *
+     * @return array
+     */
+    public function rules()
+    {
+        return [
+            'alias' => ['required'],
+            'address_1' => ['required']
+        ];
+    }
+}

+ 54 - 0
app/Shop/Addresses/Transformations/AddressTransformable.php

@@ -0,0 +1,54 @@
+<?php
+
+namespace App\Shop\Addresses\Transformations;
+
+use App\Shop\Addresses\Address;
+use App\Shop\Cities\Repositories\CityRepository;
+use App\Shop\Countries\Repositories\CountryRepository;
+use App\Shop\Customers\Customer;
+use App\Shop\Customers\Repositories\CustomerRepository;
+use App\Shop\Provinces\Province;
+use App\Shop\Provinces\Repositories\ProvinceRepository;
+use App\Shop\Cities\City;
+use App\Shop\Countries\Country;
+
+trait AddressTransformable
+{
+    /**
+     * Transform the address
+     *
+     * @param Address $address
+     *
+     * @return Address
+     * @throws \App\Shop\Cities\Exceptions\CityNotFoundException
+     * @throws \App\Shop\Countries\Exceptions\CountryNotFoundException
+     * @throws \App\Shop\Customers\Exceptions\CustomerNotFoundException
+     */
+    public function transformAddress(Address $address)
+    {
+        $obj = new Address;
+        $obj->id = $address->id;
+        $obj->alias = $address->alias;
+        $obj->address_1 = $address->address_1;
+        $obj->address_2 = $address->address_2;
+        $obj->zip = $address->zip;
+        $obj->city = $address->city;
+
+        if (isset($address->province_id)) {
+            $provinceRepo = new ProvinceRepository(new Province);
+            $province = $provinceRepo->findProvinceById($address->province_id);
+            $obj->province = $province->name;
+        }
+
+        $countryRepo = new CountryRepository(new Country);
+        $country = $countryRepo->findCountryById($address->country_id);
+        $obj->country = $country->name;
+
+        $customerRepo = new CustomerRepository(new Customer);
+        $customer = $customerRepo->findCustomerById($address->customer_id);
+        $obj->customer = $customer->name;
+        $obj->status = $address->status;
+
+        return $obj;
+    }
+}

+ 33 - 0
app/Shop/Admins/Requests/CreateEmployeeRequest.php

@@ -0,0 +1,33 @@
+<?php
+
+namespace App\Shop\Admins\Requests;
+
+use Illuminate\Foundation\Http\FormRequest;
+
+class CreateEmployeeRequest extends FormRequest
+{
+    /**
+     * Determine if the user is authorized to make this request.
+     *
+     * @return bool
+     */
+    public function authorize()
+    {
+        return true;
+    }
+
+    /**
+     * Get the validation rules that apply to the request.
+     *
+     * @return array
+     */
+    public function rules()
+    {
+        return [
+            'name' => ['required'],
+            'email' => ['required', 'email', 'unique:employees'],
+            'password' => ['required', 'min:8'],
+            'role' => ['required']
+        ];
+    }
+}

+ 31 - 0
app/Shop/Admins/Requests/LoginRequest.php

@@ -0,0 +1,31 @@
+<?php
+
+namespace App\Shop\Admins\Requests;
+
+use Illuminate\Foundation\Http\FormRequest;
+
+class LoginRequest extends FormRequest
+{
+    /**
+     * Determine if the user is authorized to make this request.
+     *
+     * @return bool
+     */
+    public function authorize()
+    {
+        return true;
+    }
+
+    /**
+     * Get the validation rules that apply to the request.
+     *
+     * @return array
+     */
+    public function rules()
+    {
+        return [
+            'email' => ['required', 'email'],
+            'password' => ['required']
+        ];
+    }
+}

+ 32 - 0
app/Shop/Admins/Requests/UpdateEmployeeRequest.php

@@ -0,0 +1,32 @@
+<?php
+
+namespace App\Shop\Admins\Requests;
+
+use Illuminate\Foundation\Http\FormRequest;
+use Illuminate\Validation\Rule;
+
+class UpdateEmployeeRequest extends FormRequest
+{
+    /**
+     * Determine if the user is authorized to make this request.
+     *
+     * @return bool
+     */
+    public function authorize()
+    {
+        return true;
+    }
+
+    /**
+     * Get the validation rules that apply to the request.
+     *
+     * @return array
+     */
+    public function rules()
+    {
+        return [
+            'name' => ['required'],
+            'email' => ['required', 'email', Rule::unique('employees', 'email')->ignore($this->segment(3))]
+        ];
+    }
+}

+ 30 - 0
app/Shop/AttributeValues/AttributeValue.php

@@ -0,0 +1,30 @@
+<?php
+
+namespace App\Shop\AttributeValues;
+
+use App\Shop\Attributes\Attribute;
+use App\Shop\ProductAttributes\ProductAttribute;
+use Illuminate\Database\Eloquent\Model;
+
+class AttributeValue extends Model
+{
+    protected $fillable = [
+        'value'
+    ];
+
+    /**
+     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
+     */
+    public function attribute()
+    {
+        return $this->belongsTo(Attribute::class);
+    }
+
+    /**
+     * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
+     */
+    public function productAttributes()
+    {
+        return $this->belongsToMany(ProductAttribute::class);
+    }
+}

+ 63 - 0
app/Shop/AttributeValues/Repositories/AttributeValueRepository.php

@@ -0,0 +1,63 @@
+<?php
+
+namespace App\Shop\AttributeValues\Repositories;
+
+use App\Shop\Attributes\Attribute;
+use App\Shop\AttributeValues\AttributeValue;
+use Jsdecena\Baserepo\BaseRepository;
+use Illuminate\Support\Collection;
+
+class AttributeValueRepository extends BaseRepository implements AttributeValueRepositoryInterface
+{
+    /**
+     * AttributeValueRepository constructor.
+     * @param AttributeValue $attributeValue
+     */
+    public function __construct(AttributeValue $attributeValue)
+    {
+        parent::__construct($attributeValue);
+        $this->model = $attributeValue;
+    }
+
+    /**
+     * @param Attribute $attribute
+     * @param array $data
+     * @return AttributeValue
+     */
+    public function createAttributeValue(Attribute $attribute, array $data) : AttributeValue
+    {
+        $attributeValue = new AttributeValue($data);
+        $attributeValue->attribute()->associate($attribute);
+        $attributeValue->save();
+        return $attributeValue;
+    }
+
+    /**
+     * Create the attribute value and associate to the attribute
+     *
+     * @param Attribute $attribute
+     * @return AttributeValue
+     */
+    public function associateToAttribute(Attribute $attribute) : AttributeValue
+    {
+        $this->model->attribute()->associate($attribute);
+        $this->model->save();
+        return $this->model;
+    }
+
+    /**
+     * Remove association from the attribute
+     */
+    public function dissociateFromAttribute() : bool
+    {
+        return $this->model->delete();
+    }
+
+    /**
+     * @return Collection
+     */
+    public function findProductAttributes() : Collection
+    {
+        return $this->model->productAttributes()->get();
+    }
+}

+ 19 - 0
app/Shop/AttributeValues/Repositories/AttributeValueRepositoryInterface.php

@@ -0,0 +1,19 @@
+<?php
+
+namespace App\Shop\AttributeValues\Repositories;
+
+use App\Shop\Attributes\Attribute;
+use App\Shop\AttributeValues\AttributeValue;
+use Jsdecena\Baserepo\BaseRepositoryInterface;
+use Illuminate\Support\Collection;
+
+interface AttributeValueRepositoryInterface extends BaseRepositoryInterface
+{
+    public function createAttributeValue(Attribute $attribute, array $data) : AttributeValue;
+
+    public function associateToAttribute(Attribute $attribute) : AttributeValue;
+
+    public function dissociateFromAttribute() : bool;
+
+    public function findProductAttributes() : Collection;
+}

+ 20 - 0
app/Shop/AttributeValues/Requests/CreateAttributeValueRequest.php

@@ -0,0 +1,20 @@
+<?php
+
+namespace App\Shop\AttributeValues\Requests;
+
+use App\Shop\Base\BaseFormRequest;
+
+class CreateAttributeValueRequest extends BaseFormRequest
+{
+    /**
+     * Get the validation rules that apply to the request.
+     *
+     * @return array
+     */
+    public function rules()
+    {
+        return [
+            'value' => ['required']
+        ];
+    }
+}

+ 21 - 0
app/Shop/Attributes/Attribute.php

@@ -0,0 +1,21 @@
+<?php
+
+namespace App\Shop\Attributes;
+
+use App\Shop\AttributeValues\AttributeValue;
+use Illuminate\Database\Eloquent\Model;
+
+class Attribute extends Model
+{
+    protected $fillable = [
+        'name'
+    ];
+
+    /**
+     * @return \Illuminate\Database\Eloquent\Relations\HasMany
+     */
+    public function values()
+    {
+        return $this->hasMany(AttributeValue::class);
+    }
+}

+ 7 - 0
app/Shop/Attributes/Exceptions/AttributeNotFoundException.php

@@ -0,0 +1,7 @@
+<?php
+
+namespace App\Shop\Attributes\Exceptions;
+
+class AttributeNotFoundException extends \Exception
+{
+}

+ 7 - 0
app/Shop/Attributes/Exceptions/CreateAttributeErrorException.php

@@ -0,0 +1,7 @@
+<?php
+
+namespace App\Shop\Attributes\Exceptions;
+
+class CreateAttributeErrorException extends \Exception
+{
+}

+ 7 - 0
app/Shop/Attributes/Exceptions/UpdateAttributeErrorException.php

@@ -0,0 +1,7 @@
+<?php
+
+namespace App\Shop\Attributes\Exceptions;
+
+class UpdateAttributeErrorException extends \Exception
+{
+}

+ 111 - 0
app/Shop/Attributes/Repositories/AttributeRepository.php

@@ -0,0 +1,111 @@
+<?php
+
+namespace App\Shop\Attributes\Repositories;
+
+use App\Shop\Attributes\Attribute;
+use App\Shop\Attributes\Exceptions\AttributeNotFoundException;
+use App\Shop\Attributes\Exceptions\CreateAttributeErrorException;
+use App\Shop\Attributes\Exceptions\UpdateAttributeErrorException;
+use App\Shop\AttributeValues\AttributeValue;
+use Jsdecena\Baserepo\BaseRepository;
+use Illuminate\Database\Eloquent\ModelNotFoundException;
+use Illuminate\Support\Collection;
+use Illuminate\Database\QueryException;
+
+class AttributeRepository extends BaseRepository implements AttributeRepositoryInterface
+{
+    /**
+     * @var Attribute
+     */
+    protected $model;
+
+    /**
+     * AttributeRepository constructor.
+     * @param Attribute $attribute
+     */
+    public function __construct(Attribute $attribute)
+    {
+        parent::__construct($attribute);
+        $this->model = $attribute;
+    }
+
+    /**
+     * @param array $data
+     * @return Attribute
+     * @throws CreateAttributeErrorException
+     */
+    public function createAttribute(array $data) : Attribute
+    {
+        try {
+            $attribute = new Attribute($data);
+            $attribute->save();
+            return $attribute;
+        } catch (QueryException $e) {
+            throw new CreateAttributeErrorException($e);
+        }
+    }
+
+    /**
+     * @param int $id
+     * @return Attribute
+     * @throws AttributeNotFoundException
+     */
+    public function findAttributeById(int $id) : Attribute
+    {
+        try {
+            return $this->findOneOrFail($id);
+        } catch (ModelNotFoundException $e) {
+            throw new AttributeNotFoundException($e);
+        }
+    }
+
+    /**
+     * @param array $data
+     * @return bool
+     * @throws UpdateAttributeErrorException
+     */
+    public function updateAttribute(array $data) : bool
+    {
+        try {
+            return $this->model->update($data);
+        } catch (QueryException $e) {
+            throw new UpdateAttributeErrorException($e);
+        }
+    }
+
+    /**
+     * @return bool|null
+     */
+    public function deleteAttribute() : ?bool
+    {
+        return $this->model->delete();
+    }
+
+    /**
+     * @param array $columns
+     * @param string $orderBy
+     * @param string $sortBy
+     * @return Collection
+     */
+    public function listAttributes($columns = array('*'), string $orderBy = 'id', string $sortBy = 'asc') : Collection
+    {
+        return $this->all($columns, $orderBy, $sortBy);
+    }
+
+    /**
+     * @return Collection
+     */
+    public function listAttributeValues() : Collection
+    {
+        return $this->model->values()->get();
+    }
+
+    /**
+     * @param AttributeValue $attributeValue
+     * @return AttributeValue
+     */
+    public function associateAttributeValue(AttributeValue $attributeValue) : AttributeValue
+    {
+        return $this->model->values()->save($attributeValue);
+    }
+}

+ 25 - 0
app/Shop/Attributes/Repositories/AttributeRepositoryInterface.php

@@ -0,0 +1,25 @@
+<?php
+
+namespace App\Shop\Attributes\Repositories;
+
+use App\Shop\Attributes\Attribute;
+use App\Shop\AttributeValues\AttributeValue;
+use Jsdecena\Baserepo\BaseRepositoryInterface;
+use Illuminate\Support\Collection;
+
+interface AttributeRepositoryInterface extends BaseRepositoryInterface
+{
+    public function createAttribute(array $data) : Attribute;
+
+    public function findAttributeById(int $id) : Attribute;
+
+    public function updateAttribute(array $data) : bool;
+
+    public function deleteAttribute() : ?bool;
+
+    public function listAttributes($columns = array('*'), string $orderBy = 'id', string $sortBy = 'asc') : Collection;
+
+    public function listAttributeValues() : Collection;
+
+    public function associateAttributeValue(AttributeValue $attributeValue) : AttributeValue;
+}

+ 20 - 0
app/Shop/Attributes/Requests/CreateAttributeRequest.php

@@ -0,0 +1,20 @@
+<?php
+
+namespace App\Shop\Attributes\Requests;
+
+use App\Shop\Base\BaseFormRequest;
+
+class CreateAttributeRequest extends BaseFormRequest
+{
+    /**
+     * Get the validation rules that apply to the request.
+     *
+     * @return array
+     */
+    public function rules()
+    {
+        return [
+            'name' => ['required']
+        ];
+    }
+}

+ 18 - 0
app/Shop/Attributes/Requests/UpdateAttributeRequest.php

@@ -0,0 +1,18 @@
+<?php
+
+namespace App\Shop\Attributes\Requests;
+
+use App\Shop\Base\BaseFormRequest;
+
+class UpdateAttributeRequest extends BaseFormRequest
+{
+    /**
+     * Get the validation rules that apply to the request.
+     *
+     * @return array
+     */
+    public function rules()
+    {
+        return [];
+    }
+}

+ 18 - 0
app/Shop/Base/BaseFormRequest.php

@@ -0,0 +1,18 @@
+<?php
+
+namespace App\Shop\Base;
+
+use Illuminate\Foundation\Http\FormRequest;
+
+abstract class BaseFormRequest extends FormRequest
+{
+    /**
+     * Determine if the user is authorized to make this request.
+     *
+     * @return bool
+     */
+    public function authorize()
+    {
+        return true;
+    }
+}

+ 19 - 0
app/Shop/Brands/Brand.php

@@ -0,0 +1,19 @@
+<?php
+
+namespace App\Shop\Brands;
+
+use App\Shop\Products\Product;
+use Illuminate\Database\Eloquent\Model;
+
+class Brand extends Model
+{
+    protected $fillable = ['name'];
+
+    /**
+     * @return \Illuminate\Database\Eloquent\Relations\HasMany
+     */
+    public function products()
+    {
+        return $this->hasMany(Product::class);
+    }
+}

Alguns ficheiros não foram mostrados porque muitos ficheiros mudaram neste diff