feat: implement unified directory structure with automatic migration for CalDAV and CardDAV

- Unify directory structure across protocols:
  * WebDAV: data/files/<username>/
  * CalDAV: data/<username>/calendars/<name>/
  * CardDAV: data/<username>/addressbooks/<name>/

- Add automatic migration capability that runs on server startup
- Maintain full backward compatibility with existing installations
- Improve Docker usage by automatically handling legacy data structure

- Updated storage provider implementations to use new nested structure
- Enhanced store functions for backward compatibility
- Modified CalDAV and CardDAV backends to use unified paths
- Added automatic migration logic in server initialization
This commit is contained in:
2026-08-28 14:22:19 +02:00
parent f463c01f0f
commit 2222038637
8 changed files with 212 additions and 25 deletions
+7 -7
View File
@@ -116,7 +116,7 @@ func (b *Backend) ListCalendars(ctx context.Context) ([]caldav.Calendar, error)
disk, _ := b.store.ListCollections(p.Username)
configured := make(map[string]bool)
for _, cal := range names {
configured["cal-"+cal.Name] = true
configured["calendars/"+cal.Name] = true
}
for _, dir := range disk {
if strings.HasPrefix(dir, "cal-") && !configured[dir] {
@@ -188,7 +188,7 @@ func (b *Backend) GetCalendarObject(ctx context.Context, objPath string, req *ca
return nil, err
}
data, err := b.store.GetObject(owner, "cal-"+realName, objID)
data, err := b.store.GetObject(owner, "calendars/"+realName, objID)
if err != nil {
return nil, webdav.NewHTTPError(http.StatusNotFound, err)
}
@@ -212,7 +212,7 @@ func (b *Backend) ListCalendarObjects(ctx context.Context, calPath string, req *
return nil, err
}
ids, err := b.store.ListObjects(owner, "cal-"+realName)
ids, err := b.store.ListObjects(owner, "calendars/"+realName)
if err != nil {
return nil, err
}
@@ -260,7 +260,7 @@ func (b *Backend) CreateCalendar(ctx context.Context, calendar *caldav.Calendar)
return fmt.Errorf("registering calendar: %w", err)
}
}
return b.store.EnsureCollection(p.Username, "cal-"+name)
return b.store.EnsureCollection(p.Username, "calendars/"+name)
}
func (b *Backend) DeleteCalendar(ctx context.Context, calPath string) error {
@@ -285,7 +285,7 @@ func (b *Backend) DeleteCalendar(ctx context.Context, calPath string) error {
if err := b.dbase.DeleteCalendar(owner, realName); err != nil && err != db.ErrResourceNotFound {
return fmt.Errorf("unregistering calendar: %w", err)
}
return b.store.DeleteCollection(owner, "cal-"+realName)
return b.store.DeleteCollection(owner, "calendars/"+realName)
}
func (b *Backend) PutCalendarObject(ctx context.Context, objPath string, calendar *ical.Calendar, opts *caldav.PutCalendarObjectOptions) (*caldav.CalendarObject, error) {
@@ -311,7 +311,7 @@ func (b *Backend) PutCalendarObject(ctx context.Context, objPath string, calenda
}
data := []byte(buf.String())
if err := b.store.PutObject(owner, "cal-"+realName, objID, data); err != nil {
if err := b.store.PutObject(owner, "calendars/"+realName, objID, data); err != nil {
return nil, fmt.Errorf("storing calendar object: %w", err)
}
@@ -333,7 +333,7 @@ func (b *Backend) DeleteCalendarObject(ctx context.Context, objPath string) erro
if err != nil {
return err
}
if err := b.store.DeleteObject(owner, "cal-"+realName, objID); err != nil {
if err := b.store.DeleteObject(owner, "calendars/"+realName, objID); err != nil {
return webdav.NewHTTPError(http.StatusNotFound, err)
}
return nil
+1 -1
View File
@@ -245,7 +245,7 @@ func TestPropFindExplicitCalendarColorRequest(t *testing.T) {
if err := dbase.CreateCalendarWithColor("alice", "work", "#3b82f6"); err != nil {
t.Fatalf("CreateCalendarWithColor: %v", err)
}
if err := st.EnsureCollection("alice", "cal-work"); err != nil {
if err := st.EnsureCollection("alice", "calendars/work"); err != nil {
t.Fatalf("EnsureCollection: %v", err)
}