use crate::abac::AttrSet;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_with::skip_serializing_none;
use std::collections::HashMap;
use std::time::SystemTime;
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct TnId(pub u32);
impl std::fmt::Display for TnId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl Serialize for TnId {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_u32(self.0)
}
}
impl<'de> Deserialize<'de> for TnId {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
Ok(TnId(u32::deserialize(deserializer)?))
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct Timestamp(pub i64);
impl Timestamp {
pub fn now() -> Timestamp {
let res = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap_or_default();
Timestamp(res.as_secs().cast_signed())
}
pub fn from_now(delta: i64) -> Timestamp {
let res = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap_or_default();
Timestamp(res.as_secs().cast_signed() + delta)
}
pub fn add_seconds(&self, seconds: i64) -> Timestamp {
Timestamp(self.0 + seconds)
}
pub fn to_iso_string(&self) -> String {
use chrono::{DateTime, SecondsFormat};
DateTime::from_timestamp(self.0, 0)
.unwrap_or_default()
.to_rfc3339_opts(SecondsFormat::Secs, true)
}
}
impl std::fmt::Display for Timestamp {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl std::cmp::PartialEq for Timestamp {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl std::cmp::PartialOrd for Timestamp {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl std::cmp::Eq for Timestamp {}
impl std::cmp::Ord for Timestamp {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.0.cmp(&other.0)
}
}
impl Serialize for Timestamp {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_i64(self.0)
}
}
impl<'de> Deserialize<'de> for Timestamp {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::{Error, Visitor};
struct TimestampVisitor;
impl Visitor<'_> for TimestampVisitor {
type Value = Timestamp;
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "an integer timestamp or ISO 8601 string")
}
fn visit_i64<E: Error>(self, v: i64) -> Result<Self::Value, E> {
Ok(Timestamp(v))
}
fn visit_u64<E: Error>(self, v: u64) -> Result<Self::Value, E> {
Ok(Timestamp(v.cast_signed()))
}
fn visit_str<E: Error>(self, v: &str) -> Result<Self::Value, E> {
use chrono::DateTime;
DateTime::parse_from_rfc3339(v)
.map(|dt| Timestamp(dt.timestamp()))
.map_err(|_| E::custom("invalid ISO 8601 timestamp"))
}
}
deserializer.deserialize_any(TimestampVisitor)
}
}
pub fn serialize_timestamp_iso<S>(ts: &Timestamp, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
use chrono::{DateTime, SecondsFormat};
let dt = DateTime::from_timestamp(ts.0, 0).unwrap_or_default();
serializer.serialize_str(&dt.to_rfc3339_opts(SecondsFormat::Secs, true))
}
pub fn serialize_timestamp_iso_opt<S>(
ts: &Option<Timestamp>,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match ts {
Some(ts) => serialize_timestamp_iso(ts, serializer),
None => serializer.serialize_none(),
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum Patch<T> {
#[default]
Undefined,
Null,
Value(T),
}
impl<T> Patch<T> {
pub fn is_undefined(&self) -> bool {
matches!(self, Patch::Undefined)
}
pub fn is_null(&self) -> bool {
matches!(self, Patch::Null)
}
pub fn is_value(&self) -> bool {
matches!(self, Patch::Value(_))
}
pub fn value(&self) -> Option<&T> {
match self {
Patch::Value(v) => Some(v),
_ => None,
}
}
pub fn as_option(&self) -> Option<Option<&T>> {
match self {
Patch::Undefined => None,
Patch::Null => Some(None),
Patch::Value(v) => Some(Some(v)),
}
}
pub fn map<U, F>(self, f: F) -> Patch<U>
where
F: FnOnce(T) -> U,
{
match self {
Patch::Undefined => Patch::Undefined,
Patch::Null => Patch::Null,
Patch::Value(v) => Patch::Value(f(v)),
}
}
}
impl<T> Serialize for Patch<T>
where
T: Serialize,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
Patch::Undefined | Patch::Null => serializer.serialize_none(),
Patch::Value(v) => v.serialize(serializer),
}
}
}
impl<'de, T> Deserialize<'de> for Patch<T>
where
T: Deserialize<'de>,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Option::<T>::deserialize(deserializer).map(|opt| match opt {
None => Patch::Null,
Some(v) => Patch::Value(v),
})
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RegisterVerifyCheckRequest {
#[serde(rename = "type")]
pub typ: String, pub id_tag: String,
pub app_domain: Option<String>,
pub token: Option<String>, }
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RegisterRequest {
#[serde(rename = "type")]
pub typ: String, pub id_tag: String,
pub app_domain: Option<String>,
pub email: String,
pub token: String,
pub lang: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RegisterVerifyRequest {
pub id_tag: String,
pub token: String,
}
#[serde_with::skip_serializing_none]
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Profile {
pub id_tag: String,
pub name: String,
#[serde(rename = "type")]
pub r#type: String,
pub profile_pic: Option<String>,
pub cover_pic: Option<String>,
pub keys: Vec<crate::auth_adapter::AuthKey>,
pub x: Option<HashMap<String, String>>,
}
#[serde_with::skip_serializing_none]
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ProfileBase {
pub id_tag: String,
pub name: String,
#[serde(rename = "type")]
pub r#type: String,
pub profile_pic: Option<String>,
pub keys: Vec<crate::auth_adapter::AuthKey>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AppDomainRes {
pub app_domain: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ProfilePatch {
#[serde(default)]
pub name: Patch<String>,
#[serde(default)]
pub x: Option<HashMap<String, Option<String>>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AdminProfilePatch {
#[serde(default)]
pub name: Patch<String>,
#[serde(default)]
pub roles: Patch<Option<Vec<String>>>,
#[serde(default)]
pub status: Patch<crate::meta_adapter::ProfileStatus>,
}
#[skip_serializing_none]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ProfileInfo {
pub id_tag: String,
pub name: String,
#[serde(rename = "type")]
pub r#type: Option<String>,
pub profile_pic: Option<String>, pub status: Option<crate::meta_adapter::ProfileStatus>,
pub connected: Option<bool>,
pub following: Option<bool>,
pub follower: Option<bool>,
pub trust: Option<crate::meta_adapter::ProfileTrust>,
pub roles: Option<Vec<String>>,
#[serde(
serialize_with = "serialize_timestamp_iso_opt",
skip_serializing_if = "Option::is_none"
)]
pub created_at: Option<Timestamp>,
pub x: Option<HashMap<String, String>>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CreateCommunityRequest {
#[serde(rename = "type")]
pub typ: String, pub name: Option<String>,
pub profile_pic: Option<String>,
pub app_domain: Option<String>, pub invite_ref: Option<String>, }
#[skip_serializing_none]
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CommunityProfileResponse {
pub id_tag: String,
pub name: String,
#[serde(rename = "type")]
pub r#type: String,
pub profile_pic: Option<String>,
#[serde(serialize_with = "serialize_timestamp_iso")]
pub created_at: Timestamp,
pub onboarding: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CreateActionRequest {
#[serde(rename = "type")]
pub r#type: String, pub sub_type: Option<String>, pub parent_id: Option<String>,
pub content: String,
pub attachments: Option<Vec<String>>, pub audience: Option<Vec<String>>,
}
#[skip_serializing_none]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ActionResponse {
pub action_id: String,
pub action_token: String,
#[serde(rename = "type")]
pub r#type: String,
pub sub_type: Option<String>,
pub parent_id: Option<String>,
pub root_id: Option<String>,
pub content: String,
pub attachments: Vec<String>,
pub issuer_tag: String,
#[serde(serialize_with = "serialize_timestamp_iso")]
pub created_at: Timestamp,
}
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ListActionsQuery {
#[serde(rename = "type")]
pub r#type: Option<String>,
pub parent_id: Option<String>,
pub offset: Option<usize>,
pub limit: Option<usize>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FileUploadResponse {
pub file_id: String,
pub descriptor: String,
pub variants: Vec<FileVariantInfo>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FileVariantInfo {
pub variant_id: String,
pub format: String,
pub size: u64,
pub resolution: Option<(u32, u32)>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TagInfo {
pub tag: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub count: Option<u32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PaginationInfo {
pub offset: usize,
pub limit: usize,
pub total: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CursorPaginationInfo {
pub next_cursor: Option<String>,
pub has_more: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CursorData {
pub s: String,
pub v: serde_json::Value,
pub id: String,
}
impl CursorData {
pub fn new(sort_field: &str, sort_value: serde_json::Value, item_id: &str) -> Self {
Self { s: sort_field.to_string(), v: sort_value, id: item_id.to_string() }
}
pub fn encode(&self) -> String {
use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD};
let json = serde_json::to_string(self).unwrap_or_default();
URL_SAFE_NO_PAD.encode(json.as_bytes())
}
pub fn decode(cursor: &str) -> Option<Self> {
use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD};
let bytes = URL_SAFE_NO_PAD.decode(cursor).ok()?;
let json = String::from_utf8(bytes).ok()?;
serde_json::from_str(&json).ok()
}
pub fn timestamp(&self) -> Option<i64> {
self.v.as_i64()
}
pub fn string_value(&self) -> Option<&str> {
self.v.as_str()
}
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ApiResponse<T> {
pub data: T,
#[serde(skip_serializing_if = "Option::is_none")]
pub pagination: Option<PaginationInfo>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cursor_pagination: Option<CursorPaginationInfo>,
#[serde(serialize_with = "serialize_timestamp_iso")]
pub time: Timestamp,
#[serde(skip_serializing_if = "Option::is_none")]
pub req_id: Option<String>,
}
impl<T> ApiResponse<T> {
pub fn new(data: T) -> Self {
Self {
data,
pagination: None,
cursor_pagination: None,
time: Timestamp::now(),
req_id: None,
}
}
pub fn with_pagination(data: T, offset: usize, limit: usize, total: usize) -> Self {
Self {
data,
pagination: Some(PaginationInfo { offset, limit, total }),
cursor_pagination: None,
time: Timestamp::now(),
req_id: None,
}
}
pub fn with_cursor_pagination(data: T, next_cursor: Option<String>, has_more: bool) -> Self {
Self {
data,
pagination: None,
cursor_pagination: Some(CursorPaginationInfo { next_cursor, has_more }),
time: Timestamp::now(),
req_id: None,
}
}
pub fn with_req_id(mut self, req_id: String) -> Self {
self.req_id = Some(req_id);
self
}
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ErrorResponse {
pub error: ErrorDetails,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ErrorDetails {
pub code: String,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub details: Option<serde_json::Value>,
}
impl ErrorResponse {
pub fn new(code: String, message: String) -> Self {
Self { error: ErrorDetails { code, message, details: None } }
}
pub fn with_details(mut self, details: serde_json::Value) -> Self {
self.error.details = Some(details);
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum AccessLevel {
None,
Read,
Comment,
Write,
Admin,
}
impl AccessLevel {
pub fn as_str(&self) -> &'static str {
match self {
Self::None => "none",
Self::Read => "read",
Self::Comment => "comment",
Self::Write => "write",
Self::Admin => "admin",
}
}
pub fn min(self, other: Self) -> Self {
match (self, other) {
(Self::None, _) | (_, Self::None) => Self::None,
(Self::Read, _) | (_, Self::Read) => Self::Read,
(Self::Comment, _) | (_, Self::Comment) => Self::Comment,
(Self::Write, _) | (_, Self::Write) => Self::Write,
(Self::Admin, Self::Admin) => Self::Admin,
}
}
pub fn max(self, other: Self) -> Self {
match (self, other) {
(Self::Admin, _) | (_, Self::Admin) => Self::Admin,
(Self::Write, _) | (_, Self::Write) => Self::Write,
(Self::Comment, _) | (_, Self::Comment) => Self::Comment,
(Self::Read, _) | (_, Self::Read) => Self::Read,
(Self::None, Self::None) => Self::None,
}
}
pub fn from_perm_char(c: char) -> Self {
match c {
'W' | 'A' => Self::Write,
'C' => Self::Comment,
_ => Self::Read,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TokenScope {
File { file_id: String, access: AccessLevel },
ApkgPublish,
}
impl TokenScope {
pub fn parse(s: &str) -> Option<Self> {
if s == "apkg:publish" {
return Some(Self::ApkgPublish);
}
let parts: Vec<&str> = s.split(':').collect();
if parts.len() == 3 && parts[0] == "file" {
let access = match parts[2] {
"W" => AccessLevel::Write,
"C" => AccessLevel::Comment,
_ => AccessLevel::Read, };
return Some(Self::File { file_id: parts[1].to_string(), access });
}
None
}
pub fn file_id(&self) -> Option<&str> {
match self {
Self::File { file_id, .. } => Some(file_id),
Self::ApkgPublish => None,
}
}
pub fn file_access(&self) -> Option<AccessLevel> {
match self {
Self::File { access, .. } => Some(*access),
Self::ApkgPublish => None,
}
}
pub fn matches_file(&self, target_file_id: &str) -> bool {
match self {
Self::File { file_id, .. } => file_id == target_file_id,
Self::ApkgPublish => false,
}
}
}
#[derive(Debug, Clone)]
pub struct ProfileAttrs {
pub id_tag: Box<str>,
pub profile_type: Box<str>,
pub tenant_tag: Box<str>,
pub roles: Vec<Box<str>>,
pub status: Box<str>,
pub following: bool,
pub connected: bool,
pub visibility: Box<str>,
}
impl AttrSet for ProfileAttrs {
fn get(&self, key: &str) -> Option<&str> {
match key {
"id_tag" => Some(&self.id_tag),
"profile_type" => Some(&self.profile_type),
"tenant_tag" | "owner_id_tag" => Some(&self.tenant_tag),
"status" => Some(&self.status),
"following" => Some(if self.following { "true" } else { "false" }),
"connected" => Some(if self.connected { "true" } else { "false" }),
"visibility" => Some(&self.visibility),
_ => None,
}
}
fn get_list(&self, key: &str) -> Option<Vec<&str>> {
match key {
"roles" => Some(self.roles.iter().map(AsRef::as_ref).collect()),
_ => None,
}
}
}
#[derive(Debug, Clone)]
pub struct ActionAttrs {
pub typ: Box<str>,
pub sub_typ: Option<Box<str>>,
pub tenant_id_tag: Box<str>,
pub issuer_id_tag: Box<str>,
pub parent_id: Option<Box<str>>,
pub root_id: Option<Box<str>>,
pub audience_tag: Vec<Box<str>>,
pub tags: Vec<Box<str>>,
pub visibility: Box<str>,
pub following: bool,
pub connected: bool,
}
impl AttrSet for ActionAttrs {
fn get(&self, key: &str) -> Option<&str> {
match key {
"type" => Some(&self.typ),
"sub_type" => self.sub_typ.as_deref(),
"tenant_id_tag" | "owner_id_tag" => Some(&self.tenant_id_tag),
"issuer_id_tag" => Some(&self.issuer_id_tag),
"parent_id" => self.parent_id.as_deref(),
"root_id" => self.root_id.as_deref(),
"visibility" => Some(&self.visibility),
"following" => Some(if self.following { "true" } else { "false" }),
"connected" => Some(if self.connected { "true" } else { "false" }),
_ => None,
}
}
fn get_list(&self, key: &str) -> Option<Vec<&str>> {
match key {
"audience_tag" => Some(self.audience_tag.iter().map(AsRef::as_ref).collect()),
"tags" => Some(self.tags.iter().map(AsRef::as_ref).collect()),
_ => None,
}
}
}
#[derive(Debug, Clone)]
pub struct FileAttrs {
pub file_id: Box<str>,
pub owner_id_tag: Box<str>,
pub mime_type: Box<str>,
pub tags: Vec<Box<str>>,
pub visibility: Box<str>,
pub access_level: AccessLevel,
pub following: bool,
pub connected: bool,
}
impl AttrSet for FileAttrs {
fn get(&self, key: &str) -> Option<&str> {
match key {
"file_id" => Some(&self.file_id),
"owner_id_tag" => Some(&self.owner_id_tag),
"mime_type" => Some(&self.mime_type),
"visibility" => Some(&self.visibility),
"access_level" => Some(self.access_level.as_str()),
"following" => Some(if self.following { "true" } else { "false" }),
"connected" => Some(if self.connected { "true" } else { "false" }),
_ => None,
}
}
fn get_list(&self, key: &str) -> Option<Vec<&str>> {
match key {
"tags" => Some(self.tags.iter().map(AsRef::as_ref).collect()),
_ => None,
}
}
}
#[derive(Debug, Clone)]
pub struct SubjectAttrs {
pub id_tag: Box<str>,
pub roles: Vec<Box<str>>,
pub tier: Box<str>, pub quota_remaining_bytes: Box<str>, pub rate_limit_remaining: Box<str>, pub banned: bool,
pub email_verified: bool,
}
impl AttrSet for SubjectAttrs {
fn get(&self, key: &str) -> Option<&str> {
match key {
"id_tag" => Some(&self.id_tag),
"tier" => Some(&self.tier),
"quota_remaining" | "quota_remaining_bytes" => Some(&self.quota_remaining_bytes),
"rate_limit_remaining" => Some(&self.rate_limit_remaining),
"banned" => Some(if self.banned { "true" } else { "false" }),
"email_verified" => Some(if self.email_verified { "true" } else { "false" }),
_ => None,
}
}
fn get_list(&self, key: &str) -> Option<Vec<&str>> {
match key {
"roles" => Some(self.roles.iter().map(AsRef::as_ref).collect()),
_ => None,
}
}
}