feat: add WannPassts booking app with Go backend and Vue frontend

Initial project setup for a privacy-focused, self-hosted scheduling app:

- Go backend with JWT auth, SQLite storage, and chi router
- Calendar integrations via Google OAuth (FreeBusy), CalDAV, and ICS links
- Public booking pages with configurable slots and booking requests
- AES-256-GCM encryption for stored provider tokens
- Vue 3 + TypeScript frontend with Vite, Pinia, and Vue Router
- Docs, .gitignore, and .env.example for local development
This commit is contained in:
Tronax 2026-08-25 18:50:28 +02:00
commit cb30223fd4
Signed by: Tronax
SSH key fingerprint: SHA256:2pKKXDZucWvaF/GzXNz0FY53EAO1YDLN80bqS+TTz/o
47 changed files with 6599 additions and 0 deletions

View file

@ -0,0 +1,41 @@
package calendar
import (
"context"
"sort"
"time"
)
// Interval ist ein "beschäftigt"-Zeitraum. Enthält bewusst KEINE Termindetails,
// nur Start und Ende mehr verlässt den Kalender des Nutzers nie.
type Interval struct {
Start time.Time
End time.Time
}
type Provider interface {
// FetchBusy liefert alle busy-Zeiträume im Fenster [from, to).
FetchBusy(ctx context.Context, from, to time.Time) ([]Interval, error)
}
// clampIntervals schneidet Intervalle aufs Fenster zurecht, verwirft ungültige
// und sortiert das Ergebnis.
func clampIntervals(in []Interval, from, to time.Time) []Interval {
out := make([]Interval, 0, len(in))
for _, iv := range in {
if !iv.End.After(iv.Start) {
continue
}
if iv.Start.Before(from) {
iv.Start = from
}
if iv.End.After(to) {
iv.End = to
}
if iv.End.After(iv.Start) {
out = append(out, iv)
}
}
sort.Slice(out, func(i, j int) bool { return out[i].Start.Before(out[j].Start) })
return out
}