├── .env.example ├── .gitattributes ├── .gitignore ├── .phpstorm.meta.php ├── Makefile ├── _ide_helper.php ├── app ├── Console │ ├── Commands │ │ ├── Advert │ │ │ └── ExpireCommand.php │ │ ├── Api │ │ │ └── DocCommand.php │ │ ├── Banner │ │ │ └── ExpireCommand.php │ │ ├── Search │ │ │ ├── InitCommand.php │ │ │ └── ReindexCommand.php │ │ └── User │ │ │ ├── RoleCommand.php │ │ │ └── VerifyCommand.php │ └── Kernel.php ├── Entity │ ├── Adverts │ │ ├── Advert │ │ │ ├── Advert.php │ │ │ ├── Dialog │ │ │ │ ├── Dialog.php │ │ │ │ └── Message.php │ │ │ ├── Photo.php │ │ │ └── Value.php │ │ ├── Attribute.php │ │ └── Category.php │ ├── Banner │ │ └── Banner.php │ ├── Page.php │ ├── Region.php │ ├── Ticket │ │ ├── Message.php │ │ ├── Status.php │ │ └── Ticket.php │ └── User │ │ ├── Network.php │ │ └── User.php ├── Events │ └── Advert │ │ └── ModerationPassed.php ├── Exceptions │ └── Handler.php ├── Http │ ├── Controllers │ │ ├── Admin │ │ │ ├── Adverts │ │ │ │ ├── AdvertController.php │ │ │ │ ├── AttributeController.php │ │ │ │ └── CategoryController.php │ │ │ ├── BannerController.php │ │ │ ├── HomeController.php │ │ │ ├── PageController.php │ │ │ ├── RegionController.php │ │ │ ├── TicketController.php │ │ │ ├── UploadController.php │ │ │ └── UsersController.php │ │ ├── Adverts │ │ │ ├── AdvertController.php │ │ │ └── FavoriteController.php │ │ ├── Api │ │ │ ├── Adverts │ │ │ │ ├── AdvertController.php │ │ │ │ └── FavoriteController.php │ │ │ ├── Auth │ │ │ │ └── RegisterController.php │ │ │ ├── HomeController.php │ │ │ └── User │ │ │ │ ├── AdvertController.php │ │ │ │ ├── FavoriteController.php │ │ │ │ └── ProfileController.php │ │ ├── Auth │ │ │ ├── ForgotPasswordController.php │ │ │ ├── LoginController.php │ │ │ ├── NetworkController.php │ │ │ ├── RegisterController.php │ │ │ └── ResetPasswordController.php │ │ ├── BannerController.php │ │ ├── Cabinet │ │ │ ├── Adverts │ │ │ │ ├── AdvertController.php │ │ │ │ ├── CreateController.php │ │ │ │ └── ManageController.php │ │ │ ├── Banners │ │ │ │ ├── BannerController.php │ │ │ │ └── CreateController.php │ │ │ ├── FavoriteController.php │ │ │ ├── HomeController.php │ │ │ ├── PhoneController.php │ │ │ ├── ProfileController.php │ │ │ └── TicketController.php │ │ ├── Controller.php │ │ ├── HomeController.php │ │ ├── PageController.php │ │ └── PaymentController.php │ ├── Kernel.php │ ├── Middleware │ │ ├── EncryptCookies.php │ │ ├── FilledProfile.php │ │ ├── RedirectIfAuthenticated.php │ │ ├── TrimStrings.php │ │ ├── TrustProxies.php │ │ └── VerifyCsrfToken.php │ ├── Requests │ │ ├── Admin │ │ │ ├── Pages │ │ │ │ └── PageRequest.php │ │ │ └── Users │ │ │ │ ├── CreateRequest.php │ │ │ │ └── UpdateRequest.php │ │ ├── Adverts │ │ │ ├── AttributesRequest.php │ │ │ ├── CreateRequest.php │ │ │ ├── EditRequest.php │ │ │ ├── PhotosRequest.php │ │ │ ├── RejectRequest.php │ │ │ └── SearchRequest.php │ │ ├── Auth │ │ │ ├── LoginRequest.php │ │ │ └── RegisterRequest.php │ │ ├── Banner │ │ │ ├── CreateRequest.php │ │ │ ├── EditRequest.php │ │ │ ├── FileRequest.php │ │ │ └── RejectRequest.php │ │ ├── Cabinet │ │ │ ├── PhoneVerifyRequest.php │ │ │ └── ProfileEditRequest.php │ │ └── Ticket │ │ │ ├── CreateRequest.php │ │ │ ├── EditRequest.php │ │ │ └── MessageRequest.php │ ├── Resources │ │ ├── Adverts │ │ │ ├── AdvertDetailResource.php │ │ │ └── AdvertListResource.php │ │ └── User │ │ │ └── ProfileResource.php │ ├── Router │ │ ├── AdvertsPath.php │ │ └── PagePath.php │ └── ViewComposers │ │ └── MenuPagesComposer.php ├── Jobs │ └── Advert │ │ └── ReindexAdvert.php ├── Listeners │ └── Advert │ │ ├── AdvertChangedListener.php │ │ └── ModerationPassedListener.php ├── Mail │ └── Auth │ │ └── VerifyMail.php ├── Notifications │ ├── Advert │ │ └── ModerationPassedNotification.php │ └── SmsChannel.php ├── Providers │ ├── AppServiceProvider.php │ ├── AuthServiceProvider.php │ ├── BroadcastServiceProvider.php │ ├── CacheServiceProvider.php │ ├── ComposerServiceProvider.php │ ├── EventServiceProvider.php │ ├── RouteServiceProvider.php │ ├── SearchServiceProvider.php │ └── SmsServiceProvider.php ├── Services │ ├── Banner │ │ └── CostCalculator.php │ ├── Search │ │ ├── AdvertIndexer.php │ │ └── BannerIndexer.php │ └── Sms │ │ ├── ArraySender.php │ │ ├── SmsRu.php │ │ └── SmsSender.php ├── UseCases │ ├── Adverts │ │ ├── AdvertService.php │ │ ├── FavoriteService.php │ │ ├── SearchResult.php │ │ └── SearchService.php │ ├── Auth │ │ ├── NetworkService.php │ │ └── RegisterService.php │ ├── Banners │ │ └── BannerService.php │ ├── Profile │ │ ├── PhoneService.php │ │ └── ProfileService.php │ └── Tickets │ │ └── TicketService.php └── helpers.php ├── artisan ├── bootstrap ├── app.php └── cache │ └── .gitignore ├── composer.json ├── composer.lock ├── config ├── app.php ├── auth.php ├── banner.php ├── broadcasting.php ├── cache.php ├── database.php ├── elasticsearch.php ├── filesystems.php ├── hashing.php ├── horizon.php ├── ide-helper.php ├── logging.php ├── mail.php ├── purifier.php ├── queue.php ├── services.php ├── session.php ├── sms.php └── view.php ├── database ├── .gitignore ├── factories │ ├── AdvertsCategoryFactory.php │ ├── RegionFactory.php │ └── UserFactory.php ├── migrations │ ├── 2014_10_12_000000_create_users_table.php │ ├── 2014_10_12_100000_create_password_resets_table.php │ ├── 2016_06_01_000001_create_oauth_auth_codes_table.php │ ├── 2016_06_01_000002_create_oauth_access_tokens_table.php │ ├── 2016_06_01_000003_create_oauth_refresh_tokens_table.php │ ├── 2016_06_01_000004_create_oauth_clients_table.php │ ├── 2016_06_01_000005_create_oauth_personal_access_clients_table.php │ ├── 2018_03_03_193348_add_user_verification.php │ ├── 2018_03_08_100246_add_user_role.php │ ├── 2018_03_08_161503_create_regions_table.php │ ├── 2018_03_08_171503_create_advert_categories_table.php │ ├── 2018_03_10_132930_create_advert_attributes_table.php │ ├── 2018_03_12_083000_add_user_last_name.php │ ├── 2018_03_12_090001_add_user_phone.php │ ├── 2018_03_12_124830_add_user_phone_auth.php │ ├── 2018_03_15_135243_create_adverts_tables.php │ ├── 2018_03_19_123423_create_advert_favorites_table.php │ ├── 2018_03_22_090624_create_banners_table.php │ ├── 2018_03_25_183121_add_networks_auth.php │ ├── 2018_03_29_174253_create_pages_table.php │ └── 2018_03_30_132456_create_tickets_tables.php └── seeds │ ├── AdvertCategoriesTableSeeder.php │ ├── DatabaseSeeder.php │ ├── RegionsTableSeeder.php │ └── UsersTableSeeder.php ├── docker-compose.yml ├── docker ├── nginx.docker ├── nginx │ ├── default.conf │ └── ssl │ │ ├── ssl-cert-snakeoil.key │ │ └── ssl-cert-snakeoil.pem ├── php-cli.docker └── php-fpm.docker ├── package.json ├── phpunit.xml ├── public ├── .htaccess ├── docs │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── index.html │ ├── oauth2-redirect.html │ ├── swagger-ui-bundle.js │ ├── swagger-ui-bundle.js.map │ ├── swagger-ui-standalone-preset.js │ ├── swagger-ui-standalone-preset.js.map │ ├── swagger-ui.css │ ├── swagger-ui.css.map │ ├── swagger-ui.js │ ├── swagger-ui.js.map │ └── swagger.json ├── favicon.ico ├── index.php ├── robots.txt ├── vendor │ └── horizon │ │ ├── css │ │ ├── app.css │ │ └── app.css.map │ │ ├── img │ │ ├── favicon.png │ │ ├── horizon.svg │ │ └── sprite.svg │ │ ├── js │ │ ├── app.js │ │ └── app.js.map │ │ └── mix-manifest.json └── web.config ├── readme.md ├── resources ├── assets │ ├── js │ │ ├── app.js │ │ └── bootstrap.js │ └── sass │ │ ├── _variables.scss │ │ └── app.scss ├── lang │ └── en │ │ ├── auth.php │ │ ├── pagination.php │ │ ├── passwords.php │ │ └── validation.php └── views │ ├── admin │ ├── _nav.blade.php │ ├── adverts │ │ ├── adverts │ │ │ ├── _nav.blade.php │ │ │ ├── index.blade.php │ │ │ └── reject.blade.php │ │ └── categories │ │ │ ├── _nav.blade.php │ │ │ ├── attributes │ │ │ ├── create.blade.php │ │ │ ├── edit.blade.php │ │ │ └── show.blade.php │ │ │ ├── create.blade.php │ │ │ ├── edit.blade.php │ │ │ ├── index.blade.php │ │ │ └── show.blade.php │ ├── banners │ │ ├── _nav.blade.php │ │ ├── index.blade.php │ │ ├── reject.blade.php │ │ └── show.blade.php │ ├── home.blade.php │ ├── pages │ │ ├── _nav.blade.php │ │ ├── create.blade.php │ │ ├── edit.blade.php │ │ ├── index.blade.php │ │ └── show.blade.php │ ├── regions │ │ ├── _list.blade.php │ │ ├── _nav.blade.php │ │ ├── create.blade.php │ │ ├── edit.blade.php │ │ ├── index.blade.php │ │ └── show.blade.php │ ├── tickets │ │ ├── _nav.blade.php │ │ ├── edit.blade.php │ │ ├── index.blade.php │ │ └── show.blade.php │ └── users │ │ ├── _nav.blade.php │ │ ├── create.blade.php │ │ ├── edit.blade.php │ │ ├── index.blade.php │ │ └── show.blade.php │ ├── adverts │ ├── edit │ │ ├── advert.blade.php │ │ ├── attributes.blade.php │ │ └── photos.blade.php │ ├── index.blade.php │ └── show.blade.php │ ├── auth │ ├── login.blade.php │ ├── passwords │ │ ├── email.blade.php │ │ └── reset.blade.php │ ├── phone.blade.php │ └── register.blade.php │ ├── banner │ └── get.blade.php │ ├── cabinet │ ├── adverts │ │ ├── _nav.blade.php │ │ ├── create │ │ │ ├── _categories.blade.php │ │ │ ├── advert.blade.php │ │ │ ├── category.blade.php │ │ │ └── region.blade.php │ │ └── index.blade.php │ ├── banners │ │ ├── _nav.blade.php │ │ ├── create │ │ │ ├── _categories.blade.php │ │ │ ├── banner.blade.php │ │ │ ├── category.blade.php │ │ │ └── region.blade.php │ │ ├── edit.blade.php │ │ ├── file.blade.php │ │ ├── index.blade.php │ │ └── show.blade.php │ ├── favorites │ │ ├── _nav.blade.php │ │ └── index.blade.php │ ├── home.blade.php │ ├── profile │ │ ├── _nav.blade.php │ │ ├── edit.blade.php │ │ ├── home.blade.php │ │ └── phone.blade.php │ └── tickets │ │ ├── _nav.blade.php │ │ ├── create.blade.php │ │ ├── index.blade.php │ │ └── show.blade.php │ ├── emails │ └── auth │ │ └── register │ │ └── verify.blade.php │ ├── home.blade.php │ ├── layouts │ ├── app.blade.php │ └── partials │ │ ├── flash.blade.php │ │ └── search.blade.php │ └── page.blade.php ├── routes ├── api.php ├── breadcrumbs.php ├── channels.php ├── console.php └── web.php ├── server.php ├── storage ├── app │ ├── .gitignore │ └── public │ │ └── .gitignore ├── debugbar │ └── .gitignore ├── docker │ └── .gitignore ├── framework │ ├── .gitignore │ ├── cache │ │ └── .gitignore │ ├── sessions │ │ └── .gitignore │ ├── testing │ │ └── .gitignore │ └── views │ │ └── .gitignore └── logs │ └── .gitignore ├── tests ├── CreatesApplication.php ├── Feature │ ├── Auth │ │ ├── LoginTest.php │ │ └── RegisterTest.php │ └── ExampleTest.php ├── TestCase.php └── Unit │ └── Entity │ └── User │ ├── CreateTest.php │ ├── PhoneTest.php │ ├── RegisterTest.php │ └── RoleTest.php ├── webpack.mix.js └── yarn.lock /.env.example: -------------------------------------------------------------------------------- 1 | APP_NAME=Laravel 2 | APP_ENV=local 3 | APP_KEY= 4 | APP_DEBUG=true 5 | APP_URL=https://localhost:8080 6 | 7 | LOG_CHANNEL=stack 8 | 9 | DB_CONNECTION=mysql 10 | DB_HOST=127.0.0.1 11 | DB_PORT=33061 12 | DB_DATABASE=app 13 | DB_USERNAME=app 14 | DB_PASSWORD=secret 15 | 16 | BROADCAST_DRIVER=log 17 | CACHE_DRIVER=redis 18 | SESSION_DRIVER=redis 19 | SESSION_LIFETIME=120 20 | QUEUE_DRIVER=redis 21 | 22 | REDIS_HOST=127.0.0.1 23 | REDIS_PASSWORD=null 24 | REDIS_PORT=63791 25 | 26 | MAIL_DRIVER=smtp 27 | MAIL_HOST=smtp.mailtrap.io 28 | MAIL_PORT=2525 29 | MAIL_USERNAME=null 30 | MAIL_PASSWORD=null 31 | MAIL_ENCRYPTION=null 32 | 33 | PUSHER_APP_ID= 34 | PUSHER_APP_KEY= 35 | PUSHER_APP_SECRET= 36 | PUSHER_APP_CLUSTER=mt1 37 | 38 | MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}" 39 | MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" 40 | 41 | SMS_DRIVER=sms.ru 42 | SMS_SMS_RU_APP_ID= 43 | SMS_SMS_RU_APP_URL= 44 | 45 | BANNER_COST_PER_MILE= 46 | 47 | ELASTICSEARCH_HOSTS=127.0.0.1:9201 48 | 49 | FACEBOOK_CLIENT_ID= 50 | FACEBOOK_CLIENT_SECRET= 51 | 52 | TWITTER_CLIENT_ID= 53 | TWITTER_CLIENT_SECRET= 54 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.css linguist-vendored 3 | *.scss linguist-vendored 4 | *.js linguist-vendored 5 | CHANGELOG.md export-ignore 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /public/build 3 | /public/storage 4 | /storage/*.key 5 | /vendor 6 | /.idea 7 | /.vscode 8 | /.vagrant 9 | Homestead.json 10 | Homestead.yaml 11 | npm-debug.log 12 | yarn-error.log 13 | .env 14 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | docker-up: memory 2 | docker-compose up -d 3 | 4 | docker-down: 5 | docker-compose down 6 | 7 | docker-build: memory 8 | docker-compose up --build -d 9 | 10 | test: 11 | docker-compose exec php-cli vendor/bin/phpunit 12 | 13 | assets-install: 14 | docker-compose exec node yarn install 15 | 16 | assets-rebuild: 17 | docker-compose exec node npm rebuild node-sass --force 18 | 19 | assets-dev: 20 | docker-compose exec node yarn run dev 21 | 22 | assets-watch: 23 | docker-compose exec node yarn run watch 24 | 25 | queue: 26 | docker-compose exec php-cli php artisan queue:work 27 | 28 | horizon: 29 | docker-compose exec php-cli php artisan horizon 30 | 31 | horizon-pause: 32 | docker-compose exec php-cli php artisan horizon:pause 33 | 34 | horizon-continue: 35 | docker-compose exec php-cli php artisan horizon:continue 36 | 37 | horizon-terminate: 38 | docker-compose exec php-cli php artisan horizon:terminate 39 | 40 | memory: 41 | sudo sysctl -w vm.max_map_count=262144 42 | 43 | perm: 44 | sudo chgrp -R www-data storage bootstrap/cache 45 | sudo chmod -R ug+rwx storage bootstrap/cache -------------------------------------------------------------------------------- /app/Console/Commands/Advert/ExpireCommand.php: -------------------------------------------------------------------------------- 1 | service = $service; 20 | } 21 | 22 | public function handle(): bool 23 | { 24 | $success = true; 25 | 26 | foreach (Advert::active()->where('expired_at', '<', Carbon::now())->cursor() as $advert) { 27 | try { 28 | $this->service->expire($advert); 29 | } catch (\DomainException $e) { 30 | $this->error($e->getMessage()); 31 | $success = false; 32 | } 33 | } 34 | 35 | return $success; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/Console/Commands/Api/DocCommand.php: -------------------------------------------------------------------------------- 1 | client = $client; 20 | } 21 | 22 | public function handle(): bool 23 | { 24 | $success = true; 25 | 26 | foreach (Banner::active()->whereRaw('`limit` - views < 100')->with('user')->cursor() as $banner) { 27 | $key = 'banner_notify_' . $banner->id; 28 | if ($this->client->get($key)) { 29 | continue; 30 | } 31 | Mail::to($banner->user->email)->queue(new BannerExpiresSoonMail($banner)); 32 | $this->client->set($key, true); 33 | } 34 | 35 | return $success; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/Console/Commands/Search/ReindexCommand.php: -------------------------------------------------------------------------------- 1 | adverts = $adverts; 22 | $this->banners = $banners; 23 | } 24 | 25 | public function handle(): bool 26 | { 27 | $this->adverts->clear(); 28 | 29 | foreach (Advert::active()->orderBy('id')->cursor() as $advert) { 30 | $this->adverts->index($advert); 31 | } 32 | 33 | $this->banners->clear(); 34 | 35 | foreach (Banner::active()->orderBy('id')->cursor() as $banner) { 36 | $this->banners->index($banner); 37 | } 38 | 39 | return true; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/Console/Commands/User/RoleCommand.php: -------------------------------------------------------------------------------- 1 | argument('email'); 17 | $role = $this->argument('role'); 18 | 19 | /** @var User $user */ 20 | if (!$user = User::where('email', $email)->first()) { 21 | $this->error('Undefined user with email ' . $email); 22 | return false; 23 | } 24 | 25 | try { 26 | $user->changeRole($role); 27 | } catch (\DomainException $e) { 28 | $this->error($e->getMessage()); 29 | return false; 30 | } 31 | 32 | $this->info('Role is successfully changed'); 33 | return true; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/Console/Commands/User/VerifyCommand.php: -------------------------------------------------------------------------------- 1 | service = $service; 22 | } 23 | 24 | public function handle(): bool 25 | { 26 | $email = $this->argument('email'); 27 | 28 | /** @var User $user */ 29 | if (!$user = User::where('email', $email)->first()) { 30 | $this->error('Undefined user with email ' . $email); 31 | return false; 32 | } 33 | 34 | try { 35 | $this->service->verify($user->id); 36 | } catch (\DomainException $e) { 37 | $this->error($e->getMessage()); 38 | return false; 39 | } 40 | 41 | $this->info('User is successfully verified'); 42 | return true; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/Console/Kernel.php: -------------------------------------------------------------------------------- 1 | command('banner:expire')->daily(); 17 | $schedule->command('advert:expire')->hourly(); 18 | } 19 | 20 | protected function commands() 21 | { 22 | $this->load(__DIR__.'/Commands'); 23 | 24 | require base_path('routes/console.php'); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/Entity/Adverts/Advert/Dialog/Dialog.php: -------------------------------------------------------------------------------- 1 | messages()->create([ 28 | 'user_id' => $userId, 29 | 'message' => $message, 30 | ]); 31 | $this->client_new_messages++; 32 | $this->save(); 33 | } 34 | 35 | public function writeMessageByClient(int $userId, string $message): void 36 | { 37 | $this->messages()->create([ 38 | 'user_id' => $userId, 39 | 'message' => $message, 40 | ]); 41 | $this->user_new_messages++; 42 | $this->save(); 43 | } 44 | 45 | public function readByOwner(): void 46 | { 47 | $this->update(['user_new_messages' => 0]); 48 | } 49 | 50 | public function readByClient(): void 51 | { 52 | $this->update(['client_new_messages' => 0]); 53 | } 54 | 55 | public function client() 56 | { 57 | return $this->belongsTo(User::class, 'client_id', 'id'); 58 | } 59 | 60 | public function messages() 61 | { 62 | return $this->hasMany(Message::class, 'dialog_id', 'id'); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/Entity/Adverts/Advert/Dialog/Message.php: -------------------------------------------------------------------------------- 1 | belongsTo(User::class, 'user_id', 'id'); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/Entity/Adverts/Advert/Photo.php: -------------------------------------------------------------------------------- 1 | 'array', 31 | ]; 32 | 33 | public static function typesList(): array 34 | { 35 | return [ 36 | self::TYPE_STRING => 'String', 37 | self::TYPE_INTEGER => 'Integer', 38 | self::TYPE_FLOAT => 'Float', 39 | ]; 40 | } 41 | 42 | public function isString(): bool 43 | { 44 | return $this->type === self::TYPE_STRING; 45 | } 46 | 47 | public function isInteger(): bool 48 | { 49 | return $this->type === self::TYPE_INTEGER; 50 | } 51 | 52 | public function isFloat(): bool 53 | { 54 | return $this->type === self::TYPE_FLOAT; 55 | } 56 | 57 | public function isNumber(): bool 58 | { 59 | return $this->isInteger() || $this->isFloat(); 60 | } 61 | 62 | public function isSelect(): bool 63 | { 64 | return \count($this->variants) > 0; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /app/Entity/Adverts/Category.php: -------------------------------------------------------------------------------- 1 | ancestors()->defaultOrder()->pluck('slug')->toArray(), [$this->slug])); 32 | } 33 | 34 | public function parentAttributes(): array 35 | { 36 | return $this->parent ? $this->parent->allAttributes() : []; 37 | } 38 | 39 | /** 40 | * @return Attribute[] 41 | */ 42 | public function allAttributes(): array 43 | { 44 | return array_merge($this->parentAttributes(), $this->attributes()->orderBy('sort')->getModels()); 45 | } 46 | 47 | public function attributes() 48 | { 49 | return $this->hasMany(Attribute::class, 'category_id', 'id'); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/Entity/Page.php: -------------------------------------------------------------------------------- 1 | ancestors()->defaultOrder()->pluck('slug')->toArray(), [$this->slug])); 32 | } 33 | 34 | public function getMenuTitle(): string 35 | { 36 | return $this->menu_title ?: $this->title; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/Entity/Region.php: -------------------------------------------------------------------------------- 1 | parent ? $this->parent->getPath() . '/' : '') . $this->slug; 26 | } 27 | 28 | public function getAddress(): string 29 | { 30 | return ($this->parent ? $this->parent->getAddress() . ', ' : '') . $this->name; 31 | } 32 | 33 | public function parent() 34 | { 35 | return $this->belongsTo(static::class, 'parent_id', 'id'); 36 | } 37 | 38 | public function children() 39 | { 40 | return $this->hasMany(static::class, 'parent_id', 'id'); 41 | } 42 | 43 | public function scopeRoots(Builder $query) 44 | { 45 | return $query->where('parent_id', null); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/Entity/Ticket/Message.php: -------------------------------------------------------------------------------- 1 | belongsTo(User::class, 'user_id', 'id'); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/Entity/Ticket/Status.php: -------------------------------------------------------------------------------- 1 | 'Open', 31 | self::APPROVED => 'Approved', 32 | self::CLOSED => 'Closed', 33 | ]; 34 | } 35 | 36 | public function isOpen(): bool 37 | { 38 | return $this->status === self::OPEN; 39 | } 40 | 41 | public function isApproved(): bool 42 | { 43 | return $this->status === self::APPROVED; 44 | } 45 | 46 | public function isClosed(): bool 47 | { 48 | return $this->status === self::CLOSED; 49 | } 50 | 51 | public function user() 52 | { 53 | return $this->belongsTo(User::class, 'user_id', 'id'); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/Entity/User/Network.php: -------------------------------------------------------------------------------- 1 | advert = $advert; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/Exceptions/Handler.php: -------------------------------------------------------------------------------- 1 | expectsJson()) { 28 | return response()->json([ 29 | 'message' => $exception->getMessage(), 30 | ], Response::HTTP_BAD_REQUEST); 31 | } 32 | 33 | return parent::render($request, $exception); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/Http/Controllers/Admin/HomeController.php: -------------------------------------------------------------------------------- 1 | validate($request, [ 14 | 'file' => 'required|image|mimes:jpg,jpeg,png', 15 | ]); 16 | 17 | $file = $request->file('file'); 18 | $path = $file->store('images', 'public'); 19 | 20 | return Storage::disk('public')->url($path); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/Http/Controllers/Adverts/FavoriteController.php: -------------------------------------------------------------------------------- 1 | service = $service; 22 | $this->middleware('auth'); 23 | } 24 | 25 | public function add(Advert $advert) 26 | { 27 | try { 28 | $this->service->add(Auth::id(), $advert->id); 29 | } catch (\DomainException $e) { 30 | return back()->with('error', $e->getMessage()); 31 | } 32 | 33 | return redirect()->route('adverts.show', $advert)->with('success', 'Advert is added to your favorites.'); 34 | } 35 | 36 | public function remove(Advert $advert) 37 | { 38 | try { 39 | $this->service->remove(Auth::id(), $advert->id); 40 | } catch (\DomainException $e) { 41 | return back()->with('error', $e->getMessage()); 42 | } 43 | 44 | return redirect()->route('adverts.show', $advert); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/Http/Controllers/Api/Adverts/FavoriteController.php: -------------------------------------------------------------------------------- 1 | service = $service; 18 | } 19 | 20 | /** 21 | * @SWG\Post( 22 | * path="/adverts/{advertId}/favorite", 23 | * tags={"Adverts"}, 24 | * @SWG\Parameter(name="advertId", in="path", required=true, type="integer"), 25 | * @SWG\Response( 26 | * response=201, 27 | * description="Success response", 28 | * ), 29 | * security={{"Bearer": {}, "OAuth2": {}}} 30 | * ) 31 | */ 32 | public function add(Advert $advert) 33 | { 34 | $this->service->add(Auth::id(), $advert->id); 35 | return response()->json([], Response::HTTP_CREATED); 36 | } 37 | 38 | /** 39 | * @SWG\Delete( 40 | * path="/adverts/{advertId}/favorite", 41 | * tags={"Adverts"}, 42 | * @SWG\Parameter(name="advertId", in="path", required=true, type="integer"), 43 | * @SWG\Response( 44 | * response=204, 45 | * description="Success response", 46 | * ), 47 | * security={{"Bearer": {}, "OAuth2": {}}} 48 | * ) 49 | */ 50 | public function remove(Advert $advert) 51 | { 52 | $this->service->remove(Auth::id(), $advert->id); 53 | return response()->json([], Response::HTTP_NO_CONTENT); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/Http/Controllers/Api/Auth/RegisterController.php: -------------------------------------------------------------------------------- 1 | service = $service; 17 | } 18 | 19 | /** 20 | * @SWG\Post( 21 | * path="/register", 22 | * tags={"Profile"}, 23 | * @SWG\Parameter(name="body", in="body", required=true, @SWG\Schema(ref="#/definitions/RegisterRequest")), 24 | * @SWG\Response( 25 | * response=201, 26 | * description="Success response", 27 | * ) 28 | * ) 29 | */ 30 | public function register(RegisterRequest $request) 31 | { 32 | $this->service->register($request); 33 | 34 | return response()->json([ 35 | 'success' => 'Check your email and click on the link to verify.' 36 | ], Response::HTTP_CREATED); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/Http/Controllers/Api/HomeController.php: -------------------------------------------------------------------------------- 1 | 'Board API', 64 | ]; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /app/Http/Controllers/Api/User/FavoriteController.php: -------------------------------------------------------------------------------- 1 | service = $service; 18 | $this->middleware('auth'); 19 | } 20 | 21 | /** 22 | * @SWG\Get( 23 | * path="/user/favorites", 24 | * tags={"Favorites"}, 25 | * @SWG\Response( 26 | * response=200, 27 | * description="Success response", 28 | * @SWG\Schema( 29 | * type="array", 30 | * @SWG\Items(ref="#/definitions/AdvertList") 31 | * ), 32 | * ), 33 | * security={{"Bearer": {}, "OAuth2": {}}} 34 | * ) 35 | */ 36 | public function index() 37 | { 38 | $adverts = Advert::favoredByUser(Auth::user())->orderByDesc('id')->paginate(20); 39 | 40 | return AdvertDetailResource::collection($adverts); 41 | } 42 | 43 | /** 44 | * @SWG\Delete( 45 | * path="/user/favorites/{advertId}", 46 | * tags={"Favorites"}, 47 | * @SWG\Parameter(name="advertId", in="path", required=true, type="integer"), 48 | * @SWG\Response( 49 | * response=204, 50 | * description="Success response", 51 | * ), 52 | * security={{"Bearer": {}, "OAuth2": {}}} 53 | * ) 54 | */ 55 | public function remove(Advert $advert) 56 | { 57 | try { 58 | $this->service->remove(Auth::id(), $advert->id); 59 | } catch (\DomainException $e) { 60 | return back()->with('error', $e->getMessage()); 61 | } 62 | 63 | return redirect()->route('cabinet.favorites.index'); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /app/Http/Controllers/Api/User/ProfileController.php: -------------------------------------------------------------------------------- 1 | service = $service; 19 | } 20 | 21 | /** 22 | * @SWG\Get( 23 | * path="/user", 24 | * tags={"Profile"}, 25 | * @SWG\Response( 26 | * response=200, 27 | * description="Success response", 28 | * @SWG\Schema(ref="#/definitions/Profile"), 29 | * ), 30 | * security={{"Bearer": {}, "OAuth2": {}}} 31 | * ) 32 | */ 33 | public function show(Request $request) 34 | { 35 | return new ProfileResource($request->user()); 36 | } 37 | 38 | /** 39 | * @SWG\Put( 40 | * path="/user", 41 | * tags={"Profile"}, 42 | * @SWG\Parameter(name="body", in="body", required=true, @SWG\Schema(ref="#/definitions/ProfileEditRequest")), 43 | * @SWG\Response( 44 | * response=200, 45 | * description="Success response", 46 | * ), 47 | * security={{"Bearer": {}, "OAuth2": {}}} 48 | * ) 49 | */ 50 | public function update(ProfileEditRequest $request) 51 | { 52 | $this->service->edit($request->user()->id, $request); 53 | 54 | $user = User::findOrFail($request->user()->id); 55 | return new ProfileResource($user); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/Http/Controllers/Auth/ForgotPasswordController.php: -------------------------------------------------------------------------------- 1 | middleware('guest'); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/Http/Controllers/Auth/NetworkController.php: -------------------------------------------------------------------------------- 1 | service = $service; 17 | } 18 | 19 | public function redirect(string $network) 20 | { 21 | return Socialite::driver($network)->redirect(); 22 | } 23 | 24 | public function callback(string $network) 25 | { 26 | $data = Socialite::driver($network)->user(); 27 | 28 | try { 29 | $user = $this->service->auth($network, $data); 30 | Auth::login($user); 31 | return redirect()->intended(); 32 | } catch (\DomainException $e) { 33 | return redirect()->route('login')->with('error', $e->getMessage()); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/Http/Controllers/Auth/RegisterController.php: -------------------------------------------------------------------------------- 1 | middleware('guest'); 17 | $this->service = $service; 18 | } 19 | 20 | public function showRegistrationForm() 21 | { 22 | return view('auth.register'); 23 | } 24 | 25 | public function register(RegisterRequest $request) 26 | { 27 | $this->service->register($request); 28 | 29 | return redirect()->route('login') 30 | ->with('success', 'Check your email and click on the link to verify.'); 31 | } 32 | 33 | public function verify($token) 34 | { 35 | if (!$user = User::where('verify_token', $token)->first()) { 36 | return redirect()->route('login') 37 | ->with('error', 'Sorry your link cannot be identified.'); 38 | } 39 | 40 | try { 41 | $this->service->verify($user->id); 42 | return redirect()->route('login')->with('success', 'Your e-mail is verified. You can now login.'); 43 | } catch (\DomainException $e) { 44 | return redirect()->route('login')->with('error', $e->getMessage()); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/Http/Controllers/Auth/ResetPasswordController.php: -------------------------------------------------------------------------------- 1 | middleware('guest'); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/Http/Controllers/BannerController.php: -------------------------------------------------------------------------------- 1 | service = $service; 16 | } 17 | 18 | public function get(Request $request) 19 | { 20 | $format = $request['format']; 21 | $category = $request['category']; 22 | $region = $request['region']; 23 | 24 | if (!$banner = $this->service->getRandomForView($category, $region, $format)) { 25 | return ''; 26 | } 27 | 28 | return view('banner.get', compact('banner')); 29 | } 30 | 31 | public function click(Banner $banner) 32 | { 33 | $this->service->click($banner); 34 | return redirect($banner->url); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/Http/Controllers/Cabinet/Adverts/AdvertController.php: -------------------------------------------------------------------------------- 1 | orderByDesc('id')->paginate(20); 14 | 15 | return view('cabinet.adverts.index', compact('adverts')); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/Http/Controllers/Cabinet/Adverts/CreateController.php: -------------------------------------------------------------------------------- 1 | service = $service; 19 | } 20 | 21 | public function category() 22 | { 23 | $categories = Category::defaultOrder()->withDepth()->get()->toTree(); 24 | 25 | return view('cabinet.adverts.create.category', compact('categories')); 26 | } 27 | 28 | public function region(Category $category, Region $region = null) 29 | { 30 | $regions = Region::where('parent_id', $region ? $region->id : null)->orderBy('name')->get(); 31 | 32 | return view('cabinet.adverts.create.region', compact('category', 'region', 'regions')); 33 | } 34 | 35 | public function advert(Category $category, Region $region = null) 36 | { 37 | return view('cabinet.adverts.create.advert', compact('category', 'region')); 38 | } 39 | 40 | public function store(CreateRequest $request, Category $category, Region $region = null) 41 | { 42 | try { 43 | $advert = $this->service->create( 44 | Auth::id(), 45 | $category->id, 46 | $region ? $region->id : null, 47 | $request 48 | ); 49 | } catch (\DomainException $e) { 50 | return back()->with('error', $e->getMessage()); 51 | } 52 | 53 | return redirect()->route('adverts.show', $advert); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/Http/Controllers/Cabinet/Banners/CreateController.php: -------------------------------------------------------------------------------- 1 | service = $service; 20 | } 21 | 22 | public function category() 23 | { 24 | $categories = Category::defaultOrder()->withDepth()->get()->toTree(); 25 | 26 | return view('cabinet.banners.create.category', compact('categories')); 27 | } 28 | 29 | public function region(Category $category, Region $region = null) 30 | { 31 | $regions = Region::where('parent_id', $region ? $region->id : null)->orderBy('name')->get(); 32 | 33 | return view('cabinet.banners.create.region', compact('category', 'region', 'regions')); 34 | } 35 | 36 | public function banner(Category $category, Region $region = null) 37 | { 38 | $formats = Banner::formatsList(); 39 | 40 | return view('cabinet.banners.create.banner', compact('category', 'region', 'formats')); 41 | } 42 | 43 | public function store(CreateRequest $request, Category $category, Region $region = null) 44 | { 45 | try { 46 | $banner = $this->service->create( 47 | Auth::user(), 48 | $category, 49 | $region, 50 | $request 51 | ); 52 | } catch (\DomainException $e) { 53 | return back()->with('error', $e->getMessage()); 54 | } 55 | 56 | return redirect()->route('cabinet.banners.show', $banner); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /app/Http/Controllers/Cabinet/FavoriteController.php: -------------------------------------------------------------------------------- 1 | service = $service; 17 | $this->middleware('auth'); 18 | } 19 | 20 | public function index() 21 | { 22 | $adverts = Advert::favoredByUser(Auth::user())->orderByDesc('id')->paginate(20); 23 | 24 | return view('cabinet.favorites.index', compact('adverts')); 25 | } 26 | 27 | public function remove(Advert $advert) 28 | { 29 | try { 30 | $this->service->remove(Auth::id(), $advert->id); 31 | } catch (\DomainException $e) { 32 | return back()->with('error', $e->getMessage()); 33 | } 34 | 35 | return redirect()->route('cabinet.favorites.index'); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/Http/Controllers/Cabinet/HomeController.php: -------------------------------------------------------------------------------- 1 | service = $service; 17 | } 18 | 19 | public function request() 20 | { 21 | try { 22 | $this->service->request(Auth::id()); 23 | } catch (\DomainException $e) { 24 | return redirect()->back()->with('error', $e->getMessage()); 25 | } 26 | 27 | return redirect()->route('cabinet.profile.phone'); 28 | } 29 | 30 | public function form() 31 | { 32 | $user = Auth::user(); 33 | 34 | return view('cabinet.profile.phone', compact('user')); 35 | } 36 | 37 | public function verify(PhoneVerifyRequest $request) 38 | { 39 | try { 40 | $this->service->verify(Auth::id(), $request); 41 | } catch (\DomainException $e) { 42 | return redirect()->route('cabinet.profile.phone')->with('error', $e->getMessage()); 43 | } 44 | 45 | return redirect()->route('cabinet.profile.home'); 46 | } 47 | 48 | public function auth() 49 | { 50 | $this->service->toggleAuth(Auth::id()); 51 | 52 | return redirect()->route('cabinet.profile.home'); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/Http/Controllers/Cabinet/ProfileController.php: -------------------------------------------------------------------------------- 1 | service = $service; 18 | } 19 | 20 | public function index() 21 | { 22 | $user = Auth::user(); 23 | 24 | return view('cabinet.profile.home', compact('user')); 25 | } 26 | 27 | public function edit() 28 | { 29 | $user = Auth::user(); 30 | 31 | return view('cabinet.profile.edit', compact('user')); 32 | } 33 | 34 | public function update(ProfileEditRequest $request) 35 | { 36 | try { 37 | $this->service->edit(Auth::id(), $request); 38 | } catch (\DomainException $e) { 39 | return redirect()->back()->with('error', $e->getMessage()); 40 | } 41 | return redirect()->route('cabinet.profile.home'); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/Http/Controllers/Controller.php: -------------------------------------------------------------------------------- 1 | orderBy('name')->getModels(); 13 | 14 | $categories = Category::whereIsRoot()->defaultOrder()->getModels(); 15 | 16 | return view('home', compact('regions', 'categories')); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/Http/Controllers/PageController.php: -------------------------------------------------------------------------------- 1 | page; 12 | 13 | return view('page', compact('page')); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/Http/Controllers/PaymentController.php: -------------------------------------------------------------------------------- 1 | pay(Carbon::now()); 30 | 31 | return 'OK' . $inv_id; 32 | } 33 | 34 | public function success(Request $request) 35 | { 36 | 37 | } 38 | 39 | public function fail(Request $request) 40 | { 41 | 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/Http/Kernel.php: -------------------------------------------------------------------------------- 1 | [ 19 | \App\Http\Middleware\EncryptCookies::class, 20 | \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, 21 | \Illuminate\Session\Middleware\StartSession::class, 22 | \Illuminate\Session\Middleware\AuthenticateSession::class, 23 | \Illuminate\View\Middleware\ShareErrorsFromSession::class, 24 | \App\Http\Middleware\VerifyCsrfToken::class, 25 | \Illuminate\Routing\Middleware\SubstituteBindings::class, 26 | ], 27 | 28 | 'api' => [ 29 | 'throttle:60,1', 30 | 'bindings', 31 | ], 32 | ]; 33 | 34 | protected $routeMiddleware = [ 35 | 'auth' => \Illuminate\Auth\Middleware\Authenticate::class, 36 | 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, 37 | 'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class, 38 | 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class, 39 | 'can' => \Illuminate\Auth\Middleware\Authorize::class, 40 | 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, 41 | 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 42 | ]; 43 | } 44 | -------------------------------------------------------------------------------- /app/Http/Middleware/EncryptCookies.php: -------------------------------------------------------------------------------- 1 | hasFilledProfile()) { 14 | return redirect() 15 | ->route('cabinet.profile.home') 16 | ->with('error', 'Please fill your profile and verify your phone.'); 17 | } 18 | 19 | return $next($request); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/Http/Middleware/RedirectIfAuthenticated.php: -------------------------------------------------------------------------------- 1 | check()) { 13 | return redirect()->route('home'); 14 | } 15 | 16 | return $next($request); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/Http/Middleware/TrimStrings.php: -------------------------------------------------------------------------------- 1 | 'required|string|max:255', 18 | 'slug' => 'required|string|max:255', 19 | 'menu_title' => 'required|string|max:255', 20 | 'parent' => 'nullable|integer|exists:pages,id', 21 | 'content' => 'nullable|string', 22 | 'description' => 'nullable|string', 23 | ]; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/Http/Requests/Admin/Users/CreateRequest.php: -------------------------------------------------------------------------------- 1 | 'required|string|max:255', 18 | 'email' => 'required|string|email|max:255|unique:users', 19 | ]; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/Http/Requests/Admin/Users/UpdateRequest.php: -------------------------------------------------------------------------------- 1 | 'required|string|max:255', 23 | 'email' => 'required|string|email|max:255|unique:users,id,' . $this->user->id, 24 | 'role' => ['required', 'string', Rule::in(array_keys(User::rolesList()))] 25 | ]; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/Http/Requests/Adverts/AttributesRequest.php: -------------------------------------------------------------------------------- 1 | advert->category->allAttributes() as $attribute) { 24 | $rules = [ 25 | $attribute->required ? 'required' : 'nullable', 26 | ]; 27 | if ($attribute->isInteger()) { 28 | $rules[] = 'integer'; 29 | } elseif ($attribute->isFloat()) { 30 | $rules[] = 'numeric'; 31 | } else { 32 | $rules[] = 'string'; 33 | $rules[] = 'max:255'; 34 | } 35 | if ($attribute->isSelect()) { 36 | $rules[] = Rule::in($attribute->variants); 37 | } 38 | $items['attribute.' . $attribute->id] = $rules; 39 | } 40 | 41 | return $items; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/Http/Requests/Adverts/CreateRequest.php: -------------------------------------------------------------------------------- 1 | category->allAttributes() as $attribute) { 26 | $rules = [ 27 | $attribute->required ? 'required' : 'nullable', 28 | ]; 29 | if ($attribute->isInteger()) { 30 | $rules[] = 'integer'; 31 | } elseif ($attribute->isFloat()) { 32 | $rules[] = 'numeric'; 33 | } else { 34 | $rules[] = 'string'; 35 | $rules[] = 'max:255'; 36 | } 37 | if ($attribute->isSelect()) { 38 | $rules[] = Rule::in($attribute->variants); 39 | } 40 | $items['attribute.' . $attribute->id] = $rules; 41 | } 42 | 43 | return array_merge([ 44 | 'title' => 'required|string', 45 | 'content' => 'required|string', 46 | 'price' => 'required|integer', 47 | 'address' => 'required|string', 48 | ], $items); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/Http/Requests/Adverts/EditRequest.php: -------------------------------------------------------------------------------- 1 | 'required|string', 25 | 'content' => 'required|string', 26 | 'price' => 'required|integer', 27 | 'address' => 'required|string', 28 | ]; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/Http/Requests/Adverts/PhotosRequest.php: -------------------------------------------------------------------------------- 1 | 'required|image|mimes:jpg,jpeg,png', 18 | ]; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/Http/Requests/Adverts/RejectRequest.php: -------------------------------------------------------------------------------- 1 | 'required|string', 18 | ]; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/Http/Requests/Adverts/SearchRequest.php: -------------------------------------------------------------------------------- 1 | 'nullable|string', 18 | 'attrs.*.equals' => 'nullable|string', 19 | 'attrs.*.from' => 'nullable|numeric', 20 | 'attrs.*.to' => 'nullable|numeric', 21 | ]; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/Http/Requests/Auth/LoginRequest.php: -------------------------------------------------------------------------------- 1 | 'required|string', 18 | 'password' => 'required|string', 19 | ]; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/Http/Requests/Auth/RegisterRequest.php: -------------------------------------------------------------------------------- 1 | 'required|string|max:255', 18 | 'email' => 'required|string|email|max:255|unique:users', 19 | 'password' => 'required|string|min:6|confirmed', 20 | ]; 21 | } 22 | } 23 | 24 | /** 25 | * @SWG\Definition( 26 | * definition="RegisterRequest", 27 | * type="object", 28 | * @SWG\Property(property="name", type="string"), 29 | * @SWG\Property(property="email", type="string"), 30 | * @SWG\Property(property="password", type="string"), 31 | * @SWG\Property(property="password_confirmation", type="string"), 32 | * ) 33 | */ -------------------------------------------------------------------------------- /app/Http/Requests/Banner/CreateRequest.php: -------------------------------------------------------------------------------- 1 | input('format')) { 20 | [$width, $height] = explode('x', $format); 21 | } 22 | 23 | return [ 24 | 'name' => 'required|string', 25 | 'limit' => 'required|integer', 26 | 'url' => 'required|url', 27 | 'format' => ['required', 'string', Rule::in(Banner::formatsList())], 28 | 'file' => 'required|image|mimes:jpg,jpeg,png|dimensions:width=' . $width . ',height=' . $height, 29 | ]; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/Http/Requests/Banner/EditRequest.php: -------------------------------------------------------------------------------- 1 | 'required|string', 18 | 'limit' => 'required|integer', 19 | 'url' => 'required|url', 20 | ]; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/Http/Requests/Banner/FileRequest.php: -------------------------------------------------------------------------------- 1 | input('format')) { 20 | [$width, $height] = explode('x', $format); 21 | } 22 | 23 | return [ 24 | 'format' => ['required', 'string', Rule::in(Banner::formatsList())], 25 | 'file' => 'required|image|mimes:jpg,jpeg,png|dimensions:width=' . $width . ',height=' . $height, 26 | ]; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/Http/Requests/Banner/RejectRequest.php: -------------------------------------------------------------------------------- 1 | 'required|string', 18 | ]; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/Http/Requests/Cabinet/PhoneVerifyRequest.php: -------------------------------------------------------------------------------- 1 | 'required|string|max:255', 18 | ]; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/Http/Requests/Cabinet/ProfileEditRequest.php: -------------------------------------------------------------------------------- 1 | 'required|string|max:255', 18 | 'last_name' => 'required|string|max:255', 19 | 'phone' => 'required|string|max:255|regex:/^\d+$/s', 20 | ]; 21 | } 22 | } 23 | 24 | /** 25 | * @SWG\Definition( 26 | * definition="ProfileEditRequest", 27 | * type="object", 28 | * @SWG\Property(property="name", type="string"), 29 | * @SWG\Property(property="last_name", type="string"), 30 | * @SWG\Property(property="phone", type="string"), 31 | * ) 32 | */ -------------------------------------------------------------------------------- /app/Http/Requests/Ticket/CreateRequest.php: -------------------------------------------------------------------------------- 1 | 'required|string|max:255', 18 | 'content' => 'required|string', 19 | ]; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/Http/Requests/Ticket/EditRequest.php: -------------------------------------------------------------------------------- 1 | 'required|string|max:255', 18 | 'content' => 'required|string', 19 | ]; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/Http/Requests/Ticket/MessageRequest.php: -------------------------------------------------------------------------------- 1 | 'required|string', 18 | ]; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/Http/Resources/User/ProfileResource.php: -------------------------------------------------------------------------------- 1 | $this->id, 21 | 'email' => $this->email, 22 | 'phone' => [ 23 | 'number' => $this->phone, 24 | 'verified' => $this->phone_verified, 25 | ], 26 | 'name' => [ 27 | 'first' => $this->name, 28 | 'last' => $this->last_name, 29 | ], 30 | ]; 31 | } 32 | } 33 | 34 | /** 35 | * @SWG\Definition( 36 | * definition="Profile", 37 | * type="object", 38 | * @SWG\Property(property="id", type="integer"), 39 | * @SWG\Property(property="email", type="string"), 40 | * @SWG\Property(property="phone", type="object", 41 | * @SWG\Property(property="number", type="string"), 42 | * @SWG\Property(property="verified", type="boolean"), 43 | * ), 44 | * @SWG\Property(property="name", type="object", 45 | * @SWG\Property(property="first", type="string"), 46 | * @SWG\Property(property="last", type="string"), 47 | * ), 48 | * ) 49 | */ -------------------------------------------------------------------------------- /app/Http/Router/PagePath.php: -------------------------------------------------------------------------------- 1 | page = $page; 20 | return $clone; 21 | } 22 | 23 | public function getRouteKey() 24 | { 25 | if (!$this->page) { 26 | throw new \BadMethodCallException('Empty page.'); 27 | } 28 | 29 | return Cache::tags(Page::class)->rememberForever('page_path_' . $this->page->id, function () { 30 | return $this->page->getPath(); 31 | }); 32 | } 33 | 34 | public function getRouteKeyName(): string 35 | { 36 | return 'page_path'; 37 | } 38 | 39 | public function resolveRouteBinding($value) 40 | { 41 | $chunks = explode('/', $value); 42 | 43 | /** @var Page|null $page */ 44 | $page = null; 45 | do { 46 | $slug = reset($chunks); 47 | if ($slug && $next = Page::where('slug', $slug)->where('parent_id', $page ? $page->id : null)->first()) { 48 | $page = $next; 49 | array_shift($chunks); 50 | } 51 | } while (!empty($slug) && !empty($next)); 52 | 53 | if (!empty($chunks)) { 54 | abort(404); 55 | } 56 | 57 | return $this 58 | ->withPage($page); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /app/Http/ViewComposers/MenuPagesComposer.php: -------------------------------------------------------------------------------- 1 | with('menuPages', Page::whereIsRoot()->defaultOrder()->getModels()); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/Jobs/Advert/ReindexAdvert.php: -------------------------------------------------------------------------------- 1 | advert = $advert; 22 | } 23 | 24 | public function handle(AdvertIndexer $indexer): void 25 | { 26 | $indexer->index($this->advert); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/Listeners/Advert/AdvertChangedListener.php: -------------------------------------------------------------------------------- 1 | advert); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/Listeners/Advert/ModerationPassedListener.php: -------------------------------------------------------------------------------- 1 | advert; 13 | $advert->user->notify(new ModerationPassedNotification($advert)); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/Mail/Auth/VerifyMail.php: -------------------------------------------------------------------------------- 1 | user = $user; 18 | } 19 | 20 | public function build() 21 | { 22 | return $this 23 | ->subject('Signup Confirmation') 24 | ->markdown('emails.auth.register.verify'); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/Notifications/Advert/ModerationPassedNotification.php: -------------------------------------------------------------------------------- 1 | advert = $advert; 22 | } 23 | 24 | public function via($notifiable) 25 | { 26 | return ['mail', SmsChannel::class]; 27 | } 28 | 29 | public function toMail($notifiable): MailMessage 30 | { 31 | return (new MailMessage) 32 | ->subject('Moderation is passed') 33 | ->greeting('Hello!') 34 | ->line('Your advert successfully passed a moderation.') 35 | ->action('View Advert', route('adverts.show', $this->advert)) 36 | ->line('Thank you for using our application!'); 37 | } 38 | 39 | public function toSms(): string 40 | { 41 | return 'Your advert successfully passed a moderation.'; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/Notifications/SmsChannel.php: -------------------------------------------------------------------------------- 1 | sender = $sender; 16 | } 17 | 18 | public function send(User $notifiable, Notification $notification): void 19 | { 20 | if (!$notifiable->isPhoneVerified()) { 21 | return; 22 | } 23 | $message = $notification->toSms($notifiable); 24 | $this->sender->send($notifiable->phone, $message); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/Providers/AppServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->singleton(CostCalculator::class, function (Application $app) { 20 | $config = $app->make('config')->get('banner'); 21 | return new CostCalculator($config['price']); 22 | }); 23 | 24 | Passport::ignoreMigrations(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/Providers/BroadcastServiceProvider.php: -------------------------------------------------------------------------------- 1 | classes as $class) { 22 | $this->registerFlusher($class); 23 | } 24 | } 25 | 26 | private function registerFlusher($class): void 27 | { 28 | $flush = function() use ($class) { 29 | Cache::tags($class)->flush(); 30 | }; 31 | 32 | /** @var Model $class */ 33 | $class::created($flush); 34 | $class::saved($flush); 35 | $class::updated($flush); 36 | $class::deleted($flush); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/Providers/ComposerServiceProvider.php: -------------------------------------------------------------------------------- 1 | [ 14 | AdvertChangedListener::class, 15 | ModerationPassedListener::class, 16 | ], 17 | ]; 18 | } 19 | -------------------------------------------------------------------------------- /app/Providers/RouteServiceProvider.php: -------------------------------------------------------------------------------- 1 | mapApiRoutes(); 25 | 26 | $this->mapWebRoutes(); 27 | 28 | // 29 | } 30 | 31 | protected function mapWebRoutes(): void 32 | { 33 | Route::middleware('web') 34 | ->namespace($this->namespace) 35 | ->group(base_path('routes/web.php')); 36 | } 37 | 38 | protected function mapApiRoutes() 39 | { 40 | Route::prefix('api') 41 | ->middleware('api') 42 | ->namespace($this->namespace) 43 | ->group(base_path('routes/api.php')); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/Providers/SearchServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->singleton(Client::class, function (Application $app) { 15 | $config = $app->make('config')->get('elasticsearch'); 16 | return ClientBuilder::create() 17 | ->setHosts($config['hosts']) 18 | ->setRetries($config['retries']) 19 | ->build(); 20 | }); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/Providers/SmsServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->singleton(SmsSender::class, function (Application $app) { 16 | $config = $app->make('config')->get('sms'); 17 | 18 | switch ($config['driver']) { 19 | case 'sms.ru': 20 | $params = $config['drivers']['sms.ru']; 21 | if (!empty($params['url'])) { 22 | return new SmsRu($params['app_id'], $params['url']); 23 | } 24 | return new SmsRu($params['app_id']); 25 | case 'array': 26 | return new ArraySender(); 27 | default: 28 | throw new \InvalidArgumentException('Undefined SMS driver ' . $config['driver']); 29 | } 30 | }); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/Services/Banner/CostCalculator.php: -------------------------------------------------------------------------------- 1 | price = $price; 12 | } 13 | 14 | public function calc(int $views): int 15 | { 16 | return floor($this->price * ($views / 1000)); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/Services/Search/BannerIndexer.php: -------------------------------------------------------------------------------- 1 | client = $client; 16 | } 17 | 18 | public function clear(): void 19 | { 20 | $this->client->deleteByQuery([ 21 | 'index' => 'banners', 22 | 'type' => 'banner', 23 | 'body' => [ 24 | 'query' => [ 25 | 'match_all' => new \stdClass(), 26 | ], 27 | ], 28 | ]); 29 | } 30 | 31 | public function index(Banner $banner): void 32 | { 33 | $regionIds = [0]; 34 | if ($banner->region) { 35 | $regionIds = [$banner->region->id]; 36 | $childrenIds = $regionIds; 37 | while ($childrenIds = Region::whereIn('parent_id', $childrenIds)->pluck('id')->toArray()) { 38 | $regionIds = array_merge($regionIds, $childrenIds); 39 | } 40 | } 41 | 42 | $this->client->index([ 43 | 'index' => 'banners', 44 | 'type' => 'banner', 45 | 'id' => $banner->id, 46 | 'body' => [ 47 | 'id' => $banner->id, 48 | 'status' => $banner->status, 49 | 'format' => $banner->format, 50 | 'categories' => array_merge( 51 | [$banner->category->id], 52 | $banner->category->descendants()->pluck('id')->toArray() 53 | ), 54 | 'regions' => $regionIds, 55 | ], 56 | ]); 57 | } 58 | 59 | public function remove(Banner $banner): void 60 | { 61 | $this->client->delete([ 62 | 'index' => 'banners', 63 | 'type' => 'banner', 64 | 'id' => $banner->id, 65 | ]); 66 | } 67 | } -------------------------------------------------------------------------------- /app/Services/Sms/ArraySender.php: -------------------------------------------------------------------------------- 1 | messages[] = [ 12 | 'to' => '+' . trim($number, '+'), 13 | 'text' => $text 14 | ]; 15 | } 16 | 17 | public function getMessages(): array 18 | { 19 | return $this->messages; 20 | } 21 | } -------------------------------------------------------------------------------- /app/Services/Sms/SmsRu.php: -------------------------------------------------------------------------------- 1 | appId = $appId; 20 | $this->url = $url; 21 | $this->client = new Client(); 22 | } 23 | 24 | public function send($number, $text): void 25 | { 26 | $this->client->post($this->url, [ 27 | 'form_params' => [ 28 | 'api_id' => $this->appId, 29 | 'to' => '+' . trim($number, '+'), 30 | 'text' => $text 31 | ], 32 | ]); 33 | } 34 | } -------------------------------------------------------------------------------- /app/Services/Sms/SmsSender.php: -------------------------------------------------------------------------------- 1 | getUser($userId); 13 | $advert = $this->getAdvert($advertId); 14 | 15 | $user->addToFavorites($advert->id); 16 | } 17 | 18 | public function remove($userId, $advertId): void 19 | { 20 | $user = $this->getUser($userId); 21 | $advert = $this->getAdvert($advertId); 22 | 23 | $user->removeFromFavorites($advert->id); 24 | } 25 | 26 | private function getUser($userId): User 27 | { 28 | return User::findOrFail($userId); 29 | } 30 | 31 | private function getAdvert($advertId): Advert 32 | { 33 | return Advert::findOrFail($advertId); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/UseCases/Adverts/SearchResult.php: -------------------------------------------------------------------------------- 1 | adverts = $adverts; 16 | $this->regionsCounts = $regionsCounts; 17 | $this->categoriesCounts = $categoriesCounts; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/UseCases/Auth/NetworkService.php: -------------------------------------------------------------------------------- 1 | getId())->first()) { 15 | return $user; 16 | } 17 | 18 | if ($data->getEmail() && $user = User::where('email', $data->getEmail())->exists()) { 19 | throw new \DomainException('User with this email is already registered.'); 20 | } 21 | 22 | $user = DB::transaction(function () use ($network, $data) { 23 | return User::registerByNetwork($network, $data->getId()); 24 | }); 25 | 26 | event(new Registered($user)); 27 | 28 | return $user; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/UseCases/Auth/RegisterService.php: -------------------------------------------------------------------------------- 1 | mailer = $mailer; 20 | $this->dispatcher = $dispatcher; 21 | } 22 | 23 | public function register(RegisterRequest $request): void 24 | { 25 | $user = User::register( 26 | $request['name'], 27 | $request['email'], 28 | $request['password'] 29 | ); 30 | 31 | $this->mailer->to($user->email)->send(new VerifyMail($user)); 32 | $this->dispatcher->dispatch(new Registered($user)); 33 | } 34 | 35 | public function verify($id): void 36 | { 37 | /** @var User $user */ 38 | $user = User::findOrFail($id); 39 | $user->verify(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/UseCases/Profile/PhoneService.php: -------------------------------------------------------------------------------- 1 | sms = $sms; 17 | } 18 | 19 | public function request($id) 20 | { 21 | $user = $this->getUser($id); 22 | 23 | $token = $user->requestPhoneVerification(Carbon::now()); 24 | $this->sms->send($user->phone, 'Phone verification token: ' . $token); 25 | } 26 | 27 | public function verify($id, PhoneVerifyRequest $request) 28 | { 29 | $user = $this->getUser($id); 30 | $user->verifyPhone($request['token'], Carbon::now()); 31 | } 32 | 33 | public function toggleAuth($id): bool 34 | { 35 | $user = $this->getUser($id); 36 | if ($user->isPhoneAuthEnabled()) { 37 | $user->disablePhoneAuth(); 38 | } else { 39 | $user->enablePhoneAuth(); 40 | } 41 | return $user->isPhoneAuthEnabled(); 42 | } 43 | 44 | private function getUser($id): User 45 | { 46 | return User::findOrFail($id); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /app/UseCases/Profile/ProfileService.php: -------------------------------------------------------------------------------- 1 | phone; 15 | $user->update($request->only('name', 'last_name', 'phone')); 16 | if ($user->phone !== $oldPhone) { 17 | $user->unverifyPhone(); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/UseCases/Tickets/TicketService.php: -------------------------------------------------------------------------------- 1 | getTicket($id); 20 | $ticket->edit( 21 | $request['subject'], 22 | $request['content'] 23 | ); 24 | } 25 | 26 | public function message(int $userId, int $id, MessageRequest $request): void 27 | { 28 | $ticket = $this->getTicket($id); 29 | $ticket->addMessage($userId, $request['message']); 30 | } 31 | 32 | public function approve(int $userId, int $id): void 33 | { 34 | $ticket = $this->getTicket($id); 35 | $ticket->approve($userId); 36 | } 37 | 38 | public function close(int $userId, int $id): void 39 | { 40 | $ticket = $this->getTicket($id); 41 | $ticket->close($userId); 42 | } 43 | 44 | public function reopen(int $userId, int $id): void 45 | { 46 | $ticket = $this->getTicket($id); 47 | $ticket->reopen($userId); 48 | } 49 | 50 | public function removeByOwner(int $id): void 51 | { 52 | $ticket = $this->getTicket($id); 53 | if (!$ticket->canBeRemoved()) { 54 | throw new \DomainException('Unable to remove active ticket'); 55 | } 56 | $ticket->delete(); 57 | } 58 | 59 | public function removeByAdmin(int $id): void 60 | { 61 | $ticket = $this->getTicket($id); 62 | $ticket->delete(); 63 | } 64 | 65 | private function getTicket($id): Ticket 66 | { 67 | return Ticket::findOrFail($id); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /app/helpers.php: -------------------------------------------------------------------------------- 1 | make(AdvertsPath::class) 14 | ->withRegion($region) 15 | ->withCategory($category); 16 | } 17 | } 18 | 19 | if (! function_exists('page_path')) { 20 | 21 | function page_path(Page $page) 22 | { 23 | return app()->make(PagePath::class) 24 | ->withPage($page); 25 | } 26 | } -------------------------------------------------------------------------------- /artisan: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | make(Illuminate\Contracts\Console\Kernel::class); 34 | 35 | $status = $kernel->handle( 36 | $input = new Symfony\Component\Console\Input\ArgvInput, 37 | new Symfony\Component\Console\Output\ConsoleOutput 38 | ); 39 | 40 | /* 41 | |-------------------------------------------------------------------------- 42 | | Shutdown The Application 43 | |-------------------------------------------------------------------------- 44 | | 45 | | Once Artisan has finished running, we will fire off the shutdown events 46 | | so that any final work may be done by the application before we shut 47 | | down the process. This is the last thing to happen to the request. 48 | | 49 | */ 50 | 51 | $kernel->terminate($input, $status); 52 | 53 | exit($status); 54 | -------------------------------------------------------------------------------- /bootstrap/app.php: -------------------------------------------------------------------------------- 1 | singleton( 30 | Illuminate\Contracts\Http\Kernel::class, 31 | App\Http\Kernel::class 32 | ); 33 | 34 | $app->singleton( 35 | Illuminate\Contracts\Console\Kernel::class, 36 | App\Console\Kernel::class 37 | ); 38 | 39 | $app->singleton( 40 | Illuminate\Contracts\Debug\ExceptionHandler::class, 41 | App\Exceptions\Handler::class 42 | ); 43 | 44 | /* 45 | |-------------------------------------------------------------------------- 46 | | Return The Application 47 | |-------------------------------------------------------------------------- 48 | | 49 | | This script returns the application instance. The instance is given to 50 | | the calling script so we can separate the building of the instances 51 | | from the actual running of the application and sending responses. 52 | | 53 | */ 54 | 55 | return $app; 56 | -------------------------------------------------------------------------------- /bootstrap/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /config/banner.php: -------------------------------------------------------------------------------- 1 | env('BANNER_COST_PER_MILE') 5 | ]; -------------------------------------------------------------------------------- /config/broadcasting.php: -------------------------------------------------------------------------------- 1 | env('BROADCAST_DRIVER', 'null'), 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Broadcast Connections 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Here you may define all of the broadcast connections that will be used 26 | | to broadcast events to other systems or over websockets. Samples of 27 | | each available type of connection are provided inside this array. 28 | | 29 | */ 30 | 31 | 'connections' => [ 32 | 33 | 'pusher' => [ 34 | 'driver' => 'pusher', 35 | 'key' => env('PUSHER_APP_KEY'), 36 | 'secret' => env('PUSHER_APP_SECRET'), 37 | 'app_id' => env('PUSHER_APP_ID'), 38 | 'options' => [ 39 | 'cluster' => env('PUSHER_APP_CLUSTER'), 40 | 'encrypted' => true, 41 | ], 42 | ], 43 | 44 | 'redis' => [ 45 | 'driver' => 'redis', 46 | 'connection' => 'default', 47 | ], 48 | 49 | 'log' => [ 50 | 'driver' => 'log', 51 | ], 52 | 53 | 'null' => [ 54 | 'driver' => 'null', 55 | ], 56 | 57 | ], 58 | 59 | ]; 60 | -------------------------------------------------------------------------------- /config/elasticsearch.php: -------------------------------------------------------------------------------- 1 | explode(',', env('ELASTICSEARCH_HOSTS')), 5 | 'retries' => 1, 6 | ]; 7 | -------------------------------------------------------------------------------- /config/hashing.php: -------------------------------------------------------------------------------- 1 | 'bcrypt', 19 | 20 | ]; 21 | -------------------------------------------------------------------------------- /config/services.php: -------------------------------------------------------------------------------- 1 | [ 18 | 'domain' => env('MAILGUN_DOMAIN'), 19 | 'secret' => env('MAILGUN_SECRET'), 20 | ], 21 | 22 | 'ses' => [ 23 | 'key' => env('SES_KEY'), 24 | 'secret' => env('SES_SECRET'), 25 | 'region' => 'us-east-1', 26 | ], 27 | 28 | 'sparkpost' => [ 29 | 'secret' => env('SPARKPOST_SECRET'), 30 | ], 31 | 32 | 'stripe' => [ 33 | 'model' => \App\Entity\User\User::class, 34 | 'key' => env('STRIPE_KEY'), 35 | 'secret' => env('STRIPE_SECRET'), 36 | ], 37 | 38 | 'facebook' => [ 39 | 'client_id' => env('FACEBOOK_CLIENT_ID'), 40 | 'client_secret' => env('FACEBOOK_CLIENT_SECRET'), 41 | 'redirect' => env('APP_URL') . '/login/facebook/callback', 42 | ], 43 | 44 | 'twitter' => [ 45 | 'client_id' => env('TWITTER_CLIENT_ID'), 46 | 'client_secret' => env('TWITTER_CLIENT_SECRET'), 47 | 'redirect' => env('APP_URL') . '/login/twitter/callback', 48 | ], 49 | ]; 50 | -------------------------------------------------------------------------------- /config/sms.php: -------------------------------------------------------------------------------- 1 | env('SMS_DRIVER', 'sms.ru'), 6 | 7 | 'drivers' => [ 8 | 'sms.ru' => [ 9 | 'app_id' => env('SMS_SMS_RU_APP_ID'), 10 | 'url' => env('SMS_SMS_RU_URL'), 11 | ], 12 | ], 13 | ]; -------------------------------------------------------------------------------- /config/view.php: -------------------------------------------------------------------------------- 1 | [ 17 | resource_path('views'), 18 | ], 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Compiled View Path 23 | |-------------------------------------------------------------------------- 24 | | 25 | | This option determines where all the compiled Blade templates will be 26 | | stored for your application. Typically, this is within the storage 27 | | directory. However, as usual, you are free to change this value. 28 | | 29 | */ 30 | 31 | 'compiled' => realpath(storage_path('framework/views')), 32 | 33 | ]; 34 | -------------------------------------------------------------------------------- /database/.gitignore: -------------------------------------------------------------------------------- 1 | *.sqlite 2 | -------------------------------------------------------------------------------- /database/factories/AdvertsCategoryFactory.php: -------------------------------------------------------------------------------- 1 | define(App\Entity\Adverts\Category::class, function (Faker $faker) { 8 | return [ 9 | 'name' => $faker->unique()->name, 10 | 'slug' => $faker->unique()->slug(2), 11 | 'parent_id' => null, 12 | ]; 13 | }); 14 | -------------------------------------------------------------------------------- /database/factories/RegionFactory.php: -------------------------------------------------------------------------------- 1 | define(\App\Entity\Region::class, function (Faker $faker) { 8 | return [ 9 | 'name' => $faker->unique()->city, 10 | 'slug' => $faker->unique()->slug(2), 11 | 'parent_id' => null, 12 | ]; 13 | }); 14 | -------------------------------------------------------------------------------- /database/factories/UserFactory.php: -------------------------------------------------------------------------------- 1 | define(User::class, function (Faker $faker) { 22 | $active = $faker->boolean; 23 | $phoneActive = $faker->boolean; 24 | return [ 25 | 'name' => $faker->name, 26 | 'last_name' => $faker->lastName, 27 | 'email' => $faker->unique()->safeEmail, 28 | 'phone' => $faker->unique()->phoneNumber, 29 | 'phone_verified' => $phoneActive, 30 | 'password' => '$2y$10$TKh8H1.PfQx37YgCzwiKb.KjNyWgaHb9cbcoQgdIVFlYg7B77UdFm', // secret 31 | 'remember_token' => str_random(10), 32 | 'verify_token' => $active ? null : Str::uuid(), 33 | 'phone_verify_token' => $phoneActive ? null : Str::uuid(), 34 | 'phone_verify_token_expire' => $phoneActive ? null : Carbon::now()->addSeconds(300), 35 | 'role' => $active ? $faker->randomElement([User::ROLE_USER, User::ROLE_ADMIN]) : User::ROLE_USER, 36 | 'status' => $active ? User::STATUS_ACTIVE : User::STATUS_WAIT, 37 | ]; 38 | }); 39 | -------------------------------------------------------------------------------- /database/migrations/2014_10_12_000000_create_users_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->string('name'); 19 | $table->string('email')->unique(); 20 | $table->string('password'); 21 | $table->rememberToken(); 22 | $table->timestamps(); 23 | }); 24 | } 25 | 26 | /** 27 | * Reverse the migrations. 28 | * 29 | * @return void 30 | */ 31 | public function down() 32 | { 33 | Schema::dropIfExists('users'); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /database/migrations/2014_10_12_100000_create_password_resets_table.php: -------------------------------------------------------------------------------- 1 | string('email')->index(); 18 | $table->string('token'); 19 | $table->timestamp('created_at')->nullable(); 20 | }); 21 | } 22 | 23 | /** 24 | * Reverse the migrations. 25 | * 26 | * @return void 27 | */ 28 | public function down() 29 | { 30 | Schema::dropIfExists('password_resets'); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /database/migrations/2016_06_01_000001_create_oauth_auth_codes_table.php: -------------------------------------------------------------------------------- 1 | string('id', 100)->primary(); 18 | $table->integer('user_id'); 19 | $table->integer('client_id'); 20 | $table->text('scopes')->nullable(); 21 | $table->boolean('revoked'); 22 | $table->dateTime('expires_at')->nullable(); 23 | }); 24 | } 25 | 26 | /** 27 | * Reverse the migrations. 28 | * 29 | * @return void 30 | */ 31 | public function down() 32 | { 33 | Schema::drop('oauth_auth_codes'); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /database/migrations/2016_06_01_000002_create_oauth_access_tokens_table.php: -------------------------------------------------------------------------------- 1 | string('id', 100)->primary(); 18 | $table->integer('user_id')->index()->nullable(); 19 | $table->integer('client_id'); 20 | $table->string('name')->nullable(); 21 | $table->text('scopes')->nullable(); 22 | $table->boolean('revoked'); 23 | $table->timestamps(); 24 | $table->dateTime('expires_at')->nullable(); 25 | }); 26 | } 27 | 28 | /** 29 | * Reverse the migrations. 30 | * 31 | * @return void 32 | */ 33 | public function down() 34 | { 35 | Schema::drop('oauth_access_tokens'); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /database/migrations/2016_06_01_000003_create_oauth_refresh_tokens_table.php: -------------------------------------------------------------------------------- 1 | string('id', 100)->primary(); 18 | $table->string('access_token_id', 100)->index(); 19 | $table->boolean('revoked'); 20 | $table->dateTime('expires_at')->nullable(); 21 | }); 22 | } 23 | 24 | /** 25 | * Reverse the migrations. 26 | * 27 | * @return void 28 | */ 29 | public function down() 30 | { 31 | Schema::drop('oauth_refresh_tokens'); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /database/migrations/2016_06_01_000004_create_oauth_clients_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->integer('user_id')->index()->nullable(); 19 | $table->string('name'); 20 | $table->string('secret', 100); 21 | $table->text('redirect'); 22 | $table->boolean('personal_access_client'); 23 | $table->boolean('password_client'); 24 | $table->boolean('revoked'); 25 | $table->timestamps(); 26 | }); 27 | } 28 | 29 | /** 30 | * Reverse the migrations. 31 | * 32 | * @return void 33 | */ 34 | public function down() 35 | { 36 | Schema::drop('oauth_clients'); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /database/migrations/2016_06_01_000005_create_oauth_personal_access_clients_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->integer('client_id')->index(); 19 | $table->timestamps(); 20 | }); 21 | } 22 | 23 | /** 24 | * Reverse the migrations. 25 | * 26 | * @return void 27 | */ 28 | public function down() 29 | { 30 | Schema::drop('oauth_personal_access_clients'); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /database/migrations/2018_03_03_193348_add_user_verification.php: -------------------------------------------------------------------------------- 1 | string('status', 16); 18 | $table->string('verify_token')->nullable()->unique(); 19 | }); 20 | 21 | DB::table('users')->update([ 22 | 'status' => 'active', 23 | ]); 24 | } 25 | 26 | /** 27 | * Reverse the migrations. 28 | * 29 | * @return void 30 | */ 31 | public function down() 32 | { 33 | Schema::table('users', function (Blueprint $table) { 34 | $table->dropColumn('status'); 35 | $table->dropColumn('verify_token'); 36 | }); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /database/migrations/2018_03_08_100246_add_user_role.php: -------------------------------------------------------------------------------- 1 | string('role', 16); 18 | }); 19 | 20 | DB::table('users')->update([ 21 | 'role' => 'user', 22 | ]); 23 | } 24 | 25 | /** 26 | * Reverse the migrations. 27 | * 28 | * @return void 29 | */ 30 | public function down() 31 | { 32 | Schema::table('users', function (Blueprint $table) { 33 | $table->dropColumn('role'); 34 | }); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /database/migrations/2018_03_08_161503_create_regions_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->string('name')->index(); 19 | $table->string('slug'); 20 | $table->unsignedInteger('parent_id')->nullable(); 21 | $table->timestamps(); 22 | $table->unique(['parent_id', 'slug']); 23 | $table->unique(['parent_id', 'name']); 24 | }); 25 | 26 | Schema::table('regions', function (Blueprint $table) { 27 | $table->foreign('parent_id')->references('id')->on('regions')->onDelete('CASCADE'); 28 | }); 29 | } 30 | 31 | /** 32 | * Reverse the migrations. 33 | * 34 | * @return void 35 | */ 36 | public function down() 37 | { 38 | Schema::dropIfExists('regions'); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /database/migrations/2018_03_08_171503_create_advert_categories_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 19 | $table->string('name')->index(); 20 | $table->string('slug'); 21 | NestedSet::columns($table); 22 | }); 23 | } 24 | 25 | /** 26 | * Reverse the migrations. 27 | * 28 | * @return void 29 | */ 30 | public function down() 31 | { 32 | Schema::dropIfExists('advert_categories'); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /database/migrations/2018_03_10_132930_create_advert_attributes_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 13 | $table->unsignedInteger('category_id'); 14 | $table->foreign('category_id')->references('id')->on('advert_categories')->onDelete('CASCADE'); 15 | $table->string('name'); 16 | $table->string('type'); 17 | $table->boolean('required'); 18 | $table->json('variants'); 19 | $table->integer('sort'); 20 | }); 21 | } 22 | 23 | public function down() 24 | { 25 | Schema::dropIfExists('advert_attributes'); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /database/migrations/2018_03_12_083000_add_user_last_name.php: -------------------------------------------------------------------------------- 1 | string('last_name')->nullable()->after('name'); 13 | }); 14 | } 15 | 16 | public function down() 17 | { 18 | Schema::table('users', function (Blueprint $table) { 19 | $table->dropColumn('last_name'); 20 | }); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /database/migrations/2018_03_12_090001_add_user_phone.php: -------------------------------------------------------------------------------- 1 | string('phone')->nullable()->after('email'); 13 | $table->boolean('phone_verified')->default(false)->after('phone'); 14 | $table->string('phone_verify_token')->nullable()->after('verify_token'); 15 | $table->timestamp('phone_verify_token_expire')->nullable()->after('phone_verify_token'); 16 | }); 17 | } 18 | 19 | public function down() 20 | { 21 | Schema::table('users', function (Blueprint $table) { 22 | $table->dropColumn('phone_verify_token_expire'); 23 | $table->dropColumn('phone_verify_token'); 24 | $table->dropColumn('phone_verified'); 25 | $table->dropColumn('phone'); 26 | }); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /database/migrations/2018_03_12_124830_add_user_phone_auth.php: -------------------------------------------------------------------------------- 1 | boolean('phone_auth')->default(false)->after('phone'); 13 | }); 14 | } 15 | 16 | public function down() 17 | { 18 | Schema::table('users', function (Blueprint $table) { 19 | $table->dropColumn('phone_auth'); 20 | }); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /database/migrations/2018_03_19_123423_create_advert_favorites_table.php: -------------------------------------------------------------------------------- 1 | unsignedInteger('user_id'); 13 | $table->foreign('user_id')->references('id')->on('users')->onDelete('CASCADE'); 14 | $table->unsignedInteger('advert_id'); 15 | $table->foreign('advert_id')->references('id')->on('advert_adverts')->onDelete('CASCADE'); 16 | $table->primary(['user_id', 'advert_id']); 17 | }); 18 | } 19 | 20 | public function down() 21 | { 22 | Schema::dropIfExists('advert_favorites'); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /database/migrations/2018_03_22_090624_create_banners_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 13 | 14 | $table->unsignedInteger('user_id'); 15 | $table->foreign('user_id')->references('id')->on('users')->onDelete('CASCADE'); 16 | $table->unsignedInteger('category_id'); 17 | $table->foreign('category_id')->references('id')->on('advert_categories'); 18 | $table->unsignedInteger('region_id')->nullable(); 19 | $table->foreign('region_id')->references('id')->on('regions'); 20 | $table->string('name'); 21 | $table->integer('views')->nullable(); 22 | $table->integer('limit'); 23 | $table->integer('clicks')->nullable(); 24 | $table->integer('cost')->nullable(); 25 | $table->string('url'); 26 | $table->string('format'); 27 | $table->string('file'); 28 | $table->string('status', 16); 29 | 30 | $table->timestamps(); 31 | $table->timestamp('published_at')->nullable(); 32 | }); 33 | } 34 | 35 | public function down() 36 | { 37 | Schema::dropIfExists('banner_banners'); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /database/migrations/2018_03_25_183121_add_networks_auth.php: -------------------------------------------------------------------------------- 1 | string('email')->nullable()->change(); 13 | $table->string('password')->nullable()->change(); 14 | }); 15 | 16 | Schema::create('user_networks', function (Blueprint $table) { 17 | $table->unsignedInteger('user_id'); 18 | $table->foreign('user_id')->references('id')->on('users')->onDelete('CASCADE'); 19 | $table->string('network'); 20 | $table->string('identity'); 21 | $table->primary(['user_id', 'identity']); 22 | }); 23 | } 24 | 25 | public function down() 26 | { 27 | Schema::dropIfExists('user_networks'); 28 | 29 | Schema::table('users', function (Blueprint $table) { 30 | $table->string('email')->change(); 31 | $table->string('password')->change(); 32 | }); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /database/migrations/2018_03_29_174253_create_pages_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 19 | $table->string('title'); 20 | $table->string('menu_title')->nullable(); 21 | $table->string('slug'); 22 | $table->mediumText('content'); 23 | $table->text('description')->nullable(); 24 | $table->timestamps(); 25 | NestedSet::columns($table); 26 | }); 27 | } 28 | 29 | /** 30 | * Reverse the migrations. 31 | * 32 | * @return void 33 | */ 34 | public function down() 35 | { 36 | Schema::dropIfExists('pages'); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /database/migrations/2018_03_30_132456_create_tickets_tables.php: -------------------------------------------------------------------------------- 1 | increments('id'); 13 | $table->unsignedInteger('user_id'); 14 | $table->foreign('user_id')->references('id')->on('users')->onDelete('CASCADE'); 15 | $table->string('subject'); 16 | $table->text('content'); 17 | $table->string('status', 16); 18 | $table->timestamps(); 19 | }); 20 | 21 | Schema::create('ticket_statuses', function (Blueprint $table) { 22 | $table->increments('id'); 23 | $table->unsignedInteger('ticket_id'); 24 | $table->foreign('ticket_id')->references('id')->on('ticket_tickets')->onDelete('CASCADE'); 25 | $table->unsignedInteger('user_id'); 26 | $table->foreign('user_id')->references('id')->on('users')->onDelete('CASCADE'); 27 | $table->string('status', 16); 28 | $table->timestamps(); 29 | }); 30 | 31 | Schema::create('ticket_messages', function (Blueprint $table) { 32 | $table->increments('id'); 33 | $table->unsignedInteger('ticket_id'); 34 | $table->foreign('ticket_id')->references('id')->on('ticket_tickets')->onDelete('CASCADE'); 35 | $table->unsignedInteger('user_id'); 36 | $table->foreign('user_id')->references('id')->on('users')->onDelete('CASCADE'); 37 | $table->text('message'); 38 | $table->timestamps(); 39 | }); 40 | } 41 | 42 | public function down() 43 | { 44 | Schema::dropIfExists('ticket_messages'); 45 | Schema::dropIfExists('ticket_statuses'); 46 | Schema::dropIfExists('ticket_tickets'); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /database/seeds/AdvertCategoriesTableSeeder.php: -------------------------------------------------------------------------------- 1 | create()->each(function(Category $category) { 11 | $counts = [0, random_int(3, 7)]; 12 | $category->children()->saveMany(factory(Category::class, $counts[array_rand($counts)])->create()->each(function(Category $category) { 13 | $counts = [0, random_int(3, 7)]; 14 | $category->children()->saveMany(factory(Category::class, $counts[array_rand($counts)])->create()); 15 | })); 16 | }); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /database/seeds/DatabaseSeeder.php: -------------------------------------------------------------------------------- 1 | call(UsersTableSeeder::class); 10 | $this->call(RegionsTableSeeder::class); 11 | $this->call(AdvertCategoriesTableSeeder::class); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /database/seeds/RegionsTableSeeder.php: -------------------------------------------------------------------------------- 1 | create()->each(function(Region $region) { 11 | $region->children()->saveMany(factory(Region::class, random_int(3, 10))->create()->each(function(Region $region) { 12 | $region->children()->saveMany(factory(Region::class, random_int(3, 10))->make()); 13 | })); 14 | }); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /database/seeds/UsersTableSeeder.php: -------------------------------------------------------------------------------- 1 | create(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /docker/nginx.docker: -------------------------------------------------------------------------------- 1 | FROM nginx:1.10 2 | 3 | ADD ./docker/nginx/default.conf /etc/nginx/conf.d/default.conf 4 | WORKDIR /var/www 5 | -------------------------------------------------------------------------------- /docker/nginx/default.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 443 ssl; 3 | index index.php index.html; 4 | root /var/www/public; 5 | 6 | ssl on; 7 | ssl_certificate /etc/nginx/ssl/ssl-cert-snakeoil.pem; 8 | ssl_certificate_key /etc/nginx/ssl/ssl-cert-snakeoil.key; 9 | 10 | index index.html; 11 | 12 | location / { 13 | try_files $uri /index.php?$args; 14 | } 15 | 16 | location /docs { 17 | try_files $uri $uri/; 18 | } 19 | 20 | location ~ \.php$ { 21 | fastcgi_split_path_info ^(.+\.php)(/.+)$; 22 | fastcgi_pass php-fpm:9000; 23 | fastcgi_index index.php; 24 | include fastcgi_params; 25 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 26 | fastcgi_param PATH_INFO $fastcgi_path_info; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /docker/nginx/ssl/ssl-cert-snakeoil.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDAvZeSu9zk2Bx3 3 | RPR6I7Bz6YG9zqpLiWJjRoWzJrLAGEcE/5vYZScC9xEwQXR6uefzJtYgDtiWFZvG 4 | UYtG9uCadiTbFyy50EOVaZAnmgBIh3C8APyARwMkUUh+UXJA+HEY5VLzSq++qxkZ 5 | dDm341VN5X1DTbhREJPwjo16vc0MQv1BTOZQifMYykP6/NM34hAUBWOkW+n/l4z5 6 | 4lE5+KAnEB/owKvPMN3fhmojWXo9DDhOOcQcuL4JrcuA1jPtEATHJEikq9ZbIn5D 7 | Q8PQ2vTQ2+crfWg1lGNydlDC0ENmt+zrwJndX/aU0a7DiT7p/2TiMHiQbdwUqSIl 8 | 0WcEYve9AgMBAAECggEBALyzIgmr2alHGB+BKCXIeUISlE5jXoDTwbrWWaG7Ongt 9 | jKKNKmRjLB2QDIkFHGfnSPraw/rg7hWKlFdGkKhqnh07m/vQJZ7KEtDeaB8NfEeG 10 | Ks413QAuBhpxZhsd3FFVq0yngF3nekafNtSf4L5zWJoKG2hgWBststlqh4Nq/ayI 11 | 2jFg9d9VanonL5RzYpEuoXb4spmGbgs8k/+dgUnYyiCPd99iM1by7mV9CUUDrQMW 12 | TVW0SZOR5CCG2ADmBgwoWlcQFuSL+ij7M53GuFHCCtCTkk7TuQS3C5TFFxGLxamn 13 | yT9YBRploZ0cKh8zD42cEqPxY0VOFHGA9iUPli0vloECgYEA5nA6hc/OG4UuQSou 14 | 9L7dp1Kao7z32O5igdYDX2pjAMmdvmj3rFzOyAB1OYQpFx+Wt6ypVQvzGdfWKKN2 15 | PguM6rALasg9xD+4EEfWrY1xM61Wcgh8+xdUbUCxmMToLm8SyzVf7Ou075/+kz2P 16 | 54BAM0TScyYi60tC1Qx6+quFgyECgYEA1h7baG/+x8ZKKRh5SdM18ce3X0fz7Q5n 17 | Yuhme9eIKzxCzODWcdfFlzNGYy1Hgx1RbsGxHxkGcASV8P6qe56b4X0ckcOqYd48 18 | 9H3cTqx65YDwFyeMyhRkBUrt/oaQK0HDMgzfTaMX37innxAX7rqgWO0xHVr/j8Ju 19 | ho9/avm8fR0CgYA4wf/IIaz0XlNMPaWfJrvVkKUCG3M4fU7KB/qAr+V+tioiUhxe 20 | 2eUcofA9oG22glNPHjn+9piEDwNyswBWu+WTkJTfUj7UbZVafTdFPAdb4R/fqnOO 21 | LOrglgSoSied+EG6x4S/CDdiphfAEHO3Y4Fsn1Duh8AmED1/2DkaMNELwQKBgHqe 22 | wzGk6XhIkxHDxTnE7eCfaeDz2LoKBKT8yCvlu0JfSTYBEG2zjqFPKOEE/i4U7RyL 23 | ab6QW0JtLd0MSl9u7oAMYP8M2ZcgaTHuneqkFeE2nMf1y0eys7DgPzkCoK9VAs63 24 | 1m2kl7h0C/IoijwZvlgOxZC1GyOdyiPfK638hLf1AoGAMw7c6o8i7r27hooMDMNZ 25 | OVOnpdPOJA/jmKYtoYPmVX9esi1f4fCQFYOPClu0byn3NvVKqZGeExaUVz8ilckr 26 | he6uNaTZI/MU0z/+ijYDuuC/oOpXdmJqDL4s+VbAWLrYrvZ1b0yUJE+14SCPbrjH 27 | GieGGl8IqMpaFSIOILfbgUw= 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /docker/nginx/ssl/ssl-cert-snakeoil.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICsjCCAZqgAwIBAgIJALb0BZ+VhG3pMA0GCSqGSIb3DQEBBQUAMBExDzANBgNV 3 | BAMTBnVidW50dTAeFw0xMzA1MzAxNjM1NTZaFw0yMzA1MjgxNjM1NTZaMBExDzAN 4 | BgNVBAMTBnVidW50dTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMC9 5 | l5K73OTYHHdE9HojsHPpgb3OqkuJYmNGhbMmssAYRwT/m9hlJwL3ETBBdHq55/Mm 6 | 1iAO2JYVm8ZRi0b24Jp2JNsXLLnQQ5VpkCeaAEiHcLwA/IBHAyRRSH5RckD4cRjl 7 | UvNKr76rGRl0ObfjVU3lfUNNuFEQk/COjXq9zQxC/UFM5lCJ8xjKQ/r80zfiEBQF 8 | Y6Rb6f+XjPniUTn4oCcQH+jAq88w3d+GaiNZej0MOE45xBy4vgmty4DWM+0QBMck 9 | SKSr1lsifkNDw9Da9NDb5yt9aDWUY3J2UMLQQ2a37OvAmd1f9pTRrsOJPun/ZOIw 10 | eJBt3BSpIiXRZwRi970CAwEAAaMNMAswCQYDVR0TBAIwADANBgkqhkiG9w0BAQUF 11 | AAOCAQEAVQoXrlGlJkYob3h9sQmuF9B086j9+ejh+GB2ILTWDP7DHOOSFh5yCLVu 12 | 2m251uXfRSy40RTM4ZfiQRZGtq6qCxIEaDieHCG/86tTCktl5XvoQjz4Loz11iDo 13 | RTLXolb4OBCeOc/bVud4O0ql14/QuLh+kIGfYoFYW4uiPEWV+N3awpodItQpYPgj 14 | zjSoPj5Ppj0sAeZ6SVmucSMQNPy8lm7JqNPBvBZSk1YXDvCiukaDXNPjFNhb1mMN 15 | kW3l/T61SlOFvQoq72dod/JKga4BGN5SFrMIaLYkXoxgK8yaFRMuyAG4+Jc806ad 16 | W7D/YjYYGZeCh6wssbb6+RFZw2g6kA== 17 | -----END CERTIFICATE----- 18 | -------------------------------------------------------------------------------- /docker/php-cli.docker: -------------------------------------------------------------------------------- 1 | FROM php:7.1-cli 2 | 3 | RUN apt-get update && apt-get install -y libmcrypt-dev mariadb-client wget unzip \ 4 | && docker-php-ext-install mcrypt pdo_mysql pcntl 5 | 6 | RUN wget https://getcomposer.org/installer -O - -q | php -- --version=1.9.1 --install-dir=/bin --filename=composer --quiet 7 | 8 | ENV COMPOSER_ALLOW_SUPERUSER 1 9 | 10 | WORKDIR /var/www 11 | -------------------------------------------------------------------------------- /docker/php-fpm.docker: -------------------------------------------------------------------------------- 1 | FROM php:7.1-fpm 2 | 3 | RUN apt-get update && apt-get install -y libmcrypt-dev mariadb-client \ 4 | && docker-php-ext-install mcrypt pdo_mysql 5 | 6 | WORKDIR /var/www 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "dev": "npm run development", 5 | "development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js", 6 | "watch": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --watch --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js", 7 | "watch-poll": "npm run watch -- --watch-poll", 8 | "hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js", 9 | "prod": "npm run production", 10 | "production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js" 11 | }, 12 | "devDependencies": { 13 | "axios": "^0.17", 14 | "bootstrap": "^4.0.0", 15 | "cross-env": "^5.1", 16 | "font-awesome": "^4.7.0", 17 | "jquery": "^3.2", 18 | "laravel-mix": "^2.0", 19 | "lodash": "^4.17.4", 20 | "popper.js": "^1.12", 21 | "summernote": "^0.8.10" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | ./tests/Feature 14 | 15 | 16 | 17 | ./tests/Unit 18 | 19 | 20 | 21 | 22 | ./app 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /public/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | 3 | Options -MultiViews -Indexes 4 | 5 | 6 | RewriteEngine On 7 | 8 | # Handle Authorization Header 9 | RewriteCond %{HTTP:Authorization} . 10 | RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] 11 | 12 | # Redirect Trailing Slashes If Not A Folder... 13 | RewriteCond %{REQUEST_FILENAME} !-d 14 | RewriteCond %{REQUEST_URI} (.+)/$ 15 | RewriteRule ^ %1 [L,R=301] 16 | 17 | # Handle Front Controller... 18 | RewriteCond %{REQUEST_FILENAME} !-d 19 | RewriteCond %{REQUEST_FILENAME} !-f 20 | RewriteRule ^ index.php [L] 21 | 22 | -------------------------------------------------------------------------------- /public/docs/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElisDN/laravel-demo-board/616969c77ae5b2cc943361495e52d242d9b4f09a/public/docs/favicon-16x16.png -------------------------------------------------------------------------------- /public/docs/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElisDN/laravel-demo-board/616969c77ae5b2cc943361495e52d242d9b4f09a/public/docs/favicon-32x32.png -------------------------------------------------------------------------------- /public/docs/swagger-ui.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":[],"names":[],"mappings":"","file":"swagger-ui.css","sourceRoot":""} -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElisDN/laravel-demo-board/616969c77ae5b2cc943361495e52d242d9b4f09a/public/favicon.ico -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | 10 | define('LARAVEL_START', microtime(true)); 11 | 12 | /* 13 | |-------------------------------------------------------------------------- 14 | | Register The Auto Loader 15 | |-------------------------------------------------------------------------- 16 | | 17 | | Composer provides a convenient, automatically generated class loader for 18 | | our application. We just need to utilize it! We'll simply require it 19 | | into the script here so that we don't have to worry about manual 20 | | loading any of our classes later on. It feels great to relax. 21 | | 22 | */ 23 | 24 | require __DIR__.'/../vendor/autoload.php'; 25 | 26 | /* 27 | |-------------------------------------------------------------------------- 28 | | Turn On The Lights 29 | |-------------------------------------------------------------------------- 30 | | 31 | | We need to illuminate PHP development, so let us turn on the lights. 32 | | This bootstraps the framework and gets it ready for use, then it 33 | | will load up this application so that we can run it and send 34 | | the responses back to the browser and delight our users. 35 | | 36 | */ 37 | 38 | $app = require_once __DIR__.'/../bootstrap/app.php'; 39 | 40 | /* 41 | |-------------------------------------------------------------------------- 42 | | Run The Application 43 | |-------------------------------------------------------------------------- 44 | | 45 | | Once we have the application, we can handle the incoming request 46 | | through the kernel, and send the associated response back to 47 | | the client's browser allowing them to enjoy the creative 48 | | and wonderful application we have prepared for them. 49 | | 50 | */ 51 | 52 | $kernel = $app->make(Illuminate\Contracts\Http\Kernel::class); 53 | 54 | $response = $kernel->handle( 55 | $request = Illuminate\Http\Request::capture() 56 | ); 57 | 58 | $response->send(); 59 | 60 | $kernel->terminate($request, $response); 61 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /public/vendor/horizon/css/app.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"/css/app.css","sources":[],"mappings":";;;;;A","sourceRoot":""} -------------------------------------------------------------------------------- /public/vendor/horizon/img/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ElisDN/laravel-demo-board/616969c77ae5b2cc943361495e52d242d9b4f09a/public/vendor/horizon/img/favicon.png -------------------------------------------------------------------------------- /public/vendor/horizon/mix-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "/js/app.js": "/js/app.js?id=954d5c3669f5448e61ac", 3 | "/css/app.css": "/css/app.css?id=5ce9973b1bc9f6a46cb2", 4 | "/js/app.js.map": "/js/app.js.map?id=e00843e66e9dfd3e036c", 5 | "/css/app.css.map": "/css/app.css.map?id=5d0439ebaab1434c7ea0" 6 | } -------------------------------------------------------------------------------- /public/web.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | Demo project for Laravel master-class https://elisdn.ru/laravel-board 2 | -------------------------------------------------------------------------------- /resources/assets/js/bootstrap.js: -------------------------------------------------------------------------------- 1 | 2 | window._ = require('lodash'); 3 | window.Popper = require('popper.js').default; 4 | 5 | /** 6 | * We'll load jQuery and the Bootstrap jQuery plugin which provides support 7 | * for JavaScript based Bootstrap features such as modals and tabs. This 8 | * code may be modified to fit the specific needs of your application. 9 | */ 10 | 11 | try { 12 | window.$ = window.jQuery = require('jquery'); 13 | 14 | require('bootstrap'); 15 | require('summernote/dist/summernote-bs4'); 16 | } catch (e) {} 17 | 18 | /** 19 | * We'll load the axios HTTP library which allows us to easily issue requests 20 | * to our Laravel back-end. This library automatically handles sending the 21 | * CSRF token as a header based on the value of the "XSRF" token cookie. 22 | */ 23 | 24 | window.axios = require('axios'); 25 | 26 | window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; 27 | 28 | /** 29 | * Next we will register the CSRF Token as a common header with Axios so that 30 | * all outgoing HTTP requests automatically have it attached. This is just 31 | * a simple convenience so we don't have to attach every token manually. 32 | */ 33 | 34 | let token = document.head.querySelector('meta[name="csrf-token"]'); 35 | 36 | if (token) { 37 | window.axios.defaults.headers.common['X-CSRF-TOKEN'] = token.content; 38 | } else { 39 | console.error('CSRF token not found: https://laravel.com/docs/csrf#csrf-x-csrf-token'); 40 | } 41 | 42 | /** 43 | * Echo exposes an expressive API for subscribing to channels and listening 44 | * for events that are broadcast by Laravel. Echo and event broadcasting 45 | * allows your team to easily build robust real-time web applications. 46 | */ 47 | 48 | // import Echo from 'laravel-echo' 49 | 50 | // window.Pusher = require('pusher-js'); 51 | 52 | // window.Echo = new Echo({ 53 | // broadcaster: 'pusher', 54 | // key: process.env.MIX_PUSHER_APP_KEY, 55 | // cluster: process.env.MIX_PUSHER_APP_CLUSTER, 56 | // encrypted: true 57 | // }); 58 | -------------------------------------------------------------------------------- /resources/assets/sass/_variables.scss: -------------------------------------------------------------------------------- 1 | 2 | // Body 3 | $body-bg: #fff; 4 | 5 | // Typography 6 | $font-family-sans-serif: "Arial", sans-serif; 7 | $font-size-base: 0.9rem; 8 | 9 | // Navbar 10 | $navbar-dark-color: rgba(#fff, .8); 11 | $navbar-dark-hover-color: rgba(#fff, .95); 12 | 13 | // Other 14 | $primary: #337ab7; 15 | $success: #4db24d; 16 | $info: #5bc0de; 17 | $warning: #f0ad4e; 18 | $danger: #d9534f; 19 | -------------------------------------------------------------------------------- /resources/assets/sass/app.scss: -------------------------------------------------------------------------------- 1 | // Variables 2 | @import "variables"; 3 | 4 | // Bootstrap 5 | @import '~bootstrap/scss/bootstrap'; 6 | 7 | @import "~font-awesome/scss/font-awesome.scss"; 8 | @import "~summernote/dist/summernote-bs4.css"; 9 | 10 | .navbar { 11 | background-color: #0c64a2; 12 | padding: 0.35rem 1rem; 13 | } 14 | 15 | body { 16 | display: flex; 17 | min-height: 100vh; 18 | flex-direction: column; 19 | } 20 | 21 | .app-content { 22 | flex: 1; 23 | } 24 | 25 | .table th, .table td { 26 | padding: 0.55rem 0.75rem; 27 | } 28 | 29 | .adverts-list { 30 | .advert { 31 | margin: 20px 0; 32 | padding-bottom: 20px; 33 | border-bottom: 1px solid #ddd; 34 | 35 | &:first-of-type { 36 | margin-top: 0; 37 | } 38 | } 39 | } 40 | 41 | .search-bar { 42 | background: #e9ecef; 43 | } 44 | 45 | .breadcrumb { 46 | background: none; 47 | padding: 0; 48 | } 49 | 50 | .banner { 51 | border: 1px solid #ddd; 52 | } -------------------------------------------------------------------------------- /resources/lang/en/auth.php: -------------------------------------------------------------------------------- 1 | 'These credentials do not match our records.', 17 | 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.', 18 | 19 | ]; 20 | -------------------------------------------------------------------------------- /resources/lang/en/pagination.php: -------------------------------------------------------------------------------- 1 | '« Previous', 17 | 'next' => 'Next »', 18 | 19 | ]; 20 | -------------------------------------------------------------------------------- /resources/lang/en/passwords.php: -------------------------------------------------------------------------------- 1 | 'Passwords must be at least six characters and match the confirmation.', 17 | 'reset' => 'Your password has been reset!', 18 | 'sent' => 'We have e-mailed your password reset link!', 19 | 'token' => 'This password reset token is invalid.', 20 | 'user' => "We can't find a user with that e-mail address.", 21 | 22 | ]; 23 | -------------------------------------------------------------------------------- /resources/views/admin/_nav.blade.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/views/admin/adverts/adverts/_nav.blade.php: -------------------------------------------------------------------------------- 1 | @include ('admin._nav', ['page' => 'adverts']) -------------------------------------------------------------------------------- /resources/views/admin/adverts/adverts/reject.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 | 5 |
6 | @csrf 7 | 8 |
9 | 10 | 11 | @if ($errors->has('reason')) 12 | {{ $errors->first('reason') }} 13 | @endif 14 |
15 | 16 |
17 | 18 |
19 |
20 | 21 | @endsection -------------------------------------------------------------------------------- /resources/views/admin/adverts/categories/_nav.blade.php: -------------------------------------------------------------------------------- 1 | @include ('admin._nav', ['page' => 'adverts_categories']) -------------------------------------------------------------------------------- /resources/views/admin/adverts/categories/attributes/show.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 | @include('admin.adverts.categories._nav') 5 | 6 |
7 | Edit 8 |
9 | @csrf 10 | @method('DELETE') 11 | 12 |
13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
ID{{ $category->id }}
Name{{ $category->name }}
Slug{{ $category->slug }}
29 | @endsection -------------------------------------------------------------------------------- /resources/views/admin/banners/_nav.blade.php: -------------------------------------------------------------------------------- 1 | @include ('admin._nav', ['page' => 'banners']) -------------------------------------------------------------------------------- /resources/views/admin/banners/reject.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 | @include('admin.banners._nav') 5 | 6 |
7 | @csrf 8 | 9 |
10 | 11 | 12 | @if ($errors->has('reason')) 13 | {{ $errors->first('reason') }} 14 | @endif 15 |
16 | 17 |
18 | 19 |
20 |
21 | 22 | @endsection -------------------------------------------------------------------------------- /resources/views/admin/home.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 | @include ('admin._nav', ['page' => '']) 5 | @endsection -------------------------------------------------------------------------------- /resources/views/admin/pages/_nav.blade.php: -------------------------------------------------------------------------------- 1 | @include ('admin._nav', ['page' => 'pages']) -------------------------------------------------------------------------------- /resources/views/admin/pages/show.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 | @include('admin.pages._nav') 5 | 6 |
7 | Edit 8 |
9 | @csrf 10 | @method('DELETE') 11 | 12 |
13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
ID{{ $page->id }}
Title{{ $page->title }}
Menu Title{{ $page->menu_title }}
Slug{{ $page->slug }}
Description{{ $page->description }}
34 | 35 |
36 |
37 | {!! clean($page->content) !!} 38 |
39 |
40 | @endsection -------------------------------------------------------------------------------- /resources/views/admin/regions/_list.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | @foreach ($regions as $region) 11 | 12 | 13 | 14 | 15 | @endforeach 16 | 17 | 18 |
NameSlug
{{ $region->name }}{{ $region->slug }}
-------------------------------------------------------------------------------- /resources/views/admin/regions/_nav.blade.php: -------------------------------------------------------------------------------- 1 | @include ('admin._nav', ['page' => 'regions']) -------------------------------------------------------------------------------- /resources/views/admin/regions/create.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 | @include('admin.regions._nav') 5 | 6 |
7 | @csrf 8 | 9 |
10 | 11 | 12 | @if ($errors->has('name')) 13 | {{ $errors->first('name') }} 14 | @endif 15 |
16 | 17 |
18 | 19 | 20 | @if ($errors->has('slug')) 21 | {{ $errors->first('slug') }} 22 | @endif 23 |
24 | 25 |
26 | 27 |
28 |
29 | @endsection -------------------------------------------------------------------------------- /resources/views/admin/regions/edit.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 | @include('admin.regions._nav') 5 | 6 |
7 | @csrf 8 | @method('PUT') 9 | 10 |
11 | 12 | 13 | @if ($errors->has('name')) 14 | {{ $errors->first('name') }} 15 | @endif 16 |
17 | 18 |
19 | 20 | 21 | @if ($errors->has('slug')) 22 | {{ $errors->first('slug') }} 23 | @endif 24 |
25 | 26 |
27 | 28 |
29 |
30 | @endsection -------------------------------------------------------------------------------- /resources/views/admin/regions/index.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 | @include('admin.regions._nav') 5 | 6 |

