├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── application ├── .editorconfig ├── .env.example ├── .gitattributes ├── .gitignore ├── app │ ├── Casts │ │ ├── Hash.php │ │ └── Json.php │ ├── Console │ │ ├── Commands │ │ │ ├── BaseCommand.php │ │ │ ├── ChangePassword.php │ │ │ ├── CreateAdmin.php │ │ │ └── CreateStaff.php │ │ └── Kernel.php │ ├── Constants.php │ ├── Exceptions │ │ ├── AppException.php │ │ └── Handler.php │ ├── Helpers.php │ ├── Http │ │ ├── Controllers │ │ │ ├── API │ │ │ │ └── V1 │ │ │ │ │ ├── AssetInfoController.php │ │ │ │ │ ├── EventsController.php │ │ │ │ │ └── NonceController.php │ │ │ ├── Account │ │ │ │ └── ProfileController.php │ │ │ ├── Admin │ │ │ │ ├── ManageEventsController.php │ │ │ │ └── ManageUsersController.php │ │ │ ├── Auth │ │ │ │ ├── ConfirmPasswordController.php │ │ │ │ ├── ForgotPasswordController.php │ │ │ │ ├── LoginController.php │ │ │ │ ├── RegisterController.php │ │ │ │ ├── ResetPasswordController.php │ │ │ │ └── VerificationController.php │ │ │ ├── Controller.php │ │ │ ├── DashboardController.php │ │ │ ├── HomeController.php │ │ │ └── Staff │ │ │ │ └── ScanTicketsController.php │ │ ├── Kernel.php │ │ ├── Middleware │ │ │ ├── AdminOnly.php │ │ │ ├── Authenticate.php │ │ │ ├── EncryptCookies.php │ │ │ ├── PreventRequestsDuringMaintenance.php │ │ │ ├── RedirectIfAuthenticated.php │ │ │ ├── StaffOnly.php │ │ │ ├── TrimStrings.php │ │ │ ├── TrustHosts.php │ │ │ ├── TrustProxies.php │ │ │ ├── ValidateSignature.php │ │ │ └── VerifyCsrfToken.php │ │ └── Traits │ │ │ └── JsonResponseTrait.php │ ├── Models │ │ ├── Event.php │ │ ├── Ticket.php │ │ └── User.php │ ├── Providers │ │ ├── AppServiceProvider.php │ │ ├── AuthServiceProvider.php │ │ ├── BroadcastServiceProvider.php │ │ ├── EventServiceProvider.php │ │ └── RouteServiceProvider.php │ ├── Services │ │ ├── EventService.php │ │ ├── TicketService.php │ │ └── UserService.php │ └── ThirdParty │ │ └── CardanoClients │ │ ├── BlockFrostClient.php │ │ └── ICardanoClient.php ├── artisan ├── bootstrap │ ├── app.php │ └── cache │ │ └── .gitignore ├── composer.json ├── composer.lock ├── config │ ├── app.php │ ├── auth.php │ ├── broadcasting.php │ ├── cache.php │ ├── cors.php │ ├── database.php │ ├── filesystems.php │ ├── gatekeeper.php │ ├── hashing.php │ ├── logging.php │ ├── mail.php │ ├── queue.php │ ├── sanctum.php │ ├── services.php │ ├── session.php │ └── view.php ├── database │ ├── .gitignore │ ├── factories │ │ └── UserFactory.php │ ├── migrations │ │ ├── 2014_10_12_000000_create_users_table.php │ │ ├── 2014_10_12_100000_create_password_resets_table.php │ │ ├── 2019_08_19_000000_create_failed_jobs_table.php │ │ ├── 2019_12_14_000001_create_personal_access_tokens_table.php │ │ ├── 2022_09_12_000001_create_events_table.php │ │ ├── 2022_09_12_000002_create_tickets_table.php │ │ ├── 2023_09_23_042340_add_location_to_events_table.php │ │ ├── 2023_09_23_043453_add_event_date_to_events.php │ │ └── 2023_09_23_043840_add_image_to_events.php │ └── seeders │ │ └── DatabaseSeeder.php ├── lang │ └── en │ │ ├── auth.php │ │ ├── pagination.php │ │ ├── passwords.php │ │ └── validation.php ├── package-lock.json ├── package.json ├── phpunit.xml ├── public │ ├── .htaccess │ ├── build │ │ ├── assets │ │ │ ├── app.5380b351.css │ │ │ └── app.ff70f951.js │ │ └── manifest.json │ ├── favicon.ico │ ├── images │ │ ├── favicon.png │ │ ├── favicon.svg │ │ └── logo.svg │ ├── index.php │ ├── js │ │ └── bridge │ │ │ └── cardano-dapp-connector-bridge.min.js │ └── robots.txt ├── resources │ ├── js │ │ ├── app.js │ │ └── bootstrap.js │ ├── nodejs │ │ └── validateNonce.js │ ├── sass │ │ ├── _variables.scss │ │ └── app.scss │ └── views │ │ ├── account │ │ └── profile.blade.php │ │ ├── admin │ │ ├── manage-events │ │ │ ├── form.blade.php │ │ │ ├── index.blade.php │ │ │ └── view.blade.php │ │ └── manage-users │ │ │ ├── form.blade.php │ │ │ └── index.blade.php │ │ ├── auth │ │ ├── login.blade.php │ │ ├── passwords │ │ │ ├── confirm.blade.php │ │ │ ├── email.blade.php │ │ │ └── reset.blade.php │ │ └── verify.blade.php │ │ ├── dashboard │ │ ├── admin-menu.blade.php │ │ ├── index.blade.php │ │ └── staff-menu.blade.php │ │ ├── event.blade.php │ │ ├── layouts │ │ ├── app.blade.php │ │ └── partials │ │ │ ├── alerts.blade.php │ │ │ └── nav.blade.php │ │ └── staff │ │ └── scan-tickets │ │ └── event.blade.php ├── routes │ ├── api.php │ ├── channels.php │ ├── console.php │ └── web.php ├── ssl │ ├── cert.pem │ └── key.pem ├── storage │ ├── app │ │ ├── .gitignore │ │ └── public │ │ │ └── .gitignore │ ├── framework │ │ ├── .gitignore │ │ ├── cache │ │ │ ├── .gitignore │ │ │ └── data │ │ │ │ └── .gitignore │ │ ├── sessions │ │ │ └── .gitignore │ │ ├── testing │ │ │ └── .gitignore │ │ └── views │ │ │ └── .gitignore │ └── logs │ │ └── .gitignore ├── tests │ ├── CreatesApplication.php │ ├── Feature │ │ └── ExampleTest.php │ ├── TestCase.php │ └── Unit │ │ └── ExampleTest.php └── vite.config.js └── docker ├── Dockerfile ├── docker-compose.custom.yml.example ├── docker-compose.yml ├── gatekeeper.app.conf ├── php.ini └── wait-for-mysql.sh /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | docker/docker-compose.custom.yml 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Latheesan Kanesamoorthy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | export COMPOSE_PROJECT_NAME=gatekeeper 2 | export COMPOSE_FILE=docker/docker-compose.yml:docker/docker-compose.custom.yml 3 | 4 | .PHONY: up 5 | up: 6 | $(MAKE) down 7 | docker-compose up -d 8 | $(MAKE) composer-install 9 | ./docker/wait-for-mysql.sh 10 | $(MAKE) db-migrate 11 | docker exec -it gatekeeper-web bash -c "npm install" 12 | 13 | .PHONY: down 14 | down: 15 | docker-compose down --remove-orphans 16 | 17 | .PHONY: build 18 | build: 19 | docker-compose build 20 | $(MAKE) up 21 | 22 | # 23 | # Helper functions 24 | # 25 | 26 | .PHONY: composer-install 27 | composer-install: 28 | docker exec -it gatekeeper-web bash -c "composer install" 29 | 30 | .PHONY: db-migrate 31 | db-migrate: 32 | docker exec -it gatekeeper-web bash -c "php artisan migrate" 33 | 34 | .PHONY: db-refresh 35 | db-refresh: 36 | docker exec -it gatekeeper-web bash -c "php artisan migrate:fresh --seed" 37 | 38 | .PHONY: tinker 39 | tinker: 40 | docker exec -it gatekeeper-web bash -c "php artisan tinker" 41 | 42 | .PHONY: status 43 | status: 44 | docker-compose ps 45 | 46 | .PHONY: logs 47 | logs: 48 | docker-compose logs -f --tail=100 49 | 50 | .PHONY: shell 51 | shell: 52 | docker exec -it gatekeeper-web bash 53 | 54 | .PHONY: stats 55 | stats: 56 | docker stats gatekeeper-web gatekeeper-mysql gatekeeper-redis 57 | 58 | .PHONY: artisan 59 | artisan: 60 | docker exec -it gatekeeper-web bash -c "php artisan $(COMMAND)" 61 | 62 | .PHONY: admin 63 | admin: 64 | docker exec -it gatekeeper-web bash -c "php artisan create:admin" 65 | 66 | .PHONY: staff 67 | staff: 68 | docker exec -it gatekeeper-web bash -c "php artisan create:staff" 69 | 70 | .PHONY: change-password 71 | change-password: 72 | docker exec -it gatekeeper-web bash -c "php artisan change:password" 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GateKeeper 2 | An open source ticketing solution using Cardano Native Assets. Bridging Web 3.0 to IRL via Blockchain. 3 | 4 | Developed by [Adam Dean](https://twitter.com/adamKDean) & [Latheesan Kanesamoorthy](https://twitter.com/LatheesanK) and maintained by the Cardano Community with ❤. 5 | 6 | ## Prerequisite 7 | - Linux OS 8 | - Make 9 | - Git 10 | - [Docker](https://docs.docker.com/desktop/install/linux-install/) / [Docker-Compose](https://docs.docker.com/compose/install/linux/) 11 | 12 | ## Local Install 13 | - Open terminal and type `cd $HOME/Desktop` 14 | - Run `docker network create --driver bridge local-gatekeeper` (**Only Required First Time Setup**) 15 | - Clone repo `git clone https://github.com/CardanoGateKeeper/Core.git` 16 | - Switch to repo dir `cd $HOME/Desktop/GateKeeper` 17 | - Copy `docker/docker-compose.custom.yml.example` as `docker/docker-compose.custom.yml` 18 | - Copy `application/.env.example` as `application/.env` 19 | - Run `make buid` to build & start the containers 20 | - Application should be running locally at [**https**://localhost:8020](https://localhost:8020) 21 | 22 | > You can connect to the dev mysql instance via host `127.0.0.1` and port `33020`, see credentials in `docker/docker-compose.custom.yml` 23 | 24 | ## Available Make Commands (Local Development) 25 | * `build` Rebuild all docker containers 26 | * `up` Restart all docker containers 27 | * `down` Shutdown all docker containers 28 | * `composer-install` Run composer install 29 | * `db-migrate` Run database migration(s) 30 | * `db-refresh` Drop all database tables, re-run the migration(s) with seeds 31 | * `status` View the status of all running containers 32 | * `logs` View the logs out of all running containers 33 | * `shell` Drop into an interactive shell inside _gatekeeper-web_ container 34 | * `stats` View the resource usage of all running containers 35 | * `artisan` Execute Laravel `artisan` command inside _gatekeeper-web_ container 36 | * `admin` Create a new admin user 37 | * `staff` Create a new staff user 38 | * `change-password` Change user account password 39 | 40 | ### How To Change Application Port 41 | * You can change the exposed application port by modifying section of `gatekeeper-web` in `docker/docker-compose.custom.yml` 42 | 43 | ### How To Change MySQL Credentials 44 | * You can change the exposed mysql port & database credentials by modifying section of `gatekeeper-mysql` in `docker/docker-compose.custom.yml` 45 | * Then update the `application/.env` to reflect the new db credentials 46 | * In production environment, it is _recommended_ to change the database credentials and not expose the mysql ports 47 | -------------------------------------------------------------------------------- /application/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | indent_style = space 8 | indent_size = 4 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | 14 | [*.{yml,yaml}] 15 | indent_size = 2 16 | 17 | [docker-compose.yml] 18 | indent_size = 4 19 | -------------------------------------------------------------------------------- /application/.env.example: -------------------------------------------------------------------------------- 1 | APP_NAME=GateKeeper 2 | APP_ENV=local 3 | APP_KEY=base64:GGEcLn1SmXe7zmRG3Sf9Gbn9hzkDq5l4Zzly/Ll6NTs= 4 | APP_DEBUG=true 5 | APP_URL=http://localhost:8015 6 | 7 | LOG_CHANNEL=errorlog 8 | LOG_DEPRECATIONS_CHANNEL=null 9 | LOG_LEVEL=debug 10 | 11 | DB_CONNECTION=mysql 12 | DB_HOST=gatekeeper-mysql 13 | DB_PORT=3306 14 | DB_DATABASE=gatekeeper 15 | DB_USERNAME=gatekeeper 16 | DB_PASSWORD=123456 17 | 18 | BROADCAST_DRIVER=redis 19 | CACHE_DRIVER=redis 20 | FILESYSTEM_DRIVER=local 21 | QUEUE_CONNECTION=redis 22 | SESSION_DRIVER=redis 23 | SESSION_LIFETIME=120 24 | SESSION_DOMAIN=localhost 25 | 26 | REDIS_HOST=gatekeeper-redis 27 | REDIS_PASSWORD=null 28 | REDIS_PORT=6379 29 | 30 | CARDANO_NETWORK=testnet 31 | BLOCKFROST_PROJECT_ID=__UPDATE_ME__ 32 | 33 | # To use NFTCDN.io for images 34 | #DEFAULT_IMAGE_CDN=nftcdn 35 | #IMAGE_CDN_URL=buffy.nftcdn.io 36 | 37 | # To use an IPFS gateway for images 38 | DEFAULT_IMAGE_CDN=ipfs 39 | IMAGE_CDN_URL=https://cloudflare-ipfs.com/ipfs/ 40 | -------------------------------------------------------------------------------- /application/.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | 3 | *.blade.php diff=html 4 | *.css diff=css 5 | *.html diff=html 6 | *.md diff=markdown 7 | *.php diff=php 8 | 9 | /.github export-ignore 10 | CHANGELOG.md export-ignore 11 | .styleci.yml export-ignore 12 | -------------------------------------------------------------------------------- /application/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /public/hot 3 | /public/storage 4 | /storage/*.key 5 | /vendor 6 | .env 7 | .env.backup 8 | .phpunit.result.cache 9 | Homestead.json 10 | Homestead.yaml 11 | auth.json 12 | npm-debug.log 13 | yarn-error.log 14 | /.idea 15 | /.vscode 16 | -------------------------------------------------------------------------------- /application/app/Casts/Hash.php: -------------------------------------------------------------------------------- 1 | algorithm = $algorithm; 26 | } 27 | 28 | /** 29 | * Prepare the given value for storage. 30 | * 31 | * @param Model $model 32 | * @param string $key 33 | * @param array $value 34 | * @param array $attributes 35 | * @return string 36 | */ 37 | public function set($model, $key, $value, $attributes): string 38 | { 39 | return is_null($this->algorithm) 40 | ? bcrypt($value) 41 | : hash($this->algorithm, $value); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /application/app/Casts/Json.php: -------------------------------------------------------------------------------- 1 | error(sprintf( 13 | '%s - %s on %s at line #%d', 14 | $errorContext, 15 | $exception->getMessage(), 16 | basename($exception->getFile()), 17 | $exception->getLine(), 18 | )); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /application/app/Console/Commands/ChangePassword.php: -------------------------------------------------------------------------------- 1 | ask(trans('Enter account email')); 35 | $newAccountPassword = $this->ask(trans('Enter new account password')); 36 | 37 | /** @var UserService $userService */ 38 | $userService = app()->make(UserService::class); 39 | 40 | $user = $userService->findByEmail($accountEmail); 41 | 42 | if (!$user) { 43 | throw new AppException(trans('Account with that email not found')); 44 | } 45 | 46 | $userService->changePassword($user, $newAccountPassword); 47 | 48 | $this->info(trans('Account password changed')); 49 | 50 | } catch (Throwable $exception) { 51 | 52 | $this->logError(trans('Failed to change account password'), $exception); 53 | 54 | } 55 | 56 | return 0; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /application/app/Console/Commands/CreateAdmin.php: -------------------------------------------------------------------------------- 1 | [ ROLE_ADMIN ], 35 | 'name' => $this->ask(trans('Enter account name')), 36 | 'email' => $this->ask(trans('Enter account email')), 37 | 'password' => $this->ask(trans('Enter account password')), 38 | ]; 39 | 40 | /** @var UserService $userService */ 41 | $userService = app()->make(UserService::class); 42 | 43 | $userService->create($userData); 44 | 45 | $this->info(trans('Admin account successfully created')); 46 | 47 | } catch (Throwable $exception) { 48 | 49 | $this->logError(trans('Failed to create admin user'), $exception); 50 | 51 | } 52 | 53 | return 0; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /application/app/Console/Commands/CreateStaff.php: -------------------------------------------------------------------------------- 1 | [ ROLE_STAFF ], 35 | 'name' => $this->ask(trans('Enter account name')), 36 | 'email' => $this->ask(trans('Enter account email')), 37 | 'password' => $this->ask(trans('Enter account password')), 38 | ]; 39 | 40 | /** @var UserService $userService */ 41 | $userService = app()->make(UserService::class); 42 | 43 | $userService->create($userData); 44 | 45 | $this->info(trans('Staff account successfully created')); 46 | 47 | } catch (Throwable $exception) { 48 | 49 | $this->logError(trans('Failed to create staff user'), $exception); 50 | 51 | } 52 | 53 | return 0; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /application/app/Console/Kernel.php: -------------------------------------------------------------------------------- 1 | command('inspire')->hourly(); 19 | } 20 | 21 | /** 22 | * Register the commands for the application. 23 | * 24 | * @return void 25 | */ 26 | protected function commands() 27 | { 28 | $this->load(__DIR__.'/Commands'); 29 | 30 | require base_path('routes/console.php'); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /application/app/Constants.php: -------------------------------------------------------------------------------- 1 | , \Psr\Log\LogLevel::*> 15 | */ 16 | protected $levels = [ 17 | // 18 | ]; 19 | 20 | /** 21 | * A list of the exception types that are not reported. 22 | * 23 | * @var array> 24 | */ 25 | protected $dontReport = [ 26 | // 27 | ]; 28 | 29 | /** 30 | * A list of the inputs that are never flashed to the session on validation exceptions. 31 | * 32 | * @var array 33 | */ 34 | protected $dontFlash = [ 35 | 'current_password', 36 | 'password', 37 | 'password_confirmation', 38 | ]; 39 | 40 | /** 41 | * Register the exception handling callbacks for the application. 42 | * 43 | * @return void 44 | */ 45 | public function register() 46 | { 47 | $this->reportable(function (Throwable $e) { 48 | // 49 | }); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /application/app/Helpers.php: -------------------------------------------------------------------------------- 1 | check() 15 | && in_array($requiredRole, auth()->user()->roles, true); 16 | } 17 | 18 | function validRoles(): array { 19 | return [ 20 | ROLE_ADMIN, 21 | ROLE_STAFF, 22 | ]; 23 | } 24 | 25 | function redirectBackWithError(string $errorContext, Throwable $exception): RedirectResponse { 26 | return redirect() 27 | ->back() 28 | ->withInput() 29 | ->with('error', sprintf( 30 | '%s - %s', 31 | $errorContext, 32 | $exception->getMessage(), 33 | )); 34 | } 35 | -------------------------------------------------------------------------------- /application/app/Http/Controllers/API/V1/AssetInfoController.php: -------------------------------------------------------------------------------- 1 | cardanoClient = $cardanoClient; 23 | } 24 | 25 | public function assetInfo(Request $request): JsonResponse 26 | { 27 | try { 28 | 29 | $request->validate([ 30 | 'asset_id' => ['required', 'string'], 31 | ]); 32 | 33 | $cacheKey = 'asset-info:' . $request->asset_id; 34 | $assetInfo = Cache::remember($cacheKey, CACHE_ONE_DAY, function() use($request) { 35 | return $this->cardanoClient->getAssetMetadata($request->asset_id); 36 | }); 37 | 38 | return $assetInfo 39 | ? $this->successResponse($assetInfo) 40 | : $this->errorResponse(trans('asset not found'), Response::HTTP_NOT_FOUND); 41 | 42 | } catch (Throwable $exception) { 43 | 44 | return $this->jsonException(trans('Failed to load asset info'), $exception); 45 | 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /application/app/Http/Controllers/API/V1/EventsController.php: -------------------------------------------------------------------------------- 1 | eventService = $eventService; 22 | } 23 | 24 | public function eventList(): JsonResponse 25 | { 26 | try { 27 | 28 | $eventList = Cache::remember('event-list', CACHE_ONE_DAY, function() { 29 | return $this->eventService->getEventList(); 30 | }); 31 | 32 | return $this->successResponse($eventList); 33 | 34 | } catch (Throwable $exception) { 35 | 36 | return $this->jsonException(trans('Failed to load event list'), $exception); 37 | 38 | } 39 | } 40 | 41 | public function eventInfo(string $uuid): JsonResponse 42 | { 43 | try { 44 | 45 | $event = $this->eventService->findByUUID($uuid); 46 | 47 | return $event 48 | ? $this->successResponse($event->only('name', 'policyIds')) 49 | : $this->errorResponse(trans('event not found'), Response::HTTP_NOT_FOUND); 50 | 51 | } catch (Throwable $exception) { 52 | 53 | return $this->jsonException(trans('Failed to load event info'), $exception); 54 | 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /application/app/Http/Controllers/Account/ProfileController.php: -------------------------------------------------------------------------------- 1 | userService = $userService; 22 | } 23 | 24 | public function index(): Renderable 25 | { 26 | $user = Auth::user(); 27 | 28 | return view( 29 | 'account.profile', 30 | compact('user'), 31 | ); 32 | } 33 | 34 | public function update(Request $request): RedirectResponse 35 | { 36 | try { 37 | 38 | $validationRules = [ 39 | 'name' => ['required', 'string', 'min:3'], 40 | 'current_password' => ['required', $this->userService->passwordRules()], 41 | ]; 42 | 43 | if (!empty($request->new_password)) { 44 | $validationRules['new_password'] = [ 45 | $this->userService->passwordRules(), 46 | ]; 47 | } 48 | 49 | $validator = Validator::make( 50 | $request->all(), 51 | $validationRules, 52 | ); 53 | 54 | if ($validator->fails()) { 55 | throw new AppException(sprintf( 56 | '%s: %s', 57 | trans('validation errors'), 58 | implode(' ', $validator->errors()->all()) 59 | )); 60 | } 61 | 62 | $this->userService->validateCurrentPassword(Auth::id(), $request->current_password); 63 | 64 | $this->userService->updateAccount(Auth::id(), $request->name, $request->new_password); 65 | 66 | return redirect() 67 | ->back() 68 | ->with('status', trans('Account updated')) 69 | ; 70 | 71 | } catch (Throwable $exception) { 72 | 73 | return redirectBackWithError( 74 | trans('Failed to update'), 75 | $exception 76 | ); 77 | 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /application/app/Http/Controllers/Admin/ManageEventsController.php: -------------------------------------------------------------------------------- 1 | eventService = $eventService; 19 | } 20 | 21 | public function index(): Renderable { 22 | $allEvents = $this->eventService->getEventList(true); 23 | 24 | return view('admin.manage-events.index', compact('allEvents'),); 25 | } 26 | 27 | public function create(): Renderable { 28 | $event = null; 29 | 30 | return view('admin.manage-events.form', compact('event'),); 31 | } 32 | 33 | public function store(Request $request): RedirectResponse { 34 | try { 35 | 36 | $payload = $request->only([ 37 | 'event_id', 38 | 'name', 39 | 'location', 40 | 'eventDate', 41 | 'eventStart', 42 | 'eventEnd', 43 | 'startDateTime', 44 | 'endDateTime', 45 | 'hodlAsset', 46 | 'policyIds', 47 | 'nonceValidForMinutes', 48 | ]); 49 | 50 | if ($request->file('image')) { 51 | $payload['image'] = $request->file('image')->store('public'); 52 | } 53 | 54 | 55 | $this->eventService->save($payload); 56 | 57 | return redirect() 58 | ->route('manage-events.index') 59 | ->with('status', !empty($request->event_id) ? trans('Event updated') : trans('Event created')); 60 | 61 | } catch (Throwable $exception) { 62 | 63 | return redirectBackWithError(trans('Failed to save event'), $exception,); 64 | 65 | } 66 | } 67 | 68 | public function show(Event $event): Renderable { 69 | $event_tickets = $event->tickets; 70 | $tickets = [ 71 | 'total' => count($event_tickets), 72 | 'checked_in' => 0, 73 | 'event_tickets' => $event_tickets, 74 | ]; 75 | 76 | foreach ($event_tickets as $ticket) { 77 | if ($ticket->isCheckedIn) { 78 | $tickets['checked_in']++; 79 | } 80 | } 81 | 82 | // $tickets = $event->tickets; 83 | return view('admin.manage-events.view', compact('event', 'tickets'),); 84 | } 85 | 86 | public function edit(Event $event): Renderable { 87 | return view('admin.manage-events.form', compact('event'),); 88 | } 89 | 90 | public function destroy(Event $event): void { 91 | dd('TODO'); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /application/app/Http/Controllers/Admin/ManageUsersController.php: -------------------------------------------------------------------------------- 1 | userService = $userService; 19 | } 20 | 21 | public function index(Request $request): Renderable 22 | { 23 | $allUsers = $this->userService->allUsers($request->search ?? null); 24 | 25 | return view( 26 | 'admin.manage-users.index', 27 | compact('allUsers'), 28 | ); 29 | } 30 | 31 | public function addUser(): Renderable 32 | { 33 | $user = null; 34 | 35 | return view( 36 | 'admin.manage-users.form', 37 | compact('user'), 38 | ); 39 | } 40 | 41 | public function edit(int $userId): Renderable|RedirectResponse 42 | { 43 | $user = $this->userService->findById($userId); 44 | 45 | if (!$user) { 46 | return redirect() 47 | ->route('admin.manage-users.index') 48 | ->with('error', trans('user does not exist')); 49 | } 50 | 51 | return view( 52 | 'admin.manage-users.form', 53 | compact('user'), 54 | ); 55 | } 56 | 57 | public function save(Request $request): RedirectResponse 58 | { 59 | try { 60 | 61 | $this->userService->save($request->only([ 62 | 'user_id', 63 | 'name', 64 | 'email', 65 | 'password', 66 | 'roles', 67 | ])); 68 | 69 | return redirect() 70 | ->route('admin.manage-users.index') 71 | ->with('status', $request->user_id 72 | ? trans('account updated') 73 | : trans('account created') 74 | ); 75 | 76 | } catch (Throwable $exception) { 77 | 78 | return redirectBackWithError(trans('failed to save user'), $exception); 79 | 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /application/app/Http/Controllers/Auth/ConfirmPasswordController.php: -------------------------------------------------------------------------------- 1 | middleware('auth'); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /application/app/Http/Controllers/Auth/ForgotPasswordController.php: -------------------------------------------------------------------------------- 1 | middleware('guest')->except('logout'); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /application/app/Http/Controllers/Auth/RegisterController.php: -------------------------------------------------------------------------------- 1 | middleware('guest'); 42 | } 43 | 44 | /** 45 | * Get a validator for an incoming registration request. 46 | * 47 | * @param array $data 48 | * @return \Illuminate\Contracts\Validation\Validator 49 | */ 50 | protected function validator(array $data) 51 | { 52 | return Validator::make($data, [ 53 | 'name' => ['required', 'string', 'max:255'], 54 | 'email' => ['required', 'string', 'email', 'max:255', 'unique:users'], 55 | 'password' => ['required', 'string', 'min:8', 'confirmed'], 56 | ]); 57 | } 58 | 59 | /** 60 | * Create a new user instance after a valid registration. 61 | * 62 | * @param array $data 63 | * @return \App\Models\User 64 | */ 65 | protected function create(array $data) 66 | { 67 | return User::create([ 68 | 'name' => $data['name'], 69 | 'email' => $data['email'], 70 | 'password' => Hash::make($data['password']), 71 | ]); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /application/app/Http/Controllers/Auth/ResetPasswordController.php: -------------------------------------------------------------------------------- 1 | middleware('auth'); 39 | $this->middleware('signed')->only('verify'); 40 | $this->middleware('throttle:6,1')->only('verify', 'resend'); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /application/app/Http/Controllers/Controller.php: -------------------------------------------------------------------------------- 1 | eventService = $eventService; 18 | } 19 | 20 | public function index(): RedirectResponse 21 | { 22 | /** 23 | * TODO: Let the user pick the event they want to generate tickets for 24 | * TODO: For now, we get the first event in the system 25 | */ 26 | 27 | $eventList = $this->eventService->getEventList(); 28 | 29 | if (!$eventList->count()) { 30 | abort(500, trans('Events missing')); 31 | } 32 | 33 | return redirect() 34 | ->route('event', $eventList->first()->uuid); 35 | } 36 | 37 | public function event(string $eventUUID): Renderable 38 | { 39 | $event = $this->eventService->findByUUID($eventUUID); 40 | 41 | if (!$event) { 42 | abort(404, trans('Event not found')); 43 | } 44 | 45 | return view( 46 | 'event', 47 | compact('event'), 48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /application/app/Http/Controllers/Staff/ScanTicketsController.php: -------------------------------------------------------------------------------- 1 | eventService = $eventService; 35 | $this->ticketService = $ticketService; 36 | $this->cardanoClient = $cardanoClient; 37 | } 38 | 39 | public function index(): RedirectResponse 40 | { 41 | /** 42 | * TODO: Let the user pick the event they want to generate tickets for 43 | * TODO: For now, we get the first event in the system 44 | */ 45 | 46 | $eventList = $this->eventService->getEventList(); 47 | 48 | if (!$eventList->count()) { 49 | abort(500, trans('Events missing')); 50 | } 51 | 52 | return redirect() 53 | ->route('staff.scan-tickets.event', $eventList->first()->uuid); 54 | } 55 | 56 | public function event(string $eventUUID): Renderable 57 | { 58 | $event = $this->eventService->findByUUID($eventUUID); 59 | 60 | if (!$event) { 61 | abort(404, trans('Event not found')); 62 | } 63 | 64 | return view( 65 | 'staff.scan-tickets.event', 66 | compact('event'), 67 | ); 68 | } 69 | 70 | public function ajaxRegisterTicket(Request $request): JsonResponse 71 | { 72 | try { 73 | 74 | if (empty($request->eventUUID) || empty($request->qr) || !str_contains($request->qr, '|')) { 75 | throw new AppException(trans('Invalid request')); 76 | } 77 | 78 | $event = $this->eventService->findByUUID($request->eventUUID); 79 | 80 | if (!$event) { 81 | throw new AppException(trans('Event not found')); 82 | } 83 | 84 | [$assetId, $ticketNonce] = explode('|', $request->qr); 85 | 86 | $ticket = $this->ticketService->findTicketByQRCode($event->id, $assetId, $ticketNonce); 87 | 88 | if (!$ticket) { 89 | throw new AppException(trans('Invalid ticket')); 90 | } 91 | 92 | if ($ticket->isCheckedIn) { 93 | throw new AppException(trans( 94 | 'Ticket already registered :checkedInAt', 95 | [ 96 | 'checkedInAt' => $ticket->checkInTime->diffForHumans(), 97 | ] 98 | )); 99 | } 100 | 101 | $this->checkAssetHodl($event, $ticket); 102 | 103 | $this->ticketService->checkInTicket($ticket, Auth::id()); 104 | 105 | return $this->successResponse([ 106 | 'success' => trans('Ticket successfully registered'), 107 | ]); 108 | 109 | } catch (Throwable $exception) { 110 | 111 | return $this->jsonException(trans('Failed to register ticket'), $exception); 112 | 113 | } 114 | } 115 | 116 | /** 117 | * @throws AppException 118 | */ 119 | private function checkAssetHodl(Event $event, Ticket $ticket): void 120 | { 121 | if (!$event->hodlAsset) { 122 | return; 123 | } 124 | 125 | if (!$this->cardanoClient->assetHodled($ticket->policyId, $ticket->assetId, $ticket->stakeKey)) { 126 | throw new AppException(trans('Asset not found in wallet')); 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /application/app/Http/Kernel.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | protected $middleware = [ 17 | // \App\Http\Middleware\TrustHosts::class, 18 | \App\Http\Middleware\TrustProxies::class, 19 | \Illuminate\Http\Middleware\HandleCors::class, 20 | \App\Http\Middleware\PreventRequestsDuringMaintenance::class, 21 | \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class, 22 | \App\Http\Middleware\TrimStrings::class, 23 | \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class, 24 | ]; 25 | 26 | /** 27 | * The application's route middleware groups. 28 | * 29 | * @var array> 30 | */ 31 | protected $middlewareGroups = [ 32 | 'web' => [ 33 | \App\Http\Middleware\EncryptCookies::class, 34 | \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, 35 | \Illuminate\Session\Middleware\StartSession::class, 36 | \Illuminate\View\Middleware\ShareErrorsFromSession::class, 37 | \App\Http\Middleware\VerifyCsrfToken::class, 38 | \Illuminate\Routing\Middleware\SubstituteBindings::class, 39 | ], 40 | 41 | 'api' => [ 42 | // \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class, 43 | 'throttle:api', 44 | \Illuminate\Routing\Middleware\SubstituteBindings::class, 45 | ], 46 | ]; 47 | 48 | /** 49 | * The application's route middleware. 50 | * 51 | * These middleware may be assigned to groups or used individually. 52 | * 53 | * @var array 54 | */ 55 | protected $routeMiddleware = [ 56 | 'auth' => \App\Http\Middleware\Authenticate::class, 57 | 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, 58 | 'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class, 59 | 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class, 60 | 'can' => \Illuminate\Auth\Middleware\Authorize::class, 61 | 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, 62 | 'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class, 63 | 'signed' => \App\Http\Middleware\ValidateSignature::class, 64 | 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 65 | 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, 66 | 'admin.only' => \App\Http\Middleware\AdminOnly::class, 67 | 'staff.only' => \App\Http\Middleware\StaffOnly::class, 68 | ]; 69 | } 70 | -------------------------------------------------------------------------------- /application/app/Http/Middleware/AdminOnly.php: -------------------------------------------------------------------------------- 1 | expectsJson()) { 18 | return route('login'); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /application/app/Http/Middleware/EncryptCookies.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | protected $except = [ 15 | // 16 | ]; 17 | } 18 | -------------------------------------------------------------------------------- /application/app/Http/Middleware/PreventRequestsDuringMaintenance.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | protected $except = [ 15 | // 16 | ]; 17 | } 18 | -------------------------------------------------------------------------------- /application/app/Http/Middleware/RedirectIfAuthenticated.php: -------------------------------------------------------------------------------- 1 | check()) { 26 | return redirect(RouteServiceProvider::HOME); 27 | } 28 | } 29 | 30 | return $next($request); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /application/app/Http/Middleware/StaffOnly.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | protected $except = [ 15 | 'current_password', 16 | 'password', 17 | 'password_confirmation', 18 | ]; 19 | } 20 | -------------------------------------------------------------------------------- /application/app/Http/Middleware/TrustHosts.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | public function hosts() 15 | { 16 | return [ 17 | $this->allSubdomainsOfApplicationUrl(), 18 | ]; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /application/app/Http/Middleware/TrustProxies.php: -------------------------------------------------------------------------------- 1 | |string|null 14 | */ 15 | protected $proxies; 16 | 17 | /** 18 | * The headers that should be used to detect proxies. 19 | * 20 | * @var int 21 | */ 22 | protected $headers = 23 | Request::HEADER_X_FORWARDED_FOR | 24 | Request::HEADER_X_FORWARDED_HOST | 25 | Request::HEADER_X_FORWARDED_PORT | 26 | Request::HEADER_X_FORWARDED_PROTO | 27 | Request::HEADER_X_FORWARDED_AWS_ELB; 28 | } 29 | -------------------------------------------------------------------------------- /application/app/Http/Middleware/ValidateSignature.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | protected $except = [ 15 | // 'fbclid', 16 | // 'utm_campaign', 17 | // 'utm_content', 18 | // 'utm_medium', 19 | // 'utm_source', 20 | // 'utm_term', 21 | ]; 22 | } 23 | -------------------------------------------------------------------------------- /application/app/Http/Middleware/VerifyCsrfToken.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | protected $except = [ 15 | // 16 | ]; 17 | } 18 | -------------------------------------------------------------------------------- /application/app/Http/Traits/JsonResponseTrait.php: -------------------------------------------------------------------------------- 1 | response(['data' => $data], $statusCode); 17 | } 18 | 19 | public function errorResponse(mixed $data, int $statusCode = 500): JsonResponse 20 | { 21 | return $this->response(['error' => $data], $statusCode); 22 | } 23 | 24 | public function jsonException(string $errorReason, Throwable $exception): JsonResponse 25 | { 26 | if (!$exception instanceof AppException && !$exception instanceof ValidationException) { 27 | Log::error($errorReason, [ 28 | 'error' => $exception->getMessage(), 29 | 'file' => basename($exception->getFile()), 30 | 'line' => $exception->getLine(), 31 | ]); 32 | } 33 | 34 | if ($exception instanceof ValidationException) { 35 | return $this->errorResponse( 36 | $exception->validator->errors()->all(), 37 | Response::HTTP_BAD_REQUEST, 38 | ); 39 | } 40 | 41 | return $this->errorResponse($exception instanceof AppException 42 | ? $exception->getMessage() 43 | : $errorReason 44 | ); 45 | } 46 | 47 | private function response(array $data, int $statusCode): JsonResponse 48 | { 49 | return response() 50 | ->json($data, $statusCode); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /application/app/Models/Event.php: -------------------------------------------------------------------------------- 1 | 33 | */ 34 | protected $casts = [ 35 | 'policyIds' => Json::class, 36 | ]; 37 | 38 | /** 39 | * The attributes that should be cast. 40 | * 41 | * @var array 42 | */ 43 | protected $dates = [ 44 | 'startDateTime', 45 | 'endDateTime', 46 | ]; 47 | 48 | public function tickets(): HasMany { 49 | return $this->hasMany(Ticket::class, 'eventId'); 50 | } 51 | 52 | public function description() { 53 | $description = ""; 54 | if ($this->eventDate) { 55 | $description .= date('l, F jS, Y', strtotime($this->eventDate)); 56 | } 57 | 58 | if ($this->eventStart && $this->eventEnd) { 59 | $description .= " " . $this->eventStart . " to " . $this->eventEnd . "."; 60 | } else if ($this->eventStart) { 61 | $description .= " " . $this->eventStart; 62 | } else if ($this->eventEnd) { 63 | $description .= " until " . $this->eventEnd; 64 | } 65 | 66 | if ($this->location) { 67 | $description .= " " . $this->location; 68 | } 69 | 70 | return $description; 71 | 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /application/app/Models/Ticket.php: -------------------------------------------------------------------------------- 1 | 32 | */ 33 | protected $dates = [ 34 | 'checkInTime', 35 | ]; 36 | 37 | public function event(): BelongsTo { 38 | return $this->belongsTo(Event::class, 'eventId'); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /application/app/Models/User.php: -------------------------------------------------------------------------------- 1 | 20 | */ 21 | protected $fillable = [ 22 | 'roles', 23 | 'name', 24 | 'email', 25 | 'password', 26 | ]; 27 | 28 | /** 29 | * The attributes that should be hidden for serialization. 30 | * 31 | * @var array 32 | */ 33 | protected $hidden = [ 34 | 'password', 35 | 'remember_token', 36 | ]; 37 | 38 | /** 39 | * The attributes that should be cast. 40 | * 41 | * @var array 42 | */ 43 | protected $casts = [ 44 | 'email_verified_at' => 'datetime', 45 | 'roles' => Json::class, 46 | 'password' => Hash::class, 47 | ]; 48 | } 49 | -------------------------------------------------------------------------------- /application/app/Providers/AppServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->bind(ICardanoClient::class, config('gatekeeper.cardanoClient')); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /application/app/Providers/AuthServiceProvider.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | protected $policies = [ 16 | // 'App\Models\Model' => 'App\Policies\ModelPolicy', 17 | ]; 18 | 19 | /** 20 | * Register any authentication / authorization services. 21 | * 22 | * @return void 23 | */ 24 | public function boot() 25 | { 26 | $this->registerPolicies(); 27 | 28 | // 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /application/app/Providers/BroadcastServiceProvider.php: -------------------------------------------------------------------------------- 1 | > 16 | */ 17 | protected $listen = [ 18 | Registered::class => [ 19 | SendEmailVerificationNotification::class, 20 | ], 21 | ]; 22 | 23 | /** 24 | * Register any events for your application. 25 | * 26 | * @return void 27 | */ 28 | public function boot() 29 | { 30 | // 31 | } 32 | 33 | /** 34 | * Determine if events and listeners should be automatically discovered. 35 | * 36 | * @return bool 37 | */ 38 | public function shouldDiscoverEvents() 39 | { 40 | return false; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /application/app/Providers/RouteServiceProvider.php: -------------------------------------------------------------------------------- 1 | configureRateLimiting(); 30 | 31 | $this->routes(function () { 32 | Route::middleware('api') 33 | ->prefix('api') 34 | ->group(base_path('routes/api.php')); 35 | 36 | Route::middleware('web') 37 | ->group(base_path('routes/web.php')); 38 | }); 39 | } 40 | 41 | /** 42 | * Configure the rate limiters for the application. 43 | * 44 | * @return void 45 | */ 46 | protected function configureRateLimiting(): void 47 | { 48 | RateLimiter::for('api', static function (Request $request) { 49 | return $request->route()->getName() === 'api.v1.asset-info' 50 | ? Limit::perMinute(120)->by($request->user()?->id ?: $request->ip()) 51 | : Limit::perMinute(60)->by($request->user()?->id ?: $request->ip()); 52 | }); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /application/app/Services/EventService.php: -------------------------------------------------------------------------------- 1 | findById($payload['event_id'])) { 39 | throw new AppException(trans('Event not found')); 40 | } 41 | 42 | $payload['policyIds'] = array_filter(preg_split("/\r\n|\n|\r/", $payload['policyIds'])); 43 | 44 | $validationRules = [ 45 | 'name' => [ 46 | 'required', 47 | 'min:3', 48 | ], 49 | 'policyIds' => [ 50 | 'required', 51 | 'array', 52 | 'min:1', 53 | ], 54 | 'endDateTime' => [ 55 | 'required', 56 | 'date', 57 | ], 58 | 'startDateTime' => ['date'], 59 | 'hodlAsset' => ['integer'], 60 | 'nonceValidForMinutes' => [ 61 | 'required', 62 | 'integer', 63 | 'min:5', 64 | ], 65 | 'location' => ['string'], 66 | 'eventStart' => ['string'], 67 | 'eventEnd' => ['string'], 68 | 'eventDate' => ['date'], 69 | 'image' => ['string'], 70 | ]; 71 | 72 | $validator = Validator::make($payload, $validationRules); 73 | 74 | if ($validator->fails()) { 75 | throw new AppException(sprintf('%s: %s', trans('validation errors'), implode(' ', $validator->errors() 76 | ->all()))); 77 | } 78 | 79 | if (!$event) { 80 | $event = new Event; 81 | } 82 | 83 | $validPayload = $validator->validated(); 84 | 85 | if (empty($validPayload['hodlAsset'])) { 86 | $validPayload['hodlAsset'] = false; 87 | } 88 | 89 | $event->fill($validPayload); 90 | $event->save(); 91 | } 92 | 93 | public function findById(int $eventId): ?Event { 94 | return Event::where('id', $eventId) 95 | ->first(); 96 | } 97 | 98 | public function findByUUID(string $uuid): ?Event { 99 | return Event::where('uuid', $uuid) 100 | ->first(); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /application/app/Services/TicketService.php: -------------------------------------------------------------------------------- 1 | where('policyId', $policyId) 15 | ->where('assetId', $assetId) 16 | ->where('stakeKey', $stakeKey) 17 | ->latest('id') 18 | ->first() 19 | ; 20 | } 21 | 22 | public function createNewTicket(int $eventId, string $policyId, string $assetId, string $stakeKey): Ticket 23 | { 24 | $ticket = new Ticket; 25 | 26 | $ticket->fill([ 27 | 'eventId' => $eventId, 28 | 'policyId' => $policyId, 29 | 'assetId' => $assetId, 30 | 'stakeKey' => $stakeKey, 31 | 'signatureNonce' => Uuid::uuid4()->getBytes(), 32 | ]); 33 | 34 | $ticket->save(); 35 | 36 | return $ticket; 37 | } 38 | 39 | public function setTicketNonceAndSignature(Ticket $ticket, string $signature): void 40 | { 41 | $ticket->update([ 42 | 'ticketNonce' => Uuid::uuid4()->getBytes(), 43 | 'signature' => $signature, 44 | ]); 45 | } 46 | 47 | public function removeOldAttempts(Ticket $currentValidTicket): void 48 | { 49 | Ticket::query() 50 | ->where('eventId', $currentValidTicket->eventId) 51 | ->where('policyId', $currentValidTicket->policyId) 52 | ->where('assetId', $currentValidTicket->assetId) 53 | ->where('id', '<>', $currentValidTicket->id) 54 | ->delete(); 55 | } 56 | 57 | public function findTicketByQRCode(int $eventId, string $assetId, string $ticketNonce): ?Ticket 58 | { 59 | return Ticket::where('eventId', $eventId) 60 | ->where('assetId', $assetId) 61 | ->where('ticketNonce', Uuid::fromString($ticketNonce)->getBytes()) 62 | ->first(); 63 | } 64 | 65 | public function checkInTicket(Ticket $ticket, int $userId): void 66 | { 67 | $ticket->fill([ 68 | 'isCheckedIn' => true, 69 | 'checkInTime' => Carbon::now()->toDateTimeString(), 70 | 'checkInUser' => $userId, 71 | ]); 72 | 73 | $ticket->save(); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /application/app/Services/UserService.php: -------------------------------------------------------------------------------- 1 | ['required', 'min:1', 'distinct', 'in:' . implode(',', validRoles())], 25 | 'name' => ['required', 'min:3'], 26 | 'email' => ['required', 'email', 'unique:users,email'], 27 | 'password' => $this->passwordRules(), 28 | ], 29 | ); 30 | 31 | if ($validator->fails()) { 32 | throw new AppException(sprintf( 33 | '%s: %s', 34 | trans('validation errors'), 35 | implode(' ', $validator->errors()->all()) 36 | )); 37 | } 38 | 39 | $user = new User; 40 | $user->fill($validator->validated()); 41 | $user->save(); 42 | 43 | return $user; 44 | } 45 | 46 | public function allUsers(?string $search = null): Collection 47 | { 48 | if (!empty($search)) { 49 | return User::where('name', 'like', '%' . $search . '%') 50 | ->orWhere('email', 'like', '%' . $search . '%') 51 | ->orWhereJsonContains('roles', $search) 52 | ->get(); 53 | } 54 | 55 | return User::all(); 56 | } 57 | 58 | public function findById(int $userId): ?User 59 | { 60 | return User::where('id', $userId) 61 | ->first(); 62 | } 63 | 64 | public function findByEmail(string $userEmail): ?User 65 | { 66 | return User::where('email', $userEmail) 67 | ->first(); 68 | } 69 | 70 | /** 71 | * @throws AppException 72 | * @throws ValidationException 73 | */ 74 | public function save(array $payload): void 75 | { 76 | $user = null; 77 | if (!empty($payload['user_id']) && !$user = $this->findById($payload['user_id'])) { 78 | throw new AppException(trans('User not found')); 79 | } 80 | 81 | $validationRules = [ 82 | 'roles.*' => ['required', 'distinct', 'in:' . implode(',', validRoles())], 83 | 'name' => ['required', 'min:3'], 84 | 'email' => ['required', 'email', 'unique:users,email' . ($user ? ',' . $user->id : '')], 85 | ]; 86 | if (!empty($payload['password'])) { 87 | $validationRules['password'] = $this->passwordRules(); 88 | } 89 | 90 | $validator = Validator::make($payload, $validationRules); 91 | 92 | if ($validator->fails()) { 93 | throw new AppException(sprintf( 94 | '%s: %s', 95 | trans('validation errors'), 96 | implode(' ', $validator->errors()->all()) 97 | )); 98 | } 99 | 100 | if (!$user) { 101 | $user = new User; 102 | } 103 | $validPayload = $validator->validated(); 104 | if (!isset($validPayload['roles'])) { 105 | $validPayload['roles'] = []; 106 | } 107 | $user->fill($validPayload); 108 | $user->save(); 109 | } 110 | 111 | /** 112 | * @throws AppException 113 | */ 114 | public function changePassword(User $user, string $newAccountPassword): void 115 | { 116 | $validator = Validator::make( 117 | ['password' => $newAccountPassword], 118 | ['password' => $this->passwordRules()], 119 | ); 120 | 121 | if ($validator->fails()) { 122 | throw new AppException(sprintf( 123 | '%s: %s', 124 | trans('validation errors'), 125 | implode(' ', $validator->errors()->all()) 126 | )); 127 | } 128 | 129 | $user->fill([ 130 | 'password' => $newAccountPassword, 131 | ]); 132 | 133 | $user->save(); 134 | } 135 | 136 | public function passwordRules(): Password 137 | { 138 | return app()->environment('local') 139 | ? Password::min(6) // Simple password rule for local dev environment only 140 | : Password::min(8) // Complex password rule for all other environments 141 | ->letters() 142 | ->mixedCase() 143 | ->numbers() 144 | ->symbols() 145 | ->uncompromised() 146 | ; 147 | } 148 | 149 | /** 150 | * @throws AppException 151 | */ 152 | public function validateCurrentPassword(int $userId, string $currentPassword): void 153 | { 154 | $user = $this->findById($userId); 155 | 156 | if (!$user) { 157 | throw new AppException(trans('User not found')); 158 | } 159 | 160 | if (!Hash::check($currentPassword, $user->password)) { 161 | throw new AppException(trans('Current password is incorrect')); 162 | } 163 | } 164 | 165 | /** 166 | * @throws AppException 167 | */ 168 | public function updateAccount(int $userId, string $accountName, ?string $newPassword = null): void 169 | { 170 | $user = $this->findById($userId); 171 | 172 | if (!$user) { 173 | throw new AppException(trans('User not found')); 174 | } 175 | 176 | $payload = [ 177 | 'name' => $accountName, 178 | ]; 179 | 180 | if (!empty($newPassword)) { 181 | $payload['password'] = $newPassword; 182 | } 183 | 184 | $user->fill($payload); 185 | $user->save(); 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /application/app/ThirdParty/CardanoClients/BlockFrostClient.php: -------------------------------------------------------------------------------- 1 | call( 18 | self::HTTP_REQUEST_GET, 19 | "assets/{$assetId}", 20 | ); 21 | } 22 | 23 | /** 24 | * @throws AppException 25 | * @throws \JsonException 26 | */ 27 | public function assetHodled(string $policyId, string $assetId, string $stakeKey): bool 28 | { 29 | $assetAddresses = $this->call( 30 | self::HTTP_REQUEST_GET, 31 | "assets/{$policyId}{$assetId}/addresses", 32 | ); 33 | 34 | if (!count($assetAddresses)) { 35 | throw new AppException(trans('Asset not found in any wallets')); 36 | } 37 | 38 | error_log("Blockfrost response:\r\n".print_r($assetAddresses,true)); 39 | 40 | $firstAssetAddress = $assetAddresses[0]['address']; 41 | 42 | $addressInfo = $this->call( 43 | self::HTTP_REQUEST_GET, 44 | "addresses/{$firstAssetAddress}", 45 | ); 46 | 47 | // error_log("Address details:\r\n".json_encode($addressInfo, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES)); 48 | error_log("Got address details {$stakeKey} {$addressInfo['stake_address']}"); 49 | 50 | return $addressInfo['stake_address'] === $stakeKey; 51 | } 52 | 53 | /** 54 | * @throws AppException 55 | * @throws \JsonException 56 | */ 57 | private function call( 58 | string $requestMethod, 59 | string $requestUri, 60 | string $payload = null, 61 | array $headers = [] 62 | ): ?array 63 | { 64 | $curl = curl_init(); 65 | $options = [ 66 | CURLOPT_URL => $this->buildEndpoint($requestUri), 67 | CURLOPT_RETURNTRANSFER => true, 68 | CURLOPT_ENCODING => '', 69 | CURLOPT_MAXREDIRS => 10, 70 | CURLOPT_TIMEOUT => 10, 71 | CURLOPT_FOLLOWLOCATION => true, 72 | CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, 73 | CURLOPT_CUSTOMREQUEST => $requestMethod, 74 | CURLOPT_HTTPHEADER => array_merge( 75 | [ 76 | 'project_id: ' . getenv('BLOCKFROST_PROJECT_ID') 77 | ], 78 | $headers 79 | ), 80 | ]; 81 | if ($requestMethod === self::HTTP_REQUEST_POST) { 82 | $options[CURLOPT_POSTFIELDS] = $payload; 83 | } 84 | curl_setopt_array($curl, $options); 85 | $response = json_decode(curl_exec($curl), true, 512, JSON_THROW_ON_ERROR); 86 | curl_close($curl); 87 | 88 | if (isset($response['error']) && (int) $response['status_code'] !== Response::HTTP_NOT_FOUND) { 89 | $error = trans('BlockFrost api error'); 90 | 91 | Log::error($error, [ 92 | 'request_method' => $requestMethod, 93 | 'request_uri' => $requestUri, 94 | 'status_code' => $response['status_code'] ?? -1, 95 | 'api_error' => $response['error'] ?? 'unknown', 96 | 'api_message' => $response['message'] ?? 'unknown', 97 | ]); 98 | 99 | throw new AppException($error); 100 | } 101 | 102 | if (isset($response['status_code']) && $response['status_code'] === Response::HTTP_NOT_FOUND) { 103 | return null; 104 | } 105 | 106 | return $response; 107 | } 108 | 109 | private function buildEndpoint(string $requestUri): string 110 | { 111 | return sprintf( 112 | 'https://cardano-%s.blockfrost.io/api/v0/%s', 113 | env('CARDANO_NETWORK'), 114 | $requestUri 115 | ); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /application/app/ThirdParty/CardanoClients/ICardanoClient.php: -------------------------------------------------------------------------------- 1 | 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 | -------------------------------------------------------------------------------- /application/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 | -------------------------------------------------------------------------------- /application/bootstrap/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /application/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "latheesan-k/gatekeeper", 3 | "type": "project", 4 | "description": "An open source ticketing solution using Cardano Native Assets. Bridging Web 3.0 to IRL via Blockchain.", 5 | "keywords": ["gatekeeper", "cardano", "ticketing", "web3", "latheesan kanesamoorthy", "adam dean"], 6 | "license": "MIT", 7 | "require": { 8 | "php": "^8.0.2", 9 | "ext-curl": "*", 10 | "endroid/qr-code": "^4.5", 11 | "guzzlehttp/guzzle": "^7.2", 12 | "laravel/framework": "^9.19", 13 | "laravel/sanctum": "^3.0", 14 | "laravel/tinker": "^2.7", 15 | "laravel/ui": "^4.0" 16 | }, 17 | "require-dev": { 18 | "fakerphp/faker": "^1.9.1", 19 | "mockery/mockery": "^1.4.4", 20 | "nunomaduro/collision": "^6.1", 21 | "phpunit/phpunit": "^9.5.10", 22 | "spatie/laravel-ignition": "^1.0" 23 | }, 24 | "autoload": { 25 | "psr-4": { 26 | "App\\": "app/", 27 | "Database\\Factories\\": "database/factories/", 28 | "Database\\Seeders\\": "database/seeders/" 29 | }, 30 | "files": [ 31 | "app/Constants.php", 32 | "app/Helpers.php" 33 | ] 34 | }, 35 | "autoload-dev": { 36 | "psr-4": { 37 | "Tests\\": "tests/" 38 | } 39 | }, 40 | "scripts": { 41 | "post-autoload-dump": [ 42 | "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump", 43 | "@php artisan package:discover --ansi" 44 | ], 45 | "post-update-cmd": [ 46 | "@php artisan vendor:publish --tag=laravel-assets --ansi --force" 47 | ], 48 | "post-root-package-install": [ 49 | "@php -r \"file_exists('.env') || copy('.env.example', '.env');\"" 50 | ], 51 | "post-create-project-cmd": [ 52 | "@php artisan key:generate --ansi" 53 | ] 54 | }, 55 | "extra": { 56 | "laravel": { 57 | "dont-discover": [] 58 | } 59 | }, 60 | "config": { 61 | "optimize-autoloader": true, 62 | "preferred-install": "dist", 63 | "sort-packages": true, 64 | "allow-plugins": { 65 | "pestphp/pest-plugin": true 66 | } 67 | }, 68 | "minimum-stability": "dev", 69 | "prefer-stable": true 70 | } 71 | -------------------------------------------------------------------------------- /application/config/auth.php: -------------------------------------------------------------------------------- 1 | [ 17 | 'guard' => 'web', 18 | 'passwords' => 'users', 19 | ], 20 | 21 | /* 22 | |-------------------------------------------------------------------------- 23 | | Authentication Guards 24 | |-------------------------------------------------------------------------- 25 | | 26 | | Next, you may define every authentication guard for your application. 27 | | Of course, a great default configuration has been defined for you 28 | | here which uses session storage and the Eloquent user provider. 29 | | 30 | | All authentication drivers have a user provider. This defines how the 31 | | users are actually retrieved out of your database or other storage 32 | | mechanisms used by this application to persist your user's data. 33 | | 34 | | Supported: "session" 35 | | 36 | */ 37 | 38 | 'guards' => [ 39 | 'web' => [ 40 | 'driver' => 'session', 41 | 'provider' => 'users', 42 | ], 43 | ], 44 | 45 | /* 46 | |-------------------------------------------------------------------------- 47 | | User Providers 48 | |-------------------------------------------------------------------------- 49 | | 50 | | All authentication drivers have a user provider. This defines how the 51 | | users are actually retrieved out of your database or other storage 52 | | mechanisms used by this application to persist your user's data. 53 | | 54 | | If you have multiple user tables or models you may configure multiple 55 | | sources which represent each model / table. These sources may then 56 | | be assigned to any extra authentication guards you have defined. 57 | | 58 | | Supported: "database", "eloquent" 59 | | 60 | */ 61 | 62 | 'providers' => [ 63 | 'users' => [ 64 | 'driver' => 'eloquent', 65 | 'model' => App\Models\User::class, 66 | ], 67 | 68 | // 'users' => [ 69 | // 'driver' => 'database', 70 | // 'table' => 'users', 71 | // ], 72 | ], 73 | 74 | /* 75 | |-------------------------------------------------------------------------- 76 | | Resetting Passwords 77 | |-------------------------------------------------------------------------- 78 | | 79 | | You may specify multiple password reset configurations if you have more 80 | | than one user table or model in the application and you want to have 81 | | separate password reset settings based on the specific user types. 82 | | 83 | | The expire time is the number of minutes that each reset token will be 84 | | considered valid. This security feature keeps tokens short-lived so 85 | | they have less time to be guessed. You may change this as needed. 86 | | 87 | */ 88 | 89 | 'passwords' => [ 90 | 'users' => [ 91 | 'provider' => 'users', 92 | 'table' => 'password_resets', 93 | 'expire' => 60, 94 | 'throttle' => 60, 95 | ], 96 | ], 97 | 98 | /* 99 | |-------------------------------------------------------------------------- 100 | | Password Confirmation Timeout 101 | |-------------------------------------------------------------------------- 102 | | 103 | | Here you may define the amount of seconds before a password confirmation 104 | | times out and the user is prompted to re-enter their password via the 105 | | confirmation screen. By default, the timeout lasts for three hours. 106 | | 107 | */ 108 | 109 | 'password_timeout' => 10800, 110 | 111 | ]; 112 | -------------------------------------------------------------------------------- /application/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 | 'host' => env('PUSHER_HOST', 'api-'.env('PUSHER_APP_CLUSTER', 'mt1').'.pusher.com') ?: 'api-'.env('PUSHER_APP_CLUSTER', 'mt1').'.pusher.com', 40 | 'port' => env('PUSHER_PORT', 443), 41 | 'scheme' => env('PUSHER_SCHEME', 'https'), 42 | 'encrypted' => true, 43 | 'useTLS' => env('PUSHER_SCHEME', 'https') === 'https', 44 | ], 45 | 'client_options' => [ 46 | // Guzzle client options: https://docs.guzzlephp.org/en/stable/request-options.html 47 | ], 48 | ], 49 | 50 | 'ably' => [ 51 | 'driver' => 'ably', 52 | 'key' => env('ABLY_KEY'), 53 | ], 54 | 55 | 'redis' => [ 56 | 'driver' => 'redis', 57 | 'connection' => 'default', 58 | ], 59 | 60 | 'log' => [ 61 | 'driver' => 'log', 62 | ], 63 | 64 | 'null' => [ 65 | 'driver' => 'null', 66 | ], 67 | 68 | ], 69 | 70 | ]; 71 | -------------------------------------------------------------------------------- /application/config/cache.php: -------------------------------------------------------------------------------- 1 | env('CACHE_DRIVER', 'file'), 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Cache Stores 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Here you may define all of the cache "stores" for your application as 26 | | well as their drivers. You may even define multiple stores for the 27 | | same cache driver to group types of items stored in your caches. 28 | | 29 | | Supported drivers: "apc", "array", "database", "file", 30 | | "memcached", "redis", "dynamodb", "octane", "null" 31 | | 32 | */ 33 | 34 | 'stores' => [ 35 | 36 | 'apc' => [ 37 | 'driver' => 'apc', 38 | ], 39 | 40 | 'array' => [ 41 | 'driver' => 'array', 42 | 'serialize' => false, 43 | ], 44 | 45 | 'database' => [ 46 | 'driver' => 'database', 47 | 'table' => 'cache', 48 | 'connection' => null, 49 | 'lock_connection' => null, 50 | ], 51 | 52 | 'file' => [ 53 | 'driver' => 'file', 54 | 'path' => storage_path('framework/cache/data'), 55 | ], 56 | 57 | 'memcached' => [ 58 | 'driver' => 'memcached', 59 | 'persistent_id' => env('MEMCACHED_PERSISTENT_ID'), 60 | 'sasl' => [ 61 | env('MEMCACHED_USERNAME'), 62 | env('MEMCACHED_PASSWORD'), 63 | ], 64 | 'options' => [ 65 | // Memcached::OPT_CONNECT_TIMEOUT => 2000, 66 | ], 67 | 'servers' => [ 68 | [ 69 | 'host' => env('MEMCACHED_HOST', '127.0.0.1'), 70 | 'port' => env('MEMCACHED_PORT', 11211), 71 | 'weight' => 100, 72 | ], 73 | ], 74 | ], 75 | 76 | 'redis' => [ 77 | 'driver' => 'redis', 78 | 'connection' => 'cache', 79 | 'lock_connection' => 'default', 80 | ], 81 | 82 | 'dynamodb' => [ 83 | 'driver' => 'dynamodb', 84 | 'key' => env('AWS_ACCESS_KEY_ID'), 85 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 86 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 87 | 'table' => env('DYNAMODB_CACHE_TABLE', 'cache'), 88 | 'endpoint' => env('DYNAMODB_ENDPOINT'), 89 | ], 90 | 91 | 'octane' => [ 92 | 'driver' => 'octane', 93 | ], 94 | 95 | ], 96 | 97 | /* 98 | |-------------------------------------------------------------------------- 99 | | Cache Key Prefix 100 | |-------------------------------------------------------------------------- 101 | | 102 | | When utilizing the APC, database, memcached, Redis, or DynamoDB cache 103 | | stores there might be other applications using the same cache. For 104 | | that reason, you may prefix every cache key to avoid collisions. 105 | | 106 | */ 107 | 108 | 'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache_'), 109 | 110 | ]; 111 | -------------------------------------------------------------------------------- /application/config/cors.php: -------------------------------------------------------------------------------- 1 | ['api/*', 'sanctum/csrf-cookie'], 19 | 20 | 'allowed_methods' => ['*'], 21 | 22 | 'allowed_origins' => ['*'], 23 | 24 | 'allowed_origins_patterns' => [], 25 | 26 | 'allowed_headers' => ['*'], 27 | 28 | 'exposed_headers' => [], 29 | 30 | 'max_age' => 0, 31 | 32 | 'supports_credentials' => false, 33 | 34 | ]; 35 | -------------------------------------------------------------------------------- /application/config/database.php: -------------------------------------------------------------------------------- 1 | env('DB_CONNECTION', 'mysql'), 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Database Connections 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Here are each of the database connections setup for your application. 26 | | Of course, examples of configuring each database platform that is 27 | | supported by Laravel is shown below to make development simple. 28 | | 29 | | 30 | | All database work in Laravel is done through the PHP PDO facilities 31 | | so make sure you have the driver for your particular database of 32 | | choice installed on your machine before you begin development. 33 | | 34 | */ 35 | 36 | 'connections' => [ 37 | 38 | 'sqlite' => [ 39 | 'driver' => 'sqlite', 40 | 'url' => env('DATABASE_URL'), 41 | 'database' => env('DB_DATABASE', database_path('database.sqlite')), 42 | 'prefix' => '', 43 | 'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true), 44 | ], 45 | 46 | 'mysql' => [ 47 | 'driver' => 'mysql', 48 | 'url' => env('DATABASE_URL'), 49 | 'host' => env('DB_HOST', '127.0.0.1'), 50 | 'port' => env('DB_PORT', '3306'), 51 | 'database' => env('DB_DATABASE', 'forge'), 52 | 'username' => env('DB_USERNAME', 'forge'), 53 | 'password' => env('DB_PASSWORD', ''), 54 | 'unix_socket' => env('DB_SOCKET', ''), 55 | 'charset' => 'utf8mb4', 56 | 'collation' => 'utf8mb4_unicode_ci', 57 | 'prefix' => '', 58 | 'prefix_indexes' => true, 59 | 'strict' => true, 60 | 'engine' => null, 61 | 'options' => extension_loaded('pdo_mysql') ? array_filter([ 62 | PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), 63 | ]) : [], 64 | ], 65 | 66 | 'pgsql' => [ 67 | 'driver' => 'pgsql', 68 | 'url' => env('DATABASE_URL'), 69 | 'host' => env('DB_HOST', '127.0.0.1'), 70 | 'port' => env('DB_PORT', '5432'), 71 | 'database' => env('DB_DATABASE', 'forge'), 72 | 'username' => env('DB_USERNAME', 'forge'), 73 | 'password' => env('DB_PASSWORD', ''), 74 | 'charset' => 'utf8', 75 | 'prefix' => '', 76 | 'prefix_indexes' => true, 77 | 'search_path' => 'public', 78 | 'sslmode' => 'prefer', 79 | ], 80 | 81 | 'sqlsrv' => [ 82 | 'driver' => 'sqlsrv', 83 | 'url' => env('DATABASE_URL'), 84 | 'host' => env('DB_HOST', 'localhost'), 85 | 'port' => env('DB_PORT', '1433'), 86 | 'database' => env('DB_DATABASE', 'forge'), 87 | 'username' => env('DB_USERNAME', 'forge'), 88 | 'password' => env('DB_PASSWORD', ''), 89 | 'charset' => 'utf8', 90 | 'prefix' => '', 91 | 'prefix_indexes' => true, 92 | // 'encrypt' => env('DB_ENCRYPT', 'yes'), 93 | // 'trust_server_certificate' => env('DB_TRUST_SERVER_CERTIFICATE', 'false'), 94 | ], 95 | 96 | ], 97 | 98 | /* 99 | |-------------------------------------------------------------------------- 100 | | Migration Repository Table 101 | |-------------------------------------------------------------------------- 102 | | 103 | | This table keeps track of all the migrations that have already run for 104 | | your application. Using this information, we can determine which of 105 | | the migrations on disk haven't actually been run in the database. 106 | | 107 | */ 108 | 109 | 'migrations' => 'migrations', 110 | 111 | /* 112 | |-------------------------------------------------------------------------- 113 | | Redis Databases 114 | |-------------------------------------------------------------------------- 115 | | 116 | | Redis is an open source, fast, and advanced key-value store that also 117 | | provides a richer body of commands than a typical key-value system 118 | | such as APC or Memcached. Laravel makes it easy to dig right in. 119 | | 120 | */ 121 | 122 | 'redis' => [ 123 | 124 | 'client' => env('REDIS_CLIENT', 'phpredis'), 125 | 126 | 'options' => [ 127 | 'cluster' => env('REDIS_CLUSTER', 'redis'), 128 | 'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'), 129 | ], 130 | 131 | 'default' => [ 132 | 'url' => env('REDIS_URL'), 133 | 'host' => env('REDIS_HOST', '127.0.0.1'), 134 | 'username' => env('REDIS_USERNAME'), 135 | 'password' => env('REDIS_PASSWORD'), 136 | 'port' => env('REDIS_PORT', '6379'), 137 | 'database' => env('REDIS_DB', '0'), 138 | ], 139 | 140 | 'cache' => [ 141 | 'url' => env('REDIS_URL'), 142 | 'host' => env('REDIS_HOST', '127.0.0.1'), 143 | 'username' => env('REDIS_USERNAME'), 144 | 'password' => env('REDIS_PASSWORD'), 145 | 'port' => env('REDIS_PORT', '6379'), 146 | 'database' => env('REDIS_CACHE_DB', '1'), 147 | ], 148 | 149 | ], 150 | 151 | ]; 152 | -------------------------------------------------------------------------------- /application/config/filesystems.php: -------------------------------------------------------------------------------- 1 | env('FILESYSTEM_DISK', 'local'), 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Filesystem Disks 21 | |-------------------------------------------------------------------------- 22 | | 23 | | Here you may configure as many filesystem "disks" as you wish, and you 24 | | may even configure multiple disks of the same driver. Defaults have 25 | | been set up for each driver as an example of the required values. 26 | | 27 | | Supported Drivers: "local", "ftp", "sftp", "s3" 28 | | 29 | */ 30 | 31 | 'disks' => [ 32 | 33 | 'local' => [ 34 | 'driver' => 'local', 35 | 'root' => storage_path('app'), 36 | 'throw' => false, 37 | ], 38 | 39 | 'public' => [ 40 | 'driver' => 'local', 41 | 'root' => storage_path('app/public'), 42 | 'url' => env('APP_URL') . '/storage', 43 | 'visibility' => 'public', 44 | 'throw' => false, 45 | ], 46 | 47 | 's3' => [ 48 | 'driver' => 's3', 49 | 'key' => env('AWS_ACCESS_KEY_ID'), 50 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 51 | 'region' => env('AWS_DEFAULT_REGION'), 52 | 'bucket' => env('AWS_BUCKET'), 53 | 'url' => env('AWS_URL'), 54 | 'endpoint' => env('AWS_ENDPOINT'), 55 | 'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false), 56 | 'throw' => false, 57 | ], 58 | 59 | ], 60 | 61 | /* 62 | |-------------------------------------------------------------------------- 63 | | Symbolic Links 64 | |-------------------------------------------------------------------------- 65 | | 66 | | Here you may configure the symbolic links that will be created when the 67 | | `storage:link` Artisan command is executed. The array keys should be 68 | | the locations of the links and the values should be their targets. 69 | | 70 | */ 71 | 72 | 'links' => [ 73 | public_path('storage') => storage_path('app/public'), 74 | public_path('public') => storage_path('app/public'), 75 | // public_path('images') => storage_path('app/images'), 76 | ], 77 | 78 | ]; 79 | -------------------------------------------------------------------------------- /application/config/gatekeeper.php: -------------------------------------------------------------------------------- 1 | BlockFrostClient::class, 8 | 9 | ]; 10 | -------------------------------------------------------------------------------- /application/config/hashing.php: -------------------------------------------------------------------------------- 1 | 'bcrypt', 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Bcrypt Options 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Here you may specify the configuration options that should be used when 26 | | passwords are hashed using the Bcrypt algorithm. This will allow you 27 | | to control the amount of time it takes to hash the given password. 28 | | 29 | */ 30 | 31 | 'bcrypt' => [ 32 | 'rounds' => env('BCRYPT_ROUNDS', 10), 33 | ], 34 | 35 | /* 36 | |-------------------------------------------------------------------------- 37 | | Argon Options 38 | |-------------------------------------------------------------------------- 39 | | 40 | | Here you may specify the configuration options that should be used when 41 | | passwords are hashed using the Argon algorithm. These will allow you 42 | | to control the amount of time it takes to hash the given password. 43 | | 44 | */ 45 | 46 | 'argon' => [ 47 | 'memory' => 65536, 48 | 'threads' => 1, 49 | 'time' => 4, 50 | ], 51 | 52 | ]; 53 | -------------------------------------------------------------------------------- /application/config/logging.php: -------------------------------------------------------------------------------- 1 | env('LOG_CHANNEL', 'stack'), 21 | 22 | /* 23 | |-------------------------------------------------------------------------- 24 | | Deprecations Log Channel 25 | |-------------------------------------------------------------------------- 26 | | 27 | | This option controls the log channel that should be used to log warnings 28 | | regarding deprecated PHP and library features. This allows you to get 29 | | your application ready for upcoming major versions of dependencies. 30 | | 31 | */ 32 | 33 | 'deprecations' => [ 34 | 'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'), 35 | 'trace' => false, 36 | ], 37 | 38 | /* 39 | |-------------------------------------------------------------------------- 40 | | Log Channels 41 | |-------------------------------------------------------------------------- 42 | | 43 | | Here you may configure the log channels for your application. Out of 44 | | the box, Laravel uses the Monolog PHP logging library. This gives 45 | | you a variety of powerful log handlers / formatters to utilize. 46 | | 47 | | Available Drivers: "single", "daily", "slack", "syslog", 48 | | "errorlog", "monolog", 49 | | "custom", "stack" 50 | | 51 | */ 52 | 53 | 'channels' => [ 54 | 'stack' => [ 55 | 'driver' => 'stack', 56 | 'channels' => ['single'], 57 | 'ignore_exceptions' => false, 58 | ], 59 | 60 | 'single' => [ 61 | 'driver' => 'single', 62 | 'path' => storage_path('logs/laravel.log'), 63 | 'level' => env('LOG_LEVEL', 'debug'), 64 | ], 65 | 66 | 'daily' => [ 67 | 'driver' => 'daily', 68 | 'path' => storage_path('logs/laravel.log'), 69 | 'level' => env('LOG_LEVEL', 'debug'), 70 | 'days' => 14, 71 | ], 72 | 73 | 'slack' => [ 74 | 'driver' => 'slack', 75 | 'url' => env('LOG_SLACK_WEBHOOK_URL'), 76 | 'username' => 'Laravel Log', 77 | 'emoji' => ':boom:', 78 | 'level' => env('LOG_LEVEL', 'critical'), 79 | ], 80 | 81 | 'papertrail' => [ 82 | 'driver' => 'monolog', 83 | 'level' => env('LOG_LEVEL', 'debug'), 84 | 'handler' => env('LOG_PAPERTRAIL_HANDLER', SyslogUdpHandler::class), 85 | 'handler_with' => [ 86 | 'host' => env('PAPERTRAIL_URL'), 87 | 'port' => env('PAPERTRAIL_PORT'), 88 | 'connectionString' => 'tls://'.env('PAPERTRAIL_URL').':'.env('PAPERTRAIL_PORT'), 89 | ], 90 | ], 91 | 92 | 'stderr' => [ 93 | 'driver' => 'monolog', 94 | 'level' => env('LOG_LEVEL', 'debug'), 95 | 'handler' => StreamHandler::class, 96 | 'formatter' => env('LOG_STDERR_FORMATTER'), 97 | 'with' => [ 98 | 'stream' => 'php://stderr', 99 | ], 100 | ], 101 | 102 | 'syslog' => [ 103 | 'driver' => 'syslog', 104 | 'level' => env('LOG_LEVEL', 'debug'), 105 | ], 106 | 107 | 'errorlog' => [ 108 | 'driver' => 'errorlog', 109 | 'level' => env('LOG_LEVEL', 'debug'), 110 | ], 111 | 112 | 'null' => [ 113 | 'driver' => 'monolog', 114 | 'handler' => NullHandler::class, 115 | ], 116 | 117 | 'emergency' => [ 118 | 'path' => storage_path('logs/laravel.log'), 119 | ], 120 | ], 121 | 122 | ]; 123 | -------------------------------------------------------------------------------- /application/config/mail.php: -------------------------------------------------------------------------------- 1 | env('MAIL_MAILER', 'smtp'), 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Mailer Configurations 21 | |-------------------------------------------------------------------------- 22 | | 23 | | Here you may configure all of the mailers used by your application plus 24 | | their respective settings. Several examples have been configured for 25 | | you and you are free to add your own as your application requires. 26 | | 27 | | Laravel supports a variety of mail "transport" drivers to be used while 28 | | sending an e-mail. You will specify which one you are using for your 29 | | mailers below. You are free to add additional mailers as required. 30 | | 31 | | Supported: "smtp", "sendmail", "mailgun", "ses", 32 | | "postmark", "log", "array", "failover" 33 | | 34 | */ 35 | 36 | 'mailers' => [ 37 | 'smtp' => [ 38 | 'transport' => 'smtp', 39 | 'host' => env('MAIL_HOST', 'smtp.mailgun.org'), 40 | 'port' => env('MAIL_PORT', 587), 41 | 'encryption' => env('MAIL_ENCRYPTION', 'tls'), 42 | 'username' => env('MAIL_USERNAME'), 43 | 'password' => env('MAIL_PASSWORD'), 44 | 'timeout' => null, 45 | 'local_domain' => env('MAIL_EHLO_DOMAIN'), 46 | ], 47 | 48 | 'ses' => [ 49 | 'transport' => 'ses', 50 | ], 51 | 52 | 'mailgun' => [ 53 | 'transport' => 'mailgun', 54 | ], 55 | 56 | 'postmark' => [ 57 | 'transport' => 'postmark', 58 | ], 59 | 60 | 'sendmail' => [ 61 | 'transport' => 'sendmail', 62 | 'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -bs -i'), 63 | ], 64 | 65 | 'log' => [ 66 | 'transport' => 'log', 67 | 'channel' => env('MAIL_LOG_CHANNEL'), 68 | ], 69 | 70 | 'array' => [ 71 | 'transport' => 'array', 72 | ], 73 | 74 | 'failover' => [ 75 | 'transport' => 'failover', 76 | 'mailers' => [ 77 | 'smtp', 78 | 'log', 79 | ], 80 | ], 81 | ], 82 | 83 | /* 84 | |-------------------------------------------------------------------------- 85 | | Global "From" Address 86 | |-------------------------------------------------------------------------- 87 | | 88 | | You may wish for all e-mails sent by your application to be sent from 89 | | the same address. Here, you may specify a name and address that is 90 | | used globally for all e-mails that are sent by your application. 91 | | 92 | */ 93 | 94 | 'from' => [ 95 | 'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'), 96 | 'name' => env('MAIL_FROM_NAME', 'Example'), 97 | ], 98 | 99 | /* 100 | |-------------------------------------------------------------------------- 101 | | Markdown Mail Settings 102 | |-------------------------------------------------------------------------- 103 | | 104 | | If you are using Markdown based email rendering, you may configure your 105 | | theme and component paths here, allowing you to customize the design 106 | | of the emails. Or, you may simply stick with the Laravel defaults! 107 | | 108 | */ 109 | 110 | 'markdown' => [ 111 | 'theme' => 'default', 112 | 113 | 'paths' => [ 114 | resource_path('views/vendor/mail'), 115 | ], 116 | ], 117 | 118 | ]; 119 | -------------------------------------------------------------------------------- /application/config/queue.php: -------------------------------------------------------------------------------- 1 | env('QUEUE_CONNECTION', 'sync'), 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Queue Connections 21 | |-------------------------------------------------------------------------- 22 | | 23 | | Here you may configure the connection information for each server that 24 | | is used by your application. A default configuration has been added 25 | | for each back-end shipped with Laravel. You are free to add more. 26 | | 27 | | Drivers: "sync", "database", "beanstalkd", "sqs", "redis", "null" 28 | | 29 | */ 30 | 31 | 'connections' => [ 32 | 33 | 'sync' => [ 34 | 'driver' => 'sync', 35 | ], 36 | 37 | 'database' => [ 38 | 'driver' => 'database', 39 | 'table' => 'jobs', 40 | 'queue' => 'default', 41 | 'retry_after' => 90, 42 | 'after_commit' => false, 43 | ], 44 | 45 | 'beanstalkd' => [ 46 | 'driver' => 'beanstalkd', 47 | 'host' => 'localhost', 48 | 'queue' => 'default', 49 | 'retry_after' => 90, 50 | 'block_for' => 0, 51 | 'after_commit' => false, 52 | ], 53 | 54 | 'sqs' => [ 55 | 'driver' => 'sqs', 56 | 'key' => env('AWS_ACCESS_KEY_ID'), 57 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 58 | 'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'), 59 | 'queue' => env('SQS_QUEUE', 'default'), 60 | 'suffix' => env('SQS_SUFFIX'), 61 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 62 | 'after_commit' => false, 63 | ], 64 | 65 | 'redis' => [ 66 | 'driver' => 'redis', 67 | 'connection' => 'default', 68 | 'queue' => env('REDIS_QUEUE', 'default'), 69 | 'retry_after' => 90, 70 | 'block_for' => null, 71 | 'after_commit' => false, 72 | ], 73 | 74 | ], 75 | 76 | /* 77 | |-------------------------------------------------------------------------- 78 | | Failed Queue Jobs 79 | |-------------------------------------------------------------------------- 80 | | 81 | | These options configure the behavior of failed queue job logging so you 82 | | can control which database and table are used to store the jobs that 83 | | have failed. You may change them to any database / table you wish. 84 | | 85 | */ 86 | 87 | 'failed' => [ 88 | 'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'), 89 | 'database' => env('DB_CONNECTION', 'mysql'), 90 | 'table' => 'failed_jobs', 91 | ], 92 | 93 | ]; 94 | -------------------------------------------------------------------------------- /application/config/sanctum.php: -------------------------------------------------------------------------------- 1 | explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf( 19 | '%s%s', 20 | 'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1', 21 | Sanctum::currentApplicationUrlWithPort() 22 | ))), 23 | 24 | /* 25 | |-------------------------------------------------------------------------- 26 | | Sanctum Guards 27 | |-------------------------------------------------------------------------- 28 | | 29 | | This array contains the authentication guards that will be checked when 30 | | Sanctum is trying to authenticate a request. If none of these guards 31 | | are able to authenticate the request, Sanctum will use the bearer 32 | | token that's present on an incoming request for authentication. 33 | | 34 | */ 35 | 36 | 'guard' => ['web'], 37 | 38 | /* 39 | |-------------------------------------------------------------------------- 40 | | Expiration Minutes 41 | |-------------------------------------------------------------------------- 42 | | 43 | | This value controls the number of minutes until an issued token will be 44 | | considered expired. If this value is null, personal access tokens do 45 | | not expire. This won't tweak the lifetime of first-party sessions. 46 | | 47 | */ 48 | 49 | 'expiration' => null, 50 | 51 | /* 52 | |-------------------------------------------------------------------------- 53 | | Sanctum Middleware 54 | |-------------------------------------------------------------------------- 55 | | 56 | | When authenticating your first-party SPA with Sanctum you may need to 57 | | customize some of the middleware Sanctum uses while processing the 58 | | request. You may change the middleware listed below as required. 59 | | 60 | */ 61 | 62 | 'middleware' => [ 63 | 'verify_csrf_token' => App\Http\Middleware\VerifyCsrfToken::class, 64 | 'encrypt_cookies' => App\Http\Middleware\EncryptCookies::class, 65 | ], 66 | 67 | ]; 68 | -------------------------------------------------------------------------------- /application/config/services.php: -------------------------------------------------------------------------------- 1 | [ 18 | 'domain' => env('MAILGUN_DOMAIN'), 19 | 'secret' => env('MAILGUN_SECRET'), 20 | 'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'), 21 | 'scheme' => 'https', 22 | ], 23 | 24 | 'postmark' => [ 25 | 'token' => env('POSTMARK_TOKEN'), 26 | ], 27 | 28 | 'ses' => [ 29 | 'key' => env('AWS_ACCESS_KEY_ID'), 30 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 31 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 32 | ], 33 | 34 | ]; 35 | -------------------------------------------------------------------------------- /application/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' => env( 32 | 'VIEW_COMPILED_PATH', 33 | realpath(storage_path('framework/views')) 34 | ), 35 | 36 | ]; 37 | -------------------------------------------------------------------------------- /application/database/.gitignore: -------------------------------------------------------------------------------- 1 | *.sqlite* 2 | -------------------------------------------------------------------------------- /application/database/factories/UserFactory.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | class UserFactory extends Factory 12 | { 13 | /** 14 | * Define the model's default state. 15 | * 16 | * @return array 17 | */ 18 | public function definition() 19 | { 20 | return [ 21 | 'name' => fake()->name(), 22 | 'email' => fake()->unique()->safeEmail(), 23 | 'email_verified_at' => now(), 24 | 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password 25 | 'remember_token' => Str::random(10), 26 | ]; 27 | } 28 | 29 | /** 30 | * Indicate that the model's email address should be unverified. 31 | * 32 | * @return static 33 | */ 34 | public function unverified() 35 | { 36 | return $this->state(fn (array $attributes) => [ 37 | 'email_verified_at' => null, 38 | ]); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /application/database/migrations/2014_10_12_000000_create_users_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->json('roles')->nullable(); 19 | $table->string('name'); 20 | $table->string('email')->unique(); 21 | $table->timestamp('email_verified_at')->nullable(); 22 | $table->string('password'); 23 | $table->rememberToken(); 24 | $table->timestamps(); 25 | }); 26 | } 27 | 28 | /** 29 | * Reverse the migrations. 30 | * 31 | * @return void 32 | */ 33 | public function down(): void 34 | { 35 | Schema::dropIfExists('users'); 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /application/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 | -------------------------------------------------------------------------------- /application/database/migrations/2019_08_19_000000_create_failed_jobs_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->string('uuid')->unique(); 19 | $table->text('connection'); 20 | $table->text('queue'); 21 | $table->longText('payload'); 22 | $table->longText('exception'); 23 | $table->timestamp('failed_at')->useCurrent(); 24 | }); 25 | } 26 | 27 | /** 28 | * Reverse the migrations. 29 | * 30 | * @return void 31 | */ 32 | public function down() 33 | { 34 | Schema::dropIfExists('failed_jobs'); 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /application/database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->morphs('tokenable'); 19 | $table->string('name'); 20 | $table->string('token', 64)->unique(); 21 | $table->text('abilities')->nullable(); 22 | $table->timestamp('last_used_at')->nullable(); 23 | $table->timestamp('expires_at')->nullable(); 24 | $table->timestamps(); 25 | }); 26 | } 27 | 28 | /** 29 | * Reverse the migrations. 30 | * 31 | * @return void 32 | */ 33 | public function down() 34 | { 35 | Schema::dropIfExists('personal_access_tokens'); 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /application/database/migrations/2022_09_12_000001_create_events_table.php: -------------------------------------------------------------------------------- 1 | id(); 21 | $table->uuid(); 22 | $table->string('name'); 23 | $table->json('policyIds'); 24 | $table->unsignedInteger('nonceValidForMinutes'); 25 | $table->boolean('hodlAsset')->default(false); 26 | $table->dateTime('startDateTime')->nullable(); 27 | $table->dateTime('endDateTime'); 28 | $table->timestamps(); 29 | }); 30 | 31 | // Seed an example event 32 | $now = Carbon::now(); 33 | $exampleEvent = new Event; 34 | $exampleEvent->fill([ 35 | 'uuid' => Str::uuid()->toString(), 36 | 'name' => 'Example Event', 37 | 'policyIds' => [ 38 | '5fa72fbeecbe80a3e15de1cacab54ba5e310e2c36ae85351132ed4ad', 39 | ], 40 | 'nonceValidForMinutes' => 15, 41 | 'hodlAsset' => false, 42 | 'startDateTime' => $now->toDateTimeString(), 43 | 'endDateTime' => $now->clone()->addDays(30)->toDateTimeString(), 44 | ]); 45 | $exampleEvent->save(); 46 | } 47 | 48 | /** 49 | * Reverse the migrations. 50 | * 51 | * @return void 52 | */ 53 | public function down(): void 54 | { 55 | Schema::dropIfExists('events'); 56 | } 57 | }; 58 | -------------------------------------------------------------------------------- /application/database/migrations/2022_09_12_000002_create_tickets_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->unsignedBigInteger('eventId')->index(); 19 | $table->foreign('eventId')->references('id')->on('events'); 20 | $table->string('policyId', 64)->index(); 21 | $table->string('assetId', 64)->index(); 22 | $table->string('stakeKey', 64)->index(); 23 | $table->char('signatureNonce', 16)->charset('binary')->unique(); 24 | $table->char('ticketNonce', 16)->charset('binary')->nullable()->unique(); 25 | $table->boolean('isCheckedIn')->default(false); 26 | $table->longText('signature')->nullable(); 27 | $table->dateTime('checkInTime')->nullable(); 28 | $table->unsignedBigInteger('checkInUser')->index()->nullable(); 29 | $table->foreign('checkInUser')->references('id')->on('users'); 30 | $table->timestamps(); 31 | }); 32 | } 33 | 34 | /** 35 | * Reverse the migrations. 36 | * 37 | * @return void 38 | */ 39 | public function down(): void 40 | { 41 | Schema::dropIfExists('tickets'); 42 | } 43 | }; 44 | -------------------------------------------------------------------------------- /application/database/migrations/2023_09_23_042340_add_location_to_events_table.php: -------------------------------------------------------------------------------- 1 | string('location') 17 | ->nullable(); 18 | $table->string('eventStart') 19 | ->nullable(); 20 | $table->string('eventEnd') 21 | ->nullable(); 22 | }); 23 | } 24 | 25 | /** 26 | * Reverse the migrations. 27 | * 28 | * @return void 29 | */ 30 | public function down() { 31 | Schema::table('events', function (Blueprint $table) { 32 | $table->dropColumn([ 33 | 'location', 34 | 'eventStart', 35 | 'eventEnd', 36 | ]); 37 | }); 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /application/database/migrations/2023_09_23_043453_add_event_date_to_events.php: -------------------------------------------------------------------------------- 1 | date('eventDate')->nullable(); 18 | }); 19 | } 20 | 21 | /** 22 | * Reverse the migrations. 23 | * 24 | * @return void 25 | */ 26 | public function down() 27 | { 28 | Schema::table('events', function (Blueprint $table) { 29 | $table->dropColumn(['eventDate']); 30 | }); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /application/database/migrations/2023_09_23_043840_add_image_to_events.php: -------------------------------------------------------------------------------- 1 | string('image') 17 | ->nullable(); 18 | }); 19 | } 20 | 21 | /** 22 | * Reverse the migrations. 23 | * 24 | * @return void 25 | */ 26 | public function down() { 27 | Schema::table('events', function (Blueprint $table) { 28 | $table->dropColumn(['image']); 29 | }); 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /application/database/seeders/DatabaseSeeder.php: -------------------------------------------------------------------------------- 1 | create(); 17 | 18 | // \App\Models\User::factory()->create([ 19 | // 'name' => 'Test User', 20 | // 'email' => 'test@example.com', 21 | // ]); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /application/lang/en/auth.php: -------------------------------------------------------------------------------- 1 | 'These credentials do not match our records.', 17 | 'password' => 'The provided password is incorrect.', 18 | 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.', 19 | 20 | ]; 21 | -------------------------------------------------------------------------------- /application/lang/en/pagination.php: -------------------------------------------------------------------------------- 1 | '« Previous', 17 | 'next' => 'Next »', 18 | 19 | ]; 20 | -------------------------------------------------------------------------------- /application/lang/en/passwords.php: -------------------------------------------------------------------------------- 1 | 'Your password has been reset!', 17 | 'sent' => 'We have emailed your password reset link!', 18 | 'throttled' => 'Please wait before retrying.', 19 | 'token' => 'This password reset token is invalid.', 20 | 'user' => "We can't find a user with that email address.", 21 | 22 | ]; 23 | -------------------------------------------------------------------------------- /application/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "dev": "vite", 5 | "build": "vite build" 6 | }, 7 | "dependencies": { 8 | "buffer": "^6.0.3", 9 | "cbor": "^8.1.0", 10 | "@emurgo/cardano-message-signing-nodejs": "^1.0.1", 11 | "@emurgo/cardano-serialization-lib-nodejs": "^11.0.5" 12 | }, 13 | "devDependencies": { 14 | "@popperjs/core": "^2.10.2", 15 | "axios": "^0.27", 16 | "bootstrap": "^5.1.3", 17 | "laravel-vite-plugin": "^0.6.0", 18 | "lodash": "^4.17.19", 19 | "postcss": "^8.1.14", 20 | "sass": "^1.32.11", 21 | "vite": "^3.0.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /application/phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | ./tests/Unit 10 | 11 | 12 | ./tests/Feature 13 | 14 | 15 | 16 | 17 | ./app 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /application/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 | # Send Requests To Front Controller... 18 | RewriteCond %{REQUEST_FILENAME} !-d 19 | RewriteCond %{REQUEST_FILENAME} !-f 20 | RewriteRule ^ index.php [L] 21 | 22 | -------------------------------------------------------------------------------- /application/public/build/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "resources/js/app.js": { 3 | "file": "assets/app.ff70f951.js", 4 | "src": "resources/js/app.js", 5 | "isEntry": true 6 | }, 7 | "resources/sass/app.scss": { 8 | "file": "assets/app.5380b351.css", 9 | "src": "resources/sass/app.scss", 10 | "isEntry": true 11 | } 12 | } -------------------------------------------------------------------------------- /application/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CardanoGateKeeper/Core/d38cd19cf2172fa91ae0b564097ae38a30384ce2/application/public/favicon.ico -------------------------------------------------------------------------------- /application/public/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CardanoGateKeeper/Core/d38cd19cf2172fa91ae0b564097ae38a30384ce2/application/public/images/favicon.png -------------------------------------------------------------------------------- /application/public/images/favicon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /application/public/images/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /application/public/index.php: -------------------------------------------------------------------------------- 1 | make(Kernel::class); 50 | 51 | $response = $kernel->handle( 52 | $request = Request::capture() 53 | )->send(); 54 | 55 | $kernel->terminate($request, $response); 56 | -------------------------------------------------------------------------------- /application/public/js/bridge/cardano-dapp-connector-bridge.min.js: -------------------------------------------------------------------------------- 1 | function initCardanoDAppConnectorBridge(e){var n="DAppConnectorBridge: ",a=null,r=null,t={type:"cardano-dapp-connector-bridge",source:null,origin:null},o={},i={connect:"connect",handshake:"handshake",enable:"enable",isEnabled:"isEnabled"};function d(e){var n=[...arguments];return n.length>0&&n.shift(),new Promise((r,i)=>{var d={payload:{type:t.type,to:a,uid:("000"+(46656*Math.random()|0).toString(36)).slice(-3)+("000"+(46656*Math.random()|0).toString(36)).slice(-3),method:e,args:n},resolve:r,reject:i};o[d.payload.uid]=d,t.source.postMessage(d.payload,t.origin)})}function c(e){return function(){return d(e,...arguments)}}function s(e){var n={};for(var a in e){var r=e[a];"string"==typeof r?"feeAddress"===a?n[a]=r:(n[a]=c(r),i[r]=r):n[a]="object"==typeof r?s(r):r}return n}function l(e){if(!r){if(e.data.method!==i.connect)return console.error("Error: "+n+"send 'connect' first."),!1;var o=e.data.initialApi;if(!(o&&o.isBridge&&o.apiVersion&&o.name))return console.error("Error: "+n+"'connect' is missing correct initialApi.",o),!1;if(!e.data.walletNamespace)return console.error("Error: "+n+"'connect' is missing walletNamespace.",e.data.walletNamespace),!1;r=function(e,r,o,i){if(window.hasOwnProperty("cardano")||(window.cardano={}),window.cardano.hasOwnProperty(o))return console.warn("Warn: "+n+"window.cardano."+o+" already present, skipping initialApi creation."),null;t.source=e,t.origin=r,a=o;var c={isBridge:!0,isEnabled:function(){return d("isEnabled")},enable:function(){return d("enable")},apiVersion:i.apiVersion,name:i.name,icon:i.icon?i.icon:null,experimental:{}};return window.cardano[o]=c,i.experimental&&(c.experimental={...s(i.experimental)}),window.cardano[o]}(e.source,e.origin,e.data.walletNamespace,o)}return!(!r||!window.hasOwnProperty("cardano")||window.cardano[e.data.walletNamespace]!==r)||(console.warn("Warn: "+n+"bridge not set up correctly:",t,r,a),!1)}window.addEventListener("message",async function(n){if(function(e){return!(!(e.data&&e.origin&&e.source)||e.data.type!==t.type||!i.hasOwnProperty(e.data.method)||a&&e.data.walletNamespace!==a)}(n)&&l(n))if(n.data.method!==i.connect){if(n.data.uid){var c=o[n.data.uid];if(c){var u=n.data.response,p=n.data.error;if(p)return c.reject(p),void delete o[n.data.uid];n.data.method===i.enable&&(null,"object"==typeof u&&(u={...s(u)})),c.resolve(u),delete o[n.data.uid]}}}else await d("handshake")&&r&&e&&e(r)},!1)} 2 | -------------------------------------------------------------------------------- /application/public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /application/resources/js/app.js: -------------------------------------------------------------------------------- 1 | import './bootstrap'; 2 | -------------------------------------------------------------------------------- /application/resources/js/bootstrap.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | window._ = _; 3 | 4 | import 'bootstrap'; 5 | 6 | import axios from 'axios'; 7 | window.axios = axios; 8 | 9 | window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; 10 | -------------------------------------------------------------------------------- /application/resources/nodejs/validateNonce.js: -------------------------------------------------------------------------------- 1 | const EMS = require('@emurgo/cardano-message-signing-nodejs'); 2 | const CSL = require('@emurgo/cardano-serialization-lib-nodejs'); 3 | const Buffer = require('buffer'); 4 | const cbor = require('cbor'); 5 | 6 | const toHexBuffer = hex => Buffer.Buffer.from(hex, 'hex'); 7 | const toHexString = array => Buffer.Buffer.from(array).toString('hex'); 8 | 9 | const sigKeyToPublicKey = (sig_key) => { 10 | const decoded = cbor.decode(sig_key); 11 | return CSL.PublicKey.from_bytes(toHexBuffer(decoded.get(-2))); 12 | } 13 | 14 | const publicKeyToStakeKey = (publicKey) => { 15 | const stake_arg = `e1` + toHexString(publicKey.hash('hex').to_bytes()); 16 | return CSL.Address.from_bytes(toHexBuffer(stake_arg)); 17 | } 18 | 19 | const verifyData = (sig_cbor, key, payload, stake_address, network_mode) => { 20 | const publicKey = sigKeyToPublicKey(key); 21 | const stakeAddr = publicKeyToStakeKey(publicKey); 22 | const coseSign1_verify = EMS.COSESign1.from_bytes(toHexBuffer(sig_cbor)); 23 | const signedSigStruc_verify = coseSign1_verify.signed_data(); 24 | const sig = CSL.Ed25519Signature.from_bytes(coseSign1_verify.signature()); 25 | 26 | const stake_prefix = network_mode === 'mainnet' ? 'stake' : 'stake_test'; 27 | 28 | const walletMatches = stakeAddr.to_bech32(stake_prefix) === stake_address; 29 | 30 | const validates = publicKey.verify(signedSigStruc_verify.to_bytes(), sig); 31 | 32 | const payloadMatches = toHexString(signedSigStruc_verify.payload()) === payload; 33 | 34 | console.log(walletMatches, payloadMatches, validates, stake_prefix, network_mode); 35 | 36 | return walletMatches && payloadMatches && validates; 37 | } 38 | 39 | const args = process.argv.slice(2); 40 | 41 | console.log(verifyData(args[0], args[1], args[2], args[3])); 42 | -------------------------------------------------------------------------------- /application/resources/sass/_variables.scss: -------------------------------------------------------------------------------- 1 | // Body 2 | $body-bg: #f8fafc; 3 | 4 | // Typography 5 | $font-family-sans-serif: 'Nunito', sans-serif; 6 | $font-size-base: 0.9rem; 7 | $line-height-base: 1.6; 8 | -------------------------------------------------------------------------------- /application/resources/sass/app.scss: -------------------------------------------------------------------------------- 1 | // Fonts 2 | @import url('https://fonts.bunny.net/css?family=Nunito'); 3 | 4 | // Variables 5 | @import 'variables'; 6 | 7 | // Bootstrap 8 | @import 'bootstrap/scss/bootstrap'; 9 | -------------------------------------------------------------------------------- /application/resources/views/account/profile.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 |
5 |
6 |
7 |
8 |
9 | 10 | {{ __('Profile') }} 11 |
12 |
13 |
14 | @csrf 15 | 16 |
17 |
18 |
19 | 20 | 21 |
22 |
23 | 24 |
25 |
26 | 27 | 28 |
29 |
30 | 31 |
32 |
33 | 34 | 35 |
36 |
37 |
38 | 39 |
40 | 41 | 42 |
43 | 44 |
45 | 46 | 47 |
Leave this blank, if you are not changing your account password
48 |
49 | 50 |
51 | 55 | 56 | 60 |
61 | 62 |
63 |
64 |
65 |
66 |
67 |
68 | @endsection 69 | -------------------------------------------------------------------------------- /application/resources/views/admin/manage-events/index.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 |
5 |
6 |
7 |
8 |
9 |
10 | 11 | {{ __('Manage Events') }} 12 |
13 |
14 | 15 |
16 |
17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | @if ($allEvents->count()) 30 | @foreach($allEvents as $event) 31 | 32 | 33 | 38 | 43 | 46 | 56 | 57 | @endforeach 58 | @else 59 | 60 | 63 | 64 | @endif 65 | 66 |
{{ __('Name') }}{{ __('Start') }}{{ __('End') }}{{ __('Policy IDs') }}{{ __('Action') }}
{{ $event->name }} 34 | {{ $event->startDateTime->toDateTimeString() }} 35 |
36 | ({{$event->startDateTime->diffForHumans()}}) 37 |
39 | {{ $event->endDateTime->toDateTimeString() }} 40 |
41 | ({{ $event->endDateTime->diffForHumans() }}) 42 |
44 | {!! implode("
", $event->policyIds) !!} 45 |
47 | 48 | 49 | {{ __('Edit') }} 50 | 51 | 52 | 53 | {{ __('View') }} 54 | 55 |
61 | {{ __('There are no events in the system') }} 62 |
67 |
68 |
69 |
70 |
71 |
72 | @endsection 73 | -------------------------------------------------------------------------------- /application/resources/views/admin/manage-events/view.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 |
5 |
6 |
7 |
8 |
9 |
10 | 11 | {{ __('Event Details') }} 12 |
13 | 19 |
20 |
21 |
22 |
23 |
24 |

{{$tickets['total']}}

25 |

Total Tickets

26 |
27 | 28 |
29 |
30 |
31 |

{{$tickets['checked_in']}}

32 |

Checked In

33 |
34 | 35 |
36 |
37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | @foreach($tickets['event_tickets'] as $ticket) 47 | 48 | 49 | 50 | 51 | 52 | @endforeach 53 | 54 |
PolicyAssetChecked In
{{$ticket->policyId}}{{hex2bin($ticket->assetId)}}{{$ticket->isCheckedIn}}
55 |
56 | 57 |
58 |
59 |
60 |
61 | 62 | @endsection 63 | 64 | @section('footer.scripts') 65 | 66 | 67 | 68 | 69 | 72 | @endsection 73 | -------------------------------------------------------------------------------- /application/resources/views/admin/manage-users/form.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 |
5 |
6 |
7 |
8 |
9 |
10 | 11 | {{ __('Manage Users') }} 12 | / 13 | @if ($user) 14 | {{ __('Edit User') }} 15 | @else 16 | {{ __('Add User') }} 17 | @endif 18 |
19 | 25 |
26 | 27 |
28 |
29 | @csrf 30 | @if ($user) 31 | 32 | @endif 33 | 34 |
35 | 38 | 39 |
40 | 41 |
42 | 45 | 46 |
Account email address must be unique across the system
47 |
48 | 49 |
50 | 53 | 54 |
Leave blank, if you do not wish to change the account password
55 |
56 | 57 |
58 | 61 | @foreach(validRoles() as $validRole) 62 |
63 | roles : [])), true) ? 'checked' : '' }} class="form-check-input" type="checkbox"> 64 | 67 |
68 | @endforeach 69 |
70 | 71 | @if ($user) 72 | 76 | @else 77 | 81 | @endif 82 | 83 | 87 |
88 |
89 |
90 |
91 |
92 |
93 | @endsection 94 | -------------------------------------------------------------------------------- /application/resources/views/admin/manage-users/index.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 |
5 |
6 |
7 |
8 |
9 |
10 | 11 | {{ __('Manage Users') }} 12 |
13 | 19 |
20 | 21 |
22 | 23 |
24 |
25 | 26 | 27 | 28 | 29 | 32 | 33 | {{ __('Clear') }} 34 | 35 |
36 |
37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | @if ($allUsers->count()) 50 | @foreach ($allUsers as $user) 51 | 52 | 55 | 58 | 67 | 70 | 76 | 77 | @endforeach 78 | @else 79 | 80 | 83 | 84 | @endif 85 | 86 |
{{ __('Name') }}{{ __('Email') }}{{ __('Roles') }}{{ __('Created') }}{{ __('Action') }}
53 | {{ $user->name }} 54 | 56 | {{ $user->email }} 57 | 59 | @if (count($user->roles)) 60 | @foreach ($user->roles as $role) 61 | {{ $role }} 62 | @endforeach 63 | @else 64 | {{ __('Guest') }} 65 | @endif 66 | 68 | {{ $user->created_at->toDateTimeString() }} 69 | 71 | 72 | 73 | {{ __('Edit') }} 74 | 75 |
81 | {{ __('No users were found in the system') }} 82 |
87 | 88 |
89 |
90 |
91 |
92 |
93 | @endsection 94 | 95 | @push('scripts') 96 | 103 | @endpush 104 | -------------------------------------------------------------------------------- /application/resources/views/auth/login.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 |
5 |
6 |
7 |
8 |
{{ __('Login') }}
9 | 10 |
11 |
12 | @csrf 13 | 14 |
15 | 16 | 17 |
18 | 19 | 20 | @error('email') 21 | 22 | {{ $message }} 23 | 24 | @enderror 25 |
26 |
27 | 28 |
29 | 30 | 31 |
32 | 33 | 34 | @error('password') 35 | 36 | {{ $message }} 37 | 38 | @enderror 39 |
40 |
41 | 42 |
43 |
44 |
45 | 46 | 47 | 50 |
51 |
52 |
53 | 54 |
55 |
56 | 59 | 60 | @if (Route::has('password.request')) 61 | 62 | {{ __('Forgot Your Password?') }} 63 | 64 | @endif 65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 | @endsection 74 | -------------------------------------------------------------------------------- /application/resources/views/auth/passwords/confirm.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 |
5 |
6 |
7 |
8 |
{{ __('Confirm Password') }}
9 | 10 |
11 | {{ __('Please confirm your password before continuing.') }} 12 | 13 |
14 | @csrf 15 | 16 |
17 | 18 | 19 |
20 | 21 | 22 | @error('password') 23 | 24 | {{ $message }} 25 | 26 | @enderror 27 |
28 |
29 | 30 |
31 |
32 | 35 | 36 | @if (Route::has('password.request')) 37 | 38 | {{ __('Forgot Your Password?') }} 39 | 40 | @endif 41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | @endsection 50 | -------------------------------------------------------------------------------- /application/resources/views/auth/passwords/email.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 |
5 |
6 |
7 |
8 |
{{ __('Reset Password') }}
9 | 10 |
11 | @if (session('status')) 12 | 15 | @endif 16 | 17 |
18 | @csrf 19 | 20 |
21 | 22 | 23 |
24 | 25 | 26 | @error('email') 27 | 28 | {{ $message }} 29 | 30 | @enderror 31 |
32 |
33 | 34 |
35 |
36 | 39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | @endsection 48 | -------------------------------------------------------------------------------- /application/resources/views/auth/passwords/reset.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 |
5 |
6 |
7 |
8 |
{{ __('Reset Password') }}
9 | 10 |
11 |
12 | @csrf 13 | 14 | 15 | 16 |
17 | 18 | 19 |
20 | 21 | 22 | @error('email') 23 | 24 | {{ $message }} 25 | 26 | @enderror 27 |
28 |
29 | 30 |
31 | 32 | 33 |
34 | 35 | 36 | @error('password') 37 | 38 | {{ $message }} 39 | 40 | @enderror 41 |
42 |
43 | 44 |
45 | 46 | 47 |
48 | 49 |
50 |
51 | 52 |
53 |
54 | 57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 | @endsection 66 | -------------------------------------------------------------------------------- /application/resources/views/auth/verify.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 |
5 |
6 |
7 |
8 |
{{ __('Verify Your Email Address') }}
9 | 10 |
11 | @if (session('resent')) 12 | 15 | @endif 16 | 17 | {{ __('Before proceeding, please check your email for a verification link.') }} 18 | {{ __('If you did not receive the email') }}, 19 |
20 | @csrf 21 | . 22 |
23 |
24 |
25 |
26 |
27 |
28 | @endsection 29 | -------------------------------------------------------------------------------- /application/resources/views/dashboard/admin-menu.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ __('Manage Users') }} 4 | 5 | 6 | 7 | {{ __('Manage Event') }} 8 | 9 | -------------------------------------------------------------------------------- /application/resources/views/dashboard/index.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 |
5 |
6 |
7 |
8 |
9 | 10 | {{ __('Dashboard') }} 11 |
12 | 13 |
14 |

