Fixed imports!
parent: tbd commit: ef0e853
Showing 54 changed files with 1755 insertions and 1663 deletions
giterated-daemon/src/authentication.rs
@@ -1,9 +1,10 @@ | ||
1 | 1 | use anyhow::Error; |
2 | use giterated_models::model::{ | |
3 | authenticated::{UserAuthenticationToken, UserTokenMetadata}, | |
4 | instance::Instance, | |
5 | user::User, | |
6 | }; | |
2 | use giterated_models::authenticated::{UserAuthenticationToken, UserTokenMetadata}; | |
3 | ||
4 | use giterated_models::instance::Instance; | |
5 | ||
6 | use giterated_models::user::User; | |
7 | ||
7 | 8 | use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, TokenData, Validation}; |
8 | 9 | use std::{sync::Arc, time::SystemTime}; |
9 | 10 | use tokio::{fs::File, io::AsyncReadExt, sync::Mutex}; |
giterated-daemon/src/authorization.rs
@@ -0,0 +1,134 @@ | ||
1 | use crate::connection::wrapper::ConnectionState; | |
2 | use giterated_models::error::OperationError; | |
3 | ||
4 | use giterated_models::object::GiteratedObject; | |
5 | ||
6 | use giterated_models::repository::{ | |
7 | Repository, RepositoryFileInspectRequest, RepositoryIssueLabelsRequest, | |
8 | RepositoryIssuesCountRequest, RepositoryIssuesRequest, | |
9 | }; | |
10 | ||
11 | use giterated_models::user::User; | |
12 | ||
13 | use giterated_models::{ | |
14 | object::ObjectRequest, | |
15 | settings::{SetSetting, Setting}, | |
16 | value::{GetValue, GiteratedObjectValue}, | |
17 | }; | |
18 | #[async_trait::async_trait] | |
19 | pub trait AuthorizedOperation<O: GiteratedObject, S: Send + Sync> { | |
20 | /// Authorizes the operation, returning whether the operation was | |
21 | /// authorized or not. | |
22 | async fn authorize( | |
23 | &self, | |
24 | authenticating_user: Option<&User>, | |
25 | object: &O, | |
26 | state: &mut S, | |
27 | ) -> Result<bool, OperationError<()>>; | |
28 | } | |
29 | ||
30 | #[async_trait::async_trait] | |
31 | impl<S: Setting + Send + Sync> AuthorizedOperation<User, ConnectionState> for SetSetting<S> { | |
32 | async fn authorize( | |
33 | &self, | |
34 | _authenticating_user: Option<&User>, | |
35 | _object: &User, | |
36 | _state: &mut ConnectionState, | |
37 | ) -> Result<bool, OperationError<()>> { | |
38 | // TODO | |
39 | Ok(true) | |
40 | } | |
41 | } | |
42 | ||
43 | #[async_trait::async_trait] | |
44 | impl<S: Setting + Send + Sync> AuthorizedOperation<Repository, ConnectionState> for SetSetting<S> { | |
45 | async fn authorize( | |
46 | &self, | |
47 | _authenticating_user: Option<&User>, | |
48 | _object: &Repository, | |
49 | _state: &mut ConnectionState, | |
50 | ) -> Result<bool, OperationError<()>> { | |
51 | // TODO | |
52 | Ok(true) | |
53 | } | |
54 | } | |
55 | ||
56 | #[async_trait::async_trait] | |
57 | impl<V: GiteratedObjectValue + Send + Sync> AuthorizedOperation<Repository, ConnectionState> | |
58 | for GetValue<V> | |
59 | { | |
60 | async fn authorize( | |
61 | &self, | |
62 | _authenticating_user: Option<&User>, | |
63 | _object: &Repository, | |
64 | _state: &mut ConnectionState, | |
65 | ) -> Result<bool, OperationError<()>> { | |
66 | // TODO | |
67 | Ok(true) | |
68 | } | |
69 | } | |
70 | ||
71 | #[async_trait::async_trait] | |
72 | impl AuthorizedOperation<Repository, ConnectionState> for ObjectRequest { | |
73 | async fn authorize( | |
74 | &self, | |
75 | _authenticating_user: Option<&User>, | |
76 | _object: &Repository, | |
77 | _state: &mut ConnectionState, | |
78 | ) -> Result<bool, OperationError<()>> { | |
79 | // TODO | |
80 | Ok(true) | |
81 | } | |
82 | } | |
83 | ||
84 | #[async_trait::async_trait] | |
85 | impl AuthorizedOperation<Repository, ConnectionState> for RepositoryFileInspectRequest { | |
86 | async fn authorize( | |
87 | &self, | |
88 | _authenticating_user: Option<&User>, | |
89 | _object: &Repository, | |
90 | _state: &mut ConnectionState, | |
91 | ) -> Result<bool, OperationError<()>> { | |
92 | // TODO | |
93 | Ok(true) | |
94 | } | |
95 | } | |
96 | ||
97 | #[async_trait::async_trait] | |
98 | impl AuthorizedOperation<Repository, ConnectionState> for RepositoryIssuesRequest { | |
99 | async fn authorize( | |
100 | &self, | |
101 | _authenticating_user: Option<&User>, | |
102 | _object: &Repository, | |
103 | _state: &mut ConnectionState, | |
104 | ) -> Result<bool, OperationError<()>> { | |
105 | // TODO | |
106 | Ok(true) | |
107 | } | |
108 | } | |
109 | ||
110 | #[async_trait::async_trait] | |
111 | impl AuthorizedOperation<Repository, ConnectionState> for RepositoryIssueLabelsRequest { | |
112 | async fn authorize( | |
113 | &self, | |
114 | _authenticating_user: Option<&User>, | |
115 | _object: &Repository, | |
116 | _state: &mut ConnectionState, | |
117 | ) -> Result<bool, OperationError<()>> { | |
118 | // TODO | |
119 | Ok(true) | |
120 | } | |
121 | } | |
122 | ||
123 | #[async_trait::async_trait] | |
124 | impl AuthorizedOperation<Repository, ConnectionState> for RepositoryIssuesCountRequest { | |
125 | async fn authorize( | |
126 | &self, | |
127 | _authenticating_user: Option<&User>, | |
128 | _object: &Repository, | |
129 | _state: &mut ConnectionState, | |
130 | ) -> Result<bool, OperationError<()>> { | |
131 | // TODO | |
132 | Ok(true) | |
133 | } | |
134 | } |
giterated-daemon/src/backend/git.rs
@@ -2,25 +2,17 @@ use anyhow::Error; | ||
2 | 2 | use async_trait::async_trait; |
3 | 3 | use futures_util::StreamExt; |
4 | 4 | |
5 | use giterated_models::{ | |
6 | model::{ | |
7 | instance::Instance, | |
8 | repository::{ | |
9 | Commit, IssueLabel, Repository, RepositoryIssue, RepositorySummary, | |
10 | RepositoryTreeEntry, RepositoryVisibility, | |
11 | }, | |
12 | settings::{AnySetting, RepositoryDescription, RepositoryVisibilitySetting, Setting}, | |
13 | user::{User, UserParseError}, | |
14 | }, | |
15 | operation::{ | |
16 | instance::RepositoryCreateRequest, | |
17 | repository::{ | |
18 | RepositoryFileInspectRequest, RepositoryIssueLabelsRequest, | |
19 | RepositoryIssuesCountRequest, RepositoryIssuesRequest, | |
20 | }, | |
21 | }, | |
22 | values::AnyValue, | |
5 | use giterated_models::instance::{Instance, RepositoryCreateRequest}; | |
6 | ||
7 | use giterated_models::repository::{ | |
8 | Commit, IssueLabel, Repository, RepositoryDescription, RepositoryFileInspectRequest, | |
9 | RepositoryIssue, RepositoryIssueLabelsRequest, RepositoryIssuesCountRequest, | |
10 | RepositoryIssuesRequest, RepositorySummary, RepositoryTreeEntry, RepositoryVisibility, | |
11 | RepositoryVisibilitySetting, | |
23 | 12 | }; |
13 | use giterated_models::settings::{AnySetting, Setting}; | |
14 | use giterated_models::user::{User, UserParseError}; | |
15 | use giterated_models::value::AnyValue; | |
24 | 16 | use serde_json::Value; |
25 | 17 | use sqlx::{Either, PgPool}; |
26 | 18 | use std::{ |
giterated-daemon/src/backend/mod.rs
@@ -9,25 +9,20 @@ use async_trait::async_trait; | ||
9 | 9 | use serde_json::Value; |
10 | 10 | |
11 | 11 | use crate::backend::git::GitBackendError; |
12 | use giterated_models::{ | |
13 | model::{ | |
14 | authenticated::UserAuthenticationToken, | |
15 | instance::Instance, | |
16 | repository::{ | |
17 | IssueLabel, Repository, RepositoryIssue, RepositorySummary, RepositoryTreeEntry, | |
18 | }, | |
19 | settings::AnySetting, | |
20 | user::User, | |
21 | }, | |
22 | operation::{ | |
23 | instance::{AuthenticationTokenRequest, RegisterAccountRequest, RepositoryCreateRequest}, | |
24 | repository::{ | |
25 | RepositoryFileInspectRequest, RepositoryIssueLabelsRequest, | |
26 | RepositoryIssuesCountRequest, RepositoryIssuesRequest, | |
27 | }, | |
28 | }, | |
29 | values::AnyValue, | |
12 | use giterated_models::authenticated::UserAuthenticationToken; | |
13 | ||
14 | use giterated_models::instance::{ | |
15 | AuthenticationTokenRequest, Instance, RegisterAccountRequest, RepositoryCreateRequest, | |
16 | }; | |
17 | ||
18 | use giterated_models::repository::{ | |
19 | IssueLabel, Repository, RepositoryFileInspectRequest, RepositoryIssue, | |
20 | RepositoryIssueLabelsRequest, RepositoryIssuesCountRequest, RepositoryIssuesRequest, | |
21 | RepositorySummary, RepositoryTreeEntry, | |
30 | 22 | }; |
23 | use giterated_models::settings::AnySetting; | |
24 | use giterated_models::user::User; | |
25 | use giterated_models::value::AnyValue; | |
31 | 26 | |
32 | 27 | #[async_trait] |
33 | 28 | pub trait RepositoryBackend { |
giterated-daemon/src/backend/settings.rs
@@ -1,6 +1,8 @@ | ||
1 | 1 | use anyhow::Error; |
2 | 2 | |
3 | use giterated_models::model::{repository::Repository, settings::AnySetting, user::User}; | |
3 | use giterated_models::repository::Repository; | |
4 | use giterated_models::settings::AnySetting; | |
5 | use giterated_models::user::User; | |
4 | 6 | |
5 | 7 | use sqlx::PgPool; |
6 | 8 | |
@@ -12,73 +14,62 @@ pub struct DatabaseSettings { | ||
12 | 14 | |
13 | 15 | #[async_trait::async_trait] |
14 | 16 | impl MetadataBackend for DatabaseSettings { |
15 | async fn user_get(&mut self, _user: &User, _name: &str) -> Result<AnySetting, Error> { | |
16 | todo!() | |
17 | async fn user_get(&mut self, user: &User, name: &str) -> Result<AnySetting, Error> { | |
18 | let row = sqlx::query_as!( | |
19 | UserSettingRow, | |
20 | "SELECT * FROM user_settings WHERE username = $1 AND name = $2", | |
21 | user.username, | |
22 | name | |
23 | ) | |
24 | .fetch_one(&self.pg_pool) | |
25 | .await?; | |
26 | ||
27 | let setting = serde_json::from_str(&row.value)?; | |
28 | ||
29 | Ok(setting) | |
17 | 30 | } |
18 | 31 | async fn user_write( |
19 | 32 | &mut self, |
20 | _user: &User, | |
21 | _name: &str, | |
22 | _value: AnySetting, | |
33 | user: &User, | |
34 | name: &str, | |
35 | value: AnySetting, | |
23 | 36 | ) -> Result<(), Error> { |
24 | // for (name, value) in settings { | |
25 | // sqlx::query!("INSERT INTO user_settings VALUES ($1, $2, $3) ON CONFLICT (username, name) DO UPDATE SET value = $3", | |
26 | // user.username, name, value) | |
27 | // .execute(&self.pg_pool).await?; | |
28 | // } | |
37 | sqlx::query!("INSERT INTO user_settings VALUES ($1, $2, $3) ON CONFLICT (username, name) DO UPDATE SET value = $3", | |
38 | user.username, name, serde_json::to_string(&value)?) | |
39 | .execute(&self.pg_pool).await?; | |
29 | 40 | |
30 | // Ok(()) | |
31 | ||
32 | todo!() | |
41 | Ok(()) | |
33 | 42 | } |
34 | 43 | |
35 | 44 | async fn repository_get( |
36 | 45 | &mut self, |
37 | _repository: &Repository, | |
38 | _name: &str, | |
46 | repository: &Repository, | |
47 | name: &str, | |
39 | 48 | ) -> Result<AnySetting, Error> { |
40 | // let settings = sqlx::query_as!( | |
41 | // RepositorySettingRow, | |
42 | // r#"SELECT * FROM repository_settings WHERE repository = $1"#, | |
43 | // repository.to_string() | |
44 | // ) | |
45 | // .fetch_many(&self.pg_pool) | |
46 | // .filter_map(|result| async move { | |
47 | // if let Ok(Either::Right(row)) = result { | |
48 | // Some(row) | |
49 | // } else { | |
50 | // None | |
51 | // } | |
52 | // }) | |
53 | // .filter_map(|row| async move { | |
54 | // if let Ok(value) = serde_json::from_str(&row.value) { | |
55 | // Some((row.name, value)) | |
56 | // } else { | |
57 | // None | |
58 | // } | |
59 | // }) | |
60 | // .collect::<Vec<_>>() | |
61 | // .await; | |
49 | let row = sqlx::query_as!( | |
50 | RepositorySettingRow, | |
51 | "SELECT * FROM repository_settings WHERE repository = $1 AND name = $2", | |
52 | format!("{}/{}", repository.owner, repository.name), | |
53 | name | |
54 | ) | |
55 | .fetch_one(&self.pg_pool) | |
56 | .await?; | |
62 | 57 | |
63 | // Ok(settings) | |
58 | let setting = serde_json::from_str(&row.value)?; | |
64 | 59 | |
65 | todo!() | |
60 | Ok(setting) | |
66 | 61 | } |
67 | 62 | async fn repository_write( |
68 | 63 | &mut self, |
69 | _repository: &Repository, | |
70 | _name: &str, | |
71 | _value: AnySetting, | |
64 | repository: &Repository, | |
65 | name: &str, | |
66 | value: AnySetting, | |
72 | 67 | ) -> Result<(), Error> { |
73 | // for (name, value) in settings { | |
74 | // sqlx::query!("INSERT INTO repository_settings VALUES ($1, $2, $3) ON CONFLICT (repository, name) DO UPDATE SET value = $3", | |
75 | // repository.to_string(), name, value) | |
76 | // .execute(&self.pg_pool).await?; | |
77 | // } | |
78 | ||
79 | // Ok(()) | |
68 | sqlx::query!("INSERT INTO repository_settings VALUES ($1, $2, $3) ON CONFLICT (repository, name) DO UPDATE SET value = $3", | |
69 | format!("{}/{}", repository.owner, repository.name), name, serde_json::to_string(&value)?) | |
70 | .execute(&self.pg_pool).await?; | |
80 | 71 | |
81 | todo!() | |
72 | Ok(()) | |
82 | 73 | } |
83 | 74 | } |
84 | 75 |
giterated-daemon/src/backend/user.rs
@@ -1,20 +1,17 @@ | ||
1 | use std::sync::Arc; | |
2 | ||
3 | 1 | use anyhow::Error; |
2 | use giterated_models::authenticated::UserAuthenticationToken; | |
3 | ||
4 | use giterated_models::instance::{AuthenticationTokenRequest, Instance, RegisterAccountRequest}; | |
5 | ||
6 | use giterated_models::settings::{AnySetting, Setting}; | |
7 | use giterated_models::user::{User, UserBio, UserDisplayName, UserParseError}; | |
8 | use giterated_models::value::AnyValue; | |
9 | use std::sync::Arc; | |
4 | 10 | |
5 | 11 | use aes_gcm::{aead::Aead, AeadCore, Aes256Gcm, Key, KeyInit}; |
6 | 12 | use argon2::{password_hash::SaltString, Argon2, PasswordHash, PasswordHasher, PasswordVerifier}; |
7 | 13 | use base64::{engine::general_purpose::STANDARD, Engine as _}; |
8 | use giterated_models::{ | |
9 | model::{ | |
10 | authenticated::UserAuthenticationToken, | |
11 | instance::Instance, | |
12 | settings::{AnySetting, Setting, UserBio, UserDisplayName}, | |
13 | user::{User, UserParseError}, | |
14 | }, | |
15 | operation::instance::{AuthenticationTokenRequest, RegisterAccountRequest}, | |
16 | values::AnyValue, | |
17 | }; | |
14 | ||
18 | 15 | use rsa::{ |
19 | 16 | pkcs8::{EncodePrivateKey, EncodePublicKey}, |
20 | 17 | rand_core::OsRng, |
giterated-daemon/src/cache_backend.rs
@@ -1,7 +1,9 @@ | ||
1 | use giterated_models::{ | |
2 | error::OperationError, | |
3 | operation::{GiteratedObject, GiteratedOperation, Object, ObjectBackend, ObjectRequestError}, | |
4 | }; | |
1 | use giterated_models::error::OperationError; | |
2 | ||
3 | use giterated_models::object::{GiteratedObject, Object, ObjectRequestError}; | |
4 | use giterated_models::object_backend::ObjectBackend; | |
5 | use giterated_models::operation::GiteratedOperation; | |
6 | ||
5 | 7 | use std::fmt::Debug; |
6 | 8 | |
7 | 9 | #[derive(Clone, Debug)] |
giterated-daemon/src/connection.rs
@@ -1,14 +1,12 @@ | ||
1 | // pub mod authentication; | |
2 | // pub mod forwarded; | |
3 | // pub mod handshake; | |
4 | // pub mod repository; | |
5 | // pub mod user; | |
6 | 1 | pub mod wrapper; |
7 | 2 | |
3 | use giterated_models::instance::Instance; | |
4 | ||
5 | use giterated_models::instance::InstanceMeta; | |
6 | ||
8 | 7 | use std::{any::type_name, collections::HashMap}; |
9 | 8 | |
10 | 9 | use anyhow::Error; |
11 | use giterated_models::model::instance::{Instance, InstanceMeta}; | |
12 | 10 | use serde::{de::DeserializeOwned, Serialize}; |
13 | 11 | use tokio::{net::TcpStream, task::JoinHandle}; |
14 | 12 | use tokio_tungstenite::WebSocketStream; |
giterated-daemon/src/connection/wrapper.rs
@@ -5,11 +5,15 @@ use std::{ | ||
5 | 5 | |
6 | 6 | use anyhow::Error; |
7 | 7 | use futures_util::{SinkExt, StreamExt}; |
8 | ||
9 | use giterated_models::instance::Instance; | |
10 | ||
11 | use giterated_models::object_backend::ObjectBackend; | |
12 | ||
8 | 13 | use giterated_models::{ |
9 | model::{authenticated::AuthenticatedPayload, instance::Instance}, | |
10 | operation::{AnyObject, AnyOperation, GiteratedMessage, ObjectBackend}, | |
14 | authenticated::AuthenticatedPayload, message::GiteratedMessage, object::AnyObject, | |
15 | operation::AnyOperation, | |
11 | 16 | }; |
12 | ||
13 | 17 | use serde::Serialize; |
14 | 18 | |
15 | 19 | use tokio::{net::TcpStream, sync::Mutex}; |
giterated-daemon/src/database_backend/handler.rs
@@ -3,9 +3,12 @@ use std::{collections::HashMap, pin::Pin, sync::Arc}; | ||
3 | 3 | use futures_util::{future::BoxFuture, Future, FutureExt}; |
4 | 4 | use giterated_models::{ |
5 | 5 | error::{GetValueError, OperationError}, |
6 | model::{repository::Repository, settings::AnySetting, user::User}, | |
7 | operation::{AnyObject, AnyOperation, GetValue, GiteratedObject, GiteratedOperation}, | |
8 | values::{AnyValue, GetSetting, GetSettingError, SetSetting, SetSettingError}, | |
6 | object::{AnyObject, GiteratedObject}, | |
7 | operation::{AnyOperation, GiteratedOperation}, | |
8 | repository::Repository, | |
9 | settings::{AnySetting, GetSetting, GetSettingError, SetSetting, SetSettingError}, | |
10 | user::User, | |
11 | value::{AnyValue, GetValue}, | |
9 | 12 | }; |
10 | 13 | |
11 | 14 | use super::DatabaseBackend; |
giterated-daemon/src/database_backend/mod.rs
@@ -2,11 +2,13 @@ pub mod handler; | ||
2 | 2 | |
3 | 3 | use std::{str::FromStr, sync::Arc}; |
4 | 4 | |
5 | use giterated_models::{ | |
6 | error::OperationError, | |
7 | model::{instance::Instance, repository::Repository, user::User}, | |
8 | operation::{GiteratedObject, GiteratedOperation, Object, ObjectBackend, ObjectRequestError}, | |
9 | }; | |
5 | use giterated_models::error::OperationError; | |
6 | use giterated_models::instance::Instance; | |
7 | use giterated_models::object::{GiteratedObject, Object, ObjectRequestError}; | |
8 | use giterated_models::object_backend::ObjectBackend; | |
9 | use giterated_models::operation::GiteratedOperation; | |
10 | use giterated_models::repository::Repository; | |
11 | use giterated_models::user::User; | |
10 | 12 | use std::fmt::Debug; |
11 | 13 | use tokio::sync::Mutex; |
12 | 14 | |
@@ -182,27 +184,22 @@ mod test { | ||
182 | 184 | use std::{str::FromStr, sync::Arc}; |
183 | 185 | |
184 | 186 | use anyhow::Error; |
185 | use giterated_models::model::settings::UserDisplayName; | |
186 | use giterated_models::operation::ObjectBackend; | |
187 | use giterated_models::values::repository::Description; | |
188 | use giterated_models::{ | |
189 | model::{ | |
190 | authenticated::UserAuthenticationToken, | |
191 | instance::Instance, | |
192 | repository::{Repository, RepositorySummary, RepositoryTreeEntry}, | |
193 | settings::AnySetting, | |
194 | user::User, | |
195 | }, | |
196 | operation::{ | |
197 | instance::{ | |
198 | AuthenticationTokenRequest, RegisterAccountRequest, RepositoryCreateRequest, | |
199 | }, | |
200 | repository::RepositoryFileInspectRequest, | |
201 | GiteratedObjectValue, | |
202 | }, | |
203 | values::{user::DisplayName, AnyValue}, | |
187 | ||
188 | use giterated_models::authenticated::UserAuthenticationToken; | |
189 | ||
190 | use giterated_models::instance::{ | |
191 | AuthenticationTokenRequest, Instance, RegisterAccountRequest, RepositoryCreateRequest, | |
204 | 192 | }; |
205 | 193 | |
194 | use giterated_models::object_backend::ObjectBackend; | |
195 | ||
196 | use giterated_models::repository::{ | |
197 | Description, Repository, RepositoryFileInspectRequest, RepositorySummary, | |
198 | RepositoryTreeEntry, | |
199 | }; | |
200 | use giterated_models::settings::AnySetting; | |
201 | use giterated_models::user::{DisplayName, User, UserDisplayName}; | |
202 | use giterated_models::value::{AnyValue, GiteratedObjectValue}; | |
206 | 203 | use serde_json::Value; |
207 | 204 | use tokio::sync::Mutex; |
208 | 205 |
giterated-daemon/src/federation/connections.rs
@@ -2,7 +2,7 @@ use std::collections::HashMap; | ||
2 | 2 | |
3 | 3 | use anyhow::Error; |
4 | 4 | use giterated_api::DaemonConnectionPool; |
5 | use giterated_models::model::instance::Instance; | |
5 | use giterated_models::instance::Instance; | |
6 | 6 | |
7 | 7 | #[derive(Default)] |
8 | 8 | pub struct InstanceConnections { |
giterated-daemon/src/keys.rs
@@ -1,7 +1,8 @@ | ||
1 | use std::collections::HashMap; | |
2 | ||
3 | 1 | use anyhow::Error; |
4 | use giterated_models::model::instance::Instance; | |
2 | ||
3 | use giterated_models::instance::Instance; | |
4 | ||
5 | use std::collections::HashMap; | |
5 | 6 | |
6 | 7 | #[derive(Default)] |
7 | 8 | pub struct PublicKeyCache { |
giterated-daemon/src/lib.rs
@@ -3,6 +3,7 @@ use std::str::FromStr; | ||
3 | 3 | use semver::{Version, VersionReq}; |
4 | 4 | |
5 | 5 | pub mod authentication; |
6 | pub mod authorization; | |
6 | 7 | pub mod backend; |
7 | 8 | pub mod cache_backend; |
8 | 9 | pub mod connection; |
giterated-daemon/src/main.rs
@@ -8,7 +8,9 @@ use giterated_daemon::{ | ||
8 | 8 | connection::{self, wrapper::connection_wrapper}, |
9 | 9 | federation::connections::InstanceConnections, |
10 | 10 | }; |
11 | use giterated_models::model::instance::Instance; | |
11 | ||
12 | use giterated_models::instance::Instance; | |
13 | ||
12 | 14 | use sqlx::{postgres::PgConnectOptions, ConnectOptions, PgPool}; |
13 | 15 | use std::{net::SocketAddr, str::FromStr, sync::Arc}; |
14 | 16 | use tokio::{ |
giterated-daemon/src/message.rs
@@ -1,11 +1,12 @@ | ||
1 | use std::{fmt::Debug, ops::Deref}; | |
2 | ||
3 | 1 | use anyhow::Error; |
4 | 2 | use futures_util::Future; |
5 | use giterated_models::model::{ | |
6 | authenticated::{AuthenticatedPayload, AuthenticationSource, UserTokenMetadata}, | |
7 | instance::Instance, | |
8 | user::User, | |
3 | ||
4 | use giterated_models::instance::Instance; | |
5 | ||
6 | use giterated_models::user::User; | |
7 | ||
8 | use giterated_models::authenticated::{ | |
9 | AuthenticatedPayload, AuthenticationSource, UserTokenMetadata, | |
9 | 10 | }; |
10 | 11 | use jsonwebtoken::{decode, Algorithm, DecodingKey, TokenData, Validation}; |
11 | 12 | use rsa::{ |
@@ -16,6 +17,7 @@ use rsa::{ | ||
16 | 17 | RsaPublicKey, |
17 | 18 | }; |
18 | 19 | use serde::{de::DeserializeOwned, Serialize}; |
20 | use std::{fmt::Debug, ops::Deref}; | |
19 | 21 | |
20 | 22 | use crate::connection::wrapper::ConnectionState; |
21 | 23 |
giterated-models/src/authenticated.rs
@@ -0,0 +1,201 @@ | ||
1 | use std::fmt::Debug; | |
2 | ||
3 | use rsa::{ | |
4 | pkcs1::DecodeRsaPrivateKey, | |
5 | pss::SigningKey, | |
6 | sha2::Sha256, | |
7 | signature::{RandomizedSigner, SignatureEncoding}, | |
8 | RsaPrivateKey, | |
9 | }; | |
10 | use serde::{Deserialize, Serialize}; | |
11 | use serde_json::Value; | |
12 | use tracing::info; | |
13 | ||
14 | use crate::{ | |
15 | instance::Instance, | |
16 | message::GiteratedMessage, | |
17 | object::{AnyObject, GiteratedObject}, | |
18 | operation::{AnyOperation, GiteratedOperation}, | |
19 | user::User, | |
20 | }; | |
21 | ||
22 | #[derive(Debug, Serialize, Deserialize)] | |
23 | pub struct UserTokenMetadata { | |
24 | pub user: User, | |
25 | pub generated_for: Instance, | |
26 | pub exp: u64, | |
27 | } | |
28 | ||
29 | #[derive(Debug)] | |
30 | pub struct Authenticated<O: GiteratedObject, D: GiteratedOperation<O>> { | |
31 | pub source: Vec<Box<dyn AuthenticationSourceProvider + Send + Sync>>, | |
32 | pub message: GiteratedMessage<O, D>, | |
33 | } | |
34 | ||
35 | #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)] | |
36 | pub struct AuthenticatedPayload { | |
37 | pub source: Vec<AuthenticationSource>, | |
38 | pub object: String, | |
39 | pub operation: String, | |
40 | pub payload: Vec<u8>, | |
41 | } | |
42 | ||
43 | impl AuthenticatedPayload { | |
44 | pub fn into_message(self) -> GiteratedMessage<AnyObject, AnyOperation> { | |
45 | GiteratedMessage { | |
46 | object: AnyObject(self.object), | |
47 | operation: self.operation, | |
48 | payload: AnyOperation(serde_json::from_slice::<Value>(&self.payload).unwrap()), | |
49 | } | |
50 | } | |
51 | } | |
52 | ||
53 | pub trait AuthenticationSourceProvider: Debug { | |
54 | fn authenticate(&self, payload: &Vec<u8>) -> AuthenticationSource; | |
55 | } | |
56 | ||
57 | pub trait AuthenticationSourceProviders: Debug { | |
58 | fn authenticate_all(&self, payload: &Vec<u8>) -> Vec<AuthenticationSource>; | |
59 | } | |
60 | ||
61 | impl<A> AuthenticationSourceProviders for A | |
62 | where | |
63 | A: AuthenticationSourceProvider, | |
64 | { | |
65 | fn authenticate_all(&self, payload: &Vec<u8>) -> Vec<AuthenticationSource> { | |
66 | vec![self.authenticate(payload)] | |
67 | } | |
68 | } | |
69 | ||
70 | impl<A, B> AuthenticationSourceProviders for (A, B) | |
71 | where | |
72 | A: AuthenticationSourceProvider, | |
73 | B: AuthenticationSourceProvider, | |
74 | { | |
75 | fn authenticate_all(&self, payload: &Vec<u8>) -> Vec<AuthenticationSource> { | |
76 | let (first, second) = self; | |
77 | ||
78 | vec![first.authenticate(payload), second.authenticate(payload)] | |
79 | } | |
80 | } | |
81 | ||
82 | impl<O: GiteratedObject, D: GiteratedOperation<O>> Authenticated<O, D> { | |
83 | pub fn new(message: GiteratedMessage<O, D>) -> Self { | |
84 | Self { | |
85 | source: vec![], | |
86 | message, | |
87 | } | |
88 | } | |
89 | ||
90 | pub fn append_authentication<P: AuthenticationSourceProvider + 'static + Send + Sync>( | |
91 | &mut self, | |
92 | authentication: P, | |
93 | ) { | |
94 | let message_payload = serde_json::to_vec(&self.message).unwrap(); | |
95 | ||
96 | info!( | |
97 | "Verifying payload: {}", | |
98 | std::str::from_utf8(&message_payload).unwrap() | |
99 | ); | |
100 | ||
101 | self.source | |
102 | .push(Box::new(authentication) as Box<dyn AuthenticationSourceProvider + Send + Sync>); | |
103 | } | |
104 | ||
105 | pub fn into_payload(mut self) -> AuthenticatedPayload { | |
106 | let payload = bincode::serialize(&self.message.payload).unwrap(); | |
107 | ||
108 | AuthenticatedPayload { | |
109 | object: self.message.object.to_string(), | |
110 | operation: self.message.operation, | |
111 | source: self | |
112 | .source | |
113 | .drain(..) | |
114 | .map(|provider| provider.as_ref().authenticate(&payload)) | |
115 | .collect::<Vec<_>>(), | |
116 | payload, | |
117 | } | |
118 | } | |
119 | } | |
120 | ||
121 | mod verified {} | |
122 | ||
123 | #[derive(Clone, Debug)] | |
124 | pub struct UserAuthenticator { | |
125 | pub user: User, | |
126 | pub token: UserAuthenticationToken, | |
127 | } | |
128 | ||
129 | impl AuthenticationSourceProvider for UserAuthenticator { | |
130 | fn authenticate(&self, _payload: &Vec<u8>) -> AuthenticationSource { | |
131 | AuthenticationSource::User { | |
132 | user: self.user.clone(), | |
133 | token: self.token.clone(), | |
134 | } | |
135 | } | |
136 | } | |
137 | ||
138 | #[derive(Debug, Clone)] | |
139 | pub struct InstanceAuthenticator { | |
140 | pub instance: Instance, | |
141 | pub private_key: String, | |
142 | } | |
143 | ||
144 | impl AuthenticationSourceProvider for InstanceAuthenticator { | |
145 | fn authenticate(&self, payload: &Vec<u8>) -> AuthenticationSource { | |
146 | let mut rng = rand::thread_rng(); | |
147 | ||
148 | let private_key = RsaPrivateKey::from_pkcs1_pem(&self.private_key).unwrap(); | |
149 | let signing_key = SigningKey::<Sha256>::new(private_key); | |
150 | let signature = signing_key.sign_with_rng(&mut rng, payload); | |
151 | ||
152 | AuthenticationSource::Instance { | |
153 | instance: self.instance.clone(), | |
154 | // TODO: Actually parse signature from private key | |
155 | signature: InstanceSignature(signature.to_bytes().into_vec()), | |
156 | } | |
157 | } | |
158 | } | |
159 | ||
160 | #[repr(transparent)] | |
161 | #[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] | |
162 | pub struct UserAuthenticationToken(String); | |
163 | ||
164 | impl From<String> for UserAuthenticationToken { | |
165 | fn from(value: String) -> Self { | |
166 | Self(value) | |
167 | } | |
168 | } | |
169 | ||
170 | impl ToString for UserAuthenticationToken { | |
171 | fn to_string(&self) -> String { | |
172 | self.0.clone() | |
173 | } | |
174 | } | |
175 | ||
176 | impl AsRef<str> for UserAuthenticationToken { | |
177 | fn as_ref(&self) -> &str { | |
178 | &self.0 | |
179 | } | |
180 | } | |
181 | ||
182 | #[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] | |
183 | pub struct InstanceSignature(Vec<u8>); | |
184 | ||
185 | impl AsRef<[u8]> for InstanceSignature { | |
186 | fn as_ref(&self) -> &[u8] { | |
187 | &self.0 | |
188 | } | |
189 | } | |
190 | ||
191 | #[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] | |
192 | pub enum AuthenticationSource { | |
193 | User { | |
194 | user: User, | |
195 | token: UserAuthenticationToken, | |
196 | }, | |
197 | Instance { | |
198 | instance: Instance, | |
199 | signature: InstanceSignature, | |
200 | }, | |
201 | } |
giterated-models/src/discovery.rs
@@ -0,0 +1,15 @@ | ||
1 | use serde::{Deserialize, Serialize}; | |
2 | ||
3 | use crate::{instance::Instance, repository::Repository}; | |
4 | ||
5 | #[derive(Clone, Hash, PartialEq, Eq, Debug, Serialize, Deserialize)] | |
6 | pub enum DiscoveryItem { | |
7 | Instance { | |
8 | instance: Instance, | |
9 | signature: Vec<u8>, | |
10 | }, | |
11 | Repository { | |
12 | repository: Repository, | |
13 | signature: Vec<u8>, | |
14 | }, | |
15 | } |
giterated-models/src/handshake.rs
@@ -1,7 +1,7 @@ | ||
1 | 1 | use semver::Version; |
2 | 2 | use serde::{Deserialize, Serialize}; |
3 | 3 | |
4 | use crate::model::instance::Instance; | |
4 | use crate::instance::Instance; | |
5 | 5 | |
6 | 6 | /// Sent by the initiator of a new inter-daemon connection. |
7 | 7 | #[derive(Clone, Debug, Serialize, Deserialize)] |
giterated-models/src/instance/mod.rs
@@ -0,0 +1,69 @@ | ||
1 | use std::{fmt::Display, str::FromStr}; | |
2 | ||
3 | use serde::{Deserialize, Serialize}; | |
4 | use thiserror::Error; | |
5 | ||
6 | mod operations; | |
7 | mod values; | |
8 | ||
9 | pub use operations::*; | |
10 | pub use values::*; | |
11 | ||
12 | use crate::object::GiteratedObject; | |
13 | ||
14 | pub struct InstanceMeta { | |
15 | pub url: String, | |
16 | pub public_key: String, | |
17 | } | |
18 | ||
19 | /// An instance, defined by the URL it can be reached at. | |
20 | /// | |
21 | /// # Textual Format | |
22 | /// An instance's textual format is its URL. | |
23 | /// | |
24 | /// ## Examples | |
25 | /// For the instance `giterated.dev`, the following [`Instance`] initialization | |
26 | /// would be valid: | |
27 | /// | |
28 | /// ``` | |
29 | /// let instance = Instance { | |
30 | /// url: String::from("giterated.dev") | |
31 | /// }; | |
32 | /// | |
33 | /// // This is correct | |
34 | /// assert_eq!(Instance::from_str("giterated.dev").unwrap(), instance); | |
35 | /// ``` | |
36 | #[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] | |
37 | pub struct Instance { | |
38 | pub url: String, | |
39 | } | |
40 | ||
41 | impl GiteratedObject for Instance { | |
42 | fn object_name() -> &'static str { | |
43 | "instance" | |
44 | } | |
45 | ||
46 | fn from_object_str(object_str: &str) -> Result<Self, anyhow::Error> { | |
47 | Ok(Instance::from_str(object_str).unwrap()) | |
48 | } | |
49 | } | |
50 | ||
51 | impl Display for Instance { | |
52 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | |
53 | f.write_str(&self.url) | |
54 | } | |
55 | } | |
56 | ||
57 | impl FromStr for Instance { | |
58 | type Err = InstanceParseError; | |
59 | ||
60 | fn from_str(s: &str) -> Result<Self, Self::Err> { | |
61 | Ok(Self { url: s.to_string() }) | |
62 | } | |
63 | } | |
64 | ||
65 | #[derive(Debug, Error)] | |
66 | pub enum InstanceParseError { | |
67 | #[error("invalid format")] | |
68 | InvalidFormat, | |
69 | } |
giterated-models/src/instance/operations.rs
@@ -0,0 +1,179 @@ | ||
1 | use secrecy::Secret; | |
2 | use serde::{Deserialize, Serialize}; | |
3 | ||
4 | use crate::{ | |
5 | authenticated::UserAuthenticationToken, | |
6 | error::{InstanceError, OperationError}, | |
7 | object::Object, | |
8 | object_backend::ObjectBackend, | |
9 | operation::GiteratedOperation, | |
10 | repository::{Repository, RepositoryVisibility}, | |
11 | user::{Password, User}, | |
12 | }; | |
13 | ||
14 | use super::Instance; | |
15 | ||
16 | /// An account registration request. | |
17 | /// | |
18 | /// # Authentication | |
19 | /// - Instance Authentication | |
20 | /// - **ONLY ACCEPTED WHEN SAME-INSTANCE** | |
21 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
22 | pub struct RegisterAccountRequest { | |
23 | pub username: String, | |
24 | pub email: Option<String>, | |
25 | pub password: Secret<Password>, | |
26 | } | |
27 | ||
28 | impl GiteratedOperation<Instance> for RegisterAccountRequest { | |
29 | type Success = UserAuthenticationToken; | |
30 | type Failure = InstanceError; | |
31 | } | |
32 | ||
33 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
34 | pub struct RegisterAccountResponse { | |
35 | pub token: String, | |
36 | } | |
37 | ||
38 | /// An authentication token request. | |
39 | /// | |
40 | /// AKA Login Request | |
41 | /// | |
42 | /// # Authentication | |
43 | /// - Instance Authentication | |
44 | /// - Identifies the Instance to issue the token for | |
45 | /// # Authorization | |
46 | /// - Credentials ([`crate::backend::AuthBackend`]-based) | |
47 | /// - Identifies the User account to issue a token for | |
48 | /// - Decrypts user private key to issue to | |
49 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
50 | pub struct AuthenticationTokenRequest { | |
51 | pub instance: Instance, | |
52 | pub username: String, | |
53 | pub password: Secret<Password>, | |
54 | } | |
55 | ||
56 | impl GiteratedOperation<Instance> for AuthenticationTokenRequest { | |
57 | type Success = UserAuthenticationToken; | |
58 | type Failure = InstanceError; | |
59 | } | |
60 | ||
61 | /// An authentication token extension request. | |
62 | /// | |
63 | /// # Authentication | |
64 | /// - Instance Authentication | |
65 | /// - Identifies the Instance to issue the token for | |
66 | /// - User Authentication | |
67 | /// - Authenticates the validity of the token | |
68 | /// # Authorization | |
69 | /// - Token-based | |
70 | /// - Validates authorization using token's authenticity | |
71 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
72 | pub struct TokenExtensionRequest { | |
73 | pub token: UserAuthenticationToken, | |
74 | } | |
75 | ||
76 | impl GiteratedOperation<Instance> for TokenExtensionRequest { | |
77 | type Success = Option<UserAuthenticationToken>; | |
78 | type Failure = InstanceError; | |
79 | } | |
80 | ||
81 | /// A request to create a repository. | |
82 | /// | |
83 | /// # Authentication | |
84 | /// - Instance Authentication | |
85 | /// - Used to validate User token `issued_for` | |
86 | /// - User Authentication | |
87 | /// - Used to source owning user | |
88 | /// - Used to authorize user token against user's instance | |
89 | /// # Authorization | |
90 | /// - Instance Authorization | |
91 | /// - Used to authorize action using User token requiring a correct `issued_for` and valid issuance from user's instance | |
92 | /// - User Authorization | |
93 | /// - Potential User permissions checks | |
94 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
95 | pub struct RepositoryCreateRequest { | |
96 | pub instance: Option<Instance>, | |
97 | pub name: String, | |
98 | pub description: Option<String>, | |
99 | pub visibility: RepositoryVisibility, | |
100 | pub default_branch: String, | |
101 | pub owner: User, | |
102 | } | |
103 | ||
104 | impl GiteratedOperation<Instance> for RepositoryCreateRequest { | |
105 | type Success = Repository; | |
106 | type Failure = InstanceError; | |
107 | } | |
108 | ||
109 | impl<B: ObjectBackend + std::fmt::Debug> Object<'_, Instance, B> { | |
110 | pub async fn register_account( | |
111 | &mut self, | |
112 | email: Option<&str>, | |
113 | username: &str, | |
114 | password: &Secret<Password>, | |
115 | ) -> Result<UserAuthenticationToken, OperationError<InstanceError>> { | |
116 | self.request::<RegisterAccountRequest>(RegisterAccountRequest { | |
117 | username: username.to_string(), | |
118 | email: email.map(|s| s.to_string()), | |
119 | password: password.clone(), | |
120 | }) | |
121 | .await | |
122 | } | |
123 | ||
124 | pub async fn authentication_token( | |
125 | &mut self, | |
126 | username: &str, | |
127 | password: &Secret<Password>, | |
128 | ) -> Result<UserAuthenticationToken, OperationError<InstanceError>> { | |
129 | self.request::<AuthenticationTokenRequest>(AuthenticationTokenRequest { | |
130 | instance: self.inner.clone(), | |
131 | username: username.to_string(), | |
132 | password: password.clone(), | |
133 | }) | |
134 | .await | |
135 | } | |
136 | ||
137 | pub async fn authentication_token_for( | |
138 | &mut self, | |
139 | instance: &Instance, | |
140 | username: &str, | |
141 | password: &Secret<Password>, | |
142 | ) -> Result<UserAuthenticationToken, OperationError<InstanceError>> { | |
143 | self.request::<AuthenticationTokenRequest>(AuthenticationTokenRequest { | |
144 | instance: instance.clone(), | |
145 | username: username.to_string(), | |
146 | password: password.clone(), | |
147 | }) | |
148 | .await | |
149 | } | |
150 | ||
151 | pub async fn token_extension( | |
152 | &mut self, | |
153 | token: &UserAuthenticationToken, | |
154 | ) -> Result<Option<UserAuthenticationToken>, OperationError<InstanceError>> { | |
155 | self.request::<TokenExtensionRequest>(TokenExtensionRequest { | |
156 | token: token.clone(), | |
157 | }) | |
158 | .await | |
159 | } | |
160 | ||
161 | pub async fn create_repository( | |
162 | &mut self, | |
163 | instance: &Instance, | |
164 | name: &str, | |
165 | visibility: RepositoryVisibility, | |
166 | default_branch: &str, | |
167 | owner: &User, | |
168 | ) -> Result<Repository, OperationError<InstanceError>> { | |
169 | self.request::<RepositoryCreateRequest>(RepositoryCreateRequest { | |
170 | instance: Some(instance.clone()), | |
171 | name: name.to_string(), | |
172 | description: None, | |
173 | visibility, | |
174 | default_branch: default_branch.to_string(), | |
175 | owner: owner.clone(), | |
176 | }) | |
177 | .await | |
178 | } | |
179 | } |
giterated-models/src/instance/values.rs
@@ -0,0 +1 @@ | ||
1 |
giterated-models/src/lib.rs
@@ -1,5 +1,13 @@ | ||
1 | pub mod authenticated; | |
2 | pub mod discovery; | |
1 | 3 | pub mod error; |
2 | 4 | pub mod handshake; |
3 | pub mod model; | |
5 | pub mod instance; | |
6 | pub mod message; | |
7 | pub mod object; | |
8 | pub mod object_backend; | |
4 | 9 | pub mod operation; |
5 | pub mod values; | |
10 | pub mod repository; | |
11 | pub mod settings; | |
12 | pub mod user; | |
13 | pub mod value; |
giterated-models/src/message.rs
@@ -0,0 +1,69 @@ | ||
1 | use serde::Serialize; | |
2 | ||
3 | use crate::{ | |
4 | object::{AnyObject, GiteratedObject}, | |
5 | operation::{AnyOperation, GiteratedOperation}, | |
6 | }; | |
7 | use std::fmt::Debug; | |
8 | ||
9 | #[derive(Serialize)] | |
10 | #[serde(bound(deserialize = "O: GiteratedObject, V: GiteratedOperation<O>"))] | |
11 | pub struct GiteratedMessage<O: GiteratedObject, V: GiteratedOperation<O>> { | |
12 | #[serde(with = "string")] | |
13 | pub object: O, | |
14 | pub operation: String, | |
15 | pub payload: V, | |
16 | } | |
17 | ||
18 | mod string { | |
19 | use std::fmt::Display; | |
20 | use std::str::FromStr; | |
21 | ||
22 | use serde::{de, Deserialize, Deserializer, Serializer}; | |
23 | ||
24 | pub fn serialize<T, S>(value: &T, serializer: S) -> Result<S::Ok, S::Error> | |
25 | where | |
26 | T: Display, | |
27 | S: Serializer, | |
28 | { | |
29 | serializer.collect_str(value) | |
30 | } | |
31 | ||
32 | pub fn deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error> | |
33 | where | |
34 | T: FromStr, | |
35 | T::Err: Display, | |
36 | D: Deserializer<'de>, | |
37 | { | |
38 | String::deserialize(deserializer)? | |
39 | .parse() | |
40 | .map_err(de::Error::custom) | |
41 | } | |
42 | } | |
43 | ||
44 | impl GiteratedMessage<AnyObject, AnyOperation> { | |
45 | pub fn try_into<O: GiteratedObject, V: GiteratedOperation<O>>( | |
46 | &self, | |
47 | ) -> Result<GiteratedMessage<O, V>, ()> { | |
48 | let object = O::from_object_str(&self.object.0).map_err(|_| ())?; | |
49 | let payload = serde_json::from_value::<V>(self.payload.0.clone()).map_err(|_| ())?; | |
50 | ||
51 | Ok(GiteratedMessage { | |
52 | object, | |
53 | operation: self.operation.clone(), | |
54 | payload, | |
55 | }) | |
56 | } | |
57 | } | |
58 | ||
59 | impl<V: GiteratedOperation<O> + Debug, O: GiteratedObject + Debug> Debug | |
60 | for GiteratedMessage<O, V> | |
61 | { | |
62 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | |
63 | f.debug_struct("GiteratedMessage") | |
64 | .field("object", &self.object) | |
65 | .field("operation", &self.operation) | |
66 | .field("payload", &self.payload) | |
67 | .finish() | |
68 | } | |
69 | } |
giterated-models/src/object.rs
@@ -0,0 +1,87 @@ | ||
1 | use std::{ | |
2 | fmt::{Debug, Display}, | |
3 | marker::PhantomData, | |
4 | str::FromStr, | |
5 | }; | |
6 | ||
7 | use anyhow::Error; | |
8 | ||
9 | use crate::{ | |
10 | error::{GetValueError, OperationError}, | |
11 | object_backend::ObjectBackend, | |
12 | operation::GiteratedOperation, | |
13 | settings::{GetSetting, GetSettingError, SetSetting, SetSettingError, Setting}, | |
14 | value::{GetValue, GiteratedObjectValue}, | |
15 | }; | |
16 | ||
17 | mod operations; | |
18 | pub use operations::*; | |
19 | ||
20 | #[derive(Debug, Clone)] | |
21 | pub struct Object<'b, O: GiteratedObject, B: ObjectBackend + 'b + Send + Sync + Clone> { | |
22 | pub(crate) inner: O, | |
23 | pub(crate) backend: B, | |
24 | pub(crate) _marker: PhantomData<&'b ()>, | |
25 | } | |
26 | ||
27 | impl<'b, B: ObjectBackend + Send + Sync + Clone, O: GiteratedObject> Object<'b, O, B> { | |
28 | pub fn object(&self) -> &O { | |
29 | &self.inner | |
30 | } | |
31 | ||
32 | pub unsafe fn new_unchecked(object: O, backend: B) -> Object<'b, O, B> { | |
33 | Object { | |
34 | inner: object, | |
35 | backend, | |
36 | _marker: PhantomData, | |
37 | } | |
38 | } | |
39 | } | |
40 | ||
41 | pub trait GiteratedObject: Send + Display + FromStr { | |
42 | fn object_name() -> &'static str; | |
43 | ||
44 | fn from_object_str(object_str: &str) -> Result<Self, Error>; | |
45 | } | |
46 | ||
47 | impl<'b, O: GiteratedObject + Clone + Debug, B: ObjectBackend> Object<'b, O, B> { | |
48 | pub async fn get<V: GiteratedObjectValue<Object = O> + Send + Debug>( | |
49 | &mut self, | |
50 | ) -> Result<V, OperationError<GetValueError>> { | |
51 | self.request(GetValue { | |
52 | value_name: V::value_name().to_string(), | |
53 | _marker: PhantomData, | |
54 | }) | |
55 | .await | |
56 | } | |
57 | ||
58 | pub async fn get_setting<S: Setting + Send + Clone + Debug>( | |
59 | &mut self, | |
60 | ) -> Result<S, OperationError<GetSettingError>> { | |
61 | self.request(GetSetting { | |
62 | setting_name: S::name().to_string(), | |
63 | _marker: PhantomData, | |
64 | }) | |
65 | .await | |
66 | } | |
67 | ||
68 | pub async fn set_setting<S: Setting + Send + Clone + Debug>( | |
69 | &mut self, | |
70 | setting: S, | |
71 | ) -> Result<(), OperationError<SetSettingError>> { | |
72 | self.request(SetSetting { | |
73 | setting_name: S::name().to_string(), | |
74 | value: setting, | |
75 | }) | |
76 | .await | |
77 | } | |
78 | ||
79 | pub async fn request<R: GiteratedOperation<O> + Debug>( | |
80 | &mut self, | |
81 | request: R, | |
82 | ) -> Result<R::Success, OperationError<R::Failure>> { | |
83 | self.backend | |
84 | .object_operation(self.inner.clone(), request) | |
85 | .await | |
86 | } | |
87 | } |
giterated-models/src/object/operations.rs
@@ -0,0 +1,53 @@ | ||
1 | use std::{convert::Infallible, fmt::Display, str::FromStr}; | |
2 | ||
3 | use serde::{Deserialize, Serialize}; | |
4 | ||
5 | use crate::{instance::Instance, operation::GiteratedOperation}; | |
6 | ||
7 | use super::GiteratedObject; | |
8 | ||
9 | #[derive(Debug, Serialize, Deserialize)] | |
10 | pub struct ObjectRequest(pub String); | |
11 | ||
12 | #[derive(Serialize, Deserialize)] | |
13 | pub struct ObjectResponse(pub Vec<u8>); | |
14 | ||
15 | impl GiteratedOperation<Instance> for ObjectRequest { | |
16 | type Success = ObjectResponse; | |
17 | ||
18 | type Failure = ObjectRequestError; | |
19 | } | |
20 | ||
21 | #[derive(Debug, thiserror::Error, Serialize, Deserialize)] | |
22 | pub enum ObjectRequestError { | |
23 | #[error("error decoding the object")] | |
24 | Deserialization(String), | |
25 | } | |
26 | ||
27 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
28 | #[repr(transparent)] | |
29 | pub struct AnyObject(pub String); | |
30 | ||
31 | impl GiteratedObject for AnyObject { | |
32 | fn object_name() -> &'static str { | |
33 | "any" | |
34 | } | |
35 | ||
36 | fn from_object_str(object_str: &str) -> Result<Self, anyhow::Error> { | |
37 | Ok(Self(object_str.to_string())) | |
38 | } | |
39 | } | |
40 | ||
41 | impl Display for AnyObject { | |
42 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | |
43 | f.write_str(&self.0) | |
44 | } | |
45 | } | |
46 | ||
47 | impl FromStr for AnyObject { | |
48 | type Err = Infallible; | |
49 | ||
50 | fn from_str(s: &str) -> Result<Self, Self::Err> { | |
51 | Ok(Self(s.to_owned())) | |
52 | } | |
53 | } |
giterated-models/src/object_backend.rs
@@ -0,0 +1,24 @@ | ||
1 | use crate::{ | |
2 | error::OperationError, | |
3 | object::{GiteratedObject, Object, ObjectRequestError}, | |
4 | operation::GiteratedOperation, | |
5 | }; | |
6 | ||
7 | use std::fmt::Debug; | |
8 | ||
9 | #[async_trait::async_trait] | |
10 | pub trait ObjectBackend: Send + Sync + Sized + Clone { | |
11 | async fn object_operation<O, D>( | |
12 | &self, | |
13 | object: O, | |
14 | operation: D, | |
15 | ) -> Result<D::Success, OperationError<D::Failure>> | |
16 | where | |
17 | O: GiteratedObject + Debug, | |
18 | D: GiteratedOperation<O> + Debug; | |
19 | ||
20 | async fn get_object<O: GiteratedObject + Debug>( | |
21 | &self, | |
22 | object_str: &str, | |
23 | ) -> Result<Object<O, Self>, OperationError<ObjectRequestError>>; | |
24 | } |
giterated-models/src/operation.rs
@@ -0,0 +1,26 @@ | ||
1 | use std::{any::type_name, fmt::Debug}; | |
2 | ||
3 | use serde::{de::DeserializeOwned, Deserialize, Serialize}; | |
4 | use serde_json::Value; | |
5 | ||
6 | use crate::object::GiteratedObject; | |
7 | ||
8 | pub trait GiteratedOperation<O: GiteratedObject>: Send + Serialize + DeserializeOwned { | |
9 | type Success: Serialize + DeserializeOwned + Send; | |
10 | type Failure: Serialize + DeserializeOwned + Send; | |
11 | ||
12 | fn operation_name() -> &'static str { | |
13 | type_name::<Self>() | |
14 | } | |
15 | } | |
16 | ||
17 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
18 | #[serde(transparent)] | |
19 | #[repr(transparent)] | |
20 | pub struct AnyOperation(pub Value); | |
21 | ||
22 | impl<O: GiteratedObject> GiteratedOperation<O> for AnyOperation { | |
23 | type Success = Vec<u8>; | |
24 | ||
25 | type Failure = Vec<u8>; | |
26 | } |
giterated-models/src/repository/mod.rs
@@ -0,0 +1,259 @@ | ||
1 | use std::fmt::{Display, Formatter}; | |
2 | use std::str::FromStr; | |
3 | ||
4 | use serde::{Deserialize, Serialize}; | |
5 | ||
6 | use crate::object::GiteratedObject; | |
7 | ||
8 | use super::{instance::Instance, user::User}; | |
9 | ||
10 | mod operations; | |
11 | mod settings; | |
12 | mod values; | |
13 | ||
14 | pub use operations::*; | |
15 | pub use settings::*; | |
16 | pub use values::*; | |
17 | ||
18 | /// A repository, defined by the instance it exists on along with | |
19 | /// its owner and name. | |
20 | /// | |
21 | /// # Textual Format | |
22 | /// A repository's textual reference is defined as: | |
23 | /// | |
24 | /// `{owner: User}/{name: String}@{instance: Instance}` | |
25 | /// | |
26 | /// # Examples | |
27 | /// For the repository named `foo` owned by `barson:giterated.dev` on the instance | |
28 | /// `giterated.dev`, the following [`Repository`] initialization would | |
29 | /// be valid: | |
30 | /// | |
31 | /// ``` | |
32 | //# use giterated_models::model::repository::Repository; | |
33 | //# use giterated_models::model::instance::Instance; | |
34 | //# use giterated_models::model::user::User; | |
35 | /// let repository = Repository { | |
36 | /// owner: User::from_str("barson:giterated.dev").unwrap(), | |
37 | /// name: String::from("foo"), | |
38 | /// instance: Instance::from_str("giterated.dev").unwrap() | |
39 | /// }; | |
40 | /// | |
41 | /// // This is correct | |
42 | /// assert_eq!(Repository::from_str("barson:giterated.dev/[email protected]").unwrap(), repository); | |
43 | /// ``` | |
44 | #[derive(Hash, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] | |
45 | pub struct Repository { | |
46 | pub owner: User, | |
47 | pub name: String, | |
48 | /// Instance the repository is on | |
49 | pub instance: Instance, | |
50 | } | |
51 | ||
52 | impl Display for Repository { | |
53 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { | |
54 | f.write_str(&format!("{}/{}@{}", self.owner, self.name, self.instance)) | |
55 | } | |
56 | } | |
57 | ||
58 | impl GiteratedObject for Repository { | |
59 | fn object_name() -> &'static str { | |
60 | "repository" | |
61 | } | |
62 | ||
63 | fn from_object_str(object_str: &str) -> Result<Self, anyhow::Error> { | |
64 | Ok(Repository::from_str(object_str)?) | |
65 | } | |
66 | } | |
67 | ||
68 | impl TryFrom<String> for Repository { | |
69 | type Error = RepositoryParseError; | |
70 | ||
71 | fn try_from(value: String) -> Result<Self, Self::Error> { | |
72 | Self::from_str(&value) | |
73 | } | |
74 | } | |
75 | ||
76 | impl FromStr for Repository { | |
77 | type Err = RepositoryParseError; | |
78 | ||
79 | fn from_str(s: &str) -> Result<Self, Self::Err> { | |
80 | let mut by_ampersand = s.split('@'); | |
81 | let mut path_split = by_ampersand.next().unwrap().split('/'); | |
82 | ||
83 | let instance = Instance::from_str(by_ampersand.next().unwrap()).unwrap(); | |
84 | let owner = User::from_str(path_split.next().unwrap()).unwrap(); | |
85 | let name = path_split.next().unwrap().to_string(); | |
86 | ||
87 | Ok(Self { | |
88 | instance, | |
89 | owner, | |
90 | name, | |
91 | }) | |
92 | } | |
93 | } | |
94 | ||
95 | #[derive(Debug, thiserror::Error)] | |
96 | pub enum RepositoryParseError {} | |
97 | ||
98 | /// Visibility of the repository to the general eye | |
99 | #[derive(PartialEq, Eq, Debug, Hash, Serialize, Deserialize, Clone, sqlx::Type)] | |
100 | #[sqlx(type_name = "visibility", rename_all = "lowercase")] | |
101 | pub enum RepositoryVisibility { | |
102 | Public, | |
103 | Unlisted, | |
104 | Private, | |
105 | } | |
106 | ||
107 | /// Implements [`Display`] for [`RepositoryVisiblity`] using [`Debug`] | |
108 | impl Display for RepositoryVisibility { | |
109 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { | |
110 | write!(f, "{:?}", self) | |
111 | } | |
112 | } | |
113 | ||
114 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
115 | pub struct RepositoryView { | |
116 | /// Name of the repository | |
117 | /// | |
118 | /// This is different than the [`Repository`] name, | |
119 | /// which may be a path. | |
120 | pub name: String, | |
121 | /// Owner of the Repository | |
122 | pub owner: User, | |
123 | /// Repository description | |
124 | pub description: Option<String>, | |
125 | /// Repository visibility | |
126 | pub visibility: RepositoryVisibility, | |
127 | /// Default branch of the repository | |
128 | pub default_branch: String, | |
129 | /// Last commit made to the repository | |
130 | pub latest_commit: Option<Commit>, | |
131 | /// Revision of the displayed tree | |
132 | pub tree_rev: Option<String>, | |
133 | /// Repository tree | |
134 | pub tree: Vec<RepositoryTreeEntry>, | |
135 | } | |
136 | ||
137 | #[derive(Debug, Clone, Serialize, Deserialize)] | |
138 | pub enum RepositoryObjectType { | |
139 | Tree, | |
140 | Blob, | |
141 | } | |
142 | ||
143 | /// Stored info for our tree entries | |
144 | #[derive(Debug, Clone, Serialize, Deserialize)] | |
145 | pub struct RepositoryTreeEntry { | |
146 | /// Name of the tree/blob | |
147 | pub name: String, | |
148 | /// Type of the tree entry | |
149 | pub object_type: RepositoryObjectType, | |
150 | /// Git supplies us with the mode at all times, and people like it displayed. | |
151 | pub mode: i32, | |
152 | /// File size | |
153 | pub size: Option<usize>, | |
154 | /// Last commit made to the tree/blob | |
155 | pub last_commit: Option<Commit>, | |
156 | } | |
157 | ||
158 | impl RepositoryTreeEntry { | |
159 | // I love you Emilia <3 | |
160 | pub fn new(name: &str, object_type: RepositoryObjectType, mode: i32) -> Self { | |
161 | Self { | |
162 | name: name.to_string(), | |
163 | object_type, | |
164 | mode, | |
165 | size: None, | |
166 | last_commit: None, | |
167 | } | |
168 | } | |
169 | } | |
170 | ||
171 | #[derive(Debug, Clone, Serialize, Deserialize)] | |
172 | pub struct RepositoryTreeEntryWithCommit { | |
173 | pub tree_entry: RepositoryTreeEntry, | |
174 | pub commit: Commit, | |
175 | } | |
176 | ||
177 | /// Info about a git commit | |
178 | #[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)] | |
179 | pub struct Commit { | |
180 | /// Unique commit ID | |
181 | pub oid: String, | |
182 | /// Shortened abbreviated OID | |
183 | /// This starts at the git config's "core.abbrev" length (default 7 characters) and | |
184 | /// iteratively extends to a longer string if that length is ambiguous. The | |
185 | /// result will be unambiguous (at least until new objects are added to the repository). | |
186 | pub short_oid: String, | |
187 | /// Full commit message | |
188 | pub message: Option<String>, | |
189 | /// Who created the commit | |
190 | pub author: CommitSignature, | |
191 | /// Who committed the commit | |
192 | pub committer: CommitSignature, | |
193 | /// Time when the commit happened | |
194 | pub time: chrono::NaiveDateTime, | |
195 | } | |
196 | ||
197 | /// Gets all info from [`git2::Commit`] for easy use | |
198 | impl From<git2::Commit<'_>> for Commit { | |
199 | fn from(commit: git2::Commit<'_>) -> Self { | |
200 | Self { | |
201 | oid: commit.id().to_string(), | |
202 | // This shouldn't ever fail, as we already know the object has an oid. | |
203 | short_oid: commit | |
204 | .as_object() | |
205 | .short_id() | |
206 | .unwrap() | |
207 | .as_str() | |
208 | .unwrap() | |
209 | .to_string(), | |
210 | message: commit.message().map(|message| message.to_string()), | |
211 | author: commit.author().into(), | |
212 | committer: commit.committer().into(), | |
213 | time: chrono::NaiveDateTime::from_timestamp_opt(commit.time().seconds(), 0).unwrap(), | |
214 | } | |
215 | } | |
216 | } | |
217 | ||
218 | /// Git commit signature | |
219 | #[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)] | |
220 | pub struct CommitSignature { | |
221 | pub name: Option<String>, | |
222 | pub email: Option<String>, | |
223 | pub time: chrono::NaiveDateTime, | |
224 | } | |
225 | ||
226 | /// Converts the signature from git2 into something usable without explicit lifetimes. | |
227 | impl From<git2::Signature<'_>> for CommitSignature { | |
228 | fn from(signature: git2::Signature<'_>) -> Self { | |
229 | Self { | |
230 | name: signature.name().map(|name| name.to_string()), | |
231 | email: signature.email().map(|email| email.to_string()), | |
232 | time: chrono::NaiveDateTime::from_timestamp_opt(signature.when().seconds(), 0).unwrap(), | |
233 | } | |
234 | } | |
235 | } | |
236 | ||
237 | #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] | |
238 | pub struct RepositorySummary { | |
239 | pub repository: Repository, | |
240 | pub owner: User, | |
241 | pub visibility: RepositoryVisibility, | |
242 | pub description: Option<String>, | |
243 | pub last_commit: Option<Commit>, | |
244 | } | |
245 | ||
246 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
247 | pub struct IssueLabel { | |
248 | pub name: String, | |
249 | pub color: String, | |
250 | } | |
251 | ||
252 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
253 | pub struct RepositoryIssue { | |
254 | pub author: User, | |
255 | pub id: u64, | |
256 | pub title: String, | |
257 | pub contents: String, | |
258 | pub labels: Vec<IssueLabel>, | |
259 | } |
giterated-models/src/repository/operations.rs
@@ -0,0 +1,111 @@ | ||
1 | use serde::{Deserialize, Serialize}; | |
2 | ||
3 | use crate::{ | |
4 | error::{OperationError, RepositoryError}, | |
5 | object::Object, | |
6 | object_backend::ObjectBackend, | |
7 | operation::GiteratedOperation, | |
8 | }; | |
9 | ||
10 | use super::{IssueLabel, Repository, RepositoryIssue, RepositoryTreeEntry}; | |
11 | ||
12 | /// A request to get a repository's information. | |
13 | /// | |
14 | /// # Authentication | |
15 | /// - Instance Authentication | |
16 | /// - Validate request against the `issued_for` public key | |
17 | /// - Validate User token against the user's instance's public key | |
18 | /// # Authorization | |
19 | /// - User Authorization | |
20 | /// - Potential User permissions checks | |
21 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
22 | pub struct RepositoryIssuesCountRequest; | |
23 | ||
24 | impl GiteratedOperation<Repository> for RepositoryIssuesCountRequest { | |
25 | type Success = u64; | |
26 | type Failure = RepositoryError; | |
27 | } | |
28 | ||
29 | /// A request to get a repository's issues count. | |
30 | /// | |
31 | /// # Authentication | |
32 | /// - Instance Authentication | |
33 | /// - Validate request against the `issued_for` public key | |
34 | /// - Validate User token against the user's instance's public key | |
35 | /// # Authorization | |
36 | /// - User Authorization | |
37 | /// - Potential User permissions checks | |
38 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
39 | pub struct RepositoryIssueLabelsRequest; | |
40 | ||
41 | impl GiteratedOperation<Repository> for RepositoryIssueLabelsRequest { | |
42 | type Success = Vec<IssueLabel>; | |
43 | type Failure = RepositoryError; | |
44 | } | |
45 | ||
46 | /// A request to get a repository's issue labels. | |
47 | /// | |
48 | /// # Authentication | |
49 | /// - Instance Authentication | |
50 | /// - Validate request against the `issued_for` public key | |
51 | /// - Validate User token against the user's instance's public key | |
52 | /// # Authorization | |
53 | /// - User Authorization | |
54 | /// - Potential User permissions checks | |
55 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
56 | pub struct RepositoryIssuesRequest; | |
57 | ||
58 | impl GiteratedOperation<Repository> for RepositoryIssuesRequest { | |
59 | type Success = Vec<RepositoryIssue>; | |
60 | type Failure = RepositoryError; | |
61 | } | |
62 | ||
63 | /// A request to inspect the tree of a repository. | |
64 | /// | |
65 | /// # Authentication | |
66 | /// - Instance Authentication | |
67 | /// - Validate request against the `issued_for` public key | |
68 | /// - Validate User token against the user's instance's public key | |
69 | /// # Authorization | |
70 | /// - User Authorization | |
71 | /// - Potential User permissions checks | |
72 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
73 | pub struct RepositoryFileInspectRequest { | |
74 | pub path: RepositoryTreeEntry, | |
75 | } | |
76 | ||
77 | impl GiteratedOperation<Repository> for RepositoryFileInspectRequest { | |
78 | type Success = Vec<RepositoryTreeEntry>; | |
79 | type Failure = RepositoryError; | |
80 | } | |
81 | ||
82 | impl<B: ObjectBackend + std::fmt::Debug> Object<'_, Repository, B> { | |
83 | pub async fn issues_count(&mut self) -> Result<u64, OperationError<RepositoryError>> { | |
84 | self.request::<RepositoryIssuesCountRequest>(RepositoryIssuesCountRequest) | |
85 | .await | |
86 | } | |
87 | ||
88 | pub async fn issue_labels( | |
89 | &mut self, | |
90 | ) -> Result<Vec<IssueLabel>, OperationError<RepositoryError>> { | |
91 | self.request::<RepositoryIssueLabelsRequest>(RepositoryIssueLabelsRequest) | |
92 | .await | |
93 | } | |
94 | ||
95 | pub async fn issues( | |
96 | &mut self, | |
97 | ) -> Result<Vec<RepositoryIssue>, OperationError<RepositoryError>> { | |
98 | self.request::<RepositoryIssuesRequest>(RepositoryIssuesRequest) | |
99 | .await | |
100 | } | |
101 | ||
102 | pub async fn inspect_files( | |
103 | &mut self, | |
104 | entry: &RepositoryTreeEntry, | |
105 | ) -> Result<Vec<RepositoryTreeEntry>, OperationError<RepositoryError>> { | |
106 | self.request::<RepositoryFileInspectRequest>(RepositoryFileInspectRequest { | |
107 | path: entry.clone(), | |
108 | }) | |
109 | .await | |
110 | } | |
111 | } |
giterated-models/src/repository/settings.rs
@@ -0,0 +1,21 @@ | ||
1 | use serde::{Deserialize, Serialize}; | |
2 | ||
3 | use crate::settings::Setting; | |
4 | ||
5 | #[derive(Debug, Serialize, Deserialize)] | |
6 | pub struct RepositoryDescription(pub String); | |
7 | ||
8 | impl Setting for RepositoryDescription { | |
9 | fn name() -> &'static str { | |
10 | "Repository Description" | |
11 | } | |
12 | } | |
13 | ||
14 | #[derive(Debug, Serialize, Deserialize)] | |
15 | pub struct RepositoryVisibilitySetting(pub String); | |
16 | ||
17 | impl Setting for RepositoryVisibilitySetting { | |
18 | fn name() -> &'static str { | |
19 | "Repository Visibility" | |
20 | } | |
21 | } |
giterated-models/src/repository/values.rs
@@ -0,0 +1,45 @@ | ||
1 | use serde::{Deserialize, Serialize}; | |
2 | ||
3 | use crate::{settings::Setting, value::GiteratedObjectValue}; | |
4 | ||
5 | use super::{Repository, RepositoryVisibility}; | |
6 | ||
7 | // pub struct RepositorySetting<V: GiteratedObjectValue>(pub V); | |
8 | ||
9 | // impl<O: GiteratedObject, V: GiteratedObjectValue<Object = O> + Send> GiteratedOperation<O> | |
10 | // for RepositorySetting<V> | |
11 | // { | |
12 | // fn operation_name(&self) -> &'static str { | |
13 | // "setting_get" | |
14 | // } | |
15 | // type Success = V; | |
16 | // type Failure = GetValueError; | |
17 | // } | |
18 | ||
19 | #[derive(Debug, Hash, Clone, PartialEq, Eq, Serialize, Deserialize)] | |
20 | pub struct Description(pub String); | |
21 | ||
22 | impl GiteratedObjectValue for Description { | |
23 | type Object = Repository; | |
24 | ||
25 | fn value_name() -> &'static str { | |
26 | "description" | |
27 | } | |
28 | } | |
29 | ||
30 | impl Setting for Description { | |
31 | fn name() -> &'static str { | |
32 | "description" | |
33 | } | |
34 | } | |
35 | ||
36 | #[derive(Debug, Hash, Clone, PartialEq, Eq, Serialize, Deserialize)] | |
37 | pub struct Visibility(pub RepositoryVisibility); | |
38 | ||
39 | impl GiteratedObjectValue for Visibility { | |
40 | type Object = Repository; | |
41 | ||
42 | fn value_name() -> &'static str { | |
43 | "visibility" | |
44 | } | |
45 | } |
giterated-models/src/settings/mod.rs
@@ -0,0 +1,18 @@ | ||
1 | mod operations; | |
2 | ||
3 | pub use operations::*; | |
4 | use serde::{de::DeserializeOwned, Deserialize, Serialize}; | |
5 | use serde_json::Value; | |
6 | ||
7 | pub trait Setting: Serialize + DeserializeOwned { | |
8 | fn name() -> &'static str; | |
9 | } | |
10 | ||
11 | #[derive(Debug, Clone, Serialize, Deserialize)] | |
12 | pub struct AnySetting(pub Value); | |
13 | ||
14 | impl Setting for AnySetting { | |
15 | fn name() -> &'static str { | |
16 | "any" | |
17 | } | |
18 | } |
giterated-models/src/settings/operations.rs
@@ -0,0 +1,48 @@ | ||
1 | use std::{fmt::Debug, marker::PhantomData}; | |
2 | ||
3 | use serde::{de::DeserializeOwned, Deserialize, Serialize}; | |
4 | use thiserror::Error; | |
5 | ||
6 | use crate::{object::GiteratedObject, operation::GiteratedOperation}; | |
7 | ||
8 | use super::Setting; | |
9 | ||
10 | #[derive(Serialize, Deserialize, Debug, Clone)] | |
11 | pub struct GetSetting<S: Setting + std::fmt::Debug + Clone> { | |
12 | pub setting_name: String, | |
13 | pub _marker: PhantomData<S>, | |
14 | } | |
15 | ||
16 | impl<O: GiteratedObject, S: Setting + Send + DeserializeOwned + Debug + Clone> GiteratedOperation<O> | |
17 | for GetSetting<S> | |
18 | { | |
19 | fn operation_name() -> &'static str { | |
20 | "get_setting" | |
21 | } | |
22 | ||
23 | type Success = S; | |
24 | ||
25 | type Failure = GetSettingError; | |
26 | } | |
27 | ||
28 | #[derive(Error, Debug, Serialize, Deserialize)] | |
29 | pub enum GetSettingError {} | |
30 | #[derive(Serialize, Deserialize, Debug, Clone)] | |
31 | #[serde(bound(deserialize = "S: Setting"))] | |
32 | pub struct SetSetting<S: Setting> { | |
33 | pub setting_name: String, | |
34 | pub value: S, | |
35 | } | |
36 | ||
37 | impl<O: GiteratedObject, S: Setting + Send> GiteratedOperation<O> for SetSetting<S> { | |
38 | fn operation_name() -> &'static str { | |
39 | "set_setting" | |
40 | } | |
41 | ||
42 | type Success = (); | |
43 | ||
44 | type Failure = SetSettingError; | |
45 | } | |
46 | ||
47 | #[derive(Error, Debug, Serialize, Deserialize)] | |
48 | pub enum SetSettingError {} |
giterated-models/src/user/mod.rs
@@ -0,0 +1,97 @@ | ||
1 | mod operations; | |
2 | mod settings; | |
3 | mod values; | |
4 | ||
5 | use std::{ | |
6 | fmt::{Display, Formatter}, | |
7 | str::FromStr, | |
8 | }; | |
9 | ||
10 | pub use operations::*; | |
11 | use secrecy::{CloneableSecret, DebugSecret, SerializableSecret, Zeroize}; | |
12 | use serde::{Deserialize, Serialize}; | |
13 | pub use settings::*; | |
14 | pub use values::*; | |
15 | ||
16 | use crate::{instance::Instance, object::GiteratedObject}; | |
17 | ||
18 | /// A user, defined by its username and instance. | |
19 | /// | |
20 | /// # Textual Format | |
21 | /// A user's textual reference is defined as: | |
22 | /// | |
23 | /// `{username: String}:{instance: Instance}` | |
24 | /// | |
25 | /// # Examples | |
26 | /// For the user with the username `barson` and the instance `giterated.dev`, | |
27 | /// the following [`User`] initialization would be valid: | |
28 | /// | |
29 | /// ``` | |
30 | /// let user = User { | |
31 | /// username: String::from("barson"), | |
32 | /// instance: Instance::from_str("giterated.dev").unwrap() | |
33 | /// }; | |
34 | /// | |
35 | /// // This is correct | |
36 | /// assert_eq!(User::from_str("barson:giterated.dev").unwrap(), user); | |
37 | /// ``` | |
38 | #[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] | |
39 | pub struct User { | |
40 | pub username: String, | |
41 | pub instance: Instance, | |
42 | } | |
43 | ||
44 | impl GiteratedObject for User { | |
45 | fn object_name() -> &'static str { | |
46 | "user" | |
47 | } | |
48 | ||
49 | fn from_object_str(object_str: &str) -> Result<Self, anyhow::Error> { | |
50 | Ok(User::from_str(object_str).unwrap()) | |
51 | } | |
52 | } | |
53 | ||
54 | impl Display for User { | |
55 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { | |
56 | write!(f, "{}:{}", self.username, self.instance.url) | |
57 | } | |
58 | } | |
59 | ||
60 | impl From<String> for User { | |
61 | fn from(user_string: String) -> Self { | |
62 | User::from_str(&user_string).unwrap() | |
63 | } | |
64 | } | |
65 | ||
66 | impl FromStr for User { | |
67 | type Err = UserParseError; | |
68 | ||
69 | fn from_str(s: &str) -> Result<Self, Self::Err> { | |
70 | if s.contains('/') { | |
71 | return Err(UserParseError); | |
72 | } | |
73 | ||
74 | let mut colon_split = s.split(':'); | |
75 | let username = colon_split.next().unwrap().to_string(); | |
76 | let instance = Instance::from_str(colon_split.next().unwrap()).unwrap(); | |
77 | ||
78 | Ok(Self { username, instance }) | |
79 | } | |
80 | } | |
81 | ||
82 | #[derive(thiserror::Error, Debug)] | |
83 | #[error("failed to parse user")] | |
84 | pub struct UserParseError; | |
85 | ||
86 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
87 | pub struct Password(pub String); | |
88 | ||
89 | impl Zeroize for Password { | |
90 | fn zeroize(&mut self) { | |
91 | self.0.zeroize() | |
92 | } | |
93 | } | |
94 | ||
95 | impl SerializableSecret for Password {} | |
96 | impl CloneableSecret for Password {} | |
97 | impl DebugSecret for Password {} |
giterated-models/src/user/operations.rs
@@ -0,0 +1,36 @@ | ||
1 | use serde::{Deserialize, Serialize}; | |
2 | ||
3 | use crate::{ | |
4 | error::{OperationError, UserError}, | |
5 | instance::Instance, | |
6 | object::Object, | |
7 | object_backend::ObjectBackend, | |
8 | operation::GiteratedOperation, | |
9 | repository::Repository, | |
10 | }; | |
11 | ||
12 | use super::User; | |
13 | ||
14 | #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] | |
15 | pub struct UserRepositoriesRequest { | |
16 | pub instance: Instance, | |
17 | pub user: User, | |
18 | } | |
19 | ||
20 | impl GiteratedOperation<User> for UserRepositoriesRequest { | |
21 | type Success = Vec<Repository>; | |
22 | type Failure = UserError; | |
23 | } | |
24 | ||
25 | impl<B: ObjectBackend + std::fmt::Debug> Object<'_, User, B> { | |
26 | pub async fn repositories( | |
27 | &mut self, | |
28 | instance: &Instance, | |
29 | ) -> Result<Vec<Repository>, OperationError<UserError>> { | |
30 | self.request::<UserRepositoriesRequest>(UserRepositoriesRequest { | |
31 | instance: instance.clone(), | |
32 | user: self.inner.clone(), | |
33 | }) | |
34 | .await | |
35 | } | |
36 | } |
giterated-models/src/user/settings.rs
@@ -0,0 +1,30 @@ | ||
1 | use serde::{Deserialize, Serialize}; | |
2 | ||
3 | use crate::settings::Setting; | |
4 | ||
5 | #[derive(Debug, Serialize, Deserialize)] | |
6 | pub struct UserBio(pub String); | |
7 | ||
8 | impl Setting for UserBio { | |
9 | fn name() -> &'static str { | |
10 | "Bio" | |
11 | } | |
12 | } | |
13 | ||
14 | #[derive(Debug, Serialize, Deserialize, Clone)] | |
15 | pub struct UserDisplayName(pub String); | |
16 | ||
17 | impl Setting for UserDisplayName { | |
18 | fn name() -> &'static str { | |
19 | "Display Name" | |
20 | } | |
21 | } | |
22 | ||
23 | #[derive(Debug, Serialize, Deserialize)] | |
24 | pub struct UserDisplayImage(pub String); | |
25 | ||
26 | impl Setting for UserDisplayImage { | |
27 | fn name() -> &'static str { | |
28 | "Profile Image" | |
29 | } | |
30 | } |
giterated-models/src/user/values.rs
@@ -0,0 +1,27 @@ | ||
1 | use serde::{Deserialize, Serialize}; | |
2 | ||
3 | use crate::value::GiteratedObjectValue; | |
4 | ||
5 | use super::User; | |
6 | ||
7 | #[derive(Debug, Hash, Clone, PartialEq, Eq, Serialize, Deserialize)] | |
8 | pub struct Bio(pub String); | |
9 | ||
10 | impl GiteratedObjectValue for Bio { | |
11 | type Object = User; | |
12 | ||
13 | fn value_name() -> &'static str { | |
14 | "bio" | |
15 | } | |
16 | } | |
17 | ||
18 | #[derive(Debug, Hash, Clone, PartialEq, Eq, Serialize, Deserialize)] | |
19 | pub struct DisplayName(pub String); | |
20 | ||
21 | impl GiteratedObjectValue for DisplayName { | |
22 | type Object = User; | |
23 | ||
24 | fn value_name() -> &'static str { | |
25 | "display_name" | |
26 | } | |
27 | } |
giterated-models/src/value.rs
@@ -0,0 +1,53 @@ | ||
1 | use std::{fmt::Debug, marker::PhantomData}; | |
2 | ||
3 | use serde::{de::DeserializeOwned, Deserialize, Serialize}; | |
4 | use serde_json::Value; | |
5 | ||
6 | use crate::{error::GetValueError, object::GiteratedObject, operation::GiteratedOperation}; | |
7 | ||
8 | pub trait GiteratedObjectValue: Serialize + DeserializeOwned { | |
9 | type Object: GiteratedObject; | |
10 | ||
11 | fn value_name() -> &'static str; | |
12 | } | |
13 | ||
14 | #[derive(Serialize, Deserialize, Debug, Clone)] | |
15 | pub struct GetValue<V: GiteratedObjectValue> { | |
16 | pub value_name: String, | |
17 | pub(crate) _marker: PhantomData<V>, | |
18 | } | |
19 | ||
20 | impl<O: GiteratedObject + Send, V: GiteratedObjectValue<Object = O> + Send> GiteratedOperation<O> | |
21 | for GetValue<V> | |
22 | { | |
23 | fn operation_name() -> &'static str { | |
24 | "get_value" | |
25 | } | |
26 | type Success = V; | |
27 | type Failure = GetValueError; | |
28 | } | |
29 | ||
30 | #[derive(Debug, Clone, Deserialize, Serialize)] | |
31 | #[serde(transparent)] | |
32 | pub struct AnyValue<O> { | |
33 | value: Value, | |
34 | #[serde(skip)] | |
35 | _marker: PhantomData<O>, | |
36 | } | |
37 | ||
38 | impl<O: GiteratedObject> AnyValue<O> { | |
39 | pub unsafe fn from_raw(value: Value) -> Self { | |
40 | Self { | |
41 | value, | |
42 | _marker: Default::default(), | |
43 | } | |
44 | } | |
45 | } | |
46 | ||
47 | impl<O: GiteratedObject> GiteratedObjectValue for AnyValue<O> { | |
48 | type Object = O; | |
49 | ||
50 | fn value_name() -> &'static str { | |
51 | todo!() | |
52 | } | |
53 | } |