Add Region

7 | 8 | @include('admin.regions._list', ['regions' => $regions]) 9 | @endsection -------------------------------------------------------------------------------- /resources/views/admin/regions/show.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 | @include('admin.regions._nav') 5 | 6 |
7 | Edit 8 |
9 | @csrf 10 | @method('DELETE') 11 | 12 |
13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
ID{{ $region->id }}
Name{{ $region->name }}
Slug{{ $region->slug }}
28 | 29 |

Add SubRegion

30 | 31 | @include('admin.regions._list', ['regions' => $regions]) 32 | @endsection -------------------------------------------------------------------------------- /resources/views/admin/tickets/_nav.blade.php: -------------------------------------------------------------------------------- 1 | @include ('admin._nav', ['page' => 'tickets']) -------------------------------------------------------------------------------- /resources/views/admin/tickets/edit.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 | @include('admin.tickets._nav') 5 | 6 |
7 | @csrf 8 | @method('PUT') 9 | 10 |
11 | 12 | 13 | @if ($errors->has('subject')) 14 | {{ $errors->first('subject') }} 15 | @endif 16 |
17 | 18 |
19 | 20 | 21 | @if ($errors->has('content')) 22 | {{ $errors->first('content') }} 23 | @endif 24 |
25 | 26 |
27 | 28 |
29 |
30 | 31 | @endsection -------------------------------------------------------------------------------- /resources/views/admin/users/_nav.blade.php: -------------------------------------------------------------------------------- 1 | @include ('admin._nav', ['page' => 'users']) -------------------------------------------------------------------------------- /resources/views/admin/users/create.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 | @include('admin.users._nav') 5 | 6 |
7 | @csrf 8 | 9 |
10 | 11 | 12 | @if ($errors->has('name')) 13 | {{ $errors->first('name') }} 14 | @endif 15 |
16 | 17 |
18 | 19 | 20 | @if ($errors->has('email')) 21 | {{ $errors->first('email') }} 22 | @endif 23 |
24 | 25 |
26 | 27 |
28 |
29 | @endsection -------------------------------------------------------------------------------- /resources/views/admin/users/edit.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 | @include('admin.users._nav') 5 | 6 |
7 | @csrf 8 | @method('PUT') 9 | 10 |
11 | 12 | 13 | @if ($errors->has('name')) 14 | {{ $errors->first('name') }} 15 | @endif 16 |
17 | 18 |
19 | 20 | 21 | @if ($errors->has('email')) 22 | {{ $errors->first('email') }} 23 | @endif 24 |
25 | 26 |
27 | 28 | 33 | @if ($errors->has('role')) 34 | {{ $errors->first('role') }} 35 | @endif 36 |
37 | 38 |
39 | 40 |
41 |
42 | @endsection -------------------------------------------------------------------------------- /resources/views/admin/users/show.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 | @include('admin.users._nav') 5 | 6 |
7 | Edit 8 | 9 | @if ($user->isWait()) 10 |
11 | @csrf 12 | 13 |
14 | @endif 15 | 16 |
17 | @csrf 18 | @method('DELETE') 19 | 20 |
21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 44 | 45 | 46 | 47 | 54 | 55 | 56 | 57 |
ID{{ $user->id }}
Name{{ $user->name }}
Email{{ $user->email }}
Status 37 | @if ($user->isWait()) 38 | Waiting 39 | @endif 40 | @if ($user->isActive()) 41 | Active 42 | @endif 43 |
Role 48 | @if ($user->isAdmin()) 49 | Admin 50 | @else 51 | User 52 | @endif 53 |
58 | @endsection -------------------------------------------------------------------------------- /resources/views/adverts/edit/photos.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 | @if (count($errors) > 0) 5 |
6 | 11 |
12 | @endif 13 | 14 |
15 | @csrf 16 | 17 |
18 | 19 | 20 |
21 | 22 |
23 | 24 |
25 |
26 | 27 | @endsection -------------------------------------------------------------------------------- /resources/views/auth/phone.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 | 5 |
6 | @csrf 7 |
8 | 9 | 10 | @if ($errors->has('token')) 11 | {{ $errors->first('token') }} 12 | @endif 13 |
14 |
15 | 16 |
17 |
18 | @endsection -------------------------------------------------------------------------------- /resources/views/banner/get.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 6 | -------------------------------------------------------------------------------- /resources/views/cabinet/adverts/_nav.blade.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/views/cabinet/adverts/create/_categories.blade.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/views/cabinet/adverts/create/category.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 | @include('cabinet.adverts._nav') 5 | 6 |

