1use serde::{Deserialize, Serialize};
5
6pub const COMMUNITY_READ_SCHEMA_VERSION: &str = "community-read-v1";
7
8#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
9#[serde(tag = "kind", rename_all = "snake_case")]
10pub enum CommunityReadScope {
11 Global,
12 Space { name: String },
13 Uncategorized,
14}
15
16#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
17pub struct CommunitySummary {
18 pub community_id: String,
19 pub space: String,
20 pub display_name: Option<String>,
21 pub member_count: u64,
22 pub published_generation: i64,
23 pub algo_version: String,
24 pub projection_version: String,
25}
26
27#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
28pub struct CommunityMember {
29 pub space: String,
30 pub node_id: String,
31 pub node_kind: String,
32 pub community_id: String,
33 pub published_generation: i64,
34 pub attachment: String,
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
38#[serde(rename_all = "snake_case")]
39pub enum PageCommunityAssignmentState {
40 Assigned,
41 Held,
42 Dropped,
43}
44
45#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
46pub struct CommunityPageAssignment {
47 pub page_id: String,
48 pub space: String,
49 pub community_id: Option<String>,
50 pub state: PageCommunityAssignmentState,
51 pub score: f64,
52 pub page_version: i64,
53 pub community_published_generation: i64,
54 pub route_version: String,
55 pub updated_at: i64,
56}
57
58#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
59pub struct CommunityListResponse {
60 pub schema_version: String,
61 pub scope: CommunityReadScope,
62 pub communities: Vec<CommunitySummary>,
63 pub next_cursor: Option<String>,
64}
65
66#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
67pub struct CommunityMemberCursor {
68 pub space: String,
69 pub node_id: String,
70}
71
72#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
73pub struct CommunityMembersResponse {
74 pub schema_version: String,
75 pub scope: CommunityReadScope,
76 pub members: Vec<CommunityMember>,
77 pub next_cursor: Option<CommunityMemberCursor>,
78}
79
80#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
81pub struct CommunityPageAssignmentsResponse {
82 pub schema_version: String,
83 pub scope: CommunityReadScope,
84 pub page_assignments: Vec<CommunityPageAssignment>,
85 pub next_cursor: Option<String>,
86}
87
88#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
89#[serde(rename_all = "snake_case")]
90pub enum CommunityProposalAction {
91 CommunitySplit,
92 CommunityMerge,
93 CommunityRename,
94}
95
96#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
97#[serde(tag = "action", rename_all = "snake_case", deny_unknown_fields)]
98pub enum CommunityProposalPayload {
99 CommunitySplit {
100 space: String,
101 source_generation: i64,
102 subject_id: String,
103 old_community_ids: Vec<String>,
104 new_community_ids: Vec<String>,
105 proposed_display_name: Option<String>,
106 },
107 CommunityMerge {
108 space: String,
109 source_generation: i64,
110 subject_id: String,
111 old_community_ids: Vec<String>,
112 new_community_ids: Vec<String>,
113 proposed_display_name: Option<String>,
114 },
115 CommunityRename {
116 space: String,
117 source_generation: i64,
118 subject_id: String,
119 proposed_display_name: String,
120 },
121}
122
123impl CommunityProposalPayload {
124 pub fn action(&self) -> CommunityProposalAction {
125 match self {
126 Self::CommunitySplit { .. } => CommunityProposalAction::CommunitySplit,
127 Self::CommunityMerge { .. } => CommunityProposalAction::CommunityMerge,
128 Self::CommunityRename { .. } => CommunityProposalAction::CommunityRename,
129 }
130 }
131
132 pub fn space(&self) -> &str {
133 match self {
134 Self::CommunitySplit { space, .. }
135 | Self::CommunityMerge { space, .. }
136 | Self::CommunityRename { space, .. } => space,
137 }
138 }
139
140 pub fn source_generation(&self) -> i64 {
141 match self {
142 Self::CommunitySplit {
143 source_generation, ..
144 }
145 | Self::CommunityMerge {
146 source_generation, ..
147 }
148 | Self::CommunityRename {
149 source_generation, ..
150 } => *source_generation,
151 }
152 }
153
154 pub fn subject_id(&self) -> &str {
155 match self {
156 Self::CommunitySplit { subject_id, .. }
157 | Self::CommunityMerge { subject_id, .. }
158 | Self::CommunityRename { subject_id, .. } => subject_id,
159 }
160 }
161
162 pub fn proposed_display_name(&self) -> Option<&str> {
163 match self {
164 Self::CommunitySplit {
165 proposed_display_name,
166 ..
167 }
168 | Self::CommunityMerge {
169 proposed_display_name,
170 ..
171 } => proposed_display_name.as_deref(),
172 Self::CommunityRename {
173 proposed_display_name,
174 ..
175 } => Some(proposed_display_name),
176 }
177 }
178}
179
180#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
181pub struct CommunityProposalSummary {
182 pub id: String,
183 pub action: CommunityProposalAction,
184 pub payload: CommunityProposalPayload,
185 pub created_at: String,
186}
187
188#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
189pub struct ListCommunityProposalsResponse {
190 pub proposals: Vec<CommunityProposalSummary>,
191}
192
193#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
194pub struct CommunityProposalAcceptResponse {
195 pub id: String,
196 pub action: CommunityProposalAction,
197 pub display_name_updated: bool,
198}
199
200#[cfg(test)]
201mod tests {
202 use super::*;
203
204 #[test]
205 fn community_wire_is_versioned_and_proposal_payloads_are_closed() {
206 let response = CommunityListResponse {
207 schema_version: COMMUNITY_READ_SCHEMA_VERSION.to_string(),
208 scope: CommunityReadScope::Space {
209 name: "work".to_string(),
210 },
211 communities: Vec::new(),
212 next_cursor: None,
213 };
214 assert_eq!(
215 serde_json::to_value(response).unwrap()["schema_version"],
216 COMMUNITY_READ_SCHEMA_VERSION
217 );
218
219 let unknown = serde_json::json!({
220 "action": "community_rename",
221 "space": "work",
222 "source_generation": 1,
223 "subject_id": "c1",
224 "proposed_display_name": "Work",
225 "unexpected": true
226 });
227 assert!(serde_json::from_value::<CommunityProposalPayload>(unknown).is_err());
228 }
229}