use crate::util::RequestBuilderExt;
use async_stream::try_stream;
use futures_core::stream::Stream;
use reqwest::Method;
use serde::{Deserialize, Serialize};
use time::OffsetDateTime;
use uuid::Uuid;
use crate::client::roles::{Permission, Role};
use crate::client::Client;
use crate::error::Error;
use crate::serde::{Empty, Paginated};
use crate::util::StrIteratorExt;
const USER_PATH: [&str; 4] = ["identity", "resources", "users", "v1"];
const VENDOR_USER_PATH: [&str; 5] = ["identity", "resources", "vendor-only", "users", "v1"];
#[derive(Debug, Clone)]
pub struct UserListConfig {
tenant_id: Option<Uuid>,
page_size: u64,
}
impl Default for UserListConfig {
fn default() -> UserListConfig {
UserListConfig {
tenant_id: None,
page_size: 50,
}
}
}
impl UserListConfig {
pub fn tenant_id(mut self, tenant_id: Uuid) -> Self {
self.tenant_id = Some(tenant_id);
self
}
pub fn page_size(mut self, page_size: u64) -> Self {
self.page_size = page_size;
self
}
}
#[derive(Debug, Clone)]
pub struct UserListPartConfig {
tenant_id: Option<Uuid>,
page_size: u64,
max_pages: u64,
starting_page: Option<u64>,
}
impl Default for UserListPartConfig {
fn default() -> UserListPartConfig {
UserListPartConfig {
tenant_id: None,
page_size: 50,
max_pages: 100,
starting_page: None,
}
}
}
impl UserListPartConfig {
pub fn tenant_id(mut self, tenant_id: Uuid) -> Self {
self.tenant_id = Some(tenant_id);
self
}
pub fn page_size(mut self, page_size: u64) -> Self {
self.page_size = page_size;
self
}
pub fn starting_page(mut self, starting_page: u64) -> Self {
self.starting_page = Some(starting_page);
self
}
pub fn max_pages(mut self, max_pages: u64) -> Self {
self.max_pages = max_pages;
self
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UserRequest<'a> {
#[serde(skip)]
pub tenant_id: Uuid,
pub name: &'a str,
pub email: &'a str,
pub metadata: serde_json::Value,
pub skip_invite_email: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CreatedUser {
pub id: Uuid,
pub name: String,
pub email: String,
#[serde(default = "crate::serde::empty_json_object")]
#[serde(deserialize_with = "crate::serde::nested_json::deserialize")]
pub metadata: serde_json::Value,
pub roles: Vec<Role>,
pub permissions: Vec<Permission>,
#[serde(with = "time::serde::rfc3339")]
pub created_at: OffsetDateTime,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct WebhookUser {
pub id: Uuid,
pub name: Option<String>,
pub email: String,
#[serde(default = "crate::serde::empty_json_object")]
#[serde(deserialize_with = "crate::serde::nested_json::deserialize")]
pub metadata: serde_json::Value,
pub roles: Vec<Role>,
pub permissions: Vec<Permission>,
#[serde(with = "time::serde::rfc3339")]
pub created_at: OffsetDateTime,
pub activated_for_tenant: Option<bool>,
pub is_locked: Option<bool>,
pub managed_by: String,
pub mfa_enrolled: bool,
pub mfa_bypass: Option<bool>,
pub phone_number: Option<String>,
pub profile_picture_url: Option<String>,
pub provider: String,
pub sub: Uuid,
pub tenant_id: Uuid,
pub tenant_ids: Option<Vec<Uuid>>,
pub tenants: Option<Vec<WebhookTenantBinding>>,
pub verified: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct User {
pub id: Uuid,
pub name: String,
pub email: String,
#[serde(default = "crate::serde::empty_json_object")]
#[serde(deserialize_with = "crate::serde::nested_json::deserialize")]
pub metadata: serde_json::Value,
pub tenants: Vec<TenantBinding>,
#[serde(with = "time::serde::rfc3339")]
pub created_at: OffsetDateTime,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct WebhookTenantBinding {
pub tenant_id: Uuid,
pub roles: Option<Vec<Role>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TenantBinding {
pub tenant_id: Uuid,
pub roles: Vec<Role>,
}
impl Client {
pub fn list_users(
&self,
config: UserListConfig,
) -> impl Stream<Item = Result<User, Error>> + '_ {
try_stream! {
let mut page = 0;
loop {
let mut req = self.build_request(Method::GET, USER_PATH);
if let Some(tenant_id) = config.tenant_id {
req = req.tenant(tenant_id);
}
let req = req.query(&[
("_limit", &*config.page_size.to_string()),
("_offset", &*page.to_string())
]);
let res: Paginated<User> = self.send_request(req).await?;
for user in res.items {
yield user;
}
page += 1;
if page >= res.metadata.total_pages {
break;
}
}
}
}
pub fn list_users_part(
&self,
config: UserListPartConfig,
) -> impl Stream<Item = Result<User, Error>> + '_ {
try_stream! {
let mut page = config.starting_page.unwrap_or(0);
let hault_page = config.max_pages + page;
loop {
let mut req = self.build_request(Method::GET, USER_PATH);
if let Some(tenant_id) = config.tenant_id {
req = req.tenant(tenant_id);
}
let req = req.query(&[
("_limit", &*config.page_size.to_string()),
("_offset", &*page.to_string())
]);
let res: Paginated<User> = self.send_request(req).await?;
for user in res.items {
yield user
}
page += 1;
if page >= res.metadata.total_pages {
break;
} else if page >= hault_page {
Err(Error::PaginationHault(page))?
}
}
}
}
pub async fn create_user(&self, user: &UserRequest<'_>) -> Result<CreatedUser, Error> {
let req = self.build_request(Method::POST, USER_PATH);
let req = req.tenant(user.tenant_id);
let req = req.json(user);
let res = self.send_request(req).await?;
Ok(res)
}
pub async fn get_user(&self, id: Uuid) -> Result<User, Error> {
let req = self.build_request(Method::GET, VENDOR_USER_PATH.chain_one(id));
let res = self.send_request(req).await?;
Ok(res)
}
pub async fn delete_user(&self, id: Uuid) -> Result<(), Error> {
let req = self.build_request(Method::DELETE, USER_PATH.chain_one(id));
let _: Empty = self.send_request(req).await?;
Ok(())
}
}