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
This commit is contained in:
Nabeel S
2021-06-04 10:51:59 -04:00
committed by GitHub
parent 17447c6903
commit 9b2e466b7e
46 changed files with 1249 additions and 456 deletions

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

View File

@@ -17,8 +17,8 @@ class EmailTest extends Command
*/
public function handle()
{
/** @var App\Notifications\EventHandler $eventHandler */
$eventHandler = app(App\Notifications\EventHandler::class);
/** @var App\Notifications\NotificationEventsHandler $eventHandler */
$eventHandler = app(App\Notifications\NotificationEventsHandler::class);
$news = new App\Models\News();
$news->user_id = 1;

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;
}
}

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
*/

View File

@@ -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');
});
}
}

View File

@@ -368,20 +368,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'

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;
}
}

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;
}
}

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;
}
/**
@@ -210,14 +204,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 +251,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,9 +296,9 @@ 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);
@@ -411,7 +404,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);
}

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
{

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
*

View File

@@ -33,7 +33,7 @@ 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';
protected static $labels = [
self::INITIATED => 'pireps.status.initialized',
@@ -58,6 +58,6 @@ 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',
];
}

View File

@@ -3,6 +3,7 @@
namespace App\Models;
use App\Contracts\Model;
use Illuminate\Notifications\Notifiable;
/**
* @property int id
@@ -13,6 +14,8 @@ use App\Contracts\Model;
*/
class News extends Model
{
use Notifiable;
public $table = 'news';
protected $fillable = [

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,8 @@ use Illuminate\Support\Collection;
* @property Flight|null flight
* @property Collection fields
* @property string status
* @property PirepState state
* @property int source
* @property int state
* @property int source
* @property string source_name
* @property Carbon submitted_at
* @property Carbon created_at
@@ -57,7 +61,9 @@ use Illuminate\Support\Collection;
*/
class Pirep extends Model
{
use AttributeEvents;
use HashIdTrait;
use Notifiable;
public $table = 'pireps';
@@ -137,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.
*/

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)
*/

View File

@@ -32,6 +32,7 @@ use Laratrust\Traits\LaratrustUserTrait;
* @property Rank rank
* @property Journal journal
* @property int rank_id
* @property string discord_id
* @property int state
* @property bool opt_in
* @property Pirep[] pireps
@@ -66,6 +67,7 @@ class User extends Authenticatable
'pilot_id',
'airline_id',
'rank_id',
'discord_id',
'api_key',
'country',
'home_airport_id',
@@ -89,6 +91,7 @@ class User extends Authenticatable
*/
protected $hidden = [
'api_key',
'discord_id',
'password',
'remember_token',
];

View File

@@ -0,0 +1,36 @@
<?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 Discord
{
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) {
$response = Psr7\Message::toString($e->getResponse());
Log::error('Error sending Discord notification: '.$e->getMessage().', '.$response);
}
}
}

View File

@@ -0,0 +1,140 @@
<?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
{
return [
'embeds' => [
[
'title' => $this->title,
'url' => $this->url,
'type' => 'rich',
'description' => $this->description,
// 'color' => hexdec($this->color),
'author' => $this->author,
'fields' => $this->fields,
'footer' => [
'text' => $this->footer ?? '',
],
'timestamp' => Carbon::now('UTC'),
],
],
];
}
}

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;

View File

