Compare commits

...
Author SHA1 Message Date
snyk-bot b60251e7b8 fix: package.json & package-lock.json to reduce vulnerabilities
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-JS-AXIOS-1579269
2021-09-06 01:31:47 +00:00
B.Fatih KOZandNabeel S 6eef246b6a Improve Map features (GeoJson) (#1290)
* Update GeoService.php

Replace double name usage with more valuable info

* Update GeoService.php

Changed `GS` with `Spd` 'cause it may be IAS or GS according to the phase of the flight.

Co-authored-by: Nabeel S <nabeelio@users.noreply.github.com>
2021-08-30 13:00:27 -04:00
B.Fatih KOZandNabeel S fc8ca69729 Fix Private Profile fields not being editable (#1289)
* Update ProfileController.php

* Update UserRepository.php

* Update UserRepository.php

Co-authored-by: Nabeel S <nabeelio@users.noreply.github.com>
2021-08-30 12:55:33 -04:00
B.Fatih KOZ 57277c558d Fix Fare not properly set in controller (#1291)
* Update FareService.php

* Update PirepController.php

Move the logic to controller as requested

* Update FareService.php

Reverting back the service changes as requested

* StyleFix
2021-08-30 12:50:04 -04:00
B.Fatih KOZ b0f52ed1d0 Remove SimBrief with Bid (#1283)
* Update BidService.php

* Update BidService.php

Delete directly ;)
2021-08-13 14:39:18 -04:00
Khendal d64e674c21 Update pirep_card.blade.php (#1282)
Added Pilot name in card to know who have submitted the pirep.
2021-08-13 11:51:52 -04:00
B.Fatih KOZ 40c911ff17 Add web middleware to laravel-widgets (#1281)
Title says it all ... Added web middleware to laravel-widgets config so the widget auto-reload / auto-refresh options can work with full functionality.
2021-08-12 08:40:41 -04:00
B.Fatih KOZ b6abe8dd5b Bid check for flights/show (#1278)
* Update FlightController.php

Add missing $bid to flight/show so when needed Load In Acars button can be placed there too.

* Update show.blade.php

Add Load In Acars button to flight details page (flights/show.blade.php)
2021-08-10 13:23:43 -04:00
Nabeel Shahzad 54c3c8f346 Fix user id check in template 2021-08-09 18:12:48 -04:00
Nabeel S c250fce84b Cast the user ID to int in the sb model (#1277) 2021-08-09 13:54:40 -04:00
Nabeel S 4125cdd373 Launch ACARS link on the simbrief page #1272 (#1276) 2021-08-09 12:01:27 -04:00
Nabeel S 6eb883de73 Dependency updates (#1274)
* Dependency updates

* Downgrade league/commonmark to 1.6
2021-08-08 11:48:45 -04:00
Nabeel Shahzad 49528c9644 Remove tar-to-zip dep 2021-08-07 12:58:31 -04:00
Nabeel Shahzad 4475813a12 remove dotfiles again 2021-08-07 12:51:13 -04:00
Nabeel Shahzad 681e39650b Try to fix dots in tar 2021-08-07 12:24:15 -04:00
Nabeel Shahzad bbbe8a8bd3 remove base dir 2021-08-07 12:16:16 -04:00
Nabeel Shahzad 0acab21712 Fix cd 2021-08-07 11:54:33 -04:00
Nabeel Shahzad a17840d690 Another zip fix attempt 2021-08-07 11:46:43 -04:00
Nabeel Shahzad fe87f21599 Restrict zip to current dir 2021-08-07 11:37:26 -04:00
Nabeel Shahzad 2756c34702 Try fixing zip 2021-08-07 11:34:06 -04:00
Nabeel S 564f9ec06e Add ability to launch acars for flight/bid #1272 (#1273)
* Add ability to launch acars for flight/bid #1272

* Check for module active/inactive status

* Just some formatting
2021-08-06 14:53:04 -04:00
Nabeel S 95e1df619e Trap exception/check for module existing on disk #1211 (#1271)
* Trap exception/check for module existing on disk #1211

* Style fixes
2021-08-06 13:30:24 -04:00
B.Fatih KOZandNabeel Shahzad 4a04d73d08 Update DownloadController.php (#1267)
* Update DownloadController.php

* StyleFix

* Another StyleFix !

* Update DownloadController.php

* Last StyleFix and Update

* Apply StyleCI fix

Co-authored-by: Nabeel Shahzad <nabeel@nabeel.sh>
2021-08-02 14:43:38 -04:00
Nabeel S 88a8ffe48a Check for blank values on import and omit them (#1266)
* Check for blank values on import and omit them

* Add paused status to the pirep changed
2021-07-22 15:56:03 -04:00
Nabeel Shahzad 78d3c6b577 Fix where for paused 2021-07-22 12:47:55 -04:00
Nabeel S f42a41286d Add a 'paused' state (#1265)
* Add a 'paused' state

* Check paused status
2021-07-22 11:59:52 -04:00
Nabeel S f8ded4e410 Update dependencies (#1262)
* Update dependencies

* Rebuild assets with updated deps
2021-07-21 10:43:36 -04:00
B.Fatih KOZ 6304f3cab4 Fix PirepService (#1261)
`$fv['name']` should be used instead of `$fv->name` due to the nature of source data.

Fixes #1260
2021-07-16 13:24:31 -04:00
Nabeel Shahzad 4eb73ab5f3 Update 3rd party services config 2021-07-13 14:38:42 -04:00
Nabeel Shahzad 2af72d137d Have emails go into the logs by default, make note to users to change that #1259 2021-07-12 17:18:28 -04:00
Nabeel Shahzad f68e068f87 Use MAIL_MAILER #1259 2021-07-12 13:50:36 -04:00
Nabeel Shahzad 89f9706520 Include all dotfiles in zip 2021-07-12 12:47:24 -04:00
Nabeel Shahzad cde6fb314b Add footer requirement into license explicitly 2021-06-20 09:07:13 -04:00
Nabeel Shahzad 1a944f320c Make the license extra clear 2021-06-19 15:58:56 -04:00
Nabeel Shahzad d7ad2bdd11 Fix PIREP rejected message #1246 2021-06-18 13:21:59 -04:00
Nabeel Shahzad 24fca650f8 Use old notification handler for admin notifications 2021-06-18 11:33:02 -04:00
Nabeel S 35359b8646 Create endpoint to load bid by ID (#1248)
* Create endpoint to load bid by ID

* Fix endpoint
2021-06-17 19:42:56 -04:00
B.Fatih KOZ b6a2fe405d Update AcarsRepository (#1245)
* Update AcarsRepository.php

Use `where` instead of `whereDate` , as you know whereDate only checks the date, it skips the time part completely, thus not providing correct live pireps collection.

* Switch from created_at to updated_at

Also remove the UTC conversion 'cause `updated_at` field is not being populated with UTC values.
2021-06-14 07:05:28 -04:00
Nabeel Shahzad bd9c429cfc Don't force status to airborne 2021-06-13 15:28:57 -04:00
Nabeel Shahzad b18f1bbe6c Fix paginate on airlines 2021-06-11 09:40:42 -04:00
Andrew Roberts a5a21fb2fd Fix missing colon on ./vendor volume (#1243)
docker up throwing error due to missing colon on the ./vendor folder
2021-06-11 08:46:37 -04:00
Nabeel Shahzad 845514da2c Fix tests 2021-06-10 13:13:09 -04:00
Nabeel Shahzad 531489bfde Remove pagination from getting airlines 2021-06-10 12:41:23 -04:00
Nabeel Shahzad 601a17e1e6 Blank out links for ACARS 2021-06-10 11:08:25 -04:00
Nabeel Shahzad 3be8b1c4b9 undo links 2021-06-10 10:09:28 -04:00
Nabeel Shahzad b4e33bb37e Update akaunting money library 2021-06-10 09:08:41 -04:00
Nabeel Shahzad a8e6f1191b Fix the missing links array 2021-06-10 08:46:56 -04:00
Nabeel Shahzad 3a0140c2d0 Don't use backup for cache key on dependencies 2021-06-09 16:29:57 -04:00
Nabeel Shahzad 547b159570 Zip file generation 2021-06-09 16:22:25 -04:00
Nabeel Shahzad cab5c03c0b Generate zip file differently 2021-06-09 16:17:06 -04:00
B.Fatih KOZandNabeel S 777a696b35 Use Profile Route for Author URL (Discord Messages) (#1227)
* Update PirepStatusChanged.php

Use profile route for author url instead of pirep

* Update PirepSubmitted.php

Use profile route for author url instead of pirep

* Update PirepPrefiled.php

Use profile route for author instead of pirep

Co-authored-by: Nabeel S <nabeelio@users.noreply.github.com>
2021-06-09 11:43:08 -04:00
B.Fatih KOZandNabeel S 90344fb6e6 Simbrief Edit & Download Latest OFP (#1228)
* SimBrief OFP Edit

Changes aim to have ability of editing generated SimBrief Flight Plans and re-downloading.

* Move url to config

* Blade update and $uri change

* Update simbrief_form.blade.php

Used `$flight->id` along with `$user->ident` to have a more unique static id value. No details given for that fields uniqueness requirements, this will be ok I think though. 
Also we are passing user's simbrief userid with api to find the flight plan, both combined, no chance to get another users plan and/or any other plan of same user.

* Update SimBriefController.php

Move `static_id` to controller

* Update simbrief_form.blade.php

Read `static_id` from controller

* StyleFix

* Update phpvms.php

* Update SimBriefService.php

Co-authored-by: Nabeel S <nabeelio@users.noreply.github.com>
2021-06-09 11:38:14 -04:00
Nabeel S 7481dab012 Remove extraneous data from API response; force API to en (#1241)
* Remove extraneous data from API response; force API to en

* Style fixes

* Remove

* Fix the meta block

* Style fixes
2021-06-09 11:20:25 -04:00
Nabeel Shahzad dead1cfd0f Style fixes 2021-06-08 17:49:42 -04:00
Nabeel Shahzad 544462f420 Fix environment mapping 2021-06-08 17:46:15 -04:00
B.Fatih KOZandNabeel S a31201dfd3 Fix secstohhmm typecast for PHP 8 (#1230)
* Update helpers.php

Looks like php8 is not liking numeric strings as we like them.

Fixes #1229

* StyleFix

Co-authored-by: Nabeel S <nabeelio@users.noreply.github.com>
2021-06-08 11:00:18 -04:00
Andrew Roberts 9d336c1140 Fix typo (#1234) 2021-06-08 10:45:17 -04:00
Nabeel S 96d33c11c8 Fix data not showing up for live map (#1223)
* Fix data not showing up for live map

* StyleCI fixes
2021-06-04 16:57:27 -04:00
48cada2053 Fixed German Translation (#1222)
* New Language: German

* Update German K

* Fixed German Translation

Co-authored-by: derrobin154 <derrobin154@gmail.com>
Co-authored-by: Nabeel S <nabeelio@users.noreply.github.com>
2021-06-04 16:53:11 -04:00
Nabeel Shahzad 2a05013f66 Discord: Only add fields and footer if they're not empty 2021-06-04 15:16:36 -04:00
Nabeel Shahzad 4267648da5 Fields/fares not being saved properly 2021-06-04 15:06:19 -04:00
Nabeel Shahzad 5ada327236 Log request/response for Discord 2021-06-04 13:57:16 -04:00
Nabeel S 82825ef77b Add setting for recording IP address (#1221)
* Add setting for recording IP address

* Record IP on registration
2021-06-04 13:20:33 -04:00
Nabeel S db532e0f16 Remove the IP address from being recorded (#1220) 2021-06-04 12:10:40 -04:00
Nabeel S 9b2e466b7e Discord notifications for PIREP and News events #433 (#1215)
* Discord notifications for events #433

* Style fixes

* Check for blank webhook urls and disable

* Cleanup items after review

* Changes and fixes

* Style fixes

* Don't load env for testing

* Fix status text

* Refactor saving fields/fares so events get the latest data

* Cleanup

* Style fixes
2021-06-04 10:51:59 -04:00
B.Fatih KOZandNabeel S 17447c6903 Block aircraft with Simbrief (#1213)
* Block Aircraft with SimBrief

Changes aim to have the ability to block an aircraft's usage if it is used to generate a SimBrief OFP.

Unused/Expired briefings will be deleted by cron like before but will now be checked by HourlyCron, so admins can define more precise restrictions for them (and the blockage period of their aircraft)

Owner of the SimBrief OFP will be able to start a flight with acars using that particular aircraft, but pilots will get an Aircraft Not Available error (similar to Aircraft State check)

To prevent SimBrief OFP packs being marked as expired/unused, during pirep prefile, pirep_id will be saved to SimBrief model along with flight_id.

And when a flight is finished (pirep file), flight_id will be removed from SimBrief model as before. Only pirep_id will remain and aircraft will be available for another OFP generation.

* Update PirepController

In case a pirep is being saved/submitted with manual entry (but the va is using simbrief effectively) same logic should be applied during save/submit button selection.
Save will act like a pirep prefile , Submit will be pirep file.

* Manual Pirep Checks

Manual pireps, prefiled from a generated simbrief should be checked too. Also pirep.show blade's submit button should provide the same simbrief checks.

* Update PirepService.php

* Change settings and move sb cron to hourly

* StyleFix (SimBriefService)

* Another StyleFix (SimBriefService)

* Update SimBriefController

Removed null check of pirep_id for aircraft list generation to prevent live flights' aircraft being listed for another ofp generation.
( Active acars flights will have both flight_id and pirep_id at simbrief table)

* Update PirepService.php

Co-authored-by: Nabeel S <nabeelio@users.noreply.github.com>
2021-06-03 16:17:16 -04:00
B.Fatih KOZandNabeel S ad86e996d7 Fix Aircraft Dropdown Grouping (#1216)
* Update PirepController

Frontend : Use `$subfleet->type`  instead of `name` for grouping aircraft list.

* Update PirepController (Admin)

Use `$subfleet->type` instead of `name` here too , for grouping aircraft list according to their subfleets.

Co-authored-by: Nabeel S <nabeelio@users.noreply.github.com>
2021-06-03 14:41:31 -04:00
Robinandderrobin154 5a5fd02dac New Language: German (#1218)
* New Language: German

* Update German K

Co-authored-by: derrobin154 <derrobin154@gmail.com>
2021-06-03 11:46:32 -04:00
Nabeel Shahzad 97f095e4e9 Set default env name to "production" and debug to false 2021-05-29 18:13:36 -04:00
Jesper Falk a2f91b6754 Add GD PHP Extension for docker container (#1212) 2021-05-28 08:48:52 -04:00
Nabeel Shahzad a9e5554dea Trap migration error for modules if they don't exist 2021-05-27 16:14:13 -04:00
B.Fatih KOZ 5b10dca868 Fix SimBriefService (#1209)
* Fix removeExpiredEntires()

* Fix SimBriefTest

No need to add `'pirep_id' => ''` , just let it be `null` (like the core code does) and no need to convert Carbon to DateTimeString.
2021-05-27 09:27:38 -04:00
B.Fatih KOZandNabeel S 11cebb3e6e Show only used Flight Types at Flight Search (#1207)
* Update Flight Controller

Show only used flight types in the search form instead of all IATA Flight Types.

* PrePR StyleFix

* StyleFix

* Use Model instead of DB

Also used the same $where array

Co-authored-by: Nabeel S <nabeelio@users.noreply.github.com>
2021-05-25 16:14:42 -04:00
B.Fatih KOZ 96c17d33bc Add ability to search Flights by Code (#1205)
* Edit Search Form

Add ability to search via flight/route code (technically it was possible but the field was not present in the blade)

* Add Code (EN)

* Add Code (ES)

* Add Code (IT)

* Add Code (PT-BR)

* FlightCode => Code

* FlightCode => Code

* FlightCode => Code

* FlightCode => Code

* FlightCode => Code
2021-05-25 15:33:02 -04:00
Nabeel Shahzad 9abc892698 Update README 2021-05-25 15:11:09 -04:00
Nabeel Shahzad 67a51da220 Cleanup the docker-compose for local testing 2021-05-25 15:07:04 -04:00
Nabeel Shahzad 7e586a273a File/dir ownership in container 2021-05-25 13:37:53 -04:00
Nabeel Shahzad 8a591c11a5 Fix docker-compose for use non-locally 2021-05-25 13:14:08 -04:00
B.Fatih KOZ 728b033675 Fix Admin: UserController and Flight Edit (#1204)
* Fix Admin / UserController

Use Role Repository and `selectBoxList` method instead of the model itself.

* Fix Flight Fields

Added empty/blank option for clearing out the days when needed

* StyleFix
2021-05-24 19:13:44 -04:00
B.Fatih KOZ 68a6ed24cb Fix Flight Importer (#1202)
* Handle Route and Level fields too during import.
* Also removed the check for `visible => true` from `firstorNew` 'cause va admin may be importing to update not visible flights too.
  (by default all new flights are visible, so no affect on new flights)

Closes #1201
2021-05-24 15:19:06 -04:00
Nabeel S 7d8a34645e Add --force flag to migrations (#1203) 2021-05-24 10:58:10 -04:00
B.Fatih KOZ 4985da991b Remove Scripts (#1200)
* Remove Scripts

Remove scripts from create blade which are designed for edit blade in the first place (thus looking for flight object and its properties). 
No need to have an eyecandy date picker or a days selector, previous working version of create.blade was more than enough to manually add fights.

* Add placeholder for date fields
2021-05-23 20:47:15 -04:00
Nabeel Shahzad c94358350a Add queue worker setting so those aren't run in cron if there's a worker 2021-05-21 12:31:32 -04:00
Nabeel S d3ec0f4de3 5 char ICAO support #1052 (#1198) 2021-05-21 10:52:47 -04:00
Nabeel S ede5b74383 Make fuel_used an optional field in PIREP file (#1197) 2021-05-21 10:02:07 -04:00
Nabeel S e7bbd6cccb Use queues for notifications #1154 (#1174)
* Use queues for notifications #1154

* Styles

* Update defaults

* Include queueworker
2021-05-20 11:54:07 -04:00
Nabeel S edcea258ce Remove the autoupdater #1133 (#1195)
* Remove the autoupdater #1133

* Remove self-updater package

* Package still needed :|
2021-05-20 11:37:27 -04:00
Nabeel Shahzad 5adc0b4072 Remove unused import 2021-05-20 09:20:12 -04:00
Nabeel Shahzad fe7120e21a Add PHP 7.4 polyfill 2021-05-20 08:55:55 -04:00
B.Fatih KOZ 30be7c245c Fix Advanced Fuel Calculations (#1190)
* Fix Advanced Fuel Calculations

Current code works fine with a fresh submitted pirep but when a re-calculation is needed, fuel amount will not be correct if the aircraft was flown later on.
Commit provides fix for such re-calculation scenarios.

* StyleFix for the Comment Line !
2021-05-19 19:01:33 -04:00
Nabeel S 7cb9ae4ec3 intl dependency/polyfill updates (#1194) 2021-05-19 13:57:28 -04:00
OlliandNabeel S 6f1a5f1dc2 Update Flight Controller to fix an issue (#1192)
Closes #1180 and #1156. But now the script isn't beeing loaded

Co-authored-by: Nabeel S <nabeelio@users.noreply.github.com>
2021-05-19 13:42:40 -04:00
Nabeel Shahzad e3c3c81d19 Include missing scripts in flight create 2021-05-19 13:33:13 -04:00
Nabeel S 352f1ee9f8 Days::in should use in_mask; tests coverage #1180 (#1193) 2021-05-19 11:20:50 -04:00
Nabeel Shahzad dff4273c72 Force empty to be 0 for in_mask #1180 2021-05-19 10:22:15 -04:00
Nabeel S 77a4acb569 Update dependencies (#1188) 2021-05-14 13:02:47 -04:00
B.Fatih KOZ ff6827b368 Fix Installer Completed Blade (#1185)
We do not have a config.php anymore, page should refer to env.php for base level configuration.
2021-05-14 12:31:24 -04:00
B.Fatih KOZ 594d0eb222 Check Aircraft Availability (#1177)
* Check Aircraft Availability before Prefile

Check if the aircraft is available for flight (State : Parked / On Ground). If not throw new exception AircraftNotAvailable

* Add Exception

AircraftNotAvailable exception, used by PirepService during prefile checks.
2021-05-13 15:26:53 -04:00
Nabeel S ee61de62fa Update to PHP 8 (#1118)
* Update to PHP 8

* Update composer file

* Remove PHP 8 incompatible library

* Remove PHP 8 incompatible library
2021-05-11 18:46:21 -04:00
B.Fatih KOZ f2be14402f PirepCancelled Event (Cron Expired Pirep Deletion) (#1178)
* PirepCancelled Event (Cron Expired Pirep Deletion)

While deleting frozen in progress pireps we should at least send a PirepCancelled event for those who are listening.

(or we may have a new PirepDeleted event to be issued in such cases, I think PirepCancelled is enough though)

* StyleFix

* Another StyleFix

* Missing Log

Of course it will not be possible to write log entries without the Log itself.
2021-05-09 18:30:50 -04:00
B.Fatih KOZ a186bfae2a Fix AirportController (Frontend) (#1176)
1. Inbound and Outbound flights were displaying all flights (active and inactive) , fixed to fetch only active flights.
2. Flash message and redirect to Dashboard was not working, instead we were getting 404 Not Found error.
2021-05-08 15:42:18 -04:00
Nabeel S 6e6ba85080 Fix for duplicated/wrong expenses being applied #915 (#1173)
* Fix for duplicated/wrong expenses being applied #915

* Style fixes
2021-05-06 12:42:56 -04:00
B.Fatih KOZandNabeel Shahzad d2272e32a6 100LL and MOGAS Fuel Cost (#1172)
* Add defaults for 100LL and MOGAS prices

Add missing defaults for 100LL and MOGAS Fuel prices.

* Calculate Fuel Cost according to Fuel Type

Calculate pirep fuel costs according to aircraft (subfleet) fuel type, use JetA as fail safe default.

* Fix for Failing AwardTest

At least attempting to fix

* Fix fuel types logic

* Style

* Invert the logic

Co-authored-by: Nabeel Shahzad <nabeel@nabeel.sh>
2021-05-06 08:16:33 -04:00
Nabeel S b6c0946795 Delete users without PIREPS; pilot leave fix (#1171)
* Delete users without PIREPS; pilot leave fix

* Style fixes
2021-05-05 09:56:28 -04:00
Andrew Roberts fca04e6b0c Allow session lifetime to be set in env variables (#1170)
* Allow session lifetime to be set in env variables

Currently the session live time defaults to 2 hours, adding an env variable will allow us to change this on a per site basis

* Add whitespace for consistency
2021-05-05 08:51:18 -04:00
Andrew Roberts 5094fce4b0 Disable autocomplete on admin email field (#1169)
* Disable autocomplete on admin email field

Fixes #1165

* Disable autocomplete on admin name and password

For sake of completeness and due to some over zealous autocompleters, also disable it on name and password fields
2021-05-04 13:26:16 -04:00
Nabeel S fe0ef60b6c Block refiling of PIREPs (#1167)
* Block refiling of PIREPs

* Style fixes

* Package updates
2021-05-03 10:00:10 -04:00
B.Fatih KOZ 5094375e13 Fixes For GDPR/Deleted Users (#1164)
* Fix DeletedUsers being displayed at Homepage

Deleted users should not be displayed at homepage / newest pilots list. PR fixes that problem.

* Do Not Display Deleted Users with LatestPireps

As the main page, Latest Pilots widget should not display GDPR/Soft deleted users too.

* StyleFix
2021-05-03 08:46:21 -04:00
315 changed files with 33053 additions and 55496 deletions
+15 -2
View File
@@ -30,6 +30,7 @@ declare -a remove_files=(
intellij_style.xml
config.php
docker-compose.yml
docker-compose.local.yml
Makefile
phpcs.xml
phpunit.xml
@@ -69,13 +70,25 @@ mkdir -p storage/framework/views
cd /tmp
echo "Current directory contents"
ls -al $BASE_DIR
echo "Parent directory contents"
ls -al $BASE_DIR/../
tar -czf $TAR_NAME -C $BASE_DIR .
echo "Calling find"
find $BASE_DIR/../ -type d -maxdepth 2
tar -czf $TAR_NAME -C $BASE_DIR --xform 's:^\./::' . [--show-transformed-names|--show-stored-names]
sha256sum $TAR_NAME >"$TAR_NAME.sha256"
tar2zip $TAR_NAME
cd $BASE_DIR;
zip -r $ZIP_NAME ./
sha256sum $ZIP_NAME >"$ZIP_NAME.sha256"
mv $ZIP_NAME /tmp
mv "$ZIP_NAME.sha256" /tmp
ls -al /tmp
echo "Moving to dist"
+1 -4
View File
@@ -7,7 +7,7 @@ jobs:
strategy:
fail-fast: true
matrix:
php-versions: ['7.3', '7.4']
php-versions: ['7.3', '7.4', '8.0']
name: PHP ${{ matrix.php-versions }}
env:
extensions: intl, pcov, mbstring
@@ -41,7 +41,6 @@ jobs:
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: ${{ runner.os }}-composer-
# Configure PHP
- name: Setup PHP
@@ -117,7 +116,6 @@ jobs:
- name: 'Install Release Dependencies'
run: |
rm -rf vendor
sudo npm i tar-to-zip -g
composer install --no-dev --prefer-dist --no-interaction --verbose
sudo chmod +x ./.github/scripts/*
@@ -180,7 +178,6 @@ jobs:
- name: 'Install Release Dependencies'
run: |
rm -rf vendor
sudo npm i tar-to-zip -g
composer install --no-dev --prefer-dist --no-interaction --verbose
sudo chmod +x ./.github/scripts/*
+4 -1
View File
@@ -2,7 +2,7 @@ FROM php:7.4-fpm-alpine
WORKDIR /var/www/
RUN apk add gmp-dev icu-dev
RUN apk add gmp-dev icu-dev zlib-dev libpng-dev
RUN curl --silent --show-error https://getcomposer.org/installer | php
# Copy any config files in
@@ -14,6 +14,7 @@ RUN docker-php-ext-install \
calendar \
intl \
pdo_mysql \
gd \
gmp \
opcache && \
docker-php-ext-enable pdo_mysql opcache
@@ -26,4 +27,6 @@ RUN php composer.phar install \
--no-scripts \
--prefer-dist
RUN chown -R www-data:www-data /var/www
EXPOSE 9000
+3 -2
View File
@@ -1,5 +1,3 @@
BSD 3-Clause License
Copyright (c) 2017, phpvms - http://www.phpvms.net
All rights reserved.
@@ -17,6 +15,9 @@ modification, are permitted provided that the following conditions are met:
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
* A "powered by phpvms" is required in page footers, unless the license has
purchased to omit it.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+3 -20
View File
@@ -82,10 +82,6 @@ test:
phpcs:
@vendor/bin/php-cs-fixer fix --config=.php_cs -v --diff --diff-format=udiff --dry-run
#.PHONY: phpstan
#phpstan:
# vendor/bin/phpstan analyse -c phpstan.neon -v --level 2 app
.PHONY: replay-acars
replay-acars:
#@php artisan phpvms:replay AAL10,AAL3113,BAW172,DAL988,FIN6,MSR986 --manual
@@ -95,11 +91,6 @@ replay-acars:
sass-watch:
sass --watch public/assets/admin/sass/paper-dashboard.scss:public/assets/admin/css/paper-dashboard.css
.PHONY: schema
schema:
#php artisan infyom:scaffold Aircraft --fieldsFile=database/schema/aircraft.json
echo ""
.PHONY: deploy-package
deploy-package:
./.travis/deploy_script.sh
@@ -109,17 +100,9 @@ reset-installer:
@php artisan database:create --reset
@php artisan migrate:refresh --seed
.PHONY: docker
docker:
@mkdir -p $(CURR_PATH)/tmp/mysql
-docker rm -f phpvms
docker build -t phpvms .
docker run --name=phpvms \
-v $(CURR_PATH):/var/www/ \
-v $(CURR_PATH)/tmp/mysql:/var/lib/mysql \
-p 8080:80 \
phpvms
.PHONY: docker-test
docker-test:
@docker-compose -f docker-compose.yml -f docker-compose.local.yml up
.PHONY: docker-clean
docker-clean:
+9 -6
View File
@@ -30,15 +30,16 @@ A full distribution, with all of the composer dependencies, is available at this
[View installation details](https://docs.phpvms.net/installation/installation)
## Development Environment
## Development Environment with Docker
A full development environment can be brought up using Docker:
A full development environment can be brought up using Docker, without having to install composer/npm locally
```bash
composer install
npm install
docker-compose build
docker-compose up
make docker-test
# **OR** with docker-compose directly
docker-compose -f docker-compose.yml -f docker-compose.local.yml up
```
Then go to `http://localhost`. If you're using dnsmasq, the `app` container is listening on `phpvms.test`, or you can add to your `/etc/hosts` file:
@@ -47,6 +48,8 @@ Then go to `http://localhost`. If you're using dnsmasq, the `app` container is l
127.0.0.1 phpvms.test
```
The `docker-compose.local.yml` overrides the `app` section in `docker-compose.yml`. The standard `docker-compose.yml` can be used if you want to deploy from the image, or as a template for your own Dockerized deployments.
### Building JS/CSS assets
Yarn is required, run:
+32
View File
@@ -0,0 +1,32 @@
<?php
namespace App\Console\Commands;
use App;
use App\Contracts\Command;
class EmailTest extends Command
{
protected $signature = 'phpvms:email-test';
protected $description = 'Send a test notification to admins';
/**
* Run dev related commands
*
* @throws \Symfony\Component\HttpFoundation\File\Exception\FileException
*/
public function handle()
{
/** @var App\Notifications\NotificationEventsHandler $eventHandler */
$eventHandler = app(App\Notifications\NotificationEventsHandler::class);
$news = new App\Models\News();
$news->user_id = 1;
$news->subject = 'Test News';
$news->body = 'Test Body';
$news->save();
$newsEvent = new App\Events\NewsAdded($news);
$eventHandler->onNewsAdded($newsEvent);
}
}
+38
View File
@@ -0,0 +1,38 @@
<?php
namespace App\Console\Commands;
use App;
use App\Contracts\Command;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Log;
class ProcessQueue extends Command
{
protected $signature = 'queue:cron';
protected $description = 'Process the queue from a cron job';
/**
* Run the queue tasks
*/
public function handle()
{
Artisan::call('queue:work', [
'--sansdaemon' => null,
'--stop-when-empty' => null,
]);
Log::info(Artisan::output());
///** @var App\Support\WorkCommand $queueWorker */
//$queueWorker = new App\Support\WorkCommand(app('queue.worker'), app('cache.store'));
//$queueWorker->setInput($queueWorker->createInputFromArguments([]));
//$queueWorker->handle();
/*$output = $this->call('queue:work', [
'--stop-when-empty' => null,
]);
Log::info($output);*/
}
}
+25
View File
@@ -0,0 +1,25 @@
<?php
namespace App\Console\Cron;
use App\Contracts\Command;
use Illuminate\Support\Facades\Artisan;
/**
* This just calls the CronHourly event, so all of the
* listeners, etc can just be called to run those tasks
*/
class JobQueue extends Command
{
protected $signature = 'cron:queue';
protected $description = 'Run the cron queue tasks';
protected $schedule;
public function handle(): void
{
$this->redirectLoggingToFile('cron');
Artisan::call('queue:cron');
$this->info(Artisan::output());
}
}
+8
View File
@@ -3,6 +3,7 @@
namespace App\Console;
use App\Console\Cron\Hourly;
use App\Console\Cron\JobQueue;
use App\Console\Cron\Monthly;
use App\Console\Cron\Nightly;
use App\Console\Cron\Weekly;
@@ -25,6 +26,13 @@ class Kernel extends ConsoleKernel
*/
protected function schedule(Schedule $schedule): void
{
// If not using the queue worker then run those via cron
if (!config('queue.worker', false)) {
$schedule->command(JobQueue::class)
->everyMinute()
->withoutOverlapping();
}
$schedule->command(Nightly::class)->dailyAt('01:00');
$schedule->command(Weekly::class)->weeklyOn(0);
$schedule->command(Monthly::class)->monthlyOn(1);
+2 -2
View File
@@ -24,9 +24,9 @@ abstract class Command extends \Illuminate\Console\Command
parent::__construct();
// Running in the console but not in the tests
if (app()->runningInConsole() && env('APP_ENV') !== 'testing') {
/*if (app()->runningInConsole() && env('APP_ENV') !== 'testing') {
$this->redirectLoggingToFile('stdout');
}
}*/
}
/**
+5
View File
@@ -170,6 +170,11 @@ class ImportExport
}
foreach ($split_values as $value) {
$value = trim($value);
if ($value === '') {
continue;
}
// This isn't in the query string format, so it's
// just a straight key-value pair set
if (strpos($value, '?') === false) {
+12 -4
View File
@@ -2,9 +2,9 @@
namespace App\Contracts;
use App\Notifications\Channels\Discord\DiscordMessage;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Log;
class Notification extends \Illuminate\Notifications\Notification implements ShouldQueue
{
@@ -17,14 +17,14 @@ class Notification extends \Illuminate\Notifications\Notification implements Sho
{
// Look in the notifications.channels config and see where this particular
// notification can go. Map it to $channels
$klass = static::class;
/*$klass = static::class;
$notif_config = config('notifications.channels', []);
if (!array_key_exists($klass, $notif_config)) {
Log::error('Notification type '.$klass.' missing from notifications config, defaulting to mail');
return;
}
$this->channels = $notif_config[$klass];
$this->channels = $notif_config[$klass];*/
}
/**
@@ -34,8 +34,16 @@ class Notification extends \Illuminate\Notifications\Notification implements Sho
*
* @return array
*/
public function via($notifiable)
/*public function via($notifiable)
{
return $this->channels;
}*/
/**
* @return DiscordMessage|null
*/
public function toDiscordChannel($notifiable): ?DiscordMessage
{
return null;
}
}
+12 -11
View File
@@ -2,11 +2,14 @@
namespace App\Contracts;
use Exception;
use Illuminate\Validation\Validator;
use function is_array;
use Prettus\Repository\Eloquent\BaseRepository;
use Prettus\Repository\Exceptions\RepositoryException;
/**
* @mixin \Prettus\Repository\Eloquent\BaseRepository
* @mixin BaseRepository
*/
abstract class Repository extends BaseRepository
{
@@ -20,8 +23,8 @@ abstract class Repository extends BaseRepository
{
try {
return $this->find($id, $columns);
} catch (\Exception $e) {
return;
} catch (Exception $e) {
return null;
}
}
@@ -32,11 +35,7 @@ abstract class Repository extends BaseRepository
*/
public function validate($values)
{
$validator = Validator::make(
$values,
$this->model()->rules
);
$validator = Validator::make($values, $this->model()->rules);
if ($validator->fails()) {
return $validator->messages();
}
@@ -50,6 +49,8 @@ abstract class Repository extends BaseRepository
* @param int $count
* @param string $sort_by created_at (default) or updated_at
*
* @throws RepositoryException
*
* @return mixed
*/
public function recent($count = null, $sort_by = 'created_at')
@@ -71,7 +72,7 @@ abstract class Repository extends BaseRepository
return $this->scopeQuery(function ($query) use ($where, $sort_by, $order_by) {
$q = $query->where($where);
// See if there are multi-column sorts
if (\is_array($sort_by)) {
if (is_array($sort_by)) {
foreach ($sort_by as $key => $sort) {
$q = $q->orderBy($key, $sort);
}
@@ -98,7 +99,7 @@ abstract class Repository extends BaseRepository
return $this->scopeQuery(function ($query) use ($col, $values, $sort_by, $order_by) {
$q = $query->whereNotIn($col, $values);
// See if there are multi-column sorts
if (\is_array($sort_by)) {
if (is_array($sort_by)) {
foreach ($sort_by as $key => $sort) {
$q = $q->orderBy($key, $sort);
}
@@ -118,7 +119,7 @@ abstract class Repository extends BaseRepository
* @param array $columns
* @param string $method
*
* @throws \Prettus\Repository\Exceptions\RepositoryException
* @throws RepositoryException
*
* @return mixed
*/
+27
View File
@@ -2,7 +2,10 @@
namespace App\Contracts;
use App\Support\Resources\CustomAnonymousResourceCollection;
use App\Support\Resources\CustomPaginatedResourceResponse;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Pagination\AbstractPaginator;
/**
* Base class for a resource/response
@@ -26,4 +29,28 @@ class Resource extends JsonResource
}
}
}
/**
* Customize the response to exclude all the extra data that isn't used. Based on:
* https://gist.github.com/derekphilipau/4be52164a69ce487dcd0673656d280da
*
* @param \Illuminate\Http\Request $request
*
* @return \Illuminate\Http\JsonResponse
*/
public function toResponse($request)
{
return $this->resource instanceof AbstractPaginator
? (new CustomPaginatedResourceResponse($this))->toResponse($request)
: parent::toResponse($request);
}
public static function collection($resource)
{
return tap(new CustomAnonymousResourceCollection($resource, static::class), function ($collection) {
if (property_exists(static::class, 'preserveKeys')) {
$collection->preserveKeys = (new static([]))->preserveKeys === true;
}
});
}
}
@@ -1,9 +1,9 @@
<?php
namespace App\Cron\Nightly;
namespace App\Cron\Hourly;
use App\Contracts\Listener;
use App\Events\CronNightly;
use App\Events\CronHourly;
use App\Services\SimBriefService;
use Illuminate\Support\Facades\Log;
@@ -22,9 +22,9 @@ class ClearExpiredSimbrief extends Listener
/**
* @param \App\Events\CronNightly $event
*/
public function handle(CronNightly $event): void
public function handle(CronHourly $event): void
{
Log::info('Nightly: Removing expired Simbrief entries');
Log::info('Hourly: Removing expired Simbrief entries');
$this->simbriefSvc->removeExpiredEntries();
}
}
+5 -1
View File
@@ -5,6 +5,7 @@ namespace App\Cron\Hourly;
use App\Contracts\Listener;
use App\Events\CronHourly;
use App\Models\Enums\PirepState;
use App\Models\Enums\PirepStatus;
use App\Models\Pirep;
use App\Services\PirepService;
use Carbon\Carbon;
@@ -38,7 +39,10 @@ class DeletePireps extends Listener
protected function deletePireps(int $expire_time_hours, int $state)
{
$dt = Carbon::now('UTC')->subHours($expire_time_hours);
$pireps = Pirep::where('created_at', '<', $dt)->where(['state' => $state])->get();
$pireps = Pirep::where('created_at', '<', $dt)
->where(['state' => $state])
->where('status', '<>', PirepStatus::PAUSED)
->get();
/** @var PirepService $pirepSvc */
$pirepSvc = app(PirepService::class);
+10 -2
View File
@@ -4,9 +4,11 @@ namespace App\Cron\Hourly;
use App\Contracts\Listener;
use App\Events\CronHourly;
use App\Events\PirepCancelled;
use App\Models\Enums\PirepState;
use App\Models\Pirep;
use Carbon\Carbon;
use Illuminate\Support\Facades\Log;
/**
* Remove expired live flights
@@ -27,8 +29,14 @@ class RemoveExpiredLiveFlights extends Listener
}
$date = Carbon::now('UTC')->subHours(setting('acars.live_time'));
Pirep::where('updated_at', '<', $date)
$pireps = Pirep::where('updated_at', '<', $date)
->where('state', PirepState::IN_PROGRESS)
->delete();
->get();
foreach ($pireps as $pirep) {
event(new PirepCancelled($pirep));
Log::info('Cron: Deleting Expired Live PIREP id='.$pirep->id.', state='.PirepState::label($pirep->state));
$pirep->delete();
}
}
}
-3
View File
@@ -7,9 +7,6 @@ use App\Events\CronNightly;
use App\Services\VersionService;
use Illuminate\Support\Facades\Log;
/**
* Determine if any pilots should be set to ON LEAVE status
*/
class NewVersionCheck extends Listener
{
private $versionSvc;
+1 -1
View File
@@ -11,7 +11,7 @@ if (!function_exists('createFactoryICAO')) {
$characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
$max = strlen($characters) - 1;
$string = '';
for ($i = 0; $i < 4; $i++) {
for ($i = 0; $i < 5; $i++) {
try {
$string .= $characters[random_int(0, $max)];
} catch (Exception $e) {
@@ -0,0 +1,26 @@
<?php
use App\Contracts\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
/**
* Increase Airport ICAO size to 5 chars
* https://github.com/nabeelio/phpvms/issues/1052
*/
class IncreaseIcaoSizes extends Migration
{
public function up()
{
Schema::table('airports', function (Blueprint $table) {
$table->string('iata', 5)->change();
$table->string('icao', 5)->change();
});
Schema::table('pireps', function (Blueprint $table) {
$table->string('dpt_airport_id', 5)->change();
$table->string('arr_airport_id', 5)->change();
$table->string('alt_airport_id', 5)->change();
});
}
}
@@ -0,0 +1,19 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
class RemoveSettingSimbriefExpireDays extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
DB::table('settings')
->where(['key' => 'simbrief.expire_days'])
->delete();
}
}
@@ -0,0 +1,32 @@
<?php
use App\Contracts\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
class DiscordFields extends Migration
{
public function up()
{
// Delete the old Discord fields and then a webhook will get added
DB::table('settings')
->where(['key' => 'notifications.discord_api_key'])
->delete();
DB::table('settings')
->where(['key' => 'notifications.discord_public_channel_id'])
->delete();
DB::table('settings')
->where(['key' => 'notifications.discord_public_channel_id'])
->delete();
// Add a field to the user to enter their own Discord ID
Schema::table('users', function (Blueprint $table) {
$table->string('discord_id')
->default('')
->after('rank_id');
});
}
}
@@ -0,0 +1,18 @@
<?php
use App\Contracts\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class DiscordPrivateChannelId extends Migration
{
public function up()
{
// Add a field to the user to enter their own Discord ID
Schema::table('users', function (Blueprint $table) {
$table->string('discord_private_channel_id')
->default('')
->after('discord_id');
});
}
}
+10 -533
View File
@@ -1,519 +1,5 @@
acars:
- id: aM8QpMPEB15e
pirep_id: b68R5gwVzpVe
type: '0'
nav_type: null
order: '0'
name: null
log: null
lat: '17.934117958358'
lon: '-76.77856721815'
heading: '124'
altitude: '8.1663703232199'
vs: '0'
gs: '0.0064996252497234'
transponder: null
autopilot: null
fuel_flow: null
sim_time: now
created_at: 'now'
updated_at: 'now'
- id: eZ6VJ3xj8vge
pirep_id: b68R5gwVzpVe
type: '0'
nav_type: null
order: '0'
name: null
log: null
lat: '17.934117498661'
lon: '-76.778566516289'
heading: '124'
altitude: '8.1664622222087'
vs: '0'
gs: '0'
transponder: null
autopilot: null
fuel_flow: null
sim_time: now
created_at: 'now'
updated_at: 'now'
- id: bo2QqDLl2lLa
pirep_id: b68R5gwVzpVe
type: '0'
nav_type: null
order: '0'
name: null
log: null
lat: '17.933884143334'
lon: '-76.778210172233'
heading: '124'
altitude: '8.1707802663933'
vs: '0'
gs: '14.419195209695'
transponder: null
autopilot: null
fuel_flow: null
sim_time: now
created_at: 'now'
updated_at: 'now'
- id: aM8QpM4qV2Be
pirep_id: b68R5gwVzpVe
type: '0'
nav_type: null
order: '0'
name: null
log: null
lat: '17.933705349611'
lon: '-76.777936938059'
heading: '124'
altitude: '8.170089899507'
vs: '0'
gs: '15.392330039049'
transponder: null
autopilot: null
fuel_flow: null
sim_time: now
created_at: 'now'
updated_at: 'now'
- id: dR6oxR9qjoOd
pirep_id: b68R5gwVzpVe
type: '0'
nav_type: null
order: '0'
name: null
log: null
lat: '17.933130691906'
lon: '-76.777152737985'
heading: '158'
altitude: '8.1683570286331'
vs: '0'
gs: '10.450467475023'
transponder: null
autopilot: null
fuel_flow: null
sim_time: now
created_at: 'now'
updated_at: 'now'
- id: erkRwJODRvEa
pirep_id: b68R5gwVzpVe
type: '0'
nav_type: null
order: '0'
name: null
log: null
lat: '17.932580370289'
lon: '-76.777450200122'
heading: '221'
altitude: '8.1695896791822'
vs: '0'
gs: '12.932334300694'
transponder: null
autopilot: null
fuel_flow: null
sim_time: now
created_at: 'now'
updated_at: 'now'
- id: b2kv53VMy7Ad
pirep_id: b68R5gwVzpVe
type: '0'
nav_type: null
order: '0'
name: null
log: null
lat: '17.932012550651'
lon: '-76.778009113329'
heading: '296'
altitude: '8.1683852467213'
vs: '0'
gs: '10.502719192825'
transponder: null
autopilot: null
fuel_flow: null
sim_time: now
created_at: 'now'
updated_at: 'now'
- id: eXDoE14JxAve
pirep_id: b68R5gwVzpVe
type: '0'
nav_type: null
order: '0'
name: null
log: null
lat: '17.932052237309'
lon: '-76.778111495187'
heading: '290'
altitude: '8.1719753739837'
vs: '0'
gs: '12.643743277153'
transponder: null
autopilot: null
fuel_flow: null
sim_time: now
created_at: 'now'
updated_at: 'now'
- id: b68R5gZ5YyOe
pirep_id: b68R5gwVzpVe
type: '0'
nav_type: null
order: '0'
name: null
log: null
lat: '17.932664026837'
lon: '-76.779705339967'
heading: '293'
altitude: '8.2203963962548'
vs: '0'
gs: '64.870244840054'
transponder: null
autopilot: null
fuel_flow: null
sim_time: now
created_at: 'now'
updated_at: 'now'
- id: bqxYvGNN5N0a
pirep_id: b68R5gwVzpVe
type: '0'
nav_type: null
order: '0'
name: null
log: null
lat: '17.934300220408'
lon: '-76.783954914814'
heading: '293'
altitude: '8.3302468103811'
vs: '0'
gs: '122.78529244208'
transponder: null
autopilot: null
fuel_flow: null
sim_time: now
created_at: 'now'
updated_at: 'now'
- id: en5rpBKw514d
pirep_id: b68R5gwVzpVe
type: '0'
nav_type: null
order: '0'
name: null
log: null
lat: '17.935736496556'
lon: '-76.78765616448'
heading: '293'
altitude: '49.554739665916'
vs: '21'
gs: '146.79677958636'
transponder: null
autopilot: null
fuel_flow: null
sim_time: now
created_at: 'now'
updated_at: 'now'
- id: erkRwJA29l2a
pirep_id: b68R5gwVzpVe
type: '0'
nav_type: null
order: '0'
name: null
log: null
lat: '17.976972983585'
lon: '-76.896548114872'
heading: '297'
altitude: '6228.684012199'
vs: '62'
gs: '281.03976876098'
transponder: null
autopilot: null
fuel_flow: null
sim_time: now
created_at: 'now'
updated_at: 'now'
- id: dR6oxR3r5nwd
pirep_id: b68R5gwVzpVe
type: '0'
nav_type: null
order: '0'
name: null
log: null
lat: '18.061360208405'
lon: '-77.056156629983'
heading: '301'
altitude: '12522.331716891'
vs: '25'
gs: '371.70833265547'
transponder: null
autopilot: null
fuel_flow: null
sim_time: now
created_at: 'now'
updated_at: 'now'
- id: e1wr82J877Zb
pirep_id: b68R5gwVzpVe
type: '0'
nav_type: null
order: '0'
name: null
log: null
lat: '18.17373613295'
lon: '-77.260347185993'
heading: '301'
altitude: '16802.15636323'
vs: '43'
gs: '421.37353688139'
transponder: null
autopilot: null
fuel_flow: null
sim_time: now
created_at: 'now'
updated_at: 'now'
- id: bqxYvGyExN2a
pirep_id: b68R5gwVzpVe
type: '0'
nav_type: null
order: '0'
name: null
log: null
lat: '18.193907285814'
lon: '-77.29810465405'
heading: '297'
altitude: '17395.953175647'
vs: '40'
gs: '425.98105291451'
transponder: null
autopilot: null
fuel_flow: null
sim_time: now
created_at: 'now'
updated_at: 'now'
- id: aQW0wQDQk5Md
pirep_id: b68R5gwVzpVe
type: '0'
nav_type: null
order: '0'
name: null
log: null
lat: '18.648921231143'
lon: '-78.014696613517'
heading: '323'
altitude: '37108.124381987'
vs: '22'
gs: '438.52028383384'
transponder: null
autopilot: null
fuel_flow: null
sim_time: now
created_at: 'now'
updated_at: 'now'
- id: e9rQ5lk3p63a
pirep_id: b68R5gwVzpVe
type: '0'
nav_type: null
order: '0'
name: null
log: null
lat: '18.796585259338'
lon: '-78.194734005052'
heading: '260'
altitude: '39718.178225504'
vs: '19'
gs: '432.41969107334'
transponder: null
autopilot: null
fuel_flow: null
sim_time: now
created_at: 'now'
updated_at: 'now'
- id: aQW0wQP8vZqd
pirep_id: b68R5gwVzpVe
type: '0'
nav_type: null
order: '0'
name: null
log: null
lat: '18.660552760063'
lon: '-78.407597067994'
heading: '242'
altitude: '40998.775690971'
vs: '-25'
gs: '449.51695639627'
transponder: null
autopilot: null
fuel_flow: null
sim_time: now
created_at: 'now'
updated_at: 'now'
- id: bqxYvGKgp32a
pirep_id: b68R5gwVzpVe
type: '0'
nav_type: null
order: '0'
name: null
log: null
lat: '18.592775999582'
lon: '-78.638711340118'
heading: '298'
altitude: '41060.297106821'
vs: '0'
gs: '427.14806185954'
transponder: null
autopilot: null
fuel_flow: null
sim_time: now
created_at: 'now'
updated_at: 'now'
- id: bqxYvGg2r02a
pirep_id: b68R5gwVzpVe
type: '0'
nav_type: null
order: '0'
name: null
log: null
lat: '18.718367591656'
lon: '-78.840980459275'
heading: '284'
altitude: '41065.55293844'
vs: '1'
gs: '424.89060056552'
transponder: null
autopilot: null
fuel_flow: null
sim_time: now
created_at: 'now'
updated_at: 'now'
- id: e0RV61wPj53b
pirep_id: b68R5gwVzpVe
type: '0'
nav_type: null
order: '0'
name: null
log: null
lat: '18.77448809677'
lon: '-79.082727467209'
heading: '283'
altitude: '41069.809333539'
vs: '0'
gs: '425.02780492483'
transponder: null
autopilot: null
fuel_flow: null
sim_time: now
created_at: 'now'
updated_at: 'now'
- id: dwpmBOo3wWwe
pirep_id: b68R5gwVzpVe
type: '0'
nav_type: null
order: '0'
name: null
log: null
lat: '18.813031600863'
lon: '-79.249543355636'
heading: '283'
altitude: '41072.545805172'
vs: '0'
gs: '425.04687332464'
transponder: null
autopilot: null
fuel_flow: null
sim_time: now
created_at: 'now'
updated_at: 'now'
- id: ejRqlxXoZPle
pirep_id: b68R5gwVzpVe
type: '0'
nav_type: null
order: '0'
name: null
log: null
lat: '18.840653974635'
lon: '-79.370042672541'
heading: '283'
altitude: '40396.200981621'
vs: '-16'
gs: '428.08364925851'
transponder: null
autopilot: null
fuel_flow: null
sim_time: now
created_at: 'now'
updated_at: 'now'
- id: eVOPBYw4q9Xa
pirep_id: b68R5gwVzpVe
type: '0'
nav_type: null
order: '0'
name: null
log: null
lat: '18.868358744495'
lon: '-79.491434926253'
heading: '283'
altitude: '39399.093383161'
vs: '-16'
gs: '427.57826037174'
transponder: null
autopilot: null
fuel_flow: null
sim_time: now
created_at: 'now'
updated_at: 'now'
- id: aOYyrOmVPzEd
pirep_id: b68R5gwVzpVe
type: '0'
nav_type: null
order: '0'
name: null
log: null
lat: '18.896191929474'
lon: '-79.613169477385'
heading: '284'
altitude: '36491.730092364'
vs: '-55'
gs: '429.74732238829'
transponder: null
autopilot: null
fuel_flow: null
sim_time: now
created_at: 'now'
updated_at: 'now'
- id: dL98oLQgyzje
pirep_id: b68R5gwVzpVe
type: '0'
nav_type: null
order: '0'
name: null
log: null
lat: '18.9242378342'
lon: '-79.736368411294'
heading: '284'
altitude: '32998.644142986'
vs: '-61'
gs: '434.90864533098'
transponder: null
autopilot: null
fuel_flow: null
sim_time: now
created_at: 'now'
updated_at: 'now'
- id: e9rQ5lqD2o8a
pirep_id: b68R5gwVzpVe
type: '0'
nav_type: null
order: '0'
name: null
log: null
lat: '18.952608029755'
lon: '-79.861339775352'
heading: '284'
altitude: '29069.168636089'
vs: '-68'
gs: '441.90475491564'
transponder: null
autopilot: null
fuel_flow: null
sim_time: now
created_at: 'now'
updated_at: 'now'
- id: av2oANWY1vma
pirep_id: b68R5gwVzpVe
- pirep_id: b68R5gwVzpVe
type: '0'
nav_type: null
order: '0'
@@ -550,8 +36,7 @@ acars:
sim_time: now
created_at: 'now'
updated_at: 'now'
- id: e1wr82gp8zGb
pirep_id: b68R5gwVzpVe
- pirep_id: b68R5gwVzpVe
type: '0'
nav_type: null
order: '0'
@@ -569,8 +54,7 @@ acars:
sim_time: now
created_at: 'now'
updated_at: 'now'
- id: dG65jDL2gK0b
pirep_id: b68R5gwVzpVe
- pirep_id: b68R5gwVzpVe
type: '0'
nav_type: null
order: '0'
@@ -588,8 +72,7 @@ acars:
sim_time: now
created_at: 'now'
updated_at: 'now'
- id: epYQ0ENmyvXa
pirep_id: b68R5gwVzpVe
- pirep_id: b68R5gwVzpVe
type: '0'
nav_type: null
order: '0'
@@ -607,8 +90,7 @@ acars:
sim_time: now
created_at: 'now'
updated_at: 'now'
- id: erkRwJPrVoEa
pirep_id: b68R5gwVzpVe
- pirep_id: b68R5gwVzpVe
type: '0'
nav_type: null
order: '0'
@@ -626,8 +108,7 @@ acars:
sim_time: now
created_at: 'now'
updated_at: 'now'
- id: e1wr82gZXqZb
pirep_id: b68R5gwVzpVe
- pirep_id: b68R5gwVzpVe
type: '0'
nav_type: null
order: '0'
@@ -645,8 +126,7 @@ acars:
sim_time: now
created_at: 'now'
updated_at: 'now'
- id: eER95AJ4XLga
pirep_id: b68R5gwVzpVe
- pirep_id: b68R5gwVzpVe
type: '0'
nav_type: null
order: '0'
@@ -664,8 +144,7 @@ acars:
sim_time: now
created_at: 'now'
updated_at: 'now'
- id: dPNZvP0O0Gza
pirep_id: b68R5gwVzpVe
- pirep_id: b68R5gwVzpVe
type: '0'
nav_type: null
order: '0'
@@ -683,8 +162,7 @@ acars:
sim_time: now
created_at: 'now'
updated_at: 'now'
- id: aQW0wQYVm2Yd
pirep_id: b68R5gwVzpVe
- pirep_id: b68R5gwVzpVe
type: '0'
nav_type: null
order: '0'
@@ -702,8 +180,7 @@ acars:
sim_time: now
created_at: 'now'
updated_at: 'now'
- id: eXDoE11x32We
pirep_id: b68R5gwVzpVe
- pirep_id: b68R5gwVzpVe
type: '0'
nav_type: null
order: '0'
+39 -11
View File
@@ -54,6 +54,13 @@
options: ''
type: text
description: 'Enter your Google Analytics Tracking ID'
- key: general.record_user_ip
name: 'Record user IP address'
group: general
value: true
options: ''
type: boolean
description: Record the user's IP address on register/login
- key: units.currency
name: 'Currency'
group: units
@@ -144,6 +151,20 @@
options:
type: text
description: If an airport's Jet A Fuel Cost isn't added, set this value by default
- key: airports.default_100ll_fuel_cost
name: 'Default 100LL Fuel Cost'
group: airports
value: 0.9
options:
type: text
description: If an airport's 100LL Fuel Cost isn't added, set this value by default
- key: airports.default_mogas_fuel_cost
name: 'Default MOGAS Fuel Cost'
group: airports
value: 0.8
options:
type: text
description: If an airport's MOGAS Fuel Cost isn't added, set this value by default
- key: bids.disable_flight_on_bid
name: 'Disable flight on bid'
group: bids
@@ -193,13 +214,13 @@
options: ''
type: boolean
description: 'Only allow briefs to be created for bidded flights'
- key: simbrief.expire_days
- key: simbrief.expire_hours
name: 'Simbrief Expire Time'
group: simbrief
value: 5
value: 6
options: ''
type: number
description: 'Days after how long to remove unused briefs'
description: 'Hours after how long to remove unused briefs'
- key: simbrief.noncharter_pax_weight
name: 'Non-Charter Passenger Weight'
group: simbrief
@@ -242,6 +263,13 @@
options: ''
type: boolean
description: 'Use privatized user name as SimBrief OFP captain name'
- key: simbrief.block_aircraft
name: 'Restrict Aircraft'
group: simbrief
value: false
options: ''
type: boolean
description: 'When enabled, an aircraft can only be used for one active SimBrief OFP and Flight/Pirep'
- key: pireps.duplicate_check_time
name: 'PIREP duplicate time check'
group: pireps
@@ -347,20 +375,20 @@
options: ''
type: boolean
description: 'Count transfer hours in calculations, like ranks and the total hours'
- key: notifications.discord_api_key
name: Discord API token
- key: notifications.discord_public_webhook_url
name: Discord Public Webhook URL
group: notifications
value: ''
options: ''
type: text
description: Discord API token for notifications
- key: 'notifications.discord_public_channel_id'
name: 'Discord Public Channel ID'
group: 'notifications'
description: The Discord Webhook URL for public notifications
- key: notifications.discord_private_webhook_url
name: Discord Private Webhook URL
group: notifications
value: ''
options: ''
type: 'text'
description: 'Discord public channel ID for broadcasat notifications'
type: text
description: The Discord Webhook URL for private notifications
- key: 'cron.random_id'
name: 'Cron Randomized ID'
group: 'cron'
+16
View File
@@ -0,0 +1,16 @@
<?php
namespace App\Events;
use App\Contracts\Event;
use App\Models\Pirep;
class PirepStateChange extends Event
{
public $pirep;
public function __construct(Pirep $pirep)
{
$this->pirep = $pirep;
}
}
+19
View File
@@ -0,0 +1,19 @@
<?php
namespace App\Events;
use App\Contracts\Event;
use App\Models\Pirep;
/**
* Status change like Boarding, Taxi, etc
*/
class PirepStatusChange extends Event
{
public $pirep;
public function __construct(Pirep $pirep)
{
$this->pirep = $pirep;
}
}
+38
View File
@@ -0,0 +1,38 @@
<?php
namespace App\Exceptions;
use App\Models\Aircraft;
class AircraftNotAvailable extends AbstractHttpException
{
public const MESSAGE = 'The aircraft is not available for flight';
private $aircraft;
public function __construct(Aircraft $aircraft)
{
$this->aircraft = $aircraft;
parent::__construct(
400,
static::MESSAGE
);
}
public function getErrorType(): string
{
return 'aircraft-not-available';
}
public function getErrorDetails(): string
{
return $this->getMessage();
}
public function getErrorMetadata(): array
{
return [
'aircraft_id' => $this->aircraft->id,
];
}
}
+44
View File
@@ -0,0 +1,44 @@
<?php
namespace App\Exceptions;
use App\Models\Pirep;
class PirepError extends AbstractHttpException
{
private $pirep;
private $error;
public function __construct(Pirep $pirep, string $error)
{
$this->error = $error;
$this->pirep = $pirep;
parent::__construct(400, $error);
}
/**
* Return the RFC 7807 error type (without the URL root)
*/
public function getErrorType(): string
{
return 'pirep-error';
}
/**
* Get the detailed error string
*/
public function getErrorDetails(): string
{
return $this->getMessage();
}
/**
* Return an array with the error details, merged with the RFC7807 response
*/
public function getErrorMetadata(): array
{
return [
'pirep_id' => $this->pirep->id,
];
}
}
@@ -152,7 +152,7 @@ class FlightController extends Controller
{
return view('admin.flights.create', [
'flight' => null,
'days' => [],
'days' => 0,
'flight_fields' => $this->flightFieldRepo->all(),
'airlines' => $this->airlineRepo->selectBoxList(),
'airports' => $this->airportRepo->selectBoxList(true, false),
@@ -7,29 +7,24 @@ use App\Repositories\KvpRepository;
use App\Services\CronService;
use App\Services\VersionService;
use App\Support\Utils;
use Codedge\Updater\UpdaterManager;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Log;
use Laracasts\Flash\Flash;
use Nwidart\Modules\Facades\Module;
class MaintenanceController extends Controller
{
private $cronSvc;
private $kvpRepo;
private $updateManager;
private $versionSvc;
public function __construct(
CronService $cronSvc,
KvpRepository $kvpRepo,
UpdaterManager $updateManager,
VersionService $versionSvc
) {
$this->cronSvc = $cronSvc;
$this->kvpRepo = $kvpRepo;
$this->updateManager = $updateManager;
$this->versionSvc = $versionSvc;
}
@@ -106,24 +101,6 @@ class MaintenanceController extends Controller
return redirect(route('admin.maintenance.index'));
}
/**
* Update the phpVMS install
*
* @param \Illuminate\Http\Request $request
*
* @return mixed
*/
public function update(Request $request)
{
$new_version_tag = $this->kvpRepo->get('latest_version_tag');
Log::info('Attempting to update to '.$new_version_tag);
$module = Module::find('updater');
$module->enable();
return redirect('/update/downloader');
}
/**
* Enable the cron, or if it's enabled, change the ID that is used
*
@@ -9,6 +9,7 @@ use Illuminate\Contracts\View\Factory;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Redirector;
use Illuminate\Support\Facades\Log;
use Illuminate\View\View;
class ModulesController extends Controller
@@ -29,6 +30,7 @@ class ModulesController extends Controller
{
$modules = $this->moduleSvc->getAllModules();
$new_modules = $this->moduleSvc->scan();
return view('admin.modules.index', [
'modules' => $modules,
'new_modules' => $new_modules,
@@ -97,7 +99,14 @@ class ModulesController extends Controller
*/
public function enable(Request $request)
{
$this->moduleSvc->addModule($request->input('name'));
$moduleName = $request->input('name');
try {
$this->moduleSvc->addModule($moduleName);
} catch (\Exception $e) {
Log::error('Error activating module '.$moduleName);
}
return redirect(route('admin.modules.index'));
}
@@ -9,6 +9,7 @@ use App\Models\Enums\PirepSource;
use App\Models\Enums\PirepState;
use App\Models\Pirep;
use App\Models\PirepComment;
use App\Models\PirepFare;
use App\Repositories\AircraftRepository;
use App\Repositories\AirlineRepository;
use App\Repositories\AirportRepository;
@@ -100,7 +101,7 @@ class PirepController extends Controller
$tmp[$ac->id] = $ac['name'].' - '.$ac['registration'];
}
$aircraft[$subfleet->name] = $tmp;
$aircraft[$subfleet->type] = $tmp;
}
return $aircraft;
@@ -151,10 +152,10 @@ class PirepController extends Controller
$count = $request->input($field_name);
}
$fares[] = [
$fares[] = new PirepFare([
'fare_id' => $fare->id,
'count' => $count,
];
]);
}
$this->fareSvc->saveForPirep($pirep, $fares);
@@ -6,12 +6,12 @@ use App\Contracts\Controller;
use App\Http\Requests\CreateUserRequest;
use App\Http\Requests\UpdateUserRequest;
use App\Models\Rank;
use App\Models\Role;
use App\Models\User;
use App\Models\UserAward;
use App\Repositories\AirlineRepository;
use App\Repositories\AirportRepository;
use App\Repositories\PirepRepository;
use App\Repositories\RoleRepository;
use App\Repositories\UserRepository;
use App\Services\UserService;
use App\Support\Timezonelist;
@@ -29,6 +29,7 @@ class UserController extends Controller
private $airlineRepo;
private $airportRepo;
private $pirepRepo;
private $roleRepo;
private $userRepo;
private $userSvc;
@@ -38,6 +39,7 @@ class UserController extends Controller
* @param AirlineRepository $airlineRepo
* @param AirportRepository $airportRepo
* @param PirepRepository $pirepRepo
* @param RoleRepository $roleRepo
* @param UserRepository $userRepo
* @param UserService $userSvc
*/
@@ -45,12 +47,14 @@ class UserController extends Controller
AirlineRepository $airlineRepo,
AirportRepository $airportRepo,
PirepRepository $pirepRepo,
RoleRepository $roleRepo,
UserRepository $userRepo,
UserService $userSvc
) {
$this->airlineRepo = $airlineRepo;
$this->airportRepo = $airportRepo;
$this->pirepRepo = $pirepRepo;
$this->roleRepo = $roleRepo;
$this->userSvc = $userSvc;
$this->userRepo = $userRepo;
}
@@ -88,6 +92,7 @@ class UserController extends Controller
->mapWithKeys(function ($item, $key) {
return [strtolower($item['alpha2']) => $item['name']];
});
$roles = $this->roleRepo->selectBoxList(false, true);
return view('admin.users.create', [
'user' => null,
@@ -98,7 +103,7 @@ class UserController extends Controller
'countries' => $countries,
'airports' => $airports,
'ranks' => Rank::all()->pluck('name', 'id'),
'roles' => Role::all()->pluck('name', 'id'),
'roles' => $roles,
]);
}
@@ -163,6 +168,7 @@ class UserController extends Controller
$airlines = $this->airlineRepo->selectBoxList();
$airports = $this->airportRepo->selectBoxList(false);
$roles = $this->roleRepo->selectBoxList(false, true);
return view('admin.users.edit', [
'user' => $user,
@@ -173,7 +179,7 @@ class UserController extends Controller
'airports' => $airports,
'airlines' => $airlines,
'ranks' => Rank::all()->pluck('name', 'id'),
'roles' => Role::all()->pluck('name', 'id'),
'roles' => $roles,
]);
}
+2 -2
View File
@@ -193,9 +193,9 @@ class AcarsController extends Controller
}
// Change the PIREP status if it's as SCHEDULED before
if ($pirep->status === PirepStatus::INITIATED) {
/*if ($pirep->status === PirepStatus::INITIATED) {
$pirep->status = PirepStatus::AIRBORNE;
}
}*/
$pirep->save();
@@ -16,9 +16,8 @@ class AirlineController extends Controller
*
* @param AirlineRepository $airlineRepo
*/
public function __construct(
AirlineRepository $airlineRepo
) {
public function __construct(AirlineRepository $airlineRepo)
{
$this->airlineRepo = $airlineRepo;
}
@@ -31,11 +30,9 @@ class AirlineController extends Controller
*/
public function index(Request $request)
{
$airports = $this->airlineRepo
->whereOrder(['active' => true], 'name', 'asc')
->paginate();
$airlines = $this->airlineRepo->whereOrder(['active' => true], 'name')->paginate();
return AirlineResource::collection($airports);
return AirlineResource::collection($airlines);
}
/**
+31 -34
View File
@@ -23,10 +23,10 @@ use App\Models\Enums\PirepFieldSource;
use App\Models\Enums\PirepSource;
use App\Models\Pirep;
use App\Models\PirepComment;
use App\Repositories\AcarsRepository;
use App\Models\PirepFare;
use App\Models\PirepFieldValue;
use App\Repositories\JournalRepository;
use App\Repositories\PirepRepository;
use App\Services\FareService;
use App\Services\Finance\PirepFinanceService;
use App\Services\PirepService;
use App\Services\UserService;
@@ -37,8 +37,6 @@ use Illuminate\Support\Facades\Log;
class PirepController extends Controller
{
private $acarsRepo;
private $fareSvc;
private $financeSvc;
private $journalRepo;
private $pirepRepo;
@@ -46,8 +44,6 @@ class PirepController extends Controller
private $userSvc;
/**
* @param AcarsRepository $acarsRepo
* @param FareService $fareSvc
* @param PirepFinanceService $financeSvc
* @param JournalRepository $journalRepo
* @param PirepRepository $pirepRepo
@@ -55,16 +51,12 @@ class PirepController extends Controller
* @param UserService $userSvc
*/
public function __construct(
AcarsRepository $acarsRepo,
FareService $fareSvc,
PirepFinanceService $financeSvc,
JournalRepository $journalRepo,
PirepRepository $pirepRepo,
PirepService $pirepSvc,
UserService $userSvc
) {
$this->acarsRepo = $acarsRepo;
$this->fareSvc = $fareSvc;
$this->financeSvc = $financeSvc;
$this->journalRepo = $journalRepo;
$this->pirepRepo = $pirepRepo;
@@ -101,7 +93,7 @@ class PirepController extends Controller
/**
* Check if a PIREP is cancelled
*
* @param $pirep
* @param Pirep $pirep
*
* @throws \App\Exceptions\PirepCancelled
*/
@@ -113,50 +105,52 @@ class PirepController extends Controller
}
/**
* @param $pirep
* @param Request $request
*
* @return PirepFieldValue[]
*/
protected function updateFields($pirep, Request $request)
protected function getFields(Request $request): ?array
{
if (!$request->filled('fields')) {
return;
return [];
}
$pirep_fields = [];
foreach ($request->input('fields') as $field_name => $field_value) {
$pirep_fields[] = [
$pirep_fields[] = new PirepFieldValue([
'name' => $field_name,
'value' => $field_value,
'source' => PirepFieldSource::ACARS,
];
]);
}
$this->pirepSvc->updateCustomFields($pirep->id, $pirep_fields);
return $pirep_fields;
}
/**
* Save the fares
*
* @param $pirep
* @param Request $request
*
* @throws \Exception
*
* @return PirepFare[]
*/
protected function updateFares($pirep, Request $request)
protected function getFares(Request $request): ?array
{
if (!$request->filled('fares')) {
return;
return [];
}
$fares = [];
foreach ($request->post('fares') as $fare) {
$fares[] = [
$fares[] = new PirepFare([
'fare_id' => $fare['id'],
'count' => $fare['count'],
];
]);
}
$this->fareSvc->saveForPirep($pirep, $fares);
return $fares;
}
/**
@@ -173,6 +167,7 @@ class PirepController extends Controller
'comments',
'flight',
'simbrief',
'position',
'user',
];
@@ -210,14 +205,13 @@ class PirepController extends Controller
$attrs = $this->parsePirep($request);
$attrs['source'] = PirepSource::ACARS;
$pirep = $this->pirepSvc->prefile($user, $attrs);
$fields = $this->getFields($request);
$fares = $this->getFares($request);
$pirep = $this->pirepSvc->prefile($user, $attrs, $fields, $fares);
Log::info('PIREP PREFILED');
Log::info($pirep->id);
$this->updateFields($pirep, $request);
$this->updateFares($pirep, $request);
return $this->get($pirep->id);
}
@@ -258,9 +252,9 @@ class PirepController extends Controller
}
}
$pirep = $this->pirepRepo->update($attrs, $pirep_id);
$this->updateFields($pirep, $request);
$this->updateFares($pirep, $request);
$fields = $this->getFields($request);
$fares = $this->getFares($request);
$pirep = $this->pirepSvc->update($pirep_id, $attrs, $fields, $fares);
event(new PirepUpdated($pirep));
@@ -303,11 +297,13 @@ class PirepController extends Controller
}
try {
$pirep = $this->pirepSvc->file($pirep, $attrs);
$this->updateFields($pirep, $request);
$this->updateFares($pirep, $request);
$fields = $this->getFields($request);
$fares = $this->getFares($request);
$pirep = $this->pirepSvc->file($pirep, $attrs, $fields, $fares);
} catch (\Exception $e) {
Log::error($e);
throw $e;
}
// See if there there is any route data posted
@@ -409,7 +405,8 @@ class PirepController extends Controller
$pirep = Pirep::find($pirep_id);
$this->checkCancelled($pirep);
$this->updateFields($pirep, $request);
$fields = $this->getFields($request);
$this->pirepSvc->updateCustomFields($pirep_id, $fields);
return new PirepFieldCollection($pirep->fields);
}
@@ -3,6 +3,7 @@
namespace App\Http\Controllers\Api;
use App\Contracts\Controller;
use App\Exceptions\Unauthorized;
use App\Exceptions\UserNotFound;
use App\Http\Resources\Bid as BidResource;
use App\Http\Resources\Pirep as PirepResource;
@@ -144,6 +145,28 @@ class UserController extends Controller
return BidResource::collection($bids);
}
/**
* Get a particular bid for a user
*
* @param $bid_id
* @param \Illuminate\Http\Request $request
*
* @return \App\Http\Resources\Bid
*/
public function get_bid($bid_id, Request $request)
{
/** @var \App\Models\User $user */
$user = Auth::user();
// Return the current bid
$bid = $this->bidSvc->getBid($user, $bid_id);
if ($bid->user_id !== $user->id) {
throw new Unauthorized(new \Exception('Bid not not belong to authenticated user'));
}
return new BidResource($bid);
}
/**
* Return the fleet that this user is allowed to
*
@@ -5,6 +5,7 @@ namespace App\Http\Controllers\Auth;
use App\Contracts\Controller;
use App\Exceptions\PilotIdNotFound;
use App\Models\Enums\UserState;
use App\Models\User;
use App\Services\UserService;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
@@ -104,14 +105,16 @@ class LoginController extends Controller
*/
protected function sendLoginResponse(Request $request)
{
/** @var User $user */
$user = Auth::user();
$user->last_ip = $request->ip();
$user->save();
if (setting('general.record_user_ip', true)) {
$user->last_ip = $request->ip();
$user->save();
}
if ($user->state !== UserState::ACTIVE && $user->state !== UserState::ON_LEAVE) {
Log::info('Trying to login '.$user->ident.', state '
.UserState::label($user->state));
Log::info('Trying to login '.$user->ident.', state '.UserState::label($user->state));
// Log them out
$this->guard()->logout();
@@ -119,11 +119,16 @@ class RegisterController extends Controller
*
* @return User
*/
protected function create(array $opts)
protected function create(Request $request): User
{
// Default options
$opts = $request->all();
$opts['password'] = Hash::make($opts['password']);
if (setting('general.record_user_ip', true)) {
$opts['last_ip'] = $request->ip();
}
// Convert transfer hours into minutes
if (isset($opts['transfer_time'])) {
$opts['transfer_time'] *= 60;
@@ -158,7 +163,7 @@ class RegisterController extends Controller
{
$this->validator($request->all())->validate();
$user = $this->create($request->all());
$user = $this->create($request);
if ($user->state === UserState::PENDING) {
return view('auth.pending');
}
@@ -36,8 +36,8 @@ class AirportController extends Controller
{
$id = strtoupper($id);
$airport = $this->airportRepo->find($id);
if (empty($airport)) {
$airport = $this->airportRepo->where('id', $id)->first();
if (!$airport) {
Flash::error('Airport not found!');
return redirect(route('frontend.dashboard.index'));
}
@@ -46,12 +46,14 @@ class AirportController extends Controller
->with(['dpt_airport', 'arr_airport', 'airline'])
->findWhere([
'arr_airport_id' => $id,
'active' => 1,
])->all();
$outbound_flights = $this->flightRepo
->with(['dpt_airport', 'arr_airport', 'airline'])
->findWhere([
'dpt_airport_id' => $id,
'active' => 1,
])->all();
return view('airports.show', [
@@ -41,22 +41,25 @@ class DownloadController extends Controller
$klass = new $class();
$obj = $klass->find($id);
$category = explode('\\', $class);
$category = end($category);
// Check if the object is there first
if (isset($obj)) {
$category = explode('\\', $class);
$category = end($category);
if ($category == 'Aircraft' && $airlines > 1) {
$group_name = $category.' > '.$obj->subfleet->airline->name.' '.$obj->icao.' '.$obj->registration;
} elseif ($category == 'Aircraft') {
$group_name = $category.' > '.$obj->icao.' '.$obj->registration;
} elseif ($category == 'Airport') {
$group_name = $category.' > '.$obj->icao.' : '.$obj->name.' ('.$obj->country.')';
} elseif ($category == 'Subfleet' && $airlines > 1) {
$group_name = $category.' > '.$obj->airline->name.' '.$obj->name;
} else {
$group_name = $category.' > '.$obj->name;
if ($category == 'Aircraft' && $airlines > 1) {
$group_name = $category.' > '.$obj->subfleet->airline->name.' '.$obj->icao.' '.$obj->registration;
} elseif ($category == 'Aircraft') {
$group_name = $category.' > '.$obj->icao.' '.$obj->registration;
} elseif ($category == 'Airport') {
$group_name = $category.' > '.$obj->icao.' : '.$obj->name.' ('.$obj->country.')';
} elseif ($category == 'Subfleet' && $airlines > 1) {
$group_name = $category.' > '.$obj->airline->name.' '.$obj->name;
} else {
$group_name = $category.' > '.$obj->name;
}
$regrouped_files[$group_name] = $files;
}
$regrouped_files[$group_name] = $files;
}
ksort($regrouped_files, SORT_STRING);
@@ -5,6 +5,7 @@ namespace App\Http\Controllers\Frontend;
use App\Contracts\Controller;
use App\Models\Bid;
use App\Models\Enums\FlightType;
use App\Models\Flight;
use App\Repositories\AirlineRepository;
use App\Repositories\AirportRepository;
use App\Repositories\Criteria\WhereCriteria;
@@ -12,6 +13,7 @@ use App\Repositories\FlightRepository;
use App\Repositories\SubfleetRepository;
use App\Repositories\UserRepository;
use App\Services\GeoService;
use App\Services\ModuleService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
@@ -24,6 +26,7 @@ class FlightController extends Controller
private $airlineRepo;
private $airportRepo;
private $flightRepo;
private $moduleSvc;
private $subfleetRepo;
private $geoSvc;
private $userRepo;
@@ -33,6 +36,7 @@ class FlightController extends Controller
* @param AirportRepository $airportRepo
* @param FlightRepository $flightRepo
* @param GeoService $geoSvc
* @param ModuleService $moduleSvc
* @param SubfleetRepository $subfleetRepo
* @param UserRepository $userRepo
*/
@@ -41,6 +45,7 @@ class FlightController extends Controller
AirportRepository $airportRepo,
FlightRepository $flightRepo,
GeoService $geoSvc,
ModuleService $moduleSvc,
SubfleetRepository $subfleetRepo,
UserRepository $userRepo
) {
@@ -48,6 +53,7 @@ class FlightController extends Controller
$this->airportRepo = $airportRepo;
$this->flightRepo = $flightRepo;
$this->geoSvc = $geoSvc;
$this->moduleSvc = $moduleSvc;
$this->subfleetRepo = $subfleetRepo;
$this->userRepo = $userRepo;
}
@@ -105,6 +111,20 @@ class FlightController extends Controller
Log::emergency($e);
}
// Get only used Flight Types for the search form
// And filter according to settings
$usedtypes = Flight::select('flight_type')
->where($where)
->groupby('flight_type')
->orderby('flight_type')
->get();
// Build collection with type codes and labels
$flight_types = collect('', '');
foreach ($usedtypes as $ftype) {
$flight_types->put($ftype->flight_type, FlightType::label($ftype->flight_type));
}
$flights = $this->flightRepo->searchCriteria($request)
->with([
'dpt_airport',
@@ -113,12 +133,20 @@ class FlightController extends Controller
'simbrief' => function ($query) use ($user) {
$query->where('user_id', $user->id);
}, ])
->orderBy('flight_number', 'asc')
->orderBy('route_leg', 'asc')
->orderBy('flight_number')
->orderBy('route_leg')
->paginate();
$saved_flights = Bid::where('user_id', Auth::id())
->pluck('flight_id')->toArray();
$saved_flights = [];
$bids = Bid::where('user_id', Auth::id())->get();
foreach ($bids as $bid) {
if (!$bid->flight) {
$bid->delete();
continue;
}
$saved_flights[$bid->flight_id] = $bid->id;
}
return view('flights.index', [
'user' => $user,
@@ -128,13 +156,14 @@ class FlightController extends Controller
'saved' => $saved_flights,
'subfleets' => $this->subfleetRepo->selectBoxList(true),
'flight_number' => $request->input('flight_number'),
'flight_types' => FlightType::select(true),
'flight_types' => $flight_types,
'flight_type' => $request->input('flight_type'),
'arr_icao' => $request->input('arr_icao'),
'dep_icao' => $request->input('dep_icao'),
'subfleet_id' => $request->input('subfleet_id'),
'simbrief' => !empty(setting('simbrief.api_key')),
'simbrief_bids' => setting('simbrief.only_bids'),
'acars_plugin' => $this->moduleSvc->isModuleActive('VMSAcars'),
]);
}
@@ -161,7 +190,7 @@ class FlightController extends Controller
}
$flights->add($bid->flight);
$saved_flights[] = $bid->flight->id;
$saved_flights[$bid->flight_id] = $bid->id;
}
return view('flights.bids', [
@@ -173,6 +202,7 @@ class FlightController extends Controller
'subfleets' => $this->subfleetRepo->selectBoxList(true),
'simbrief' => !empty(setting('simbrief.api_key')),
'simbrief_bids' => setting('simbrief.only_bids'),
'acars_plugin' => $this->moduleSvc->isModuleActive('VMSAcars'),
]);
}
@@ -193,9 +223,14 @@ class FlightController extends Controller
$map_features = $this->geoSvc->flightGeoJson($flight);
// See if the user has a bid for this flight
$bid = Bid::where(['user_id' => Auth::id(), 'flight_id' => $flight->id])->first();
return view('flights.show', [
'flight' => $flight,
'map_features' => $map_features,
'bid' => $bid,
'acars_plugin' => $this->moduleSvc->isModuleActive('VMSAcars'),
]);
}
}
@@ -3,6 +3,7 @@
namespace App\Http\Controllers\Frontend;
use App\Contracts\Controller;
use App\Models\Enums\UserState;
use App\Models\User;
use Illuminate\Database\QueryException;
use Illuminate\Support\Facades\Log;
@@ -15,7 +16,7 @@ class HomeController extends Controller
public function index()
{
try {
$users = User::orderBy('created_at', 'desc')->take(4)->get();
$users = User::where('state', '!=', UserState::DELETED)->orderBy('created_at', 'desc')->take(4)->get();
} catch (\PDOException $e) {
Log::emergency($e);
return view('system/errors/database_error', [
@@ -9,6 +9,7 @@ use App\Models\Enums\PirepSource;
use App\Models\Enums\PirepState;
use App\Models\Fare;
use App\Models\Pirep;
use App\Models\PirepFare;
use App\Models\SimBrief;
use App\Models\User;
use App\Repositories\AircraftRepository;
@@ -103,7 +104,7 @@ class PirepController extends Controller
$tmp[$ac->id] = $ac['name'].' - '.$ac['registration'];
}
$aircraft[$subfleet->name] = $tmp;
$aircraft[$subfleet->type] = $tmp;
}
return $aircraft;
@@ -112,19 +113,18 @@ class PirepController extends Controller
/**
* Save any custom fields found
*
* @param Pirep $pirep
* @param Request $request
*/
protected function saveCustomFields(Pirep $pirep, Request $request)
protected function saveCustomFields(Request $request): array
{
$custom_fields = [];
$fields = [];
$pirep_fields = $this->pirepFieldRepo->all();
foreach ($pirep_fields as $field) {
if (!$request->filled($field->slug)) {
continue;
}
$custom_fields[] = [
$fields[] = [
'name' => $field->name,
'slug' => $field->slug,
'value' => $request->input($field->slug),
@@ -132,8 +132,9 @@ class PirepController extends Controller
];
}
Log::info('PIREP Custom Fields', $custom_fields);
$this->pirepSvc->updateCustomFields($pirep->id, $custom_fields);
Log::info('PIREP Custom Fields', $fields);
return $fields;
}
/**
@@ -159,10 +160,10 @@ class PirepController extends Controller
$count = $request->input($field_name);
}
$fares[] = [
$fares[] = new PirepFare([
'fare_id' => $fare->id,
'count' => $count,
];
]);
}
$this->fareSvc->saveForPirep($pirep, $fares);
@@ -389,8 +390,8 @@ class PirepController extends Controller
$attrs['submitted_at'] = Carbon::now('UTC');
$pirep->submitted_at = Carbon::now('UTC');
$pirep = $this->pirepSvc->create($pirep);
$this->saveCustomFields($pirep, $request);
$fields = $this->saveCustomFields($request);
$pirep = $this->pirepSvc->create($pirep, $fields);
$this->saveFares($pirep, $request);
$this->pirepSvc->saveRoute($pirep);
@@ -399,7 +400,13 @@ class PirepController extends Controller
if ($brief !== null) {
/** @var SimBriefService $sbSvc */
$sbSvc = app(SimBriefService::class);
$sbSvc->attachSimbriefToPirep($pirep, $brief);
// Keep the flight_id with SimBrief depending on the button selected
// Save = Keep the flight_id , Submit = Remove the flight_id
if ($attrs['submit'] === 'save') {
$sbSvc->attachSimbriefToPirep($pirep, $brief, true);
} elseif ($attrs['submit'] === 'submit') {
$sbSvc->attachSimbriefToPirep($pirep, $brief);
}
}
}
@@ -525,7 +532,8 @@ class PirepController extends Controller
$this->pirepSvc->saveRoute($pirep);
}
$this->saveCustomFields($pirep, $request);
$fields = $this->saveCustomFields($request);
$this->pirepSvc->updateCustomFields($pirep->id, $fields);
$this->saveFares($pirep, $request);
if ($attrs['submit'] === 'save') {
@@ -10,6 +10,7 @@ use App\Repositories\AirlineRepository;
use App\Repositories\AirportRepository;
use App\Repositories\UserRepository;
use App\Support\Countries;
use App\Support\Discord;
use App\Support\Timezonelist;
use App\Support\Utils;
use Illuminate\Http\Request;
@@ -122,7 +123,7 @@ class ProfileController extends Controller
$airlines = $this->airlineRepo->selectBoxList();
$airports = $this->airportRepo->selectBoxList(false, setting('pilots.home_hubs_only'));
$userFields = $this->userRepo->getUserFields($user, true);
$userFields = $this->userRepo->getUserFields($user);
return view('profile.edit', [
'user' => $user,
@@ -181,6 +182,16 @@ class ProfileController extends Controller
Storage::delete($user->avatar);
}
// Find out the user's private channel id
/*
// TODO: Uncomment when Discord API functionality is enabled
if ($request->filled('discord_id')) {
$discord_id = $request->post('discord_id');
if ($discord_id !== $user->discord_id) {
$req_data['discord_private_channel_id'] = Discord::getPrivateChannelId($discord_id);
}
}*/
if ($request->hasFile('avatar')) {
$avatar = $request->file('avatar');
$file_name = $user->ident.'.'.$avatar->getClientOriginalExtension();
@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Frontend;
use App\Exceptions\AssetNotFound;
use App\Models\Aircraft;
use App\Models\Bid;
use App\Models\Enums\AircraftState;
use App\Models\Enums\AircraftStatus;
use App\Models\Enums\FareType;
@@ -14,6 +15,7 @@ use App\Models\SimBrief;
use App\Models\User;
use App\Repositories\FlightRepository;
use App\Services\FareService;
use App\Services\ModuleService;
use App\Services\SimBriefService;
use App\Services\UserService;
use Exception;
@@ -24,17 +26,20 @@ class SimBriefController
{
private $fareSvc;
private $flightRepo;
private $moduleSvc;
private $simBriefSvc;
private $userSvc;
public function __construct(
FareService $fareSvc,
FlightRepository $flightRepo,
ModuleService $moduleSvc,
SimBriefService $simBriefSvc,
UserService $userSvc
) {
$this->fareSvc = $fareSvc;
$this->flightRepo = $flightRepo;
$this->moduleSvc = $moduleSvc;
$this->simBriefSvc = $simBriefSvc;
$this->userSvc = $userSvc;
}
@@ -70,6 +75,9 @@ class SimBriefController
return redirect(route('frontend.flights.index'));
}
// Generate SimBrief Static ID
$static_id = $user->ident.'_'.$flight->id;
// No aircraft selected, show selection form
if (!$aircraft_id) {
// If no subfleets defined for flight get them from user
@@ -100,6 +108,14 @@ class SimBriefController
$aircrafts = $aircrafts->where('airport_id', $flight->dpt_airport_id);
}
if (setting('simbrief.block_aircraft')) {
// Build a list of aircraft_id's being used for active sb packs
$sb_aircraft = SimBrief::whereNotNull('flight_id')->pluck('aircraft_id');
// Filter aircraft list to non used/blocked ones
$aircrafts = $aircrafts->whereNotIn('id', $sb_aircraft);
}
return view('flights.simbrief_aircraft', [
'flight' => $flight,
'aircrafts' => $aircrafts,
@@ -215,7 +231,7 @@ class SimBriefController
// Show the main simbrief form
return view('flights.simbrief_form', [
'user' => Auth::user(),
'user' => $user,
'flight' => $flight,
'aircraft' => $aircraft,
'pax_weight' => $pax_weight,
@@ -230,6 +246,7 @@ class SimBriefController
'tpayload' => $tpayload,
'tcargoload' => $tcargoload,
'loaddist' => implode(' ', $loaddist),
'static_id' => $static_id,
]);
}
@@ -242,7 +259,11 @@ class SimBriefController
*/
public function briefing($id)
{
$simbrief = SimBrief::find($id);
/** @var User $user */
$user = Auth::user();
/** @var SimBrief $simbrief */
$simbrief = SimBrief::with(['flight'])->find($id);
if (!$simbrief) {
flash()->error('SimBrief briefing not found');
return redirect(route('frontend.flights.index'));
@@ -255,11 +276,18 @@ class SimBriefController
$equipment = substr($str, $wc + 1, $tr - 2);
$transponder = substr($str, $tr + 1);
// See if a bid exists for this flight
$bid = Bid::where(['user_id' => $user->id, 'flight_id' => $simbrief->flight_id])->first();
return view('flights.simbrief_briefing', [
'simbrief' => $simbrief,
'wakecat' => $wakecat,
'equipment' => $equipment,
'transponder' => $transponder,
'user' => $user,
'simbrief' => $simbrief,
'wakecat' => $wakecat,
'equipment' => $equipment,
'transponder' => $transponder,
'bid' => $bid,
'flight' => $simbrief->flight,
'acars_plugin' => $this->moduleSvc->isModuleActive('VMSAcars'),
]);
}
@@ -360,6 +388,31 @@ class SimBriefController
]);
}
/**
* Get the latest generated OFP. Pass in two additional items, the Simbrief userid and static_id
* This will get the latest edited/regenerated of from Simbrief and update our records
* We do not need to send the fares again, so used an empty array
*/
public function update_ofp(Request $request)
{
/** @var User $user */
$user = Auth::user();
$ofp_id = $request->input('ofp_id');
$flight_id = $request->input('flight_id');
$aircraft_id = $request->input('aircraft_id');
$sb_userid = $request->input('sb_userid');
$sb_static_id = $request->input('sb_static_id');
$fares = [];
$simbrief = $this->simBriefSvc->downloadOfp($user->id, $ofp_id, $flight_id, $aircraft_id, $fares, $sb_userid, $sb_static_id);
if ($simbrief === null) {
$error = new AssetNotFound(new Exception('Simbrief OFP not found'));
return $error->getResponse();
}
return redirect(route('frontend.simbrief.briefing', [$ofp_id]));
}
/**
* Generate the API code
*
@@ -25,7 +25,6 @@ use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Validator;
use Illuminate\View\View;
use function in_array;
use Laracasts\Flash\Flash;
use RuntimeException;
class InstallerController extends Controller
@@ -3,49 +3,32 @@
namespace App\Http\Controllers\System;
use App\Contracts\Controller;
use App\Repositories\KvpRepository;
use App\Services\AnalyticsService;
use App\Services\Installer\InstallerService;
use App\Services\Installer\MigrationService;
use App\Services\Installer\SeederService;
use Codedge\Updater\UpdaterManager;
use function count;
use Illuminate\Contracts\View\Factory;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\View\View;
class UpdateController extends Controller
{
private $analyticsSvc;
private $installerSvc;
private $kvpRepo;
private $migrationSvc;
private $seederSvc;
private $updateManager;
/**
* @param AnalyticsService $analyticsSvc
* @param InstallerService $installerSvc
* @param KvpRepository $kvpRepo
* @param MigrationService $migrationSvc
* @param SeederService $seederSvc
* @param UpdaterManager $updateManager
*/
public function __construct(
AnalyticsService $analyticsSvc,
InstallerService $installerSvc,
KvpRepository $kvpRepo,
MigrationService $migrationSvc,
SeederService $seederSvc,
UpdaterManager $updateManager
SeederService $seederSvc
) {
$this->analyticsSvc = $analyticsSvc;
$this->migrationSvc = $migrationSvc;
$this->seederSvc = $seederSvc;
$this->installerSvc = $installerSvc;
$this->kvpRepo = $kvpRepo;
$this->updateManager = $updateManager;
}
/**
@@ -107,38 +90,4 @@ class UpdateController extends Controller
{
return redirect('/admin');
}
/**
* Show the update page with the latest version
*
* @return Factory|View
*/
public function updater()
{
$version = $this->kvpRepo->get('latest_version_tag');
return view('system.updater.downloader/downloader', [
'version' => $version,
]);
}
/**
* Download the actual update and then forward the user to the updater page
*
* @return mixed
*/
public function update_download()
{
$version = $this->kvpRepo->get('latest_version_tag');
if (empty($version)) {
return view('system.updater.steps.step1-no-update');
}
$release = $this->updateManager->source('github')->fetch($version);
$this->updateManager->source('github')->update($release);
$this->analyticsSvc->sendUpdate();
Log::info('Update completed to '.$version.', redirecting');
return redirect('/update');
}
}
+3
View File
@@ -50,6 +50,9 @@ class ApiAuth implements Middleware
return $user;
});
// Force english locale for API
app()->setLocale('en');
return $next($request);
}
+1
View File
@@ -15,6 +15,7 @@ class JsonResponse implements Middleware
{
$response = $next($request);
$response->headers->set('Content-Type', 'application/json');
$response->headers->set('charset', 'utf-8');
return $response;
}
+1 -1
View File
@@ -19,7 +19,7 @@ class FileRequest extends FormRequest
return [
'distance' => 'required|numeric',
'flight_time' => 'required|integer',
'fuel_used' => 'required|numeric',
'fuel_used' => 'sometimes|numeric',
'block_time' => 'sometimes|integer',
'airline_id' => 'sometimes|exists:airlines,id',
'aircraft_id' => 'sometimes|exists:aircraft,id',
+1 -1
View File
@@ -18,7 +18,7 @@ class News extends Resource
$res = parent::toArray($request);
$res['user'] = [
'id' => $this->user->id,
'name' => $this->user->name,
'name' => $this->user->name_private,
];
return $res;
+22
View File
@@ -0,0 +1,22 @@
<?php
namespace App\Listeners;
use Illuminate\Log\Events\MessageLogged;
use Symfony\Component\Console\Output\ConsoleOutput;
/**
* Show logs in the console
*
* https://stackoverflow.com/questions/48264479/log-laravel-with-artisan-output
*/
class MessageLoggedListener
{
public function handle(MessageLogged $event)
{
if (app()->runningInConsole() && app()->environment() !== 'testing') {
$output = new ConsoleOutput();
$output->writeln("<$event->level>$event->message</$event->level>");
}
}
}
+1 -4
View File
@@ -6,10 +6,7 @@ use App\Contracts\Listener;
use App\Events\PirepPrefiled;
/**
* Look for and run any of the award classes. Don't modify this.
* See the documentation on creating awards:
*
* @url http://docs.phpvms.net/customizing/awards
* Handler for PIREP events
*/
class PirepEventsHandler extends Listener
{
+9
View File
@@ -12,6 +12,7 @@ use Carbon\Carbon;
* @property int id
* @property mixed subfleet_id
* @property string airport_id The apt where the aircraft is
* @property string ident
* @property string name
* @property string icao
* @property string registration
@@ -71,6 +72,14 @@ class Aircraft extends Model
'zfw' => 'nullable|numeric',
];
/**
* @return string
*/
public function getIdentAttribute(): string
{
return $this->registration.' ('.$this->icao.')';
}
/**
* See if this aircraft is active
*
+3 -2
View File
@@ -6,6 +6,7 @@ use App\Contracts\Model;
use App\Models\Enums\JournalType;
use App\Models\Traits\FilesTrait;
use App\Models\Traits\JournalTrait;
use Illuminate\Support\Str;
/**
* Class Airline
@@ -84,7 +85,7 @@ class Airline extends Model
*/
public function setIataAttribute($iata)
{
$this->attributes['iata'] = strtoupper($iata);
$this->attributes['iata'] = Str::upper($iata);
}
/**
@@ -94,7 +95,7 @@ class Airline extends Model
*/
public function setIcaoAttribute($icao): void
{
$this->attributes['icao'] = strtoupper($icao);
$this->attributes['icao'] = Str::upper($icao);
}
public function subfleets()
+1 -1
View File
@@ -6,7 +6,7 @@ use App\Contracts\Model;
use Carbon\Carbon;
/**
* @property string user_id
* @property int user_id
* @property string flight_id
* @property Carbon created_at
* @property Carbon updated_at
+1 -1
View File
@@ -77,7 +77,7 @@ class Days extends Enum
*/
public static function in($mask, $day): bool
{
return ($mask & $day) === $day;
return in_mask($mask, $day);
}
/**
+2 -3
View File
@@ -4,9 +4,6 @@ namespace App\Models\Enums;
use App\Contracts\Enum;
/**
* Class PirepState
*/
class PirepState extends Enum
{
public const IN_PROGRESS = 0; // flight is ongoing
@@ -16,6 +13,7 @@ class PirepState extends Enum
public const DELETED = 4;
public const DRAFT = 5;
public const REJECTED = 6;
public const PAUSED = 7;
protected static $labels = [
self::IN_PROGRESS => 'pireps.state.in_progress',
@@ -25,5 +23,6 @@ class PirepState extends Enum
self::DELETED => 'pireps.state.deleted',
self::DRAFT => 'pireps.state.draft',
self::REJECTED => 'pireps.state.rejected',
self::PAUSED => 'pireps.state.paused',
];
}
+4 -2
View File
@@ -33,7 +33,8 @@ class PirepStatus extends Enum
public const LANDED = 'LAN';
public const ARRIVED = 'ONB'; // On block
public const CANCELLED = 'DX';
public const EMERG_DECENT = 'EMG';
public const EMERG_DESCENT = 'EMG';
public const PAUSED = 'PSD';
protected static $labels = [
self::INITIATED => 'pireps.status.initialized',
@@ -58,6 +59,7 @@ class PirepStatus extends Enum
self::LANDED => 'pireps.status.landed',
self::ARRIVED => 'pireps.status.arrived',
self::CANCELLED => 'pireps.status.cancelled',
self::EMERG_DECENT => 'pireps.status.emerg_decent',
self::EMERG_DESCENT => 'pireps.status.emerg_decent',
self::PAUSED => 'pireps.status.paused',
];
}
+2 -1
View File
@@ -13,6 +13,7 @@ use App\Models\Traits\ReferenceTrait;
* @property string flight_type
* @property string ref_model
* @property string ref_model_id
* @property bool charge_to_user
*
* @mixin \Illuminate\Database\Eloquent\Builder
*/
@@ -36,7 +37,7 @@ class Expense extends Model
];
public static $rules = [
'active' => 'boolean',
'active' => 'bool',
'airline_id' => 'integer',
'amount' => 'float',
'multiplier' => 'bool',
+8 -2
View File
@@ -3,13 +3,19 @@
namespace App\Models;
use App\Contracts\Model;
use Illuminate\Notifications\Notifiable;
/**
* @property string subject
* @property string body
* @property int id
* @property int|mixed user_id
* @property string subject
* @property string body
* @property User user
*/
class News extends Model
{
use Notifiable;
public $table = 'news';
protected $fillable = [
+28 -13
View File
@@ -3,6 +3,8 @@
namespace App\Models;
use App\Contracts\Model;
use App\Events\PirepStateChange;
use App\Events\PirepStatusChange;
use App\Models\Enums\AcarsType;
use App\Models\Enums\PirepFieldSource;
use App\Models\Enums\PirepState;
@@ -10,7 +12,9 @@ use App\Models\Traits\HashIdTrait;
use App\Support\Units\Distance;
use App\Support\Units\Fuel;
use Carbon\Carbon;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Collection;
use Kleemans\AttributeEvents;
/**
* @property string id
@@ -44,8 +48,9 @@ use Illuminate\Support\Collection;
* @property Flight|null flight
* @property Collection fields
* @property string status
* @property bool state
* @property string source
* @property int state
* @property int source
* @property string source_name
* @property Carbon submitted_at
* @property Carbon created_at
* @property Carbon updated_at
@@ -56,7 +61,9 @@ use Illuminate\Support\Collection;
*/
class Pirep extends Model
{
use AttributeEvents;
use HashIdTrait;
use Notifiable;
public $table = 'pireps';
@@ -136,6 +143,14 @@ class Pirep extends Model
'route' => 'nullable',
];
/**
* Auto-dispatch events for lifecycle state changes
*/
protected $dispatchesEvents = [
'status:*' => PirepStatusChange::class,
'state:*' => PirepStateChange::class,
];
/*
* If a PIREP is in these states, then it can't be changed.
*/
@@ -180,22 +195,22 @@ class Pirep extends Model
/**
* Create a new PIREP from a SimBrief instance
*
* @param \App\Models\SimBrief $simBrief
* @param \App\Models\SimBrief $simbrief
*
* @return \App\Models\Pirep
*/
public static function fromSimBrief(SimBrief $simBrief): self
public static function fromSimBrief(SimBrief $simbrief): self
{
return new self([
'flight_id' => $simBrief->flight->id,
'airline_id' => $simBrief->flight->airline_id,
'flight_number' => $simBrief->flight->flight_number,
'route_code' => $simBrief->flight->route_code,
'route_leg' => $simBrief->flight->route_leg,
'dpt_airport_id' => $simBrief->flight->dpt_airport_id,
'arr_airport_id' => $simBrief->flight->arr_airport_id,
'route' => $simBrief->xml->getRouteString(),
'level' => $simBrief->xml->getFlightLevel(),
'flight_id' => $simbrief->flight->id,
'airline_id' => $simbrief->flight->airline_id,
'flight_number' => $simbrief->flight->flight_number,
'route_code' => $simbrief->flight->route_code,
'route_leg' => $simbrief->flight->route_leg,
'dpt_airport_id' => $simbrief->flight->dpt_airport_id,
'arr_airport_id' => $simbrief->flight->arr_airport_id,
'route' => $simbrief->xml->getRouteString(),
'level' => $simbrief->xml->getFlightLevel(),
]);
}
+1
View File
@@ -11,6 +11,7 @@ use App\Models\Enums\PirepFieldSource;
* @property string slug
* @property string value
* @property string source
* @property Pirep pirep
*
* @method static updateOrCreate(array $array, array $array1)
*/
+9 -6
View File
@@ -41,6 +41,10 @@ class SimBrief extends Model
'updated_at',
];
protected $casts = [
'user_id' => 'integer',
];
/** @var \App\Models\SimBriefXML Store a cached version of the XML object */
private $xml_instance;
@@ -92,13 +96,12 @@ class SimBrief extends Model
public function flight()
{
if (!empty($this->attributes['flight_id'])) {
return $this->belongsTo(Flight::class, 'flight_id');
}
return $this->belongsTo(Flight::class, 'flight_id');
}
if (!empty($this->attributes['pirep_id'])) {
return $this->belongsTo(Pirep::class, 'pirep_id');
}
public function pirep()
{
return $this->belongsTo(Pirep::class, 'pirep_id');
}
public function user()
+1
View File
@@ -20,6 +20,7 @@ use App\Models\Traits\FilesTrait;
* @property float cost_delay_minute
* @property Airline airline
* @property Airport hub
* @property int fuel_type
*/
class Subfleet extends Model
{
+9 -3
View File
@@ -3,7 +3,6 @@
namespace App\Models;
use App\Models\Enums\JournalType;
use App\Models\Enums\PirepState;
use App\Models\Traits\JournalTrait;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
@@ -33,8 +32,11 @@ use Laratrust\Traits\LaratrustUserTrait;
* @property Rank rank
* @property Journal journal
* @property int rank_id
* @property string discord_id
* @property int state
* @property string last_ip
* @property bool opt_in
* @property Pirep[] pireps
* @property string last_pirep_id
* @property Pirep last_pirep
* @property UserFieldValue[] fields
@@ -66,6 +68,8 @@ class User extends Authenticatable
'pilot_id',
'airline_id',
'rank_id',
'discord_id',
'discord_private_channel_id',
'api_key',
'country',
'home_airport_id',
@@ -80,6 +84,7 @@ class User extends Authenticatable
'status',
'toc_accepted',
'opt_in',
'last_ip',
'created_at',
'updated_at',
];
@@ -89,7 +94,9 @@ class User extends Authenticatable
*/
protected $hidden = [
'api_key',
'discord_id',
'password',
'last_ip',
'remember_token',
];
@@ -251,8 +258,7 @@ class User extends Authenticatable
public function pireps()
{
return $this->hasMany(Pirep::class, 'user_id')
->where('state', '!=', PirepState::CANCELLED);
return $this->hasMany(Pirep::class, 'user_id');
}
public function rank()
@@ -0,0 +1,145 @@
<?php
namespace App\Notifications\Channels\Discord;
use Carbon\Carbon;
/**
* Original is from https://gist.github.com/freekmurze/e4415090f650e070d3de8b905875cf78
*
* Markdown guide: https://birdie0.github.io/discord-webhooks-guide/other/discord_markdown.html
*/
class DiscordMessage
{
const COLOR_SUCCESS = '#0B6623';
const COLOR_WARNING = '#FD6A02';
const COLOR_ERROR = '#ED2939';
public $webhook_url;
protected $title;
protected $url;
protected $description;
protected $timestamp;
protected $footer;
protected $color;
protected $author = [];
protected $fields = [];
/**
* Supply the webhook URL that this should be going to
*/
public function webhook(string $url): self
{
$this->webhook_url = $url;
return $this;
}
/**
* Title of the notification
*/
public function title(string $title): self
{
$this->title = $title;
return $this;
}
public function url(string $url): self
{
$this->url = $url;
return $this;
}
/**
* @param array|string $descriptionLines
*/
public function description($descriptionLines): self
{
if (!is_array($descriptionLines)) {
$descriptionLines = [$descriptionLines];
}
$this->description = implode(PHP_EOL, $descriptionLines);
return $this;
}
/**
* Set the author information:
* [
* 'name' => '',
* 'url' => '',
* 'icon_url' => '',
*/
public function author(array $author): self
{
$this->author = $author;
return $this;
}
/**
* Set the fields
*/
public function fields(array $fields): self
{
$this->fields = [];
foreach ($fields as $name => $value) {
$this->fields[] = [
'name' => '**'.$name.'**', // bold
'value' => $value,
'inline' => true,
];
}
return $this;
}
public function footer(string $footer): self
{
$this->footer = $footer;
return $this;
}
public function success(): self
{
$this->color = static::COLOR_SUCCESS;
return $this;
}
public function warning(): self
{
$this->color = static::COLOR_WARNING;
return $this;
}
public function error(): self
{
$this->color = static::COLOR_ERROR;
return $this;
}
public function toArray(): array
{
$embeds = [
'title' => $this->title,
'url' => $this->url,
'type' => 'rich',
'description' => $this->description,
'author' => $this->author,
'timestamp' => Carbon::now('UTC'),
];
if (!empty($this->fields)) {
$embeds['fields'] = $this->fields;
}
if (!empty($this->footer)) {
$embeds['footer'] = [
'text' => $this->footer,
];
}
return [
'embeds' => [$embeds],
];
}
}
@@ -0,0 +1,38 @@
<?php
namespace App\Notifications\Channels\Discord;
use App\Contracts\Notification;
use App\Support\HttpClient;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Psr7;
use Illuminate\Support\Facades\Log;
class DiscordWebhook
{
private $httpClient;
public function __construct(HttpClient $httpClient)
{
$this->httpClient = $httpClient;
}
public function send($notifiable, Notification $notification)
{
$message = $notification->toDiscordChannel($notifiable);
if ($message === null || empty($message->webhook_url)) {
Log::debug('Discord notifications not configured, skipping');
return;
}
try {
$data = $message->toArray();
$this->httpClient->post($message->webhook_url, $data);
} catch (RequestException $e) {
$request = Psr7\Message::toString($e->getRequest());
$response = Psr7\Message::toString($e->getResponse());
Log::error('Error sending Discord notification: request: '.$e->getMessage().', '.$request);
Log::error('Error sending Discord notification: response: '.$response);
}
}
}
+1 -1
View File
@@ -17,7 +17,7 @@ trait MailChannel
* @param string $template Markdown template to use
* @param array $args Arguments to pass to the template
*/
public function setMailable($subject, $template, $args)
public function setMailable(string $subject, string $template, array $args)
{
$this->mailSubject = $subject;
$this->mailTemplate = $template;
@@ -4,14 +4,15 @@ namespace App\Notifications\Messages;
use App\Contracts\Notification;
use App\Models\User;
use App\Notifications\Channels\Discord\DiscordMessage;
use App\Notifications\Channels\Discord\DiscordWebhook;
use App\Notifications\Channels\MailChannel;
use Illuminate\Contracts\Queue\ShouldQueue;
class AdminUserRegistered extends Notification
class AdminUserRegistered extends Notification implements ShouldQueue
{
use MailChannel;
public $channels = ['mail'];
private $user;
/**
@@ -31,6 +32,32 @@ class AdminUserRegistered extends Notification
);
}
public function via($notifiable)
{
return ['mail', DiscordWebhook::class];
}
/**
* Send a Discord notification
*
* @param User $pirep
* @param mixed $user
*
* @return DiscordMessage|null
*/
public function toDiscordChannel($user): ?DiscordMessage
{
if (empty(setting('notifications.discord_private_webhook_url'))) {
return null;
}
$dm = new DiscordMessage();
return $dm->webhook(setting('notifications.discord_private_webhook_url'))
->success()
->title('New User Registered: '.$user->ident)
->fields([]);
}
public function toArray($notifiable)
{
return [
+32 -2
View File
@@ -4,13 +4,15 @@ namespace App\Notifications\Messages;
use App\Contracts\Notification;
use App\Models\News;
use App\Notifications\Channels\Discord\DiscordMessage;
use App\Notifications\Channels\Discord\DiscordWebhook;
use App\Notifications\Channels\MailChannel;
use Illuminate\Contracts\Queue\ShouldQueue;
class NewsAdded extends Notification
class NewsAdded extends Notification implements ShouldQueue
{
use MailChannel;
public $channels = ['mail'];
public $requires_opt_in = true;
private $news;
@@ -27,6 +29,34 @@ class NewsAdded extends Notification
);
}
public function via($notifiable)
{
return ['mail', DiscordWebhook::class];
}
/**
* @param News $news
*
* @return DiscordMessage|null
*/
public function toDiscordChannel($news): ?DiscordMessage
{
if (empty(setting('notifications.discord_public_webhook_url'))) {
return null;
}
$dm = new DiscordMessage();
return $dm->webhook(setting('notifications.discord_public_webhook_url'))
->success()
->title('News: '.$news->subject)
->author([
'name' => $news->user->ident.' - '.$news->user->name_private,
'url' => '',
'icon_url' => $news->user->resolveAvatarUrl(),
])
->description($news->body);
}
/**
* Get the array representation of the notification.
*
+8 -4
View File
@@ -5,16 +5,15 @@ namespace App\Notifications\Messages;
use App\Contracts\Notification;
use App\Models\Pirep;
use App\Notifications\Channels\MailChannel;
use Illuminate\Contracts\Queue\ShouldQueue;
/**
* Send the PIREP accepted message to a particular user
* Send the PIREP accepted message to a particular user, can also be sent to Discord
*/
class PirepAccepted extends Notification
class PirepAccepted extends Notification implements ShouldQueue
{
use MailChannel;
public $channels = ['mail'];
private $pirep;
/**
@@ -35,6 +34,11 @@ class PirepAccepted extends Notification
);
}
public function via($notifiable)
{
return ['mail'];
}
/**
* Get the array representation of the notification.
*
@@ -0,0 +1,102 @@
<?php
namespace App\Notifications\Messages;
use App\Contracts\Notification;
use App\Models\Pirep;
use App\Notifications\Channels\Discord\DiscordMessage;
use App\Notifications\Channels\Discord\DiscordWebhook;
use App\Support\Units\Distance;
use App\Support\Units\Time;
use Illuminate\Contracts\Queue\ShouldQueue;
use PhpUnitsOfMeasure\Exception\NonNumericValue;
use PhpUnitsOfMeasure\Exception\NonStringUnitName;
/**
* Send the PIREP accepted message to a particular user, can also be sent to Discord
*/
class PirepPrefiled extends Notification implements ShouldQueue
{
private $pirep;
/**
* Create a new notification instance.
*
* @param Pirep $pirep
*/
public function __construct(Pirep $pirep)
{
parent::__construct();
$this->pirep = $pirep;
}
public function via($notifiable)
{
return [DiscordWebhook::class];
}
/**
* Send a Discord notification
*
* @param Pirep $pirep
*
* @return DiscordMessage|null
*/
public function toDiscordChannel($pirep): ?DiscordMessage
{
if (empty(setting('notifications.discord_public_webhook_url'))) {
return null;
}
$title = 'Flight '.$pirep->airline->code.$pirep->ident.' Prefiled';
$fields = [
'Flight' => $pirep->airline->code.$pirep->ident,
'Departure Airport' => $pirep->dpt_airport_id,
'Arrival Airport' => $pirep->arr_airport_id,
'Equipment' => $pirep->aircraft->ident,
'Flight Time (Planned)' => Time::minutesToTimeString($pirep->planned_flight_time),
];
if ($pirep->planned_distance) {
try {
$planned_distance = new Distance(
$pirep->planned_distance,
config('phpvms.internal_units.distance')
);
$pd = $planned_distance[$planned_distance->unit].' '.$planned_distance->unit;
$fields['Distance (Planned)'] = $pd;
} catch (NonNumericValue $e) {
} catch (NonStringUnitName $e) {
}
}
$dm = new DiscordMessage();
return $dm->webhook(setting('notifications.discord_public_webhook_url'))
->success()
->title($title)
->description($pirep->user->discord_id ? 'Flight by <@'.$pirep->user->discord_id.'>' : '')
->url(route('frontend.pireps.show', [$pirep->id]))
->author([
'name' => $pirep->user->ident.' - '.$pirep->user->name_private,
'url' => route('frontend.profile.show', [$pirep->user_id]),
'icon_url' => $pirep->user->resolveAvatarUrl(),
])
->fields($fields);
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
*
* @return array
*/
public function toArray($notifiable)
{
return [
'pirep_id' => $this->pirep->id,
'user_id' => $this->pirep->user_id,
];
}
}
+8 -4
View File
@@ -5,19 +5,18 @@ namespace App\Notifications\Messages;
use App\Contracts\Notification;
use App\Models\Pirep;
use App\Notifications\Channels\MailChannel;
use Illuminate\Contracts\Queue\ShouldQueue;
class PirepRejected extends Notification
class PirepRejected extends Notification implements ShouldQueue
{
use MailChannel;
public $channels = ['mail'];
private $pirep;
/**
* Create a new notification instance.
*
* @param \App\Models\Pirep $pirep
* @param Pirep $pirep
*/
public function __construct(Pirep $pirep)
{
@@ -32,6 +31,11 @@ class PirepRejected extends Notification
);
}
public function via($notifiable)
{
return ['mail'];
}
/**
* Get the array representation of the notification.
*
@@ -0,0 +1,142 @@
<?php
namespace App\Notifications\Messages;
use App\Contracts\Notification;
use App\Models\Enums\PirepStatus;
use App\Models\Pirep;
use App\Notifications\Channels\Discord\DiscordMessage;
use App\Notifications\Channels\Discord\DiscordWebhook;
use App\Support\Units\Distance;
use App\Support\Units\Time;
use Illuminate\Contracts\Queue\ShouldQueue;
use PhpUnitsOfMeasure\Exception\NonNumericValue;
use PhpUnitsOfMeasure\Exception\NonStringUnitName;
/**
* Send the PIREP accepted message to a particular user, can also be sent to Discord
*/
class PirepStatusChanged extends Notification implements ShouldQueue
{
private $pirep;
// TODO: Int'l languages for these
protected static $verbs = [
PirepStatus::INITIATED => 'is initialized',
PirepStatus::SCHEDULED => 'is scheduled',
PirepStatus::BOARDING => 'is boarding',
PirepStatus::RDY_START => 'is ready for start',
PirepStatus::PUSHBACK_TOW => 'is pushing back',
PirepStatus::DEPARTED => 'has departed',
PirepStatus::RDY_DEICE => 'is ready for de-icing',
PirepStatus::STRT_DEICE => 'is de-icing',
PirepStatus::GRND_RTRN => 'on ground return',
PirepStatus::TAXI => 'is taxiing',
PirepStatus::TAKEOFF => 'has taken off',
PirepStatus::INIT_CLIM => 'in initial climb',
PirepStatus::AIRBORNE => 'is enroute',
PirepStatus::ENROUTE => 'is enroute',
PirepStatus::DIVERTED => 'has diverted',
PirepStatus::APPROACH => 'on approach',
PirepStatus::APPROACH_ICAO => 'on approach',
PirepStatus::ON_FINAL => 'on final approach',
PirepStatus::LANDING => 'is landing',
PirepStatus::LANDED => 'has landed',
PirepStatus::ARRIVED => 'has arrived',
PirepStatus::CANCELLED => 'is cancelled',
PirepStatus::PAUSED => 'is paused',
PirepStatus::EMERG_DESCENT => 'in emergency descent',
];
/**
* Create a new notification instance.
*
* @param Pirep $pirep
*/
public function __construct(Pirep $pirep)
{
parent::__construct();
$this->pirep = $pirep;
}
public function via($notifiable)
{
return [DiscordWebhook::class];
}
/**
* Send a Discord notification
*
* @param Pirep $pirep
*
* @return DiscordMessage|null
*/
public function toDiscordChannel($pirep): ?DiscordMessage
{
if (empty(setting('notifications.discord_public_webhook_url'))) {
return null;
}
$title = 'Flight '.$pirep->airline->code.$pirep->ident.' '.self::$verbs[$pirep->status];
$fields = [
'Flight' => $pirep->airline->code.$pirep->ident,
'Departure Airport' => $pirep->dpt_airport_id,
'Arrival Airport' => $pirep->arr_airport_id,
'Equipment' => $pirep->aircraft->ident,
'Flight Time' => Time::minutesToTimeString($pirep->flight_time),
];
// Show the distance, but include the planned distance if it's been set
if ($pirep->distance) {
$unit = config('phpvms.internal_units.distance');
try {
$planned_distance = new Distance($pirep->distance, $unit);
$pd = $planned_distance[$planned_distance->unit];
$fields['Distance'] = $pd;
// Add the planned distance in
if ($pirep->planned_distance) {
try {
$planned_distance = new Distance($pirep->planned_distance, $unit);
$pd = $planned_distance[$planned_distance->unit];
$fields['Distance'] .= '/'.$pd;
} catch (NonNumericValue | NonStringUnitName $e) {
}
}
$fields['Distance'] .= ' '.$planned_distance->unit;
} catch (NonNumericValue | NonStringUnitName $e) {
}
}
$dm = new DiscordMessage();
return $dm->webhook(setting('notifications.discord_public_webhook_url'))
->success()
->title($title)
->description($pirep->user->discord_id ? 'Flight by <@'.$pirep->user->discord_id.'>' : '')
->url(route('frontend.pireps.show', [$pirep->id]))
->author([
'name' => $pirep->user->ident.' - '.$pirep->user->name_private,
'url' => route('frontend.profile.show', [$pirep->user_id]),
'icon_url' => $pirep->user->resolveAvatarUrl(),
])
->fields($fields);
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
*
* @return array
*/
public function toArray($notifiable)
{
return [
'pirep_id' => $this->pirep->id,
'user_id' => $this->pirep->user_id,
];
}
}
+62 -3
View File
@@ -4,14 +4,19 @@ namespace App\Notifications\Messages;
use App\Contracts\Notification;
use App\Models\Pirep;
use App\Notifications\Channels\Discord\DiscordMessage;
use App\Notifications\Channels\Discord\DiscordWebhook;
use App\Notifications\Channels\MailChannel;
use App\Support\Units\Distance;
use App\Support\Units\Time;
use Illuminate\Contracts\Queue\ShouldQueue;
use PhpUnitsOfMeasure\Exception\NonNumericValue;
use PhpUnitsOfMeasure\Exception\NonStringUnitName;
class PirepSubmitted extends Notification
class PirepSubmitted extends Notification implements ShouldQueue
{
use MailChannel;
public $channels = ['mail'];
private $pirep;
/**
@@ -32,6 +37,60 @@ class PirepSubmitted extends Notification
);
}
public function via($notifiable)
{
return ['mail', DiscordWebhook::class];
}
/**
* Send a Discord notification
*
* @param Pirep $pirep
*
* @return DiscordMessage|null
*/
public function toDiscordChannel($pirep): ?DiscordMessage
{
if (empty(setting('notifications.discord_public_webhook_url'))) {
return null;
}
$title = 'Flight '.$pirep->airline->code.$pirep->ident.' Filed';
$fields = [
'Flight' => $pirep->airline->code.$pirep->ident,
'Departure Airport' => $pirep->dpt_airport_id,
'Arrival Airport' => $pirep->arr_airport_id,
'Equipment' => $pirep->aircraft->ident,
'Flight Time (Planned)' => Time::minutesToTimeString($pirep->planned_flight_time),
];
if ($pirep->planned_distance) {
try {
$planned_distance = new Distance(
$pirep->planned_distance,
config('phpvms.internal_units.distance')
);
$pd = $planned_distance[$planned_distance->unit].' '.$planned_distance->unit;
$fields['Distance (Planned)'] = $pd;
} catch (NonNumericValue | NonStringUnitName $e) {
}
}
$dm = new DiscordMessage();
return $dm->webhook(setting('notifications.discord_public_webhook_url'))
->success()
->title($title)
->description($pirep->user->discord_id ? 'Flight by <@'.$pirep->user->discord_id.'>' : '')
->url(route('frontend.pireps.show', [$pirep->id]))
->author([
'name' => $pirep->user->ident.' - '.$pirep->user->name_private,
'url' => route('frontend.profile.show', [$pirep->user_id]),
'icon_url' => $pirep->user->resolveAvatarUrl(),
])
->fields($fields);
}
/**
* Get the array representation of the notification.
*
+8 -4
View File
@@ -5,17 +5,16 @@ namespace App\Notifications\Messages;
use App\Contracts\Notification;
use App\Models\User;
use App\Notifications\Channels\MailChannel;
use Illuminate\Contracts\Queue\ShouldQueue;
class UserPending extends Notification
class UserPending extends Notification implements ShouldQueue
{
use MailChannel;
public $channels = ['mail'];
private $user;
/**
* @param \App\Models\User $user
* @param User $user
*/
public function __construct(User $user)
{
@@ -30,6 +29,11 @@ class UserPending extends Notification
);
}
public function via($notifiable)
{
return ['mail'];
}
/**
* Get the array representation of the notification.
*
@@ -5,19 +5,18 @@ namespace App\Notifications\Messages;
use App\Contracts\Notification;
use App\Models\User;
use App\Notifications\Channels\MailChannel;
use Illuminate\Contracts\Queue\ShouldQueue;
class UserRegistered extends Notification
class UserRegistered extends Notification implements ShouldQueue
{
use MailChannel;
public $channels = ['mail'];
private $user;
/**
* Create a new notification instance.
*
* @param \App\Models\User $user
* @param User $user
*/
public function __construct(User $user)
{
@@ -32,6 +31,11 @@ class UserRegistered extends Notification
);
}
public function via($notifiable)
{
return ['mail'];
}
public function toArray($notifiable)
{
return [
+9 -3
View File
@@ -5,13 +5,14 @@ namespace App\Notifications\Messages;
use App\Contracts\Notification;
use App\Models\User;
use App\Notifications\Channels\MailChannel;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
class UserRejected extends Notification
class UserRejected extends Notification implements ShouldQueue
{
use Queueable;
use MailChannel;
public $channels = ['mail'];
private $user;
/**
@@ -30,6 +31,11 @@ class UserRejected extends Notification
);
}
public function via($notifiable)
{
return ['mail'];
}
/**
* Get the array representation of the notification.
*
@@ -6,13 +6,13 @@ use App\Contracts\Listener;
use App\Events\NewsAdded;
use App\Events\PirepAccepted;
use App\Events\PirepFiled;
use App\Events\PirepPrefiled;
use App\Events\PirepRejected;
use App\Events\PirepStatusChange;
use App\Events\UserRegistered;
use App\Events\UserStateChanged;
use App\Models\Enums\UserState;
use App\Models\User;
use App\Notifications\Messages\PirepSubmitted;
use App\Notifications\Messages\UserPending;
use App\Notifications\Messages\UserRejected;
use App\Notifications\Notifiables\Broadcast;
use Exception;
@@ -23,17 +23,19 @@ use Illuminate\Support\Facades\Notification;
/**
* Listen for different events and map them to different notifications
*/
class EventHandler extends Listener
class NotificationEventsHandler extends Listener
{
private static $broadcastNotifyable;
public static $callbacks = [
NewsAdded::class => 'onNewsAdded',
PirepAccepted::class => 'onPirepAccepted',
PirepFiled::class => 'onPirepFile',
PirepRejected::class => 'onPirepRejected',
UserRegistered::class => 'onUserRegister',
UserStateChanged::class => 'onUserStateChange',
NewsAdded::class => 'onNewsAdded',
PirepPrefiled::class => 'onPirepPrefile',
PirepStatusChange::class => 'onPirepStatusChange',
PirepAccepted::class => 'onPirepAccepted',
PirepFiled::class => 'onPirepFile',
PirepRejected::class => 'onPirepRejected',
UserRegistered::class => 'onUserRegister',
UserStateChanged::class => 'onUserStateChange',
];
public function __construct()
@@ -56,6 +58,7 @@ class EventHandler extends Listener
}
try {
// $this->notifyUser($user, $notification);
Notification::send([$user], $notification);
} catch (Exception $e) {
Log::emergency('Error emailing admin ('.$user->email.'). Error='.$e->getMessage());
@@ -117,19 +120,24 @@ class EventHandler extends Listener
.$event->user->ident.' is '
.UserState::label($event->user->state).', sending active email');
/*
* Send all of the admins a notification that a new user registered
*/
$this->notifyAdmins(new Messages\AdminUserRegistered($event->user));
/*
* Send the user a confirmation email
*/
if ($event->user->state === UserState::ACTIVE) {
$this->notifyUser($event->user, new Messages\UserRegistered($event->user));
} elseif ($event->user->state === UserState::PENDING) {
$this->notifyUser($event->user, new UserPending($event->user));
$this->notifyUser($event->user, new Messages\UserPending($event->user));
}
/*
* Send all of the admins a notification that a new user registered
*/
$this->notifyAdmins(new Messages\AdminUserRegistered($event->user));
/**
* Discord and other notifications
*/
Notification::send([$event->user], new Messages\AdminUserRegistered($event->user));
}
/**
@@ -152,15 +160,34 @@ class EventHandler extends Listener
}
}
/**
* Prefile notification
*/
public function onPirepPrefile(PirepPrefiled $event): void
{
Log::info('NotificationEvents::onPirepPrefile: '.$event->pirep->id.' prefiled');
Notification::send([$event->pirep], new Messages\PirepPrefiled($event->pirep));
}
/**
* Status Change notification
*/
public function onPirepStatusChange(PirepStatusChange $event): void
{
Log::info('NotificationEvents::onPirepStatusChange: '.$event->pirep->id.' prefiled');
Notification::send([$event->pirep], new Messages\PirepStatusChanged($event->pirep));
}
/**
* Notify the admins that a new PIREP has been filed
*
* @param \App\Events\PirepFiled $event
* @param PirepFiled $event
*/
public function onPirepFile(PirepFiled $event): void
{
Log::info('NotificationEvents::onPirepFile: '.$event->pirep->id.' filed ');
$this->notifyAdmins(new PirepSubmitted($event->pirep));
Log::info('NotificationEvents::onPirepFile: '.$event->pirep->id.' filed');
$this->notifyAdmins(new Messages\PirepSubmitted($event->pirep));
Notification::send([$event->pirep], new Messages\PirepSubmitted($event->pirep));
}
/**
@@ -194,5 +221,6 @@ class EventHandler extends Listener
{
Log::info('NotificationEvents::onNewsAdded');
$this->notifyAllUsers(new Messages\NewsAdded($event->news));
Notification::send([$event->news], new Messages\NewsAdded($event->news));
}
}
+2 -2
View File
@@ -2,11 +2,11 @@
namespace App\Providers;
use App\Cron\Hourly\ClearExpiredSimbrief;
use App\Cron\Hourly\DeletePireps;
use App\Cron\Hourly\RemoveExpiredBids;
use App\Cron\Hourly\RemoveExpiredLiveFlights;
use App\Cron\Nightly\ApplyExpenses;
use App\Cron\Nightly\ClearExpiredSimbrief;
use App\Cron\Nightly\NewVersionCheck;
use App\Cron\Nightly\PilotLeave;
use App\Cron\Nightly\RecalculateBalances;
@@ -31,7 +31,6 @@ class CronServiceProvider extends ServiceProvider
SetActiveFlights::class,
RecalculateStats::class,
NewVersionCheck::class,
ClearExpiredSimbrief::class,
],
CronWeekly::class => [
@@ -45,6 +44,7 @@ class CronServiceProvider extends ServiceProvider
DeletePireps::class,
RemoveExpiredBids::class,
RemoveExpiredLiveFlights::class,
ClearExpiredSimbrief::class,
],
];
}
+7 -2
View File
@@ -11,7 +11,7 @@ use App\Listeners\ExpenseListener;
use App\Listeners\FinanceEventHandler;
use App\Listeners\PirepEventsHandler;
use App\Listeners\UserStateListener;
use App\Notifications\EventHandler;
use App\Notifications\NotificationEventsHandler;
use Codedge\Updater\Events\UpdateAvailable;
use Codedge\Updater\Events\UpdateSucceeded;
use Illuminate\Auth\Events\Registered;
@@ -39,12 +39,17 @@ class EventServiceProvider extends ServiceProvider
UpdateAvailable::class => [],
UpdateSucceeded::class => [],
// Log messages out to the console if running there
'Illuminate\Log\Events\MessageLogged' => [
'App\Listeners\MessageLoggedListener',
],
];
protected $subscribe = [
BidEventHandler::class,
FinanceEventHandler::class,
EventHandler::class,
NotificationEventsHandler::class,
AwardHandler::class,
PirepEventsHandler::class,
];
+8 -4
View File
@@ -90,10 +90,6 @@ class RouteServiceProvider extends ServiceProvider
Route::post('/run-migrations', 'UpdateController@run_migrations')->name('run_migrations');
Route::get('/complete', 'UpdateController@complete')->name('complete');
// Routes for the update downloader
Route::get('/downloader', 'UpdateController@updater')->name('updater');
Route::post('/downloader', 'UpdateController@update_download')->name('update_download');
});
}
@@ -144,6 +140,7 @@ class RouteServiceProvider extends ServiceProvider
Route::get('simbrief/generate', 'SimBriefController@generate')->name('simbrief.generate');
Route::post('simbrief/apicode', 'SimBriefController@api_code')->name('simbrief.api_code');
Route::get('simbrief/check_ofp', 'SimBriefController@check_ofp')->name('simbrief.check_ofp');
Route::get('simbrief/update_ofp', 'SimBriefController@update_ofp')->name('simbrief.update_ofp');
Route::get('simbrief/{id}', 'SimBriefController@briefing')->name('simbrief.briefing');
Route::get('simbrief/{id}/prefile', 'SimBriefController@prefile')->name('simbrief.prefile');
Route::get('simbrief/{id}/cancel', 'SimBriefController@cancel')->name('simbrief.cancel');
@@ -537,6 +534,9 @@ class RouteServiceProvider extends ServiceProvider
Route::get('fleet', 'FleetController@index');
Route::get('fleet/aircraft/{id}', 'FleetController@get_aircraft');
Route::get('subfleet', 'FleetController@index');
Route::get('subfleet/aircraft/{id}', 'FleetController@get_aircraft');
Route::get('flights', 'FlightController@index');
Route::get('flights/search', 'FlightController@search');
Route::get('flights/{id}', 'FlightController@get');
@@ -584,6 +584,10 @@ class RouteServiceProvider extends ServiceProvider
Route::get('user/fleet', 'UserController@fleet');
Route::get('user/pireps', 'UserController@pireps');
Route::get('bids', 'UserController@bids');
Route::get('bids/{id}', 'UserController@get_bid');
Route::get('user/bids/{id}', 'UserController@get_bid');
Route::get('user/bids', 'UserController@bids');
Route::put('user/bids', 'UserController@bids');
Route::post('user/bids', 'UserController@bids');
+3 -3
View File
@@ -69,11 +69,11 @@ class AcarsRepository extends Repository
->where(['state' => PirepState::IN_PROGRESS]);
if ($live_time !== null && $live_time > 0) {
$st = Carbon::now('UTC')->subHours($live_time);
$q = $q->whereDate('created_at', '>=', $st);
$st = Carbon::now()->subHours($live_time);
$q = $q->where('updated_at', '>=', $st);
}
$q = $q->orderBy('created_at', 'desc');
$q = $q->orderBy('updated_at', 'desc');
return $q->get();
}
+7 -2
View File
@@ -36,9 +36,14 @@ class UserRepository extends Repository
*
* @return \App\Models\UserField[]|\Illuminate\Database\Eloquent\Collection|\Illuminate\Support\Collection
*/
public function getUserFields(User $user, $only_public_fields = true): Collection
public function getUserFields(User $user, $only_public_fields = null): Collection
{
$fields = UserField::where(['private' => !$only_public_fields])->get();
if (is_bool($only_public_fields)) {
$fields = UserField::where(['private' => !$only_public_fields])->get();
} else {
$fields = UserField::get();
}
return $fields->map(function ($field, $_) use ($user) {
foreach ($user->fields as $userFieldValue) {
if ($userFieldValue->field->slug === $field->slug) {
+26 -5
View File
@@ -8,6 +8,7 @@ use App\Exceptions\UserBidLimit;
use App\Models\Bid;
use App\Models\Flight;
use App\Models\Pirep;
use App\Models\SimBrief;
use App\Models\User;
use Illuminate\Support\Facades\Log;
@@ -30,12 +31,24 @@ class BidService extends Service
*
* @param $bid_id
*
* @return \App\Models\Bid|\Illuminate\Database\Eloquent\Model|tests/ImporterTest.php:521object|null
* @return \App\Models\Bid|\Illuminate\Database\Eloquent\Model|object|null
*/
public function getBid($bid_id)
public function getBid(User $user, $bid_id): Bid
{
return Bid::with(['flight', 'flight.simbrief'])
->where(['id' => $bid_id])->first();
$with = [
'flight',
'flight.fares',
'flight.simbrief' => function ($query) use ($user) {
$query->where('user_id', $user->id);
},
'flight.simbrief.aircraft',
'flight.simbrief.aircraft.subfleet',
'flight.subfleets',
'flight.subfleets.aircraft',
'flight.subfleets.fares',
];
return Bid::with($with)->where(['id' => $bid_id])->first();
}
/**
@@ -132,7 +145,7 @@ class BidService extends Service
$flight->has_bid = true;
$flight->save();
return $this->getBid($bid->id);
return $this->getBid($user, $bid->id);
}
/**
@@ -152,6 +165,14 @@ class BidService extends Service
$bid->forceDelete();
}
// Remove SimBrief OFP when removing the bid if it is not flown
if (setting('simbrief.only_bids')) {
$simbrief = SimBrief::where([
'user_id' => $user->id,
'flight_id' => $flight->id,
])->whereNull('pirep_id')->delete();
}
// Only flip the flag if there are no bids left for this flight
$bid_count = Bid::where(['flight_id' => $flight->id])->count();
if ($bid_count === 0) {
+5 -9
View File
@@ -292,14 +292,14 @@ class FareService extends Service
/**
* Save the list of fares
*
* @param Pirep $pirep
* @param array $fares ['fare_id', 'count']
* @param Pirep $pirep
* @param PirepFare[] $fares
*
* @throws \Exception
*/
public function saveForPirep(Pirep $pirep, array $fares)
{
if (!$fares) {
if (!$fares || empty($fares)) {
return;
}
@@ -308,13 +308,9 @@ class FareService extends Service
// Add them in
foreach ($fares as $fare) {
$fare['pirep_id'] = $pirep->id;
// other fields: ['fare_id', 'count']
$fare->pirep_id = $pirep->id;
Log::info('Saving fare pirep='.$pirep->id.', fare='.$fare['count']);
$field = new PirepFare($fare);
$field->save();
$fare->save();
}
}
}
+55 -23
View File
@@ -8,7 +8,10 @@ use App\Models\Aircraft;
use App\Models\Airport;
use App\Models\Enums\ExpenseType;
use App\Models\Enums\FareType;
use App\Models\Enums\FuelType;
use App\Models\Enums\PirepSource;
use App\Models\Enums\PirepState;
use App\Models\Enums\PirepStatus;
use App\Models\Expense;
use App\Models\Pirep;
use App\Models\Subfleet;
@@ -151,18 +154,39 @@ class PirepFinanceService extends Service
*/
public function payFuelCosts(Pirep $pirep): void
{
// Get Airport Fuel Prices or Use Defaults
$ap = $pirep->dpt_airport;
// Get Airport Fuel Cost or revert back to settings
if (empty($ap->fuel_jeta_cost)) {
$fuel_cost = setting('airports.default_jet_a_fuel_cost');
// Get Aircraft Fuel Type from Subfleet
// And set $fuel_cost according to type (Failsafe is Jet A)
$sf = $pirep->aircraft->subfleet;
if ($sf) {
$fuel_type = $sf->fuel_type;
} else {
$fuel_cost = $ap->fuel_jeta_cost;
$fuel_type = FuelType::JET_A;
}
if ($fuel_type === FuelType::LOW_LEAD) {
$fuel_cost = !empty($ap->fuel_100ll_cost) ? $ap->fuel_100ll_cost : setting('airports.default_100ll_fuel_cost');
} elseif ($fuel_type === FuelType::MOGAS) {
$fuel_cost = !empty($ap->fuel_mogas_cost) ? $ap->fuel_mogas_cost : setting('airports.default_mogas_fuel_cost');
} else { // Default to JetA
$fuel_cost = !empty($ap->fuel_jeta_cost) ? $ap->fuel_jeta_cost : setting('airports.default_jet_a_fuel_cost');
}
if (setting('pireps.advanced_fuel', false)) {
$ac = $pirep->aircraft;
// Reading second row by skip(1) to reach the previous accepted pirep. Current pirep is at the first row
$prev_flight = Pirep::where('aircraft_id', $ac->id)->where('state', 2)->where('status', 'ONB')->orderby('submitted_at', 'desc')->skip(1)->first();
// To get proper fuel values, we need to fetch current pirep and older ones only. Scenario: ReCalculating finances
$prev_flight = Pirep::where([
'aircraft_id' => $pirep->aircraft->id,
'state' => PirepState::ACCEPTED,
'status' => PirepStatus::ARRIVED,
])
->where('submitted_at', '<=', $pirep->submitted_at)
->orderby('submitted_at', 'desc')
->skip(1)
->first();
if ($prev_flight) {
// If there is a pirep use its values to calculate the remaining fuel
// and calculate the uplifted fuel amount for this pirep
@@ -181,8 +205,7 @@ class PirepFinanceService extends Service
}
$debit = Money::createFromAmount($fuel_amount * $fuel_cost);
Log::info('Finance: Fuel cost, (fuel='.$fuel_amount.', cost='.$fuel_cost.') D='
.$debit->getAmount());
Log::info('Finance: Fuel cost, (fuel='.$fuel_amount.', cost='.$fuel_cost.') D='.$debit->getAmount());
$this->financeSvc->debitFromJournal(
$pirep->airline->journal,
@@ -283,20 +306,29 @@ class PirepFinanceService extends Service
}
// Form the memo, with some specific ones depending on the group
if ($expense->ref_model === Subfleet::class
&& $expense->ref_model_id === $pirep->aircraft->subfleet->id
) {
$memo = "Subfleet Expense: {$expense->name} ({$pirep->aircraft->subfleet->name})";
$transaction_group = "Subfleet: {$expense->name} ({$pirep->aircraft->subfleet->name})";
} elseif ($expense->ref_model === Aircraft::class
&& $expense->ref_model_id === $pirep->aircraft->id
) {
$memo = "Aircraft Expense: {$expense->name} ({$pirep->aircraft->name})";
$transaction_group = "Aircraft: {$expense->name} "
."({$pirep->aircraft->name}-{$pirep->aircraft->registration})";
if ($expense->ref_model === Subfleet::class) {
if ((int) ($expense->ref_model_id) === $pirep->aircraft->subfleet->id) {
$memo = "Subfleet Expense: $expense->name ({$pirep->aircraft->subfleet->name}) dd";
$transaction_group = "Subfleet: $expense->name ({$pirep->aircraft->subfleet->name})";
} else { // Skip any subfleets that weren't used for this flight
return;
}
} elseif ($expense->ref_model === Aircraft::class) {
if ((int) ($expense->ref_model_id) === $pirep->aircraft->id) {
$memo = "Aircraft Expense: $expense->name ({$pirep->aircraft->name})";
$transaction_group = "Aircraft: $expense->name "
."({$pirep->aircraft->name}-{$pirep->aircraft->registration})";
} else { // Skip any aircraft expenses that weren't used for this flight
return;
}
} else {
$memo = "Expense: {$expense->name}";
$transaction_group = "Expense: {$expense->name}";
// Skip any expenses that aren't for the airline this flight was for
if ($expense->airline_id && $expense->airline_id !== $pirep->airline_id) {
return;
}
$memo = "Expense: $expense->name";
$transaction_group = "Expense: $expense->name";
}
$debit = Money::createFromAmount($expense->amount);
@@ -339,8 +371,8 @@ class PirepFinanceService extends Service
$expenses->map(function (Expense $expense, $i) use ($pirep) {
Log::info('Finance: PIREP: '.$pirep->id.', airport expense:', $expense->toArray());
$memo = "Airport Expense: {$expense->name} ({$expense->ref_model_id})";
$transaction_group = "Airport: {$expense->ref_model_id}";
$memo = "Airport Expense: $expense->name ($expense->ref_model_id)";
$transaction_group = "Airport: $expense->ref_model_id";
$debit = Money::createFromAmount($expense->amount);
+3 -3
View File
@@ -309,7 +309,7 @@ class GeoService extends Service
foreach ($all_route_points as $point) {
$route->addPoint($point->lat, $point->lon, [
'name' => $point->name,
'popup' => $point->name.' ('.$point->name.')',
'popup' => $point->name.' ('.$point->lat.', '.$point->lon.')',
'icon' => '',
]);
}
@@ -351,7 +351,7 @@ class GeoService extends Service
foreach ($planned_route as $point) {
$planned->addPoint($point->lat, $point->lon, [
'name' => $point->name,
'popup' => $point->name.' ('.$point->name.')',
'popup' => $point->name.' ('.$point->lat.', '.$point->lon.')',
]);
}
@@ -369,7 +369,7 @@ class GeoService extends Service
$actual->addPoint($point->lat, $point->lon, [
'pirep_id' => $pirep->id,
'name' => $point->altitude,
'popup' => 'GS: '.$point->gs.'<br />Alt: '.$point->altitude,
'popup' => 'Spd: '.$point->gs.' kts<br />Alt: '.$point->altitude.' ft<br />Pos: '.$point->lat.', '.$point->lon,
]);
}
+9 -1
View File
@@ -85,7 +85,6 @@ class FlightImporter extends ImportExport
'flight_number' => $row['flight_number'],
'route_code' => $row['route_code'],
'route_leg' => $row['route_leg'],
'visible' => true,
], $row);
$row['dpt_airport'] = strtoupper($row['dpt_airport']);
@@ -99,6 +98,10 @@ class FlightImporter extends ImportExport
$flight->setAttribute('alt_airport_id', $row['alt_airport']);
}
// Handle Route and Level Fields
$flight->setAttribute('route', strtoupper($row['route']));
$flight->setAttribute('level', $row['level']);
// Any specific transformations
// Check for a valid value
@@ -209,6 +212,11 @@ class FlightImporter extends ImportExport
$count = 0;
$subfleets = $this->parseMultiColumnValues($col);
foreach ($subfleets as $subfleet_type) {
$subfleet_type = trim($subfleet_type);
if (empty($subfleet_type)) {
continue;
}
$subfleet = Subfleet::firstOrCreate(
['type' => $subfleet_type],
['name' => $subfleet_type]
+3 -3
View File
@@ -16,9 +16,9 @@ use Symfony\Component\HttpFoundation\File\Exception\FileException;
class ConfigService extends Service
{
protected static $defaultValues = [
'APP_ENV' => 'prod',
'APP_ENV' => 'production',
'APP_KEY' => '',
'APP_DEBUG' => true,
'APP_DEBUG' => false,
'APP_LOCALE' => 'en',
'DEBUG_TOOLBAR' => false,
'SITE_NAME' => '',
@@ -33,7 +33,7 @@ class ConfigService extends Service
'DB_EMULATE_PREPARES' => false,
'CACHE_DRIVER' => 'array',
'CACHE_PREFIX' => '',
'MAIL_DRIVER' => 'mail',
'MAIL_DRIVER' => 'log',
'MAIL_HOST' => '',
'MAIL_PORT' => 587,
'MAIL_ENCRYPTION' => '',
+11 -7
View File
@@ -68,7 +68,7 @@ class MigrationService extends Service
$availMigrations[] = $filepath;
}
Log::info('Migrations available: '.count($availMigrations));
//Log::info('Migrations available: '.count($availMigrations));
return $availMigrations;
}
@@ -77,12 +77,12 @@ class MigrationService extends Service
* Run all of the migrations that are available. Just call artisan since
* it looks into all of the module directories, etc
*/
public function runAllMigrations()
public function runAllMigrations(): string
{
// A little ugly, run the main migration first, this makes sure the migration table is there
$output = '';
Artisan::call('migrate');
Artisan::call('migrate', ['--force' => true]);
$output .= trim(Artisan::output());
// Then get any remaining migrations that are left over
@@ -92,10 +92,14 @@ class MigrationService extends Service
$migrator = $this->getMigrator();
$availMigrations = $this->migrationsAvailable();
Log::info('Running '.count($availMigrations).' available migrations');
$ret = $migrator->run($availMigrations);
Log::info('Ran '.count($ret).' migrations');
if (count($availMigrations) > 0) {
Log::info('Running '.count($availMigrations).' available migrations');
$ret = $migrator->run($availMigrations);
Log::info('Ran '.count($ret).' migrations');
return $output."\n".implode("\n", $ret);
return $output."\n".implode("\n", $ret);
}
return $output;
}
}
+4 -1
View File
@@ -21,7 +21,10 @@ class SeederService extends Service
private $offsets = [];
// Map an environment to a seeder directory, if we want to share
public static $seed_mapper = [];
public static $seed_mapper = [
'local' => 'dev',
'production' => 'prod',
];
public function __construct(DatabaseService $databaseSvc)
{
+4
View File
@@ -48,6 +48,10 @@ class AviationWeather extends Metar
$res = $this->httpClient->get($url, []);
$xml = simplexml_load_string($res);
if ($xml->errors && count($xml->errors->children()) > 0) {
return '';
}
$attrs = $xml->data->attributes();
if (!isset($attrs['num_results'])) {
return '';
+57 -13
View File
@@ -88,13 +88,56 @@ class ModuleService extends Service
}
/**
* Get All modules from Database
* Get all of the modules from database but make sure they also exist on disk
*
* @return object
*/
public function getAllModules(): object
public function getAllModules(): array
{
return Module::all();
$modules = [];
$moduleList = Module::all();
/** @var Module $module */
foreach ($moduleList as $module) {
/** @var \Nwidart\Modules\Module $moduleInstance */
$moduleInstance = \Nwidart\Modules\Facades\Module::find($module->name);
if (empty($moduleInstance)) {
continue;
}
if (file_exists($moduleInstance->getPath())) {
$modules[] = $module;
}
}
return $modules;
}
/**
* Determine if a module is active - also checks that the module exists properly
*
* @param string $name
*
* @return bool
*/
public function isModuleActive(string $name): bool
{
$module = Module::where('name', $name)->first();
if (empty($module)) {
return false;
}
/** @var \Nwidart\Modules\Module $moduleInstance */
$moduleInstance = \Nwidart\Modules\Facades\Module::find($module->name);
if (empty($moduleInstance)) {
return false;
}
if (!file_exists($moduleInstance->getPath())) {
return false;
}
return $moduleInstance->isEnabled();
}
/**
@@ -102,9 +145,9 @@ class ModuleService extends Service
*
* @param $id
*
* @return object
* @return Module
*/
public function getModule($id): object
public function getModule($id): Module
{
return Module::find($id);
}
@@ -126,7 +169,11 @@ class ModuleService extends Service
'enabled' => 1,
]);
Artisan::call('module:migrate '.$module_name);
try {
Artisan::call('module:migrate '.$module_name, ['--force' => true]);
} catch (Exception $e) {
Log::error('Error running migration for '.$module_name.'; error='.$e);
}
return true;
}
@@ -144,22 +191,20 @@ class ModuleService extends Service
*/
public function installModule(UploadedFile $file): FlashNotifier
{
$file_ext = $file->getClientOriginalExtension();
$file_ext = strtolower($file->getClientOriginalExtension());
$allowed_extensions = ['zip', 'tar', 'gz'];
if (!in_array($file_ext, $allowed_extensions, true)) {
throw new ModuleInvalidFileType();
}
$module = null;
$new_dir = rand();
File::makeDirectory(
storage_path('app/tmp/modules/'.$new_dir),
0777,
true
);
$temp_ext_folder = storage_path('app/tmp/modules/'.$new_dir);
$temp = storage_path('app/tmp/modules/'.$new_dir);
@@ -215,7 +260,6 @@ class ModuleService extends Service
}
File::moveDirectory($temp, $toCopy);
File::deleteDirectory($temp_ext_folder);
try {
@@ -225,7 +269,7 @@ class ModuleService extends Service
}
Artisan::call('config:cache');
Artisan::call('module:migrate '.$module);
Artisan::call('module:migrate '.$module, ['--force' => true]);
return flash()->success('Module Installed');
}
@@ -246,7 +290,7 @@ class ModuleService extends Service
]);
if ($status === true) {
Artisan::call('module:migrate '.$module->name);
Artisan::call('module:migrate '.$module->name, ['--force' => true]);
}
return true;
+127 -34
View File
@@ -11,13 +11,16 @@ use App\Events\PirepRejected;
use App\Events\UserStatsChanged;
use App\Exceptions\AircraftInvalid;
use App\Exceptions\AircraftNotAtAirport;
use App\Exceptions\AircraftNotAvailable;
use App\Exceptions\AircraftPermissionDenied;
use App\Exceptions\AirportNotFound;
use App\Exceptions\PirepCancelNotAllowed;
use App\Exceptions\PirepError;
use App\Exceptions\UserNotAtAirport;
use App\Models\Acars;
use App\Models\Aircraft;
use App\Models\Enums\AcarsType;
use App\Models\Enums\AircraftState;
use App\Models\Enums\FlightType;
use App\Models\Enums\PirepSource;
use App\Models\Enums\PirepState;
@@ -33,7 +36,6 @@ use App\Repositories\AircraftRepository;
use App\Repositories\AirportRepository;
use App\Repositories\PirepRepository;
use Carbon\Carbon;
use function count;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Support\Facades\Log;
@@ -42,16 +44,18 @@ class PirepService extends Service
private $aircraftRepo;
private $airportRepo;
private $airportSvc;
private $fareSvc;
private $geoSvc;
private $pirepRepo;
private $simBriefSvc;
private $userSvc;
/**
* @param AircraftRepository $aircraftRepo
* @param GeoService $geoSvc
* @param AirportRepository $airportRepo
* @param AirportService $airportSvc
* @param AircraftRepository $aircraftRepo
* @param FareService $fareSvc
* @param GeoService $geoSvc
* @param PirepRepository $pirepRepo
* @param SimBriefService $simBriefSvc
* @param UserService $userSvc
@@ -60,6 +64,7 @@ class PirepService extends Service
AirportRepository $airportRepo,
AirportService $airportSvc,
AircraftRepository $aircraftRepo,
FareService $fareSvc,
GeoService $geoSvc,
PirepRepository $pirepRepo,
SimBriefService $simBriefSvc,
@@ -68,6 +73,7 @@ class PirepService extends Service
$this->airportRepo = $airportRepo;
$this->airportSvc = $airportSvc;
$this->aircraftRepo = $aircraftRepo;
$this->fareSvc = $fareSvc;
$this->geoSvc = $geoSvc;
$this->pirepRepo = $pirepRepo;
$this->simBriefSvc = $simBriefSvc;
@@ -77,15 +83,17 @@ class PirepService extends Service
/**
* Create a prefiled PIREP
*
* @param \App\Models\User $user
* @param array $attrs
* @param User $user
* @param array $attrs
* @param PirepFieldValue[] $fields
* @param PirepFare[] $fares
*
* @throws AirportNotFound If one of the departure or arrival airports isn't found locally
* @throws \Exception
*
* @return \App\Models\Pirep
*/
public function prefile(User $user, array $attrs): Pirep
public function prefile(User $user, array $attrs, array $fields = [], array $fares = []): Pirep
{
$attrs['user_id'] = $user->id;
$attrs['state'] = PirepState::IN_PROGRESS;
@@ -131,13 +139,33 @@ class PirepService extends Service
throw new AircraftPermissionDenied($user, $pirep->aircraft);
}
// See if this aircraft is at the departure airport
// See if this aircraft is valid
/** @var Aircraft $aircraft */
$aircraft = $this->aircraftRepo->findWithoutFail($pirep->aircraft_id);
if ($aircraft === null) {
throw new AircraftInvalid($aircraft);
}
// See if this aircraft is available for flight
/** @var Aircraft $aircraft */
$aircraft = $this->aircraftRepo->where('id', $pirep->aircraft_id)->where('state', AircraftState::PARKED)->first();
if ($aircraft === null) {
throw new AircraftNotAvailable($pirep->aircraft);
}
// See if this aircraft is being used by another user's active simbrief ofp
if (setting('simbrief.block_aircraft', false)) {
$sb_aircraft = SimBrief::select('aircraft_id')
->where('aircraft_id', $pirep->aircraft_id)
->where('user_id', '!=', $pirep->user_id)
->whereNotNull('flight_id')
->count();
if ($sb_aircraft > 0) {
throw new AircraftNotAvailable($pirep->aircraft);
}
}
// See if this aircraft is at the departure airport
/* @noinspection NotOptimalIfConditionsInspection */
if (setting('pireps.only_aircraft_at_dpt_airport') && $aircraft->airport_id !== $pirep->dpt_airport_id) {
throw new AircraftNotAtAirport($pirep->aircraft);
@@ -153,9 +181,24 @@ class PirepService extends Service
}
}
event(new PirepPrefiled($pirep));
$pirep->status = PirepStatus::INITIATED;
$pirep->save();
$pirep->refresh();
// Check if there is a simbrief_id, update it to have the pirep_id
// Keep the flight_id until the end of flight (pirep file)
if (array_key_exists('simbrief_id', $attrs)) {
/** @var SimBrief $simbrief */
$simbrief = SimBrief::find($attrs['simbrief_id']);
if ($simbrief) {
$this->simBriefSvc->attachSimbriefToPirep($pirep, $simbrief, true);
}
}
$this->updateCustomFields($pirep->id, $fields);
$this->fareSvc->saveForPirep($pirep, $fares);
event(new PirepPrefiled($pirep));
return $pirep;
}
@@ -168,10 +211,10 @@ class PirepService extends Service
*
* @return Pirep
*/
public function create(Pirep $pirep, array $field_values = []): Pirep
public function create(Pirep $pirep, array $fields = []): Pirep
{
if (empty($field_values)) {
$field_values = [];
if (empty($fields)) {
$fields = [];
}
// Check the block times. If a block on (arrival) time isn't
@@ -207,9 +250,23 @@ class PirepService extends Service
$pirep->save();
$pirep->refresh();
if (count($field_values) > 0) {
$this->updateCustomFields($pirep->id, $field_values);
}
$this->updateCustomFields($pirep->id, $fields);
return $pirep;
}
/**
* @param PirepFieldValue[] $fields
* @param PirepFare[] $fares
*
* @throws \Prettus\Validator\Exceptions\ValidatorException
* @throws \Exception
*/
public function update(string $pirep_id, array $attrs, array $fields = [], array $fares = []): Pirep
{
$pirep = $this->pirepRepo->update($attrs, $pirep_id);
$this->updateCustomFields($pirep_id, $fields);
$this->fareSvc->saveForPirep($pirep, $fares);
return $pirep;
}
@@ -217,18 +274,31 @@ class PirepService extends Service
/**
* Finalize a PIREP (meaning it's been filed)
*
* @param Pirep $pirep
* @param array $attrs
* @param array PirepFieldValue[] $field_values
* @param Pirep $pirep
* @param array $attrs
* @param PirepFieldValue[] $fields
* @param PirepFare[] $fares
*
* @throws \Exception
*
* @return Pirep
*/
public function file(Pirep $pirep, array $attrs = [], array $field_values = []): Pirep
public function file(Pirep $pirep, array $attrs = [], array $fields = [], array $fares = []): Pirep
{
if (empty($field_values)) {
$field_values = [];
if (empty($fields)) {
$fields = [];
}
// Check if the PIREP has already been submitted
$is_already_submitted = in_array($pirep->state, [
PirepState::PENDING,
PirepState::ACCEPTED,
PirepState::CANCELLED,
PirepState::REJECTED,
], true);
if ($is_already_submitted) {
throw new PirepError($pirep, 'PIREP has already been submitted');
}
$attrs['state'] = PirepState::PENDING;
@@ -273,9 +343,8 @@ class PirepService extends Service
$pirep->save();
$pirep->refresh();
if (count($field_values) > 0) {
$this->updateCustomFields($pirep->id, $field_values);
}
$this->updateCustomFields($pirep->id, $fields);
$this->fareSvc->saveForPirep($pirep, $fares);
return $pirep;
}
@@ -404,6 +473,19 @@ class PirepService extends Service
}
}
// Check if there is a simbrief_id, change it to be set to the PIREP
// at the end of the flight when it's been submitted finally.
// Prefile, Save (as draft) and File already have this but the Submit button
// visible at pireps.show blade uses this function so Simbrief also needs to
// checked here too (to remove the flight_id and release the aircraft)
if (!empty($pirep->simbrief)) {
/** @var SimBrief $simbrief */
$simbrief = SimBrief::find($pirep->simbrief->id);
if ($simbrief) {
$this->simBriefSvc->attachSimbriefToPirep($pirep, $simbrief);
}
}
Log::info('New PIREP filed', [$pirep]);
event(new PirepFiled($pirep));
@@ -459,30 +541,41 @@ class PirepService extends Service
*/
public function delete(Pirep $pirep): void
{
$user_id = $pirep->user_id;
$w = ['pirep_id' => $pirep->id];
PirepComment::where($w)->forceDelete();
PirepFare::where($w)->forceDelete();
PirepFieldValue::where($w)->forceDelete();
SimBrief::where($w)->forceDelete();
$pirep->forceDelete();
// Update the user's last PIREP
$last_pirep = Pirep::where(['user_id' => $user_id, 'state' => PirepState::ACCEPTED])
->latest('submitted_at')
->first();
$user = User::find($user_id);
$user->last_pirep_id = !empty($last_pirep) ? $last_pirep->id : null;
$user->save();
}
/**
* Update any custom PIREP fields
*
* @param $pirep_id
* @param array $field_values
* @param string $pirep_id
* @param PirepFieldValue[] $field_values
*/
public function updateCustomFields($pirep_id, array $field_values)
public function updateCustomFields(string $pirep_id, array $field_values): void
{
if (!$field_values || empty($field_values)) {
return;
}
foreach ($field_values as $fv) {
PirepFieldValue::updateOrCreate(
['pirep_id' => $pirep_id,
'name' => $fv['name'],
],
['value' => $fv['value'],
'source' => $fv['source'],
]
['pirep_id' => $pirep_id, 'name' => $fv['name']],
['value' => $fv['value'], 'source' => $fv['source']]
);
}
}
@@ -495,7 +588,7 @@ class PirepService extends Service
*
* @return Pirep
*/
public function changeState(Pirep $pirep, int $new_state)
public function changeState(Pirep $pirep, int $new_state): Pirep
{
Log::info('PIREP '.$pirep->id.' state change from '.$pirep->state.' to '.$new_state);
+30 -18
View File
@@ -26,11 +26,13 @@ class SimBriefService extends Service
* Check to see if the OFP exists server-side. If it does, download it and
* cache it immediately
*
* @param string $user_id User who generated this
* @param string $ofp_id The SimBrief OFP ID
* @param string $flight_id The flight ID
* @param string $ac_id The aircraft ID
* @param array $fares Full list of fares for the flightß
* @param string $user_id User who generated this
* @param string $ofp_id The SimBrief OFP ID
* @param string $flight_id The flight ID
* @param string $ac_id The aircraft ID
* @param array $fares Full list of fares for the flight
* @param string $sb_userid User's Simbrief ID (Used for Update)
* @param string $sb_static_id Static ID for the generated OFP (Used for Update)
*
* @return SimBrief|null
*/
@@ -39,10 +41,18 @@ class SimBriefService extends Service
string $ofp_id,
string $flight_id,
string $ac_id,
array $fares = []
array $fares = [],
string $sb_user_id = null,
string $sb_static_id = null
) {
$uri = str_replace('{id}', $ofp_id, config('phpvms.simbrief_url'));
if ($sb_user_id && $sb_static_id) {
// $uri = str_replace('{sb_user_id}', $sb_user_id, config('phpvms.simbrief_update_url'));
// $uri = str_replace('{sb_static_id}', $sb_static_id, $uri);
$uri = 'https://www.simbrief.com/api/xml.fetcher.php?userid='.$sb_user_id.'&static_id='.$sb_static_id;
}
$opts = [
'connect_timeout' => 2, // wait two seconds by default
'allow_redirects' => false,
@@ -132,21 +142,23 @@ class SimBriefService extends Service
*
* 1. Read from the XML the basic PIREP info (dep, arr), and then associate the PIREP
* to the flight ID
* 2. Remove the flight ID from the SimBrief field and assign the pirep_id to the row
* 2. Remove the flight ID from the SimBrief model and assign the pirep ID to the row
* at the end of the flight. Keep flight ID until the flight ends (pirep file).
* 3. Update the planned flight route in the acars table
* 4. Add additional flight fields (ones which match ACARS)
*
* @param $pirep
* @param SimBrief $simBrief The briefing to create the PIREP from
* @param SimBrief $simBrief The briefing to create the PIREP from
* @param bool $keep_flight True keeps the flight_id, default is false
*
* @return \App\Models\Pirep
*/
public function attachSimbriefToPirep($pirep, SimBrief $simBrief): Pirep
public function attachSimbriefToPirep($pirep, SimBrief $simBrief, $keep_flight = false): Pirep
{
$this->addRouteToPirep($pirep, $simBrief);
$simBrief->pirep_id = $pirep->id;
$simBrief->flight_id = null;
$simBrief->flight_id = !empty($keep_flight) ? $pirep->flight_id : null;
$simBrief->save();
return $pirep;
@@ -185,24 +197,24 @@ class SimBriefService extends Service
}
/**
* Remove any expired entries from the SimBrief table. Expired means there's
* a flight_id attached to it, but no pirep_id (meaning it was never used for
* an actual flight)
* Remove any expired entries from the SimBrief table.
* Expired means there's a flight_id attached to it, but no pirep_id
* (meaning it was never used for an actual flight)
*/
public function removeExpiredEntries(): void
{
$expire_days = setting('simbrief.expire_days', 5);
$expire_time = Carbon::now('UTC')->subDays($expire_days)->toDateTimeString();
$expire_hours = setting('simbrief.expire_hours', 6);
$expire_time = Carbon::now('UTC')->subHours($expire_hours);
$briefs = SimBrief::where([
['pirep_id', '=', ''],
['created_at', '<', $expire_time],
['pirep_id', null],
['created_at', '<=', $expire_time],
])->get();
foreach ($briefs as $brief) {
$brief->delete();
// TODO: Delete any assets
// TODO: Delete any assets (Which assets ?)
}
}
}

Some files were not shown because too many files have changed in this diff Show More