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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
/*
 * ForgeFlux StarChart - A federated software forge spider
 * Copyright (C) 2022  Aravinth Manivannan <realaravinth@batsense.net>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
#![warn(missing_docs)]
//! # `Starchart` database operations
//!
//! Traits and datastructures used in Starchart to interact with database.
//!
//! To use an unsupported database with Starchart, traits present within this crate should be
//! implemented.
//!
//!
//! ## Organisation
//!
//! Database functionallity is divided accross various modules:
//!
//! - [errors](crate::auth): error data structures used in this crate
//! - [ops](crate::ops): meta operations like connection pool creation, migrations and getting
//! connection from pool
use std::str::FromStr;

use serde::{Deserialize, Serialize};
use url::Url;

pub mod errors;
pub mod ops;
#[cfg(feature = "test")]
pub mod tests;

use dev::*;
pub use ops::GetConnection;

pub mod prelude {
    //! useful imports for users working with a supported database

    pub use super::errors::*;
    pub use super::ops::*;
    pub use super::*;
}

pub mod dev {
    //! useful imports for supporting a new database
    pub use super::prelude::*;
    pub use async_trait::async_trait;
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
/// Data related to a Starchart instance
pub struct Starchart {
    /// URL of the Starchart instance
    pub instance_url: String,
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
/// create a new forge on the database
pub struct CreateForge<'a> {
    /// url of the Starchart instance
    /// None = local instance
    /// Some(&'a str) = foreign instance
    pub starchart_url: Option<&'a str>,
    /// url of the forge instance: with scheme but remove trailing slash
    pub url: Url,
    /// forge type: which software is the instance running?
    pub forge_type: ForgeImplementation,
}

/// Get url from URL
/// Utility function for uniform url format
pub fn clean_url(url: &Url) -> String {
    let mut url = url.clone();
    url.set_path("");
    url.set_query(None);
    url.set_fragment(None);
    url.as_str().to_string()
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
/// user data
pub struct User {
    /// url of the forge instance: with scheme but remove trailing slash
    /// url can be derived  from html_link also, but used to link to user's forge instance
    pub url: String,
    /// username of the user
    pub username: String,
    /// html link to the user profile
    pub html_link: String,
    /// OPTIONAL: html link to the user's profile photo
    pub profile_photo: Option<String>,
    /// is this user an import
    pub import: bool,
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
/// add new user to database
pub struct AddUser<'a> {
    /// url of the forge instance: with scheme but remove trailing slash
    /// url can be derived  from html_link also, but used to link to user's forge instance
    pub url: Url,
    /// username of the user
    pub username: &'a str,
    /// html link to the user profile
    pub html_link: &'a str,
    /// OPTIONAL: html link to the user's profile photo
    pub profile_photo: Option<&'a str>,
    /// is this user an import
    pub import: bool,
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
/// add new repository to database
pub struct AddRepository<'a> {
    /// html link to the repository
    pub html_link: &'a str,
    /// repository topic tags
    pub tags: Option<Vec<&'a str>>,
    /// url of the forge instance: with scheme but remove trailing slash
    /// url can be derived  from html_link also, but used to link to user's forge instance
    pub url: Url,
    /// repository name
    pub name: &'a str,
    /// repository owner
    pub owner: &'a str,
    /// repository description, if any
    pub description: Option<&'a str>,
    /// repository website, if any
    pub website: Option<&'a str>,
    /// is this repository an import
    pub import: bool,
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
/// data representing a forge instance
pub struct Forge {
    /// url of the Starchart instance
    /// None = local instance
    /// Some(&'a str) = foreign instance
    pub starchart_url: Option<String>,
    /// url of the forge
    pub url: String,
    /// type of the forge
    pub forge_type: ForgeImplementation,
    /// last crawl
    pub last_crawl_on: Option<i64>,
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
/// repository
pub struct Repository {
    /// html link to the repository
    pub html_url: String,
    /// repository topic tags
    pub tags: Option<Vec<String>>,
    /// url of the forge instance: with scheme but remove trailing slash
    /// url can be derived  from html_link also, but used to link to user's forge instance
    pub url: String,
    /// repository name
    pub name: String,
    /// repository owner
    pub username: String,
    /// repository description, if any
    pub description: Option<String>,
    /// repository website, if any
    pub website: Option<String>,
    /// is this repository an import
    pub import: bool,
}

#[async_trait]
/// Starchart's database requirements. To implement support for $Database, kindly implement this
/// trait.
pub trait SCDatabase: std::marker::Send + std::marker::Sync + CloneSPDatabase {
    /// ping DB
    async fn ping(&self) -> bool;

    /// create forge instance
    async fn create_forge_instance(&self, f: &CreateForge) -> DBResult<()>;

    /// get forge instance data
    async fn get_forge(&self, url: &Url) -> DBResult<Forge>;

    /// delete forge instance
    async fn delete_forge_instance(&self, url: &Url) -> DBResult<()>;

    /// check if a forge instance exists
    async fn forge_exists(&self, url: &Url) -> DBResult<bool>;

    /// check if forge type exists
    async fn forge_type_exists(&self, forge_type: &ForgeImplementation) -> DBResult<bool>;

    /// Get all forges
    async fn get_all_forges(
        &self,
        with_imports: bool,
        offset: u32,
        limit: u32,
    ) -> DBResult<Vec<Forge>>;

    /// add new user to database
    async fn add_user(&self, u: &AddUser) -> DBResult<()>;

    /// get user data
    async fn get_user(&self, username: &str, url: &Url) -> DBResult<User>;

    /// check if an user exists. When url of a forge instance is provided, username search is
    /// done only on that forge
    async fn user_exists(&self, username: &str, url: Option<&Url>) -> DBResult<bool>;

    /// delete user
    async fn delete_user(&self, username: &str, url: &Url) -> DBResult<()>;

    /// delete repository
    async fn delete_repository(&self, owner: &str, name: &str, url: &Url) -> DBResult<()>;

    /// check if a repository exists.
    async fn repository_exists(&self, name: &str, owner: &str, url: &Url) -> DBResult<bool>;

    /// Get all repositories
    async fn get_all_repositories(&self, offset: u32, limit: u32) -> DBResult<Vec<Repository>>;

    /// add new repository to database.
    async fn create_repository(&self, r: &AddRepository) -> DBResult<()>;

    /// Search all repositories
    async fn search_repository(&self, query: &str) -> DBResult<Vec<Repository>>;

    /// Add Starchart instance to introducer
    async fn add_starchart_to_introducer(&self, url: &Url) -> DBResult<()>;

    /// Get all introduced Starchart instances
    async fn get_all_introduced_starchart_instances(
        &self,
        offset: u32,
        limit: u32,
    ) -> DBResult<Vec<Starchart>>;

    /// Add word to mini index
    async fn add_word_to_mini_index(&self, word: &str) -> DBResult<()>;

    /// Remove word from mini index
    async fn rm_word_from_mini_index(&self, word: &str) -> DBResult<()>;

    /// Check if word exists in mini index
    async fn is_word_mini_indexed(&self, word: &str) -> DBResult<bool>;

    /// consolidate and export mini index
    async fn export_mini_index(&self) -> DBResult<String>;

    /// Import mini-index
    async fn import_mini_index(
        &self,
        starchart_instance_url: &Url,
        mini_index: &str,
    ) -> DBResult<()>;

    /// Delete imported mini-index
    async fn rm_imported_mini_index(&self, starchart_instance_url: &Url) -> DBResult<()>;

    /// Search mini index
    async fn search_mini_index(&self, query: &str) -> DBResult<Vec<String>>;

    /// Mark a Starchart instance as imported
    async fn record_starchart_imports(&self, starchart_url: &Url) -> DBResult<()>;

    /// Unmark a Starchart instance as imported
    async fn rm_starchart_import(&self, starchart_url: &Url) -> DBResult<()>;

    /// Check if Starchart instance is imported
    async fn is_starchart_imported(&self, starchart_url: &Url) -> DBResult<bool>;
}

/// Trait to clone SCDatabase
pub trait CloneSPDatabase {
    /// clone DB
    fn clone_db(&self) -> Box<dyn SCDatabase>;
}

impl<T> CloneSPDatabase for T
where
    T: SCDatabase + Clone + 'static,
{
    fn clone_db(&self) -> Box<dyn SCDatabase> {
        Box::new(self.clone())
    }
}

impl Clone for Box<dyn SCDatabase> {
    fn clone(&self) -> Self {
        (**self).clone_db()
    }
}

/// Forge type: Gitea, Sourcehut, GitLab, etc. Support is currently only available for Gitea
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ForgeImplementation {
    /// [Gitea](https://gitea.io) softare forge
    Gitea,
}

impl ForgeImplementation {
    /// Convert [ForgeImplementation] to [str]
    pub const fn to_str(&self) -> &'static str {
        match self {
            ForgeImplementation::Gitea => "gitea",
        }
    }
}

impl FromStr for ForgeImplementation {
    type Err = DBError;

    /// Convert [str] to [ForgeImplementation]
    fn from_str(s: &str) -> DBResult<Self> {
        const GITEA: &str = ForgeImplementation::Gitea.to_str();
        let s = s.trim();
        match s {
            GITEA => Ok(Self::Gitea),
            _ => Err(DBError::UnknownForgeType(s.to_owned())),
        }
    }
}