1use crate::abac::AttrSet;
7use serde::{Deserialize, Deserializer, Serialize, Serializer};
8use serde_with::skip_serializing_none;
9use std::time::SystemTime;
10
11#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
15pub struct TnId(pub u32);
16
17impl std::fmt::Display for TnId {
18 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
19 write!(f, "{}", self.0)
20 }
21}
22
23impl Serialize for TnId {
24 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
25 where
26 S: serde::Serializer,
27 {
28 serializer.serialize_u32(self.0)
29 }
30}
31
32impl<'de> Deserialize<'de> for TnId {
33 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
34 where
35 D: serde::Deserializer<'de>,
36 {
37 Ok(TnId(u32::deserialize(deserializer)?))
38 }
39}
40
41#[derive(Clone, Copy, Debug, Default)]
45pub struct Timestamp(pub i64);
46
47impl Timestamp {
48 pub fn now() -> Timestamp {
49 let res = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap_or_default();
50 Timestamp(res.as_secs().cast_signed())
51 }
52
53 pub fn from_now(delta: i64) -> Timestamp {
54 let res = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap_or_default();
55 Timestamp(res.as_secs().cast_signed() + delta)
56 }
57
58 pub fn add_seconds(&self, seconds: i64) -> Timestamp {
60 Timestamp(self.0 + seconds)
61 }
62
63 pub fn to_iso_string(&self) -> String {
65 use chrono::{DateTime, SecondsFormat};
66 DateTime::from_timestamp(self.0, 0)
67 .unwrap_or_default()
68 .to_rfc3339_opts(SecondsFormat::Secs, true)
69 }
70}
71
72impl std::fmt::Display for Timestamp {
73 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74 write!(f, "{}", self.0)
75 }
76}
77
78impl std::cmp::PartialEq for Timestamp {
79 fn eq(&self, other: &Self) -> bool {
80 self.0 == other.0
81 }
82}
83
84impl std::cmp::PartialOrd for Timestamp {
85 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
86 Some(self.cmp(other))
87 }
88}
89
90impl std::cmp::Eq for Timestamp {}
91
92impl std::cmp::Ord for Timestamp {
93 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
94 self.0.cmp(&other.0)
95 }
96}
97
98impl Serialize for Timestamp {
99 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
100 where
101 S: serde::Serializer,
102 {
103 serializer.serialize_i64(self.0)
104 }
105}
106
107impl<'de> Deserialize<'de> for Timestamp {
108 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
109 where
110 D: serde::Deserializer<'de>,
111 {
112 use serde::de::{Error, Visitor};
113
114 struct TimestampVisitor;
115
116 impl Visitor<'_> for TimestampVisitor {
117 type Value = Timestamp;
118
119 fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
120 write!(f, "an integer timestamp or ISO 8601 string")
121 }
122
123 fn visit_i64<E: Error>(self, v: i64) -> Result<Self::Value, E> {
124 Ok(Timestamp(v))
125 }
126
127 fn visit_u64<E: Error>(self, v: u64) -> Result<Self::Value, E> {
128 Ok(Timestamp(v.cast_signed()))
129 }
130
131 fn visit_str<E: Error>(self, v: &str) -> Result<Self::Value, E> {
132 use chrono::DateTime;
133 DateTime::parse_from_rfc3339(v)
134 .map(|dt| Timestamp(dt.timestamp()))
135 .map_err(|_| E::custom("invalid ISO 8601 timestamp"))
136 }
137 }
138
139 deserializer.deserialize_any(TimestampVisitor)
140 }
141}
142
143pub fn serialize_timestamp_iso<S>(ts: &Timestamp, serializer: S) -> Result<S::Ok, S::Error>
145where
146 S: Serializer,
147{
148 use chrono::{DateTime, SecondsFormat};
149 let dt = DateTime::from_timestamp(ts.0, 0).unwrap_or_default();
150 serializer.serialize_str(&dt.to_rfc3339_opts(SecondsFormat::Secs, true))
151}
152
153pub fn serialize_timestamp_iso_opt<S>(
155 ts: &Option<Timestamp>,
156 serializer: S,
157) -> Result<S::Ok, S::Error>
158where
159 S: Serializer,
160{
161 match ts {
162 Some(ts) => serialize_timestamp_iso(ts, serializer),
163 None => serializer.serialize_none(),
164 }
165}
166
167#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
174pub enum Patch<T> {
175 #[default]
177 Undefined,
178 Null,
180 Value(T),
182}
183
184impl<T> Patch<T> {
185 pub fn is_undefined(&self) -> bool {
187 matches!(self, Patch::Undefined)
188 }
189
190 pub fn is_null(&self) -> bool {
192 matches!(self, Patch::Null)
193 }
194
195 pub fn is_value(&self) -> bool {
197 matches!(self, Patch::Value(_))
198 }
199
200 pub fn value(&self) -> Option<&T> {
202 match self {
203 Patch::Value(v) => Some(v),
204 _ => None,
205 }
206 }
207
208 pub fn as_option(&self) -> Option<Option<&T>> {
210 match self {
211 Patch::Undefined => None,
212 Patch::Null => Some(None),
213 Patch::Value(v) => Some(Some(v)),
214 }
215 }
216
217 pub fn map<U, F>(self, f: F) -> Patch<U>
219 where
220 F: FnOnce(T) -> U,
221 {
222 match self {
223 Patch::Undefined => Patch::Undefined,
224 Patch::Null => Patch::Null,
225 Patch::Value(v) => Patch::Value(f(v)),
226 }
227 }
228}
229
230impl<T> Serialize for Patch<T>
231where
232 T: Serialize,
233{
234 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
235 where
236 S: Serializer,
237 {
238 match self {
239 Patch::Undefined | Patch::Null => serializer.serialize_none(),
240 Patch::Value(v) => v.serialize(serializer),
241 }
242 }
243}
244
245impl<'de, T> Deserialize<'de> for Patch<T>
246where
247 T: Deserialize<'de>,
248{
249 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
250 where
251 D: Deserializer<'de>,
252 {
253 Option::<T>::deserialize(deserializer).map(|opt| match opt {
254 None => Patch::Null,
255 Some(v) => Patch::Value(v),
256 })
257 }
258}
259
260#[derive(Debug, Clone, Serialize, Deserialize)]
265#[serde(rename_all = "camelCase")]
266pub struct RegisterVerifyCheckRequest {
267 #[serde(rename = "type")]
268 pub typ: String, pub id_tag: String,
270 pub app_domain: Option<String>,
271 pub token: Option<String>, }
273
274#[derive(Debug, Clone, Serialize, Deserialize)]
276#[serde(rename_all = "camelCase")]
277pub struct RegisterRequest {
278 #[serde(rename = "type")]
279 pub typ: String, pub id_tag: String,
281 pub app_domain: Option<String>,
282 pub email: String,
283 pub token: String,
284 pub lang: Option<String>,
285}
286
287#[derive(Debug, Clone, Serialize, Deserialize)]
289#[serde(rename_all = "camelCase")]
290pub struct RegisterVerifyRequest {
291 pub id_tag: String,
292 pub token: String,
293}
294
295#[serde_with::skip_serializing_none]
297#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
298#[serde(rename_all = "camelCase")]
299pub struct Profile {
300 pub id_tag: String,
301 pub name: String,
302 #[serde(rename = "type")]
303 pub r#type: String,
304 pub profile_pic: Option<String>,
305 pub cover_pic: Option<String>,
306 pub keys: Vec<crate::auth_adapter::AuthKey>,
307}
308
309#[derive(Debug, Clone, Serialize, Deserialize)]
311#[serde(rename_all = "camelCase")]
312pub struct ProfilePatch {
313 pub name: Patch<String>,
314}
315
316#[derive(Debug, Clone, Serialize, Deserialize)]
318#[serde(rename_all = "camelCase")]
319pub struct AdminProfilePatch {
320 #[serde(default)]
322 pub name: Patch<String>,
323
324 #[serde(default)]
326 pub roles: Patch<Option<Vec<String>>>,
327 #[serde(default)]
328 pub status: Patch<crate::meta_adapter::ProfileStatus>,
329}
330
331#[skip_serializing_none]
333#[derive(Debug, Clone, Serialize, Deserialize)]
334#[serde(rename_all = "camelCase")]
335pub struct ProfileInfo {
336 pub id_tag: String,
337 pub name: String,
338 #[serde(rename = "type")]
339 pub r#type: Option<String>,
340 pub profile_pic: Option<String>, pub status: Option<String>,
342 pub connected: Option<bool>,
343 pub following: Option<bool>,
344 pub roles: Option<Vec<String>>,
345 #[serde(
346 serialize_with = "serialize_timestamp_iso_opt",
347 skip_serializing_if = "Option::is_none"
348 )]
349 pub created_at: Option<Timestamp>,
350}
351
352#[derive(Debug, Clone, Deserialize)]
354#[serde(rename_all = "camelCase")]
355pub struct CreateCommunityRequest {
356 #[serde(rename = "type")]
357 pub typ: String, pub name: Option<String>,
359 pub profile_pic: Option<String>,
360 pub app_domain: Option<String>, pub invite_ref: Option<String>, }
363
364#[skip_serializing_none]
366#[derive(Debug, Clone, Serialize)]
367#[serde(rename_all = "camelCase")]
368pub struct CommunityProfileResponse {
369 pub id_tag: String,
370 pub name: String,
371 #[serde(rename = "type")]
372 pub r#type: String,
373 pub profile_pic: Option<String>,
374 #[serde(serialize_with = "serialize_timestamp_iso")]
375 pub created_at: Timestamp,
376}
377
378#[derive(Debug, Clone, Serialize, Deserialize)]
383#[serde(rename_all = "camelCase")]
384pub struct CreateActionRequest {
385 #[serde(rename = "type")]
386 pub r#type: String, pub sub_type: Option<String>, pub parent_id: Option<String>,
389 pub content: String,
391 pub attachments: Option<Vec<String>>, pub audience: Option<Vec<String>>,
393}
394
395#[skip_serializing_none]
397#[derive(Debug, Clone, Serialize, Deserialize)]
398#[serde(rename_all = "camelCase")]
399pub struct ActionResponse {
400 pub action_id: String,
401 pub action_token: String,
402 #[serde(rename = "type")]
403 pub r#type: String,
404 pub sub_type: Option<String>,
405 pub parent_id: Option<String>,
406 pub root_id: Option<String>,
407 pub content: String,
408 pub attachments: Vec<String>,
409 pub issuer_tag: String,
410 #[serde(serialize_with = "serialize_timestamp_iso")]
411 pub created_at: Timestamp,
412}
413
414#[derive(Debug, Clone, Default, Deserialize)]
416#[serde(rename_all = "camelCase")]
417pub struct ListActionsQuery {
418 #[serde(rename = "type")]
419 pub r#type: Option<String>,
420 pub parent_id: Option<String>,
421 pub offset: Option<usize>,
422 pub limit: Option<usize>,
423}
424
425#[derive(Debug, Clone, Serialize, Deserialize)]
427#[serde(rename_all = "camelCase")]
428pub struct FileUploadResponse {
429 pub file_id: String,
430 pub descriptor: String,
431 pub variants: Vec<FileVariantInfo>,
432}
433
434#[derive(Debug, Clone, Serialize, Deserialize)]
436#[serde(rename_all = "camelCase")]
437pub struct FileVariantInfo {
438 pub variant_id: String,
439 pub format: String,
440 pub size: u64,
441 pub resolution: Option<(u32, u32)>,
442}
443
444#[derive(Debug, Clone, Serialize, Deserialize)]
446pub struct TagInfo {
447 pub tag: String,
448 #[serde(skip_serializing_if = "Option::is_none")]
449 pub count: Option<u32>,
450}
451
452#[derive(Debug, Clone, Serialize, Deserialize)]
457#[serde(rename_all = "camelCase")]
458pub struct PaginationInfo {
459 pub offset: usize,
460 pub limit: usize,
461 pub total: usize,
462}
463
464#[derive(Debug, Clone, Serialize, Deserialize)]
469#[serde(rename_all = "camelCase")]
470pub struct CursorPaginationInfo {
471 pub next_cursor: Option<String>,
473 pub has_more: bool,
475}
476
477#[derive(Debug, Clone, Serialize, Deserialize)]
479pub struct CursorData {
480 pub s: String,
482 pub v: serde_json::Value,
484 pub id: String,
486}
487
488impl CursorData {
489 pub fn new(sort_field: &str, sort_value: serde_json::Value, item_id: &str) -> Self {
491 Self { s: sort_field.to_string(), v: sort_value, id: item_id.to_string() }
492 }
493
494 pub fn encode(&self) -> String {
496 use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
497 let json = serde_json::to_string(self).unwrap_or_default();
498 URL_SAFE_NO_PAD.encode(json.as_bytes())
499 }
500
501 pub fn decode(cursor: &str) -> Option<Self> {
503 use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
504 let bytes = URL_SAFE_NO_PAD.decode(cursor).ok()?;
505 let json = String::from_utf8(bytes).ok()?;
506 serde_json::from_str(&json).ok()
507 }
508
509 pub fn timestamp(&self) -> Option<i64> {
511 self.v.as_i64()
512 }
513
514 pub fn string_value(&self) -> Option<&str> {
516 self.v.as_str()
517 }
518}
519
520#[derive(Debug, Serialize, Deserialize)]
522#[serde(rename_all = "camelCase")]
523pub struct ApiResponse<T> {
524 pub data: T,
525 #[serde(skip_serializing_if = "Option::is_none")]
526 pub pagination: Option<PaginationInfo>,
527 #[serde(skip_serializing_if = "Option::is_none")]
528 pub cursor_pagination: Option<CursorPaginationInfo>,
529 #[serde(serialize_with = "serialize_timestamp_iso")]
530 pub time: Timestamp,
531 #[serde(skip_serializing_if = "Option::is_none")]
532 pub req_id: Option<String>,
533}
534
535impl<T> ApiResponse<T> {
536 pub fn new(data: T) -> Self {
538 Self {
539 data,
540 pagination: None,
541 cursor_pagination: None,
542 time: Timestamp::now(),
543 req_id: None,
544 }
545 }
546
547 pub fn with_pagination(data: T, offset: usize, limit: usize, total: usize) -> Self {
549 Self {
550 data,
551 pagination: Some(PaginationInfo { offset, limit, total }),
552 cursor_pagination: None,
553 time: Timestamp::now(),
554 req_id: None,
555 }
556 }
557
558 pub fn with_cursor_pagination(data: T, next_cursor: Option<String>, has_more: bool) -> Self {
560 Self {
561 data,
562 pagination: None,
563 cursor_pagination: Some(CursorPaginationInfo { next_cursor, has_more }),
564 time: Timestamp::now(),
565 req_id: None,
566 }
567 }
568
569 pub fn with_req_id(mut self, req_id: String) -> Self {
571 self.req_id = Some(req_id);
572 self
573 }
574}
575
576#[derive(Debug, Serialize, Deserialize)]
578#[serde(rename_all = "camelCase")]
579pub struct ErrorResponse {
580 pub error: ErrorDetails,
581}
582
583#[derive(Debug, Serialize, Deserialize)]
585#[serde(rename_all = "camelCase")]
586pub struct ErrorDetails {
587 pub code: String,
588 pub message: String,
589 #[serde(skip_serializing_if = "Option::is_none")]
590 pub details: Option<serde_json::Value>,
591}
592
593impl ErrorResponse {
594 pub fn new(code: String, message: String) -> Self {
596 Self { error: ErrorDetails { code, message, details: None } }
597 }
598
599 pub fn with_details(mut self, details: serde_json::Value) -> Self {
601 self.error.details = Some(details);
602 self
603 }
604}
605
606#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
611#[serde(rename_all = "lowercase")]
612pub enum AccessLevel {
613 None,
614 Read,
615 Comment,
616 Write,
617 Admin,
618}
619
620impl AccessLevel {
621 pub fn as_str(&self) -> &'static str {
622 match self {
623 Self::None => "none",
624 Self::Read => "read",
625 Self::Comment => "comment",
626 Self::Write => "write",
627 Self::Admin => "admin",
628 }
629 }
630
631 pub fn min(self, other: Self) -> Self {
634 match (self, other) {
635 (Self::None, _) | (_, Self::None) => Self::None,
636 (Self::Read, _) | (_, Self::Read) => Self::Read,
637 (Self::Comment, _) | (_, Self::Comment) => Self::Comment,
638 (Self::Write, _) | (_, Self::Write) => Self::Write,
639 (Self::Admin, Self::Admin) => Self::Admin,
640 }
641 }
642
643 pub fn from_perm_char(c: char) -> Self {
648 match c {
649 'W' | 'A' => Self::Write,
650 'C' => Self::Comment,
651 _ => Self::Read,
652 }
653 }
654}
655
656#[derive(Debug, Clone, PartialEq, Eq)]
661pub enum TokenScope {
662 File { file_id: String, access: AccessLevel },
664 ApkgPublish,
666}
667
668impl TokenScope {
669 pub fn parse(s: &str) -> Option<Self> {
676 if s == "apkg:publish" {
677 return Some(Self::ApkgPublish);
678 }
679 let parts: Vec<&str> = s.split(':').collect();
680 if parts.len() == 3 && parts[0] == "file" {
681 let access = match parts[2] {
682 "W" => AccessLevel::Write,
683 "C" => AccessLevel::Comment,
684 _ => AccessLevel::Read, };
686 return Some(Self::File { file_id: parts[1].to_string(), access });
687 }
688 None
689 }
690
691 pub fn file_id(&self) -> Option<&str> {
693 match self {
694 Self::File { file_id, .. } => Some(file_id),
695 Self::ApkgPublish => None,
696 }
697 }
698
699 pub fn file_access(&self) -> Option<AccessLevel> {
701 match self {
702 Self::File { access, .. } => Some(*access),
703 Self::ApkgPublish => None,
704 }
705 }
706
707 pub fn matches_file(&self, target_file_id: &str) -> bool {
709 match self {
710 Self::File { file_id, .. } => file_id == target_file_id,
711 Self::ApkgPublish => false,
712 }
713 }
714}
715
716#[derive(Debug, Clone)]
718pub struct ProfileAttrs {
719 pub id_tag: Box<str>,
720 pub profile_type: Box<str>,
721 pub tenant_tag: Box<str>,
722 pub roles: Vec<Box<str>>,
723 pub status: Box<str>,
724 pub following: bool,
725 pub connected: bool,
726 pub visibility: Box<str>,
727}
728
729impl AttrSet for ProfileAttrs {
730 fn get(&self, key: &str) -> Option<&str> {
731 match key {
732 "id_tag" => Some(&self.id_tag),
733 "profile_type" => Some(&self.profile_type),
734 "tenant_tag" | "owner_id_tag" => Some(&self.tenant_tag),
735 "status" => Some(&self.status),
736 "following" => Some(if self.following { "true" } else { "false" }),
737 "connected" => Some(if self.connected { "true" } else { "false" }),
738 "visibility" => Some(&self.visibility),
739 _ => None,
740 }
741 }
742
743 fn get_list(&self, key: &str) -> Option<Vec<&str>> {
744 match key {
745 "roles" => Some(self.roles.iter().map(AsRef::as_ref).collect()),
746 _ => None,
747 }
748 }
749}
750
751#[derive(Debug, Clone)]
753pub struct ActionAttrs {
754 pub typ: Box<str>,
755 pub sub_typ: Option<Box<str>>,
756 pub tenant_id_tag: Box<str>,
758 pub issuer_id_tag: Box<str>,
760 pub parent_id: Option<Box<str>>,
761 pub root_id: Option<Box<str>>,
762 pub audience_tag: Vec<Box<str>>,
763 pub tags: Vec<Box<str>>,
764 pub visibility: Box<str>,
765 pub following: bool,
767 pub connected: bool,
769}
770
771impl AttrSet for ActionAttrs {
772 fn get(&self, key: &str) -> Option<&str> {
773 match key {
774 "type" => Some(&self.typ),
775 "sub_type" => self.sub_typ.as_deref(),
776 "tenant_id_tag" | "owner_id_tag" => Some(&self.tenant_id_tag),
778 "issuer_id_tag" => Some(&self.issuer_id_tag),
779 "parent_id" => self.parent_id.as_deref(),
780 "root_id" => self.root_id.as_deref(),
781 "visibility" => Some(&self.visibility),
782 "following" => Some(if self.following { "true" } else { "false" }),
783 "connected" => Some(if self.connected { "true" } else { "false" }),
784 _ => None,
785 }
786 }
787
788 fn get_list(&self, key: &str) -> Option<Vec<&str>> {
789 match key {
790 "audience_tag" => Some(self.audience_tag.iter().map(AsRef::as_ref).collect()),
791 "tags" => Some(self.tags.iter().map(AsRef::as_ref).collect()),
792 _ => None,
793 }
794 }
795}
796
797#[derive(Debug, Clone)]
799pub struct FileAttrs {
800 pub file_id: Box<str>,
801 pub owner_id_tag: Box<str>,
802 pub mime_type: Box<str>,
803 pub tags: Vec<Box<str>>,
804 pub visibility: Box<str>,
805 pub access_level: AccessLevel,
806 pub following: bool,
808 pub connected: bool,
810}
811
812impl AttrSet for FileAttrs {
813 fn get(&self, key: &str) -> Option<&str> {
814 match key {
815 "file_id" => Some(&self.file_id),
816 "owner_id_tag" => Some(&self.owner_id_tag),
817 "mime_type" => Some(&self.mime_type),
818 "visibility" => Some(&self.visibility),
819 "access_level" => Some(self.access_level.as_str()),
820 "following" => Some(if self.following { "true" } else { "false" }),
821 "connected" => Some(if self.connected { "true" } else { "false" }),
822 _ => None,
823 }
824 }
825
826 fn get_list(&self, key: &str) -> Option<Vec<&str>> {
827 match key {
828 "tags" => Some(self.tags.iter().map(AsRef::as_ref).collect()),
829 _ => None,
830 }
831 }
832}
833
834#[derive(Debug, Clone)]
839pub struct SubjectAttrs {
840 pub id_tag: Box<str>,
841 pub roles: Vec<Box<str>>,
842 pub tier: Box<str>, pub quota_remaining_bytes: Box<str>, pub rate_limit_remaining: Box<str>, pub banned: bool,
846 pub email_verified: bool,
847}
848
849impl AttrSet for SubjectAttrs {
850 fn get(&self, key: &str) -> Option<&str> {
851 match key {
852 "id_tag" => Some(&self.id_tag),
853 "tier" => Some(&self.tier),
854 "quota_remaining" | "quota_remaining_bytes" => Some(&self.quota_remaining_bytes),
855 "rate_limit_remaining" => Some(&self.rate_limit_remaining),
856 "banned" => Some(if self.banned { "true" } else { "false" }),
857 "email_verified" => Some(if self.email_verified { "true" } else { "false" }),
858 _ => None,
859 }
860 }
861
862 fn get_list(&self, key: &str) -> Option<Vec<&str>> {
863 match key {
864 "roles" => Some(self.roles.iter().map(AsRef::as_ref).collect()),
865 _ => None,
866 }
867 }
868}
869
870