Skip to content

Commit

Permalink
Audit support
Browse files Browse the repository at this point in the history
  • Loading branch information
FatihKoz committed Jun 30, 2024
1 parent 52711e5 commit 9cb07b9
Show file tree
Hide file tree
Showing 5 changed files with 389 additions and 0 deletions.
152 changes: 152 additions & 0 deletions Http/Controllers/DB_AuditController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
<?php

namespace Modules\DisposableBasic\Http\Controllers;

use App\Contracts\Controller;
use App\Models\Pirep;
use App\Models\PirepFieldValue;
use App\Models\User;
use App\Models\UserField;
use App\Models\UserFieldValue;
use App\Models\Enums\PirepState;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use League\Csv\CharsetConverter;
use League\Csv\Writer;

class DB_AuditController extends Controller
{
public function ivao()
{
$network = 'IVAO';
$audit_period = 91;
$audit_start = Carbon::now()->subDays($audit_period)->startOfDay();
$audit_start_buffer = $audit_start->copy()->subDays(1)->startOfDay();
$audit_end = Carbon::now()->subDays(1)->endOfDay();

$network_field_name = DB_Setting('dbasic.networkcheck_fieldname_ivao', 'IVAO ID');
$network_field_id = optional(UserField::select('id')->where('name', $network_field_name)->first())->id;

$network_ids = UserFieldValue::where('user_field_id', $network_field_id)->whereNotNull('value')->pluck('value')->toArray();
$network_pilotids = UserFieldValue::where('user_field_id', $network_field_id)->whereNotNull('value')->pluck('user_id')->toArray();
$network_pilots = User::whereIn('id', $network_pilotids)->orderBy('created_at')->get();
$network_pireps = PirepFieldValue::where('slug', 'network-online')->where('value', $network)->whereBetween('created_at', [$audit_start_buffer, $audit_end])->pluck('pirep_id')->toArray();

$eager_load = ['aircraft', 'airline', 'user', 'field_values'];
$audit_pireps = Pirep::with($eager_load)->where('state', PirepState::ACCEPTED)->whereIn('user_id', $network_pilotids)->whereIn('id', $network_pireps)->whereBetween('submitted_at', [$audit_start, $audit_end])->orderBy('submitted_at')->get();
$audit_pirepids = $audit_pireps->pluck('id')->toArray();
$audit_pilotids = $audit_pireps->pluck('user_id')->toArray();
$audit_pilots = User::whereIn('id', $audit_pilotids)->orderBy('created_at')->get();

return view('DBasic::audits.ivao', [
'audit_start' => $audit_start,
'audit_end' => $audit_end,
'audit_ids' => isset($audit_pilotids) ? $audit_pilotids : null,
'audit_pilots' => isset($audit_pilots) ? $audit_pilots : null,
'audit_pireps' => isset($audit_pireps) ? $audit_pireps : null,
'audit_pids' => isset($audit_pirepids) ? json_encode($audit_pirepids) : null,
'audit_time' => isset($audit_pireps) ? $audit_pireps->sum('flight_time') : null,
'field_name' => $network_field_name,
'is_admin' => (optional(Auth::user())->hasRole(['admin'])) ? true : false,
'network_ids' => isset($network_ids) ? $network_ids : null,
'network_pilots' => isset($network_pilots) ? $network_pilots : null,
]);
}

public function vatsim()
{
$network = 'VATSIM';
$audit_period = 91;
$audit_start = Carbon::now()->subDays($audit_period)->startOfDay();
$audit_start_buffer = $audit_start->copy()->subDays(1)->startOfDay();
$audit_end = Carbon::now()->subDays(1)->endOfDay();

$network_field_name = DB_Setting('dbasic.networkcheck_fieldname_vatsim', 'VATSIM ID');
$network_field_id = optional(UserField::select('id')->where('name', $network_field_name)->first())->id;

$network_ids = UserFieldValue::where('user_field_id', $network_field_id)->whereNotNull('value')->pluck('value')->toArray();
$network_pilotids = UserFieldValue::where('user_field_id', $network_field_id)->whereNotNull('value')->pluck('user_id')->toArray();
$network_pilots = User::whereIn('id', $network_pilotids)->orderBy('created_at')->get();
$network_pireps = PirepFieldValue::where('slug', 'network-online')->where('value', $network)->whereBetween('created_at', [$audit_start_buffer, $audit_end])->pluck('pirep_id')->toArray();

$eager_load = ['aircraft', 'airline', 'user', 'field_values'];
$audit_pireps = Pirep::with($eager_load)->where('state', PirepState::ACCEPTED)->whereIn('user_id', $network_pilotids)->whereIn('id', $network_pireps)->whereBetween('submitted_at', [$audit_start, $audit_end])->orderBy('submitted_at')->get();
$audit_pirepids = $audit_pireps->pluck('id')->toArray();
$audit_pilotids = $audit_pireps->pluck('user_id')->toArray();
$audit_pilots = User::whereIn('id', $audit_pilotids)->orderBy('created_at')->get();

return view('DBasic::audits.vatsim', [
'audit_start' => $audit_start,
'audit_end' => $audit_end,
'audit_ids' => isset($audit_pilotids) ? $audit_pilotids : null,
'audit_pilots' => isset($audit_pilots) ? $audit_pilots : null,
'audit_pireps' => isset($audit_pireps) ? $audit_pireps : null,
'audit_pids' => isset($audit_pirepids) ? json_encode($audit_pirepids) : null,
'audit_time' => isset($audit_pireps) ? $audit_pireps->sum('flight_time') : null,
'field_name' => $network_field_name,
'is_admin' => (optional(Auth::user())->hasRole(['admin'])) ? true : false,
'network_ids' => isset($network_ids) ? $network_ids : null,
'network_pilots' => isset($network_pilots) ? $network_pilots : null,
]);
}

// Export Pireps
public function export_pireps(Request $request)
{
$network = $request->network;
$start = Carbon::parse($request->start);
$end = Carbon::parse($request->end);
$pireps = Pirep::with(['aircraft', 'airline', 'user', 'field_values'])->whereIn('id', json_decode($request->pireps))->orderBy('submitted_at')->get();

$file_name = strtolower($network) . '-audit-pireps-' . $start->format('dMY') . '-' . $end->format('dMY') . '.csv';
$header = ['callsign', 'orig_icao', 'dest_icao', 'date', 'dep_time', 'arr_time', 'aircraft'];
$path = $this->runExport($pireps, $header, $file_name);

return response()->download($path, $file_name, ['content-type' => 'text/csv'])->deleteFileAfterSend(true);
}

protected function runExport(Collection $collection, $columns, $filename): string
{
// Create the directory under storage/app
Storage::makeDirectory('export');
$path = storage_path('/app/export/' . $filename);
Log::info('Exporting audit pireps to ' . $path);
$writer = $this->openCsv($path);
// Write out the header first
$writer->insertOne($columns);
// Write the rest of the rows
foreach ($collection as $row) {
$writer->insertOne($this->ProcessRow($row, $columns));
}

return $path;
}

protected function openCsv($path): Writer
{
$writer = Writer::createFromPath($path, 'w+');
CharsetConverter::addTo($writer, 'utf-8', 'utf-8');

return $writer;
}

protected function ProcessRow($row, $columns): array
{
$ret = [];

// Prepare fields
$ret['callsign'] = $row->fields->firstWhere('slug', 'network-callsign-used')->value ?? $row->airline->icao . $row->flight_number;
$ret['orig_icao'] = $row->dpt_airport_id;
$ret['dest_icao'] = $row->arr_airport_id;
$ret['date'] = $row->submitted_at->format('d.M.Y');
$ret['dep_time'] = $row->block_off_time->format('H:i');
$ret['arr_time'] = $row->block_on_time->format('H:i');
$ret['aircraft'] = $row->aircraft->icao;

return $ret;
}
}
3 changes: 3 additions & 0 deletions Providers/DB_ServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ protected function registerRoutes()
// Public Pages (for IVAO/VATSIM Audits)
Route::get('dreports', 'DB_PirepController@index')->name('reports');
Route::get('dstatistics', 'DB_StatisticController@index')->name('statistics');
Route::get('dvatsim', 'DB_AuditController@vatsim')->name('vatsim');
Route::get('divao', 'DB_AuditController@ivao')->name('ivao');
Route::post('daudit_export', 'DB_AuditController@export_pireps')->name('audit.export');
// Plain Pages
Route::get('dp_roster', 'DB_WebController@roster')->name('dp_roster');
Route::get('dp_stats', 'DB_WebController@stats')->name('dp_stats');
Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ This module pack aims to cover basic needs of any Virtual Airline with some new
* Manual Awarding and Manual Payment features
* Aircraft state control (in use, in air, on ground)
* IVAO/VATSIM Network Presence Check (with additional callsign checks)
* Support for IVAO/VATSIM audits (works with presence check results and data)
* Pirep Auto Rejecting capabilities,
* Some widgets to enhance any page/layout as per virtual airline needs
* Database checks (to identify errors easily when needed)
Expand Down Expand Up @@ -97,6 +98,9 @@ DBasic.roster /droster // Roster index page (full roster)
DBasic.scenery /dscenery // My Sceneries page
DBasic.stats /dstats // Statistics index page
DBasic.statistics /dstatistics // Statistics index page (public, for IVAO/VATSIM)

