Compare commits

...

7 commits

14 changed files with 908 additions and 554 deletions

View file

@ -1,58 +1,26 @@
APP_NAME=Laravel APP_NAME=
APP_ENV=local APP_ENV=
APP_KEY= APP_KEY=
APP_DEBUG=true APP_DEBUG=
APP_URL=http://localhost APP_URL=
LOG_CHANNEL=stack CURRENT_TRIP_ID=
LOG_DEPRECATIONS_CHANNEL=null PAST_TRIP_IDS=
LOG_LEVEL=debug
DB_CONNECTION=mysql MAIL_MAILER=
DB_HOST=127.0.0.1 MAILJET_APIKEY=
DB_PORT=3306 MAILJET_APISECRET=
DB_DATABASE=laravel MAIL_FROM_ADDRESS=
DB_USERNAME=root MAIL_FROM_NAME=
DB_PASSWORD=
BROADCAST_DRIVER=log DAILY_DIGEST_TO=
CACHE_DRIVER=file WEEKLY_DIGEST_TO=
FILESYSTEM_DISK=local MONTHLY_DIGEST_TO=
QUEUE_CONNECTION=sync
SESSION_DRIVER=file
SESSION_LIFETIME=120
MEMCACHED_HOST=127.0.0.1 LOG_CHANNEL=
REDIS_HOST=127.0.0.1 BROADCAST_DRIVER=
REDIS_PASSWORD=null CACHE_DRIVER=
REDIS_PORT=6379 QUEUE_CONNECTION=
SESSION_DRIVER=
MAIL_MAILER=smtp SESSION_LIFETIME=
MAIL_HOST=mailhog
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="${APP_NAME}"
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false
PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_HOST=
PUSHER_PORT=443
PUSHER_SCHEME=https
PUSHER_APP_CLUSTER=mt1
VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
VITE_PUSHER_HOST="${PUSHER_HOST}"
VITE_PUSHER_PORT="${PUSHER_PORT}"
VITE_PUSHER_SCHEME="${PUSHER_SCHEME}"
VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"

View file

@ -0,0 +1,57 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Mail\Digest;
use Illuminate\Support\Facades\Mail;
class SendDigest extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'digest:send {--D|daily} {--W|weekly} {--M|monthly}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Send a digest of recent updates.';
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
if (!$this->option('daily') && !$this->option('weekly') && !$this->option('monthly')) {
$this->error('No schedule specified.');
return;
}
// These are seperated because I may want to send multiple types
// of digest in a single commend.
if ($this->option('daily')) {
foreach (config('app.daily_digest_recipients') as $recipient) {
Mail::to($recipient)->send(new Digest('daily', config('app.current_trip_id')));
}
}
if ($this->option('weekly')) {
foreach (config('app.weekly_digest_recipients') as $recipient) {
Mail::to($recipient)->send(new Digest('weekly', config('app.current_trip_id')));
}
}
if ($this->option('monthly')) {
foreach (config('app.monthly_digest_recipients') as $recipient) {
Mail::to($recipient)->send(new Digest('monthly', config('app.current_trip_id')));
}
}
}
}

View file

