use super::*;
pub(crate) fn seaorm_schema(selection: &Selection) -> TokenStream {
let extra_user = &selection.user;
let extra_session = &selection.session;
let extra_entities = &selection.extra;
let imports = quote! {
use alibi::AuthSchema;
use alibi::seaorm::sea_orm;
use alibi::seaorm::sea_orm::entity::prelude::*;
use alibi::seaorm::sea_orm::{ConnectionTrait, Schema};
use alibi::seaorm::{AuthEntity, DatabaseConnection};
};
let user_entity = gen_entity("user", "users", EntityRole::User, extra_user);
let session_entity = gen_entity("session", "sessions", EntityRole::Session, extra_session);
let account_entity = gen_entity("account", "accounts", EntityRole::Account, &[]);
let verification_entity = gen_entity(
"verification",
"verifications",
EntityRole::Verification,
&[],
);
let extra_entity_tokens: Vec<TokenStream> = extra_entities
.iter()
.map(|entity| gen_extra_entity(entity))
.collect();
let schema_impl = quote! {
pub struct AppAuthSchema;
impl AuthSchema for AppAuthSchema {
type User = user::Model;
type Session = session::Model;
type Account = account::Model;
type Verification = verification::Model;
}
};
let extra_migration_statements: Vec<TokenStream> = extra_entities
.iter()
.map(|entity| {
let mod_ident = format_ident!("{}", entity.mod_name);
quote! { schema.create_table_from_entity(#mod_ident::Entity).if_not_exists().to_owned() }
})
.collect();
let migration_fn = quote! {
pub async fn run_app_migrations(
database: &DatabaseConnection,
) -> Result<(), sea_orm::DbErr> {
let schema = Schema::new(database.get_database_backend());
for statement in [
schema.create_table_from_entity(user::Entity).if_not_exists().to_owned(),
schema.create_table_from_entity(session::Entity).if_not_exists().to_owned(),
schema.create_table_from_entity(account::Entity).if_not_exists().to_owned(),
schema.create_table_from_entity(verification::Entity).if_not_exists().to_owned(),
#(#extra_migration_statements,)*
] {
let _ = database.execute(&statement).await?;
}
Ok(())
}
};
quote! {
#imports
#user_entity
#session_entity
#account_entity
#verification_entity
#(#extra_entity_tokens)*
#schema_impl
#migration_fn
}
}
pub(crate) fn gen_entity(
mod_name: &str,
table_name: &str,
role: EntityRole,
plugin_fields: &[FieldDef],
) -> TokenStream {
let mod_ident = format_ident!("{}", mod_name);
let role_str = mod_name;
let core = registry::core_fields(role);
let all_fields: Vec<&FieldDef> = core.iter().chain(plugin_fields.iter()).collect();
let field_tokens: Vec<TokenStream> = all_fields
.iter()
.map(|f| {
let name = format_ident!("{}", f.name);
#[expect(
clippy::panic,
reason = "type strings come from hardcoded registry; parse failure is a bug"
)]
let ty: syn::Type = syn::parse_str(f.ty)
.unwrap_or_else(|e| panic!("invalid type `{}` for field `{}`: {e}", f.ty, f.name));
let column_attr = f.column_name.map(|column_name| {
quote! { #[sea_orm(column_name = #column_name)] }
});
let default_attr = f.default_value.map(|value| {
quote! { #[sea_orm(default_value = #value)] }
});
if f.is_primary_key {
quote! {
#column_attr
#default_attr
#[sea_orm(primary_key, auto_increment = false)]
pub #name: #ty,
}
} else {
quote! {
#column_attr
#default_attr
pub #name: #ty,
}
}
})
.collect();
quote! {
mod #mod_ident {
use super::*;
#[derive(Clone, Debug, serde::Serialize, DeriveEntityModel, AuthEntity)]
#[auth(role = #role_str)]
#[sea_orm(table_name = #table_name)]
pub struct Model {
#(#field_tokens)*
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
impl ActiveModelBehavior for ActiveModel {}
}
}
}
pub(crate) fn gen_extra_entity(entity: &ExtraEntitySchema) -> TokenStream {
let mod_ident = format_ident!("{}", entity.mod_name);
let table_name = entity.table_name;
let field_tokens: Vec<TokenStream> = entity
.fields
.iter()
.map(|f| {
let name = format_ident!("{}", f.name);
#[expect(
clippy::panic,
reason = "type strings come from hardcoded registry; parse failure is a bug"
)]
let ty: syn::Type = syn::parse_str(f.ty)
.unwrap_or_else(|e| panic!("invalid type `{}` for field `{}`: {e}", f.ty, f.name));
let column_attr = f.column_name.map(|column_name| {
quote! { #[sea_orm(column_name = #column_name)] }
});
let default_attr = f.default_value.map(|value| {
quote! { #[sea_orm(default_value = #value)] }
});
if f.is_primary_key {
quote! {
#column_attr
#default_attr
#[sea_orm(primary_key, auto_increment = false)]
pub #name: #ty,
}
} else {
quote! {
#column_attr
#default_attr
pub #name: #ty,
}
}
})
.collect();
let derive_attrs = entity.role.map_or_else(
|| {
quote! {
#[derive(Clone, Debug, serde::Serialize, DeriveEntityModel)]
}
},
|role| {
let role_str = match role {
EntityRole::User => "user",
EntityRole::Session => "session",
EntityRole::Account => "account",
EntityRole::Verification => "verification",
};
quote! {
#[derive(Clone, Debug, serde::Serialize, DeriveEntityModel, AuthEntity)]
#[auth(role = #role_str)]
}
},
);
quote! {
mod #mod_ident {
use super::*;
#derive_attrs
#[sea_orm(table_name = #table_name)]
pub struct Model {
#(#field_tokens)*
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
impl ActiveModelBehavior for ActiveModel {}
}
}
}