2025-04-13 01:04:04 +02:00
|
|
|
use chrono::Utc;
|
2026-02-02 23:56:40 +01:00
|
|
|
use sqlx::{PgPool, Row, types::Uuid};
|
2025-04-13 01:04:04 +02:00
|
|
|
use std::sync::Arc;
|
|
|
|
|
|
2026-02-02 23:56:40 +01:00
|
|
|
use crate::common::errors::DomainError;
|
2026-02-14 01:29:34 +01:00
|
|
|
use crate::domain::entities::calendar::Calendar;
|
|
|
|
|
use crate::domain::repositories::calendar_repository::{
|
|
|
|
|
CalendarRepository, CalendarRepositoryResult,
|
|
|
|
|
};
|
2025-04-13 01:04:04 +02:00
|
|
|
|
|
|
|
|
pub struct CalendarPgRepository {
|
|
|
|
|
pool: Arc<PgPool>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl CalendarPgRepository {
|
|
|
|
|
pub fn new(pool: Arc<PgPool>) -> Self {
|
|
|
|
|
Self { pool }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl CalendarRepository for CalendarPgRepository {
|
|
|
|
|
async fn create_calendar(&self, calendar: Calendar) -> CalendarRepositoryResult<Calendar> {
|
|
|
|
|
let row = sqlx::query(
|
|
|
|
|
r#"
|
|
|
|
|
INSERT INTO caldav.calendars (id, name, owner_id, description, color, is_public, created_at, updated_at)
|
|
|
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
|
|
|
|
RETURNING id, name, owner_id, description, color, is_public, created_at, updated_at
|
|
|
|
|
"#
|
|
|
|
|
)
|
|
|
|
|
.bind(calendar.id())
|
|
|
|
|
.bind(calendar.name())
|
|
|
|
|
.bind(calendar.owner_id())
|
|
|
|
|
.bind(calendar.description())
|
|
|
|
|
.bind(calendar.color())
|
2026-02-12 09:41:25 +01:00
|
|
|
.bind(false) // is_public doesn't exist as a field
|
2025-04-13 01:04:04 +02:00
|
|
|
.bind(calendar.created_at())
|
|
|
|
|
.bind(calendar.updated_at())
|
|
|
|
|
.fetch_one(&*self.pool)
|
|
|
|
|
.await
|
|
|
|
|
.map_err(|e| DomainError::database_error(format!("Failed to create calendar: {}", e)))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
// Build the Calendar object using its with_id constructor
|
2025-04-13 01:04:04 +02:00
|
|
|
let result = Calendar::with_id(
|
|
|
|
|
row.get("id"),
|
|
|
|
|
row.get("name"),
|
|
|
|
|
row.get("owner_id"),
|
|
|
|
|
row.get("description"),
|
|
|
|
|
row.get("color"),
|
|
|
|
|
row.get("created_at"),
|
|
|
|
|
row.get("updated_at"),
|
2026-02-14 01:29:34 +01:00
|
|
|
)
|
|
|
|
|
.map_err(|e| {
|
|
|
|
|
DomainError::database_error(format!("Failed to create calendar object: {}", e))
|
|
|
|
|
})?;
|
2025-04-13 01:04:04 +02:00
|
|
|
|
|
|
|
|
Ok(result)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fn update_calendar(&self, calendar: Calendar) -> CalendarRepositoryResult<Calendar> {
|
|
|
|
|
let now = Utc::now();
|
|
|
|
|
let row = sqlx::query(
|
|
|
|
|
r#"
|
|
|
|
|
UPDATE caldav.calendars
|
|
|
|
|
SET name = $1, description = $2, color = $3, is_public = $4, updated_at = $5
|
|
|
|
|
WHERE id = $6
|
|
|
|
|
RETURNING id, name, owner_id, description, color, is_public, created_at, updated_at
|
2026-02-14 01:29:34 +01:00
|
|
|
"#,
|
2025-04-13 01:04:04 +02:00
|
|
|
)
|
|
|
|
|
.bind(calendar.name())
|
|
|
|
|
.bind(calendar.description())
|
|
|
|
|
.bind(calendar.color())
|
2026-02-12 09:41:25 +01:00
|
|
|
.bind(false) // is_public doesn't exist as a field
|
2025-04-13 01:04:04 +02:00
|
|
|
.bind(now)
|
|
|
|
|
.bind(calendar.id())
|
|
|
|
|
.fetch_one(&*self.pool)
|
|
|
|
|
.await
|
|
|
|
|
.map_err(|e| DomainError::database_error(format!("Failed to update calendar: {}", e)))?;
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
// Build the Calendar object using its with_id constructor
|
2025-04-13 01:04:04 +02:00
|
|
|
let result = Calendar::with_id(
|
|
|
|
|
row.get("id"),
|
|
|
|
|
row.get("name"),
|
|
|
|
|
row.get("owner_id"),
|
|
|
|
|
row.get("description"),
|
|
|
|
|
row.get("color"),
|
|
|
|
|
row.get("created_at"),
|
|
|
|
|
row.get("updated_at"),
|
2026-02-14 01:29:34 +01:00
|
|
|
)
|
|
|
|
|
.map_err(|e| {
|
|
|
|
|
DomainError::database_error(format!("Failed to create calendar object: {}", e))
|
|
|
|
|
})?;
|
2025-04-13 01:04:04 +02:00
|
|
|
|
|
|
|
|
Ok(result)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fn delete_calendar(&self, id: &Uuid) -> CalendarRepositoryResult<()> {
|
|
|
|
|
sqlx::query(
|
|
|
|
|
r#"
|
|
|
|
|
DELETE FROM caldav.calendars
|
|
|
|
|
WHERE id = $1
|
2026-02-14 01:29:34 +01:00
|
|
|
"#,
|
2025-04-13 01:04:04 +02:00
|
|
|
)
|
|
|
|
|
.bind(id)
|
|
|
|
|
.execute(&*self.pool)
|
|
|
|
|
.await
|
|
|
|
|
.map_err(|e| DomainError::database_error(format!("Failed to delete calendar: {}", e)))?;
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fn find_calendar_by_id(&self, id: &Uuid) -> CalendarRepositoryResult<Calendar> {
|
|
|
|
|
let row = sqlx::query(
|
|
|
|
|
r#"
|
|
|
|
|
SELECT id, name, owner_id, description, color, is_public, created_at, updated_at
|
|
|
|
|
FROM caldav.calendars
|
|
|
|
|
WHERE id = $1
|
2026-02-14 01:29:34 +01:00
|
|
|
"#,
|
2025-04-13 01:04:04 +02:00
|
|
|
)
|
|
|
|
|
.bind(id)
|
|
|
|
|
.fetch_optional(&*self.pool)
|
|
|
|
|
.await
|
|
|
|
|
.map_err(|e| DomainError::database_error(format!("Failed to get calendar by id: {}", e)))?
|
|
|
|
|
.ok_or_else(|| DomainError::not_found("Calendar", id.to_string()))?;
|
|
|
|
|
|
|
|
|
|
let calendar = Calendar::with_id(
|
|
|
|
|
row.get("id"),
|
|
|
|
|
row.get("name"),
|
|
|
|
|
row.get("owner_id"),
|
|
|
|
|
row.get("description"),
|
|
|
|
|
row.get("color"),
|
|
|
|
|
row.get("created_at"),
|
|
|
|
|
row.get("updated_at"),
|
2026-02-14 01:29:34 +01:00
|
|
|
)
|
|
|
|
|
.map_err(|e| {
|
|
|
|
|
DomainError::database_error(format!("Failed to create calendar object: {}", e))
|
|
|
|
|
})?;
|
2025-04-13 01:04:04 +02:00
|
|
|
|
|
|
|
|
Ok(calendar)
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-14 01:29:34 +01:00
|
|
|
async fn list_calendars_by_owner(
|
|
|
|
|
&self,
|
2026-03-07 14:59:32 +01:00
|
|
|
owner_id: Uuid,
|
2026-02-14 01:29:34 +01:00
|
|
|
) -> CalendarRepositoryResult<Vec<Calendar>> {
|
2025-04-13 01:04:04 +02:00
|
|
|
let rows = sqlx::query(
|
|
|
|
|
r#"
|
|
|
|
|
SELECT id, name, owner_id, description, color, is_public, created_at, updated_at
|
|
|
|
|
FROM caldav.calendars
|
|
|
|
|
WHERE owner_id = $1
|
|
|
|
|
ORDER BY name
|
2026-02-14 01:29:34 +01:00
|
|
|
"#,
|
2025-04-13 01:04:04 +02:00
|
|
|
)
|
|
|
|
|
.bind(owner_id)
|
|
|
|
|
.fetch_all(&*self.pool)
|
|
|
|
|
.await
|
2026-02-14 01:29:34 +01:00
|
|
|
.map_err(|e| {
|
|
|
|
|
DomainError::database_error(format!("Failed to get calendars by owner: {}", e))
|
|
|
|
|
})?;
|
2025-04-13 01:04:04 +02:00
|
|
|
|
|
|
|
|
let mut calendars = Vec::new();
|
|
|
|
|
for row in rows {
|
|
|
|
|
let calendar = Calendar::with_id(
|
|
|
|
|
row.get("id"),
|
|
|
|
|
row.get("name"),
|
|
|
|
|
row.get("owner_id"),
|
|
|
|
|
row.get("description"),
|
|
|
|
|
row.get("color"),
|
|
|
|
|
row.get("created_at"),
|
|
|
|
|
row.get("updated_at"),
|
2026-02-14 01:29:34 +01:00
|
|
|
)
|
|
|
|
|
.map_err(|e| {
|
|
|
|
|
DomainError::database_error(format!("Failed to create calendar object: {}", e))
|
|
|
|
|
})?;
|
2025-04-13 01:04:04 +02:00
|
|
|
calendars.push(calendar);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(calendars)
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-14 01:29:34 +01:00
|
|
|
async fn find_calendar_by_name_and_owner(
|
|
|
|
|
&self,
|
|
|
|
|
name: &str,
|
2026-03-07 14:59:32 +01:00
|
|
|
owner_id: Uuid,
|
2026-02-14 01:29:34 +01:00
|
|
|
) -> CalendarRepositoryResult<Calendar> {
|
2025-04-13 01:04:04 +02:00
|
|
|
let row = sqlx::query(
|
|
|
|
|
r#"
|
|
|
|
|
SELECT id, name, owner_id, description, color, is_public, created_at, updated_at
|
|
|
|
|
FROM caldav.calendars
|
|
|
|
|
WHERE name = $1 AND owner_id = $2
|
2026-02-14 01:29:34 +01:00
|
|
|
"#,
|
2025-04-13 01:04:04 +02:00
|
|
|
)
|
|
|
|
|
.bind(name)
|
|
|
|
|
.bind(owner_id)
|
|
|
|
|
.fetch_optional(&*self.pool)
|
|
|
|
|
.await
|
2026-02-14 01:29:34 +01:00
|
|
|
.map_err(|e| {
|
|
|
|
|
DomainError::database_error(format!("Failed to find calendar by name and owner: {}", e))
|
|
|
|
|
})?
|
|
|
|
|
.ok_or_else(|| {
|
|
|
|
|
DomainError::not_found("Calendar", format!("{} (owned by {})", name, owner_id))
|
|
|
|
|
})?;
|
2025-04-13 01:04:04 +02:00
|
|
|
|
|
|
|
|
let calendar = Calendar::with_id(
|
|
|
|
|
row.get("id"),
|
|
|
|
|
row.get("name"),
|
|
|
|
|
row.get("owner_id"),
|
|
|
|
|
row.get("description"),
|
|
|
|
|
row.get("color"),
|
|
|
|
|
row.get("created_at"),
|
|
|
|
|
row.get("updated_at"),
|
2026-02-14 01:29:34 +01:00
|
|
|
)
|
|
|
|
|
.map_err(|e| {
|
|
|
|
|
DomainError::database_error(format!("Failed to create calendar object: {}", e))
|
|
|
|
|
})?;
|
2025-04-13 01:04:04 +02:00
|
|
|
|
|
|
|
|
Ok(calendar)
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-14 01:29:34 +01:00
|
|
|
async fn list_public_calendars(
|
|
|
|
|
&self,
|
|
|
|
|
limit: i64,
|
|
|
|
|
offset: i64,
|
|
|
|
|
) -> CalendarRepositoryResult<Vec<Calendar>> {
|
2025-04-13 01:04:04 +02:00
|
|
|
let rows = sqlx::query(
|
|
|
|
|
r#"
|
|
|
|
|
SELECT id, name, owner_id, description, color, is_public, created_at, updated_at
|
|
|
|
|
FROM caldav.calendars
|
|
|
|
|
WHERE is_public = true
|
|
|
|
|
ORDER BY name
|
|
|
|
|
LIMIT $1 OFFSET $2
|
2026-02-14 01:29:34 +01:00
|
|
|
"#,
|
2025-04-13 01:04:04 +02:00
|
|
|
)
|
|
|
|
|
.bind(limit)
|
|
|
|
|
.bind(offset)
|
|
|
|
|
.fetch_all(&*self.pool)
|
|
|
|
|
.await
|
2026-02-14 01:29:34 +01:00
|
|
|
.map_err(|e| {
|
|
|
|
|
DomainError::database_error(format!("Failed to get public calendars: {}", e))
|
|
|
|
|
})?;
|
2025-04-13 01:04:04 +02:00
|
|
|
|
|
|
|
|
let mut calendars = Vec::new();
|
|
|
|
|
for row in rows {
|
|
|
|
|
let calendar = Calendar::with_id(
|
|
|
|
|
row.get("id"),
|
|
|
|
|
row.get("name"),
|
|
|
|
|
row.get("owner_id"),
|
|
|
|
|
row.get("description"),
|
2026-02-14 01:29:34 +01:00
|
|
|
row.get("color"),
|
2025-04-13 01:04:04 +02:00
|
|
|
row.get("created_at"),
|
|
|
|
|
row.get("updated_at"),
|
2026-02-14 01:29:34 +01:00
|
|
|
)
|
|
|
|
|
.map_err(|e| {
|
|
|
|
|
DomainError::database_error(format!("Failed to create calendar object: {}", e))
|
|
|
|
|
})?;
|
2025-04-13 01:04:04 +02:00
|
|
|
calendars.push(calendar);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(calendars)
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-14 01:29:34 +01:00
|
|
|
async fn get_calendar_property(
|
|
|
|
|
&self,
|
|
|
|
|
calendar_id: &Uuid,
|
|
|
|
|
property_name: &str,
|
|
|
|
|
) -> CalendarRepositoryResult<Option<String>> {
|
2025-04-13 01:04:04 +02:00
|
|
|
let row = sqlx::query(
|
|
|
|
|
r#"
|
|
|
|
|
SELECT value
|
|
|
|
|
FROM caldav.calendar_properties
|
|
|
|
|
WHERE calendar_id = $1 AND name = $2
|
2026-02-14 01:29:34 +01:00
|
|
|
"#,
|
2025-04-13 01:04:04 +02:00
|
|
|
)
|
|
|
|
|
.bind(calendar_id)
|
|
|
|
|
.bind(property_name)
|
|
|
|
|
.fetch_optional(&*self.pool)
|
|
|
|
|
.await
|
2026-02-14 01:29:34 +01:00
|
|
|
.map_err(|e| {
|
|
|
|
|
DomainError::database_error(format!("Failed to get calendar property: {}", e))
|
|
|
|
|
})?;
|
|
|
|
|
|
2025-04-13 01:04:04 +02:00
|
|
|
Ok(row.map(|r| r.get("value")))
|
|
|
|
|
}
|
2026-02-14 01:29:34 +01:00
|
|
|
|
|
|
|
|
async fn set_calendar_property(
|
|
|
|
|
&self,
|
|
|
|
|
calendar_id: &Uuid,
|
|
|
|
|
property_name: &str,
|
|
|
|
|
property_value: &str,
|
|
|
|
|
) -> CalendarRepositoryResult<()> {
|
2025-04-13 01:04:04 +02:00
|
|
|
sqlx::query(
|
|
|
|
|
r#"
|
|
|
|
|
INSERT INTO caldav.calendar_properties (calendar_id, name, value)
|
|
|
|
|
VALUES ($1, $2, $3)
|
|
|
|
|
ON CONFLICT (calendar_id, name) DO UPDATE SET value = $3
|
2026-02-14 01:29:34 +01:00
|
|
|
"#,
|
2025-04-13 01:04:04 +02:00
|
|
|
)
|
|
|
|
|
.bind(calendar_id)
|
|
|
|
|
.bind(property_name)
|
|
|
|
|
.bind(property_value)
|
|
|
|
|
.execute(&*self.pool)
|
|
|
|
|
.await
|
2026-02-14 01:29:34 +01:00
|
|
|
.map_err(|e| {
|
|
|
|
|
DomainError::database_error(format!("Failed to set calendar property: {}", e))
|
|
|
|
|
})?;
|
|
|
|
|
|
2025-04-13 01:04:04 +02:00
|
|
|
Ok(())
|
|
|
|
|
}
|
2026-02-14 01:29:34 +01:00
|
|
|
|
|
|
|
|
async fn remove_calendar_property(
|
|
|
|
|
&self,
|
|
|
|
|
calendar_id: &Uuid,
|
|
|
|
|
property_name: &str,
|
|
|
|
|
) -> CalendarRepositoryResult<()> {
|
2025-04-13 01:04:04 +02:00
|
|
|
sqlx::query(
|
|
|
|
|
r#"
|
|
|
|
|
DELETE FROM caldav.calendar_properties
|
|
|
|
|
WHERE calendar_id = $1 AND name = $2
|
2026-02-14 01:29:34 +01:00
|
|
|
"#,
|
2025-04-13 01:04:04 +02:00
|
|
|
)
|
|
|
|
|
.bind(calendar_id)
|
|
|
|
|
.bind(property_name)
|
|
|
|
|
.execute(&*self.pool)
|
|
|
|
|
.await
|
2026-02-14 01:29:34 +01:00
|
|
|
.map_err(|e| {
|
|
|
|
|
DomainError::database_error(format!("Failed to remove calendar property: {}", e))
|
|
|
|
|
})?;
|
|
|
|
|
|
2025-04-13 01:04:04 +02:00
|
|
|
Ok(())
|
|
|
|
|
}
|
2026-02-14 01:29:34 +01:00
|
|
|
|
|
|
|
|
async fn get_calendar_properties(
|
|
|
|
|
&self,
|
|
|
|
|
calendar_id: &Uuid,
|
|
|
|
|
) -> CalendarRepositoryResult<std::collections::HashMap<String, String>> {
|
2025-04-13 01:04:04 +02:00
|
|
|
let rows = sqlx::query(
|
|
|
|
|
r#"
|
|
|
|
|
SELECT name, value
|
|
|
|
|
FROM caldav.calendar_properties
|
|
|
|
|
WHERE calendar_id = $1
|
2026-02-14 01:29:34 +01:00
|
|
|
"#,
|
2025-04-13 01:04:04 +02:00
|
|
|
)
|
|
|
|
|
.bind(calendar_id)
|
|
|
|
|
.fetch_all(&*self.pool)
|
|
|
|
|
.await
|
2026-02-14 01:29:34 +01:00
|
|
|
.map_err(|e| {
|
|
|
|
|
DomainError::database_error(format!("Failed to get calendar properties: {}", e))
|
|
|
|
|
})?;
|
|
|
|
|
|
2025-04-13 01:04:04 +02:00
|
|
|
let mut properties = std::collections::HashMap::new();
|
|
|
|
|
for row in rows {
|
|
|
|
|
properties.insert(row.get("name"), row.get("value"));
|
|
|
|
|
}
|
2026-02-14 01:29:34 +01:00
|
|
|
|
2025-04-13 01:04:04 +02:00
|
|
|
Ok(properties)
|
|
|
|
|
}
|
2026-02-14 01:29:34 +01:00
|
|
|
}
|