use super::{AddMemberInput, EndpointContext, OrganizationPlugin};
use better_auth_core::endpoint_input::ValidatedBody;
use better_auth_core::{
AuthContext, AuthError, AuthRecordFields, AuthRequest, AuthResponse, AuthResult, AuthRoute,
AuthSchema, FieldValue, FromFieldMap, HttpMethod, NativeRequest,
};
use serde_json::Value;
enum MemberInput {
Json(Option<Value>),
Native(FieldValue),
}
pub struct OrganizationApi<'a, S: AuthSchema> {
plugin: OrganizationPlugin,
context: &'a AuthContext<S>,
source: NativeRequest<'a>,
transaction: Option<&'a dyn better_auth_core::store::AuthTransaction<S>>,
}
impl<'a, S: AuthSchema> OrganizationApi<'a, S> {
pub fn from_context(context: &'a AuthContext<S>) -> AuthResult<Self> {
let plugin = context
.extensions
.get::<OrganizationPlugin>()
.ok_or_else(|| AuthError::config("OrganizationPlugin is not registered"))?;
Ok(Self::with_plugin(plugin.clone(), context))
}
pub(super) fn with_plugin(plugin: OrganizationPlugin, context: &'a AuthContext<S>) -> Self {
Self {
plugin,
context,
source: NativeRequest::default(),
transaction: None,
}
}
pub fn from_endpoint(endpoint: &EndpointContext<'a, S>) -> AuthResult<Self> {
let mut api = Self::from_context(endpoint.auth)?;
api.transaction = endpoint.transaction;
Ok(api)
}
pub fn with_request(mut self, source: NativeRequest<'a>) -> Self {
self.source = source;
self
}
pub async fn add_member(&self, body: Option<Value>) -> AuthResult<Value> {
self.add_member_response(MemberInput::Json(body))
.await?
.body
.json()?
.ok_or_else(|| AuthError::internal("Native addMember returned undefined"))
}
pub(super) async fn add_member_value(&self, body: FieldValue) -> AuthResult<FieldValue> {
self.add_member_response(MemberInput::Native(body))
.await?
.body
.field_value()
}
async fn add_member_response(&self, body: MemberInput) -> AuthResult<AuthResponse> {
let schema = self.plugin.config.schema.member.clone();
let route = AuthRoute::server_only(HttpMethod::Post, "addMember")
.body_validator(move |request| validate(request, &schema));
let handler = |request: AuthRequest, context: std::sync::Arc<AuthContext<S>>| async move {
let input = request
.validated_body::<AddMemberInput>()
.ok_or_else(|| AuthError::internal("Missing validated member input"))?;
let mut endpoint = EndpointContext::native(
Some(&request),
request.original_request(),
request.input_field_value()?,
&context,
);
endpoint.transaction = self.transaction;
let member = self
.plugin
.add_member_core(input.clone(), &request, &endpoint)
.await?;
Ok(AuthResponse::native(None, member.field_values()?.into()))
};
match body {
MemberInput::Json(body) => {
self.context
.dispatch_native(self.source, route, body, None, handler)
.await
}
MemberInput::Native(body) => {
self.context
.dispatch_native_value(self.source, route, body, FieldValue::Undefined, handler)
.await
}
}
}
}
pub(super) fn validate(
request: &AuthRequest,
schema: &better_auth_core::user_fields::UserConfig,
) -> AuthResult<ValidatedBody> {
use super::super::input::{self, BaseField};
let raw = request.input_field_value()?;
let mut errors = Vec::new();
let Some(object) = raw.as_object() else {
return Err(AuthError::RequestValidation {
message: input::native_invalid_type("body", "object", Some(&raw)),
});
};
let body = input::native_fields(
schema,
&object.snapshot_fields()?,
&[
("userId", BaseField::CoercedString, true),
("role", BaseField::Roles, true),
("organizationId", BaseField::String, false),
("teamId", BaseField::String, false),
],
"body",
false,
false,
&mut errors,
)?;
input::finish(errors)?;
Ok(ValidatedBody::native(
body.clone().into(),
AddMemberInput::from_field_values(body)?,
))
}