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.
54 lines
2.2 KiB
Cheetah
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}}
|