everruns_core/
organization.rs1use chrono::{DateTime, Utc};
8use serde::{Deserialize, Serialize};
9use std::fmt;
10use std::str::FromStr;
11use uuid::Uuid;
12
13pub const DEFAULT_ORG_ID: i64 = 1;
15
16pub const DEFAULT_ORG_PUBLIC_ID: &str = "org_00000000000000000000000000000001";
18
19pub const ANONYMOUS_USER_ID: Uuid = Uuid::from_bytes([
23 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
24]);
25
26pub const ANONYMOUS_USER_EMAIL: &str = "anonymous@local";
28
29pub const ANONYMOUS_USER_NAME: &str = "Anonymous";
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
34#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
35pub struct Organization {
36 pub public_id: String,
38 pub name: String,
40 pub created_at: DateTime<Utc>,
41 pub updated_at: DateTime<Utc>,
42}
43
44#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
47#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
48#[serde(rename_all = "lowercase")]
49pub enum OrgRole {
50 Member,
51 Admin,
52 #[default]
53 Owner,
54}
55
56impl OrgRole {
57 pub fn has_permission(self, required: OrgRole) -> bool {
59 self.level() >= required.level()
60 }
61
62 pub fn as_str(self) -> &'static str {
64 match self {
65 OrgRole::Member => "member",
66 OrgRole::Admin => "admin",
67 OrgRole::Owner => "owner",
68 }
69 }
70
71 fn level(self) -> u8 {
72 match self {
73 OrgRole::Member => 0,
74 OrgRole::Admin => 1,
75 OrgRole::Owner => 2,
76 }
77 }
78}
79
80impl fmt::Display for OrgRole {
81 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82 f.write_str(self.as_str())
83 }
84}
85
86impl FromStr for OrgRole {
87 type Err = String;
88
89 fn from_str(s: &str) -> Result<Self, Self::Err> {
90 match s {
91 "member" => Ok(OrgRole::Member),
92 "admin" => Ok(OrgRole::Admin),
93 "owner" => Ok(OrgRole::Owner),
94 _ => Err(format!("invalid org role: {s}")),
95 }
96 }
97}
98
99#[derive(Debug, Clone, Serialize, Deserialize)]
101#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
102pub struct OrgMembership {
103 #[serde(skip_serializing)]
105 pub org_id: i64,
106 pub public_id: String,
108 pub name: String,
110 #[serde(default)]
112 pub role: OrgRole,
113}
114
115pub fn org_public_id_from_internal(org_id: i64) -> String {
121 if org_id == DEFAULT_ORG_ID {
122 return DEFAULT_ORG_PUBLIC_ID.to_string();
123 }
124 format!("org_{:032x}", org_id)
125}
126
127pub fn generate_org_public_id() -> String {
130 let uuid = Uuid::new_v4();
131 format!("org_{}", uuid.simple())
132}
133
134pub fn validate_org_public_id(public_id: &str) -> bool {
137 if !public_id.starts_with("org_") {
138 return false;
139 }
140 let suffix = &public_id[4..];
141 suffix.len() == 32
142 && suffix
143 .chars()
144 .all(|c| c.is_ascii_hexdigit() && !c.is_ascii_uppercase())
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150
151 #[test]
152 fn test_generate_org_public_id() {
153 let id = generate_org_public_id();
154 assert!(id.starts_with("org_"));
155 assert_eq!(id.len(), 36); assert!(validate_org_public_id(&id));
157 }
158
159 #[test]
160 fn test_validate_org_public_id() {
161 assert!(validate_org_public_id(
163 "org_00000000000000000000000000000001"
164 ));
165 assert!(validate_org_public_id(
166 "org_2f3c1b3e6a9d4c6f8a1d4e9c9b7f21a0"
167 ));
168
169 assert!(!validate_org_public_id(
171 "organization_12345678901234567890123456789012"
172 ));
173
174 assert!(!validate_org_public_id("org_123"));
176
177 assert!(!validate_org_public_id(
179 "org_123456789012345678901234567890123"
180 ));
181
182 assert!(!validate_org_public_id(
184 "org_2F3C1B3E6A9D4C6F8A1D4E9C9B7F21A0"
185 ));
186
187 assert!(!validate_org_public_id(
189 "org_ghijklmnopqrstuvwxyz1234567890"
190 ));
191 }
192
193 #[test]
194 fn test_default_org_public_id_valid() {
195 assert!(validate_org_public_id(DEFAULT_ORG_PUBLIC_ID));
196 }
197
198 #[test]
199 fn test_org_role_hierarchy() {
200 assert!(OrgRole::Owner.has_permission(OrgRole::Owner));
201 assert!(OrgRole::Owner.has_permission(OrgRole::Admin));
202 assert!(OrgRole::Owner.has_permission(OrgRole::Member));
203
204 assert!(!OrgRole::Admin.has_permission(OrgRole::Owner));
205 assert!(OrgRole::Admin.has_permission(OrgRole::Admin));
206 assert!(OrgRole::Admin.has_permission(OrgRole::Member));
207
208 assert!(!OrgRole::Member.has_permission(OrgRole::Owner));
209 assert!(!OrgRole::Member.has_permission(OrgRole::Admin));
210 assert!(OrgRole::Member.has_permission(OrgRole::Member));
211 }
212
213 #[test]
214 fn test_org_role_str_roundtrip() {
215 for role in [OrgRole::Member, OrgRole::Admin, OrgRole::Owner] {
216 let s = role.as_str();
217 let parsed: OrgRole = s.parse().unwrap();
218 assert_eq!(parsed, role);
219 }
220 }
221
222 #[test]
223 fn test_org_role_default() {
224 assert_eq!(OrgRole::default(), OrgRole::Owner);
225 }
226}