feat: usos sign-in, private calendar feed and timetable archive

This commit is contained in:
hh committed 2026-10-11 00:47:38 +00:00
commit 7d4e21f977
33 files changed
+3270

No files matched your search

+114
View File
@@ -0,0 +1,114 @@
package ical
import (
"io"
"strings"
"time"
"unicode/utf8"
"usoscal/internal/store"
)
const (
stamp = "20060102T150405Z"
lineLimit = 75
)
func Summary(event store.Event) string {
if event.Kind == "" || event.Kind == event.Title {
return event.Title
}
return event.Title + " (" + event.Kind + ")"
}
func Location(event store.Event) string {
place := event.Room
switch {
case place != "" && event.Building != "":
place += " (" + event.Building + ")"
case place == "":
place = event.Building
}
return joinNonEmpty(", ", place, event.Address)
}
func Description(event store.Event) string {
about := event.Kind
if event.Group != "" {
about = joinNonEmpty(", ", about, "group "+event.Group)
}
lecturers := ""
if event.Lecturers != "" {
lecturers = "Lecturer: " + event.Lecturers
}
return joinNonEmpty("\n", about, lecturers, event.URL)
}
func Write(w io.Writer, name, host string, events []store.Event) error {
var b strings.Builder
line(&b, "BEGIN:VCALENDAR")
line(&b, "VERSION:2.0")
line(&b, "PRODID:-//usoscal//EN")
line(&b, "CALSCALE:GREGORIAN")
line(&b, "METHOD:PUBLISH")
line(&b, "X-WR-CALNAME:"+escape(name))
line(&b, "X-WR-TIMEZONE:Europe/Warsaw")
line(&b, "REFRESH-INTERVAL;VALUE=DURATION:PT6H")
line(&b, "X-PUBLISHED-TTL:PT6H")
for _, event := range events {
line(&b, "BEGIN:VEVENT")
line(&b, "UID:"+event.UID+"@"+host)
line(&b, "DTSTAMP:"+utc(event.UpdatedAt))
line(&b, "LAST-MODIFIED:"+utc(event.UpdatedAt))
line(&b, "DTSTART:"+utc(event.Start))
line(&b, "DTEND:"+utc(event.End))
line(&b, "SUMMARY:"+escape(Summary(event)))
if location := Location(event); location != "" {
line(&b, "LOCATION:"+escape(location))
}
if description := Description(event); description != "" {
line(&b, "DESCRIPTION:"+escape(description))
}
if event.URL != "" {
line(&b, "URL:"+event.URL)
}
line(&b, "END:VEVENT")
}
line(&b, "END:VCALENDAR")
_, err := io.WriteString(w, b.String())
return err
}
func utc(t time.Time) string {
return t.UTC().Format(stamp)
}
func joinNonEmpty(separator string, parts ...string) string {
kept := parts[:0:0]
for _, part := range parts {
if part != "" {
kept = append(kept, part)
}
}
return strings.Join(kept, separator)
}
var escaper = strings.NewReplacer(`\`, `\\`, ";", `\;`, ",", `\,`, "\r\n", `\n`, "\n", `\n`, "\r", "")
func escape(text string) string {
return escaper.Replace(text)
}
func line(b *strings.Builder, content string) {
for len(content) > lineLimit {
cut := lineLimit
for cut > 0 && !utf8.RuneStart(content[cut]) {
cut--
}
b.WriteString(content[:cut])
b.WriteString("\r\n ")
content = content[cut:]
}
b.WriteString(content)
b.WriteString("\r\n")
}