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 { 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 { 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())) } } } }