miryad_core/users/
user.rs1use chrono::Utc;
2use sea_orm::entity::prelude::*;
3use sea_orm::{ConnectionTrait, Set};
4
5#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
6#[sea_orm(table_name = "miryad_users")]
7pub struct Model {
8 #[sea_orm(primary_key)]
9 pub id: i32,
10 #[sea_orm(unique)]
12 pub subject: String,
13 pub email: Option<String>,
14 pub display_name: Option<String>,
15 pub created_at: DateTimeUtc,
16}
17
18#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
19pub enum Relation {}
20
21impl ActiveModelBehavior for ActiveModel {}
22
23pub type User = Entity;
24
25pub async fn resolve_user<C: ConnectionTrait>(
30 db: &C,
31 subject: &str,
32 email: Option<&str>,
33) -> Result<Model, DbErr> {
34 if let Some(existing) = Entity::find().filter(Column::Subject.eq(subject)).one(db).await? {
35 return Ok(existing);
36 }
37
38 let active = ActiveModel {
39 subject: Set(subject.to_string()),
40 email: Set(email.map(str::to_string)),
41 display_name: Set(None),
42 created_at: Set(Utc::now()),
43 ..Default::default()
44 };
45
46 match active.insert(db).await {
47 Ok(model) => Ok(model),
48 Err(_) => Entity::find()
49 .filter(Column::Subject.eq(subject))
50 .one(db)
51 .await?
52 .ok_or_else(|| DbErr::RecordNotFound(format!("user with subject {subject} vanished"))),
53 }
54}
55
56#[cfg(test)]
57mod tests {
58 use super::*;
59 use crate::migration::Migrator;
60 use sea_orm_migration::MigratorTrait;
61
62 async fn test_db() -> DatabaseConnection {
63 let db = sea_orm::Database::connect("sqlite::memory:")
64 .await
65 .expect("in-memory sqlite connects");
66 Migrator::up(&db, None).await.expect("migrations apply cleanly");
67 db
68 }
69
70 #[tokio::test]
71 async fn resolve_user_creates_then_reuses_same_row() {
72 let db = test_db().await;
73 let first = resolve_user(&db, "sub-1", Some("a@example.com"))
74 .await
75 .expect("first resolve succeeds");
76 let second = resolve_user(&db, "sub-1", Some("a@example.com"))
77 .await
78 .expect("second resolve succeeds");
79 assert_eq!(first.id, second.id);
80 }
81}