{{ __('You are logged in!') }}

15 | 16 |
17 | 18 | @if ($isAdmin) 19 | @include('dashboard.admin-menu') 20 | @endif 21 | 22 | @if ($isStaff) 23 | @include('dashboard.staff-menu') 24 | @endif 25 | 26 |
27 |
28 |
29 |
30 |
31 |
32 | @endsection 33 | -------------------------------------------------------------------------------- /application/resources/views/dashboard/staff-menu.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ __('Scan Tickets') }} 4 | 5 | -------------------------------------------------------------------------------- /application/resources/views/layouts/app.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | @hasSection('title') 8 | @yield('title') 9 | @else 10 | {{config('app.name')}} 11 | @endif 12 | @hasSection('og') 13 | @yield('og') 14 | @endif 15 | 16 | 17 | 18 | 19 | @vite(['resources/sass/app.scss', 'resources/js/app.js']) 20 | 21 | 22 | 41 | 42 | 43 |
44 | 65 | 66 |
67 | @include('layouts.partials.alerts') 68 | @yield('content') 69 |
70 |
71 |
72 |

73 | Powered by GateKeeper 74 | 75 | View on Github 76 | 77 |

78 |

79 | An open source project created by Adam K. Dean & Latheesan Kanesamoorthy.
80 | Maintained by the Cardano community with 81 |

