a06673b
use time::OffsetDateTime;
use uuid::Uuid;
use db::conn::sea_orm::{ActiveModelTrait, DatabaseConnection, EntityTrait, QueryFilter};
use db::user::user_agreement_acceptances::{
ActiveModel as AcceptActiveModel, Entity as AcceptEntity,
};
use crate::config::AuthConfig;
use crate::error::AuthError;
use super::{AcceptAgreementCmd, AcceptAgreementResp};
/// Return every agreement doc the service currently publishes (terms +
/// privacy). Callers should diff with what they have already accepted.
pub fn list_agreements(config: &AuthConfig) -> Vec<crate::config::AgreementDoc> {
vec![
config.agreements.terms.clone(),
config.agreements.privacy.clone(),
]
}
/// Record that the user accepted a specific version of an agreement. The
/// agreement must match a known current version in the configuration or the
/// call is rejected.
pub async fn accept_agreement(
cmd: AcceptAgreementCmd,
db: &DatabaseConnection,
config: &AuthConfig,
) -> Result<AcceptAgreementResp, AuthError> {
let known = match cmd.agreement_key.as_str() {
"terms" => Some(&config.agreements.terms),
"privacy" => Some(&config.agreements.privacy),
_ => None,
}
.ok_or_else(|| AuthError::NotFound(format!("unknown agreement: {}", cmd.agreement_key)))?;
if known.version != cmd.agreement_version {
return Err(AuthError::BadRequest(format!(
"expected version {}, got {}",
known.version, cmd.agreement_version
)));
}
// Idempotent insert: ignore conflict on the (user_id, agreement_key,
// agreement_version) unique key.
let key = cmd.agreement_key.clone();
let version = cmd.agreement_version.clone();
let am: AcceptActiveModel = AcceptActiveModel::builder()
.set_id(Uuid::new_v4())
.set_user_id(cmd.user_id)
.set_agreement_key(cmd.agreement_key)
.set_agreement_version(cmd.agreement_version)
.set_accepted_at(OffsetDateTime::now_utc())
.into();
// Try insert; if a unique-key collision already exists, treat as success.
match am.insert(db).await {
Ok(_) => Ok(AcceptAgreementResp { recorded: true }),
Err(_) => {
let exists = AcceptEntity::find()
.filter(AcceptEntity::COLUMN.user_id.eq(cmd.user_id))
.filter(AcceptEntity::COLUMN.agreement_key.eq(key))
.filter(AcceptEntity::COLUMN.agreement_version.eq(version))
.one(db)
.await?;
if exists.is_some() {
Ok(AcceptAgreementResp { recorded: true })
} else {
Err(AuthError::Internal("could not record agreement".into()))
}
}
}
}