Adds basic subscription handling, fixes timezone issues app wide, improves event saving and timezone issues there, updates user table and seeding, moves to email address as principal uri component and not ulid
This commit is contained in:
parent
8d0738019f
commit
a82d9fe01f
20
.env.example
20
.env.example
@ -1,16 +1,20 @@
|
||||
APP_NAME=Laravel
|
||||
APP_NAME=Kithkin
|
||||
APP_ENV=local
|
||||
APP_KEY=
|
||||
APP_DEBUG=true
|
||||
APP_URL=http://localhost
|
||||
|
||||
APP_LOCALE=en
|
||||
APP_FALLBACK_LOCALE=en
|
||||
APP_FAKER_LOCALE=en_US
|
||||
APP_TIMEZONE=America/New_York # custom
|
||||
|
||||
APP_MAINTENANCE_DRIVER=file
|
||||
# APP_MAINTENANCE_STORE=database
|
||||
|
||||
ADMIN_EMAIL=admin@kithkin.dev
|
||||
ADMIN_PASSWORD=
|
||||
ADMIN_NAME="Kithkin Admin"
|
||||
|
||||
PHP_CLI_SERVER_WORKERS=4
|
||||
|
||||
BCRYPT_ROUNDS=12
|
||||
@ -20,12 +24,12 @@ LOG_STACK=single
|
||||
LOG_DEPRECATIONS_CHANNEL=null
|
||||
LOG_LEVEL=debug
|
||||
|
||||
DB_CONNECTION=sqlite
|
||||
# DB_HOST=127.0.0.1
|
||||
# DB_PORT=3306
|
||||
# DB_DATABASE=laravel
|
||||
# DB_USERNAME=root
|
||||
# DB_PASSWORD=
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=3306
|
||||
DB_DATABASE=kithkin
|
||||
DB_USERNAME=root
|
||||
DB_PASSWORD=
|
||||
|
||||
SESSION_DRIVER=database
|
||||
SESSION_LIFETIME=120
|
||||
|
@ -7,6 +7,7 @@ use Illuminate\Support\Str;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use App\Models\Calendar;
|
||||
use App\Models\CalendarMeta;
|
||||
use App\Models\CalendarInstance;
|
||||
|
||||
class CalendarController extends Controller
|
||||
{
|
||||
@ -15,7 +16,7 @@ class CalendarController extends Controller
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$principal = 'principals/' . auth()->id();
|
||||
$principal = auth()->user()->principal_uri;
|
||||
|
||||
$calendars = Calendar::query()
|
||||
->select('calendars.*', 'ci.displayname as instance_displayname') // ← add
|
||||
@ -54,7 +55,7 @@ class CalendarController extends Controller
|
||||
// update the calendar instance row
|
||||
$instance = CalendarInstance::create([
|
||||
'calendarid' => $calId,
|
||||
'principaluri' => 'principals/'.auth()->id(),
|
||||
'principaluri' => auth()->user()->principal_uri,
|
||||
'uri' => Str::uuid(),
|
||||
'displayname' => $data['name'],
|
||||
'description' => $data['description'] ?? null,
|
||||
@ -82,15 +83,13 @@ class CalendarController extends Controller
|
||||
|
||||
$calendar->load([
|
||||
'meta',
|
||||
'instances' => fn ($q) =>
|
||||
$q->where('principaluri', 'principals/'.auth()->id()),
|
||||
'instances' => fn ($q) => $q->where('principaluri', auth()->user()->principal_uri),
|
||||
]);
|
||||
|
||||
/* grab the single instance for convenience in the view */
|
||||
$instance = $calendar->instances->first();
|
||||
$caldavUrl = $instance?->caldavUrl(); // null-safe
|
||||
|
||||
|
||||
/* events + meta, newest first */
|
||||
$events = $calendar->events()
|
||||
->with('meta')
|
||||
@ -113,7 +112,7 @@ class CalendarController extends Controller
|
||||
$calendar->load([
|
||||
'meta',
|
||||
'instances' => fn ($q) =>
|
||||
$q->where('principaluri', 'principals/'.auth()->id()),
|
||||
$q->where('principaluri', auth()->user()->principal_uri),
|
||||
]);
|
||||
|
||||
$instance = $calendar->instances->first(); // may be null but shouldn’t
|
||||
@ -137,7 +136,7 @@ class CalendarController extends Controller
|
||||
|
||||
// update the instance row
|
||||
$calendar->instances()
|
||||
->where('principaluri', 'principals/'.auth()->id())
|
||||
->where('principaluri', auth()->user()->principal_uri)
|
||||
->update([
|
||||
'displayname' => $data['name'],
|
||||
'description' => $data['description'] ?? '',
|
||||
|
@ -4,42 +4,50 @@ namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use App\Services\Dav\LaravelSabreAuthBackend;
|
||||
use App\Services\Dav\LaravelSabrePrincipalBackend;
|
||||
use Sabre\DAV;
|
||||
use Sabre\DAV\Auth\Plugin as AuthPlugin;
|
||||
use Sabre\DAVACL\Plugin as ACLPlugin;
|
||||
use Sabre\DAVACL\PrincipalCollection;
|
||||
use Sabre\CalDAV\CalendarRoot;
|
||||
use Sabre\CalDAV\Plugin as CalDavPlugin;
|
||||
use Sabre\CalDAV\Backend\PDO as CalDAVPDO;
|
||||
use Sabre\CardDAV\AddressBookRoot;
|
||||
|
||||
class DavController extends Controller
|
||||
{
|
||||
public function handle()
|
||||
{
|
||||
// debug
|
||||
\Log::info('SabreDAV DavController');
|
||||
|
||||
// get raw pdo from laravel
|
||||
$pdo = DB::connection()->getPdo(); // get raw PDO from Laravel
|
||||
|
||||
// setup the backends
|
||||
$authBackend = new LaravelSabreAuthBackend();
|
||||
$principalBackend = new LaravelSabrePrincipalBackend();
|
||||
\Log::info('SabreDAV DavController');
|
||||
$calendarBackend = new CalDAVPDO($pdo);
|
||||
|
||||
$nodes = [
|
||||
new PrincipalCollection($principalBackend),
|
||||
new CalendarRoot($principalBackend, $calendarBackend)
|
||||
// Add your Calendars or Addressbooks here
|
||||
];
|
||||
|
||||
$server = new DAV\Server($nodes);
|
||||
$server->setBaseUri('/dav/');
|
||||
|
||||
$server->addPlugin(new AuthPlugin($authBackend, 'WebDAV'));
|
||||
$server->addPlugin(new AuthPlugin($authBackend, 'Kithkin DAV'));
|
||||
$server->addPlugin(new ACLPlugin());
|
||||
$server->addPlugin(new CalDavPlugin());
|
||||
|
||||
$server->on('beforeMethod', function () {
|
||||
\Log::info('SabreDAV beforeMethod triggered');
|
||||
});
|
||||
|
||||
ob_start();
|
||||
$server->exec();
|
||||
|
||||
$status = $server->httpResponse->getStatus();
|
||||
$content = ob_get_contents();
|
||||
ob_end_clean();
|
||||
$server->exec();
|
||||
exit;
|
||||
}
|
||||
|
@ -12,14 +12,32 @@ use App\Models\EventMeta;
|
||||
|
||||
class EventController extends Controller
|
||||
{
|
||||
/**
|
||||
* create a new event page
|
||||
*/
|
||||
public function create(Calendar $calendar)
|
||||
{
|
||||
$this->authorize('update', $calendar);
|
||||
return view('events.form', ['calendar' => $calendar, 'event' => new Event]);
|
||||
|
||||
$instance = $calendar->instanceForUser();
|
||||
$event = new Event;
|
||||
$event->meta = (object) [
|
||||
'title' => '',
|
||||
'description' => '',
|
||||
'location' => '',
|
||||
'start_at' => null,
|
||||
'end_at' => null,
|
||||
'all_day' => false,
|
||||
'category' => '',
|
||||
];
|
||||
$start = $event->start_at;
|
||||
$end = $event->end_at;
|
||||
|
||||
return view('events.form', compact('calendar', 'instance', 'event', 'start', 'end'));
|
||||
}
|
||||
|
||||
/**
|
||||
* insert VEVENT into Sabre’s calendarobjects + meta row
|
||||
* insert vevent into sabre’s calendarobjects + meta row
|
||||
*/
|
||||
public function store(Request $req, Calendar $calendar)
|
||||
{
|
||||
@ -37,8 +55,13 @@ class EventController extends Controller
|
||||
|
||||
// prepare payload
|
||||
$uid = Str::uuid() . '@' . parse_url(config('app.url'), PHP_URL_HOST);
|
||||
$start = new Carbon($data['start_at'], $calendar->timezone);
|
||||
$end = new Carbon($data['end_at'], $calendar->timezone);
|
||||
|
||||
// store events as UTC in the database; convert to calendar time in the view
|
||||
$client_timezone = $calendar->timezone ?? 'UTC';
|
||||
$start = Carbon::createFromFormat('Y-m-d\TH:i', $data['start_at'], $client_timezone)
|
||||
->setTimezone('UTC');
|
||||
$end = Carbon::createFromFormat('Y-m-d\TH:i', $data['end_at'], $client_timezone)
|
||||
->setTimezone('UTC');
|
||||
|
||||
// prepare strings
|
||||
$description = $data['description'] ?? '';
|
||||
@ -46,7 +69,6 @@ class EventController extends Controller
|
||||
$description = str_replace("\n", '\\n', $description);
|
||||
$location = str_replace("\n", '\\n', $location);
|
||||
|
||||
|
||||
/* build minimal iCalendar payload */
|
||||
$ical = <<<ICS
|
||||
BEGIN:VCALENDAR
|
||||
@ -89,10 +111,25 @@ ICS;
|
||||
return redirect()->route('calendars.show', $calendar);
|
||||
}
|
||||
|
||||
/**
|
||||
* show the event edit form
|
||||
*/
|
||||
public function edit(Calendar $calendar, Event $event)
|
||||
{
|
||||
$this->authorize('update', $calendar);
|
||||
return view('events.form', compact('calendar', 'event'));
|
||||
|
||||
$instance = $calendar->instanceForUser();
|
||||
$timezone = $instance?->timezone ?? 'UTC';
|
||||
|
||||
$start = optional($event->meta?->start_at)
|
||||
?->timezone($timezone)
|
||||
?->format('Y-m-d\TH:i');
|
||||
|
||||
$end = optional($event->meta?->end_at)
|
||||
?->timezone($timezone)
|
||||
?->format('Y-m-d\TH:i');
|
||||
|
||||
return view('events.form', compact('calendar', 'instance', 'event', 'start', 'end'));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -112,9 +149,9 @@ ICS;
|
||||
]);
|
||||
|
||||
// rebuild the icalendar payload
|
||||
$tz = $calendar->timezone;
|
||||
$start = new \Carbon\Carbon($data['start_at'], $tz);
|
||||
$end = new \Carbon\Carbon($data['end_at'], $tz);
|
||||
$calendar_timezone = $calendar->timezone ?? 'UTC';
|
||||
$start = Carbon::createFromFormat('Y-m-d\TH:i', $data['start_at'], $calendar_timezone)->setTimezone($calendar_timezone);
|
||||
$end = Carbon::createFromFormat('Y-m-d\TH:i', $data['end_at'], $calendar_timezone)->setTimezone($calendar_timezone);
|
||||
|
||||
// prepare strings
|
||||
$description = $data['description'] ?? '';
|
||||
@ -132,8 +169,8 @@ PRODID:-//Kithkin//Laravel CalDAV//EN
|
||||
BEGIN:VEVENT
|
||||
UID:$uid
|
||||
DTSTAMP:{$start->utc()->format('Ymd\\THis\\Z')}
|
||||
DTSTART;TZID={$tz}:{$start->format('Ymd\\THis')}
|
||||
DTEND;TZID={$tz}:{$end->format('Ymd\\THis')}
|
||||
DTSTART;TZID={$calendar_timezone}:{$start->format('Ymd\\THis')}
|
||||
DTEND;TZID={$calendar_timezone}:{$end->format('Ymd\\THis')}
|
||||
SUMMARY:{$data['title']}
|
||||
DESCRIPTION:$description
|
||||
LOCATION:$location
|
||||
|
67
app/Http/Controllers/SubscriptionController.php
Normal file
67
app/Http/Controllers/SubscriptionController.php
Normal file
@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\CalendarInstance;
|
||||
use Illuminate\Support\Facades\Response;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class SubscriptionController extends Controller
|
||||
{
|
||||
public function download(string $calendarUri)
|
||||
{
|
||||
$instance = CalendarInstance::where('uri', $calendarUri)->firstOrFail();
|
||||
|
||||
$calendar = $instance->calendar()->with(['events.meta'])->firstOrFail();
|
||||
$timezone = $instance->timezone ?? 'UTC';
|
||||
|
||||
$ical = $this->generateICalendarFeed($calendar->events, $timezone);
|
||||
|
||||
return Response::make($ical, 200, [
|
||||
'Content-Type' => 'text/calendar; charset=utf-8',
|
||||
'Content-Disposition' => 'inline; filename="' . $calendarUri . '.ics"',
|
||||
]);
|
||||
}
|
||||
|
||||
protected function generateICalendarFeed($events, string $tz): string
|
||||
{
|
||||
$output = [];
|
||||
$output[] = 'BEGIN:VCALENDAR';
|
||||
$output[] = 'VERSION:2.0';
|
||||
$output[] = 'PRODID:-//Kithkin Calendar//EN';
|
||||
$output[] = 'CALSCALE:GREGORIAN';
|
||||
$output[] = 'METHOD:PUBLISH';
|
||||
|
||||
foreach ($events as $event) {
|
||||
$meta = $event->meta;
|
||||
|
||||
if (!$meta || !$meta->start_at || !$meta->end_at) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$start = Carbon::parse($meta->start_at)->timezone($tz)->format('Ymd\THis');
|
||||
$end = Carbon::parse($meta->end_at)->timezone($tz)->format('Ymd\THis');
|
||||
|
||||
$output[] = 'BEGIN:VEVENT';
|
||||
$output[] = 'UID:' . $event->uid;
|
||||
$output[] = 'SUMMARY:' . $this->escape($meta->title ?? '(Untitled)');
|
||||
$output[] = 'DESCRIPTION:' . $this->escape($meta->description ?? '');
|
||||
$output[] = 'DTSTART;TZID=' . $tz . ':' . $start;
|
||||
$output[] = 'DTEND;TZID=' . $tz . ':' . $end;
|
||||
$output[] = 'DTSTAMP:' . Carbon::parse($event->lastmodified)->format('Ymd\THis\Z');
|
||||
if ($meta->location) {
|
||||
$output[] = 'LOCATION:' . $this->escape($meta->location);
|
||||
}
|
||||
$output[] = 'END:VEVENT';
|
||||
}
|
||||
|
||||
$output[] = 'END:VCALENDAR';
|
||||
|
||||
return implode("\r\n", $output);
|
||||
}
|
||||
|
||||
protected function escape(?string $text): string
|
||||
{
|
||||
return str_replace(['\\', ';', ',', "\n"], ['\\\\', '\;', '\,', '\n'], $text ?? '');
|
||||
}
|
||||
}
|
@ -15,7 +15,6 @@ class Calendar extends Model
|
||||
protected $fillable = [
|
||||
'displayname',
|
||||
'description',
|
||||
'timezone',
|
||||
];
|
||||
|
||||
/* all event components (VEVENT, VTODO, …) */
|
||||
@ -30,9 +29,19 @@ class Calendar extends Model
|
||||
return $this->hasOne(CalendarMeta::class, 'calendar_id');
|
||||
}
|
||||
|
||||
/* get instance */
|
||||
/* get instances */
|
||||
public function instances()
|
||||
{
|
||||
return $this->hasMany(CalendarInstance::class, 'calendarid');
|
||||
}
|
||||
|
||||
/* get the primary? instance for a user */
|
||||
public function instanceForUser(?User $user = null)
|
||||
{
|
||||
$user = $user ?? auth()->user();
|
||||
|
||||
return $this->instances()
|
||||
->where('principaluri', 'principals/' . $user->email)
|
||||
->first();
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,15 @@ class CalendarInstance extends Model
|
||||
{
|
||||
protected $table = 'calendarinstances';
|
||||
public $timestamps = false;
|
||||
protected $fillable = [
|
||||
'calendarid',
|
||||
'principaluri',
|
||||
'uri',
|
||||
'displayname',
|
||||
'description',
|
||||
'calendarcolor',
|
||||
'timezone'
|
||||
];
|
||||
|
||||
public function calendar(): BelongsTo
|
||||
{
|
||||
@ -20,7 +29,7 @@ class CalendarInstance extends Model
|
||||
// e.g. https://kithkin.lan/dav/calendars/1/48f888f3-c5c5-…/
|
||||
return url(
|
||||
'/dav/calendars/' .
|
||||
auth()->id() . '/' .
|
||||
auth()->user()->email . '/' .
|
||||
$this->uri . '/'
|
||||
);
|
||||
}
|
||||
|
@ -24,6 +24,12 @@ class Event extends Model
|
||||
'calendardata',
|
||||
];
|
||||
|
||||
/* casts */
|
||||
protected $casts = [
|
||||
'start_at' => 'datetime',
|
||||
'end_at' => 'datetime',
|
||||
];
|
||||
|
||||
/* owning calendar */
|
||||
public function calendar(): BelongsTo
|
||||
{
|
||||
|
@ -61,4 +61,12 @@ class User extends Authenticatable
|
||||
{
|
||||
return $this->hasMany(Calendar::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* get the current user's principal uri
|
||||
*/
|
||||
public function getPrincipalUriAttribute(): string
|
||||
{
|
||||
return 'principals/' . $this->email;
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ class CalendarPolicy
|
||||
public function view(User $user, Calendar $calendar): bool
|
||||
{
|
||||
return $calendar->instances()
|
||||
->where('principaluri', 'principals/'.$user->id)
|
||||
->where('principaluri', 'principals/'.$user->email)
|
||||
->exists();
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,8 @@ use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Sabre\DAV\Auth\Backend\AbstractBasic;
|
||||
use Sabre\DAV\Auth\Backend\BasicCallBack;
|
||||
use Sabre\HTTP\RequestInterface;
|
||||
use Sabre\HTTP\ResponseInterface;
|
||||
|
||||
class LaravelSabreAuthBackend extends AbstractBasic
|
||||
{
|
||||
@ -15,6 +17,11 @@ class LaravelSabreAuthBackend extends AbstractBasic
|
||||
\Log::info('LaravelSabreAuthBackend instantiated');
|
||||
}
|
||||
|
||||
public function challenge(RequestInterface $request, ResponseInterface $response)
|
||||
{
|
||||
$response->addHeader('WWW-Authenticate', 'Basic realm="WebDAV", charset="UTF-8"');
|
||||
}
|
||||
|
||||
protected function validateUserPass($username, $password)
|
||||
{
|
||||
$user = User::where('email', $username)->first();
|
||||
@ -23,18 +30,18 @@ class LaravelSabreAuthBackend extends AbstractBasic
|
||||
// THIS is the new required step
|
||||
$this->currentPrincipal = 'principals/' . $user->email;
|
||||
|
||||
\Log::info('SabreDAV auth success', ['principal' => $this->currentPrincipal]);
|
||||
\Log::info('validateUserPass auth success', ['principal' => $this->currentPrincipal, 'username' => $username]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
\Log::warning('SabreDAV auth failed', ['username' => $username]);
|
||||
\Log::warning('validateUserPass auth failed', ['username' => $username]);
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getCurrentPrincipal()
|
||||
{
|
||||
\Log::debug('SabreDAV getCurrentPrincipal', ['current' => $$this->currentPrincipal]);
|
||||
\Log::debug('getCurrentPrincipal', ['current' => $$this->currentPrincipal]);
|
||||
return $this->currentPrincipal;
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ class LaravelSabrePrincipalBackend extends AbstractBackend
|
||||
{
|
||||
return User::all()->map(function ($user) {
|
||||
return [
|
||||
'uri' => 'principals/' . $user->id,
|
||||
'uri' => 'principals/' . $user->email,
|
||||
'{DAV:}displayname' => $user->name,
|
||||
];
|
||||
})->toArray();
|
||||
|
@ -10,7 +10,7 @@ return new class extends Migration
|
||||
{
|
||||
Schema::create('users', function (Blueprint $table) {
|
||||
$table->ulid('id')->primary(); // ulid primary key
|
||||
$table->string('uri')->unique()->nullable()->after('email'); // formerly from sabre principals table
|
||||
$table->string('uri')->nullable(); // formerly from sabre principals table
|
||||
$table->string('displayname')->nullable(); // formerly from sabre principals table
|
||||
$table->string('name')->nullable(); // custom name if necessary
|
||||
$table->string('email')->unique();
|
||||
|
@ -27,7 +27,7 @@ class DatabaseSeeder extends Seeder
|
||||
]
|
||||
);
|
||||
|
||||
/** fill the Sabre-friendly columns */
|
||||
/** fill the sabre-friendly columns */
|
||||
$user->update([
|
||||
'uri' => 'principals/'.$user->email,
|
||||
'displayname' => $user->name,
|
||||
|
@ -32,7 +32,7 @@
|
||||
|
||||
<div>
|
||||
<dt class="text-sm font-medium text-gray-500">{{ __('Timezone') }}</dt>
|
||||
<dd class="mt-1 text-gray-900">{{ $instance->timezone }}</dd>
|
||||
<dd class="mt-1 text-gray-900"> {{ $instance?->timezone ?? __('Not set') }}</dd>
|
||||
</div>
|
||||
|
||||
@php
|
||||
|
@ -30,7 +30,7 @@
|
||||
<div class="mb-6">
|
||||
<x-input-label for="title" :value="__('Title')" />
|
||||
<x-text-input id="title" name="title" type="text" class="mt-1 block w-full"
|
||||
:value="old('title', $event->meta->title)" required autofocus />
|
||||
:value="old('title', $event->meta?->title ?? '')" required autofocus />
|
||||
<x-input-error class="mt-2" :messages="$errors->get('title')" />
|
||||
</div>
|
||||
|
||||
@ -38,7 +38,7 @@
|
||||
<div class="mb-6">
|
||||
<x-input-label for="description" :value="__('Description')" />
|
||||
<textarea id="description" name="description" rows="3"
|
||||
class="mt-1 block w-full rounded-md shadow-sm border-gray-300 focus:border-indigo-300 focus:ring">{{ old('description', $event->meta->description) }}</textarea>
|
||||
class="mt-1 block w-full rounded-md shadow-sm border-gray-300 focus:border-indigo-300 focus:ring">{{ old('description', $event->meta?->description ?? '') }}</textarea>
|
||||
<x-input-error class="mt-2" :messages="$errors->get('description')" />
|
||||
</div>
|
||||
|
||||
@ -46,7 +46,7 @@
|
||||
<div class="mb-6">
|
||||
<x-input-label for="location" :value="__('Location')" />
|
||||
<x-text-input id="location" name="location" type="text" class="mt-1 block w-full"
|
||||
:value="old('location', $event->meta->location)" />
|
||||
:value="old('location', $event->meta?->location ?? '')" />
|
||||
<x-input-error class="mt-2" :messages="$errors->get('location')" />
|
||||
</div>
|
||||
|
||||
@ -57,9 +57,8 @@
|
||||
<x-input-label for="start_at" :value="__('Starts')" />
|
||||
<x-text-input id="start_at" name="start_at" type="datetime-local"
|
||||
class="mt-1 block w-full"
|
||||
:value="old('start_at', $event->meta->start_at
|
||||
? $event->meta->start_at->format('Y-m-d\TH:i')
|
||||
: '')" required />
|
||||
:value="old('start_at', $start)"
|
||||
required />
|
||||
<x-input-error class="mt-2" :messages="$errors->get('start_at')" />
|
||||
</div>
|
||||
|
||||
@ -67,9 +66,8 @@
|
||||
<x-input-label for="end_at" :value="__('Ends')" />
|
||||
<x-text-input id="end_at" name="end_at" type="datetime-local"
|
||||
class="mt-1 block w-full"
|
||||
:value="old('end_at', $event->meta->end_at
|
||||
? $event->meta->end_at->format('Y-m-d\TH:i')
|
||||
: '')" required />
|
||||
:value="old('end_at', $end)"
|
||||
required />
|
||||
<x-input-error class="mt-2" :messages="$errors->get('end_at')" />
|
||||
</div>
|
||||
|
||||
@ -78,7 +76,7 @@
|
||||
{{-- All-day --}}
|
||||
<div class="flex items-center mb-6">
|
||||
<input id="all_day" name="all_day" type="checkbox" value="1"
|
||||
@checked(old('all_day', $event->meta->all_day)) />
|
||||
@checked(old('all_day', $event->meta?->all_day)) />
|
||||
<label for="all_day" class="ms-2 text-sm text-gray-700">
|
||||
{{ __('All day event') }}
|
||||
</label>
|
||||
|
@ -5,6 +5,7 @@ use App\Http\Controllers\ProfileController;
|
||||
use App\Http\Controllers\CalendarController;
|
||||
use App\Http\Controllers\EventController;
|
||||
use App\Http\Controllers\DavController;
|
||||
use App\Http\Controllers\SubscriptionController;
|
||||
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
|
||||
|
||||
/*
|
||||
@ -56,9 +57,13 @@ require __DIR__.'/auth.php';
|
||||
| authentication for DAV clients.
|
||||
*/
|
||||
|
||||
// default dav handling
|
||||
Route::match(
|
||||
['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS', 'HEAD', 'PROPFIND', 'PROPPATCH', 'MKCOL', 'COPY', 'MOVE', 'REPORT', 'LOCK', 'UNLOCK'],
|
||||
'/dav/{any?}',
|
||||
[DavController::class, 'handle']
|
||||
)->where('any', '.*')
|
||||
->withoutMiddleware([VerifyCsrfToken::class]);
|
||||
|
||||
// subscriptions
|
||||
Route::get('/subscriptions/{calendarUri}.ics', [SubscriptionController::class, 'download']);
|
||||
|
Loading…
Reference in New Issue
Block a user