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
41 lines
980 B
Go
41 lines
980 B
Go
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
|
||
}
|