diff --git a/crates/tranquil-db-traits/src/error.rs b/crates/tranquil-db-traits/src/error.rs index 43add05..547a110 100644 --- a/crates/tranquil-db-traits/src/error.rs +++ b/crates/tranquil-db-traits/src/error.rs @@ -1,5 +1,31 @@ use thiserror::Error; +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct ColumnRef { + table: &'static str, + column: &'static str, +} + +impl ColumnRef { + pub const fn new(table: &'static str, column: &'static str) -> Self { + Self { table, column } + } + + pub const fn table(&self) -> &'static str { + self.table + } + + pub const fn column(&self) -> &'static str { + self.column + } +} + +impl std::fmt::Display for ColumnRef { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}.{}", self.table, self.column) + } +} + #[derive(Debug, Error)] pub enum DbError { #[error("Database query error: {0}")] @@ -29,6 +55,9 @@ pub enum DbError { #[error("Corrupt data in column: {0}")] CorruptData(&'static str), + #[error("Column {0} has a value that isn't valid for its type")] + InvalidColumn(ColumnRef), + #[error("Other database error: {0}")] Other(String), } diff --git a/crates/tranquil-db-traits/src/lib.rs b/crates/tranquil-db-traits/src/lib.rs index c628930..b71a3eb 100644 --- a/crates/tranquil-db-traits/src/lib.rs +++ b/crates/tranquil-db-traits/src/lib.rs @@ -20,7 +20,7 @@ pub use delegation::{ AuditLogEntry, ControllerInfo, DelegatedAccountInfo, DelegationActionType, DelegationGrant, DelegationRepository, }; -pub use error::DbError; +pub use error::{ColumnRef, DbError}; pub use infra::{ AdminAccountInfo, CommsChannel, CommsStatus, CommsType, DeletionRequest, DeletionRequestWithToken, InfraRepository, InviteCodeInfo, InviteCodeRow, InviteCodeSortOrder, diff --git a/crates/tranquil-db/src/postgres/mod.rs b/crates/tranquil-db/src/postgres/mod.rs index a05da3a..1f97998 100644 --- a/crates/tranquil-db/src/postgres/mod.rs +++ b/crates/tranquil-db/src/postgres/mod.rs @@ -10,7 +10,106 @@ mod sso; mod user; use sqlx::PgPool; +use std::str::FromStr; use std::sync::Arc; +use tranquil_db_traits::{ColumnRef, DbError}; + +pub(crate) mod col { + use tranquil_db_traits::ColumnRef; + + pub const ACCOUNT_DELEGATIONS_CONTROLLER_DID: ColumnRef = + ColumnRef::new("account_delegations", "controller_did"); + pub const ACCOUNT_DELEGATIONS_DELEGATED_DID: ColumnRef = + ColumnRef::new("account_delegations", "delegated_did"); + pub const ACCOUNT_DELEGATIONS_GRANTED_BY: ColumnRef = + ColumnRef::new("account_delegations", "granted_by"); + pub const ACCOUNT_DELEGATIONS_REVOKED_BY: ColumnRef = + ColumnRef::new("account_delegations", "revoked_by"); + pub const ACCOUNT_DELETION_REQUESTS_DID: ColumnRef = + ColumnRef::new("account_deletion_requests", "did"); + pub const APP_PASSWORDS_CREATED_BY_CONTROLLER_DID: ColumnRef = + ColumnRef::new("app_passwords", "created_by_controller_did"); + pub const BACKLINKS_URI: ColumnRef = ColumnRef::new("backlinks", "uri"); + pub const BLOBS_CID: ColumnRef = ColumnRef::new("blobs", "cid"); + pub const DELEGATION_AUDIT_LOG_ACTOR_DID: ColumnRef = + ColumnRef::new("delegation_audit_log", "actor_did"); + pub const DELEGATION_AUDIT_LOG_CONTROLLER_DID: ColumnRef = + ColumnRef::new("delegation_audit_log", "controller_did"); + pub const DELEGATION_AUDIT_LOG_DELEGATED_DID: ColumnRef = + ColumnRef::new("delegation_audit_log", "delegated_did"); + pub const INVITE_CODES_FOR_ACCOUNT: ColumnRef = ColumnRef::new("invite_codes", "for_account"); + pub const OAUTH_2FA_CHALLENGE_DID: ColumnRef = ColumnRef::new("oauth_2fa_challenge", "did"); + pub const OAUTH_AUTHORIZATION_REQUEST_CONTROLLER_DID: ColumnRef = + ColumnRef::new("oauth_authorization_request", "controller_did"); + pub const OAUTH_AUTHORIZATION_REQUEST_DID: ColumnRef = + ColumnRef::new("oauth_authorization_request", "did"); + pub const OAUTH_TOKEN_CONTROLLER_DID: ColumnRef = + ColumnRef::new("oauth_token", "controller_did"); + pub const OAUTH_TOKEN_DID: ColumnRef = ColumnRef::new("oauth_token", "did"); + pub const PASSKEYS_DID: ColumnRef = ColumnRef::new("passkeys", "did"); + pub const RECORD_BLOBS_BLOB_CID: ColumnRef = ColumnRef::new("record_blobs", "blob_cid"); + pub const RECORD_BLOBS_RECORD_URI: ColumnRef = ColumnRef::new("record_blobs", "record_uri"); + pub const RECORDS_COLLECTION: ColumnRef = ColumnRef::new("records", "collection"); + pub const RECORDS_RECORD_CID: ColumnRef = ColumnRef::new("records", "record_cid"); + pub const RECORDS_RKEY: ColumnRef = ColumnRef::new("records", "rkey"); + pub const REPO_SEQ_BLOBS: ColumnRef = ColumnRef::new("repo_seq", "blobs"); + pub const REPO_SEQ_BLOCKS_CIDS: ColumnRef = ColumnRef::new("repo_seq", "blocks_cids"); + pub const REPO_SEQ_COMMIT_CID: ColumnRef = ColumnRef::new("repo_seq", "commit_cid"); + pub const REPO_SEQ_DID: ColumnRef = ColumnRef::new("repo_seq", "did"); + pub const REPO_SEQ_HANDLE: ColumnRef = ColumnRef::new("repo_seq", "handle"); + pub const REPO_SEQ_PREV_CID: ColumnRef = ColumnRef::new("repo_seq", "prev_cid"); + pub const REPO_SEQ_PREV_DATA_CID: ColumnRef = ColumnRef::new("repo_seq", "prev_data_cid"); + pub const REPO_SEQ_REV: ColumnRef = ColumnRef::new("repo_seq", "rev"); + pub const REPOS_REPO_REV: ColumnRef = ColumnRef::new("repos", "repo_rev"); + pub const REPOS_REPO_ROOT_CID: ColumnRef = ColumnRef::new("repos", "repo_root_cid"); + pub const RESERVED_SIGNING_KEYS_DID: ColumnRef = ColumnRef::new("reserved_signing_keys", "did"); + pub const RESERVED_SIGNING_KEYS_PUBLIC_KEY_DID_KEY: ColumnRef = + ColumnRef::new("reserved_signing_keys", "public_key_did_key"); + pub const SESSION_TOKENS_CONTROLLER_DID: ColumnRef = + ColumnRef::new("session_tokens", "controller_did"); + pub const SESSION_TOKENS_DID: ColumnRef = ColumnRef::new("session_tokens", "did"); + pub const USERS_DID: ColumnRef = ColumnRef::new("users", "did"); + pub const USERS_HANDLE: ColumnRef = ColumnRef::new("users", "handle"); +} + +pub(crate) fn column(value: String, name: ColumnRef) -> Result { + T::from_str(&value).map_err(|_| { + tracing::error!( + column = %name, + value = %value, + "column has a value that isn't valid for its type" + ); + DbError::InvalidColumn(name) + }) +} + +pub(crate) fn opt_column( + value: Option, + name: ColumnRef, +) -> Result, DbError> { + value.map(|v| column(v, name)).transpose() +} + +pub(crate) fn legacy_column(value: String, name: ColumnRef) -> Option { + match T::from_str(&value) { + Ok(v) => Some(v), + Err(_) => { + tracing::warn!( + column = %name, + value = %value, + "ignoring a column value that isn't valid for its type" + ); + None + } + } +} + +pub(crate) fn column_vec( + values: Vec, + name: ColumnRef, +) -> Result, DbError> { + values.into_iter().map(|v| column(v, name)).collect() +} pub use backlink::PostgresBacklinkRepository; pub use blob::PostgresBlobRepository;