Choose category:

7 | 8 | @include('cabinet.adverts.create._categories', ['categories' => $categories]) 9 | 10 | @endsection -------------------------------------------------------------------------------- /resources/views/cabinet/adverts/create/region.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 | @include('cabinet.adverts._nav') 5 | 6 | @if ($region) 7 |

8 | Add Advert for {{ $region->name }} 9 |

10 | @else 11 |

12 | Add Advert for all regions 13 |

14 | @endif 15 | 16 |

Or choose nested region:

17 | 18 | 25 | @endsection -------------------------------------------------------------------------------- /resources/views/cabinet/adverts/index.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 | @include('cabinet.adverts._nav') 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | @foreach ($adverts as $advert) 20 | 21 | 22 | 23 | 24 | 29 | 30 | 41 | 42 | @endforeach 43 | 44 | 45 |
IDUpdatedTitleRegionCategoryStatus
{{ $advert->id }}{{ $advert->updated_at }}{{ $advert->title }} 25 | @if ($advert->region) 26 | {{ $advert->region->name }} 27 | @endif 28 | {{ $advert->category->name }} 31 | @if ($advert->isDraft()) 32 | Draft 33 | @elseif ($advert->isOnModeration()) 34 | Moderation 35 | @elseif ($advert->isActive()) 36 | Active 37 | @elseif ($advert->isClosed()) 38 | Closed 39 | @endif 40 |
46 | 47 | {{ $adverts->links() }} 48 | @endsection -------------------------------------------------------------------------------- /resources/views/cabinet/banners/_nav.blade.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/views/cabinet/banners/create/_categories.blade.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/views/cabinet/banners/create/category.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 | @include('cabinet.banners._nav') 5 | 6 |

