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
+72 -2
View File
@@ -560,11 +560,11 @@ impl AppServiceFactory {
core.zip_service = zip_service;
// 10. Assemble final AppState
let app_state = AppState {
let mut app_state = AppState {
core,
repositories: repos,
applications: apps,
db_pool,
db_pool: db_pool.clone(),
auth_service: auth_services,
trash_service,
share_service,
@@ -573,7 +573,53 @@ impl AppServiceFactory {
storage_usage_service,
calendar_service: None,
contact_service: None,
calendar_use_case: None,
addressbook_use_case: None,
contact_use_case: None,
};
// 11. Wire CalDAV/CardDAV services when database is available
if let Some(ref pool) = db_pool {
// CalDAV
let calendar_repo: Arc<dyn crate::domain::repositories::calendar_repository::CalendarRepository> = Arc::new(
crate::infrastructure::repositories::pg::CalendarPgRepository::new(pool.clone())
);
let event_repo: Arc<dyn crate::domain::repositories::calendar_event_repository::CalendarEventRepository> = Arc::new(
crate::infrastructure::repositories::pg::CalendarEventPgRepository::new(pool.clone())
);
let calendar_storage = Arc::new(
crate::infrastructure::adapters::calendar_storage_adapter::CalendarStorageAdapter::new(
calendar_repo,
event_repo,
)
);
let calendar_service = Arc::new(
crate::application::services::calendar_service::CalendarService::new(calendar_storage)
);
app_state.calendar_use_case = Some(calendar_service as Arc<dyn crate::application::ports::calendar_ports::CalendarUseCase>);
// CardDAV
let address_book_repo: Arc<dyn crate::domain::repositories::address_book_repository::AddressBookRepository> = Arc::new(
crate::infrastructure::repositories::pg::AddressBookPgRepository::new(pool.clone())
);
let contact_repo: Arc<dyn crate::domain::repositories::contact_repository::ContactRepository> = Arc::new(
crate::infrastructure::repositories::pg::ContactPgRepository::new(pool.clone())
);
let group_repo: Arc<dyn crate::domain::repositories::contact_repository::ContactGroupRepository> = Arc::new(
crate::infrastructure::repositories::pg::ContactGroupPgRepository::new(pool.clone())
);
let contact_storage = Arc::new(
crate::infrastructure::adapters::contact_storage_adapter::ContactStorageAdapter::new(
address_book_repo,
contact_repo,
group_repo,
)
);
app_state.addressbook_use_case = Some(contact_storage.clone() as Arc<dyn crate::application::ports::carddav_ports::AddressBookUseCase>);
app_state.contact_use_case = Some(contact_storage as Arc<dyn crate::application::ports::carddav_ports::ContactUseCase>);
tracing::info!("CalDAV and CardDAV services initialized with PostgreSQL repositories");
}
Ok(app_state)
}
@@ -650,6 +696,9 @@ pub struct AppState {
pub storage_usage_service: Option<Arc<dyn crate::application::ports::storage_ports::StorageUsagePort>>,
pub calendar_service: Option<Arc<dyn crate::application::ports::storage_ports::StorageUseCase>>,
pub contact_service: Option<Arc<dyn crate::application::ports::storage_ports::StorageUseCase>>,
pub calendar_use_case: Option<Arc<dyn crate::application::ports::calendar_ports::CalendarUseCase>>,
pub addressbook_use_case: Option<Arc<dyn crate::application::ports::carddav_ports::AddressBookUseCase>>,
pub contact_use_case: Option<Arc<dyn crate::application::ports::carddav_ports::ContactUseCase>>,
}
impl Default for AppState {
@@ -785,6 +834,9 @@ impl Default for AppState {
storage_usage_service: None,
calendar_service: None,
contact_service: None,
calendar_use_case: None,
addressbook_use_case: None,
contact_use_case: None,
}
}
}
@@ -808,6 +860,9 @@ impl AppState {
storage_usage_service: None,
calendar_service: None,
contact_service: None,
calendar_use_case: None,
addressbook_use_case: None,
contact_use_case: None,
}
}
@@ -910,6 +965,21 @@ impl AppState {
self
}
pub fn with_calendar_use_case(mut self, calendar_use_case: Arc<dyn crate::application::ports::calendar_ports::CalendarUseCase>) -> Self {
self.calendar_use_case = Some(calendar_use_case);
self
}
pub fn with_addressbook_use_case(mut self, addressbook_use_case: Arc<dyn crate::application::ports::carddav_ports::AddressBookUseCase>) -> Self {
self.addressbook_use_case = Some(addressbook_use_case);
self
}
pub fn with_contact_use_case(mut self, contact_use_case: Arc<dyn crate::application::ports::carddav_ports::ContactUseCase>) -> Self {
self.contact_use_case = Some(contact_use_case);
self
}
pub fn with_zip_service(mut self, zip_service: Arc<dyn ZipPort>) -> Self {
self.core.zip_service = zip_service;
self