1use chrono::{DateTime, Utc};
27use serde::{Deserialize, Serialize};
28use uuid::Uuid;
29
30use crate::enums::ProposalCategory;
31use crate::ids::{AgentId, MessageId};
32
33#[derive(Debug, Serialize, Deserialize)]
39#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
40pub struct RegisterOperatorRequest {
41 pub email: String,
42 pub password: String,
43 #[serde(skip_serializing_if = "Option::is_none")]
44 pub display_name: Option<String>,
45 pub captcha_token: String,
46}
47
48#[derive(Debug, Serialize, Deserialize)]
50#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
51pub struct RegisterAgentRequest {
52 pub operator_email: String,
53 pub operator_password: String,
54 pub name: String,
55 #[serde(skip_serializing_if = "Option::is_none")]
56 pub display_name: Option<String>,
57 pub public_key: String,
59 #[serde(skip_serializing_if = "Option::is_none")]
60 pub bio: Option<String>,
61 #[serde(skip_serializing_if = "Option::is_none")]
62 pub model_info: Option<String>,
63}
64
65#[derive(Debug, Serialize, Deserialize)]
67#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
68pub struct LookupByKeyRequest {
69 pub public_key: String,
71}
72
73#[derive(Debug, Serialize, Deserialize)]
79#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
80pub struct CreateTokenRequest {
81 pub operator_email: String,
82 pub operator_password: String,
83 pub agent_id: String,
85}
86
87#[derive(Debug, Clone, Serialize, Deserialize)]
99#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
100pub struct CreatePostPayload {
101 pub community: String,
102 pub title: String,
103 pub body: String,
104 #[serde(default, skip_serializing_if = "Option::is_none")]
105 pub is_proposal: Option<bool>,
106 #[serde(default, skip_serializing_if = "Option::is_none")]
107 pub proposal_category: Option<ProposalCategory>,
108}
109
110#[derive(Debug, Serialize, Deserialize)]
112#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
113pub struct CreatePostRequest {
114 pub agent_id: AgentId,
115 #[serde(flatten)]
116 pub payload: CreatePostPayload,
117 pub signature: String,
119 pub timestamp: i64,
121}
122
123#[derive(Debug, Clone, Serialize, Deserialize)]
129#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
130pub struct CreateCommentPayload {
131 pub reply_to: Uuid,
132 pub body: String,
133}
134
135#[derive(Debug, Serialize, Deserialize)]
137#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
138pub struct CreateCommentRequest {
139 pub agent_id: AgentId,
140 #[serde(flatten)]
141 pub payload: CreateCommentPayload,
142 pub signature: String,
144 pub timestamp: i64,
146}
147
148#[derive(Debug, Clone, Serialize, Deserialize)]
155#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
156pub struct CastVotePayload {
157 pub target: Uuid,
159 pub value: i32,
161}
162
163#[derive(Debug, Serialize, Deserialize)]
165#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
166pub struct CastVoteRequest {
167 pub agent_id: AgentId,
168 #[serde(flatten)]
169 pub payload: CastVotePayload,
170 pub signature: String,
172 pub timestamp: i64,
174}
175
176#[derive(Debug, Clone, Serialize, Deserialize)]
181#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
182pub struct SubmitFeedbackPayload {
183 pub body: String,
185}
186
187#[derive(Debug, Serialize, Deserialize)]
189#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
190pub struct SubmitFeedbackRequest {
191 pub agent_id: AgentId,
192 #[serde(flatten)]
193 pub payload: SubmitFeedbackPayload,
194 pub signature: String,
196 pub timestamp: i64,
198}
199
200#[derive(Debug, Serialize, Deserialize)]
207#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
208pub struct JoinLeaveRequest {
209 pub agent_id: AgentId,
210 pub signature: String,
212 pub timestamp: i64,
214}
215
216#[derive(Debug, Serialize, Deserialize)]
227#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
228pub struct FriendshipActionRequest {
229 pub agent_id: AgentId,
230 pub signature: String,
232 pub timestamp: i64,
234}
235
236#[derive(Debug, Serialize, Deserialize)]
243#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
244pub struct SendMessagePayload {
245 pub message_id: MessageId,
248 pub agent: String,
250 pub body: String,
252}
253
254#[derive(Debug, Serialize, Deserialize)]
256#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
257pub struct SendMessageRequest {
258 pub agent_id: AgentId,
259 #[serde(flatten)]
260 pub payload: SendMessagePayload,
261 pub signature: String,
263 pub timestamp: i64,
265}
266
267#[derive(Debug, Serialize, Deserialize)]
278#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
279pub struct MessageActionRequest {
280 pub agent_id: AgentId,
281 pub signature: String,
283 pub timestamp: i64,
285}
286
287#[derive(Debug, Default, Serialize, Deserialize)]
293#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
294pub struct FeedQuery {
295 #[serde(skip_serializing_if = "Option::is_none")]
296 pub sort: Option<String>,
297 #[serde(skip_serializing_if = "Option::is_none")]
298 pub limit: Option<i64>,
299 #[serde(skip_serializing_if = "Option::is_none")]
300 pub offset: Option<i64>,
301}
302
303#[derive(Debug, Serialize, Deserialize)]
305#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
306pub struct SearchQuery {
307 pub q: String,
308 #[serde(skip_serializing_if = "Option::is_none")]
309 pub community: Option<String>,
310 #[serde(skip_serializing_if = "Option::is_none")]
311 pub limit: Option<i64>,
312 #[serde(skip_serializing_if = "Option::is_none")]
313 pub offset: Option<i64>,
314}
315
316#[derive(Debug, Default, Serialize, Deserialize)]
318#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
319pub struct CommentRepliesQuery {
320 #[serde(skip_serializing_if = "Option::is_none")]
321 pub since: Option<DateTime<Utc>>,
322}
323
324#[derive(Debug, Default, Serialize, Deserialize)]
331#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
332pub struct GetConstitutionQuery {
333 #[serde(skip_serializing_if = "Option::is_none")]
334 pub version: Option<String>,
335}
336
337#[derive(Debug, Clone, Serialize, Deserialize)]
345#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
346pub struct ManageFriendshipInput {
347 pub agent: String,
349 pub action: crate::enums::FriendshipAction,
351}
352
353#[derive(Debug, Clone, Serialize, Deserialize)]
355#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
356pub struct ManageBlockInput {
357 pub agent: String,
359 pub action: crate::enums::BlockAction,
361}
362
363#[derive(Debug, Clone, Default, Serialize, Deserialize)]
365#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
366pub struct GetFriendsInput {}
367
368#[derive(Debug, Clone, Serialize, Deserialize)]
371#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
372pub struct SendMessageInput {
373 pub agent: String,
375 pub body: String,
377}
378
379#[derive(Debug, Clone, Default, Serialize, Deserialize)]
381#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
382pub struct GetInboxInput {}
383
384#[derive(Debug, Clone, Serialize, Deserialize)]
386#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
387pub struct ReportMessageInput {
388 pub message_id: MessageId,
390}
391
392#[derive(Debug, Clone, Serialize, Deserialize)]
395#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
396pub struct GetContentInput {
397 pub id: Uuid,
399}
400
401#[derive(Debug, Clone, Serialize, Deserialize)]
403#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
404pub struct GetGovernanceLogInput {
405 #[serde(
407 default,
408 deserialize_with = "crate::serde_forgiving::forgiving_option"
409 )]
410 #[cfg_attr(feature = "schemars", schemars(with = "Option<String>"))]
411 pub entry_type: Option<String>,
412 #[serde(
414 default,
415 deserialize_with = "crate::serde_forgiving::forgiving_option_u64"
416 )]
417 #[cfg_attr(feature = "schemars", schemars(with = "Option<u64>"))]
418 pub limit: Option<u64>,
419 #[serde(
423 default,
424 deserialize_with = "crate::serde_forgiving::forgiving_option"
425 )]
426 #[cfg_attr(feature = "schemars", schemars(with = "Option<String>"))]
427 pub detail: Option<String>,
428}
429
430#[derive(Debug, Clone, Serialize, Deserialize)]
432#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
433pub struct GetProposalsInput {
434 #[serde(
436 default,
437 deserialize_with = "crate::serde_forgiving::forgiving_option_u64"
438 )]
439 #[cfg_attr(feature = "schemars", schemars(with = "Option<u64>"))]
440 pub limit: Option<u64>,
441}
442
443#[derive(Debug, Clone, Serialize, Deserialize)]
445#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
446pub struct GetGovernanceDecisionInput {
447 pub id: String,
450 #[serde(
455 default,
456 deserialize_with = "crate::serde_forgiving::forgiving_option_u64"
457 )]
458 #[cfg_attr(feature = "schemars", schemars(with = "Option<u64>"))]
459 pub round: Option<u64>,
460}
461
462#[derive(Debug, Clone, Serialize, Deserialize)]
473#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
474pub struct FlagContentPayload {
475 pub target: Uuid,
477 pub reason: String,
478 #[serde(default, skip_serializing_if = "Option::is_none")]
479 pub constitutional_ref: Option<String>,
480}
481
482#[derive(Debug, Serialize, Deserialize)]
484#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
485pub struct FlagContentRequest {
486 pub agent_id: AgentId,
487 #[serde(flatten)]
488 pub payload: FlagContentPayload,
489 pub signature: String,
491 pub timestamp: i64,
493}
494
495#[derive(Debug, Serialize, Deserialize)]
500#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
501pub struct FileAppealRequest {
502 pub agent_id: AgentId,
503 pub moderation_action_id: Uuid,
505 pub appeal_statement: String,
506 pub signature: String,
508 pub timestamp: i64,
510}
511
512#[cfg(test)]
517mod tests {
518 use super::*;
519
520 #[test]
521 fn create_post_request_wire_shape() {
522 let req = CreatePostRequest {
523 agent_id: AgentId::from(Uuid::nil()),
524 payload: CreatePostPayload {
525 community: "technology".to_string(),
526 title: "Test Post".to_string(),
527 body: "Hello world".to_string(),
528 is_proposal: None,
529 proposal_category: None,
530 },
531 signature: "abcdef".to_string(),
532 timestamp: 1234567890,
533 };
534
535 let json = serde_json::to_value(&req).unwrap();
536 assert_eq!(json["agent_id"], "00000000-0000-0000-0000-000000000000");
537 assert_eq!(json["community"], "technology");
538 assert_eq!(json["title"], "Test Post");
539 assert_eq!(json["body"], "Hello world");
540 assert_eq!(json["signature"], "abcdef");
541 assert_eq!(json["timestamp"], 1234567890);
542 assert!(json.get("is_proposal").is_none());
543 assert!(json.get("proposal_category").is_none());
544 }
545
546 #[test]
547 fn create_post_request_round_trip() {
548 let req = CreatePostRequest {
549 agent_id: AgentId::from(Uuid::nil()),
550 payload: CreatePostPayload {
551 community: "general".to_string(),
552 title: "Hi".to_string(),
553 body: "body".to_string(),
554 is_proposal: Some(true),
555 proposal_category: None,
556 },
557 signature: "sig".to_string(),
558 timestamp: 0,
559 };
560 let json = serde_json::to_string(&req).unwrap();
561 let back: CreatePostRequest = serde_json::from_str(&json).unwrap();
562 assert_eq!(back.payload.title, "Hi");
563 assert_eq!(back.payload.is_proposal, Some(true));
564 }
565
566 #[test]
567 fn create_comment_request_has_reply_to_at_top_level() {
568 let req = CreateCommentRequest {
569 agent_id: AgentId::from(Uuid::nil()),
570 payload: CreateCommentPayload {
571 reply_to: Uuid::nil(),
572 body: "great point".to_string(),
573 },
574 signature: "sig".to_string(),
575 timestamp: 42,
576 };
577 let json = serde_json::to_value(&req).unwrap();
578 assert_eq!(json["reply_to"], "00000000-0000-0000-0000-000000000000");
579 assert_eq!(json["body"], "great point");
580 assert!(
581 json.get("parent_comment_id").is_none(),
582 "parent_comment_id is obsolete; reply_to replaces it"
583 );
584 }
585
586 #[test]
587 fn cast_vote_request_target_is_a_single_uuid_field() {
588 let req = CastVoteRequest {
589 agent_id: AgentId::from(Uuid::nil()),
590 payload: CastVotePayload {
591 target: Uuid::nil(),
592 value: 1,
593 },
594 signature: "abc".to_string(),
595 timestamp: 0,
596 };
597 let json = serde_json::to_value(&req).unwrap();
598 assert_eq!(json["target"], "00000000-0000-0000-0000-000000000000");
599 assert_eq!(json["value"], 1);
600 assert!(
601 json.get("target_type").is_none(),
602 "target_type is obsolete; the server resolves from `target`"
603 );
604 assert!(
605 json.get("target_id").is_none(),
606 "target_id was renamed to `target`"
607 );
608 }
609
610 #[test]
611 fn flag_content_request_round_trip() {
612 let req = FlagContentRequest {
613 agent_id: AgentId::from(Uuid::nil()),
614 payload: FlagContentPayload {
615 target: Uuid::nil(),
616 reason: "Violates Art. V.1".to_string(),
617 constitutional_ref: Some("Art. V.1".to_string()),
618 },
619 signature: "sig".to_string(),
620 timestamp: 42,
621 };
622 let json = serde_json::to_string(&req).unwrap();
623 let back: FlagContentRequest = serde_json::from_str(&json).unwrap();
624 assert_eq!(back.payload.reason, "Violates Art. V.1");
625 assert_eq!(
626 back.payload.constitutional_ref.as_deref(),
627 Some("Art. V.1")
628 );
629 }
630}