82 |
83 |
84 |
85 | 86 | @stack('scripts') 87 | @yield('footer.scripts'); 88 | 89 | 90 | -------------------------------------------------------------------------------- /application/resources/views/layouts/partials/alerts.blade.php: -------------------------------------------------------------------------------- 1 | @if (session('status') || session('error')) 2 |
3 |
4 |
5 | 6 | @if (session('status')) 7 | 12 | @endif 13 | 14 | @if (session('error')) 15 | 20 | @endif 21 | 22 |
23 |
24 |
25 | @endif 26 | -------------------------------------------------------------------------------- /application/resources/views/layouts/partials/nav.blade.php: -------------------------------------------------------------------------------- 1 | 47 | -------------------------------------------------------------------------------- /application/routes/api.php: -------------------------------------------------------------------------------- 1 | group(function() { 12 | 13 | Route::prefix('events')->group(function() { 14 | Route::get('/', [EventsController::class, 'eventList'])->name('api.v1.event-list'); 15 | Route::get('{uuid}', [EventsController::class, 'eventInfo'])->name('api.v1.event-info'); 16 | }); 17 | 18 | Route::post('asset-info', [AssetInfoController::class, 'assetInfo'])->name('api.v1.asset-info'); 19 | 20 | Route::post('generate-nonce', [NonceController::class, 'generateNonce'])->name('api.v1.generate-nonce'); 21 | Route::post('validate-nonce', [NonceController::class, 'validateNonce'])->name('api.v1.validate-nonce'); 22 | 23 | }); 24 | -------------------------------------------------------------------------------- /application/routes/channels.php: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CardanoGateKeeper/Core/d38cd19cf2172fa91ae0b564097ae38a30384ce2/application/routes/channels.php -------------------------------------------------------------------------------- /application/routes/console.php: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CardanoGateKeeper/Core/d38cd19cf2172fa91ae0b564097ae38a30384ce2/application/routes/console.php -------------------------------------------------------------------------------- /application/routes/web.php: -------------------------------------------------------------------------------- 1 | name('event'); 17 | 18 | Auth::routes([ 19 | 'register' => false, 20 | 'reset' => false, 21 | 'verify' => false, 22 | ]); 23 | 24 | Route::middleware('auth')->group(function() { 25 | 26 | // Dashboard 27 | Route::get('/dashboard', [DashboardController::class, 'index'])->name('dashboard.index'); 28 | 29 | // Profile 30 | Route::prefix('profile')->group(function() { 31 | Route::get('/', [ProfileController::class, 'index'])->name('account.profile'); 32 | Route::post('update', [ProfileController::class, 'update'])->name('account.profile.update'); 33 | }); 34 | 35 | // Admin 36 | Route::prefix('admin')->middleware('admin.only')->group(function() { 37 | 38 | // Manage Users 39 | Route::prefix('manage-users')->group(function() { 40 | Route::get('/', [ManageUsersController::class, 'index'])->name('admin.manage-users.index'); 41 | Route::get('add-user', [ManageUsersController::class, 'addUser'])->name('admin.manage-users.add-user'); 42 | Route::get('{userId}/edit', [ManageUsersController::class, 'edit'])->name('admin.manage-users.edit'); 43 | Route::post('save', [ManageUsersController::class, 'save'])->name('admin.manage-users.save'); 44 | }); 45 | 46 | // Manage Events 47 | Route::resource('manage-events', ManageEventsController::class)->parameters([ 48 | 'manage-events' => 'event' 49 | ]); 50 | 51 | }); 52 | 53 | // Staff 54 | Route::prefix('staff')->middleware('staff.only')->group(function() { 55 | Route::prefix('scan-tickets')->group(function() { 56 | Route::get('/', [ScanTicketsController::class, 'index'])->name('staff.scan-tickets.index'); 57 | Route::get('{eventUUID}', [ScanTicketsController::class, 'event'])->name('staff.scan-tickets.event'); 58 | Route::post('ajax/register-ticket', [ScanTicketsController::class, 'ajaxRegisterTicket'])->name('staff.scan-tickets.ajax.register-ticket'); 59 | }); 60 | }); 61 | 62 | }); 63 | -------------------------------------------------------------------------------- /application/ssl/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFCTCCAvGgAwIBAgIUVgTeZNQKEQEsJ9T6nofVpoGe0mIwDQYJKoZIhvcNAQEL 3 | BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTIyMDkxMzAyNDYzMloXDTI1MDYw 4 | OTAyNDYzMlowFDESMBAGA1UEAwwJbG9jYWxob3N0MIICIjANBgkqhkiG9w0BAQEF 5 | AAOCAg8AMIICCgKCAgEAyNOxMSVuNtSVaqt/zN0VsobCKLqREi6bf+d3QsY7+Vmf 6 | Ct107TcckM9cy11T5ZOVtPUvO1qrN1BWc2S25cYe04ceQKmWwdcFAbEK01x82Xpk 7 | 4Gnw/FXrV0PGLry7FR7yh0gCDlbhk82GDjy03FZQd6kwNddT7ccw41yuiEqj6Ftm 8 | qKWAXCUTxdGVJj0IXs14TfVgAH0j0oaWU7kaiJMxsKmGLobmNePIZaADcnpj5HJJ 9 | uhPnkO9Yj49A7H7jVGxAsArUfEMuxpiqdOuzc3+6K/KcIGy7kTfHWiHHi5fv6DJ1 10 | 2Zlv1wP4SOPKLkgO/C3XxzUAiZ10ICpl1DJTvfbD0JDgb84m87/fgUrGe+QmAKii 11 | JKCmAI6RYJkWLhg9wqQUTt+htY0UT2kXOD8v8yWTwuieM18Le9EYXwG9h75Rn95R 12 | BwM1GM3dnijDR4275J46ARDrjETBFVg4rS4tZDEIkMS77ad9UrZrXGzVesJtuiGk 13 | xQK+8zTqyheaVX3J7f89pKtOhKDAocvSbjZdYiOHjx5TttPoomS5g4cNCHmoXKwq 14 | epZ88JTirT7yagdB52Y1VR8RtX9wpvUPwsLr5WsZVlvDaDgqC1iI064WgTzOmjYa 15 | 2XfmX38b4iomtkRTE5sN8mjHcxArSctMLy8hCrcHU+pqKTUnYG7bhUwLXm3sUncC 16 | AwEAAaNTMFEwHQYDVR0OBBYEFPp+I6l59YeHo/dcpmXrZegFqfVbMB8GA1UdIwQY 17 | MBaAFPp+I6l59YeHo/dcpmXrZegFqfVbMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZI 18 | hvcNAQELBQADggIBAHmmlhkbgR7mFQpNEBn2Xr1RNdWFOyB4T52AEsC9OQ0xldPZ 19 | 9H8Y+zMwIxkuUb1GMwTIGLmnafFpId6+2tIKCsWfgv1O2AUkbwq65WfCN5HeYJab 20 | qgXVQZzxq1GnpkO+gn/60zqzSH1CP6RBQbI3gkM37dfpSJe3Zvb+Y1kVjvNsg+nV 21 | JYwu77xKajZblOS0q/cXmGLmM/O0KSE2eKVx0eOnWA7QCPRKTm04D0fiDaODVHIW 22 | xhS0/K9zv50M4bgt8gE/e+sH5gAE/xJScjUsDBIkpguDdKr6ZJTshktDqFVTD/HG 23 | TQZ8hhN2d+wiXrc+rXii+Ul3UzZokU2rURz0aHp1UbcKMPaiKhb1IvettAtbKoRU 24 | y1/mKueVDeyv91hml/TG8WgxG+3HIsnI1f9zV0SELL9Tnc97VsEzXBDylvbEkloc 25 | y/N53q9J+4mX12F1g469W4Ms096cYeMFtjfS1delkOznLAc+yeuHtoe2rfCgKRGY 26 | XPw0NHbpXeXv0vADIDe4o0bIRxngTcZ987tPu+xdfjC9eBkj1dUVEZQfwfugti19 27 | WpREYb0+uZumwq+w92ugK60NNh/o38RMdXlNd08RwFJT5d1rUg91sNDKtjXgdN4G 28 | o18LbeOSeNPtza5Ass6uY91B3CffxNmv+zB7UpnN21QSV9yX1+smWwL+qQut 29 | -----END CERTIFICATE----- 30 | -------------------------------------------------------------------------------- /application/ssl/key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQDI07ExJW421JVq 3 | q3/M3RWyhsIoupESLpt/53dCxjv5WZ8K3XTtNxyQz1zLXVPlk5W09S87Wqs3UFZz 4 | ZLblxh7Thx5AqZbB1wUBsQrTXHzZemTgafD8VetXQ8YuvLsVHvKHSAIOVuGTzYYO 5 | PLTcVlB3qTA111PtxzDjXK6ISqPoW2aopYBcJRPF0ZUmPQhezXhN9WAAfSPShpZT 6 | uRqIkzGwqYYuhuY148hloANyemPkckm6E+eQ71iPj0DsfuNUbECwCtR8Qy7GmKp0 7 | 67Nzf7or8pwgbLuRN8daIceLl+/oMnXZmW/XA/hI48ouSA78LdfHNQCJnXQgKmXU 8 | MlO99sPQkOBvzibzv9+BSsZ75CYAqKIkoKYAjpFgmRYuGD3CpBRO36G1jRRPaRc4 9 | Py/zJZPC6J4zXwt70RhfAb2HvlGf3lEHAzUYzd2eKMNHjbvknjoBEOuMRMEVWDit 10 | Li1kMQiQxLvtp31StmtcbNV6wm26IaTFAr7zNOrKF5pVfcnt/z2kq06EoMChy9Ju 11 | Nl1iI4ePHlO20+iiZLmDhw0IeahcrCp6lnzwlOKtPvJqB0HnZjVVHxG1f3Cm9Q/C 12 | wuvlaxlWW8NoOCoLWIjTrhaBPM6aNhrZd+ZffxviKia2RFMTmw3yaMdzECtJy0wv 13 | LyEKtwdT6mopNSdgbtuFTAtebexSdwIDAQABAoICAAZM3do02mhN8wINr0y2AuA+ 14 | 6ghtEfAIFkjE8jDeFzOTZDHrEKgAepzwZHDc1Kz3HFM4/epWxdhOZOfp2PMMbBsm 15 | ugQ9TcV3AWk0LKrE1AXemRuRN0YzS9bJAYal1Rish99GmwnoH9uLLxFzRhlDe/LF 16 | FAYcYz2qahY36iHMdafsdiQETSqW3y/ti0hP0dtnhsarU/+v7VfFcSHfRL3UIFLy 17 | fosnIEHAi6DkjDcjL88S5s03oqQhXh3i2J6SvH8BhmX/cPsVAHqqf257ln2hHC4U 18 | xmEeCCReI5roRGJUueoG6zuQXLnaHgfrXYRmLlIq/gH537JxhhscOWWWzpm/DBWI 19 | tMTK5hzeS9DVcjZyrJGWSZgJhBdq/ieqhemx4lg15nRZLkihG+tf25/8LBjGONov 20 | xaECcR38hMZp1D0s2B2MVHiTR6KSwR10fhdqHuCntoO2FwnduCTYGo5WF6EA3Lv+ 21 | vBJTmwPULJ9SfBn2EUPrIG0TQLqdvXipZQBVCKXUmYdvggFWdfdl11dyQi1KXMPu 22 | Q0Gqxn0R7gjINgYGgR3jySoNfKZJLb7LvkmsYWyKSxti3oF8XBFjrQRRLvyaV6MR 23 | VqvzN3lrJ90lhCuNLlX5QVmMTmbJf+cMY/i35A8u1mWFnFBu9tGkNTg8pWAvtfxn 24 | k+J4EP855uO1e7MAY++BAoIBAQDU7AgZZo/D8nn3srPYs/OgKIsKb6O4XQ+Zb/qH 25 | eiW/Qe7q+L8V099MNXSwq2fVjt9Z76midZvLwBCteoJMbuUFdcIuQV1jCS3WxTc6 26 | J5pZVrjWM0obXl3+4CpLCxQyvMvxvHwgE5skCMl1/dV5X8UttetkvtOxlIP/e7Jr 27 | AEX9Sz6XF00SwQcbz8wYADzzsjitqg7UAbUow70aov/x6GtFMCpe8eJbFx3Htd5+ 28 | He0eE7NnQdz7GOJ9zNh9lM0cg2YKpPgklqHstdyv42PPQyBluJ0HN9fqTWupzB5n 29 | Rc3KQFTwA8IUiPXjTz2UTioNL2QyNfh3KPkklBLh3w9k0n8JAoIBAQDxdTbwKAPP 30 | BAwl33+3ZZEo81V20aOsq4S2PRW1Hf96Z4Lda/mHnoKRjR07VRx0rC8vFAs41ZKe 31 | ZuHtVRNBaeceVS3zziW0FSjl+JjEWiuQsgvjt0SOvQr9x0YLO1Pqp81bdNopHRZN 32 | mOy5p0dhjjUnMJFwc+/j8tyl267F9x7jJbGndd3YyxO9Hd2vl/jK+Gn8OBvLFsJZ 33 | dPB6gijWjJgjXDN/bLPZqrEl6y+PukaE8B18zk6yd+xXlZfRYVTndWO+eWvbXgp2 34 | nyEn308Qq/Ri5V+YiErUuHryh8cyH1Fog1w5x+tYXYVVs6ykFgLk12yJWcmmptV/ 35 | TreZEFt5yyV/AoIBAGY2jMjM0HUrYNvE4MtFz81xyRWQdFLb8dIhzG3e+GHxH+WS 36 | 1gB/fnGSM9tf3W/Kr3P4NkH0HX9ZS2hMKGLS1YSifD5CSLdzP5sbf6CeRF1g4UiK 37 | xKeQzKS/Le5qeh7FPU5lwlyPrOlGgpfu69zhWcHQ1Bi/9tYHyroJwqPVNMQcrAZS 38 | PEClg2kZfwuCicAfLVBJE+c4jqPsG0q/NwkFHwHO1nhPu+8okLY/m3iFmz/WC0il 39 | eJTd5FSp0r+ugCyQ85pmiWzfFwqQX0ncahykR9gtIMzHRRKIagfQIkRHYTwXV+8E 40 | jtXWzdt6PcCFKXC88GeeOxHRDJE/uDTQJQ6scikCggEASyrESYsLdHy8E2Y6aity 41 | Px9RccXdgDl8/QaTJWkLSV3D/Y5JhhCwcQNPIFNkomWf4rYIsE68cBdzKeEcN3by 42 | fhziS03XqvITY1Q6qpWvjkH/NYquCwmKzGomwgRcIWbPpTMBX18wvENpHnqW3CXb 43 | IjIadLmC6qGGUxPKmtJNiUUIwysMPxspL+yGQM28o+Mli2EkNdsF8bdXzauLbw8h 44 | pZBkb7y1WIOs1jMbAVFp9qSRaXMISMLQjouBUatNNSj0Pi2qiAYEl9z+A12fTX9a 45 | excQ5Xk4YnRzweDHADGDtFeAdHRqLX4frghbLFG14Ou3XCLZhcnWGOW2fi37bDTz 46 | nQKCAQEA0382uhZ0SVEFAn6OY7mcHPCu8L9WBN6Kbm8ns4gv3gzRdK17dOy3Ke4f 47 | nvxbHSxp/wl00Rv+IJ3hh6NzXrvVR4GirsRAiDVJqfykTFufarU9aQi0y8jBXZAV 48 | El+5/6NbrcpBrg18M87Zdt3HgxtVQ5AdRwO0TGHz7CQTTqsVivZRV8pwbVu7BUyB 49 | zkSEBNOgEIq/anqcsDCXyY0zqWPuQMl+Kdup/dlb3XO17aofPgIYQ71EhWIyP9B+ 50 | 2+l0AZG70zTjpzGdGoQ6zNJ+TZ97g2QGIkYTpJgROtEO/0iqLOwTQ7lYduS83ecf 51 | gYUYmGKCW6VeLt36NTtonhp6/n2gCg== 52 | -----END PRIVATE KEY----- 53 | -------------------------------------------------------------------------------- /application/storage/app/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !public/ 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /application/storage/app/public/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /application/storage/framework/.gitignore: -------------------------------------------------------------------------------- 1 | compiled.php 2 | config.php 3 | down 4 | events.scanned.php 5 | maintenance.php 6 | routes.php 7 | routes.scanned.php 8 | schedule-* 9 | services.json 10 | -------------------------------------------------------------------------------- /application/storage/framework/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !data/ 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /application/storage/framework/cache/data/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /application/storage/framework/sessions/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /application/storage/framework/testing/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /application/storage/framework/views/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /application/storage/logs/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /application/tests/CreatesApplication.php: -------------------------------------------------------------------------------- 1 | make(Kernel::class)->bootstrap(); 19 | 20 | return $app; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /application/tests/Feature/ExampleTest.php: -------------------------------------------------------------------------------- 1 | get('/'); 18 | 19 | $response->assertStatus(200); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /application/tests/TestCase.php: -------------------------------------------------------------------------------- 1 | assertTrue(true); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /application/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import laravel from 'laravel-vite-plugin'; 3 | 4 | export default defineConfig({ 5 | plugins: [ 6 | laravel({ 7 | input: [ 8 | 'resources/sass/app.scss', 9 | 'resources/js/app.js', 10 | ], 11 | refresh: true, 12 | }), 13 | ], 14 | }); 15 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:8.1-apache-buster 2 | MAINTAINER Latheesan Kanesamoorthy (https://twitter.com/LatheesanK) 3 | 4 | # Update base system 5 | ARG DEBIAN_FRONTEND=noninteractive 6 | RUN apt-get update && \ 7 | apt-get install --no-install-recommends -y wget curl nano sudo libpng-dev libfreetype6-dev libzip-dev zip unzip 8 | RUN curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash - 9 | RUN sudo apt-get install -y nodejs 10 | 11 | # Install & enable required php libraries 12 | RUN pecl install -o -f redis \ 13 | && rm -rf /tmp/pear \ 14 | && docker-php-ext-enable redis \ 15 | && docker-php-ext-install -j$(nproc) pcntl zip mysqli pdo_mysql bcmath \ 16 | && docker-php-ext-configure gd --enable-gd --with-freetype \ 17 | && docker-php-ext-install -j$(nproc) gd 18 | 19 | # Configure php & apache 20 | RUN cp /usr/local/etc/php/php.ini-production php.ini && \ 21 | rm -rf /etc/apache2/sites-available/* && \ 22 | rm -rf /etc/apache2/sites-enabled/* && \ 23 | echo 'ServerName gatekeeper.app' >> /etc/apache2/apache2.conf 24 | COPY /docker/php.ini /usr/local/etc/php/conf.d/custom.ini 25 | COPY /docker/gatekeeper.app.conf /etc/apache2/sites-available/gatekeeper.app.conf 26 | RUN a2enmod rewrite ssl && \ 27 | a2ensite gatekeeper.app 28 | 29 | # Clean-up 30 | RUN sudo apt-get -y purge && sudo apt-get -y clean && \ 31 | sudo apt-get -y autoremove && sudo rm -rf /var/lib/apt/lists/* && \ 32 | sudo rm -rf /usr/bin/apt* 33 | 34 | # Create gatekeeper user 35 | RUN adduser --disabled-password --gecos '' gatekeeper && \ 36 | echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers && \ 37 | adduser gatekeeper sudo && \ 38 | chown -R gatekeeper:gatekeeper /home/gatekeeper/.* 39 | 40 | # Install composer globally 41 | RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" \ 42 | && php composer-setup.php \ 43 | && rm -f composer-setup.php \ 44 | && mv composer.phar /usr/local/bin/composer 45 | 46 | # Set gatekeeper user 47 | USER gatekeeper 48 | WORKDIR /home/gatekeeper/application 49 | 50 | # Expose apache port 51 | EXPOSE 443 52 | -------------------------------------------------------------------------------- /docker/docker-compose.custom.yml.example: -------------------------------------------------------------------------------- 1 | services: 2 | 3 | # Web Service 4 | gatekeeper-web: 5 | ports: [ "8020:443" ] 6 | 7 | # MySQL Service 8 | gatekeeper-mysql: 9 | environment: 10 | MYSQL_ROOT_PASSWORD: 123456 11 | MYSQL_DATABASE: gatekeeper 12 | MYSQL_USER: gatekeeper 13 | MYSQL_PASSWORD: 123456 14 | ports: [ "33020:3306" ] 15 | -------------------------------------------------------------------------------- /docker/docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | 3 | # Web Service 4 | gatekeeper-web: 5 | build: 6 | context: .. 7 | dockerfile: docker/Dockerfile 8 | container_name: gatekeeper-web 9 | restart: unless-stopped 10 | tty: true 11 | networks: [ local-gatekeeper ] 12 | volumes: 13 | - ../application:/home/gatekeeper/application 14 | depends_on: 15 | - gatekeeper-mysql 16 | ports: [ "443:443" ] 17 | 18 | # MySQL Service 19 | gatekeeper-mysql: 20 | image: mysql:8.0 21 | container_name: gatekeeper-mysql 22 | restart: unless-stopped 23 | tty: true 24 | networks: [ local-gatekeeper ] 25 | environment: 26 | MYSQL_ROOT_PASSWORD: 123456 27 | MYSQL_DATABASE: gatekeeper 28 | MYSQL_USER: gatekeeper 29 | MYSQL_PASSWORD: 123456 30 | healthcheck: 31 | test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"] 32 | interval: 5s 33 | timeout: 10s 34 | retries: 30 35 | volumes: [ "mysql:/var/lib/mysql:cached" ] 36 | 37 | # Redis Service 38 | gatekeeper-redis: 39 | container_name: gatekeeper-redis 40 | image: redis:6 41 | restart: always 42 | volumes: 43 | - redis:/data 44 | networks: [ local-gatekeeper ] 45 | 46 | volumes: 47 | mysql: 48 | driver: local 49 | redis: 50 | 51 | networks: 52 | local-gatekeeper: 53 | external: true 54 | -------------------------------------------------------------------------------- /docker/gatekeeper.app.conf: -------------------------------------------------------------------------------- 1 | ServerTokens Prod 2 | ServerSignature Off 3 | 4 | 5 | DocumentRoot "/home/gatekeeper/application/public" 6 | DirectoryIndex index.php 7 | RewriteEngine On 8 | 9 | SSLEngine on 10 | SSLCertificateFile /home/gatekeeper/application/ssl/cert.pem 11 | SSLCertificateKeyFile /home/gatekeeper/application/ssl/key.pem 12 | 13 | 14 | Options -MultiViews -Indexes 15 | 16 | 17 | CustomLog /dev/stdout combined 18 | ErrorLog /dev/stderr 19 | 20 | 21 | SSLOptions +StdEnvVars 22 | 23 | 24 | 25 | Options Includes FollowSymLinks MultiViews 26 | AllowOverride None 27 | Require all granted 28 | 29 | # Handle Authorization Header 30 | RewriteCond %{HTTP:Authorization} . 31 | RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] 32 | 33 | # Redirect Trailing Slashes If Not A Folder... 34 | RewriteCond %{REQUEST_FILENAME} !-d 35 | RewriteCond %{REQUEST_URI} (.+)/$ 36 | RewriteRule ^ %1 [L,R=301] 37 | 38 | # Handle Front Controller... 39 | RewriteCond %{REQUEST_FILENAME} !-d 40 | RewriteCond %{REQUEST_FILENAME} !-f 41 | RewriteRule ^ index.php [L] 42 | 43 | 44 | -------------------------------------------------------------------------------- /docker/php.ini: -------------------------------------------------------------------------------- 1 | [PHP] 2 | date.timezone=UTC 3 | memory_limit=256M 4 | upload_max_filesize=10M 5 | post_max_size=10M 6 | expose_php=Off 7 | -------------------------------------------------------------------------------- /docker/wait-for-mysql.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | while [ $(docker ps | grep -c "healthy.*gatekeeper-mysql$") == 0 ] 4 | do 5 | echo "Waiting for database to be healthy" 6 | sleep 5s 7 | done 8 | --------------------------------------------------------------------------------