2 Commits

Author SHA1 Message Date
Lennart J. Kurzweg (Nx2)
77498b6261 fix orphans 2026-04-30 20:14:21 +02:00
Lennart J. Kurzweg (Nx2)
960c080f1c fix aggregate source discrepancy 2026-04-24 19:08:36 +02:00
3 changed files with 29 additions and 17 deletions

View File

@@ -102,7 +102,7 @@ func (b *DBBackend) initSchema(ctx context.Context) error {
)`, )`,
`CREATE TABLE IF NOT EXISTS calendar_objects ( `CREATE TABLE IF NOT EXISTS calendar_objects (
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
calendar_id INTEGER REFERENCES calendars(id) ON DELETE CASCADE, calendar_id INTEGER NOT NULL REFERENCES calendars(id) ON DELETE CASCADE,
path TEXT NOT NULL, path TEXT NOT NULL,
data TEXT NOT NULL, data TEXT NOT NULL,
etag TEXT NOT NULL, etag TEXT NOT NULL,
@@ -110,20 +110,20 @@ func (b *DBBackend) initSchema(ctx context.Context) error {
)`, )`,
`CREATE TABLE IF NOT EXISTS addressbooks ( `CREATE TABLE IF NOT EXISTS addressbooks (
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
owner_id INTEGER REFERENCES users(id) ON DELETE CASCADE, owner_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
path TEXT UNIQUE NOT NULL, path TEXT UNIQUE NOT NULL,
name TEXT, name TEXT,
description TEXT description TEXT
)`, )`,
`CREATE TABLE IF NOT EXISTS addressbook_access ( `CREATE TABLE IF NOT EXISTS addressbook_access (
addressbook_id INTEGER REFERENCES addressbooks(id) ON DELETE CASCADE, addressbook_id INTEGER NOT NULL REFERENCES addressbooks(id) ON DELETE CASCADE,
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE, user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
mode TEXT NOT NULL, mode TEXT NOT NULL,
PRIMARY KEY (addressbook_id, user_id) PRIMARY KEY (addressbook_id, user_id)
)`, )`,
`CREATE TABLE IF NOT EXISTS addressbook_objects ( `CREATE TABLE IF NOT EXISTS addressbook_objects (
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
addressbook_id INTEGER REFERENCES addressbooks(id) ON DELETE CASCADE, addressbook_id INTEGER NOT NULL REFERENCES addressbooks(id) ON DELETE CASCADE,
path TEXT NOT NULL, path TEXT NOT NULL,
data TEXT NOT NULL, data TEXT NOT NULL,
etag TEXT NOT NULL, etag TEXT NOT NULL,
@@ -258,6 +258,7 @@ func (b *DBBackend) syncConfig(ctx context.Context, cfg *config.Config) error {
for _, a := range c.Access { for _, a := range c.Access {
if a.ICS != "" { if a.ICS != "" {
pubPath := prefix + fmt.Sprintf("/public/%s/%s.ics", c.Owner, c.ID) pubPath := prefix + fmt.Sprintf("/public/%s/%s.ics", c.Owner, c.ID)
log.Printf("pp: %s", pubPath)
b.publicAccess[pubPath] = publicInfo{ b.publicAccess[pubPath] = publicInfo{
InternalPath: path, InternalPath: path,
Mode: a.ICS, Mode: a.ICS,
@@ -768,14 +769,14 @@ func (b *DBBackend) listAggregateObjects(ctx context.Context, aggPath string, ag
username, _ := b.getUsername(ctx) username, _ := b.getUsername(ctx)
for _, sourceID := range agg.Sources { for _, sourceID := range agg.Sources {
sourcePath := b.prefix + fmt.Sprintf("/%s/calendars/%s/", agg.Owner, sourceID)
var calID int var calID int
var ownerName string var ownerName string
var sourcePath string
err := b.pool.QueryRow(ctx, ` err := b.pool.QueryRow(ctx, `
SELECT c.id, u.name FROM calendars c SELECT c.id, u.name, c.path FROM calendars c
JOIN users u ON c.owner_id = u.id JOIN users u ON c.owner_id = u.id
WHERE c.path = $1`, sourcePath).Scan(&calID, &ownerName) WHERE c.name = $1`, sourceID).Scan(&calID, &ownerName, &sourcePath)
if err != nil { if err != nil {
continue continue
} }
@@ -800,7 +801,7 @@ func (b *DBBackend) listAggregateObjects(ctx context.Context, aggPath string, ag
} }
for _, obj := range objs { for _, obj := range objs {
// Prepend source name so Diane knows this is a "[calendar]" event // Prepend source name so user knows this is a "[calendar]" event
for _, child := range obj.Data.Children { for _, child := range obj.Data.Children {
if child.Name == "VEVENT" || child.Name == "VTODO" { if child.Name == "VEVENT" || child.Name == "VTODO" {
descr := child.Props.Get("DESCRIPTION") descr := child.Props.Get("DESCRIPTION")
@@ -831,7 +832,7 @@ func (b *DBBackend) GetCalendarObject(ctx context.Context, p string, req *caldav
fileName := path.Base(p) fileName := path.Base(p)
// Step 1: Check if this is a request for a virtual item in an aggregate // Step 1: Check if this is a request for a virtual item in an aggregate
if agg, ok := b.aggregates[dirPath]; ok { if _, ok := b.aggregates[dirPath]; ok {
hasAccess := slices.Contains(b.userAggs[username], dirPath) hasAccess := slices.Contains(b.userAggs[username], dirPath)
if !hasAccess { if !hasAccess {
@@ -846,10 +847,10 @@ func (b *DBBackend) GetCalendarObject(ctx context.Context, p string, req *caldav
sourceID := parts[0] sourceID := parts[0]
realFileName := parts[1] realFileName := parts[1]
sourcePath := b.prefix + fmt.Sprintf("/%s/calendars/%s/", agg.Owner, sourceID)
var calID int var calID int
var ownerName string var ownerName string
err := b.pool.QueryRow(ctx, "SELECT c.id, u.name FROM calendars c JOIN users u ON c.owner_id = u.id WHERE c.path = $1", sourcePath).Scan(&calID, &ownerName) var sourcePath string
err := b.pool.QueryRow(ctx, "SELECT c.id, u.name, c.path FROM calendars c JOIN users u ON c.owner_id = u.id WHERE c.name = $1", sourceID).Scan(&calID, &ownerName, &sourcePath)
if err != nil { if err != nil {
return nil, webdav.NewHTTPError(http.StatusNotFound, errors.New("source calendar not found")) return nil, webdav.NewHTTPError(http.StatusNotFound, errors.New("source calendar not found"))
} }

View File

@@ -39,9 +39,19 @@ func InjectColor(r *http.Request, ctx context.Context, handler http.Handler, w h
body := buf.Bytes() body := buf.Bytes()
// this models after the Radicale Response, largely AI code // this models after the Radicale Response, largely AI code
// 1. Add namespaces to the root multistatus tag // 1. Add namespaces to the root multistatus tag only if they are missing
reMultistatus := regexp.MustCompile(`(<[a-zA-Z0-9]*:?multistatus)`) if !bytes.Contains(body, []byte("xmlns:ICAL=")) {
body = reMultistatus.ReplaceAll(body, []byte(`$1 xmlns:ICAL="http://apple.com/ns/ical/" xmlns:C="urn:ietf:params:xml:ns:caldav" xmlns:CS="http://calendarserver.org/ns/"`)) reMultistatus := regexp.MustCompile(`(<[a-zA-Z0-9]*:?multistatus)`)
body = reMultistatus.ReplaceAll(body, []byte(`$1 xmlns:ICAL="http://apple.com/ns/ical/"`))
}
if !bytes.Contains(body, []byte("xmlns:C=")) && !bytes.Contains(body, []byte("xmlns:c=")) {
reMultistatus := regexp.MustCompile(`(<[a-zA-Z0-9]*:?multistatus)`)
body = reMultistatus.ReplaceAll(body, []byte(`$1 xmlns:C="urn:ietf:params:xml:ns:caldav"`))
}
if !bytes.Contains(body, []byte("xmlns:CS=")) {
reMultistatus := regexp.MustCompile(`(<[a-zA-Z0-9]*:?multistatus)`)
body = reMultistatus.ReplaceAll(body, []byte(`$1 xmlns:CS="http://calendarserver.org/ns/"`))
}
// 2. Response processing // 2. Response processing
reResponse := regexp.MustCompile(`(?s)<[a-zA-Z0-9]*:?response.*?>.*?</[a-zA-Z0-9]*:?response>`) reResponse := regexp.MustCompile(`(?s)<[a-zA-Z0-9]*:?response.*?>.*?</[a-zA-Z0-9]*:?response>`)
@@ -62,7 +72,7 @@ func InjectColor(r *http.Request, ctx context.Context, handler http.Handler, w h
return resp return resp
} }
// 1. Strip any existing conflicting tags that might be in 404 blocks // 1. Strip any existing conflicting tags that might be in 404 blocks (non-greedy)
reStrip := regexp.MustCompile(`(?s)<[a-zA-Z0-9]*:?(calendar-color|getctag|calendar-order).*?/>|<[a-zA-Z0-9]*:?(calendar-color|getctag|calendar-order).*?>.*?</[a-zA-Z0-9]*:?(calendar-color|getctag|calendar-order)>`) reStrip := regexp.MustCompile(`(?s)<[a-zA-Z0-9]*:?(calendar-color|getctag|calendar-order).*?/>|<[a-zA-Z0-9]*:?(calendar-color|getctag|calendar-order).*?>.*?</[a-zA-Z0-9]*:?(calendar-color|getctag|calendar-order)>`)
resp = reStrip.ReplaceAll(resp, []byte("")) resp = reStrip.ReplaceAll(resp, []byte(""))

View File

@@ -157,6 +157,7 @@ func main() {
caldavHandler.ServeHTTP(w, r.WithContext(ctx)) caldavHandler.ServeHTTP(w, r.WithContext(ctx))
} }
} else { } else {
log.Printf("Not found: %s", r)
http.NotFound(w, r) http.NotFound(w, r)
} }
} }