1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
use bonsaidb::{
    core::{
        admin::{AuthenticationToken, PermissionGroup, Role},
        connection::{AsyncConnection, IdentityReference},
        document::{CollectionDocument, Emit},
        permissions::Statement,
        schema::{Collection, NamedCollection, Schema, SerializedCollection},
    },
    files::{BonsaiFiles, FileConfig, FilesSchema},
};
use serde::{Deserialize, Serialize};

use crate::permissions::{project_resource_name, DossierAction};

#[derive(Schema, Debug)]
#[schema(name = "dossier", collections = [Project, ApiToken], include = [FilesSchema<DossierFiles>])]
pub struct Dossier;

#[derive(Debug)]
pub enum DossierFiles {}

impl FileConfig for DossierFiles {
    type Metadata = Option<Metadata>;
    const BLOCK_SIZE: usize = BonsaiFiles::BLOCK_SIZE;

    fn files_name() -> bonsaidb::core::schema::CollectionName {
        BonsaiFiles::files_name()
    }

    fn blocks_name() -> bonsaidb::core::schema::CollectionName {
        BonsaiFiles::blocks_name()
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, Copy)]
pub struct Metadata {
    pub blake3: [u8; 32],
}

#[derive(Collection, Debug, Clone, Serialize, Deserialize)]
#[collection(name = "projects", primary_key = u32, views = [ProjectBySlug])]
pub struct Project {
    pub slug: String,
}

bonsaidb::core::define_basic_unique_mapped_view!(
    ProjectBySlug,
    Project,
    1,
    "by-slug",
    String,
    |project: CollectionDocument<Project>| project.header.emit_key(project.contents.slug)
);

impl NamedCollection for Project {
    type ByNameView = ProjectBySlug;
}

#[derive(Collection, Debug, Clone, Serialize, Deserialize)]
#[collection(name = "api-tokens", primary_key = u64, natural_id = |token: &ApiToken| Some(token.authentication_token_id))]
pub struct ApiToken {
    pub label: String,
    pub authentication_token_id: u64,
    pub project_id: u32,
}

impl ApiToken {
    pub async fn create<C: AsyncConnection>(
        label: String,
        project_id: u32,
        connection: &C,
        admin: &C,
    ) -> anyhow::Result<(
        CollectionDocument<Self>,
        CollectionDocument<AuthenticationToken>,
    )> {
        let group = PermissionGroup {
            name: label.clone(),
            statements: vec![Statement::for_resource(project_resource_name(project_id))
                .allowing(&DossierAction::SyncFiles)],
        }
        .push_into_async(admin)
        .await?;
        let role = Role {
            name: label.clone(),
            groups: vec![group.header.id],
        }
        .push_into_async(admin)
        .await?;
        let authentication_token =
            AuthenticationToken::create_async(IdentityReference::role(role.header.id)?, admin)
                .await?;
        let api_token = ApiToken {
            project_id,
            label,
            authentication_token_id: authentication_token.header.id,
        }
        .push_into_async(connection)
        .await?;
        Ok((api_token, authentication_token))
    }

    pub async fn delete<C: AsyncConnection>(
        api_token: &CollectionDocument<Self>,
        connection: &C,
        admin: &C,
    ) -> anyhow::Result<()> {
        if let Some(auth_token) =
            AuthenticationToken::get_async(&api_token.header.id, admin).await?
        {
            auth_token.delete_async(admin).await?;
            println!("Authentication Token {} deleted", auth_token.header.id);
        }
        if let Some(role) = Role::load_async(&api_token.contents.label, admin).await? {
            role.delete_async(admin).await?;
            println!("Role {} deleted", role.header.id);
        }
        if let Some(group) = PermissionGroup::load_async(&api_token.contents.label, admin).await? {
            group.delete_async(admin).await?;
            println!("Permission Group {} deleted", group.header.id);
        }

        api_token.delete_async(connection).await?;
        println!("Api Token {} deleted", api_token.contents.label);
        Ok(())
    }
}