use better_auth_core::{
AuthContext, AuthError, AuthRecordFields, AuthRequest, AuthResponse, AuthResult, AuthSchema,
FieldValue,
};
use super::{CreateBody, TeamBody, authorize, find_team, list_teams, optional_string};
use crate::plugins::organization::{
OrganizationConfig, fields,
handlers::{optional_session, request_present},
hooks::*,
request,
};
pub(super) async fn create(
req: &AuthRequest,
ctx: &AuthContext<impl AuthSchema>,
config: &OrganizationConfig,
) -> AuthResult<AuthResponse> {
let session = optional_session(req, ctx).await?;
if session.is_none() && request_present(req, ctx) {
return Err(AuthError::from_status(
"UNAUTHORIZED",
better_auth_core::FieldValue::Undefined,
));
}
let body: CreateBody = request::read(req, &config.schema)?;
let additional_fields = body.additional_fields.clone();
let organization_id = optional_string(body.organization_id, "body.organizationId")?;
let org = organization_id
.as_deref()
.filter(|id| !id.is_empty())
.map(FieldValue::from)
.map(Ok)
.or_else(|| {
session
.as_ref()
.map(|session| session.session_property("activeOrganizationId"))
})
.transpose()?
.filter(FieldValue::is_truthy)
.ok_or_else(|| {
AuthError::from_code(
"BAD_REQUEST",
"NO_ACTIVE_ORGANIZATION",
"No active organization",
)
})?;
if let Some(session) = &session {
let member = ctx
.database
.get_member_with_user_value(&org, &session.user_property("id")?)
.await?
.ok_or_else(|| {
AuthError::from_code(
"FORBIDDEN",
"YOU_ARE_NOT_ALLOWED_TO_INVITE_USERS_TO_THIS_ORGANIZATION",
"You are not allowed to invite users to this organization",
)
})?;
authorize(
&member.member,
&org,
("team", "create"),
AuthError::from_code(
"FORBIDDEN",
"YOU_ARE_NOT_ALLOWED_TO_CREATE_TEAMS_IN_THIS_ORGANIZATION",
"You are not allowed to create teams in this organization",
),
config,
ctx,
)
.await?;
}
let teams = list_teams(&org, ctx, config).await?;
if let Some(maximum) = config
.team_limit(
OrganizationTeamLimit {
organization_id: &org,
session: session.as_ref(),
},
OrganizationEndpoint::new(ctx, Some(req)),
)
.await?
.filter(|limit| *limit > 0)
&& teams.len() >= maximum
{
return Err(AuthError::from_code(
"BAD_REQUEST",
"YOU_HAVE_REACHED_THE_MAXIMUM_NUMBER_OF_TEAMS",
"You have reached the maximum number of teams",
));
}
let organization = ctx
.database
.get_organization_by_id_value(&org)
.await?
.ok_or_else(|| {
AuthError::from_code(
"BAD_REQUEST",
"ORGANIZATION_NOT_FOUND",
"Organization not found",
)
})?;
let organization_view = fields::organization(&organization, ctx)?;
let created_at = chrono::Utc::now();
let mut data = OrganizationTeamDraft {
id: additional_fields
.get("id")
.and_then(better_auth_core::FieldValue::as_str)
.map(str::to_owned),
additional_fields,
name: body.name,
organization_id: better_auth_core::SchemaValue::from_field(org),
created_at: None,
updated_at: None,
};
if let Some(hooks) = &config.hooks {
hooks
.before_create_team(
&mut data,
&organization_view,
session
.as_ref()
.map(|session| session.user_value())
.transpose()?
.as_ref(),
)
.await?;
}
let team = ctx
.database
.create_team(data.into_create(created_at, Some(created_at)))
.await?;
let team = fields::team(team, config)?;
if let Some(hooks) = &config.hooks {
hooks
.after_create_team(OrganizationTeamEvent {
team: &team,
user: session
.as_ref()
.map(|session| session.user_value())
.transpose()?
.as_ref(),
organization: &organization_view,
})
.await?;
}
Ok(AuthResponse::native(None, team.field_values()?.into()))
}
pub(super) async fn remove(
req: &AuthRequest,
ctx: &AuthContext<impl AuthSchema>,
config: &OrganizationConfig,
) -> AuthResult<AuthResponse> {
let session = optional_session(req, ctx).await?;
let body: TeamBody = request::read(req, &config.schema)?;
let org = body
.organization_id
.as_deref()
.filter(|id| !id.is_empty())
.map(FieldValue::from)
.map(Ok)
.or_else(|| {
session
.as_ref()
.map(|session| session.session_property("activeOrganizationId"))
})
.transpose()?
.filter(FieldValue::is_truthy)
.ok_or_else(|| {
AuthError::from_code(
"BAD_REQUEST",
"NO_ACTIVE_ORGANIZATION",
"No active organization",
)
})?;
if session.is_none() && request_present(req, ctx) {
return Err(AuthError::from_status(
"UNAUTHORIZED",
better_auth_core::FieldValue::Undefined,
));
}
if let Some(session) = &session {
let member = ctx
.database
.get_member_with_user_value(&org, &session.user_property("id")?)
.await?
.ok_or_else(|| {
AuthError::from_code(
"FORBIDDEN",
"YOU_ARE_NOT_ALLOWED_TO_DELETE_THIS_TEAM",
"You are not allowed to delete this team",
)
})?;
if session
.session_property("activeTeamId")?
.strict_equals(&body.team_id.as_str().into())
{
return Err(AuthError::from_code(
"FORBIDDEN",
"YOU_ARE_NOT_ALLOWED_TO_DELETE_THIS_TEAM",
"You are not allowed to delete this team",
));
}
authorize(
&member.member,
&org,
("team", "delete"),
AuthError::from_code(
"FORBIDDEN",
"YOU_ARE_NOT_ALLOWED_TO_DELETE_TEAMS_IN_THIS_ORGANIZATION",
"You are not allowed to delete teams in this organization",
),
config,
ctx,
)
.await?;
}
let team = find_team(&body.team_id.as_str().into(), &org, ctx, config).await?;
if !team.organization_id.field_value().strict_equals(&org) {
return Err(AuthError::from_code(
"BAD_REQUEST",
"TEAM_NOT_FOUND",
"Team not found",
));
}
if !config.teams.allow_removing_all_teams && list_teams(&org, ctx, config).await?.len() <= 1 {
return Err(AuthError::from_code(
"BAD_REQUEST",
"UNABLE_TO_REMOVE_LAST_TEAM",
"Unable to remove last team",
));
}
let organization = ctx
.database
.get_organization_by_id_value(&org)
.await?
.ok_or_else(|| {
AuthError::from_code(
"BAD_REQUEST",
"ORGANIZATION_NOT_FOUND",
"Organization not found",
)
})?;
let organization_view = fields::organization(&organization, ctx)?;
if let Some(hooks) = &config.hooks {
hooks
.before_delete_team(OrganizationTeamEvent {
team: &team,
user: session
.as_ref()
.map(|session| session.user_value())
.transpose()?
.as_ref(),
organization: &organization_view,
})
.await?;
}
ctx.database
.delete_team_for_organization_value(&team.id.field_value(), &org)
.await?;
if let Some(hooks) = &config.hooks {
hooks
.after_delete_team(OrganizationTeamEvent {
team: &team,
user: session
.as_ref()
.map(|session| session.user_value())
.transpose()?
.as_ref(),
organization: &organization_view,
})
.await?;
}
AuthResponse::json(
None,
&serde_json::json!({"message":"Team removed successfully."}),
)
}