diff --git a/Config/config.php b/Config/config.php new file mode 100644 index 0000000..3c82eb3 --- /dev/null +++ b/Config/config.php @@ -0,0 +1,8 @@ + 'CHJumpSeat' +]; diff --git a/Database/migrations/2024_07_22_064951_create_jumpseat_requests_table.php b/Database/migrations/2024_07_22_064951_create_jumpseat_requests_table.php new file mode 100644 index 0000000..e8b3723 --- /dev/null +++ b/Database/migrations/2024_07_22_064951_create_jumpseat_requests_table.php @@ -0,0 +1,41 @@ +id(); + $table->unsignedBigInteger('user_id'); + $table->string('airport_id'); + $table->integer('type')->default(0); // Automatic or Manual + $table->string('request_reason')->nullable(); + $table->string('deny_reason')->nullable(); + $table->integer('status')->default(0); + $table->unsignedBigInteger('approver_id')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('ch_jumpseat_requests'); + } +} diff --git a/Database/migrations/2024_08_13_051351_seed_jumpseat_settings.php b/Database/migrations/2024_08_13_051351_seed_jumpseat_settings.php new file mode 100644 index 0000000..2cd39cf --- /dev/null +++ b/Database/migrations/2024_08_13_051351_seed_jumpseat_settings.php @@ -0,0 +1,42 @@ + 'ch_jumpseat_price']); + $setting->id = 'ch_jumpseat_price'; + $setting->offset = 9000; + $setting->order = 9100; + $setting->key = 'ch_jupmpseat.price'; + $setting->name = 'User Price'; + $setting->value = 1000; + $setting->default = 1000; + $setting->group = 'chjumpseat'; + $setting->type = 'int'; + $setting->description = 'The price to set for a user created jumpseat with the decimal suppressed if the currency applies (e.g. $1000.00 would be entered as "100000")'; + $setting->save(); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + // + } +} diff --git a/Http/Controllers/.DS_Store b/Http/Controllers/.DS_Store new file mode 100644 index 0000000..af0ff30 Binary files /dev/null and b/Http/Controllers/.DS_Store differ diff --git a/Http/Controllers/Admin/AdminController.php b/Http/Controllers/Admin/AdminController.php new file mode 100644 index 0000000..a27a4c4 --- /dev/null +++ b/Http/Controllers/Admin/AdminController.php @@ -0,0 +1,49 @@ + CHJumpseatRequest::orderByDesc('created_at')->paginate(30)]); + } + + public function status(CHJumpseatRequest $id, Request $request) { + $status = $request->input('status'); + + $id->status = $status; + $id->approver_id = Auth::user()->id; + $id->save(); + + if ($request->input('status') == 1) { + // do the Jumpseat + $user = User::find($id->user_id); + + $user->curr_airport_id = $id->airport_id; + $user->save(); + Notification::send($id, new JumpSeatApproval($id)); + } + return response()->redirectToRoute('admin.chjumpseat.index'); + } +} diff --git a/Http/Controllers/Api/ApiController.php b/Http/Controllers/Api/ApiController.php new file mode 100644 index 0000000..b014b86 --- /dev/null +++ b/Http/Controllers/Api/ApiController.php @@ -0,0 +1,42 @@ +message('Hello, world!'); + } + + /** + * Handles /hello + * + * @param Request $request + * + * @return mixed + */ + public function hello(Request $request) + { + // Another way to return JSON, this for a custom response + // It's recommended to use Resources for responses from the database + return response()->json([ + 'name' => Auth::user()->name, + ]); + } + +} diff --git a/Http/Controllers/Frontend/IndexController.php b/Http/Controllers/Frontend/IndexController.php new file mode 100644 index 0000000..b614e9e --- /dev/null +++ b/Http/Controllers/Frontend/IndexController.php @@ -0,0 +1,104 @@ +middleware('auth'); + } + /** + * Display a listing of the resource. + * + * @param Request $request + * + * @return mixed + */ + public function index(Request $request) + { + $jsrs = CHJumpseatRequest::where(['user_id' => Auth::user()->id])->orderByDesc('created_at')->paginate(20); + return view('chjumpseat::index', ['requests' => $jsrs]); + } + + /** + * Show the form for creating a new resource. + * + * @param Request $request + * + * @return mixed + */ + public function create(Request $request) + { + $price = new Money(setting('ch_jumpseat.price', 100000)); + return view('chjumpseat::create', ['price' => $price]); + } + + /** + * Store a newly created resource in storage. + * + * @param Request $request + * + * @return mixed + */ + public function store(Request $request) + { + $data = $request->all(); + if (!isset($data['airport_id'])) { + Flash::error("No Airport Selected! Please select an airport before submitting a request."); + return response()->redirectToRoute('chjumpseat.create'); + } + + // If pay for immediate Jumpseat, check the balance and apply to journal, then move the pilot. + $user = User::find(Auth::user()->id); + $data['user_id'] = Auth::user()->id; + $curr_apt = $user->curr_airport_id; + + if ($curr_apt === $data['airport_id']) { + Flash::info("Jumpseat Request Cancelled. You're already at this airport!"); + return response()->redirectToRoute('frontend.dashboard.index'); + } + $jumpseat_price = new Money(setting('ch_jumpseat.price', 100000)); + $journal = optional($user->journal)->balance ?? new Money(0); + // Create a new jumpseat Request + $jsr = CHJumpseatRequest::create($data); + if ($jsr->type == 1 && $journal->getValue() >= $jumpseat_price->getValue()) { + + $user->curr_airport_id = $data['airport_id']; + $user->save(); + $jsr->status = 1; + $jsr->save(); + $this->financeService->debitFromJournal($user->journal, $jumpseat_price, $jsr, "Jumpseat: {$curr_apt}->{$data['airport_id']}", null, null); + Flash::success("Jumpseat Request Fulfilled"); + return response()->redirectToRoute('frontend.dashboard.index'); + } else { + $jsr->type = 0; + $jsr->save(); + Notification::send($jsr, new JumpSeatRequested($jsr)); + $add = ""; + if (!($journal->getValue() >= $jumpseat_price->getValue())) { + Flash::warning("Jumpseat request created. However, you did not have sufficient funds to make a instant transfer. Your Request has been submitted for approval."); + } else { + Flash::info("Jumpseat request created."); + } + } + + return response()->redirectToRoute('chjumpseat.index'); + } +} diff --git a/Http/Routes/admin.php b/Http/Routes/admin.php new file mode 100644 index 0000000..cfc6193 --- /dev/null +++ b/Http/Routes/admin.php @@ -0,0 +1,5 @@ +name('index'); +Route::post('/{id}', 'AdminController@status')->name('status'); diff --git a/Http/Routes/api.php b/Http/Routes/api.php new file mode 100644 index 0000000..d3eb393 --- /dev/null +++ b/Http/Routes/api.php @@ -0,0 +1,17 @@ + []], function() { + Route::get('/', 'ApiController@index'); +}); + +/** + * This is required to have a valid API key + */ +Route::group(['middleware' => [ + 'api.auth' +]], function() { + Route::get('/hello', 'ApiController@hello'); +}); diff --git a/Http/Routes/web.php b/Http/Routes/web.php new file mode 100644 index 0000000..931975f --- /dev/null +++ b/Http/Routes/web.php @@ -0,0 +1,12 @@ +name('index'); +Route::get('/create', [\Modules\CHJumpSeat\Http\Controllers\Frontend\IndexController::class, 'create'])->name('create'); +Route::post('/', [\Modules\CHJumpSeat\Http\Controllers\Frontend\IndexController::class, 'store'])->name('store'); +/* + * To register a route that needs to be authentication, wrap it in a + * Route::group() with the auth middleware + */ +// Route::group(['middleware' => 'auth'], function() { +// Route::get('/', 'IndexController@index'); +// }) diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..9f1ec53 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,82 @@ +# Cardinal Horizon phpVMS Module Software License + +## Preamble + +This license agreement has been created with the goal of addressing the following concerns: + +* Licensing software for source accessible software (i.e. php is a interpreted language) when DRM and encryption are +practically impossible or too expensive to implement. +* The needs to modify the software to best suit the user with enough protections in place to prevent those modifications +and software from being redistributed. + +To make it simple to read and understand, the license agreement has been broken down into a more "question and answer" +format. The examples provided do not constitute all the situations that may be encountered for a specific clause in the +agreement, and therefore are only intended as guidance. + +If you have any questions, feel free to email taylor@cardinalhorizon.com. + +## Whom is this agreement applicable? + +This license agreement is by and between Taylor Broad, d/b/a Cardinal Horizon (Software Provider) and You (Licensee). + +## What does the agreement apply to? + +This agreement covers the access and use of the phpVMS addons developed by the Software Provider, in both binary and +source code for the addons that references this agreement in the documentation and/or version control, hereafter known as the “Software”. + +## What am I receiving? +The Licensee is receiving a license to use the Software for their personal use. + +The Licensee is **not** receiving ownership of the Software, nor is the Software being created under a work-for-hire agreement. + +## What can I do with the software? + +- Use the software as part of your phpVMS based website. +- Modify the software as needed/desired to best fit your needs. + - Modifications to the software fall under the same terms of this license agreement (i.e. Copy-left) + +## What can I NOT do with the software? + +Unless prior written consent has been obtained from the Software Provider, the following is not permitted. + +- Alteration or removal of any notices, including, but not limited to: copyright notices, attribution, and licenses. +- Use, reverse engineer, or modify for non-phpVMS based environments. +- Redistribute the original software and modifications to anyone other than yourself. + - If the Licensee desires to distribute the software to other individuals to perform work-for-hire for the Licensee, the Licensee +**shall** notify the Software Provider for approval. +- Commercial use, defined as any use where the Licensee receives a monetary benefit for use of the software. + - E.g. you charge a membership fee for your community. +- Advertise the software as their own proprietary (in-house, exclusive) software +- Obfuscate the origins of the software. + +## Am I responsible for anything? +The Licensee shall make the best effort to protect the access to the software to prevent its download and acquisition +by those who didn't accept this license agreement. + +In cases where there is a breach where the software is stolen, the Licensee shall notify the Software Provider as soon +as practical. + +## What if I violate the license agreement? +You will be allotted 15 days to cure the breach upon written notice or face immediate agreement termination. + +In the event of termination the Licensee must destroy any and all copies of the Software, and provide confirmation of +doing so within 7 days of termination of the agreement. + +## The other legal stuff +### Warranties + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +### Copyright Notice +Unless prior written permission is granted, The Licensee agrees to display an appropriate copyright and attribution notice +on the website where the software is used. + +### Modification + +The Software Provider retains the right to change the software agreement at any time. + +If the Software Provider notifies you of a change to the license terms, you have 30 days to accept the new license agreement. +Updating or continuing the use of the software constitutes acceptance of the new license terms. diff --git a/Listeners/TestEventListener.php b/Listeners/TestEventListener.php new file mode 100644 index 0000000..1da3596 --- /dev/null +++ b/Listeners/TestEventListener.php @@ -0,0 +1,21 @@ + 'integer', + 'type' => 'integer' + ]; + public function airport() { + return $this->belongsTo(Airport::class); + } + + public function user() { + return $this->belongsTo(User::class); + } + public function approver() { + return $this->belongsTo(User::class); + } +} diff --git a/Notifications/JumpSeatApproval.php b/Notifications/JumpSeatApproval.php new file mode 100644 index 0000000..303da36 --- /dev/null +++ b/Notifications/JumpSeatApproval.php @@ -0,0 +1,86 @@ +webhook(setting('notifications.discord_private_webhook_url')) + ->success() + ->title('Jumpseat Approved: '.$this->jsr->user->ident.' - '.$this->jsr->user->name_private) + ->author([ + 'name' => $this->jsr->approver->ident.' - '.$this->jsr->approver->name_private, + 'url' => '', + 'icon_url' => $this->jsr->approver->resolveAvatarUrl(), + ]) + ->fields([ + 'Current Airport' => $this->jsr->user->curr_airport_id, + 'Requested Airport' => $this->jsr->airport_id, + 'Reason' => $this->jsr->request_reason ?? "Not Filled" + ]); + } + + /** + * Get the mail representation of the notification. + * + * @param mixed $notifiable + * @return \Illuminate\Notifications\Messages\MailMessage + */ + public function toMail($notifiable) + { + return (new MailMessage) + ->line('The introduction to the notification.') + ->action('Notification Action', 'https://laravel.com') + ->line('Thank you for using our application!'); + } + + /** + * Get the array representation of the notification. + * + * @param mixed $notifiable + * @return array + */ + public function toArray($notifiable) + { + return [ + // + ]; + } +} diff --git a/Notifications/JumpSeatRequested.php b/Notifications/JumpSeatRequested.php new file mode 100644 index 0000000..dda1b26 --- /dev/null +++ b/Notifications/JumpSeatRequested.php @@ -0,0 +1,86 @@ +webhook(setting('notifications.discord_private_webhook_url')) + ->warning() + ->title('Jumpseat Requested: '.$this->jsr->user->ident.' - '.$this->jsr->user->name_private) + ->author([ + 'name' => $this->jsr->user->ident.' - '.$this->jsr->user->name_private, + 'url' => '', + 'icon_url' => $this->jsr->user->resolveAvatarUrl(), + ]) + ->fields([ + 'Current Airport' => $this->jsr->user->curr_airport_id, + 'Requested Airport' => $this->jsr->airport_id, + 'Reason' => $this->jsr->request_reason ?? "Not Filled" + ]); + } + + /** + * Get the mail representation of the notification. + * + * @param mixed $notifiable + * @return \Illuminate\Notifications\Messages\MailMessage + */ + public function toMail($notifiable) + { + return (new MailMessage) + ->line('The introduction to the notification.') + ->action('Notification Action', 'https://laravel.com') + ->line('Thank you for using our application!'); + } + + /** + * Get the array representation of the notification. + * + * @param mixed $notifiable + * @return array + */ + public function toArray($notifiable) + { + return [ + // + ]; + } +} diff --git a/Providers/AppServiceProvider.php b/Providers/AppServiceProvider.php new file mode 100644 index 0000000..5b5070d --- /dev/null +++ b/Providers/AppServiceProvider.php @@ -0,0 +1,93 @@ +moduleSvc = app('App\Services\ModuleService'); + + $this->registerTranslations(); + $this->registerConfig(); + $this->registerViews(); + + $this->registerLinks(); + + // Uncomment this if you have migrations + // $this->loadMigrationsFrom(__DIR__ . '/../$MIGRATIONS_PATH$'); + } + + /** + * Register the service provider. + */ + public function register() + { + // + } + + /** + * Add module links here + */ + public function registerLinks(): void + { + // Show this link if logged in + $this->moduleSvc->addFrontendLink('JumpSeat', '/chjumpseat/', '', $logged_in=true); + + // Admin links: + $this->moduleSvc->addAdminLink('JumpSeat', '/admin/chjumpseat'); + } + + /** + * Register config. + */ + protected function registerConfig() + { + $this->publishes([ + __DIR__.'/../Config/config.php' => config_path('chjumpseat.php'), + ], 'chjumpseat'); + + $this->mergeConfigFrom(__DIR__.'/../Config/config.php', 'chjumpseat'); + } + + /** + * Register views. + */ + public function registerViews() + { + $viewPath = resource_path('views/modules/chjumpseat'); + $sourcePath = __DIR__.'/../Resources/views'; + + $this->publishes([$sourcePath => $viewPath],'views'); + + $this->loadViewsFrom(array_merge(array_map(function ($path) { + return $path . '/modules/chjumpseat'; + }, \Config::get('view.paths')), [$sourcePath]), 'chjumpseat'); + } + + /** + * Register translations. + */ + public function registerTranslations() + { + $langPath = resource_path('lang/modules/chjumpseat'); + + if (is_dir($langPath)) { + $this->loadTranslationsFrom($langPath, 'chjumpseat'); + } else { + $this->loadTranslationsFrom(__DIR__ .'/../Resources/lang', 'chjumpseat'); + } + } +} diff --git a/Providers/EventServiceProvider.php b/Providers/EventServiceProvider.php new file mode 100644 index 0000000..24786f3 --- /dev/null +++ b/Providers/EventServiceProvider.php @@ -0,0 +1,25 @@ + [TestEventListener::class], + ]; + + /** + * Register any events for your application. + */ + public function boot() + { + parent::boot(); + } +} diff --git a/Providers/RouteServiceProvider.php b/Providers/RouteServiceProvider.php new file mode 100644 index 0000000..abb037b --- /dev/null +++ b/Providers/RouteServiceProvider.php @@ -0,0 +1,95 @@ +registerWebRoutes(); + $this->registerAdminRoutes(); + $this->registerApiRoutes(); + } + + /** + * + */ + protected function registerWebRoutes(): void + { + $config = [ + 'as' => 'chjumpseat.', + 'prefix' => 'chjumpseat', + 'namespace' => $this->namespace.'\Frontend', + 'middleware' => ['web'], + ]; + + Route::group($config, function() { + $this->loadRoutesFrom(__DIR__.'/../Http/Routes/web.php'); + }); + } + + protected function registerAdminRoutes(): void + { + $config = [ + 'as' => 'admin.chjumpseat.', + 'prefix' => 'admin/chjumpseat', + 'namespace' => $this->namespace.'\Admin', + 'middleware' => ['web', 'ability:admin,admin-access'], + ]; + + Route::group($config, function() { + $this->loadRoutesFrom(__DIR__.'/../Http/Routes/admin.php'); + }); + } + + /** + * Register any API routes your module has. Remove this if you aren't using any + */ + protected function registerApiRoutes(): void + { + $config = [ + 'as' => 'api.chjumpseat.', + 'prefix' => 'api/chjumpseat', + 'namespace' => $this->namespace.'\Api', + 'middleware' => ['api'], + ]; + + Route::group($config, function() { + $this->loadRoutesFrom(__DIR__.'/../Http/Routes/api.php'); + }); + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..e9007a4 --- /dev/null +++ b/README.md @@ -0,0 +1,14 @@ +# CHJumpSeat +The CHJumpSeat Module is a very simple module that allows pilots to request free Jumpseats or paid ones. For more information, visit https://cardinalhorizon.com/products/modules/jumpseat +## License Agreement +By downloading and running this software, you agree to the terms and conditions outlined by the Cardinal Horizon phpVMS +Module Software License. A copy is included in this repository +## Installation +To download, go to the releases section, as seen on the right, then download the binary. + +Once Downloaded, extract the module folder, with this readme included, into your phpVMS modules folder. + +Once extracted, go into your phpVMS admin panel and enable the module. If the module doesn't appear, clear +your application cache under Admin > Maintenance. + +Once enabled, force a update to push through the database migrations. diff --git a/Resources/.DS_Store b/Resources/.DS_Store new file mode 100644 index 0000000..41329a8 Binary files /dev/null and b/Resources/.DS_Store differ diff --git a/Resources/views/.DS_Store b/Resources/views/.DS_Store new file mode 100644 index 0000000..aa3899d Binary files /dev/null and b/Resources/views/.DS_Store differ diff --git a/Resources/views/admin/index.blade.php b/Resources/views/admin/index.blade.php new file mode 100644 index 0000000..c024656 --- /dev/null +++ b/Resources/views/admin/index.blade.php @@ -0,0 +1,105 @@ +@extends('chjumpseat::layouts.admin') + +@section('title', 'CHJumpSeat') + +@section('content') +
Created On | +Type | +Airport | +Request Reason | +Status | +
---|---|---|---|---|
+ {{$req->created_at}} + | +
+ @php
+ $color = 'badge-info';
+ if($req->type === 0) {
+ $color = 'badge-warning';
+ $text = "Request";
+ } elseif ($req->type === 1) {
+ $color = 'badge-info';
+ $text = "Self";
+ } else {
+ $color = 'badge-secondary';
+ $text = "Unknown";
+ }
+ @endphp
+ {{ $text }}
+ |
+ + {{$req->airport->id}} - {{$req->airport->name}} + | ++ {{$req->request_reason}} + | +
+ @php
+ $color = 'badge-info';
+ if($req->status === 0) {
+ $color = 'badge-warning';
+ $text = "Pending";
+ } elseif ($req->status === 1) {
+ $color = 'badge-success';
+ $text = "Accepted";
+ } elseif ($req->status === 2) {
+ $color = 'badge-danger';
+ $text = "Rejected";
+ }
+ @endphp
+ {{ $text }}
+ |
+