Choose category:

7 | 8 | @include('cabinet.banners.create._categories', ['categories' => $categories]) 9 | 10 | @endsection -------------------------------------------------------------------------------- /resources/views/cabinet/banners/create/region.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 | @include('cabinet.banners._nav') 5 | 6 | @if ($region) 7 |

8 | Add Advert for {{ $region->name }} 9 |

10 | @else 11 |

12 | Add Advert for all regions 13 |

14 | @endif 15 | 16 |

Or choose nested region:

17 | 18 | 25 | @endsection -------------------------------------------------------------------------------- /resources/views/cabinet/banners/edit.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 | @include('cabinet.banners._nav') 5 | 6 |
7 | @csrf 8 | @method('PUT') 9 | 10 |
11 | 12 | 13 | @if ($errors->has('name')) 14 | {{ $errors->first('name') }} 15 | @endif 16 |
17 | 18 |
19 | 20 | 21 | @if ($errors->has('limit')) 22 | {{ $errors->first('limit') }} 23 | @endif 24 |
25 | 26 |
27 | 28 | 29 | @if ($errors->has('url')) 30 | {{ $errors->first('url') }} 31 | @endif 32 |
33 | 34 |
35 | 36 |
37 |
38 | 39 | @endsection -------------------------------------------------------------------------------- /resources/views/cabinet/banners/file.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 | @include('cabinet.banners._nav') 5 | 6 |
7 | @csrf 8 | @method('PUT') 9 | 10 |
11 | 12 | 17 | @if ($errors->has('format')) 18 | {{ $errors->first('format') }} 19 | @endif 20 |
21 | 22 |
23 | 24 | 25 | @if ($errors->has('file')) 26 | {{ $errors->first('file') }} 27 | @endif 28 |
29 | 30 |
31 | 32 |
33 |
34 | 35 | @endsection -------------------------------------------------------------------------------- /resources/views/cabinet/favorites/_nav.blade.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/views/cabinet/favorites/index.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 | @include('cabinet.favorites._nav') 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | @foreach ($adverts as $advert) 20 | 21 | 22 | 23 | 24 | 29 | 30 | 37 | 38 | @endforeach 39 | 40 | 41 |
IDUpdatedTitleRegionCategory
{{ $advert->id }}{{ $advert->updated_at }}{{ $advert->title }} 25 | @if ($advert->region) 26 | {{ $advert->region->name }} 27 | @endif 28 | {{ $advert->category->name }} 31 |
32 | @csrf 33 | @method('DELETE') 34 | 35 |
36 |
42 | 43 | {{ $adverts->links() }} 44 | @endsection -------------------------------------------------------------------------------- /resources/views/cabinet/home.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 | 12 | @endsection -------------------------------------------------------------------------------- /resources/views/cabinet/profile/_nav.blade.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/views/cabinet/profile/edit.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 | @include('cabinet.profile._nav') 5 | 6 |
7 | @csrf 8 | @method('PUT') 9 | 10 |
11 | 12 | 13 | @if ($errors->has('name')) 14 | {{ $errors->first('name') }} 15 | @endif 16 |
17 | 18 |
19 | 20 | 21 | @if ($errors->has('last_name')) 22 | {{ $errors->first('last_name') }} 23 | @endif 24 |
25 | 26 |
27 | 28 | 29 | @if ($errors->has('phone')) 30 | {{ $errors->first('phone') }} 31 | @endif 32 |
33 | 34 |
35 | 36 |
37 |
38 | @endsection -------------------------------------------------------------------------------- /resources/views/cabinet/profile/home.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 | @include('cabinet.profile._nav') 5 | 6 |
7 | Edit 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 34 | 35 | @if ($user->phone) 36 | 37 | 47 | 48 | @endif 49 | 50 |
First Name{{ $user->name }}
Last Name{{ $user->last_name }}
Email{{ $user->email }}
Phone 23 | @if ($user->phone) 24 | {{ $user->phone }} 25 | @if (!$user->isPhoneVerified()) 26 | (is not verified)
27 |
28 | @csrf 29 | 30 |
31 | @endif 32 | @endif 33 |
Two Factor Auth 38 |
39 | @csrf 40 | @if ($user->isPhoneAuthEnabled()) 41 | 42 | @else 43 | 44 | @endif 45 |
46 |
51 | @endsection -------------------------------------------------------------------------------- /resources/views/cabinet/profile/phone.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 | @include('cabinet.profile._nav') 5 | 6 |
7 | @csrf 8 | @method('PUT') 9 | 10 |
11 | 12 | 13 | @if ($errors->has('token')) 14 | {{ $errors->first('token') }} 15 | @endif 16 |
17 | 18 |
19 | 20 |
21 |
22 | @endsection -------------------------------------------------------------------------------- /resources/views/cabinet/tickets/_nav.blade.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/views/cabinet/tickets/create.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 | @include('cabinet.tickets._nav') 5 | 6 |
7 | @csrf 8 | 9 |
10 | 11 | 12 | @if ($errors->has('subject')) 13 | {{ $errors->first('subject') }} 14 | @endif 15 |
16 | 17 |
18 | 19 | 20 | @if ($errors->has('content')) 21 | {{ $errors->first('content') }} 22 | @endif 23 |
24 | 25 |
26 | 27 |
28 |
29 | 30 | @endsection -------------------------------------------------------------------------------- /resources/views/cabinet/tickets/index.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 | @include('cabinet.tickets._nav') 5 | 6 |

