Files
Oxicloud/src/infrastructure/repositories/pg/calendar_pg_repository.rs
T

501 lines
15 KiB
Rust
Raw Normal View History

2025-04-13 01:04:04 +02:00
use chrono::Utc;
use sqlx::{PgPool, Row, types::Uuid};
2025-04-13 01:04:04 +02:00
use std::sync::Arc;
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())
.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
// 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())
.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
// 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,
owner_id: &str,
) -> 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,
owner_id: &str,
) -> 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_calendars_shared_with_user(
&self,
user_id: &str,
) -> CalendarRepositoryResult<Vec<Calendar>> {
2025-04-13 01:04:04 +02:00
let rows = sqlx::query(
r#"
SELECT c.id, c.name, c.owner_id, c.description, c.color, c.is_public, c.created_at, c.updated_at
FROM caldav.calendars c
INNER JOIN caldav.calendar_shares s ON c.id = s.calendar_id
WHERE s.user_id = $1
ORDER BY c.name
"#
)
.bind(user_id)
.fetch_all(&*self.pool)
.await
.map_err(|e| DomainError::database_error(format!("Failed to get shared calendars: {}", e)))?;
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 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 user_has_calendar_access(
&self,
calendar_id: &Uuid,
user_id: &str,
) -> CalendarRepositoryResult<bool> {
2025-04-13 01:04:04 +02:00
// Check if the user is the owner of the calendar or has a share
let row = sqlx::query(
r#"
SELECT EXISTS (
SELECT 1 FROM caldav.calendars c
WHERE c.id = $1 AND (c.owner_id = $2 OR c.is_public = true)
UNION
SELECT 1 FROM caldav.calendar_shares s
WHERE s.calendar_id = $1 AND s.user_id = $2
) as has_access
2026-02-14 01:29:34 +01:00
"#,
2025-04-13 01:04:04 +02:00
)
.bind(calendar_id)
.bind(user_id)
.fetch_one(&*self.pool)
.await
2026-02-14 01:29:34 +01:00
.map_err(|e| {
DomainError::database_error(format!("Failed to check calendar access: {}", e))
})?;
2025-04-13 01:04:04 +02:00
Ok(row.get::<bool, _>("has_access"))
}
2026-02-14 01:29:34 +01:00
async fn share_calendar(
&self,
calendar_id: &Uuid,
user_id: &str,
access_level: &str,
) -> CalendarRepositoryResult<()> {
2025-04-13 01:04:04 +02:00
// Validate access level
if !["read", "write", "owner"].contains(&access_level) {
2026-02-14 01:29:34 +01:00
return Err(DomainError::validation_error(format!(
"Invalid access level: '{}'. Must be 'read', 'write', or 'owner'",
access_level
)));
2025-04-13 01:04:04 +02:00
}
2026-02-14 01:29:34 +01:00
2025-04-13 01:04:04 +02:00
sqlx::query(
r#"
INSERT INTO caldav.calendar_shares (calendar_id, user_id, access_level)
VALUES ($1, $2, $3)
ON CONFLICT (calendar_id, user_id) DO UPDATE SET access_level = $3
2026-02-14 01:29:34 +01:00
"#,
2025-04-13 01:04:04 +02:00
)
.bind(calendar_id)
.bind(user_id)
.bind(access_level)
.execute(&*self.pool)
.await
.map_err(|e| DomainError::database_error(format!("Failed to share calendar: {}", e)))?;
Ok(())
}
2026-02-14 01:29:34 +01:00
async fn remove_calendar_sharing(
&self,
calendar_id: &Uuid,
user_id: &str,
) -> CalendarRepositoryResult<()> {
2025-04-13 01:04:04 +02:00
sqlx::query(
r#"
DELETE FROM caldav.calendar_shares
WHERE calendar_id = $1 AND user_id = $2
2026-02-14 01:29:34 +01:00
"#,
2025-04-13 01:04:04 +02:00
)
.bind(calendar_id)
.bind(user_id)
.execute(&*self.pool)
.await
.map_err(|e| DomainError::database_error(format!("Failed to unshare calendar: {}", e)))?;
Ok(())
}
2026-02-14 01:29:34 +01:00
async fn get_calendar_shares(
&self,
calendar_id: &Uuid,
) -> CalendarRepositoryResult<Vec<(String, String)>> {
2025-04-13 01:04:04 +02:00
let rows = sqlx::query(
r#"
SELECT user_id, access_level
FROM caldav.calendar_shares
WHERE calendar_id = $1
ORDER BY user_id
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 shares: {}", e))
})?;
2025-04-13 01:04:04 +02:00
let mut shares = Vec::new();
for row in rows {
shares.push((row.get("user_id"), row.get("access_level")));
}
Ok(shares)
}
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
}