DBasic.ivao /divao // Audit ready page for IVAO (public)
DBasic.vatsim /dvatsim // Audit ready page for VATSIM (public)
```

Also for embedding in your main (landing) sites, some public url's are available.
Expand Down Expand Up @@ -718,6 +722,12 @@ _Not providing attribution link will result in removal of access and no support

## Release / Update Notes

30.JUN.24

* Added another failsafe for Map Widget (for missing airlines)
* Added GDPR compliant IVAO and VATSIM audit support pages (last 90 days is considered)
_Admins can export data as csv files and see their all/network network members_

07.JUN.24

* Added NextRank Widget
Expand Down
112 changes: 112 additions & 0 deletions Resources/views/audits/ivao.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
@extends('app')
@section('title', 'IVAO Audit Records')

@section('content')
<div class="row">
<div class="col-8">
<div class="card mb-2">
<div class="card-header p-0">
<h5 class="m-1 p-1">Accepted Pireps</h5>
</div>
<div class="card-body table-responsive overflow-auto p-0" style="max-height: 70vh;">
<table class="table table-sm table-borderless table-striped text-center mb-0">
<tr>
<th class="text-start">Callsign</th>
<th>Orig</th>
<th>Dest</th>
<th>Date</th>
<th>Off Block</th>
<th>On Block</th>
<th>Aircraft</th>
<th class="text-end">Remarks</th>
</tr>
@foreach($audit_pireps as $p)
<tr>
<td class="text-start">{{ $p->fields->firstWhere('slug', 'network-callsign-used')->value ?? $p->airline->icao.$p->flight_number }}</td>
<td>{{ $p->dpt_airport_id }}</td>
<td>{{ $p->arr_airport_id }}</td>
<td>{{ $p->submitted_at->format('d.M.Y') }}</td>
<td>{{ $p->block_off_time->format('H:i') }}</td>
<td>{{ $p->block_on_time->format('H:i') }}</td>
<td>{{ $p->aircraft->icao }}</td>
<td class="text-end">{{ optional($p->fields->firstWhere('slug', 'network-presence-check'))->value.'%' }}</td>
</tr>
@endforeach
</table>
</div>
<div class="card-footer p-1 text-end small fw-bold">
@if($is_admin)
<form class="form" method="post" action="{{ route('DBasic.audit.export') }}">
@csrf
<input type="hidden" name="network" value="ivao">
<input type="hidden" name="start" value="{{ $audit_start }}">
<input type="hidden" name="end" value="{{ $audit_end }}">
<input type="hidden" name="pireps" value=" {{ $audit_pids }}">
<input class="btn btn-success btn-sm mx-1 p-0 px-1 float-start" type="submit" value="Export Pireps">
</form>
@endif
@if(filled($audit_pireps) && $audit_pireps->count() > 0)
Displaying {{ $audit_pireps->count() }} PIREPs submitted between {{ $audit_start->format('d.M.Y H:i') }} and {{ $audit_end->format('d.M.Y H:i') }}
@endif
</div>
</div>
<span class="small float-start">Audit report prepared in {{ round(microtime(true) - LARAVEL_START, 2) }} seconds</span>
</div>
@if($is_admin)
{{-- Active Network Members --}}
<div class="col-2">
<div class="card mb-2">
<div class="card-header p-0">
<h5 class="m-1 p-1">IVAO Pilots (Active)</h5>
</div>
<div class="card-body table-responsive overflow-auto p-0" style="max-height: 70vh;">
<table class="table table-sm table-borderless table-striped mb-0">
<tr>
<th>Network ID</th>
<th class="text-end">Member Since</th>
</tr>
@foreach($audit_pilots as $u)
<tr>
<td>{{ optional($u->fields->firstWhere('name', $field_name))->value }}</td>
<td class="text-end">{{ $u->created_at->format('M Y') }}</td>
</tr>
@endforeach
</table>
</div>
<div class="card-footer p-1 text-end small fw-bold">
@if(filled($audit_pilots) && $audit_pilots->count() > 0)
Listing {{ $audit_pilots->count() }} active members
@endif
</div>
</div>
</div>
{{-- All Network Members --}}
<div class="col-2">
<div class="card mb-2">
<div class="card-header p-0">
<h5 class="m-1 p-1">IVAO Pilots (All)</h5>
</div>
<div class="card-body table-responsive overflow-auto p-0" style="max-height: 70vh;">
<table class="table table-sm table-borderless table-striped mb-0">
<tr>
<th>Network ID</th>
<th class="text-end">Member Since</th>
</tr>
@foreach($network_pilots as $u)
<tr>
<td>{{ optional($u->fields->firstWhere('name', $field_name))->value }}</td>
<td class="text-end">{{ $u->created_at->format('M Y') }}</td>
</tr>
@endforeach
</table>
</div>
<div class="card-footer p-1 text-end small fw-bold">
@if(filled($network_pilots) && $network_pilots->count() > 0)
Listing {{ $network_pilots->count() }} members
@endif
</div>
</div>
</div>
@endif
</div>
@endsection
Loading

0 comments on commit 9cb07b9

Please sign in to comment.