1use crate::abac::AttrSet;
4use serde::{Deserialize, Deserializer, Serialize, Serializer};
5use serde_with::skip_serializing_none;
6use std::time::SystemTime;
7
8#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
12pub struct TnId(pub u32);
13
14impl std::fmt::Display for TnId {
15 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
16 write!(f, "{}", self.0)
17 }
18}
19
20impl Serialize for TnId {
21 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
22 where
23 S: serde::Serializer,
24 {
25 serializer.serialize_u32(self.0)
26 }
27}
28
29impl<'de> Deserialize<'de> for TnId {
30 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
31 where
32 D: serde::Deserializer<'de>,
33 {
34 Ok(TnId(u32::deserialize(deserializer)?))
35 }
36}
37
38#[derive(Clone, Copy, Debug, Default)]
42pub struct Timestamp(pub i64);
43
44impl Timestamp {
45 pub fn now() -> Timestamp {
46 let res = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap_or_default();
47 Timestamp(res.as_secs() as i64)
48 }
49
50 pub fn from_now(delta: i64) -> Timestamp {
51 let res = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap_or_default();
52 Timestamp(res.as_secs() as i64 + delta)
53 }
54
55 pub fn add_seconds(&self, seconds: i64) -> Timestamp {
57 Timestamp(self.0 + seconds)
58 }
59}
60
61impl std::fmt::Display for Timestamp {
62 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63 write!(f, "{}", self.0)
64 }
65}
66
67impl std::cmp::PartialEq for Timestamp {
68 fn eq(&self, other: &Self) -> bool {
69 self.0 == other.0
70 }
71}
72
73impl std::cmp::PartialOrd for Timestamp {
74 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
75 Some(self.cmp(other))
76 }
77}
78
79impl std::cmp::Eq for Timestamp {}
80
81impl std::cmp::Ord for Timestamp {
82 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
83 self.0.cmp(&other.0)
84 }
85}
86
87impl Serialize for Timestamp {
88 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
89 where
90 S: serde::Serializer,
91 {
92 serializer.serialize_i64(self.0)
93 }
94}
95
96impl<'de> Deserialize<'de> for Timestamp {
97 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
98 where
99 D: serde::Deserializer<'de>,
100 {
101 use serde::de::{Error, Visitor};
102
103 struct TimestampVisitor;
104
105 impl<'de> Visitor<'de> for TimestampVisitor {
106 type Value = Timestamp;
107
108 fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
109 write!(f, "an integer timestamp or ISO 8601 string")
110 }
111
112 fn visit_i64<E: Error>(self, v: i64) -> Result<Self::Value, E> {
113 Ok(Timestamp(v))
114 }
115
116 fn visit_u64<E: Error>(self, v: u64) -> Result<Self::Value, E> {
117 Ok(Timestamp(v as i64))
118 }
119
120 fn visit_str<E: Error>(self, v: &str) -> Result<Self::Value, E> {
121 use chrono::DateTime;
122 DateTime::parse_from_rfc3339(v)
123 .map(|dt| Timestamp(dt.timestamp()))
124 .map_err(|_| E::custom("invalid ISO 8601 timestamp"))
125 }
126 }
127
128 deserializer.deserialize_any(TimestampVisitor)
129 }
130}
131
132pub fn serialize_timestamp_iso<S>(ts: &Timestamp, serializer: S) -> Result<S::Ok, S::Error>
134where
135 S: Serializer,
136{
137 use chrono::{DateTime, SecondsFormat};
138 let dt = DateTime::from_timestamp(ts.0, 0).unwrap_or_default();
139 serializer.serialize_str(&dt.to_rfc3339_opts(SecondsFormat::Secs, true))
140}
141
142pub fn serialize_timestamp_iso_opt<S>(
144 ts: &Option<Timestamp>,
145 serializer: S,
146) -> Result<S::Ok, S::Error>
147where
148 S: Serializer,
149{
150 match ts {
151 Some(ts) => serialize_timestamp_iso(ts, serializer),
152 None => serializer.serialize_none(),
153 }
154}
155
156#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
163pub enum Patch<T> {
164 #[default]
166 Undefined,
167 Null,
169 Value(T),
171}
172
173impl<T> Patch<T> {
174 pub fn is_undefined(&self) -> bool {
176 matches!(self, Patch::Undefined)
177 }
178
179 pub fn is_null(&self) -> bool {
181 matches!(self, Patch::Null)
182 }
183
184 pub fn is_value(&self) -> bool {
186 matches!(self, Patch::Value(_))
187 }
188
189 pub fn value(&self) -> Option<&T> {
191 match self {
192 Patch::Value(v) => Some(v),
193 _ => None,
194 }
195 }
196
197 pub fn as_option(&self) -> Option<Option<&T>> {
199 match self {
200 Patch::Undefined => None,
201 Patch::Null => Some(None),
202 Patch::Value(v) => Some(Some(v)),
203 }
204 }
205
206 pub fn map<U, F>(self, f: F) -> Patch<U>
208 where
209 F: FnOnce(T) -> U,
210 {
211 match self {
212 Patch::Undefined => Patch::Undefined,
213 Patch::Null => Patch::Null,
214 Patch::Value(v) => Patch::Value(f(v)),
215 }
216 }
217}
218
219impl<T> Serialize for Patch<T>
220where
221 T: Serialize,
222{
223 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
224 where
225 S: Serializer,
226 {
227 match self {
228 Patch::Undefined => serializer.serialize_none(),
229 Patch::Null => serializer.serialize_none(),
230 Patch::Value(v) => v.serialize(serializer),
231 }
232 }
233}
234
235impl<'de, T> Deserialize<'de> for Patch<T>
236where
237 T: Deserialize<'de>,
238{
239 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
240 where
241 D: Deserializer<'de>,
242 {
243 Option::<T>::deserialize(deserializer).map(|opt| match opt {
244 None => Patch::Null,
245 Some(v) => Patch::Value(v),
246 })
247 }
248}
249
250#[derive(Debug, Clone, Serialize, Deserialize)]
255#[serde(rename_all = "camelCase")]
256pub struct RegisterVerifyCheckRequest {
257 #[serde(rename = "type")]
258 pub typ: String, pub id_tag: String,
260 pub app_domain: Option<String>,
261 pub token: Option<String>, }
263
264#[derive(Debug, Clone, Serialize, Deserialize)]
266#[serde(rename_all = "camelCase")]
267pub struct RegisterRequest {
268 #[serde(rename = "type")]
269 pub typ: String, pub id_tag: String,
271 pub app_domain: Option<String>,
272 pub email: String,
273 pub token: String,
274 pub lang: Option<String>,
275}
276
277#[derive(Debug, Clone, Serialize, Deserialize)]
279#[serde(rename_all = "camelCase")]
280pub struct RegisterVerifyRequest {
281 pub id_tag: String,
282 pub token: String,
283}
284
285#[serde_with::skip_serializing_none]
287#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
288#[serde(rename_all = "camelCase")]
289pub struct Profile {
290 pub id_tag: String,
291 pub name: String,
292 #[serde(rename = "type")]
293 pub r#type: String,
294 pub profile_pic: Option<String>,
295 pub cover_pic: Option<String>,
296 pub keys: Vec<crate::auth_adapter::AuthKey>,
297}
298
299#[derive(Debug, Clone, Serialize, Deserialize)]
301#[serde(rename_all = "camelCase")]
302pub struct ProfilePatch {
303 pub name: Patch<String>,
304}
305
306#[derive(Debug, Clone, Serialize, Deserialize)]
308#[serde(rename_all = "camelCase")]
309pub struct AdminProfilePatch {
310 #[serde(default)]
312 pub name: Patch<String>,
313
314 #[serde(default)]
316 pub roles: Patch<Option<Vec<String>>>,
317 #[serde(default)]
318 pub status: Patch<crate::meta_adapter::ProfileStatus>,
319}
320
321#[skip_serializing_none]
323#[derive(Debug, Clone, Serialize, Deserialize)]
324#[serde(rename_all = "camelCase")]
325pub struct ProfileInfo {
326 pub id_tag: String,
327 pub name: String,
328 #[serde(rename = "type")]
329 pub r#type: Option<String>,
330 pub profile_pic: Option<String>, pub status: Option<String>,
332 pub connected: Option<bool>,
333 pub following: Option<bool>,
334 pub roles: Option<Vec<String>>,
335 #[serde(
336 serialize_with = "serialize_timestamp_iso_opt",
337 skip_serializing_if = "Option::is_none"
338 )]
339 pub created_at: Option<Timestamp>,
340}
341
342#[derive(Debug, Clone, Deserialize)]
344#[serde(rename_all = "camelCase")]
345pub struct CreateCommunityRequest {
346 #[serde(rename = "type")]
347 pub typ: String, pub name: Option<String>,
349 pub profile_pic: Option<String>,
350 pub app_domain: Option<String>, pub invite_ref: Option<String>, }
353
354#[skip_serializing_none]
356#[derive(Debug, Clone, Serialize)]
357#[serde(rename_all = "camelCase")]
358pub struct CommunityProfileResponse {
359 pub id_tag: String,
360 pub name: String,
361 #[serde(rename = "type")]
362 pub r#type: String,
363 pub profile_pic: Option<String>,
364 #[serde(serialize_with = "serialize_timestamp_iso")]
365 pub created_at: Timestamp,
366}
367
368#[derive(Debug, Clone, Serialize, Deserialize)]
373#[serde(rename_all = "camelCase")]
374pub struct CreateActionRequest {
375 #[serde(rename = "type")]
376 pub r#type: String, pub sub_type: Option<String>, pub parent_id: Option<String>,
379 pub content: String,
381 pub attachments: Option<Vec<String>>, pub audience: Option<Vec<String>>,
383}
384
385#[skip_serializing_none]
387#[derive(Debug, Clone, Serialize, Deserialize)]
388#[serde(rename_all = "camelCase")]
389pub struct ActionResponse {
390 pub action_id: String,
391 pub action_token: String,
392 #[serde(rename = "type")]
393 pub r#type: String,
394 pub sub_type: Option<String>,
395 pub parent_id: Option<String>,
396 pub root_id: Option<String>,
397 pub content: String,
398 pub attachments: Vec<String>,
399 pub issuer_tag: String,
400 #[serde(serialize_with = "serialize_timestamp_iso")]
401 pub created_at: Timestamp,
402}
403
404#[derive(Debug, Clone, Default, Deserialize)]
406#[serde(rename_all = "camelCase")]
407pub struct ListActionsQuery {
408 #[serde(rename = "type")]
409 pub r#type: Option<String>,
410 pub parent_id: Option<String>,
411 pub offset: Option<usize>,
412 pub limit: Option<usize>,
413}
414
415#[derive(Debug, Clone, Serialize, Deserialize)]
417#[serde(rename_all = "camelCase")]
418pub struct ReactionRequest {
419 #[serde(rename = "type")]
420 pub r#type: String, pub content: Option<String>, }
423
424#[skip_serializing_none]
426#[derive(Debug, Clone, Serialize, Deserialize)]
427#[serde(rename_all = "camelCase")]
428pub struct ReactionResponse {
429 pub id: String,
430 pub action_id: String,
431 pub reactor_id_tag: String,
432 #[serde(rename = "type")]
433 pub r#type: String,
434 pub content: Option<String>,
435 #[serde(serialize_with = "serialize_timestamp_iso")]
436 pub created_at: Timestamp,
437}
438
439#[derive(Debug, Clone, Serialize, Deserialize)]
441#[serde(rename_all = "camelCase")]
442pub struct FileUploadResponse {
443 pub file_id: String,
444 pub descriptor: String,
445 pub variants: Vec<FileVariantInfo>,
446}
447
448#[derive(Debug, Clone, Serialize, Deserialize)]
450#[serde(rename_all = "camelCase")]
451pub struct FileVariantInfo {
452 pub variant_id: String,
453 pub format: String,
454 pub size: u64,
455 pub resolution: Option<(u32, u32)>,
456}
457
458#[derive(Debug, Clone, Serialize, Deserialize)]
460pub struct TagInfo {
461 pub tag: String,
462 #[serde(skip_serializing_if = "Option::is_none")]
463 pub count: Option<u32>,
464}
465
466#[derive(Debug, Clone, Serialize, Deserialize)]
471#[serde(rename_all = "camelCase")]
472pub struct PaginationInfo {
473 pub offset: usize,
474 pub limit: usize,
475 pub total: usize,
476}
477
478#[derive(Debug, Clone, Serialize, Deserialize)]
483#[serde(rename_all = "camelCase")]
484pub struct CursorPaginationInfo {
485 pub next_cursor: Option<String>,
487 pub has_more: bool,
489}
490
491#[derive(Debug, Clone, Serialize, Deserialize)]
493pub struct CursorData {
494 pub s: String,
496 pub v: serde_json::Value,
498 pub id: String,
500}
501
502impl CursorData {
503 pub fn new(sort_field: &str, sort_value: serde_json::Value, item_id: &str) -> Self {
505 Self { s: sort_field.to_string(), v: sort_value, id: item_id.to_string() }
506 }
507
508 pub fn encode(&self) -> String {
510 use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
511 let json = serde_json::to_string(self).unwrap_or_default();
512 URL_SAFE_NO_PAD.encode(json.as_bytes())
513 }
514
515 pub fn decode(cursor: &str) -> Option<Self> {
517 use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
518 let bytes = URL_SAFE_NO_PAD.decode(cursor).ok()?;
519 let json = String::from_utf8(bytes).ok()?;
520 serde_json::from_str(&json).ok()
521 }
522
523 pub fn timestamp(&self) -> Option<i64> {
525 self.v.as_i64()
526 }
527
528 pub fn string_value(&self) -> Option<&str> {
530 self.v.as_str()
531 }
532}
533
534#[derive(Debug, Serialize, Deserialize)]
536#[serde(rename_all = "camelCase")]
537pub struct ApiResponse<T> {
538 pub data: T,
539 #[serde(skip_serializing_if = "Option::is_none")]
540 pub pagination: Option<PaginationInfo>,
541 #[serde(skip_serializing_if = "Option::is_none")]
542 pub cursor_pagination: Option<CursorPaginationInfo>,
543 #[serde(serialize_with = "serialize_timestamp_iso")]
544 pub time: Timestamp,
545 #[serde(skip_serializing_if = "Option::is_none")]
546 pub req_id: Option<String>,
547}
548
549impl<T> ApiResponse<T> {
550 pub fn new(data: T) -> Self {
552 Self {
553 data,
554 pagination: None,
555 cursor_pagination: None,
556 time: Timestamp::now(),
557 req_id: None,
558 }
559 }
560
561 pub fn with_pagination(data: T, offset: usize, limit: usize, total: usize) -> Self {
563 Self {
564 data,
565 pagination: Some(PaginationInfo { offset, limit, total }),
566 cursor_pagination: None,
567 time: Timestamp::now(),
568 req_id: None,
569 }
570 }
571
572 pub fn with_cursor_pagination(data: T, next_cursor: Option<String>, has_more: bool) -> Self {
574 Self {
575 data,
576 pagination: None,
577 cursor_pagination: Some(CursorPaginationInfo { next_cursor, has_more }),
578 time: Timestamp::now(),
579 req_id: None,
580 }
581 }
582
583 pub fn with_req_id(mut self, req_id: String) -> Self {
585 self.req_id = Some(req_id);
586 self
587 }
588}
589
590#[derive(Debug, Serialize, Deserialize)]
592#[serde(rename_all = "camelCase")]
593pub struct ErrorResponse {
594 pub error: ErrorDetails,
595}
596
597#[derive(Debug, Serialize, Deserialize)]
599#[serde(rename_all = "camelCase")]
600pub struct ErrorDetails {
601 pub code: String,
602 pub message: String,
603 #[serde(skip_serializing_if = "Option::is_none")]
604 pub details: Option<serde_json::Value>,
605}
606
607impl ErrorResponse {
608 pub fn new(code: String, message: String) -> Self {
610 Self { error: ErrorDetails { code, message, details: None } }
611 }
612
613 pub fn with_details(mut self, details: serde_json::Value) -> Self {
615 self.error.details = Some(details);
616 self
617 }
618}
619
620#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
625#[serde(rename_all = "lowercase")]
626pub enum AccessLevel {
627 None,
628 Read,
629 Write,
630 Admin,
631}
632
633impl AccessLevel {
634 pub fn as_str(&self) -> &'static str {
635 match self {
636 Self::None => "none",
637 Self::Read => "read",
638 Self::Write => "write",
639 Self::Admin => "admin",
640 }
641 }
642}
643
644#[derive(Debug, Clone, PartialEq, Eq)]
649pub enum TokenScope {
650 File { file_id: String, access: AccessLevel },
652}
653
654impl TokenScope {
655 pub fn parse(s: &str) -> Option<Self> {
661 let parts: Vec<&str> = s.split(':').collect();
662 if parts.len() >= 3 && parts[0] == "file" {
663 let access = match parts[2] {
664 "W" => AccessLevel::Write,
665 _ => AccessLevel::Read, };
667 return Some(Self::File { file_id: parts[1].to_string(), access });
668 }
669 None
670 }
671
672 pub fn file_id(&self) -> Option<&str> {
674 match self {
675 Self::File { file_id, .. } => Some(file_id),
676 }
677 }
678
679 pub fn file_access(&self) -> Option<AccessLevel> {
681 match self {
682 Self::File { access, .. } => Some(*access),
683 }
684 }
685
686 pub fn matches_file(&self, target_file_id: &str) -> bool {
688 match self {
689 Self::File { file_id, .. } => file_id == target_file_id,
690 }
691 }
692}
693
694#[derive(Debug, Clone)]
696pub struct ProfileAttrs {
697 pub id_tag: Box<str>,
698 pub profile_type: Box<str>,
699 pub tenant_tag: Box<str>,
700 pub roles: Vec<Box<str>>,
701 pub status: Box<str>,
702 pub following: bool,
703 pub connected: bool,
704 pub visibility: Box<str>,
705}
706
707impl AttrSet for ProfileAttrs {
708 fn get(&self, key: &str) -> Option<&str> {
709 match key {
710 "id_tag" => Some(&self.id_tag),
711 "profile_type" => Some(&self.profile_type),
712 "tenant_tag" | "owner_id_tag" => Some(&self.tenant_tag),
713 "status" => Some(&self.status),
714 "following" => Some(if self.following { "true" } else { "false" }),
715 "connected" => Some(if self.connected { "true" } else { "false" }),
716 "visibility" => Some(&self.visibility),
717 _ => None,
718 }
719 }
720
721 fn get_list(&self, key: &str) -> Option<Vec<&str>> {
722 match key {
723 "roles" => Some(self.roles.iter().map(|s| s.as_ref()).collect()),
724 _ => None,
725 }
726 }
727}
728
729#[derive(Debug, Clone)]
731pub struct ActionAttrs {
732 pub typ: Box<str>,
733 pub sub_typ: Option<Box<str>>,
734 pub tenant_id_tag: Box<str>,
736 pub issuer_id_tag: Box<str>,
738 pub parent_id: Option<Box<str>>,
739 pub root_id: Option<Box<str>>,
740 pub audience_tag: Vec<Box<str>>,
741 pub tags: Vec<Box<str>>,
742 pub visibility: Box<str>,
743 pub following: bool,
745 pub connected: bool,
747}
748
749impl AttrSet for ActionAttrs {
750 fn get(&self, key: &str) -> Option<&str> {
751 match key {
752 "type" => Some(&self.typ),
753 "sub_type" => self.sub_typ.as_deref(),
754 "tenant_id_tag" | "owner_id_tag" => Some(&self.tenant_id_tag),
756 "issuer_id_tag" => Some(&self.issuer_id_tag),
757 "parent_id" => self.parent_id.as_deref(),
758 "root_id" => self.root_id.as_deref(),
759 "visibility" => Some(&self.visibility),
760 "following" => Some(if self.following { "true" } else { "false" }),
761 "connected" => Some(if self.connected { "true" } else { "false" }),
762 _ => None,
763 }
764 }
765
766 fn get_list(&self, key: &str) -> Option<Vec<&str>> {
767 match key {
768 "audience_tag" => Some(self.audience_tag.iter().map(|s| s.as_ref()).collect()),
769 "tags" => Some(self.tags.iter().map(|s| s.as_ref()).collect()),
770 _ => None,
771 }
772 }
773}
774
775#[derive(Debug, Clone)]
777pub struct FileAttrs {
778 pub file_id: Box<str>,
779 pub owner_id_tag: Box<str>,
780 pub mime_type: Box<str>,
781 pub tags: Vec<Box<str>>,
782 pub visibility: Box<str>,
783 pub access_level: AccessLevel,
784 pub following: bool,
786 pub connected: bool,
788}
789
790impl AttrSet for FileAttrs {
791 fn get(&self, key: &str) -> Option<&str> {
792 match key {
793 "file_id" => Some(&self.file_id),
794 "owner_id_tag" => Some(&self.owner_id_tag),
795 "mime_type" => Some(&self.mime_type),
796 "visibility" => Some(&self.visibility),
797 "access_level" => Some(self.access_level.as_str()),
798 "following" => Some(if self.following { "true" } else { "false" }),
799 "connected" => Some(if self.connected { "true" } else { "false" }),
800 _ => None,
801 }
802 }
803
804 fn get_list(&self, key: &str) -> Option<Vec<&str>> {
805 match key {
806 "tags" => Some(self.tags.iter().map(|s| s.as_ref()).collect()),
807 _ => None,
808 }
809 }
810}
811
812#[derive(Debug, Clone)]
817pub struct SubjectAttrs {
818 pub id_tag: Box<str>,
819 pub roles: Vec<Box<str>>,
820 pub tier: Box<str>, pub quota_remaining_bytes: Box<str>, pub rate_limit_remaining: Box<str>, pub banned: bool,
824 pub email_verified: bool,
825}
826
827impl AttrSet for SubjectAttrs {
828 fn get(&self, key: &str) -> Option<&str> {
829 match key {
830 "id_tag" => Some(&self.id_tag),
831 "tier" => Some(&self.tier),
832 "quota_remaining" => Some(&self.quota_remaining_bytes),
833 "quota_remaining_bytes" => Some(&self.quota_remaining_bytes),
834 "rate_limit_remaining" => Some(&self.rate_limit_remaining),
835 "banned" => Some(if self.banned { "true" } else { "false" }),
836 "email_verified" => Some(if self.email_verified { "true" } else { "false" }),
837 _ => None,
838 }
839 }
840
841 fn get_list(&self, key: &str) -> Option<Vec<&str>> {
842 match key {
843 "roles" => Some(self.roles.iter().map(|r| r.as_ref()).collect()),
844 _ => None,
845 }
846 }
847}
848
849