@ -15,7 +15,9 @@ class Kernel extends ConsoleKernel
*/ */
protected function schedule(Schedule $schedule) protected function schedule(Schedule $schedule)
{ {
// $schedule->command('inspire')->hourly(); $schedule->command('digest:send --daily')->daily();
$schedule->command('digest:send --weekly')->weekly();
$schedule->command('digest:send --monthly')->monthly();
} }
/** /**

View file

@ -0,0 +1,129 @@
<?php
namespace App\Http\Controllers;
use DateTime;
use App\Mail\WeeklyDigest;
use GuzzleHttp\Client;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Storage;
class TrackerController extends Controller
{
/**
* Show the list of past trips.
* TODO: Get these from the Wayward API rather than hard-coding them.
*
* @return \Illuminate\View\View
*/
public function show_past_trips_list() {
return view('past-trips');
}
/**
* Show a trip in tracker app.
*
* @param Request $request
* @param string? $tripId
* @return \Illuminate\View\View
*/
public function show_trip(Request $request, string $tripId = null) {
$tripId = $tripId ?? config('app.current_trip_id');
if (!$tripId) return view('no-trip');
$viewMode = $request->input('show', null);
$tripData = $this->get_trip_data($tripId);
return view(
'tracker',
[
'trip' => $tripData,
'showAllCheckins' => ($viewMode === 'all')
]
);
}
/**
* Retrieves a trip from the local cache, or from the Wayward API.
*
* The retrieval algorithm is as follows:
* 1. If no local cache exists, download the trip data
* 2. If a local cache exists:
* 1. If the trip is inactive (i.e., finished), use the cache
* 2. If the trip is active:
* 1. If the trip was actively tracking at last check, use the
* cache if it's less than an hour old. Otherwise, download
* the new trip data.
* 2. If the trip was not actively tracking at lest check, use
* the cache if it's less than three hours old. Otherwise,
* download the new trip data.
* 3. If new trip data is downloaded, overwrite any cached data.
*
* @param string $tripId
* @return string
*/
public function get_trip_data(string $tripId) {
$tripFileName = ( $tripId ?? config('app.current_trip_id') ) . '.json';
// Returns the cached trip data if the trip is inactive (i.e., finished)
// or less than 13 hours old, depending on whether it was tracking
// at last check or not.
if (Storage::disk('local')->exists($tripFileName)) {
Log::debug("Cached trip file '{$tripFileName}' found...");
$cachedData = json_decode(Storage::disk('local')->get($tripFileName))->trip;
if ($cachedData->is_active) {
Log::debug("Cached trip file '{$tripFileName}' is for an active trip.");
$cachedDataUpdatedAt = new DateTime($cachedData->updated_at);
$now = new DateTime();
$cachingTimeout = ($cachedData->is_tracking) ? 1 : 3;
$cachedDataAge = intval(($now->getTimestamp() - $cachedDataUpdatedAt->getTimestamp()) / 3600);
if ($cachedDataAge <= $cachingTimeout) {
Log::debug("Cached trip file '{$tripFileName}' is younger than {$cachingTimeout} hours, showing from cache...");
return $cachedData;
}
} else {
Log::debug("Cached trip file '{$tripFileName}' is for an old trip, showing from cache...");
return $cachedData;
}
}
// Otherwise, download the trip data from the Wayward API.
Log::debug("No cached trip file found for '{$tripFileName}'.");
$client = new Client([
'base_uri' => 'https://app.wayward.travel/',
'timeout' => 3.0
]);
$response = $client->get('trip/'.($tripId ?? config('app.current_trip_id')).'/user/zmld8ko6qy7d9j3xvq10/json');
switch ($response->getStatusCode()) {
case 200:
$data = json_decode($response->getBody());
// Cache the downloaded file if it does not exist locally.
if (Storage::disk('local')->missing($tripFileName)) {
Log::debug("Caching new trip file '{$tripFileName}'.");
Storage::disk('local')->put($tripFileName, json_encode($data));
} else {
$cachedData = json_decode(Storage::disk('local')->get($tripFileName));
if ($data->trip->updated_at !== $cachedData->trip->updated_at) {
Log::debug("Cached trip file '{$tripFileName}' has different 'updated_at' time, updating cache...");
Storage::disk('local')->put($tripFileName, json_encode($data));
// TODO: Cache photos locally
} else {
Log::debug("Cached trip file '{$tripFileName}' has same 'updated_at' time, showing from cache...");
$data = $cachedData;
}
}
return $data->trip;
default:
// TODO: Add proper error handling.
return "Something went wrong";
}
}
}

91
app/Mail/Digest.php Normal file
View file

