1pub use kcode_k1_invites::UserId;
2pub use kcode_k1_txn_ordering::TxId;
3
4#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
5pub struct GroupId(TxId);
6
7#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
8pub enum SentinelGroup {
9 AllUsers,
10 AllModels,
11 LocalModels,
12}
13
14pub const ALL_USERS: GroupId = GroupId::new(TxId::from_bytes([
15 255, 75, 49, 71, 82, 80, 0, 0, 0, 0, 0, 1,
16]));
17pub const ALL_MODELS: GroupId = GroupId::new(TxId::from_bytes([
18 255, 75, 49, 71, 82, 80, 0, 0, 0, 0, 0, 2,
19]));
20pub const LOCAL_MODELS: GroupId = GroupId::new(TxId::from_bytes([
21 255, 75, 49, 71, 82, 80, 0, 0, 0, 0, 0, 3,
22]));
23
24impl GroupId {
25 pub const fn new(txid: TxId) -> Self {
26 Self(txid)
27 }
28
29 pub const fn txid(self) -> TxId {
30 self.0
31 }
32
33 pub const fn sentinel(self) -> Option<SentinelGroup> {
34 match self {
35 ALL_USERS => Some(SentinelGroup::AllUsers),
36 ALL_MODELS => Some(SentinelGroup::AllModels),
37 LOCAL_MODELS => Some(SentinelGroup::LocalModels),
38 _ => None,
39 }
40 }
41}
42
43#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
44pub struct ModelId([u8; 32]);
45
46impl ModelId {
47 pub const fn from_bytes(bytes: [u8; 32]) -> Self {
48 Self(bytes)
49 }
50
51 pub const fn as_bytes(&self) -> &[u8; 32] {
52 &self.0
53 }
54
55 pub const fn into_bytes(self) -> [u8; 32] {
56 self.0
57 }
58}
59
60#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
61pub struct GroupName(String);
62
63impl GroupName {
64 pub fn new(value: String) -> Result<Self, String> {
65 if !(1..=128).contains(&value.len()) {
66 return Err("group name must be 1 through 128 UTF-8 bytes".to_owned());
67 }
68 if value.chars().any(char::is_control) {
69 return Err("group name must not contain control characters".to_owned());
70 }
71 if !value.chars().any(|character| !character.is_whitespace()) {
72 return Err("group name must contain a non-whitespace character".to_owned());
73 }
74 Ok(Self(value))
75 }
76
77 pub fn as_str(&self) -> &str {
78 &self.0
79 }
80
81 pub fn into_string(self) -> String {
82 self.0
83 }
84}
85
86#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
87pub enum GroupRole {
88 User,
89 Admin,
90 Owner,
91}
92
93#[derive(Clone, Debug, Eq, PartialEq)]
94pub struct GroupUser {
95 user_id: UserId,
96 role: GroupRole,
97}
98
99impl GroupUser {
100 pub fn user_id(&self) -> UserId {
101 self.user_id
102 }
103
104 pub fn role(&self) -> GroupRole {
105 self.role
106 }
107}
108
109#[derive(Clone, Debug, Eq, PartialEq)]
110pub struct GroupRevision {
111 group_id: GroupId,
112 txid: TxId,
113}
114
115impl GroupRevision {
116 pub fn group_id(&self) -> GroupId {
117 self.group_id
118 }
119
120 pub fn txid(&self) -> TxId {
121 self.txid
122 }
123}
124
125#[derive(Clone, Debug, Eq, PartialEq)]
126pub struct Group {
127 id: GroupId,
128 name: GroupName,
129 revision: GroupRevision,
130 users: Vec<GroupUser>,
131 models: Vec<ModelId>,
132}
133
134impl Group {
135 pub fn id(&self) -> GroupId {
136 self.id
137 }
138
139 pub fn name(&self) -> &GroupName {
140 &self.name
141 }
142
143 pub fn revision(&self) -> &GroupRevision {
144 &self.revision
145 }
146
147 pub fn users(&self) -> &[GroupUser] {
148 &self.users
149 }
150
151 pub fn models(&self) -> &[ModelId] {
152 &self.models
153 }
154}
155
156#[derive(Clone, Debug, Eq, PartialEq)]
157pub struct GroupMemberships {
158 revision: Option<TxId>,
159 user_groups: Vec<GroupId>,
160 model_groups: Vec<GroupId>,
161 shared_groups: Vec<GroupId>,
162}
163
164impl GroupMemberships {
165 pub fn revision(&self) -> Option<TxId> {
166 self.revision
167 }
168
169 pub fn user_groups(&self) -> &[GroupId] {
170 &self.user_groups
171 }
172
173 pub fn model_groups(&self) -> &[GroupId] {
174 &self.model_groups
175 }
176
177 pub fn shared_groups(&self) -> &[GroupId] {
178 &self.shared_groups
179 }
180}
181
182#[derive(Clone, Debug, Eq, PartialEq)]
183pub enum GroupAction {
184 Create {
185 owner: UserId,
186 name: GroupName,
187 },
188 Rename {
189 group: GroupId,
190 actor: UserId,
191 name: GroupName,
192 },
193 SetUserRole {
194 group: GroupId,
195 actor: UserId,
196 user: UserId,
197 role: Option<GroupRole>,
198 },
199 SetModelMembership {
200 group: GroupId,
201 actor: UserId,
202 model: ModelId,
203 present: bool,
204 },
205}
206
207#[derive(Clone, Debug, Eq, PartialEq)]
208pub enum ApplyOutcome {
209 Applied(GroupRevision),
210 Unchanged(GroupRevision),
211 Rejected(String),
212}
213
214pub mod projection_values {
215 use super::{
216 Group, GroupId, GroupMemberships, GroupName, GroupRevision, GroupRole, GroupUser, ModelId,
217 TxId, UserId,
218 };
219
220 pub fn group_user(user_id: UserId, role: GroupRole) -> GroupUser {
221 GroupUser { user_id, role }
222 }
223
224 pub fn group_revision(group_id: GroupId, txid: TxId) -> GroupRevision {
225 GroupRevision { group_id, txid }
226 }
227
228 pub fn group(
229 id: GroupId,
230 name: GroupName,
231 revision: GroupRevision,
232 users: Vec<GroupUser>,
233 models: Vec<ModelId>,
234 ) -> Group {
235 Group {
236 id,
237 name,
238 revision,
239 users,
240 models,
241 }
242 }
243
244 pub fn group_memberships(
245 revision: Option<TxId>,
246 user_groups: Vec<GroupId>,
247 model_groups: Vec<GroupId>,
248 shared_groups: Vec<GroupId>,
249 ) -> GroupMemberships {
250 GroupMemberships {
251 revision,
252 user_groups,
253 model_groups,
254 shared_groups,
255 }
256 }
257}