Files
projax/web/templates/caldav_admin.tmpl
mAi 96b61f7ed4 feat(phase 2 caldav): list + link + create CalDAV calendars
m's CalDAV server (dav.msbls.de, SabreDAV) now feeds projax via a thin
read-only-plus-create-on-demand integration. No background sync; tasks
fetched live on detail-page render.

New caldav/ package
- ListCalendars (PROPFIND Depth: 1, filters non-calendar collections)
- ListTodos (REPORT calendar-query for VTODO; hand-rolled iCalendar
  parser for UID/SUMMARY/STATUS/DUE/PRIORITY/LAST-MODIFIED — RFC 5545
  line-folding aware)
- CreateCalendar (MKCALENDAR, 405 → ErrCalendarExists for the "link
  instead" branch)
- httptest-stubbed tests cover all four paths.

Store
- ItemLink shape + LinksByType / LinksByRefType / AddLink / DeleteLink.
  AddLink upserts on (item_id, ref_type, ref_id, rel) so re-linking the
  same calendar is idempotent.

Web
- GET /admin/caldav — discovery + auto-suggested matches + manual
  linker. Suggestion = lowercased displayname == projax slug or title.
- POST /admin/caldav/link — insert item_links row.
- POST /admin/caldav/unlink — delete by link id.
- POST /i/{path}/caldav/create — MKCALENDAR at <base>/<slug>/, then
  AddLink. On 405 (already exists), fall back to link-only.
- Detail page Tasks section: per-calendar block with open VTODOs +
  collapsed completed (30d window). Errors per calendar logged and
  skipped, so one bad calendar does not blank the page.
- nav adds /admin/caldav link.

main.go
- DAV_URL + DAV_USER + DAV_PASSWORD optional. Missing DAV_URL → CalDAV
  off (admin page renders "not configured" notice). DAV_URL set but
  user/pass missing → fail fast at boot.

docs/design.md gains §5 documenting the integration shape.
deploy/dokploy.yaml lists the two new secrets + the env var.

Phase 2.b (writeback / two-way / background sync) is parked.
2026-05-15 16:57:43 +02:00

54 lines
2.2 KiB
Cheetah

{{define "content"}}
<h1>CalDAV calendars</h1>
<p class="muted">{{len .Suggestions}} calendars discovered on dav.msbls.de. Auto-match is case-insensitive on display name vs projax title/slug. Confirm or override; link rows live in <code>projax.item_links</code> with <code>ref_type=caldav-list</code>.</p>
<table class="classify">
<thead>
<tr><th>Calendar</th><th>Suggested / linked projax item</th><th>Action</th></tr>
</thead>
<tbody>
{{range .Suggestions}}
<tr>
<td>
<strong>{{.Calendar.DisplayName}}</strong>
<br><small class="slug">{{.Calendar.URL}}</small>
</td>
<td>
{{if .AlreadyLink}}
<span class="muted">linked →</span> <a href="/i/{{.Item.PrimaryPath}}">{{.Item.Title}}</a>
{{else if .Item}}
<span class="muted">suggested →</span> <a href="/i/{{.Item.PrimaryPath}}">{{.Item.Title}}</a>
<small class="muted">({{.Item.PrimaryPath}})</small>
{{else}}
<em class="muted">no match — pick manually</em>
{{end}}
</td>
<td>
{{if .AlreadyLink}}
<form method="post" action="/admin/caldav/unlink" class="inline">
<input type="hidden" name="link_id" value="{{.AlreadyLink.ID}}">
<button type="submit" class="cancel">unlink</button>
</form>
{{else}}
<form method="post" action="/admin/caldav/link" class="inline">
<input type="hidden" name="calendar_url" value="{{.Calendar.URL}}">
<input type="hidden" name="display_name" value="{{.Calendar.DisplayName}}">
<input type="hidden" name="color" value="{{.Calendar.Color}}">
<select name="item_id" required>
<option value="">— pick projax item —</option>
{{range $it := $.Items}}
<option value="{{$it.ID}}" {{if and $.Item (eq $it.ID $.Item.ID)}}selected{{end}}>{{$it.PrimaryPath}}</option>
{{end}}
</select>
<button type="submit">link</button>
</form>
{{end}}
</td>
</tr>
{{else}}
<tr><td colspan="3"><em>No calendars discovered.</em></td></tr>
{{end}}
</tbody>
</table>
{{end}}