use std::path::{Path, PathBuf};
use thiserror::Error;
use turso::{Connection, Database, Error as TursoError, Row};
use aves::utils::database::{
DatabaseError, MigrationRunner, connect_database, maintain_database, open_database_at_path,
};
use crate::migrations::MIGRATIONS;
#[derive(Debug, Error)]
pub(super) enum StoreError {
#[error(transparent)]
Database(#[from] DatabaseError),
#[error(transparent)]
Turso(#[from] TursoError),
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(super) struct MessagingConnection {
pub(super) id: String,
pub(super) display_name: String,
pub(super) provider_type: String,
pub(super) credential_id: String,
pub(super) transport_settings: String,
pub(super) enabled: bool,
pub(super) retention_seconds: i64,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(super) struct PermissionRequest {
pub(super) request_id: String,
pub(super) connection_id: String,
pub(super) provider_message_id: Option<String>,
pub(super) destination: String,
pub(super) request_text: String,
pub(super) expires_at: i64,
pub(super) flow_execution_id: String,
pub(super) flow_node_id: String,
pub(super) status: String,
pub(super) provider_receipt: Option<String>,
pub(super) decided_at: Option<i64>,
pub(super) outstanding_edit: bool,
}
pub(super) struct MessageStore {
database: Database,
path: PathBuf,
}
impl MessageStore {
pub(super) async fn open(config_dir: &Path) -> Result<Self, StoreError> {
let path = config_dir.join("messages.db");
let database = open_database_at_path(path.clone()).await?;
MigrationRunner::new("__peitho_migrations", MIGRATIONS)
.apply(&database)
.await?;
Ok(Self { database, path })
}
async fn connection(&self) -> Result<Connection, StoreError> {
Ok(connect_database(&self.database).await?)
}
pub(super) async fn maintain(&self) {
maintain_database(&self.database, &self.path).await;
}
pub(super) async fn list_connections(&self) -> Result<Vec<MessagingConnection>, StoreError> {
let connection = self.connection().await?;
let mut rows = connection
.query(include_str!("queries/list_connections.sql"), ())
.await?;
let mut result = Vec::new();
while let Some(row) = rows.next().await? {
result.push(Self::read_connection(&row)?);
}
Ok(result)
}
pub(super) async fn get_connection(&self, id: &str) -> Result<Option<MessagingConnection>, StoreError> {
let connection = self.connection().await?;
let mut rows = connection
.query(include_str!("queries/get_connection.sql"), (id,))
.await?;
Ok(rows
.next()
.await?
.as_ref()
.map(Self::read_connection)
.transpose()?)
}
pub(super) async fn create_connection(&self, value: &MessagingConnection) -> Result<(), StoreError> {
let connection = self.connection().await?;
connection
.execute(
include_str!("queries/create_connection.sql"),
(
value.id.as_str(),
value.display_name.as_str(),
value.provider_type.as_str(),
value.credential_id.as_str(),
value.transport_settings.as_str(),
i64::from(value.enabled),
value.retention_seconds,
),
)
.await?;
Ok(())
}
pub(super) async fn update_connection(&self, value: &MessagingConnection) -> Result<(), StoreError> {
let connection = self.connection().await?;
connection
.execute(
include_str!("queries/update_connection.sql"),
(
value.id.as_str(),
value.display_name.as_str(),
value.provider_type.as_str(),
value.credential_id.as_str(),
value.transport_settings.as_str(),
value.retention_seconds,
),
)
.await?;
Ok(())
}
pub(super) async fn set_enabled(&self, id: &str, enabled: bool) -> Result<(), StoreError> {
let connection = self.connection().await?;
connection
.execute(
include_str!("queries/set_connection_enabled.sql"),
(id, i64::from(enabled)),
)
.await?;
Ok(())
}
pub(super) async fn delete_connection(&self, id: &str) -> Result<(), StoreError> {
let connection = self.connection().await?;
connection
.execute(include_str!("queries/delete_connection.sql"), (id,))
.await?;
Ok(())
}
pub(super) async fn insert_prepared(&self, request: &PermissionRequest) -> Result<(), StoreError> {
let connection = self.connection().await?;
connection
.execute(
include_str!("queries/insert_prepared.sql"),
(
request.request_id.as_str(),
request.connection_id.as_str(),
request.destination.as_str(),
request.request_text.as_str(),
request.expires_at,
request.flow_execution_id.as_str(),
request.flow_node_id.as_str(),
),
)
.await?;
Ok(())
}
pub(super) async fn mark_pending(
&self,
request_id: &str,
message_id: &str,
receipt: &str,
) -> Result<bool, StoreError> {
let connection = self.connection().await?;
Ok(connection
.execute(
include_str!("queries/mark_pending.sql"),
(request_id, message_id, receipt),
)
.await?
== 1)
}
pub(super) async fn attach_provider_message(
&self,
request_id: &str,
provider_message_id: &str,
provider_receipt: &str,
) -> Result<bool, StoreError> {
let connection = self.connection().await?;
Ok(connection
.execute(
include_str!("queries/attach_provider_message.sql"),
(request_id, provider_message_id, provider_receipt),
)
.await?
== 1)
}
pub(super) async fn finalize_atomic(
&self,
request_id: &str,
status: &str,
decided_at: i64,
outstanding_edit: bool,
) -> Result<bool, StoreError> {
let connection = self.connection().await?;
Ok(connection
.execute(
include_str!("queries/finalize_atomic.sql"),
(request_id, status, decided_at, i64::from(outstanding_edit)),
)
.await?
== 1)
}
pub(super) async fn clear_outstanding_edit(&self, request_id: &str) -> Result<(), StoreError> {
let connection = self.connection().await?;
connection
.execute(include_str!("queries/clear_outstanding_edit.sql"), (request_id,))
.await?;
Ok(())
}
pub(super) async fn get_permission_request(
&self,
request_id: &str,
) -> Result<Option<PermissionRequest>, StoreError> {
let connection = self.connection().await?;
let mut rows = connection
.query(include_str!("queries/get_permission_request.sql"), (request_id,))
.await?;
Ok(rows
.next()
.await?
.as_ref()
.map(Self::read_permission_request)
.transpose()?)
}
pub(super) async fn get_by_correlation(
&self,
flow_execution_id: &str,
flow_node_id: &str,
) -> Result<Option<PermissionRequest>, StoreError> {
let connection = self.connection().await?;
let mut rows = connection
.query(
include_str!("queries/get_by_correlation.sql"),
(flow_execution_id, flow_node_id),
)
.await?;
Ok(rows
.next()
.await?
.as_ref()
.map(Self::read_permission_request)
.transpose()?)
}
pub(super) async fn scan_unfinished(&self, now: i64) -> Result<Vec<PermissionRequest>, StoreError> {
let connection = self.connection().await?;
let mut rows = connection
.query(include_str!("queries/scan_unfinished.sql"), (now,))
.await?;
let mut result = Vec::new();
while let Some(row) = rows.next().await? {
result.push(Self::read_permission_request(&row)?);
}
Ok(result)
}
pub(super) async fn scan_outstanding_edits(&self) -> Result<Vec<PermissionRequest>, StoreError> {
let connection = self.connection().await?;
let mut rows = connection
.query(include_str!("queries/scan_outstanding_edits.sql"), ())
.await?;
let mut result = Vec::new();
while let Some(row) = rows.next().await? {
result.push(Self::read_permission_request(&row)?);
}
Ok(result)
}
pub(super) async fn mark_deleted_for_connection(&self, id: &str, now: i64) -> Result<u64, StoreError> {
let connection = self.connection().await?;
Ok(connection
.execute(include_str!("queries/mark_deleted_for_connection.sql"), (id, now))
.await?)
}
#[allow(clippy::too_many_arguments)]
pub(super) async fn insert_inbound(
&self,
raw: &[u8],
body: Option<&str>,
metadata: Option<&str>,
connection_id: &str,
channel_id: Option<&str>,
received_at: i64,
retention_seconds: i64,
expires_at: i64,
) -> Result<(), StoreError> {
let connection = self.connection().await?;
connection
.execute(
include_str!("queries/insert_inbound.sql"),
(
raw,
body,
metadata,
connection_id,
channel_id,
received_at,
retention_seconds,
expires_at,
),
)
.await?;
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub(super) async fn insert_callback(
&self,
raw: &str,
request_id: Option<&str>,
action: Option<&str>,
decided_at: Option<i64>,
connection_id: Option<&str>,
received_at: i64,
retention_seconds: i64,
expires_at: i64,
) -> Result<(), StoreError> {
let connection = self.connection().await?;
connection
.execute(
include_str!("queries/insert_callback.sql"),
(
raw,
request_id,
action,
decided_at,
connection_id,
received_at,
retention_seconds,
expires_at,
),
)
.await?;
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub(super) async fn insert_accepted_send(
&self,
body: &str,
metadata: Option<&str>,
receipt: &str,
connection_id: &str,
received_at: i64,
retention_seconds: i64,
expires_at: i64,
) -> Result<(), StoreError> {
let connection = self.connection().await?;
connection
.execute(
include_str!("queries/insert_accepted_send.sql"),
(
body,
metadata,
receipt,
connection_id,
received_at,
retention_seconds,
expires_at,
),
)
.await?;
Ok(())
}
pub(super) async fn prune_expired(&self, now: i64) -> Result<(), StoreError> {
let connection = self.connection().await?;
for query in [
include_str!("queries/prune_expired.sql"),
include_str!("queries/prune_callbacks.sql"),
include_str!("queries/prune_accepted_sends.sql"),
] {
connection.execute(query, (now,)).await?;
}
Ok(())
}
fn read_connection(row: &Row) -> Result<MessagingConnection, TursoError> {
Ok(MessagingConnection {
id: row.get(0)?,
display_name: row.get(1)?,
provider_type: row.get(2)?,
credential_id: row.get(3)?,
transport_settings: row.get(4)?,
enabled: row.get::<i64>(5)? != 0,
retention_seconds: row.get(6)?,
})
}
fn read_permission_request(row: &Row) -> Result<PermissionRequest, TursoError> {
Ok(PermissionRequest {
request_id: row.get(0)?,
connection_id: row.get(1)?,
provider_message_id: row.get(2)?,
destination: row.get(3)?,
request_text: row.get(4)?,
expires_at: row.get(5)?,
flow_execution_id: row.get(6)?,
flow_node_id: row.get(7)?,
status: row.get(8)?,
provider_receipt: row.get(9)?,
decided_at: row.get(10)?,
outstanding_edit: row.get::<i64>(11)? != 0,
})
}
}