Add Ticket

7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | @foreach ($tickets as $ticket) 21 | 22 | 23 | 24 | 25 | 26 | 35 | 36 | @endforeach 37 | 38 | 39 |
IDCreatedUpdatedSubjectStatus
{{ $ticket->id }}{{ $ticket->created_at }}{{ $ticket->updated_at }}{{ $ticket->subject }} 27 | @if ($ticket->isOpen()) 28 | Open 29 | @elseif ($ticket->isApproved()) 30 | Approved 31 | @elseif ($ticket->isClosed()) 32 | Closed 33 | @endif 34 |
40 | 41 | {{ $tickets->links() }} 42 | @endsection -------------------------------------------------------------------------------- /resources/views/emails/auth/register/verify.blade.php: -------------------------------------------------------------------------------- 1 | @component('mail::message') 2 | # Email Confirmation 3 | 4 | Please refer to the following link: 5 | 6 | @component('mail::button', ['url' => route('register.verify', ['token' => $user->verify_token])]) 7 | Verify Email 8 | @endcomponent 9 | 10 | Thanks,
11 | {{ config('app.name') }} 12 | @endcomponent 13 | -------------------------------------------------------------------------------- /resources/views/home.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('breadcrumbs', '') 4 | 5 | @section('content') 6 | 7 |
8 |
9 | All Categories 10 |
11 |
12 |
13 | @foreach (array_chunk($categories, 3) as $chunk) 14 |
15 | 20 |
21 | @endforeach 22 |
23 |
24 |
25 | 26 |
27 |
28 | All Regions 29 |
30 |
31 |
32 | @foreach (array_chunk($regions, 3) as $chunk) 33 |
34 | 39 |
40 | @endforeach 41 |
42 |
43 |
44 | 45 | @endsection 46 | -------------------------------------------------------------------------------- /resources/views/layouts/partials/flash.blade.php: -------------------------------------------------------------------------------- 1 | @if (session('status')) 2 |
3 | {{ session('status') }} 4 |
5 | @endif 6 | 7 | @if (session('success')) 8 |
9 | {{ session('success') }} 10 |
11 | @endif 12 | 13 | @if (session('error')) 14 |
15 | {{ session('error') }} 16 |
17 | @endif 18 | 19 | @if (session('info')) 20 |
21 | {{ session('info') }} 22 |
23 | @endif -------------------------------------------------------------------------------- /resources/views/page.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('meta') 4 | 5 | @endsection 6 | 7 | @section('content') 8 |

