style: cargo fmt --all

This commit is contained in:
Dionisio
2026-03-03 01:49:18 +01:00
parent 1df52fd702
commit efcf88c4d7
29 changed files with 2754 additions and 2732 deletions
+3 -2
View File
@@ -3,6 +3,7 @@ pub mod pg;
// Re-exportar para facilitar acceso
pub use pg::{
AppPasswordPgRepository, DeviceCodePgRepository, FileBlobReadRepository, FileBlobWriteRepository,
FolderDbRepository, SessionPgRepository, TrashDbRepository, UserPgRepository,
AppPasswordPgRepository, DeviceCodePgRepository, FileBlobReadRepository,
FileBlobWriteRepository, FolderDbRepository, SessionPgRepository, TrashDbRepository,
UserPgRepository,
};
@@ -1,188 +1,178 @@
//! PostgreSQL repository for App Passwords.
use crate::application::ports::auth_ports::AppPasswordStoragePort;
use crate::common::errors::DomainError;
use crate::domain::entities::app_password::AppPassword;
use async_trait::async_trait;
use chrono::{DateTime, Utc};
use sqlx::PgPool;
use std::sync::Arc;
pub struct AppPasswordPgRepository {
pool: Arc<PgPool>,
}
impl AppPasswordPgRepository {
pub fn new(pool: Arc<PgPool>) -> Self {
Self { pool }
}
fn pool(&self) -> &PgPool {
&self.pool
}
}
#[async_trait]
impl AppPasswordStoragePort for AppPasswordPgRepository {
async fn create(&self, ap: AppPassword) -> Result<AppPassword, DomainError> {
sqlx::query(
r#"
INSERT INTO auth.app_passwords
(id, user_id, label, password_hash, prefix, scopes,
created_at, last_used_at, expires_at, active)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
"#,
)
.bind(&ap.id)
.bind(&ap.user_id)
.bind(&ap.label)
.bind(&ap.password_hash)
.bind(&ap.prefix)
.bind(&ap.scopes)
.bind(ap.created_at)
.bind(ap.last_used_at)
.bind(ap.expires_at)
.bind(ap.active)
.execute(self.pool())
.await
.map_err(|e| DomainError::internal_error("AppPasswordPg", format!("create: {e}")))?;
Ok(ap)
}
async fn list_by_user(&self, user_id: &str) -> Result<Vec<AppPassword>, DomainError> {
let rows = sqlx::query_as::<_, AppPasswordRow>(
r#"
SELECT id, user_id, label, password_hash, prefix, scopes,
created_at, last_used_at, expires_at, active
FROM auth.app_passwords
WHERE user_id = $1
ORDER BY created_at DESC
"#,
)
.bind(user_id)
.fetch_all(self.pool())
.await
.map_err(|e| DomainError::internal_error("AppPasswordPg", format!("list: {e}")))?;
Ok(rows.into_iter().map(|r| r.into()).collect())
}
async fn get_by_id(&self, id: &str) -> Result<AppPassword, DomainError> {
let row = sqlx::query_as::<_, AppPasswordRow>(
r#"
SELECT id, user_id, label, password_hash, prefix, scopes,
created_at, last_used_at, expires_at, active
FROM auth.app_passwords
WHERE id = $1
"#,
)
.bind(id)
.fetch_optional(self.pool())
.await
.map_err(|e| DomainError::internal_error("AppPasswordPg", format!("get_by_id: {e}")))?
.ok_or_else(|| DomainError::not_found("AppPassword", id))?;
Ok(row.into())
}
async fn get_active_by_user_id(
&self,
user_id: &str,
) -> Result<Vec<AppPassword>, DomainError> {
let rows = sqlx::query_as::<_, AppPasswordRow>(
r#"
SELECT id, user_id, label, password_hash, prefix, scopes,
created_at, last_used_at, expires_at, active
FROM auth.app_passwords
WHERE user_id = $1
AND active = TRUE
AND (expires_at IS NULL OR expires_at > NOW())
"#,
)
.bind(user_id)
.fetch_all(self.pool())
.await
.map_err(|e| {
DomainError::internal_error("AppPasswordPg", format!("get_active: {e}"))
})?;
Ok(rows.into_iter().map(|r| r.into()).collect())
}
async fn touch_last_used(&self, id: &str) -> Result<(), DomainError> {
sqlx::query("UPDATE auth.app_passwords SET last_used_at = NOW() WHERE id = $1")
.bind(id)
.execute(self.pool())
.await
.map_err(|e| {
DomainError::internal_error("AppPasswordPg", format!("touch: {e}"))
})?;
Ok(())
}
async fn revoke(&self, id: &str) -> Result<(), DomainError> {
let result =
sqlx::query("UPDATE auth.app_passwords SET active = FALSE WHERE id = $1")
.bind(id)
.execute(self.pool())
.await
.map_err(|e| {
DomainError::internal_error("AppPasswordPg", format!("revoke: {e}"))
})?;
if result.rows_affected() == 0 {
return Err(DomainError::not_found("AppPassword", id));
}
Ok(())
}
async fn delete_expired(&self) -> Result<u64, DomainError> {
let result = sqlx::query(
r#"
DELETE FROM auth.app_passwords
WHERE (active = FALSE)
OR (expires_at IS NOT NULL AND expires_at < NOW())
"#,
)
.execute(self.pool())
.await
.map_err(|e| {
DomainError::internal_error("AppPasswordPg", format!("delete_expired: {e}"))
})?;
Ok(result.rows_affected())
}
}
/// Internal row struct for sqlx mapping.
#[derive(sqlx::FromRow)]
struct AppPasswordRow {
id: String,
user_id: String,
label: String,
password_hash: String,
prefix: String,
scopes: String,
created_at: DateTime<Utc>,
last_used_at: Option<DateTime<Utc>>,
expires_at: Option<DateTime<Utc>>,
active: bool,
}
impl From<AppPasswordRow> for AppPassword {
fn from(r: AppPasswordRow) -> Self {
AppPassword {
id: r.id,
user_id: r.user_id,
label: r.label,
password_hash: r.password_hash,
prefix: r.prefix,
scopes: r.scopes,
created_at: r.created_at,
last_used_at: r.last_used_at,
expires_at: r.expires_at,
active: r.active,
}
}
}
//! PostgreSQL repository for App Passwords.
use crate::application::ports::auth_ports::AppPasswordStoragePort;
use crate::common::errors::DomainError;
use crate::domain::entities::app_password::AppPassword;
use async_trait::async_trait;
use chrono::{DateTime, Utc};
use sqlx::PgPool;
use std::sync::Arc;
pub struct AppPasswordPgRepository {
pool: Arc<PgPool>,
}
impl AppPasswordPgRepository {
pub fn new(pool: Arc<PgPool>) -> Self {
Self { pool }
}
fn pool(&self) -> &PgPool {
&self.pool
}
}
#[async_trait]
impl AppPasswordStoragePort for AppPasswordPgRepository {
async fn create(&self, ap: AppPassword) -> Result<AppPassword, DomainError> {
sqlx::query(
r#"
INSERT INTO auth.app_passwords
(id, user_id, label, password_hash, prefix, scopes,
created_at, last_used_at, expires_at, active)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
"#,
)
.bind(&ap.id)
.bind(&ap.user_id)
.bind(&ap.label)
.bind(&ap.password_hash)
.bind(&ap.prefix)
.bind(&ap.scopes)
.bind(ap.created_at)
.bind(ap.last_used_at)
.bind(ap.expires_at)
.bind(ap.active)
.execute(self.pool())
.await
.map_err(|e| DomainError::internal_error("AppPasswordPg", format!("create: {e}")))?;
Ok(ap)
}
async fn list_by_user(&self, user_id: &str) -> Result<Vec<AppPassword>, DomainError> {
let rows = sqlx::query_as::<_, AppPasswordRow>(
r#"
SELECT id, user_id, label, password_hash, prefix, scopes,
created_at, last_used_at, expires_at, active
FROM auth.app_passwords
WHERE user_id = $1
ORDER BY created_at DESC
"#,
)
.bind(user_id)
.fetch_all(self.pool())
.await
.map_err(|e| DomainError::internal_error("AppPasswordPg", format!("list: {e}")))?;
Ok(rows.into_iter().map(|r| r.into()).collect())
}
async fn get_by_id(&self, id: &str) -> Result<AppPassword, DomainError> {
let row = sqlx::query_as::<_, AppPasswordRow>(
r#"
SELECT id, user_id, label, password_hash, prefix, scopes,
created_at, last_used_at, expires_at, active
FROM auth.app_passwords
WHERE id = $1
"#,
)
.bind(id)
.fetch_optional(self.pool())
.await
.map_err(|e| DomainError::internal_error("AppPasswordPg", format!("get_by_id: {e}")))?
.ok_or_else(|| DomainError::not_found("AppPassword", id))?;
Ok(row.into())
}
async fn get_active_by_user_id(&self, user_id: &str) -> Result<Vec<AppPassword>, DomainError> {
let rows = sqlx::query_as::<_, AppPasswordRow>(
r#"
SELECT id, user_id, label, password_hash, prefix, scopes,
created_at, last_used_at, expires_at, active
FROM auth.app_passwords
WHERE user_id = $1
AND active = TRUE
AND (expires_at IS NULL OR expires_at > NOW())
"#,
)
.bind(user_id)
.fetch_all(self.pool())
.await
.map_err(|e| DomainError::internal_error("AppPasswordPg", format!("get_active: {e}")))?;
Ok(rows.into_iter().map(|r| r.into()).collect())
}
async fn touch_last_used(&self, id: &str) -> Result<(), DomainError> {
sqlx::query("UPDATE auth.app_passwords SET last_used_at = NOW() WHERE id = $1")
.bind(id)
.execute(self.pool())
.await
.map_err(|e| DomainError::internal_error("AppPasswordPg", format!("touch: {e}")))?;
Ok(())
}
async fn revoke(&self, id: &str) -> Result<(), DomainError> {
let result = sqlx::query("UPDATE auth.app_passwords SET active = FALSE WHERE id = $1")
.bind(id)
.execute(self.pool())
.await
.map_err(|e| DomainError::internal_error("AppPasswordPg", format!("revoke: {e}")))?;
if result.rows_affected() == 0 {
return Err(DomainError::not_found("AppPassword", id));
}
Ok(())
}
async fn delete_expired(&self) -> Result<u64, DomainError> {
let result = sqlx::query(
r#"
DELETE FROM auth.app_passwords
WHERE (active = FALSE)
OR (expires_at IS NOT NULL AND expires_at < NOW())
"#,
)
.execute(self.pool())
.await
.map_err(|e| {
DomainError::internal_error("AppPasswordPg", format!("delete_expired: {e}"))
})?;
Ok(result.rows_affected())
}
}
/// Internal row struct for sqlx mapping.
#[derive(sqlx::FromRow)]
struct AppPasswordRow {
id: String,
user_id: String,
label: String,
password_hash: String,
prefix: String,
scopes: String,
created_at: DateTime<Utc>,
last_used_at: Option<DateTime<Utc>>,
expires_at: Option<DateTime<Utc>>,
active: bool,
}
impl From<AppPasswordRow> for AppPassword {
fn from(r: AppPasswordRow) -> Self {
AppPassword {
id: r.id,
user_id: r.user_id,
label: r.label,
password_hash: r.password_hash,
prefix: r.prefix,
scopes: r.scopes,
created_at: r.created_at,
last_used_at: r.last_used_at,
expires_at: r.expires_at,
active: r.active,
}
}
}
@@ -1,261 +1,259 @@
//! PostgreSQL repository for Device Authorization Grant (RFC 8628) codes.
use async_trait::async_trait;
use sqlx::{PgPool, Row};
use std::sync::Arc;
use crate::application::ports::auth_ports::DeviceCodeStoragePort;
use crate::common::errors::{DomainError, ErrorKind};
use crate::domain::entities::device_code::{DeviceCode, DeviceCodeStatus};
pub struct DeviceCodePgRepository {
pool: Arc<PgPool>,
}
impl DeviceCodePgRepository {
pub fn new(pool: Arc<PgPool>) -> Self {
Self { pool }
}
fn map_row(row: &sqlx::postgres::PgRow) -> Result<DeviceCode, DomainError> {
let status_str: String = row.try_get("status").map_err(|e| {
DomainError::new(
ErrorKind::DatabaseError,
"DeviceCode",
format!("Failed to read status: {}", e),
)
})?;
let status = DeviceCodeStatus::from_str(&status_str).unwrap_or(DeviceCodeStatus::Expired);
Ok(DeviceCode::from_raw(
row.try_get("id").unwrap_or_default(),
row.try_get("device_code").unwrap_or_default(),
row.try_get("user_code").unwrap_or_default(),
row.try_get("client_name").unwrap_or_default(),
row.try_get("scopes").unwrap_or_default(),
status,
row.try_get("user_id").ok(),
row.try_get("access_token").ok(),
row.try_get("refresh_token").ok(),
row.try_get("verification_uri").unwrap_or_default(),
row.try_get("verification_uri_complete").ok(),
row.try_get("expires_at").unwrap_or_default(),
row.try_get::<i32, _>("poll_interval_secs").unwrap_or(5),
row.try_get("last_poll_at").ok(),
row.try_get("created_at").unwrap_or_default(),
row.try_get("authorized_at").ok(),
))
}
}
#[async_trait]
impl DeviceCodeStoragePort for DeviceCodePgRepository {
async fn create_device_code(&self, dc: DeviceCode) -> Result<DeviceCode, DomainError> {
sqlx::query(
r#"
INSERT INTO auth.device_codes (
id, device_code, user_code, client_name, scopes, status,
user_id, access_token, refresh_token,
verification_uri, verification_uri_complete,
expires_at, poll_interval_secs, last_poll_at,
created_at, authorized_at
) VALUES (
$1, $2, $3, $4, $5, $6::auth.device_code_status,
$7, $8, $9,
$10, $11,
$12, $13, $14,
$15, $16
)
"#,
)
.bind(dc.id())
.bind(dc.device_code())
.bind(dc.user_code())
.bind(dc.client_name())
.bind(dc.scopes())
.bind(dc.status().as_str())
.bind(dc.user_id())
.bind(dc.access_token())
.bind(dc.refresh_token())
.bind(dc.verification_uri())
.bind(dc.verification_uri_complete())
.bind(dc.expires_at())
.bind(dc.poll_interval_secs())
.bind(dc.last_poll_at())
.bind(dc.created_at())
.bind(dc.authorized_at())
.execute(self.pool.as_ref())
.await
.map_err(|e| {
DomainError::new(
ErrorKind::DatabaseError,
"DeviceCode",
format!("Failed to create device code: {}", e),
)
})?;
Ok(dc)
}
async fn get_by_device_code(&self, device_code: &str) -> Result<DeviceCode, DomainError> {
let row = sqlx::query(
r#"
SELECT id, device_code, user_code, client_name, scopes,
status::text AS status, user_id, access_token, refresh_token,
verification_uri, verification_uri_complete,
expires_at, poll_interval_secs, last_poll_at,
created_at, authorized_at
FROM auth.device_codes
WHERE device_code = $1
"#,
)
.bind(device_code)
.fetch_one(self.pool.as_ref())
.await
.map_err(|e| match e {
sqlx::Error::RowNotFound => DomainError::new(
ErrorKind::NotFound,
"DeviceCode",
"Device code not found",
),
_ => DomainError::new(
ErrorKind::DatabaseError,
"DeviceCode",
format!("Failed to fetch device code: {}", e),
),
})?;
Self::map_row(&row)
}
async fn get_pending_by_user_code(&self, user_code: &str) -> Result<DeviceCode, DomainError> {
let row = sqlx::query(
r#"
SELECT id, device_code, user_code, client_name, scopes,
status::text AS status, user_id, access_token, refresh_token,
verification_uri, verification_uri_complete,
expires_at, poll_interval_secs, last_poll_at,
created_at, authorized_at
FROM auth.device_codes
WHERE user_code = $1
AND status = 'pending'
AND expires_at > NOW()
"#,
)
.bind(user_code)
.fetch_one(self.pool.as_ref())
.await
.map_err(|e| match e {
sqlx::Error::RowNotFound => DomainError::new(
ErrorKind::NotFound,
"DeviceCode",
"User code not found or expired",
),
_ => DomainError::new(
ErrorKind::DatabaseError,
"DeviceCode",
format!("Failed to fetch by user code: {}", e),
),
})?;
Self::map_row(&row)
}
async fn update_device_code(&self, dc: DeviceCode) -> Result<(), DomainError> {
sqlx::query(
r#"
UPDATE auth.device_codes SET
status = $2::auth.device_code_status,
user_id = $3,
access_token = $4,
refresh_token = $5,
last_poll_at = $6,
authorized_at = $7
WHERE id = $1
"#,
)
.bind(dc.id())
.bind(dc.status().as_str())
.bind(dc.user_id())
.bind(dc.access_token())
.bind(dc.refresh_token())
.bind(dc.last_poll_at())
.bind(dc.authorized_at())
.execute(self.pool.as_ref())
.await
.map_err(|e| {
DomainError::new(
ErrorKind::DatabaseError,
"DeviceCode",
format!("Failed to update device code: {}", e),
)
})?;
Ok(())
}
async fn delete_expired(&self) -> Result<u64, DomainError> {
let result = sqlx::query(
r#"
DELETE FROM auth.device_codes
WHERE expires_at < NOW()
AND status IN ('pending', 'expired')
"#,
)
.execute(self.pool.as_ref())
.await
.map_err(|e| {
DomainError::new(
ErrorKind::DatabaseError,
"DeviceCode",
format!("Failed to delete expired device codes: {}", e),
)
})?;
Ok(result.rows_affected())
}
async fn list_by_user(&self, user_id: &str) -> Result<Vec<DeviceCode>, DomainError> {
let rows = sqlx::query(
r#"
SELECT id, device_code, user_code, client_name, scopes,
status::text AS status, user_id, access_token, refresh_token,
verification_uri, verification_uri_complete,
expires_at, poll_interval_secs, last_poll_at,
created_at, authorized_at
FROM auth.device_codes
WHERE user_id = $1
ORDER BY created_at DESC
"#,
)
.bind(user_id)
.fetch_all(self.pool.as_ref())
.await
.map_err(|e| {
DomainError::new(
ErrorKind::DatabaseError,
"DeviceCode",
format!("Failed to list device codes: {}", e),
)
})?;
rows.iter().map(Self::map_row).collect()
}
async fn delete_by_id(&self, id: &str) -> Result<(), DomainError> {
sqlx::query("DELETE FROM auth.device_codes WHERE id = $1")
.bind(id)
.execute(self.pool.as_ref())
.await
.map_err(|e| {
DomainError::new(
ErrorKind::DatabaseError,
"DeviceCode",
format!("Failed to delete device code: {}", e),
)
})?;
Ok(())
}
}
//! PostgreSQL repository for Device Authorization Grant (RFC 8628) codes.
use async_trait::async_trait;
use sqlx::{PgPool, Row};
use std::sync::Arc;
use crate::application::ports::auth_ports::DeviceCodeStoragePort;
use crate::common::errors::{DomainError, ErrorKind};
use crate::domain::entities::device_code::{DeviceCode, DeviceCodeStatus};
pub struct DeviceCodePgRepository {
pool: Arc<PgPool>,
}
impl DeviceCodePgRepository {
pub fn new(pool: Arc<PgPool>) -> Self {
Self { pool }
}
fn map_row(row: &sqlx::postgres::PgRow) -> Result<DeviceCode, DomainError> {
let status_str: String = row.try_get("status").map_err(|e| {
DomainError::new(
ErrorKind::DatabaseError,
"DeviceCode",
format!("Failed to read status: {}", e),
)
})?;
let status = DeviceCodeStatus::from_str(&status_str).unwrap_or(DeviceCodeStatus::Expired);
Ok(DeviceCode::from_raw(
row.try_get("id").unwrap_or_default(),
row.try_get("device_code").unwrap_or_default(),
row.try_get("user_code").unwrap_or_default(),
row.try_get("client_name").unwrap_or_default(),
row.try_get("scopes").unwrap_or_default(),
status,
row.try_get("user_id").ok(),
row.try_get("access_token").ok(),
row.try_get("refresh_token").ok(),
row.try_get("verification_uri").unwrap_or_default(),
row.try_get("verification_uri_complete").ok(),
row.try_get("expires_at").unwrap_or_default(),
row.try_get::<i32, _>("poll_interval_secs").unwrap_or(5),
row.try_get("last_poll_at").ok(),
row.try_get("created_at").unwrap_or_default(),
row.try_get("authorized_at").ok(),
))
}
}
#[async_trait]
impl DeviceCodeStoragePort for DeviceCodePgRepository {
async fn create_device_code(&self, dc: DeviceCode) -> Result<DeviceCode, DomainError> {
sqlx::query(
r#"
INSERT INTO auth.device_codes (
id, device_code, user_code, client_name, scopes, status,
user_id, access_token, refresh_token,
verification_uri, verification_uri_complete,
expires_at, poll_interval_secs, last_poll_at,
created_at, authorized_at
) VALUES (
$1, $2, $3, $4, $5, $6::auth.device_code_status,
$7, $8, $9,
$10, $11,
$12, $13, $14,
$15, $16
)
"#,
)
.bind(dc.id())
.bind(dc.device_code())
.bind(dc.user_code())
.bind(dc.client_name())
.bind(dc.scopes())
.bind(dc.status().as_str())
.bind(dc.user_id())
.bind(dc.access_token())
.bind(dc.refresh_token())
.bind(dc.verification_uri())
.bind(dc.verification_uri_complete())
.bind(dc.expires_at())
.bind(dc.poll_interval_secs())
.bind(dc.last_poll_at())
.bind(dc.created_at())
.bind(dc.authorized_at())
.execute(self.pool.as_ref())
.await
.map_err(|e| {
DomainError::new(
ErrorKind::DatabaseError,
"DeviceCode",
format!("Failed to create device code: {}", e),
)
})?;
Ok(dc)
}
async fn get_by_device_code(&self, device_code: &str) -> Result<DeviceCode, DomainError> {
let row = sqlx::query(
r#"
SELECT id, device_code, user_code, client_name, scopes,
status::text AS status, user_id, access_token, refresh_token,
verification_uri, verification_uri_complete,
expires_at, poll_interval_secs, last_poll_at,
created_at, authorized_at
FROM auth.device_codes
WHERE device_code = $1
"#,
)
.bind(device_code)
.fetch_one(self.pool.as_ref())
.await
.map_err(|e| match e {
sqlx::Error::RowNotFound => {
DomainError::new(ErrorKind::NotFound, "DeviceCode", "Device code not found")
}
_ => DomainError::new(
ErrorKind::DatabaseError,
"DeviceCode",
format!("Failed to fetch device code: {}", e),
),
})?;
Self::map_row(&row)
}
async fn get_pending_by_user_code(&self, user_code: &str) -> Result<DeviceCode, DomainError> {
let row = sqlx::query(
r#"
SELECT id, device_code, user_code, client_name, scopes,
status::text AS status, user_id, access_token, refresh_token,
verification_uri, verification_uri_complete,
expires_at, poll_interval_secs, last_poll_at,
created_at, authorized_at
FROM auth.device_codes
WHERE user_code = $1
AND status = 'pending'
AND expires_at > NOW()
"#,
)
.bind(user_code)
.fetch_one(self.pool.as_ref())
.await
.map_err(|e| match e {
sqlx::Error::RowNotFound => DomainError::new(
ErrorKind::NotFound,
"DeviceCode",
"User code not found or expired",
),
_ => DomainError::new(
ErrorKind::DatabaseError,
"DeviceCode",
format!("Failed to fetch by user code: {}", e),
),
})?;
Self::map_row(&row)
}
async fn update_device_code(&self, dc: DeviceCode) -> Result<(), DomainError> {
sqlx::query(
r#"
UPDATE auth.device_codes SET
status = $2::auth.device_code_status,
user_id = $3,
access_token = $4,
refresh_token = $5,
last_poll_at = $6,
authorized_at = $7
WHERE id = $1
"#,
)
.bind(dc.id())
.bind(dc.status().as_str())
.bind(dc.user_id())
.bind(dc.access_token())
.bind(dc.refresh_token())
.bind(dc.last_poll_at())
.bind(dc.authorized_at())
.execute(self.pool.as_ref())
.await
.map_err(|e| {
DomainError::new(
ErrorKind::DatabaseError,
"DeviceCode",
format!("Failed to update device code: {}", e),
)
})?;
Ok(())
}
async fn delete_expired(&self) -> Result<u64, DomainError> {
let result = sqlx::query(
r#"
DELETE FROM auth.device_codes
WHERE expires_at < NOW()
AND status IN ('pending', 'expired')
"#,
)
.execute(self.pool.as_ref())
.await
.map_err(|e| {
DomainError::new(
ErrorKind::DatabaseError,
"DeviceCode",
format!("Failed to delete expired device codes: {}", e),
)
})?;
Ok(result.rows_affected())
}
async fn list_by_user(&self, user_id: &str) -> Result<Vec<DeviceCode>, DomainError> {
let rows = sqlx::query(
r#"
SELECT id, device_code, user_code, client_name, scopes,
status::text AS status, user_id, access_token, refresh_token,
verification_uri, verification_uri_complete,
expires_at, poll_interval_secs, last_poll_at,
created_at, authorized_at
FROM auth.device_codes
WHERE user_id = $1
ORDER BY created_at DESC
"#,
)
.bind(user_id)
.fetch_all(self.pool.as_ref())
.await
.map_err(|e| {
DomainError::new(
ErrorKind::DatabaseError,
"DeviceCode",
format!("Failed to list device codes: {}", e),
)
})?;
rows.iter().map(Self::map_row).collect()
}
async fn delete_by_id(&self, id: &str) -> Result<(), DomainError> {
sqlx::query("DELETE FROM auth.device_codes WHERE id = $1")
.bind(id)
.execute(self.pool.as_ref())
.await
.map_err(|e| {
DomainError::new(
ErrorKind::DatabaseError,
"DeviceCode",
format!("Failed to delete device code: {}", e),
)
})?;
Ok(())
}
}
@@ -268,10 +268,18 @@ impl FolderRepository for FolderDbRepository {
limit: usize,
include_total: bool,
) -> Result<(Vec<Folder>, Option<usize>), DomainError> {
let rows: Vec<(String, String, String, Option<String>, String, i64, i64, i64)> =
if let Some(pid) = parent_id {
sqlx::query_as(
r#"
let rows: Vec<(
String,
String,
String,
Option<String>,
String,
i64,
i64,
i64,
)> = if let Some(pid) = parent_id {
sqlx::query_as(
r#"
SELECT id::text, name, path, parent_id::text, user_id,
EXTRACT(EPOCH FROM created_at)::bigint,
EXTRACT(EPOCH FROM updated_at)::bigint,
@@ -281,15 +289,15 @@ impl FolderRepository for FolderDbRepository {
ORDER BY name
LIMIT $2 OFFSET $3
"#,
)
.bind(pid)
.bind(limit as i64)
.bind(offset as i64)
.fetch_all(self.pool())
.await
} else {
sqlx::query_as(
r#"
)
.bind(pid)
.bind(limit as i64)
.bind(offset as i64)
.fetch_all(self.pool())
.await
} else {
sqlx::query_as(
r#"
SELECT id::text, name, path, parent_id::text, user_id,
EXTRACT(EPOCH FROM created_at)::bigint,
EXTRACT(EPOCH FROM updated_at)::bigint,
@@ -299,13 +307,13 @@ impl FolderRepository for FolderDbRepository {
ORDER BY name
LIMIT $1 OFFSET $2
"#,
)
.bind(limit as i64)
.bind(offset as i64)
.fetch_all(self.pool())
.await
}
.map_err(|e| DomainError::internal_error("FolderDb", format!("paginate: {e}")))?;
)
.bind(limit as i64)
.bind(offset as i64)
.fetch_all(self.pool())
.await
}
.map_err(|e| DomainError::internal_error("FolderDb", format!("paginate: {e}")))?;
// total_count is identical in every row; 0 when the result set is empty.
let total = if include_total {
@@ -333,10 +341,18 @@ impl FolderRepository for FolderDbRepository {
limit: usize,
include_total: bool,
) -> Result<(Vec<Folder>, Option<usize>), DomainError> {
let rows: Vec<(String, String, String, Option<String>, String, i64, i64, i64)> =
if let Some(pid) = parent_id {
sqlx::query_as(
r#"
let rows: Vec<(
String,
String,
String,
Option<String>,
String,
i64,
i64,
i64,
)> = if let Some(pid) = parent_id {
sqlx::query_as(
r#"
SELECT id::text, name, path, parent_id::text, user_id,
EXTRACT(EPOCH FROM created_at)::bigint,
EXTRACT(EPOCH FROM updated_at)::bigint,
@@ -346,16 +362,16 @@ impl FolderRepository for FolderDbRepository {
ORDER BY name
LIMIT $3 OFFSET $4
"#,
)
.bind(pid)
.bind(owner_id)
.bind(limit as i64)
.bind(offset as i64)
.fetch_all(self.pool())
.await
} else {
sqlx::query_as(
r#"
)
.bind(pid)
.bind(owner_id)
.bind(limit as i64)
.bind(offset as i64)
.fetch_all(self.pool())
.await
} else {
sqlx::query_as(
r#"
SELECT id::text, name, path, parent_id::text, user_id,
EXTRACT(EPOCH FROM created_at)::bigint,
EXTRACT(EPOCH FROM updated_at)::bigint,
@@ -365,16 +381,14 @@ impl FolderRepository for FolderDbRepository {
ORDER BY name
LIMIT $2 OFFSET $3
"#,
)
.bind(owner_id)
.bind(limit as i64)
.bind(offset as i64)
.fetch_all(self.pool())
.await
}
.map_err(|e| {
DomainError::internal_error("FolderDb", format!("paginate_by_owner: {e}"))
})?;
)
.bind(owner_id)
.bind(limit as i64)
.bind(offset as i64)
.fetch_all(self.pool())
.await
}
.map_err(|e| DomainError::internal_error("FolderDb", format!("paginate_by_owner: {e}")))?;
let total = if include_total {
Some(rows.first().map_or(0, |r| r.7) as usize)
@@ -867,10 +881,7 @@ impl FolderRepository for FolderDbRepository {
user_id: &str,
) -> Result<Vec<Folder>, DomainError> {
let (where_extra, name_pattern) = match name_contains {
Some(name) if name.len() >= 3 => (
" AND fo.name ILIKE $3",
Some(format!("%{}%", name)),
),
Some(name) if name.len() >= 3 => (" AND fo.name ILIKE $3", Some(format!("%{}%", name))),
_ => ("", None),
};