use async_trait::async_trait;
use chrono::{DateTime, Utc};
use sqlx::PgPool;
use tranquil_types::{Did, Handle};
use uuid::Uuid;
use tranquil_db_traits::{
AccountSearchResult, AccountType, ChannelVerificationStatus, CommsChannel, DbError,
DidWebOverrides, NotificationPrefs, OAuthTokenWithUser, PasswordResetResult, SsoProviderType,
StoredBackupCode, StoredPasskey, TotpRecord, TotpRecordState, User2faStatus, UserAuthInfo,
UserCommsPrefs, UserConfirmSignup, UserDidWebInfo, UserEmailInfo, UserForDeletion,
UserForDidDoc, UserForDidDocBuild, UserForPasskeyRecovery, UserForPasskeySetup,
UserForRecovery, UserForVerification, UserIdAndHandle, UserIdAndPasswordHash,
UserIdHandleEmail, UserInfoForAuth, UserKeyInfo, UserKeyWithId, UserLegacyLoginPref,
UserLoginCheck, UserLoginFull, UserLoginInfo, UserPasswordInfo, UserRepository,
UserResendVerification, UserResetCodeInfo, UserRow, UserSessionInfo, UserStatus,
UserVerificationInfo, UserWithKey, WebauthnChallengeType,
};
pub struct PostgresUserRepository {
pool: PgPool,
}
impl PostgresUserRepository {
pub fn new(pool: PgPool) -> Self {
Self { pool }
}
}
pub(crate) fn map_sqlx_error(e: sqlx::Error) -> DbError {
match e {
sqlx::Error::RowNotFound => DbError::NotFound,
sqlx::Error::Database(db_err) => {
let msg = db_err.message().to_string();
if db_err.is_unique_violation() || db_err.is_foreign_key_violation() {
DbError::Constraint(msg)
} else {
DbError::Query(msg)
}
}
sqlx::Error::PoolTimedOut => DbError::Connection("Pool timed out".into()),
_ => DbError::Other(e.to_string()),
}
}
async fn consume_invite_code(
conn: &mut sqlx::PgConnection,
code: &str,
user_id: Uuid,
) -> Result<(), tranquil_db_traits::CreateAccountError> {
let map_err = |e: sqlx::Error| tranquil_db_traits::CreateAccountError::Database(e.to_string());
let decremented = sqlx::query!(
"UPDATE invite_codes SET available_uses = available_uses - 1 WHERE code = $1 AND available_uses > 0 AND COALESCE(disabled, false) = false",
code
)
.execute(&mut *conn)
.await
.map_err(map_err)?
.rows_affected();
if decremented == 0 {
return Err(tranquil_db_traits::CreateAccountError::InviteCodeUnavailable);
}
sqlx::query!(
"INSERT INTO invite_code_uses (code, used_by_user) VALUES ($1, $2)",
code,
user_id
)
.execute(&mut *conn)
.await
.map_err(map_err)?;
Ok(())
}
#[async_trait]
impl UserRepository for PostgresUserRepository {
async fn get_by_did(&self, did: &Did) -> Result