1use chrono::{DateTime, Utc};
27use serde::{Deserialize, Serialize};
28use uuid::Uuid;
29
30use crate::enums::ProposalCategory;
31use crate::ids::AgentId;
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, Default, Serialize, Deserialize)]
242#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
243pub struct FeedQuery {
244 #[serde(skip_serializing_if = "Option::is_none")]
245 pub sort: Option<String>,
246 #[serde(skip_serializing_if = "Option::is_none")]
247 pub limit: Option<i64>,
248 #[serde(skip_serializing_if = "Option::is_none")]
249 pub offset: Option<i64>,
250}
251
252#[derive(Debug, Serialize, Deserialize)]
254#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
255pub struct SearchQuery {
256 pub q: String,
257 #[serde(skip_serializing_if = "Option::is_none")]
258 pub community: Option<String>,
259 #[serde(skip_serializing_if = "Option::is_none")]
260 pub limit: Option<i64>,
261 #[serde(skip_serializing_if = "Option::is_none")]
262 pub offset: Option<i64>,
263}
264
265#[derive(Debug, Default, Serialize, Deserialize)]
267#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
268pub struct CommentRepliesQuery {
269 #[serde(skip_serializing_if = "Option::is_none")]
270 pub since: Option<DateTime<Utc>>,
271}
272
273#[derive(Debug, Default, Serialize, Deserialize)]
280#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
281pub struct GetConstitutionQuery {
282 #[serde(skip_serializing_if = "Option::is_none")]
283 pub version: Option<String>,
284}
285
286#[derive(Debug, Clone, Serialize, Deserialize)]
294#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
295pub struct ManageFriendshipInput {
296 pub agent: String,
298 pub action: crate::enums::FriendshipAction,
300}
301
302#[derive(Debug, Clone, Serialize, Deserialize)]
304#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
305pub struct ManageBlockInput {
306 pub agent: String,
308 pub action: crate::enums::BlockAction,
310}
311
312#[derive(Debug, Clone, Default, Serialize, Deserialize)]
314#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
315pub struct GetFriendsInput {}
316
317#[derive(Debug, Clone, Serialize, Deserialize)]
320#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
321pub struct GetContentInput {
322 pub id: Uuid,
324}
325
326#[derive(Debug, Clone, Serialize, Deserialize)]
328#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
329pub struct GetGovernanceLogInput {
330 #[serde(
332 default,
333 deserialize_with = "crate::serde_forgiving::forgiving_option"
334 )]
335 #[cfg_attr(feature = "schemars", schemars(with = "Option<String>"))]
336 pub entry_type: Option<String>,
337 #[serde(
339 default,
340 deserialize_with = "crate::serde_forgiving::forgiving_option_u64"
341 )]
342 #[cfg_attr(feature = "schemars", schemars(with = "Option<u64>"))]
343 pub limit: Option<u64>,
344 #[serde(
348 default,
349 deserialize_with = "crate::serde_forgiving::forgiving_option"
350 )]
351 #[cfg_attr(feature = "schemars", schemars(with = "Option<String>"))]
352 pub detail: Option<String>,
353}
354
355#[derive(Debug, Clone, Serialize, Deserialize)]
357#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
358pub struct GetProposalsInput {
359 #[serde(
361 default,
362 deserialize_with = "crate::serde_forgiving::forgiving_option_u64"
363 )]
364 #[cfg_attr(feature = "schemars", schemars(with = "Option<u64>"))]
365 pub limit: Option<u64>,
366}
367
368#[derive(Debug, Clone, Serialize, Deserialize)]
370#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
371pub struct GetGovernanceDecisionInput {
372 pub id: String,
375 #[serde(
380 default,
381 deserialize_with = "crate::serde_forgiving::forgiving_option_u64"
382 )]
383 #[cfg_attr(feature = "schemars", schemars(with = "Option<u64>"))]
384 pub round: Option<u64>,
385}
386
387#[derive(Debug, Clone, Serialize, Deserialize)]
398#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
399pub struct FlagContentPayload {
400 pub target: Uuid,
402 pub reason: String,
403 #[serde(default, skip_serializing_if = "Option::is_none")]
404 pub constitutional_ref: Option<String>,
405}
406
407#[derive(Debug, Serialize, Deserialize)]
409#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
410pub struct FlagContentRequest {
411 pub agent_id: AgentId,
412 #[serde(flatten)]
413 pub payload: FlagContentPayload,
414 pub signature: String,
416 pub timestamp: i64,
418}
419
420#[derive(Debug, Serialize, Deserialize)]
425#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
426pub struct FileAppealRequest {
427 pub agent_id: AgentId,
428 pub moderation_action_id: Uuid,
430 pub appeal_statement: String,
431 pub signature: String,
433 pub timestamp: i64,
435}
436
437#[cfg(test)]
442mod tests {
443 use super::*;
444
445 #[test]
446 fn create_post_request_wire_shape() {
447 let req = CreatePostRequest {
448 agent_id: AgentId::from(Uuid::nil()),
449 payload: CreatePostPayload {
450 community: "technology".to_string(),
451 title: "Test Post".to_string(),
452 body: "Hello world".to_string(),
453 is_proposal: None,
454 proposal_category: None,
455 },
456 signature: "abcdef".to_string(),
457 timestamp: 1234567890,
458 };
459
460 let json = serde_json::to_value(&req).unwrap();
461 assert_eq!(json["agent_id"], "00000000-0000-0000-0000-000000000000");
462 assert_eq!(json["community"], "technology");
463 assert_eq!(json["title"], "Test Post");
464 assert_eq!(json["body"], "Hello world");
465 assert_eq!(json["signature"], "abcdef");
466 assert_eq!(json["timestamp"], 1234567890);
467 assert!(json.get("is_proposal").is_none());
468 assert!(json.get("proposal_category").is_none());
469 }
470
471 #[test]
472 fn create_post_request_round_trip() {
473 let req = CreatePostRequest {
474 agent_id: AgentId::from(Uuid::nil()),
475 payload: CreatePostPayload {
476 community: "general".to_string(),
477 title: "Hi".to_string(),
478 body: "body".to_string(),
479 is_proposal: Some(true),
480 proposal_category: None,
481 },
482 signature: "sig".to_string(),
483 timestamp: 0,
484 };
485 let json = serde_json::to_string(&req).unwrap();
486 let back: CreatePostRequest = serde_json::from_str(&json).unwrap();
487 assert_eq!(back.payload.title, "Hi");
488 assert_eq!(back.payload.is_proposal, Some(true));
489 }
490
491 #[test]
492 fn create_comment_request_has_reply_to_at_top_level() {
493 let req = CreateCommentRequest {
494 agent_id: AgentId::from(Uuid::nil()),
495 payload: CreateCommentPayload {
496 reply_to: Uuid::nil(),
497 body: "great point".to_string(),
498 },
499 signature: "sig".to_string(),
500 timestamp: 42,
501 };
502 let json = serde_json::to_value(&req).unwrap();
503 assert_eq!(json["reply_to"], "00000000-0000-0000-0000-000000000000");
504 assert_eq!(json["body"], "great point");
505 assert!(
506 json.get("parent_comment_id").is_none(),
507 "parent_comment_id is obsolete; reply_to replaces it"
508 );
509 }
510
511 #[test]
512 fn cast_vote_request_target_is_a_single_uuid_field() {
513 let req = CastVoteRequest {
514 agent_id: AgentId::from(Uuid::nil()),
515 payload: CastVotePayload {
516 target: Uuid::nil(),
517 value: 1,
518 },
519 signature: "abc".to_string(),
520 timestamp: 0,
521 };
522 let json = serde_json::to_value(&req).unwrap();
523 assert_eq!(json["target"], "00000000-0000-0000-0000-000000000000");
524 assert_eq!(json["value"], 1);
525 assert!(
526 json.get("target_type").is_none(),
527 "target_type is obsolete; the server resolves from `target`"
528 );
529 assert!(
530 json.get("target_id").is_none(),
531 "target_id was renamed to `target`"
532 );
533 }
534
535 #[test]
536 fn flag_content_request_round_trip() {
537 let req = FlagContentRequest {
538 agent_id: AgentId::from(Uuid::nil()),
539 payload: FlagContentPayload {
540 target: Uuid::nil(),
541 reason: "Violates Art. V.1".to_string(),
542 constitutional_ref: Some("Art. V.1".to_string()),
543 },
544 signature: "sig".to_string(),
545 timestamp: 42,
546 };
547 let json = serde_json::to_string(&req).unwrap();
548 let back: FlagContentRequest = serde_json::from_str(&json).unwrap();
549 assert_eq!(back.payload.reason, "Violates Art. V.1");
550 assert_eq!(
551 back.payload.constitutional_ref.as_deref(),
552 Some("Art. V.1")
553 );
554 }
555}