refactoring hexagonal and clean architecture

This commit is contained in:
Diocrafts
2026-02-08 13:40:23 +01:00
parent 3e4fb67c19
commit a82faa5eaf
101 changed files with 7433 additions and 9721 deletions
@@ -58,12 +58,12 @@ impl ContactStorageAdapter {
.ok_or_else(|| DomainError::new(ErrorKind::NotFound, "AddressBook", "Address book not found"))?;
// Check if user is owner
if address_book.owner_id == user_id {
if address_book.owner_id() == user_id {
return Ok(address_book);
}
// Check if address book is public
if address_book.is_public {
if address_book.is_public() {
return Ok(address_book);
}
@@ -84,7 +84,7 @@ impl ContactStorageAdapter {
.ok_or_else(|| DomainError::new(ErrorKind::NotFound, "AddressBook", "Address book not found"))?;
// Owner always has write access
if address_book.owner_id == user_id {
if address_book.owner_id() == user_id {
return Ok(address_book);
}
@@ -132,41 +132,41 @@ impl ContactStorageAdapter {
fn generate_vcard(contact: &Contact) -> String {
let mut vcard = String::from("BEGIN:VCARD\nVERSION:3.0\n");
if let Some(ref full_name) = contact.full_name {
if let Some(full_name) = contact.full_name() {
vcard.push_str(&format!("FN:{}\n", full_name));
}
if contact.first_name.is_some() || contact.last_name.is_some() {
let last = contact.last_name.as_deref().unwrap_or("");
let first = contact.first_name.as_deref().unwrap_or("");
if contact.first_name().is_some() || contact.last_name().is_some() {
let last = contact.last_name().unwrap_or("");
let first = contact.first_name().unwrap_or("");
vcard.push_str(&format!("N:{};{};;;\n", last, first));
}
if let Some(ref nickname) = contact.nickname {
if let Some(nickname) = contact.nickname() {
vcard.push_str(&format!("NICKNAME:{}\n", nickname));
}
for email in &contact.email {
for email in contact.email() {
vcard.push_str(&format!("EMAIL;TYPE={}:{}\n", email.r#type.to_uppercase(), email.email));
}
for phone in &contact.phone {
for phone in contact.phone() {
vcard.push_str(&format!("TEL;TYPE={}:{}\n", phone.r#type.to_uppercase(), phone.number));
}
if let Some(ref org) = contact.organization {
if let Some(org) = contact.organization() {
vcard.push_str(&format!("ORG:{}\n", org));
}
if let Some(ref title) = contact.title {
if let Some(title) = contact.title() {
vcard.push_str(&format!("TITLE:{}\n", title));
}
if let Some(ref notes) = contact.notes {
if let Some(notes) = contact.notes() {
vcard.push_str(&format!("NOTE:{}\n", notes));
}
vcard.push_str(&format!("UID:{}\n", contact.uid));
vcard.push_str(&format!("UID:{}\n", contact.uid()));
vcard.push_str("END:VCARD\n");
vcard
@@ -176,16 +176,13 @@ impl ContactStorageAdapter {
#[async_trait]
impl AddressBookUseCase for ContactStorageAdapter {
async fn create_address_book(&self, dto: CreateAddressBookDto) -> Result<AddressBookDto, DomainError> {
let address_book = AddressBook {
id: Uuid::new_v4(),
name: dto.name,
owner_id: dto.owner_id,
description: dto.description,
color: dto.color,
is_public: dto.is_public.unwrap_or(false),
created_at: chrono::Utc::now(),
updated_at: chrono::Utc::now(),
};
let address_book = AddressBook::new(
dto.name,
dto.owner_id,
dto.description,
dto.color,
dto.is_public.unwrap_or(false),
);
let created = self.address_book_repository.create_address_book(address_book).await?;
Ok(AddressBookDto::from(created))
@@ -198,18 +195,18 @@ impl AddressBookUseCase for ContactStorageAdapter {
let mut address_book = self.check_write_access(&uuid, &update.user_id).await?;
if let Some(name) = update.name {
address_book.name = name;
address_book.set_name(name);
}
if let Some(description) = update.description {
address_book.description = Some(description);
address_book.set_description(Some(description));
}
if let Some(color) = update.color {
address_book.color = Some(color);
address_book.set_color(Some(color));
}
if let Some(is_public) = update.is_public {
address_book.is_public = is_public;
address_book.set_is_public(is_public);
}
address_book.updated_at = chrono::Utc::now();
address_book.set_updated_at(chrono::Utc::now());
let updated = self.address_book_repository.update_address_book(address_book).await?;
Ok(AddressBookDto::from(updated))
@@ -224,7 +221,7 @@ impl AddressBookUseCase for ContactStorageAdapter {
.await?
.ok_or_else(|| DomainError::new(ErrorKind::NotFound, "AddressBook", "Address book not found"))?;
if address_book.owner_id != user_id {
if address_book.owner_id() != user_id {
return Err(DomainError::new(ErrorKind::AccessDenied, "AddressBook", "Only owner can delete address book"));
}
@@ -261,7 +258,7 @@ impl AddressBookUseCase for ContactStorageAdapter {
.await?
.ok_or_else(|| DomainError::new(ErrorKind::NotFound, "AddressBook", "Address book not found"))?;
if address_book.owner_id != user_id {
if address_book.owner_id() != user_id {
return Err(DomainError::new(ErrorKind::AccessDenied, "AddressBook", "Only owner can share"));
}
@@ -277,7 +274,7 @@ impl AddressBookUseCase for ContactStorageAdapter {
.await?
.ok_or_else(|| DomainError::new(ErrorKind::NotFound, "AddressBook", "Address book not found"))?;
if address_book.owner_id != user_id {
if address_book.owner_id() != user_id {
return Err(DomainError::new(ErrorKind::AccessDenied, "AddressBook", "Only owner can unshare"));
}
@@ -293,7 +290,7 @@ impl AddressBookUseCase for ContactStorageAdapter {
.await?
.ok_or_else(|| DomainError::new(ErrorKind::NotFound, "AddressBook", "Address book not found"))?;
if address_book.owner_id != user_id {
if address_book.owner_id() != user_id {
return Err(DomainError::new(ErrorKind::AccessDenied, "AddressBook", "Only owner can view shares"));
}
@@ -309,34 +306,35 @@ impl ContactUseCase for ContactStorageAdapter {
// Check write access
self.check_write_access(&address_book_id, &dto.user_id).await?;
let contact = Contact {
id: Uuid::new_v4(),
let now = chrono::Utc::now();
let mut contact = Contact::from_raw(
Uuid::new_v4(),
address_book_id,
uid: format!("{}@oxicloud", Uuid::new_v4()),
full_name: dto.full_name,
first_name: dto.first_name,
last_name: dto.last_name,
nickname: dto.nickname,
email: dto.email.into_iter().map(Self::dto_to_email).collect(),
phone: dto.phone.into_iter().map(Self::dto_to_phone).collect(),
address: dto.address.into_iter().map(Self::dto_to_address).collect(),
organization: dto.organization,
title: dto.title,
notes: dto.notes,
photo_url: dto.photo_url,
birthday: dto.birthday,
anniversary: dto.anniversary,
vcard: String::new(),
etag: Uuid::new_v4().to_string(),
created_at: chrono::Utc::now(),
updated_at: chrono::Utc::now(),
};
format!("{}@oxicloud", Uuid::new_v4()),
dto.full_name,
dto.first_name,
dto.last_name,
dto.nickname,
dto.email.into_iter().map(Self::dto_to_email).collect(),
dto.phone.into_iter().map(Self::dto_to_phone).collect(),
dto.address.into_iter().map(Self::dto_to_address).collect(),
dto.organization,
dto.title,
dto.notes,
dto.photo_url,
dto.birthday,
dto.anniversary,
String::new(),
Uuid::new_v4().to_string(),
now,
now,
);
// Generate vCard
let mut contact_with_vcard = contact;
contact_with_vcard.vcard = Self::generate_vcard(&contact_with_vcard);
let vcard = Self::generate_vcard(&contact);
contact.set_vcard(vcard);
let created = self.contact_repository.create_contact(contact_with_vcard).await?;
let created = self.contact_repository.create_contact(contact).await?;
Ok(ContactDto::from(created))
}
@@ -347,28 +345,29 @@ impl ContactUseCase for ContactStorageAdapter {
self.check_write_access(&address_book_id, &dto.user_id).await?;
// Parse vCard - for now, create a basic contact with the raw vCard
let contact = Contact {
id: Uuid::new_v4(),
let now = chrono::Utc::now();
let contact = Contact::from_raw(
Uuid::new_v4(),
address_book_id,
uid: format!("{}@oxicloud", Uuid::new_v4()),
full_name: Some("Imported Contact".to_string()),
first_name: None,
last_name: None,
nickname: None,
email: Vec::new(),
phone: Vec::new(),
address: Vec::new(),
organization: None,
title: None,
notes: None,
photo_url: None,
birthday: None,
anniversary: None,
vcard: dto.vcard,
etag: Uuid::new_v4().to_string(),
created_at: chrono::Utc::now(),
updated_at: chrono::Utc::now(),
};
format!("{}@oxicloud", Uuid::new_v4()),
Some("Imported Contact".to_string()),
None,
None,
None,
Vec::new(),
Vec::new(),
Vec::new(),
None,
None,
None,
None,
None,
None,
dto.vcard,
Uuid::new_v4().to_string(),
now,
now,
);
let created = self.contact_repository.create_contact(contact).await?;
Ok(ContactDto::from(created))
@@ -383,51 +382,52 @@ impl ContactUseCase for ContactStorageAdapter {
.ok_or_else(|| DomainError::new(ErrorKind::NotFound, "Contact", "Contact not found"))?;
// Check write access to the address book
self.check_write_access(&contact.address_book_id, &update.user_id).await?;
self.check_write_access(contact.address_book_id(), &update.user_id).await?;
if let Some(full_name) = update.full_name {
contact.full_name = Some(full_name);
contact.set_full_name(Some(full_name));
}
if let Some(first_name) = update.first_name {
contact.first_name = Some(first_name);
contact.set_first_name(Some(first_name));
}
if let Some(last_name) = update.last_name {
contact.last_name = Some(last_name);
contact.set_last_name(Some(last_name));
}
if let Some(nickname) = update.nickname {
contact.nickname = Some(nickname);
contact.set_nickname(Some(nickname));
}
if let Some(emails) = update.email {
contact.email = emails.into_iter().map(Self::dto_to_email).collect();
contact.set_email(emails.into_iter().map(Self::dto_to_email).collect());
}
if let Some(phones) = update.phone {
contact.phone = phones.into_iter().map(Self::dto_to_phone).collect();
contact.set_phone(phones.into_iter().map(Self::dto_to_phone).collect());
}
if let Some(addresses) = update.address {
contact.address = addresses.into_iter().map(Self::dto_to_address).collect();
contact.set_address(addresses.into_iter().map(Self::dto_to_address).collect());
}
if let Some(organization) = update.organization {
contact.organization = Some(organization);
contact.set_organization(Some(organization));
}
if let Some(title) = update.title {
contact.title = Some(title);
contact.set_title(Some(title));
}
if let Some(notes) = update.notes {
contact.notes = Some(notes);
contact.set_notes(Some(notes));
}
if let Some(photo_url) = update.photo_url {
contact.photo_url = Some(photo_url);
contact.set_photo_url(Some(photo_url));
}
if let Some(birthday) = update.birthday {
contact.birthday = Some(birthday);
contact.set_birthday(Some(birthday));
}
if let Some(anniversary) = update.anniversary {
contact.anniversary = Some(anniversary);
contact.set_anniversary(Some(anniversary));
}
contact.updated_at = chrono::Utc::now();
contact.etag = Uuid::new_v4().to_string();
contact.vcard = Self::generate_vcard(&contact);
contact.set_updated_at(chrono::Utc::now());
contact.set_etag(Uuid::new_v4().to_string());
let vcard = Self::generate_vcard(&contact);
contact.set_vcard(vcard);
let updated = self.contact_repository.update_contact(contact).await?;
Ok(ContactDto::from(updated))
@@ -442,7 +442,7 @@ impl ContactUseCase for ContactStorageAdapter {
.ok_or_else(|| DomainError::new(ErrorKind::NotFound, "Contact", "Contact not found"))?;
// Check write access
self.check_write_access(&contact.address_book_id, user_id).await?;
self.check_write_access(contact.address_book_id(), user_id).await?;
self.contact_repository.delete_contact(&uuid).await
}
@@ -456,7 +456,7 @@ impl ContactUseCase for ContactStorageAdapter {
.ok_or_else(|| DomainError::new(ErrorKind::NotFound, "Contact", "Contact not found"))?;
// Check read access
self.check_address_book_access(&contact.address_book_id, user_id).await?;
self.check_address_book_access(contact.address_book_id(), user_id).await?;
Ok(ContactDto::from(contact))
}
@@ -487,13 +487,10 @@ impl ContactUseCase for ContactStorageAdapter {
// Check write access
self.check_write_access(&address_book_id, &dto.user_id).await?;
let group = ContactGroup {
id: Uuid::new_v4(),
let group = ContactGroup::new(
address_book_id,
name: dto.name,
created_at: chrono::Utc::now(),
updated_at: chrono::Utc::now(),
};
dto.name,
);
let created = self.group_repository.create_group(group).await?;
Ok(ContactGroupDto::from(created))
@@ -508,10 +505,10 @@ impl ContactUseCase for ContactStorageAdapter {
.ok_or_else(|| DomainError::new(ErrorKind::NotFound, "ContactGroup", "Group not found"))?;
// Check write access
self.check_write_access(&group.address_book_id, &update.user_id).await?;
self.check_write_access(group.address_book_id(), &update.user_id).await?;
group.name = update.name;
group.updated_at = chrono::Utc::now();
group.set_name(update.name);
group.set_updated_at(chrono::Utc::now());
let updated = self.group_repository.update_group(group).await?;
Ok(ContactGroupDto::from(updated))
@@ -526,7 +523,7 @@ impl ContactUseCase for ContactStorageAdapter {
.ok_or_else(|| DomainError::new(ErrorKind::NotFound, "ContactGroup", "Group not found"))?;
// Check write access
self.check_write_access(&group.address_book_id, user_id).await?;
self.check_write_access(group.address_book_id(), user_id).await?;
self.group_repository.delete_group(&uuid).await
}
@@ -540,7 +537,7 @@ impl ContactUseCase for ContactStorageAdapter {
.ok_or_else(|| DomainError::new(ErrorKind::NotFound, "ContactGroup", "Group not found"))?;
// Check read access
self.check_address_book_access(&group.address_book_id, user_id).await?;
self.check_address_book_access(group.address_book_id(), user_id).await?;
Ok(ContactGroupDto::from(group))
}
@@ -565,7 +562,7 @@ impl ContactUseCase for ContactStorageAdapter {
.ok_or_else(|| DomainError::new(ErrorKind::NotFound, "ContactGroup", "Group not found"))?;
// Check write access
self.check_write_access(&group.address_book_id, user_id).await?;
self.check_write_access(group.address_book_id(), user_id).await?;
self.group_repository.add_contact_to_group(&group_id, &contact_id).await
}
@@ -580,7 +577,7 @@ impl ContactUseCase for ContactStorageAdapter {
.ok_or_else(|| DomainError::new(ErrorKind::NotFound, "ContactGroup", "Group not found"))?;
// Check write access
self.check_write_access(&group.address_book_id, user_id).await?;
self.check_write_access(group.address_book_id(), user_id).await?;
self.group_repository.remove_contact_from_group(&group_id, &contact_id).await
}
@@ -594,7 +591,7 @@ impl ContactUseCase for ContactStorageAdapter {
.ok_or_else(|| DomainError::new(ErrorKind::NotFound, "ContactGroup", "Group not found"))?;
// Check read access
self.check_address_book_access(&group.address_book_id, user_id).await?;
self.check_address_book_access(group.address_book_id(), user_id).await?;
let contacts = self.group_repository.get_contacts_in_group(&uuid).await?;
Ok(contacts.into_iter().map(ContactDto::from).collect())
@@ -609,7 +606,7 @@ impl ContactUseCase for ContactStorageAdapter {
.ok_or_else(|| DomainError::new(ErrorKind::NotFound, "Contact", "Contact not found"))?;
// Check read access
self.check_address_book_access(&contact.address_book_id, user_id).await?;
self.check_address_book_access(contact.address_book_id(), user_id).await?;
let groups = self.group_repository.get_groups_for_contact(&uuid).await?;
Ok(groups.into_iter().map(ContactGroupDto::from).collect())
@@ -624,9 +621,9 @@ impl ContactUseCase for ContactStorageAdapter {
.ok_or_else(|| DomainError::new(ErrorKind::NotFound, "Contact", "Contact not found"))?;
// Check read access
self.check_address_book_access(&contact.address_book_id, user_id).await?;
self.check_address_book_access(contact.address_book_id(), user_id).await?;
Ok(contact.vcard)
Ok(contact.vcard().to_string())
}
async fn get_contacts_as_vcards(&self, address_book_id: &str, user_id: &str) -> Result<Vec<(String, String)>, DomainError> {
@@ -639,7 +636,7 @@ impl ContactUseCase for ContactStorageAdapter {
Ok(contacts
.into_iter()
.map(|c| (c.id.to_string(), c.vcard))
.map(|c| (c.id().to_string(), c.vcard().to_string()))
.collect())
}
}