@ -0,0 +1,91 @@
<?php
namespace App\Mail;
use DateTime;
use App\Http\Controllers\TrackerController;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
class Digest extends Mailable
{
use Queueable, SerializesModels;
/**
* The type of digest (e.g. weekly, monthly, etc.).
* TODO: Replace with enum
*
* @var string
*/
public $digest_type;
/**
* The current trip locations as a JSON object.
*
* @var string
*/
public $locations;
/**
* The current trip checkins as a JSON object.
*
* @var string
*/
public $checkins;
/**
* Create a new message instance.
*
* @param string $digest_type
* @param string $trip_id
* @return void
*/
public function __construct(string $digest_type, string $trip_id)
{
$this->digest_type = $digest_type;
$trip = (new TrackerController)->get_trip_data($trip_id);
$cutoffDateTime = new DateTime();
switch ($this->digest_type) {
case 'daily':
$cutoffDateTime->modify('-1 day');
break;
case 'weekly':
$cutoffDateTime->modify('-1 week');
break;
case 'monthly':
$cutoffDateTime->modify('-1 month');
break;
default:
}
$this->locations = array_filter(
$trip->locations,
function($elem) use ($cutoffDateTime) {
$elemDateTime = new DateTime($elem->created_at);
return $elemDateTime > $cutoffDateTime;
}
);
$this->checkins = array_filter(
$trip->checkins,
function($elem) use ($cutoffDateTime) {
$elemDateTime = new DateTime($elem->created_at);
return $elemDateTime > $cutoffDateTime;
}
);
}
/**
* Build the message.
*
* @return $this
*/
public function build()
{
return $this->view('emails.digest')
->subject("track.bengoldsworthy.net ".ucwords($this->digest_type)." Digest");
}
}

View file

@ -1,34 +0,0 @@
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
class WeeklyDigest extends Mailable
{
use Queueable, SerializesModels;
/**
* Create a new message instance.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Build the message.
*
* @return $this
*/
public function build()
{
return $this->view('emails.weekly-digest')
->subject('Weekly Digest');
}
}

44
app/helper.php Normal file
View file

@ -0,0 +1,44 @@
<?php
if (!function_exists('render_date_difference')) {
function render_date_difference ($start_time) {
$minute = 60;
$hour = $minute * 60;
$day = $hour * 24;
$week = $day * 7;
$month = $week * 4;
$year = $month * 12;
$start_time_dt = new DateTime($start_time);
$now = new DateTime();
$trip_start_difference = intval(($now->getTimestamp() - $start_time_dt->getTimestamp()));
$start_tag = '<span title="' . date('G:H, j M Y', strtotime($start_time)) . '">';
$end_tag = '</span>';
$unit = null;
$div = 1;
if ($trip_start_difference < $minute) {
$unit = 'second';
} else if ($trip_start_difference < $hour) {
$div = $minute;
$unit = 'minute';
} else if ($trip_start_difference < $day) {
$div = $hour;
$unit = 'hour';
} else if ($trip_start_difference < $week) {
$div = $day;
$unit = 'day';
} else if ($trip_start_difference < $month) {
$div = $week;
$unit = 'week';
} else if ($trip_start_difference < $year) {
$div = $month;
$unit = 'month';
} else {
$div = $year;
$unit = 'year';
}
return "{$start_tag}" . ( floor ( $trip_start_difference / $div ) ) . " {$unit}" . ( ( floor ( $trip_start_difference / $div ) > 1 ) ? 's' : '' ) . " ago{$end_tag}";
}
}

View file