{{ $page->title }}

9 | 10 | @if ($page->children) 11 | 16 | @endif 17 | 18 | {!! clean($page->content) !!} 19 | @endsection -------------------------------------------------------------------------------- /routes/channels.php: -------------------------------------------------------------------------------- 1 | id === (int) $id; 16 | }); 17 | -------------------------------------------------------------------------------- /routes/console.php: -------------------------------------------------------------------------------- 1 | comment(Inspiring::quote()); 18 | })->describe('Display an inspiring quote'); 19 | -------------------------------------------------------------------------------- /server.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | 10 | $uri = urldecode( 11 | parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) 12 | ); 13 | 14 | // This file allows us to emulate Apache's "mod_rewrite" functionality from the 15 | // built-in PHP web server. This provides a convenient way to test a Laravel 16 | // application without having installed a "real" web server software here. 17 | if ($uri !== '/' && file_exists(__DIR__.'/public'.$uri)) { 18 | return false; 19 | } 20 | 21 | require_once __DIR__.'/public/index.php'; 22 | -------------------------------------------------------------------------------- /storage/app/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !public/ 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /storage/app/public/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/debugbar/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /storage/docker/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/.gitignore: -------------------------------------------------------------------------------- 1 | config.php 2 | routes.php 3 | schedule-* 4 | compiled.php 5 | services.json 6 | events.scanned.php 7 | routes.scanned.php 8 | down 9 | -------------------------------------------------------------------------------- /storage/framework/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/sessions/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/testing/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/views/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/logs/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /tests/CreatesApplication.php: -------------------------------------------------------------------------------- 1 | make(Kernel::class)->bootstrap(); 20 | 21 | Hash::driver('bcrypt')->setRounds(4); 22 | 23 | return $app; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/Feature/Auth/LoginTest.php: -------------------------------------------------------------------------------- 1 | get('/login'); 13 | 14 | $response 15 | ->assertStatus(200) 16 | ->assertSee('Login'); 17 | } 18 | 19 | public function testErrors(): void 20 | { 21 | $response = $this->post('/login', [ 22 | 'email' => '', 23 | 'password' => '', 24 | ]); 25 | 26 | $response 27 | ->assertStatus(302) 28 | ->assertSessionHasErrors(['email', 'password']); 29 | } 30 | 31 | public function testWait(): void 32 | { 33 | $user = factory(User::class)->create(['status' => User::STATUS_WAIT]); 34 | 35 | $response = $this->post('/login', [ 36 | 'email' => $user->email, 37 | 'password' => 'secret', 38 | ]); 39 | 40 | $response 41 | ->assertStatus(302) 42 | ->assertRedirect('/') 43 | ->assertSessionHas('error', 'You need to confirm your account. Please check your email.'); 44 | } 45 | 46 | public function testActive(): void 47 | { 48 | $user = factory(User::class)->create(['status' => User::STATUS_ACTIVE]); 49 | 50 | $response = $this->post('/login', [ 51 | 'email' => $user->email, 52 | 'password' => 'secret', 53 | ]); 54 | 55 | $response 56 | ->assertStatus(302) 57 | ->assertRedirect('/cabinet'); 58 | 59 | $this->assertAuthenticated(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /tests/Feature/ExampleTest.php: -------------------------------------------------------------------------------- 1 | get('/'); 18 | 19 | $response->assertStatus(200); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | name); 23 | self::assertEquals($email, $user->email); 24 | self::assertNotEmpty($user->password); 25 | 26 | self::assertTrue($user->isActive()); 27 | self::assertFalse($user->isAdmin()); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/Unit/Entity/User/RegisterTest.php: -------------------------------------------------------------------------------- 1 | name); 24 | self::assertEquals($email, $user->email); 25 | self::assertNotEmpty($user->password); 26 | self::assertNotEquals($password, $user->password); 27 | 28 | self::assertTrue($user->isWait()); 29 | self::assertFalse($user->isActive()); 30 | self::assertFalse($user->isAdmin()); 31 | } 32 | 33 | public function testVerify(): void 34 | { 35 | $user = User::register('name', 'email', 'password'); 36 | 37 | $user->verify(); 38 | 39 | self::assertFalse($user->isWait()); 40 | self::assertTrue($user->isActive()); 41 | } 42 | 43 | public function testAlreadyVerified(): void 44 | { 45 | $user = User::register('name', 'email', 'password'); 46 | $user->verify(); 47 | 48 | $this->expectExceptionMessage('User is already verified.'); 49 | $user->verify(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /tests/Unit/Entity/User/RoleTest.php: -------------------------------------------------------------------------------- 1 | create(['role' => User::ROLE_USER]); 16 | 17 | self::assertFalse($user->isAdmin()); 18 | 19 | $user->changeRole(User::ROLE_ADMIN); 20 | 21 | self::assertTrue($user->isAdmin()); 22 | } 23 | 24 | public function testAlready(): void 25 | { 26 | $user = factory(User::class)->create(['role' => User::ROLE_ADMIN]); 27 | 28 | $this->expectExceptionMessage('Role is already assigned.'); 29 | 30 | $user->changeRole(User::ROLE_ADMIN); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /webpack.mix.js: -------------------------------------------------------------------------------- 1 | let mix = require('laravel-mix'); 2 | let webpack = require('webpack'); 3 | 4 | /* 5 | |-------------------------------------------------------------------------- 6 | | Mix Asset Management 7 | |-------------------------------------------------------------------------- 8 | | 9 | | Mix provides a clean, fluent API for defining some Webpack build steps 10 | | for your Laravel application. By default, we are compiling the Sass 11 | | file for the application as well as bundling up all the JS files. 12 | | 13 | */ 14 | 15 | mix 16 | .setPublicPath('public/build') 17 | .setResourceRoot('/build/') 18 | .js('resources/assets/js/app.js', 'js') 19 | .sass('resources/assets/sass/app.scss', 'css') 20 | .version(); 21 | 22 | mix.webpackConfig({ 23 | plugins: [ 24 | new webpack.IgnorePlugin(/^codemirror$/) 25 | ] 26 | }); --------------------------------------------------------------------------------