ng_repo/
repo.rs

1// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
2// All rights reserved.
3// Licensed under the Apache License, Version 2.0
4// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
5// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
6// at your option. All files in the project carrying such
7// notice may not be copied, modified, or distributed except
8// according to those terms.
9
10//! Repository
11
12use core::fmt;
13use std::collections::HashMap;
14use std::collections::HashSet;
15use std::sync::Arc;
16
17use crate::errors::*;
18#[allow(unused_imports)]
19use crate::log::*;
20use crate::store::Store;
21use crate::types::*;
22
23impl RepositoryV0 {
24    pub fn new_with_meta(id: &PubKey, metadata: &Vec<u8>) -> RepositoryV0 {
25        RepositoryV0 {
26            id: id.clone(),
27            metadata: metadata.clone(),
28            verification_program: vec![],
29            fork_of: vec![],
30            creator: None,
31        }
32    }
33}
34
35impl Repository {
36    pub fn new(id: &RepoId) -> Self {
37        Repository::V0(RepositoryV0 {
38            id: id.clone(),
39            verification_program: vec![],
40            creator: None,
41            fork_of: vec![],
42            metadata: vec![],
43        })
44    }
45    pub fn new_with_meta(id: &PubKey, metadata: &Vec<u8>) -> Repository {
46        Repository::V0(RepositoryV0::new_with_meta(id, metadata))
47    }
48    pub fn id(&self) -> &PubKey {
49        match self {
50            Self::V0(v0) => &v0.id,
51        }
52    }
53}
54
55#[derive(Debug)]
56pub struct UserInfo {
57    /// list of permissions granted to user, with optional metadata
58    pub permissions: HashMap<PermissionV0, Vec<u8>>,
59    pub id: UserId,
60}
61
62impl UserInfo {
63    pub fn has_any_perm(&self, perms: &HashSet<PermissionV0>) -> Result<(), NgError> {
64        //log_debug!("perms {:?}", perms);
65        if self.has_perm(&PermissionV0::Owner).is_ok() {
66            return Ok(());
67        }
68        let is_admin = self.has_perm(&PermissionV0::Admin).is_ok();
69        //log_debug!("is_admin {}", is_admin);
70        //is_delegated_by_admin
71        let has_perms: HashSet<&PermissionV0> = self.permissions.keys().collect();
72        //log_debug!("has_perms {:?}", has_perms);
73        for perm in perms {
74            if is_admin && perm.is_delegated_by_admin() || has_perms.contains(perm) {
75                return Ok(());
76            }
77        }
78        // if has_perms.intersection(perms).count() > 0 {
79        //     Ok(())
80        // } else {
81        Err(NgError::PermissionDenied)
82    }
83    pub fn has_perm(&self, perm: &PermissionV0) -> Result<&Vec<u8>, NgError> {
84        self.permissions.get(perm).ok_or(NgError::PermissionDenied)
85    }
86}
87
88#[derive(Debug)]
89pub struct BranchInfo {
90    pub id: BranchId,
91
92    pub branch_type: BranchType,
93
94    pub topic: TopicId,
95
96    pub topic_priv_key: Option<BranchWriteCapSecret>,
97
98    pub read_cap: ReadCap,
99
100    pub current_heads: Vec<ObjectRef>,
101
102    pub commits_nbr: u64,
103}
104
105/// In memory Repository representation. With helper functions that access the underlying UserStorage and keeps proxy of the values
106#[derive(Debug)]
107pub struct Repo {
108    pub id: RepoId,
109    /// Repo definition
110    pub repo_def: Repository,
111
112    pub read_cap: Option<ReadCap>,
113
114    pub write_cap: Option<RepoWriteCapSecret>,
115
116    pub signer: Option<SignerCap>,
117
118    pub members: HashMap<Digest, UserInfo>,
119
120    pub branches: HashMap<BranchId, BranchInfo>,
121
122    /// if opened_branches is empty, it means the repo has not been opened yet.
123    /// if a branchId is present in the hashmap, it means it is opened.
124    /// the boolean indicates if the branch is opened as publisher or not
125    pub opened_branches: HashMap<BranchId, bool>,
126
127    /*pub main_branch_rc: Option<BranchId>,
128
129    pub chat_branch_rc: Option<BranchId>,
130
131    // only used if it is a StoreRepo
132    pub store_branch_rc: Option<BranchId>,
133    pub overlay_branch_rc: Option<BranchId>,
134
135    // only used if it is a private StoreRepo
136    pub user_branch_rc: Option<BranchId>,*/
137    pub store: Arc<Store>,
138}
139
140impl fmt::Display for Repo {
141    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142        writeln!(f, "====== Repo ====== {}", self.id)?;
143
144        write!(f, "== repo_def:    {}", self.repo_def)?;
145
146        if self.signer.is_some() {
147            writeln!(f, "== signer:   {:?}", self.signer)?;
148        }
149
150        writeln!(f, "== members:   {:?}", self.members)?;
151
152        Ok(())
153    }
154}
155
156impl Repo {
157    #[cfg(any(test, feature = "testing"))]
158    #[allow(deprecated)]
159    pub fn new_with_perms(perms: &[PermissionV0], store: Arc<Store>) -> Self {
160        let pub_key = PubKey::nil();
161        Self::new_with_member(&pub_key, &pub_key, perms, store)
162    }
163
164    pub fn update_branch_current_heads(
165        &mut self,
166        branch: &BranchId,
167        commit_ref: ObjectRef,
168        past: Vec<ObjectRef>,
169    ) -> Result<Vec<ObjectRef>, VerifierError> {
170        //log_info!("from branch {} HEAD UPDATED TO {}", branch, commit_ref.id);
171        if let Some(branch) = self.branches.get_mut(branch) {
172            let mut set: HashSet<&ObjectRef> = HashSet::from_iter(branch.current_heads.iter());
173            for p in past {
174                set.remove(&p);
175            }
176            branch.current_heads = set.into_iter().cloned().collect();
177            branch.current_heads.push(commit_ref);
178            branch.commits_nbr += 1;
179            // we return the new current heads
180            Ok(branch.current_heads.to_vec())
181        } else {
182            Err(VerifierError::BranchNotFound)
183        }
184    }
185
186    pub fn new_with_member(
187        repo_id: &PubKey,
188        member: &UserId,
189        perms: &[PermissionV0],
190        store: Arc<Store>,
191    ) -> Self {
192        let mut members = HashMap::new();
193        let permissions = HashMap::from_iter(
194            perms
195                .iter()
196                .map(|p| (*p, vec![]))
197                .collect::<Vec<(PermissionV0, Vec<u8>)>>()
198                .iter()
199                .cloned(),
200        );
201        let overlay = store.get_store_repo().overlay_id_for_read_purpose();
202        let member_hash = CommitContent::author_digest(member, overlay);
203        //log_debug!("added member {:?} {:?}", member, member_hash);
204        members.insert(
205            member_hash,
206            UserInfo {
207                id: *member,
208                permissions,
209            },
210        );
211        Self {
212            id: repo_id.clone(),
213            repo_def: Repository::new(&repo_id),
214            members,
215            store,
216            signer: None,
217            read_cap: None,
218            write_cap: None,
219            branches: HashMap::new(),
220            opened_branches: HashMap::new(),
221            //main_branch_rc: None,
222        }
223    }
224
225    pub fn verify_permission(&self, commit: &Commit) -> Result<(), NgError> {
226        let content_author = commit.content_v0().author;
227        let body = commit.load_body(&self.store)?;
228        match self.members.get(&content_author) {
229            Some(info) => return info.has_any_perm(&body.required_permission()),
230            None => {}
231        }
232        Err(NgError::PermissionDenied)
233    }
234
235    pub fn member_pubkey(&self, hash: &Digest) -> Result<UserId, NgError> {
236        match self.members.get(hash) {
237            Some(user_info) => Ok(user_info.id),
238            None => Err(NgError::NotFound),
239        }
240    }
241
242    pub fn branch(&self, id: &BranchId) -> Result<&BranchInfo, NgError> {
243        //TODO: load the BranchInfo from storage
244        self.branches.get(id).ok_or(NgError::BranchNotFound)
245    }
246
247    pub fn branch_mut(&mut self, id: &BranchId) -> Result<&mut BranchInfo, NgError> {
248        //TODO: load the BranchInfo from storage
249        self.branches.get_mut(id).ok_or(NgError::BranchNotFound)
250    }
251
252    pub fn overlay_branch(&self) -> Option<&BranchInfo> {
253        for (_, branch) in self.branches.iter() {
254            if branch.branch_type == BranchType::Overlay {
255                return Some(branch);
256            }
257        }
258        None
259    }
260
261    pub fn user_branch(&self) -> Option<&BranchInfo> {
262        for (_, branch) in self.branches.iter() {
263            if branch.branch_type == BranchType::User {
264                return Some(branch);
265            }
266        }
267        None
268    }
269
270    pub fn main_branch(&self) -> Option<&BranchInfo> {
271        for (_, branch) in self.branches.iter() {
272            if branch.branch_type == BranchType::Main {
273                return Some(branch);
274            }
275        }
276        None
277    }
278
279    pub fn root_branch(&self) -> Option<&BranchInfo> {
280        for (_, branch) in self.branches.iter() {
281            if branch.branch_type == BranchType::Root {
282                return Some(branch);
283            }
284        }
285        None
286    }
287
288    pub fn overlay_branch_read_cap(&self) -> Option<&ReadCap> {
289        match self.overlay_branch() {
290            Some(bi) => Some(&bi.read_cap),
291            None => self.read_cap.as_ref(), // this is for private stores that don't have an overlay branch
292        }
293    }
294
295    pub fn branch_is_opened(&self, branch: &BranchId) -> bool {
296        self.opened_branches.contains_key(branch)
297    }
298
299    pub fn branch_is_opened_as_publisher(&self, branch: &BranchId) -> bool {
300        match self.opened_branches.get(branch) {
301            Some(val) => *val,
302            None => false,
303        }
304    }
305
306    // pub(crate) fn get_store(&self) -> &Store {
307    //     self.store.unwrap()
308    // }
309}