@ -28,7 +28,10 @@
"App\\": "app/", "App\\": "app/",
"Database\\Factories\\": "database/factories/", "Database\\Factories\\": "database/factories/",
"Database\\Seeders\\": "database/seeders/" "Database\\Seeders\\": "database/seeders/"
} },
"files": [
"app/helper.php"
]
}, },
"autoload-dev": { "autoload-dev": {
"psr-4": { "psr-4": {

819
composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -58,8 +58,33 @@ return [
'asset_url' => env('ASSET_URL'), 'asset_url' => env('ASSET_URL'),
/*
|--------------------------------------------------------------------------
| Trip IDs
|--------------------------------------------------------------------------
|
| Get the IDs for current and past trips.
| TODO: Update when I have the proper API routes for Wayward.
|
*/
'current_trip_id' => env('CURRENT_TRIP_ID'), 'current_trip_id' => env('CURRENT_TRIP_ID'),
'past_trip_ids' => explode(',', env('PAST_TRIP_IDS')),
/*
|--------------------------------------------------------------------------
| Digest Mailing Lists
|--------------------------------------------------------------------------
|
| The the mailing lists for the various digests.
|
*/
'daily_digest_recipients' => explode(',', env('DAILY_DIGEST_TO')),
'weekly_digest_recipients' => explode(',', env('WEEKLY_DIGEST_TO')),
'monthly_digest_recipients' => explode(',', env('MONTHLY_DIGEST_TO')),
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Application Timezone | Application Timezone

View file

@ -0,0 +1,57 @@
<style>
:root {
--dark: #020202;
--light: #fffff0;
}
body {
font-size: 16px;
margin: 1em;
color: var(--dark, #020202);
background-color: var(--light, #fffff0);
background-color: #fffff0;
font-family: "Palatino Linotype", "Book Antiqua", Palatino, serif;
}
.checkin {
border-top: 1px solid black;
padding: 1em 0;
}
.checkin__summary {
cursor: pointer;
}
.checkin__title {
display: inline;
font-size: 1em;
}
.checkin__meta {
margin: 0;
}
</style>
<h1>track.bengoldsworthy.net {{ ucwords($digest_type) }} Digest</h1>
<p><i>Generated: {{ (new DateTime())->format('D jS F Y') }}</i></p>
<p>View the tracker <a href="https://track.bengoldsworthy.net">here.</a></p>
<hr>
<h2>Checkins</h2>
<ol>
@foreach($checkins as $checkin)
<li>
<details class="checkin" id="{{ $checkin->id }}">
<summary class="checkin__summary">
<h3 class="checkin__title">{!! $checkin->title ?? "<i>Untitled</i>" !!}</h3>
<p class="checkin__meta">{!! render_date_difference($checkin->date) !!}</p>
</summary>
@if($checkin->note)
{!! $checkin->note !!}
@endif
@if($checkin->image_url)
<img class="popup__image" loading="lazy" src="{{ $checkin->image_url }}">
@endif
</details>
</li>
@endforeach
</ol>

View file

@ -1 +0,0 @@
<p>Hello, world</p>

View file

@ -169,47 +169,3 @@
</footer> </footer>
</body> </body>
</html> </html>
@php
function render_date_difference ($start_time) {
$minute = 60;
$hour = $minute * 60;
$day = $hour * 24;
$week = $day * 7;
$month = $week * 4;
$year = $month * 12;
$start_time_dt = new DateTime($start_time);
$now = new DateTime();
$trip_start_difference = intval(($now->getTimestamp() - $start_time_dt->getTimestamp()));
$start_tag = '<span title="' . date('G:H, j M Y', strtotime($start_time)) . '">';
$end_tag = '</span>';
$unit = null;
$div = 1;
if ($trip_start_difference < $minute) {
$unit = 'second';
} else if ($trip_start_difference < $hour) {
$div = $minute;
$unit = 'minute';
} else if ($trip_start_difference < $day) {
$div = $hour;
$unit = 'hour';
} else if ($trip_start_difference < $week) {
$div = $day;
$unit = 'day';
} else if ($trip_start_difference < $month) {
$div = $week;
$unit = 'week';
} else if ($trip_start_difference < $year) {
$div = $month;
$unit = 'month';
} else {
$div = $year;
$unit = 'year';
}
return "{$start_tag}" . ( floor ( $trip_start_difference / $div ) ) . " {$unit}" . ( ( floor ( $trip_start_difference / $div ) > 1 ) ? 's' : '' ) . " ago{$end_tag}";
}
@endphp

View file

@ -1,82 +1,8 @@
<?php <?php
use GuzzleHttp\Client;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Storage; use App\Http\Controllers\TrackerController;
use App\Mail\WeeklyDigest;
use Illuminate\Support\Facades\Mail;
Route::get('/past-trips', function () { Route::get('/past-trips', [TrackerController::class, 'show_past_trips_list']);
return view('past-trips');
});
Route::get('/{tripId?}', function (Request $request, $tripId = null) { Route::get('/{tripId?}', [TrackerController::class, 'show_trip'])->where('tripId', '[0-9a-z]{20}');
//Mail::to('me@bengoldsworthy.net')->send(new WeeklyDigest());
if (!$tripId && !config('app.current_trip_id')) return view('no-trip');
$viewMode = $request->input('show', null);
// If there is a file in the local cache that is less than 13 hours old
// (depending on whether it was actively tracking at last check), use that.
$tripFileName = ( $tripId ?? config('app.current_trip_id') ) . '.json';
if (Storage::disk('local')->exists($tripFileName)) {
Log::debug("Cached trip file '{$tripFileName}' found...");
$cachedData = json_decode(Storage::disk('local')->get($tripFileName))->trip;
if ($cachedData->is_active) {
Log::debug("Cached trip file '{$tripFileName}' is for an active trip.");
$cachedDataUpdatedAt = new DateTime($cachedData->updated_at);
$now = new DateTime();
$cachingTimeout = ($cachedData->is_tracking) ? 1 : 3;
$cachedDataAge = intval(($now->getTimestamp() - $cachedDataUpdatedAt->getTimestamp()) / 3600);
if ($cachedDataAge <= $cachingTimeout) {
Log::debug("Cached trip file '{$tripFileName}' is younger than {$cachingTimeout} hours, showing from cache...");
return view('tracker', ['trip' => $cachedData, 'showAllCheckins' => ($viewMode === 'all')]);
}
} else {
Log::debug("Cached trip file '{$tripFileName}' is for an old trip, showing from cache...");
return view('tracker', ['trip' => $cachedData, 'showAllCheckins' => ($viewMode === 'all')]);
}
}
/*
* Otherwise, download the latest tracking data.
*/
Log::debug("No cached trip file found for '{$tripFileName}'.");
$client = new Client([
'base_uri' => 'https://app.wayward.travel/',
'timeout' => 3.0
]);
$response = $client->get('trip/'.($tripId ?? config('app.current_trip_id')).'/user/zmld8ko6qy7d9j3xvq10/json');
if ($response->getStatusCode() == 200) {
$data = json_decode($response->getBody());
// Cache the downloaded file if it does not exist locally.
if (Storage::disk('local')->missing($tripFileName)) {
Log::debug("Caching new trip file '{$tripFileName}'.");
Storage::disk('local')->put($tripFileName, json_encode($data));
} else {
$cachedData = json_decode(Storage::disk('local')->get($tripFileName));
if ($data->trip->updated_at !== $cachedData->trip->updated_at) {
Log::debug("Cached trip file '{$tripFileName}' has different 'updated_at' time, updating cache...");
Storage::disk('local')->put($tripFileName, json_encode($data));
// TODO: Cache photos locally
} else {
Log::debug("Cached trip file '{$tripFileName}' has same 'updated_at' time, showing from cache...");
$data = $cachedData;
}
}
return view('tracker', ['trip' => $data->trip, 'showAllCheckins' => ($viewMode === 'all')]);
} else {
// TODO: Return proper error
return 'Something went wrong';
}
})->where('tripId', '[0-9a-z]{20}');