1use serde::Serialize;
4use sova_core::Error;
5
6pub const ROLE_OWNER: &str = "owner";
7pub const ROLE_ADMIN: &str = "admin";
8pub const ROLE_EDITOR: &str = "editor";
9pub const ROLE_VIEWER: &str = "viewer";
10
11pub const INVITE_PENDING: &str = "pending";
12pub const INVITE_ACCEPTED: &str = "accepted";
13pub const INVITE_REVOKED: &str = "revoked";
14
15#[derive(Clone, Debug, PartialEq, Eq, Hash)]
16pub struct ScopeRef {
17 pub kind: String,
18 pub id: i64,
19}
20
21impl ScopeRef {
22 pub fn new(kind: impl Into<String>, id: i64) -> Self {
23 Self {
24 kind: kind.into(),
25 id,
26 }
27 }
28}
29
30#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
31pub struct ResourceRef {
32 pub kind: &'static str,
33 pub id: i64,
34}
35
36impl ResourceRef {
37 pub fn new(kind: &'static str, id: i64) -> Self {
38 Self { kind, id }
39 }
40}
41
42#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)]
43#[serde(rename_all = "snake_case")]
44pub enum Ability {
45 View,
46 Create,
47 Update,
48 Delete,
49 Manage,
50}
51
52#[derive(Clone, Debug, Serialize)]
53pub struct Membership {
54 pub id: i64,
55 pub scope_kind: String,
56 pub scope_id: i64,
57 pub user_id: i64,
58 pub role: String,
59}
60
61#[derive(Clone, Copy, Debug, Default, Serialize)]
62pub struct Capabilities {
63 pub view: bool,
64 pub create: bool,
65 pub update: bool,
66 pub delete: bool,
67 pub manage: bool,
68}
69
70impl Capabilities {
71 pub fn full() -> Self {
72 Self {
73 view: true,
74 create: true,
75 update: true,
76 delete: true,
77 manage: true,
78 }
79 }
80
81 pub fn read_only() -> Self {
82 Self {
83 view: true,
84 ..Default::default()
85 }
86 }
87
88 pub fn has(&self, ability: Ability) -> bool {
89 match ability {
90 Ability::View => self.view,
91 Ability::Create => self.create,
92 Ability::Update => self.update,
93 Ability::Delete => self.delete,
94 Ability::Manage => self.manage,
95 }
96 }
97}
98
99pub fn normalize_role(role: &str) -> Option<&'static str> {
100 match role.trim().to_lowercase().as_str() {
101 "owner" => Some(ROLE_OWNER),
102 "admin" => Some(ROLE_ADMIN),
103 "editor" => Some(ROLE_EDITOR),
104 "viewer" | "read" => Some(ROLE_VIEWER),
105 _ => None,
106 }
107}
108
109pub fn is_privileged_role(role: &str) -> bool {
110 role == ROLE_OWNER || role == ROLE_ADMIN
111}
112
113pub fn ability_error(ability: Ability) -> Error {
114 match ability {
115 Ability::View => Error::NotFound,
116 _ => Error::custom(403, "forbidden"),
117 }
118}