use serde::{Deserialize, Serialize};
use url::Url;
#[cfg(feature = "utoipa")]
use utoipa::{IntoParams, ToSchema};
#[cfg(feature = "validator")]
use validator::Validate;
use crate::v1::types::error::Error;
use crate::v1::types::user_config::UserConfigUser;
use crate::v1::types::user_status::Status;
use crate::v1::types::util::{some_option, Diff, Time};
use crate::v1::types::MediaId;
use super::email::EmailInfo;
use super::user_config::UserConfigGlobal;
use super::{ChannelId, RoomId, UserId, UserVerId};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(ToSchema))]
pub struct UserWebhook {
pub room_id: Option<RoomId>,
pub channel_id: ChannelId,
pub creator_id: UserId,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(ToSchema))]
#[cfg_attr(feature = "validator", derive(Validate))]
pub struct User {
pub id: UserId,
pub version_id: UserVerId,
#[cfg_attr(feature = "utoipa", schema(min_length = 1, max_length = 64))]
#[cfg_attr(feature = "validator", validate(length(min = 1, max = 64)))]
pub name: String,
#[cfg_attr(
feature = "utoipa",
schema(required = false, min_length = 1, max_length = 8192)
)]
#[cfg_attr(feature = "validator", validate(length(min = 1, max = 8192)))]
pub description: Option<String>,
pub avatar: Option<MediaId>,
pub banner: Option<MediaId>,
pub bot: Option<Bot>,
pub system: bool,
pub puppet: Option<Puppet>,
pub webhook: Option<UserWebhook>,
pub suspended: Option<Suspended>,
pub status: Status,
pub registered_at: Option<Time>,
pub deleted_at: Option<Time>,
#[serde(skip_serializing_if = "Option::is_none")]
pub emails: Option<Vec<EmailInfo>>,
pub user_config: Option<UserConfigUser>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(ToSchema))]
pub struct Suspended {
pub created_at: Time,
pub expires_at: Option<Time>,
pub reason: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(ToSchema))]
pub struct Puppet {
pub owner_id: UserId,
pub external_platform: ExternalPlatform,
#[cfg_attr(
feature = "utoipa",
schema(required = false, min_length = 1, max_length = 8192)
)]
pub external_id: String,
pub external_url: Option<Url>,
pub alias_id: Option<UserId>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(ToSchema))]
pub struct Bot {
pub owner_id: UserId,
pub access: BotAccess,
pub is_bridge: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(ToSchema))]
#[cfg_attr(feature = "validator", derive(Validate))]
pub struct UserWithPrivate {
#[serde(flatten)]
pub inner: User,
pub config: UserConfigGlobal,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(ToSchema))]
#[cfg_attr(feature = "validator", derive(Validate))]
pub struct UserCreate {
#[cfg_attr(feature = "utoipa", schema(min_length = 1, max_length = 64))]
#[cfg_attr(feature = "validator", validate(length(min = 1, max = 64)))]
pub name: String,
#[cfg_attr(
feature = "utoipa",
schema(required = false, min_length = 1, max_length = 8192)
)]
#[cfg_attr(feature = "validator", validate(length(min = 1, max = 8192)))]
pub description: Option<String>,
}
pub struct BotCreate;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(ToSchema))]
#[cfg_attr(feature = "validator", derive(Validate))]
pub struct PuppetCreate {
#[cfg_attr(feature = "utoipa", schema(min_length = 1, max_length = 64))]
#[cfg_attr(feature = "validator", validate(length(min = 1, max = 64)))]
pub name: String,
#[cfg_attr(
feature = "utoipa",
schema(required = false, min_length = 1, max_length = 8192)
)]
#[cfg_attr(feature = "validator", validate(length(min = 1, max = 8192)))]
pub description: Option<String>,
pub bot: bool,
pub system: bool,
}
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(ToSchema))]
#[cfg_attr(feature = "validator", derive(Validate))]
pub struct UserPatch {
#[cfg_attr(
feature = "utoipa",
schema(required = false, min_length = 1, max_length = 64)
)]
#[cfg_attr(feature = "validator", validate(length(min = 1, max = 64)))]
pub name: Option<String>,
#[cfg_attr(
feature = "utoipa",
schema(required = false, min_length = 1, max_length = 8192)
)]
#[cfg_attr(feature = "validator", validate(length(min = 1, max = 8192)))]
#[serde(default, deserialize_with = "some_option")]
pub description: Option<Option<String>>,
#[serde(default, deserialize_with = "some_option")]
pub avatar: Option<Option<MediaId>>,
#[serde(default, deserialize_with = "some_option")]
pub banner: Option<Option<MediaId>>,
}
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(ToSchema))]
pub enum BotAccess {
#[default]
Private,
Public {
is_discoverable: bool,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(ToSchema))]
#[serde(untagged)]
pub enum ExternalPlatform {
Discord,
Other(String),
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(ToSchema))]
pub enum UserState {
Active,
Suspended,
Deleted,
}
impl Diff<User> for UserPatch {
fn changes(&self, other: &User) -> bool {
self.name.changes(&other.name)
|| self.description.changes(&other.description)
|| self.avatar.changes(&other.avatar)
|| self.banner.changes(&other.banner)
}
}
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(ToSchema))]
#[cfg_attr(feature = "validator", derive(Validate))]
pub struct Relationship {
pub relation: Option<RelationshipType>,
#[serde(flatten)]
pub ignore: Option<Ignore>,
}
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(ToSchema))]
pub struct RelationshipWithUserId {
#[serde(flatten)]
pub inner: Relationship,
pub user_id: UserId,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(ToSchema))]
pub struct UserWithRelationship {
#[serde(flatten)]
pub inner: User,
pub relationship: Relationship,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(ToSchema))]
#[cfg_attr(feature = "validator", derive(Validate))]
pub struct RelationshipPatch {
#[serde(default, deserialize_with = "some_option")]
pub relation: Option<Option<RelationshipType>>,
#[cfg_attr(feature = "utoipa", schema(required = false))]
#[serde(default, flatten, deserialize_with = "some_option")]
pub ignore: Option<Option<Ignore>>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(ToSchema))]
pub struct Ignore {
pub until: Option<Time>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "utoipa", derive(ToSchema))]
pub enum RelationshipType {
Friend,
Outgoing,
Incoming,
Block,
}
impl Diff<Relationship> for RelationshipPatch {
fn changes(&self, other: &Relationship) -> bool {
self.relation.changes(&other.relation) || self.ignore.changes(&other.ignore)
}
}
impl User {
pub fn is_suspended(&self) -> bool {
if let Some(s) = &self.suspended {
if s.expires_at.is_some_and(|t| *t < *Time::now_utc()) {
false
} else {
true
}
} else {
false
}
}
pub fn ensure_unsuspended(&self) -> Result<(), Error> {
if self.is_suspended() {
Err(Error::UserSuspended)
} else {
Ok(())
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "utoipa", derive(ToSchema))]
#[serde(rename_all = "snake_case")]
pub enum UserListFilter {
Guest,
Registered,
Bot,
Puppet,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "utoipa", derive(ToSchema, IntoParams))]
#[serde(rename_all = "snake_case")]
pub struct UserListParams {
pub filter: Option<UserListFilter>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "utoipa", derive(ToSchema, IntoParams))]
pub struct SuspendRequest {
pub expires_at: Option<Time>,
}
impl User {
pub fn can_dm(&self) -> bool {
self.webhook.is_none()
}
pub fn can_friend(&self) -> bool {
self.webhook.is_none() && self.bot.is_none() && self.puppet.is_none()
}
}