subscription = $subscription; } /** * Main entry-point executed by the queue worker. */ public function handle(): void { /** * download the remote .ics file with retry and a long timeout */ try { $ics = Http::retry(3, 5000)->timeout(30) ->withHeaders(['User-Agent' => 'Kithkin CalDAV Bot']) ->get($this->subscription->source) ->throw() // throws if not 2xx ->body(); } catch (ConnectionException | \Throwable $e) { Log::warning('Feed fetch failed', [ 'sub' => $this->subscription->id, 'msg' => $e->getMessage(), ]); /* mark the job as failed and let Horizon / queue retry logic handle it */ $this->fail($e); return; } /** * get the mirror calendar, or lazy create it */ $meta = $this->subscription->meta ?? $this->subscription->meta()->create(); if (! $meta->calendar_id) { $meta->calendar_id = $this->createMirrorCalendar($meta); $meta->save(); } $calendarId = $meta->calendar_id; /** * parse and upsert vevents */ $vcalendar = Reader::read($ics); Log::info('Syncing subscription '.$this->subscription->id); foreach ($vcalendar->VEVENT as $vevent) { $uid = (string) $vevent->UID; $now = now()->timestamp; $blob = (string) $vevent->serialize(); /** @var Event $object */ $object = Event::updateOrCreate( ['uid' => $uid, 'calendarid' => $calendarId], [ 'uri' => Str::uuid().'.ics', 'lastmodified' => $now, 'etag' => md5($blob), 'size' => strlen($blob), 'componenttype' => 'VEVENT', 'calendardata' => $blob, ] ); $startUtc = Carbon::parse($vevent->DTSTART->getDateTime()); $endUtc = isset($vevent->DTEND) ? Carbon::parse($vevent->DTEND->getDateTime()) : $startUtc; EventMeta::upsertForEvent($object->id, [ 'title' => (string) ($vevent->SUMMARY ?? 'Untitled'), 'description' => (string) ($vevent->DESCRIPTION ?? ''), 'location' => (string) ($vevent->LOCATION ?? ''), 'all_day' => $vevent->DTSTART->isFloating(), 'start_at' => $startUtc->utc(), 'end_at' => $endUtc->utc(), ]); } Log::info('Syncing subscription post foreach '.$this->subscription->id); } /** * Lazily builds the shadow calendar + instance when missing * (for legacy subscriptions added before we moved creation to the controller). */ private function createMirrorCalendar($meta): int { // check if controller created one already and return it if so if ($meta->calendar_id) { return $meta->calendar_id; } // check if a mirror calendar already exists, and return that if so $existing = CalendarInstance::where('principaluri', $this->subscription->principaluri) ->where('description', 'Remote feed: '.$this->subscription->source) ->first(); if ($existing) { return $existing->calendarid; } // otherwise create the new master calendar in `calendars` $calendar = Calendar::create([ 'synctoken' => 1, 'components' => 'VEVENT', ]); // attach an instance for this user CalendarInstance::create([ 'calendarid' => $calendar->id, 'principaluri' => $this->subscription->principaluri, 'uri' => Str::uuid(), 'displayname' => $this->subscription->displayname, 'description' => 'Remote feed: '.$this->subscription->source, 'calendarcolor' => $meta->color ?? '#1a1a1a', 'timezone' => config('app.timezone', 'UTC'), ]); return $calendar->id; } }