Initial commit
This commit is contained in:
commit
c909d539bc
7178 files changed
+756624
No files matched your search
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\ExternalBusyWindow;
|
||||
use App\Models\ExternalCalendar;
|
||||
use Carbon\CarbonImmutable;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Sabre\VObject\Reader;
|
||||
|
||||
class SyncNextcloudBusyJob implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, Queueable, SerializesModels;
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
\Log::info('SyncNextcloudBusyJob running at '.now());
|
||||
// Choose which calendars to sync (you can expand later to many).
|
||||
$calendars = ExternalCalendar::where('is_enabled', true)->get();
|
||||
if ($calendars->isEmpty()) return;
|
||||
|
||||
$windowStart = now()->utc();
|
||||
$windowEnd = now()->addWeeks(6)->utc(); // adjust horizon if needed
|
||||
|
||||
foreach ($calendars as $cal) {
|
||||
try {
|
||||
$resp = Http::timeout(20)->get($cal->url);
|
||||
if (!$resp->ok()) {
|
||||
// log and continue
|
||||
\Log::warning("ICS fetch failed", ['cal' => $cal->id, 'status' => $resp->status()]);
|
||||
continue;
|
||||
}
|
||||
|
||||
$ics = $resp->body();
|
||||
if (!trim($ics)) continue;
|
||||
|
||||
$vcal = Reader::read($ics); // VCalendar
|
||||
|
||||
// Expand recurring events into single instances within window
|
||||
// Mutates the calendar to contain expanded VEVENTs
|
||||
$vcal = $vcal->expand(
|
||||
new \DateTime($windowStart->format('Y-m-d\TH:i:s\Z')),
|
||||
new \DateTime($windowEnd->format('Y-m-d\TH:i:s\Z'))
|
||||
);
|
||||
|
||||
// Remove existing windows in this horizon (simple strategy)
|
||||
ExternalBusyWindow::where('external_calendar_id', $cal->id)
|
||||
->whereBetween('starts_at', [$windowStart, $windowEnd])
|
||||
->delete();
|
||||
|
||||
$rows = [];
|
||||
$nowTs = now();
|
||||
|
||||
// Iterate expanded events
|
||||
foreach ($vcal->select('VEVENT') as $ve) {
|
||||
// Treat TRANSPARENT as free; OPAQUE as busy (default busy)
|
||||
$transp = strtoupper((string)($ve->TRANSP ?? 'OPAQUE'));
|
||||
if ($transp === 'TRANSPARENT') continue;
|
||||
|
||||
// Safely extract start/end in UTC
|
||||
$startDt = $ve->DTSTART->getDateTime(new \DateTimeZone('UTC'));
|
||||
// DTEND may be absent; if so, use DTSTART as a zero-length block (or add a default length)
|
||||
$endDt = isset($ve->DTEND)
|
||||
? $ve->DTEND->getDateTime(new \DateTimeZone('UTC'))
|
||||
: (clone $startDt);
|
||||
|
||||
// Skip malformed ranges
|
||||
if ($endDt <= $startDt) continue;
|
||||
|
||||
// Only keep those overlapping our window
|
||||
$startsAt = CarbonImmutable::instance($startDt);
|
||||
$endsAt = CarbonImmutable::instance($endDt);
|
||||
if ($endsAt <= $windowStart || $startsAt >= $windowEnd) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$rows[] = [
|
||||
'external_calendar_id' => $cal->id,
|
||||
'starts_at' => $startsAt->toDateTimeString(),
|
||||
'ends_at' => $endsAt->toDateTimeString(),
|
||||
'transparency'=> 'OPAQUE',
|
||||
'last_seen_at'=> $nowTs->toDateTimeString(),
|
||||
'created_at' => $nowTs->toDateTimeString(),
|
||||
'updated_at' => $nowTs->toDateTimeString(),
|
||||
];
|
||||
}
|
||||
|
||||
if (!empty($rows)) {
|
||||
// Bulk insert
|
||||
ExternalBusyWindow::insert($rows);
|
||||
}
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
\Log::error("ICS sync error", ['cal' => $cal->id, 'e' => $e->getMessage()]);
|
||||
// Continue with other calendars
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user