@@ -4,17 +4,15 @@ namespace App\Notifications\Messages;
use App\Contracts\Notification;
use App\Models\User;
use App\Notifications\Channels\Discord\Discord;
use App\Notifications\Channels\Discord\DiscordMessage;
use App\Notifications\Channels\MailChannel;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
class AdminUserRegistered extends Notification implements ShouldQueue
{
use Queueable;
use MailChannel;
public $channels = ['mail'];
private $user;
/**
@@ -34,6 +32,32 @@ class AdminUserRegistered extends Notification implements ShouldQueue
);
}
public function via($notifiable)
{
return ['mail', Discord::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 [

View File

@@ -4,16 +4,15 @@ namespace App\Notifications\Messages;
use App\Contracts\Notification;
use App\Models\News;
use App\Notifications\Channels\Discord\Discord;
use App\Notifications\Channels\Discord\DiscordMessage;
use App\Notifications\Channels\MailChannel;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
class NewsAdded extends Notification implements ShouldQueue
{
use Queueable;
use MailChannel;
public $channels = ['mail'];
public $requires_opt_in = true;
private $news;
@@ -30,6 +29,34 @@ class NewsAdded extends Notification implements ShouldQueue
);
}
public function via($notifiable)
{
return ['mail', Discord::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.
*

View File

@@ -5,19 +5,15 @@ namespace App\Notifications\Messages;
use App\Contracts\Notification;
use App\Models\Pirep;
use App\Notifications\Channels\MailChannel;
use Illuminate\Bus\Queueable;
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 implements ShouldQueue
{
use Queueable;
use MailChannel;
public $channels = ['mail'];
private $pirep;
/**
@@ -38,6 +34,11 @@ class PirepAccepted extends Notification implements ShouldQueue
);
}
public function via($notifiable)
{
return ['mail'];
}
/**
* Get the array representation of the notification.
*

View File

@@ -0,0 +1,102 @@
<?php
namespace App\Notifications\Messages;
use App\Contracts\Notification;
use App\Models\Pirep;
use App\Notifications\Channels\Discord\Discord;
use App\Notifications\Channels\Discord\DiscordMessage;
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 [Discord::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.pireps.show', [$pirep->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,
];
}
}

View File

@@ -5,22 +5,18 @@ namespace App\Notifications\Messages;
use App\Contracts\Notification;
use App\Models\Pirep;
use App\Notifications\Channels\MailChannel;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
class PirepRejected extends Notification implements ShouldQueue
{
use Queueable;
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)
{
@@ -35,6 +31,11 @@ class PirepRejected extends Notification implements ShouldQueue
);
}
public function via($notifiable)
{
return ['mail'];
}
/**
* Get the array representation of the notification.
*

View File

@@ -0,0 +1,141 @@
<?php
namespace App\Notifications\Messages;
use App\Contracts\Notification;
use App\Models\Enums\PirepStatus;
use App\Models\Pirep;
use App\Notifications\Channels\Discord\Discord;
use App\Notifications\Channels\Discord\DiscordMessage;
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 => 'has cancelled',
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 [Discord::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.pireps.show', [$pirep->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,
];
}
}

View File

@@ -4,17 +4,19 @@ namespace App\Notifications\Messages;
use App\Contracts\Notification;
use App\Models\Pirep;
use App\Notifications\Channels\Discord\Discord;
use App\Notifications\Channels\Discord\DiscordMessage;
use App\Notifications\Channels\MailChannel;
use Illuminate\Bus\Queueable;
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 implements ShouldQueue
{
use Queueable;
use MailChannel;
public $channels = ['mail'];
private $pirep;
/**
@@ -35,6 +37,60 @@ class PirepSubmitted extends Notification implements ShouldQueue
);
}
public function via($notifiable)
{
return ['mail', Discord::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.pireps.show', [$pirep->id]),
'icon_url' => $pirep->user->resolveAvatarUrl(),
])
->fields($fields);
}
/**
* Get the array representation of the notification.
*

View File

@@ -5,20 +5,16 @@ 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 UserPending extends Notification implements ShouldQueue
{
use Queueable;
use MailChannel;
public $channels = ['mail'];
private $user;
/**
* @param \App\Models\User $user
* @param User $user
*/
public function __construct(User $user)
{
@@ -33,6 +29,11 @@ class UserPending extends Notification implements ShouldQueue
);
}
public function via($notifiable)
{
return ['mail'];
}
/**
* Get the array representation of the notification.
*

View File

@@ -5,22 +5,18 @@ 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 UserRegistered extends Notification implements ShouldQueue
{
use Queueable;
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)
{
@@ -35,6 +31,11 @@ class UserRegistered extends Notification implements ShouldQueue
);
}
public function via($notifiable)
{
return ['mail'];
}
public function toArray($notifiable)
{
return [

View File

@@ -13,8 +13,6 @@ class UserRejected extends Notification implements ShouldQueue
use Queueable;
use MailChannel;
public $channels = ['mail'];
private $user;
/**
@@ -33,6 +31,11 @@ class UserRejected extends Notification implements ShouldQueue
);
}
public function via($notifiable)
{
return ['mail'];
}
/**
* Get the array representation of the notification.
*

View File

@@ -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,7 +58,8 @@ class EventHandler extends Listener
}
try {
Notification::send([$user], $notification);
$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));
}
}

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;
@@ -49,7 +49,7 @@ class EventServiceProvider extends ServiceProvider
protected $subscribe = [
BidEventHandler::class,
FinanceEventHandler::class,
EventHandler::class,
NotificationEventsHandler::class,
AwardHandler::class,
PirepEventsHandler::class,
];

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();
}
}
}

View File

@@ -36,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;
@@ -45,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
@@ -63,6 +64,7 @@ class PirepService extends Service
AirportRepository $airportRepo,
AirportService $airportSvc,
AircraftRepository $aircraftRepo,
FareService $fareSvc,
GeoService $geoSvc,
PirepRepository $pirepRepo,
SimBriefService $simBriefSvc,
@@ -71,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;
@@ -80,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;
@@ -176,6 +181,7 @@ class PirepService extends Service
}
}
$pirep->status = PirepStatus::INITIATED;
$pirep->save();
$pirep->refresh();
@@ -189,6 +195,9 @@ class PirepService extends Service
}
}
$this->updateCustomFields($pirep->id, $fields);
$this->fareSvc->saveForPirep($pirep, $fares);
event(new PirepPrefiled($pirep));
return $pirep;
@@ -202,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
@@ -241,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;
}
@@ -251,18 +274,19 @@ 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
@@ -319,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;
}
@@ -518,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]
);
}
}
@@ -554,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);

View File

@@ -3,6 +3,7 @@
namespace App\Support;
use GuzzleHttp\Client as GuzzleClient;
use GuzzleHttp\RequestOptions;
/**
* Helper for HTTP stuff
@@ -11,9 +12,8 @@ class HttpClient
{
private $httpClient;
public function __construct(
GuzzleClient $httpClient
) {
public function __construct(GuzzleClient $httpClient)
{
$this->httpClient = $httpClient;
}
@@ -45,6 +45,29 @@ class HttpClient
return $body;
}
/**
* @param $uri
* @param $body
* @param array $opts
*
* @return mixed
*/
public function post($uri, $body, array $opts = [])
{
$opts = array_merge([
'connect_timeout' => 2,
RequestOptions::JSON => $body,
], $opts);
$response = $this->httpClient->post($uri, $opts);
$content_type = $response->getHeaderLine('content-type');
if (strpos($content_type, 'application/json') !== false) {
$body = \GuzzleHttp\json_decode($body, true);
}
return $body;
}
/**
* Download a file to a given path
*

View File

@@ -67,7 +67,8 @@
"laravel/legacy-factories": "^1.1",
"fakerphp/faker": "^v1.14",
"wildbit/swiftmailer-postmark": "^3.3",
"queueworker/sansdaemon": "^1.2"
"queueworker/sansdaemon": "^1.2",
"jpkleemans/attribute-events": "^1.1"
},
"require-dev": {
"barryvdh/laravel-debugbar": "^3.5",

573
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -26,6 +26,7 @@ services:
- ./composer-lock.json:/var/www/composer-lock.json
- ./env.php:/var/www/env.php
- ./resources/docker/php/www.conf:/usr/local/etc/php-fpm.d/www.conf
- ./vendor/var/www/vendor
depends_on:
- mysql
- redis

View File

@@ -46,7 +46,7 @@ class FlightRouteAwards extends Award
*/
public function check($dptarr = null): bool
{
if ($this->user->last_pirep_id === null) {
if ($this->user->last_pirep_id === null || empty($this->user->last_pirep)) {
return false;
}

View File

@@ -3,7 +3,7 @@
namespace Modules\Awards\Awards;
use App\Contracts\Award;
use Log;
use Illuminate\Support\Facades\Log;
/**
* All award classes need to extend Award and implement the check() method

View File

@@ -2,7 +2,7 @@
<div class="col-md-12">
<table class="table">
<tr>
<td>@lang('common.name')</td>
<td>{{ __('common.name') }}</td>
<td>
<div class="input-group form-group-no-border{{ $errors->has('name') ? ' has-danger' : '' }}">
{{ Form::text('name', null, ['class' => 'form-control']) }}
@@ -14,7 +14,7 @@
</tr>
<tr>
<td>@lang('common.email')</td>
<td>{{ __('common.email') }}</td>
<td>
<div class="input-group form-group-no-border{{ $errors->has('email') ? ' has-danger' : '' }}">
{{ Form::text('email', null, ['class' => 'form-control']) }}
@@ -26,7 +26,22 @@
</tr>
<tr>
<td>@lang('common.airline')</td>
<td>Discord ID <span class="small">
<a href="https://support.discord.com/hc/en-us/articles/206346498-Where-can-I-find-my-User-Server-Message-ID-" target="_blank">
How to find your ID</a></span>
</td>
<td>
<div class="input-group form-group-no-border{{ $errors->has('discord_id') ? ' has-danger' : '' }}">
{{ Form::text('discord_id', null, ['class' => 'form-control']) }}
</div>
@if ($errors->has('discord_id'))
<p class="text-danger">{{ $errors->first('discord_id') }}</p>
@endif
</td>
</tr>
<tr>
<td>{{ __('common.airline') }}</td>
<td>
<div class="input-group form-group-no-border{{ $errors->has('airline') ? ' has-danger' : '' }}">
{{ Form::select('airline_id', $airlines, null , ['class' => 'form-control select2']) }}
@@ -38,7 +53,7 @@
</tr>
<tr>
<td>@lang('airports.home')</td>
<td>{{ __('airports.home') }}</td>
<td>
<div class="input-group form-group-no-border{{ $errors->has('home_airport_id') ? ' has-danger' : '' }}">
{{ Form::select('home_airport_id', $airports, null , ['class' => 'form-control select2']) }}
@@ -50,7 +65,7 @@
</tr>
<tr>
<td>@lang('common.country')</td>
<td>{{ __('common.country') }}</td>
<td>
<div class="input-group form-group-no-border{{ $errors->has('country') ? ' has-danger' : '' }}">
{{ Form::select('country', $countries, null, ['class' => 'form-control select2' ]) }}
@@ -62,7 +77,7 @@
</tr>
<tr>
<td>@lang('common.timezone')</td>
<td>{{ __('common.timezone') }}</td>
<td>
<div class="input-group form-group-no-border{{ $errors->has('timezone') ? ' has-danger' : '' }}">
{{ Form::select('timezone', $timezones, null, ['class' => 'form-control select2' ]) }}
@@ -74,9 +89,9 @@
</tr>
<tr>
<td>@lang('profile.changepassword')</td>
<td>{{ __('profile.changepassword') }}</td>
<td>
<p>@lang('profile.newpassword'):</p>
<p>{{ __('profile.newpassword') }}:</p>
<div class="input-group form-group-no-border{{ $errors->has('password') ? ' has-danger' : '' }}">
{{ Form::password('password', ['class' => 'form-control']) }}
</div>
@@ -84,7 +99,7 @@
<p class="text-danger">{{ $errors->first('password') }}</p>
@endif
<p>@lang('passwords.confirm'):</p>
<p>{{ __('passwords.confirm') }}:</p>
<div class="input-group form-group-no-border{{ $errors->has('password_confirmation') ? ' has-danger' : '' }}">
{{ Form::password('password_confirmation', ['class' => 'form-control']) }}
</div>
@@ -94,14 +109,14 @@
</td>
</tr>
<tr>
<td>@lang('profile.avatar')</td>
<td>{{ __('profile.avatar') }}</td>
<td>
<div class="input-group form-group-no-border{{ $errors->has('avatar') ? ' has-danger' : '' }}">
{{ Form::file('avatar', null) }}
</div>
<p class="small">@lang('profile.avatarresize', [
<p class="small">{{ __('profile.avatarresize', [
'width' => config('phpvms.avatar.width'),
'height' => config('phpvms.avatar.height')])
'height' => config('phpvms.avatar.height')]) }}
</p>
@if ($errors->has('avatar'))
<p class="text-danger">{{ $errors->first('avatar') }}</p>
@@ -130,13 +145,13 @@
@endforeach
<tr>
<td>@lang('profile.opt-in')</td>
<td>{{ __('profile.opt-in') }}</td>
<td>
<div class="input-group form-group-no-border">
{{ Form::hidden('opt_in', 0, false) }}
{{ Form::checkbox('opt_in', 1, null) }}
</div>
<p class="small">@lang('profile.opt-in-descrip')
<p class="small">{{ __('profile.opt-in-descrip') }}
</p>
</td>
</tr>

View File

@@ -153,6 +153,10 @@
<td>@lang('profile.apikey')&nbsp;&nbsp;<span class="description">(@lang('profile.dontshare'))</span></td>
<td><span id="apiKey_show" style="display: none">{{ $user->api_key }} <i class="fas fa-eye-slash" onclick="apiKeyHide()"></i></span><span id="apiKey_hide">@lang('profile.apikey-show') <i class="fas fa-eye" onclick="apiKeyShow()"></i></span></td>
</tr>
<tr>
<td>Discord ID</td>
<td>{{ $user->discord_id ?? '-' }}</td>
</tr>
<tr>
<td>@lang('common.timezone')</td>
<td>{{ $user->timezone }}</td>
@@ -174,7 +178,7 @@
@if(!$field->private)
<tr>
<td>{{ $field->name }}</td>
<td>{{ $field->value }}</td>
<td>{{ $field->value ?? '-'}}</td>
</tr>
@endif
@endforeach

View File

@@ -450,6 +450,7 @@ class AcarsTest extends TestCase
'level' => 38000,
'planned_distance' => 400,
'planned_flight_time' => 120,
'status' => PirepStatus::BOARDING,
'route' => 'POINTA POINTB',
'source_name' => 'AcarsTest::testAcarsUpdates',
'fields' => [
@@ -487,6 +488,8 @@ class AcarsTest extends TestCase
$uri = '/api/pireps/'.$pirep_id.'/update';
$this->post($uri, [
'flight_time' => 60,
'distance' => 20,
'status' => PirepStatus::AIRBORNE,
'fields' => [
'custom_field' => 'custom_value_changed',
],

View File

@@ -14,6 +14,7 @@ trait CreatesApplication
public function createApplication()
{
$app = require __DIR__.'/../bootstrap/app.php';
$app->loadEnvironmentFrom('');
$app->make(Kernel::class)->bootstrap();
return $app;

View File

@@ -13,6 +13,7 @@ use App\Models\Flight;
use App\Models\Journal;
use App\Models\JournalTransaction;
use App\Models\Pirep;
use App\Models\PirepFare;
use App\Models\Subfleet;
use App\Models\User;
use App\Repositories\ExpenseRepository;
@@ -763,11 +764,10 @@ class FinanceTest extends TestCase
// Override the fares
$fare_counts = [];
foreach ($fares as $fare) {
$fare_counts[] = [
$fare_counts[] = new PirepFare([
'fare_id' => $fare->id,
'price' => $fare->price,
'count' => round($fare->capacity / 2),
];
]);
}
$this->fareSvc->saveForPirep($pirep, $fare_counts);
@@ -777,7 +777,7 @@ class FinanceTest extends TestCase
foreach ($all_fares as $fare) {
$set_fare = $fare_counts->where('fare_id', $fare->id)->first();
$this->assertEquals($set_fare['count'], $fare->count);
$this->assertEquals($set_fare['price'], $fare->price);
$this->assertNotEmpty($fare->price);
}
}
@@ -892,11 +892,10 @@ class FinanceTest extends TestCase
// Override the fares
$fare_counts = [];
foreach ($fares as $fare) {
$fare_counts[] = [
$fare_counts[] = new PirepFare([
'fare_id' => $fare->id,
'price' => $fare->price,
'count' => 100,
];
]);
}
$this->fareSvc->saveForPirep($pirep, $fare_counts);
@@ -948,11 +947,10 @@ class FinanceTest extends TestCase
// Override the fares
$fare_counts = [];
foreach ($fares as $fare) {
$fare_counts[] = [
$fare_counts[] = new PirepFare([
'fare_id' => $fare->id,
'price' => $fare->price,
'count' => 100,
];
]);
}
$this->fareSvc->saveForPirep($pirep, $fare_counts);
@@ -1074,11 +1072,11 @@ class FinanceTest extends TestCase
// Override the fares
$fare_counts = [];
foreach ($fares as $fare) {
$fare_counts[] = [
$fare_counts[] = new PirepFare([
'fare_id' => $fare->id,
'price' => $fare->price,
'count' => 100,
];
]);
}
$this->fareSvc->saveForPirep($pirep, $fare_counts);

View File

@@ -4,6 +4,7 @@ namespace Tests;
use App\Events\UserRegistered;
use App\Models\Enums\UserState;
use App\Models\User;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Notification;
@@ -26,7 +27,7 @@ class RegistrationTest extends TestCase
setting('pilots.auto_accept', true);
$attrs = factory(\App\Models\User::class)->make()->toArray();
$attrs = factory(User::class)->make()->toArray();
$attrs['password'] = Hash::make('secret');
$user = $userSvc->createUser($attrs);

View File

@@ -57,11 +57,10 @@ abstract class TestCase extends \Illuminate\Foundation\Testing\TestCase
ThrottleRequests::class
);
Notification::fake();
Artisan::call('database:create', ['--reset' => true]);
Artisan::call('migrate:refresh', ['--env' => 'testing', '--force' => true]);
Notification::fake();
// $this->disableExceptionHandling();
}