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().cast_signed())
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().cast_signed() + delta)
53 }
54
55 pub fn add_seconds(&self, seconds: i64) -> Timestamp {
57 Timestamp(self.0 + seconds)
58 }
59
60 pub fn to_iso_string(&self) -> String {
62 use chrono::{DateTime, SecondsFormat};
63 DateTime::from_timestamp(self.0, 0)
64 .unwrap_or_default()
65 .to_rfc3339_opts(SecondsFormat::Secs, true)
66 }
67}
68
69impl std::fmt::Display for Timestamp {
70 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71 write!(f, "{}", self.0)
72 }
73}
74
75impl std::cmp::PartialEq for Timestamp {
76 fn eq(&self, other: &Self) -> bool {
77 self.0 == other.0
78 }
79}
80
81impl std::cmp::PartialOrd for Timestamp {
82 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
83 Some(self.cmp(other))
84 }
85}
86
87impl std::cmp::Eq for Timestamp {}
88
89impl std::cmp::Ord for Timestamp {
90 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
91 self.0.cmp(&other.0)
92 }
93}
94
95impl Serialize for Timestamp {
96 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
97 where
98 S: serde::Serializer,
99 {
100 serializer.serialize_i64(self.0)
101 }
102}
103
104impl<'de> Deserialize<'de> for Timestamp {
105 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
106 where
107 D: serde::Deserializer<'de>,
108 {
109 use serde::de::{Error, Visitor};
110
111 struct TimestampVisitor;
112
113 impl Visitor<'_> for TimestampVisitor {
114 type Value = Timestamp;
115
116 fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
117 write!(f, "an integer timestamp or ISO 8601 string")
118 }
119
120 fn visit_i64<E: Error>(self, v: i64) -> Result<Self::Value, E> {
121 Ok(Timestamp(v))
122 }
123
124 fn visit_u64<E: Error>(self, v: u64) -> Result<Self::Value, E> {
125 Ok(Timestamp(v.cast_signed()))
126 }
127
128 fn visit_str<E: Error>(self, v: &str) -> Result<Self::Value, E> {
129 use chrono::DateTime;
130 DateTime::parse_from_rfc3339(v)
131 .map(|dt| Timestamp(dt.timestamp()))
132 .map_err(|_| E::custom("invalid ISO 8601 timestamp"))
133 }
134 }
135
136 deserializer.deserialize_any(TimestampVisitor)
137 }
138}
139
140pub fn serialize_timestamp_iso<S>(ts: &Timestamp, serializer: S) -> Result<S::Ok, S::Error>
142where
143 S: Serializer,
144{
145 use chrono::{DateTime, SecondsFormat};
146 let dt = DateTime::from_timestamp(ts.0, 0).unwrap_or_default();
147 serializer.serialize_str(&dt.to_rfc3339_opts(SecondsFormat::Secs, true))
148}
149
150pub fn serialize_timestamp_iso_opt<S>(
152 ts: &Option<Timestamp>,
153 serializer: S,
154) -> Result<S::Ok, S::Error>
155where
156 S: Serializer,
157{
158 match ts {
159 Some(ts) => serialize_timestamp_iso(ts, serializer),
160 None => serializer.serialize_none(),
161 }
162}
163
164#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
171pub enum Patch<T> {
172 #[default]
174 Undefined,
175 Null,
177 Value(T),
179}
180
181impl<T> Patch<T> {
182 pub fn is_undefined(&self) -> bool {
184 matches!(self, Patch::Undefined)
185 }
186
187 pub fn is_null(&self) -> bool {
189 matches!(self, Patch::Null)
190 }
191
192 pub fn is_value(&self) -> bool {
194 matches!(self, Patch::Value(_))
195 }
196
197 pub fn value(&self) -> Option<&T> {
199 match self {
200 Patch::Value(v) => Some(v),
201 _ => None,
202 }
203 }
204
205 pub fn as_option(&self) -> Option<Option<&T>> {
207 match self {
208 Patch::Undefined => None,
209 Patch::Null => Some(None),
210 Patch::Value(v) => Some(Some(v)),
211 }
212 }
213
214 pub fn map<U, F>(self, f: F) -> Patch<U>
216 where
217 F: FnOnce(T) -> U,
218 {
219 match self {
220 Patch::Undefined => Patch::Undefined,
221 Patch::Null => Patch::Null,
222 Patch::Value(v) => Patch::Value(f(v)),
223 }
224 }
225}
226
227impl<T> Serialize for Patch<T>
228where
229 T: Serialize,
230{
231 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
232 where
233 S: Serializer,
234 {
235 match self {
236 Patch::Undefined | Patch::Null => serializer.serialize_none(),
237 Patch::Value(v) => v.serialize(serializer),
238 }
239 }
240}
241
242impl<'de, T> Deserialize<'de> for Patch<T>
243where
244 T: Deserialize<'de>,
245{
246 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
247 where
248 D: Deserializer<'de>,
249 {
250 Option::<T>::deserialize(deserializer).map(|opt| match opt {
251 None => Patch::Null,
252 Some(v) => Patch::Value(v),
253 })
254 }
255}
256
257#[derive(Debug, Clone, Serialize, Deserialize)]
262#[serde(rename_all = "camelCase")]
263pub struct RegisterVerifyCheckRequest {
264 #[serde(rename = "type")]
265 pub typ: String, pub id_tag: String,
267 pub app_domain: Option<String>,
268 pub token: Option<String>, }
270
271#[derive(Debug, Clone, Serialize, Deserialize)]
273#[serde(rename_all = "camelCase")]
274pub struct RegisterRequest {
275 #[serde(rename = "type")]
276 pub typ: String, pub id_tag: String,
278 pub app_domain: Option<String>,
279 pub email: String,
280 pub token: String,
281 pub lang: Option<String>,
282}
283
284#[derive(Debug, Clone, Serialize, Deserialize)]
286#[serde(rename_all = "camelCase")]
287pub struct RegisterVerifyRequest {
288 pub id_tag: String,
289 pub token: String,
290}
291
292#[serde_with::skip_serializing_none]
294#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
295#[serde(rename_all = "camelCase")]
296pub struct Profile {
297 pub id_tag: String,
298 pub name: String,
299 #[serde(rename = "type")]
300 pub r#type: String,
301 pub profile_pic: Option<String>,
302 pub cover_pic: Option<String>,
303 pub keys: Vec<crate::auth_adapter::AuthKey>,
304}
305
306#[derive(Debug, Clone, Serialize, Deserialize)]
308#[serde(rename_all = "camelCase")]
309pub struct ProfilePatch {
310 pub name: Patch<String>,
311}
312
313#[derive(Debug, Clone, Serialize, Deserialize)]
315#[serde(rename_all = "camelCase")]
316pub struct AdminProfilePatch {
317 #[serde(default)]
319 pub name: Patch<String>,
320
321 #[serde(default)]
323 pub roles: Patch<Option<Vec<String>>>,
324 #[serde(default)]
325 pub status: Patch<crate::meta_adapter::ProfileStatus>,
326}
327
328#[skip_serializing_none]
330#[derive(Debug, Clone, Serialize, Deserialize)]
331#[serde(rename_all = "camelCase")]
332pub struct ProfileInfo {
333 pub id_tag: String,
334 pub name: String,
335 #[serde(rename = "type")]
336 pub r#type: Option<String>,
337 pub profile_pic: Option<String>, pub status: Option<String>,
339 pub connected: Option<bool>,
340 pub following: Option<bool>,
341 pub roles: Option<Vec<String>>,
342 #[serde(
343 serialize_with = "serialize_timestamp_iso_opt",
344 skip_serializing_if = "Option::is_none"
345 )]
346 pub created_at: Option<Timestamp>,
347}
348
349#[derive(Debug, Clone, Deserialize)]
351#[serde(rename_all = "camelCase")]
352pub struct CreateCommunityRequest {
353 #[serde(rename = "type")]
354 pub typ: String, pub name: Option<String>,
356 pub profile_pic: Option<String>,
357 pub app_domain: Option<String>, pub invite_ref: Option<String>, }
360
361#[skip_serializing_none]
363#[derive(Debug, Clone, Serialize)]
364#[serde(rename_all = "camelCase")]
365pub struct CommunityProfileResponse {
366 pub id_tag: String,
367 pub name: String,
368 #[serde(rename = "type")]
369 pub r#type: String,
370 pub profile_pic: Option<String>,
371 #[serde(serialize_with = "serialize_timestamp_iso")]
372 pub created_at: Timestamp,
373}
374
375#[derive(Debug, Clone, Serialize, Deserialize)]
380#[serde(rename_all = "camelCase")]
381pub struct CreateActionRequest {
382 #[serde(rename = "type")]
383 pub r#type: String, pub sub_type: Option<String>, pub parent_id: Option<String>,
386 pub content: String,
388 pub attachments: Option<Vec<String>>, pub audience: Option<Vec<String>>,
390}
391
392#[skip_serializing_none]
394#[derive(Debug, Clone, Serialize, Deserialize)]
395#[serde(rename_all = "camelCase")]
396pub struct ActionResponse {
397 pub action_id: String,
398 pub action_token: String,
399 #[serde(rename = "type")]
400 pub r#type: String,
401 pub sub_type: Option<String>,
402 pub parent_id: Option<String>,
403 pub root_id: Option<String>,
404 pub content: String,
405 pub attachments: Vec<String>,
406 pub issuer_tag: String,
407 #[serde(serialize_with = "serialize_timestamp_iso")]
408 pub created_at: Timestamp,
409}
410
411#[derive(Debug, Clone, Default, Deserialize)]
413#[serde(rename_all = "camelCase")]
414pub struct ListActionsQuery {
415 #[serde(rename = "type")]
416 pub r#type: Option<String>,
417 pub parent_id: Option<String>,
418 pub offset: Option<usize>,
419 pub limit: Option<usize>,
420}
421
422#[derive(Debug, Clone, Serialize, Deserialize)]
424#[serde(rename_all = "camelCase")]
425pub struct FileUploadResponse {
426 pub file_id: String,
427 pub descriptor: String,
428 pub variants: Vec<FileVariantInfo>,
429}
430
431#[derive(Debug, Clone, Serialize, Deserialize)]
433#[serde(rename_all = "camelCase")]
434pub struct FileVariantInfo {
435 pub variant_id: String,
436 pub format: String,
437 pub size: u64,
438 pub resolution: Option<(u32, u32)>,
439}
440
441#[derive(Debug, Clone, Serialize, Deserialize)]
443pub struct TagInfo {
444 pub tag: String,
445 #[serde(skip_serializing_if = "Option::is_none")]
446 pub count: Option<u32>,
447}
448
449#[derive(Debug, Clone, Serialize, Deserialize)]
454#[serde(rename_all = "camelCase")]
455pub struct PaginationInfo {
456 pub offset: usize,
457 pub limit: usize,
458 pub total: usize,
459}
460
461#[derive(Debug, Clone, Serialize, Deserialize)]
466#[serde(rename_all = "camelCase")]
467pub struct CursorPaginationInfo {
468 pub next_cursor: Option<String>,
470 pub has_more: bool,
472}
473
474#[derive(Debug, Clone, Serialize, Deserialize)]
476pub struct CursorData {
477 pub s: String,
479 pub v: serde_json::Value,
481 pub id: String,
483}
484
485impl CursorData {
486 pub fn new(sort_field: &str, sort_value: serde_json::Value, item_id: &str) -> Self {
488 Self { s: sort_field.to_string(), v: sort_value, id: item_id.to_string() }
489 }
490
491 pub fn encode(&self) -> String {
493 use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
494 let json = serde_json::to_string(self).unwrap_or_default();
495 URL_SAFE_NO_PAD.encode(json.as_bytes())
496 }
497
498 pub fn decode(cursor: &str) -> Option<Self> {
500 use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
501 let bytes = URL_SAFE_NO_PAD.decode(cursor).ok()?;
502 let json = String::from_utf8(bytes).ok()?;
503 serde_json::from_str(&json).ok()
504 }
505
506 pub fn timestamp(&self) -> Option<i64> {
508 self.v.as_i64()
509 }
510
511 pub fn string_value(&self) -> Option<&str> {
513 self.v.as_str()
514 }
515}
516
517#[derive(Debug, Serialize, Deserialize)]
519#[serde(rename_all = "camelCase")]
520pub struct ApiResponse<T> {
521 pub data: T,
522 #[serde(skip_serializing_if = "Option::is_none")]
523 pub pagination: Option<PaginationInfo>,
524 #[serde(skip_serializing_if = "Option::is_none")]
525 pub cursor_pagination: Option<CursorPaginationInfo>,
526 #[serde(serialize_with = "serialize_timestamp_iso")]
527 pub time: Timestamp,
528 #[serde(skip_serializing_if = "Option::is_none")]
529 pub req_id: Option<String>,
530}
531
532impl<T> ApiResponse<T> {
533 pub fn new(data: T) -> Self {
535 Self {
536 data,
537 pagination: None,
538 cursor_pagination: None,
539 time: Timestamp::now(),
540 req_id: None,
541 }
542 }
543
544 pub fn with_pagination(data: T, offset: usize, limit: usize, total: usize) -> Self {
546 Self {
547 data,
548 pagination: Some(PaginationInfo { offset, limit, total }),
549 cursor_pagination: None,
550 time: Timestamp::now(),
551 req_id: None,
552 }
553 }
554
555 pub fn with_cursor_pagination(data: T, next_cursor: Option<String>, has_more: bool) -> Self {
557 Self {
558 data,
559 pagination: None,
560 cursor_pagination: Some(CursorPaginationInfo { next_cursor, has_more }),
561 time: Timestamp::now(),
562 req_id: None,
563 }
564 }
565
566 pub fn with_req_id(mut self, req_id: String) -> Self {
568 self.req_id = Some(req_id);
569 self
570 }
571}
572
573#[derive(Debug, Serialize, Deserialize)]
575#[serde(rename_all = "camelCase")]
576pub struct ErrorResponse {
577 pub error: ErrorDetails,
578}
579
580#[derive(Debug, Serialize, Deserialize)]
582#[serde(rename_all = "camelCase")]
583pub struct ErrorDetails {
584 pub code: String,
585 pub message: String,
586 #[serde(skip_serializing_if = "Option::is_none")]
587 pub details: Option<serde_json::Value>,
588}
589
590impl ErrorResponse {
591 pub fn new(code: String, message: String) -> Self {
593 Self { error: ErrorDetails { code, message, details: None } }
594 }
595
596 pub fn with_details(mut self, details: serde_json::Value) -> Self {
598 self.error.details = Some(details);
599 self
600 }
601}
602
603#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
608#[serde(rename_all = "lowercase")]
609pub enum AccessLevel {
610 None,
611 Read,
612 Write,
613 Admin,
614}
615
616impl AccessLevel {
617 pub fn as_str(&self) -> &'static str {
618 match self {
619 Self::None => "none",
620 Self::Read => "read",
621 Self::Write => "write",
622 Self::Admin => "admin",
623 }
624 }
625
626 pub fn min(self, other: Self) -> Self {
628 match (self, other) {
629 (Self::None, _) | (_, Self::None) => Self::None,
630 (Self::Read, _) | (_, Self::Read) => Self::Read,
631 (Self::Write, _) | (_, Self::Write) => Self::Write,
632 (Self::Admin, Self::Admin) => Self::Admin,
633 }
634 }
635
636 pub fn from_perm_char(c: char) -> Self {
639 match c {
640 'W' | 'A' => Self::Write,
641 _ => Self::Read,
642 }
643 }
644}
645
646#[derive(Debug, Clone, PartialEq, Eq)]
651pub enum TokenScope {
652 File { file_id: String, access: AccessLevel },
654 ApkgPublish,
656}
657
658impl TokenScope {
659 pub fn parse(s: &str) -> Option<Self> {
665 if s == "apkg:publish" {
666 return Some(Self::ApkgPublish);
667 }
668 let parts: Vec<&str> = s.split(':').collect();
669 if parts.len() == 3 && parts[0] == "file" {
670 let access = match parts[2] {
671 "W" => AccessLevel::Write,
672 _ => AccessLevel::Read, };
674 return Some(Self::File { file_id: parts[1].to_string(), access });
675 }
676 None
677 }
678
679 pub fn file_id(&self) -> Option<&str> {
681 match self {
682 Self::File { file_id, .. } => Some(file_id),
683 Self::ApkgPublish => None,
684 }
685 }
686
687 pub fn file_access(&self) -> Option<AccessLevel> {
689 match self {
690 Self::File { access, .. } => Some(*access),
691 Self::ApkgPublish => None,
692 }
693 }
694
695 pub fn matches_file(&self, target_file_id: &str) -> bool {
697 match self {
698 Self::File { file_id, .. } => file_id == target_file_id,
699 Self::ApkgPublish => false,
700 }
701 }
702}
703
704#[derive(Debug, Clone)]
706pub struct ProfileAttrs {
707 pub id_tag: Box<str>,
708 pub profile_type: Box<str>,
709 pub tenant_tag: Box<str>,
710 pub roles: Vec<Box<str>>,
711 pub status: Box<str>,
712 pub following: bool,
713 pub connected: bool,
714 pub visibility: Box<str>,
715}
716
717impl AttrSet for ProfileAttrs {
718 fn get(&self, key: &str) -> Option<&str> {
719 match key {
720 "id_tag" => Some(&self.id_tag),
721 "profile_type" => Some(&self.profile_type),
722 "tenant_tag" | "owner_id_tag" => Some(&self.tenant_tag),
723 "status" => Some(&self.status),
724 "following" => Some(if self.following { "true" } else { "false" }),
725 "connected" => Some(if self.connected { "true" } else { "false" }),
726 "visibility" => Some(&self.visibility),
727 _ => None,
728 }
729 }
730
731 fn get_list(&self, key: &str) -> Option<Vec<&str>> {
732 match key {
733 "roles" => Some(self.roles.iter().map(AsRef::as_ref).collect()),
734 _ => None,
735 }
736 }
737}
738
739#[derive(Debug, Clone)]
741pub struct ActionAttrs {
742 pub typ: Box<str>,
743 pub sub_typ: Option<Box<str>>,
744 pub tenant_id_tag: Box<str>,
746 pub issuer_id_tag: Box<str>,
748 pub parent_id: Option<Box<str>>,
749 pub root_id: Option<Box<str>>,
750 pub audience_tag: Vec<Box<str>>,
751 pub tags: Vec<Box<str>>,
752 pub visibility: Box<str>,
753 pub following: bool,
755 pub connected: bool,
757}
758
759impl AttrSet for ActionAttrs {
760 fn get(&self, key: &str) -> Option<&str> {
761 match key {
762 "type" => Some(&self.typ),
763 "sub_type" => self.sub_typ.as_deref(),
764 "tenant_id_tag" | "owner_id_tag" => Some(&self.tenant_id_tag),
766 "issuer_id_tag" => Some(&self.issuer_id_tag),
767 "parent_id" => self.parent_id.as_deref(),
768 "root_id" => self.root_id.as_deref(),
769 "visibility" => Some(&self.visibility),
770 "following" => Some(if self.following { "true" } else { "false" }),
771 "connected" => Some(if self.connected { "true" } else { "false" }),
772 _ => None,
773 }
774 }
775
776 fn get_list(&self, key: &str) -> Option<Vec<&str>> {
777 match key {
778 "audience_tag" => Some(self.audience_tag.iter().map(AsRef::as_ref).collect()),
779 "tags" => Some(self.tags.iter().map(AsRef::as_ref).collect()),
780 _ => None,
781 }
782 }
783}
784
785#[derive(Debug, Clone)]
787pub struct FileAttrs {
788 pub file_id: Box<str>,
789 pub owner_id_tag: Box<str>,
790 pub mime_type: Box<str>,
791 pub tags: Vec<Box<str>>,
792 pub visibility: Box<str>,
793 pub access_level: AccessLevel,
794 pub following: bool,
796 pub connected: bool,
798}
799
800impl AttrSet for FileAttrs {
801 fn get(&self, key: &str) -> Option<&str> {
802 match key {
803 "file_id" => Some(&self.file_id),
804 "owner_id_tag" => Some(&self.owner_id_tag),
805 "mime_type" => Some(&self.mime_type),
806 "visibility" => Some(&self.visibility),
807 "access_level" => Some(self.access_level.as_str()),
808 "following" => Some(if self.following { "true" } else { "false" }),
809 "connected" => Some(if self.connected { "true" } else { "false" }),
810 _ => None,
811 }
812 }
813
814 fn get_list(&self, key: &str) -> Option<Vec<&str>> {
815 match key {
816 "tags" => Some(self.tags.iter().map(AsRef::as_ref).collect()),
817 _ => None,
818 }
819 }
820}
821
822#[derive(Debug, Clone)]
827pub struct SubjectAttrs {
828 pub id_tag: Box<str>,
829 pub roles: Vec<Box<str>>,
830 pub tier: Box<str>, pub quota_remaining_bytes: Box<str>, pub rate_limit_remaining: Box<str>, pub banned: bool,
834 pub email_verified: bool,
835}
836
837impl AttrSet for SubjectAttrs {
838 fn get(&self, key: &str) -> Option<&str> {
839 match key {
840 "id_tag" => Some(&self.id_tag),
841 "tier" => Some(&self.tier),
842 "quota_remaining" | "quota_remaining_bytes" => Some(&self.quota_remaining_bytes),
843 "rate_limit_remaining" => Some(&self.rate_limit_remaining),
844 "banned" => Some(if self.banned { "true" } else { "false" }),
845 "email_verified" => Some(if self.email_verified { "true" } else { "false" }),
846 _ => None,
847 }
848 }
849
850 fn get_list(&self, key: &str) -> Option<Vec<&str>> {
851 match key {
852 "roles" => Some(self.roles.iter().map(AsRef::as_ref).collect()),
853 _ => None,
854 }
855 }
856}
857
858