feat: complete CalDAV (RFC 4791) and CardDAV (RFC 6352) implementation

- CalDAV: MKCALENDAR, PROPFIND, PUT/GET/DELETE events, REPORT calendar-query
- CardDAV: MKCOL, PROPFIND, PUT/GET/DELETE vCards, REPORT addressbook-query
- Fix routing: move CalDAV/CardDAV to top-level merge() with explicit routes
- Fix DB schema: VARCHAR(36) -> UUID for entity IDs, vcard_data -> vcard
- Fix 15 repository stub methods that returned empty results
- Fix vCard parser in ContactStorageAdapter (was hardcoded stub)
- All operations tested end-to-end in Docker (201/207/200/204 as expected)
This commit is contained in:
Dionisio
2026-02-10 18:46:59 +01:00
parent 5bd505ccd7
commit ef9ed2cc31
22 changed files with 3479 additions and 1125 deletions
@@ -324,4 +324,80 @@ impl CalendarUseCase for CalendarService {
self.calendar_storage.get_events_in_time_range(calendar_id, &start, &end).await
}
// ─── User-contextualized variants (for CalDAV protocol handler) ──
async fn create_calendar_for_user(&self, calendar: CreateCalendarDto, user_id: &str) -> Result<CalendarDto, DomainError> {
self.calendar_storage.create_calendar(calendar, user_id).await
}
async fn update_calendar_for_user(&self, calendar_id: &str, update: UpdateCalendarDto, user_id: &str) -> Result<CalendarDto, DomainError> {
let has_access = self.calendar_storage.check_calendar_access(calendar_id, user_id).await?;
if !has_access {
return Err(DomainError::new(ErrorKind::AccessDenied, "Calendar", "You don't have permission to update this calendar"));
}
self.calendar_storage.update_calendar(calendar_id, update).await
}
async fn delete_calendar_for_user(&self, calendar_id: &str, user_id: &str) -> Result<(), DomainError> {
let has_access = self.calendar_storage.check_calendar_access(calendar_id, user_id).await?;
if !has_access {
return Err(DomainError::new(ErrorKind::AccessDenied, "Calendar", "You don't have permission to delete this calendar"));
}
self.calendar_storage.delete_calendar(calendar_id).await
}
async fn get_calendar_for_user(&self, calendar_id: &str, user_id: &str) -> Result<CalendarDto, DomainError> {
let calendar = self.calendar_storage.get_calendar(calendar_id).await?;
let has_access = self.calendar_storage.check_calendar_access(calendar_id, user_id).await?;
if !has_access && !calendar.is_public {
return Err(DomainError::new(ErrorKind::AccessDenied, "Calendar", "You don't have permission to view this calendar"));
}
Ok(calendar)
}
async fn list_my_calendars_for_user(&self, user_id: &str) -> Result<Vec<CalendarDto>, DomainError> {
self.calendar_storage.list_calendars_by_owner(user_id).await
}
async fn list_events_for_user(&self, calendar_id: &str, limit: Option<i64>, offset: Option<i64>, user_id: &str) -> Result<Vec<CalendarEventDto>, DomainError> {
let has_access = self.calendar_storage.check_calendar_access(calendar_id, user_id).await?;
let calendar = self.calendar_storage.get_calendar(calendar_id).await?;
if !has_access && !calendar.is_public {
return Err(DomainError::new(ErrorKind::AccessDenied, "Calendar", "You don't have permission to view events in this calendar"));
}
if limit.is_some() || offset.is_some() {
let limit = limit.unwrap_or(100);
let offset = offset.unwrap_or(0);
self.calendar_storage.list_events_by_calendar_paginated(calendar_id, limit, offset).await
} else {
self.calendar_storage.list_events_by_calendar(calendar_id).await
}
}
async fn get_events_in_range_for_user(&self, calendar_id: &str, start: DateTime<Utc>, end: DateTime<Utc>, user_id: &str) -> Result<Vec<CalendarEventDto>, DomainError> {
let has_access = self.calendar_storage.check_calendar_access(calendar_id, user_id).await?;
let calendar = self.calendar_storage.get_calendar(calendar_id).await?;
if !has_access && !calendar.is_public {
return Err(DomainError::new(ErrorKind::AccessDenied, "Calendar", "You don't have permission to view events in this calendar"));
}
self.calendar_storage.get_events_in_time_range(calendar_id, &start, &end).await
}
async fn create_event_from_ical_for_user(&self, event: CreateEventICalDto, user_id: &str) -> Result<CalendarEventDto, DomainError> {
let has_access = self.calendar_storage.check_calendar_access(&event.calendar_id, user_id).await?;
if !has_access {
return Err(DomainError::new(ErrorKind::AccessDenied, "Calendar", "You don't have permission to add events to this calendar"));
}
self.calendar_storage.create_event_from_ical(event).await
}
async fn delete_event_for_user(&self, event_id: &str, user_id: &str) -> Result<(), DomainError> {
let event = self.calendar_storage.get_event(event_id).await?;
let has_access = self.calendar_storage.check_calendar_access(&event.calendar_id, user_id).await?;
if !has_access {
return Err(DomainError::new(ErrorKind::AccessDenied, "Calendar", "You don't have permission to delete events in this calendar"));
}
self.calendar_storage.delete_event(event_id).await
}
}
@@ -198,6 +198,14 @@ impl FileWritePort for MockFileRepository {
unimplemented!()
}
async fn rename_file(
&self,
_file_id: &str,
_new_name: &str,
) -> std::result::Result<File, DomainError> {
unimplemented!()
}
async fn delete_file(&self, _id: &str) -> std::result::Result<(), DomainError> {
Ok(())
}