diff --git a/giterated-daemon/src/authentication.rs b/giterated-daemon/src/authentication.rs index 88caab9..ec32dee 100644 --- a/giterated-daemon/src/authentication.rs +++ b/giterated-daemon/src/authentication.rs @@ -1,9 +1,10 @@ use anyhow::Error; -use giterated_models::model::{ - authenticated::{UserAuthenticationToken, UserTokenMetadata}, - instance::Instance, - user::User, -}; +use giterated_models::authenticated::{UserAuthenticationToken, UserTokenMetadata}; + +use giterated_models::instance::Instance; + +use giterated_models::user::User; + use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, TokenData, Validation}; use std::{sync::Arc, time::SystemTime}; use tokio::{fs::File, io::AsyncReadExt, sync::Mutex}; diff --git a/giterated-daemon/src/authorization.rs b/giterated-daemon/src/authorization.rs new file mode 100644 index 0000000..4376db8 --- /dev/null +++ b/giterated-daemon/src/authorization.rs @@ -0,0 +1,134 @@ +use crate::connection::wrapper::ConnectionState; +use giterated_models::error::OperationError; + +use giterated_models::object::GiteratedObject; + +use giterated_models::repository::{ + Repository, RepositoryFileInspectRequest, RepositoryIssueLabelsRequest, + RepositoryIssuesCountRequest, RepositoryIssuesRequest, +}; + +use giterated_models::user::User; + +use giterated_models::{ + object::ObjectRequest, + settings::{SetSetting, Setting}, + value::{GetValue, GiteratedObjectValue}, +}; +#[async_trait::async_trait] +pub trait AuthorizedOperation { + /// Authorizes the operation, returning whether the operation was + /// authorized or not. + async fn authorize( + &self, + authenticating_user: Option<&User>, + object: &O, + state: &mut S, + ) -> Result>; +} + +#[async_trait::async_trait] +impl AuthorizedOperation for SetSetting { + async fn authorize( + &self, + _authenticating_user: Option<&User>, + _object: &User, + _state: &mut ConnectionState, + ) -> Result> { + // TODO + Ok(true) + } +} + +#[async_trait::async_trait] +impl AuthorizedOperation for SetSetting { + async fn authorize( + &self, + _authenticating_user: Option<&User>, + _object: &Repository, + _state: &mut ConnectionState, + ) -> Result> { + // TODO + Ok(true) + } +} + +#[async_trait::async_trait] +impl AuthorizedOperation + for GetValue +{ + async fn authorize( + &self, + _authenticating_user: Option<&User>, + _object: &Repository, + _state: &mut ConnectionState, + ) -> Result> { + // TODO + Ok(true) + } +} + +#[async_trait::async_trait] +impl AuthorizedOperation for ObjectRequest { + async fn authorize( + &self, + _authenticating_user: Option<&User>, + _object: &Repository, + _state: &mut ConnectionState, + ) -> Result> { + // TODO + Ok(true) + } +} + +#[async_trait::async_trait] +impl AuthorizedOperation for RepositoryFileInspectRequest { + async fn authorize( + &self, + _authenticating_user: Option<&User>, + _object: &Repository, + _state: &mut ConnectionState, + ) -> Result> { + // TODO + Ok(true) + } +} + +#[async_trait::async_trait] +impl AuthorizedOperation for RepositoryIssuesRequest { + async fn authorize( + &self, + _authenticating_user: Option<&User>, + _object: &Repository, + _state: &mut ConnectionState, + ) -> Result> { + // TODO + Ok(true) + } +} + +#[async_trait::async_trait] +impl AuthorizedOperation for RepositoryIssueLabelsRequest { + async fn authorize( + &self, + _authenticating_user: Option<&User>, + _object: &Repository, + _state: &mut ConnectionState, + ) -> Result> { + // TODO + Ok(true) + } +} + +#[async_trait::async_trait] +impl AuthorizedOperation for RepositoryIssuesCountRequest { + async fn authorize( + &self, + _authenticating_user: Option<&User>, + _object: &Repository, + _state: &mut ConnectionState, + ) -> Result> { + // TODO + Ok(true) + } +} diff --git a/giterated-daemon/src/backend/git.rs b/giterated-daemon/src/backend/git.rs index 2010954..c75935b 100644 --- a/giterated-daemon/src/backend/git.rs +++ b/giterated-daemon/src/backend/git.rs @@ -2,25 +2,17 @@ use anyhow::Error; use async_trait::async_trait; use futures_util::StreamExt; -use giterated_models::{ - model::{ - instance::Instance, - repository::{ - Commit, IssueLabel, Repository, RepositoryIssue, RepositorySummary, - RepositoryTreeEntry, RepositoryVisibility, - }, - settings::{AnySetting, RepositoryDescription, RepositoryVisibilitySetting, Setting}, - user::{User, UserParseError}, - }, - operation::{ - instance::RepositoryCreateRequest, - repository::{ - RepositoryFileInspectRequest, RepositoryIssueLabelsRequest, - RepositoryIssuesCountRequest, RepositoryIssuesRequest, - }, - }, - values::AnyValue, +use giterated_models::instance::{Instance, RepositoryCreateRequest}; + +use giterated_models::repository::{ + Commit, IssueLabel, Repository, RepositoryDescription, RepositoryFileInspectRequest, + RepositoryIssue, RepositoryIssueLabelsRequest, RepositoryIssuesCountRequest, + RepositoryIssuesRequest, RepositorySummary, RepositoryTreeEntry, RepositoryVisibility, + RepositoryVisibilitySetting, }; +use giterated_models::settings::{AnySetting, Setting}; +use giterated_models::user::{User, UserParseError}; +use giterated_models::value::AnyValue; use serde_json::Value; use sqlx::{Either, PgPool}; use std::{ diff --git a/giterated-daemon/src/backend/mod.rs b/giterated-daemon/src/backend/mod.rs index ef3ffca..ff3ee91 100644 --- a/giterated-daemon/src/backend/mod.rs +++ b/giterated-daemon/src/backend/mod.rs @@ -9,25 +9,20 @@ use async_trait::async_trait; use serde_json::Value; use crate::backend::git::GitBackendError; -use giterated_models::{ - model::{ - authenticated::UserAuthenticationToken, - instance::Instance, - repository::{ - IssueLabel, Repository, RepositoryIssue, RepositorySummary, RepositoryTreeEntry, - }, - settings::AnySetting, - user::User, - }, - operation::{ - instance::{AuthenticationTokenRequest, RegisterAccountRequest, RepositoryCreateRequest}, - repository::{ - RepositoryFileInspectRequest, RepositoryIssueLabelsRequest, - RepositoryIssuesCountRequest, RepositoryIssuesRequest, - }, - }, - values::AnyValue, +use giterated_models::authenticated::UserAuthenticationToken; + +use giterated_models::instance::{ + AuthenticationTokenRequest, Instance, RegisterAccountRequest, RepositoryCreateRequest, +}; + +use giterated_models::repository::{ + IssueLabel, Repository, RepositoryFileInspectRequest, RepositoryIssue, + RepositoryIssueLabelsRequest, RepositoryIssuesCountRequest, RepositoryIssuesRequest, + RepositorySummary, RepositoryTreeEntry, }; +use giterated_models::settings::AnySetting; +use giterated_models::user::User; +use giterated_models::value::AnyValue; #[async_trait] pub trait RepositoryBackend { diff --git a/giterated-daemon/src/backend/settings.rs b/giterated-daemon/src/backend/settings.rs index 1e03bdb..92216e5 100644 --- a/giterated-daemon/src/backend/settings.rs +++ b/giterated-daemon/src/backend/settings.rs @@ -1,6 +1,8 @@ use anyhow::Error; -use giterated_models::model::{repository::Repository, settings::AnySetting, user::User}; +use giterated_models::repository::Repository; +use giterated_models::settings::AnySetting; +use giterated_models::user::User; use sqlx::PgPool; @@ -12,73 +14,62 @@ pub struct DatabaseSettings { #[async_trait::async_trait] impl MetadataBackend for DatabaseSettings { - async fn user_get(&mut self, _user: &User, _name: &str) -> Result { - todo!() + async fn user_get(&mut self, user: &User, name: &str) -> Result { + let row = sqlx::query_as!( + UserSettingRow, + "SELECT * FROM user_settings WHERE username = $1 AND name = $2", + user.username, + name + ) + .fetch_one(&self.pg_pool) + .await?; + + let setting = serde_json::from_str(&row.value)?; + + Ok(setting) } async fn user_write( &mut self, - _user: &User, - _name: &str, - _value: AnySetting, + user: &User, + name: &str, + value: AnySetting, ) -> Result<(), Error> { - // for (name, value) in settings { - // sqlx::query!("INSERT INTO user_settings VALUES ($1, $2, $3) ON CONFLICT (username, name) DO UPDATE SET value = $3", - // user.username, name, value) - // .execute(&self.pg_pool).await?; - // } + sqlx::query!("INSERT INTO user_settings VALUES ($1, $2, $3) ON CONFLICT (username, name) DO UPDATE SET value = $3", + user.username, name, serde_json::to_string(&value)?) + .execute(&self.pg_pool).await?; - // Ok(()) - - todo!() + Ok(()) } async fn repository_get( &mut self, - _repository: &Repository, - _name: &str, + repository: &Repository, + name: &str, ) -> Result { - // let settings = sqlx::query_as!( - // RepositorySettingRow, - // r#"SELECT * FROM repository_settings WHERE repository = $1"#, - // repository.to_string() - // ) - // .fetch_many(&self.pg_pool) - // .filter_map(|result| async move { - // if let Ok(Either::Right(row)) = result { - // Some(row) - // } else { - // None - // } - // }) - // .filter_map(|row| async move { - // if let Ok(value) = serde_json::from_str(&row.value) { - // Some((row.name, value)) - // } else { - // None - // } - // }) - // .collect::>() - // .await; + let row = sqlx::query_as!( + RepositorySettingRow, + "SELECT * FROM repository_settings WHERE repository = $1 AND name = $2", + format!("{}/{}", repository.owner, repository.name), + name + ) + .fetch_one(&self.pg_pool) + .await?; - // Ok(settings) + let setting = serde_json::from_str(&row.value)?; - todo!() + Ok(setting) } async fn repository_write( &mut self, - _repository: &Repository, - _name: &str, - _value: AnySetting, + repository: &Repository, + name: &str, + value: AnySetting, ) -> Result<(), Error> { - // for (name, value) in settings { - // sqlx::query!("INSERT INTO repository_settings VALUES ($1, $2, $3) ON CONFLICT (repository, name) DO UPDATE SET value = $3", - // repository.to_string(), name, value) - // .execute(&self.pg_pool).await?; - // } - - // Ok(()) + sqlx::query!("INSERT INTO repository_settings VALUES ($1, $2, $3) ON CONFLICT (repository, name) DO UPDATE SET value = $3", + format!("{}/{}", repository.owner, repository.name), name, serde_json::to_string(&value)?) + .execute(&self.pg_pool).await?; - todo!() + Ok(()) } } diff --git a/giterated-daemon/src/backend/user.rs b/giterated-daemon/src/backend/user.rs index 81de57f..cc7cc8e 100644 --- a/giterated-daemon/src/backend/user.rs +++ b/giterated-daemon/src/backend/user.rs @@ -1,20 +1,17 @@ -use std::sync::Arc; - use anyhow::Error; +use giterated_models::authenticated::UserAuthenticationToken; + +use giterated_models::instance::{AuthenticationTokenRequest, Instance, RegisterAccountRequest}; + +use giterated_models::settings::{AnySetting, Setting}; +use giterated_models::user::{User, UserBio, UserDisplayName, UserParseError}; +use giterated_models::value::AnyValue; +use std::sync::Arc; use aes_gcm::{aead::Aead, AeadCore, Aes256Gcm, Key, KeyInit}; use argon2::{password_hash::SaltString, Argon2, PasswordHash, PasswordHasher, PasswordVerifier}; use base64::{engine::general_purpose::STANDARD, Engine as _}; -use giterated_models::{ - model::{ - authenticated::UserAuthenticationToken, - instance::Instance, - settings::{AnySetting, Setting, UserBio, UserDisplayName}, - user::{User, UserParseError}, - }, - operation::instance::{AuthenticationTokenRequest, RegisterAccountRequest}, - values::AnyValue, -}; + use rsa::{ pkcs8::{EncodePrivateKey, EncodePublicKey}, rand_core::OsRng, diff --git a/giterated-daemon/src/cache_backend.rs b/giterated-daemon/src/cache_backend.rs index b4a784f..a236d9f 100644 --- a/giterated-daemon/src/cache_backend.rs +++ b/giterated-daemon/src/cache_backend.rs @@ -1,7 +1,9 @@ -use giterated_models::{ - error::OperationError, - operation::{GiteratedObject, GiteratedOperation, Object, ObjectBackend, ObjectRequestError}, -}; +use giterated_models::error::OperationError; + +use giterated_models::object::{GiteratedObject, Object, ObjectRequestError}; +use giterated_models::object_backend::ObjectBackend; +use giterated_models::operation::GiteratedOperation; + use std::fmt::Debug; #[derive(Clone, Debug)] diff --git a/giterated-daemon/src/connection.rs b/giterated-daemon/src/connection.rs index 2e43434..222e5f5 100644 --- a/giterated-daemon/src/connection.rs +++ b/giterated-daemon/src/connection.rs @@ -1,14 +1,12 @@ -// pub mod authentication; -// pub mod forwarded; -// pub mod handshake; -// pub mod repository; -// pub mod user; pub mod wrapper; +use giterated_models::instance::Instance; + +use giterated_models::instance::InstanceMeta; + use std::{any::type_name, collections::HashMap}; use anyhow::Error; -use giterated_models::model::instance::{Instance, InstanceMeta}; use serde::{de::DeserializeOwned, Serialize}; use tokio::{net::TcpStream, task::JoinHandle}; use tokio_tungstenite::WebSocketStream; diff --git a/giterated-daemon/src/connection/wrapper.rs b/giterated-daemon/src/connection/wrapper.rs index 99f87fd..c4b762a 100644 --- a/giterated-daemon/src/connection/wrapper.rs +++ b/giterated-daemon/src/connection/wrapper.rs @@ -5,11 +5,15 @@ use std::{ use anyhow::Error; use futures_util::{SinkExt, StreamExt}; + +use giterated_models::instance::Instance; + +use giterated_models::object_backend::ObjectBackend; + use giterated_models::{ - model::{authenticated::AuthenticatedPayload, instance::Instance}, - operation::{AnyObject, AnyOperation, GiteratedMessage, ObjectBackend}, + authenticated::AuthenticatedPayload, message::GiteratedMessage, object::AnyObject, + operation::AnyOperation, }; - use serde::Serialize; use tokio::{net::TcpStream, sync::Mutex}; diff --git a/giterated-daemon/src/database_backend/handler.rs b/giterated-daemon/src/database_backend/handler.rs index 8730e07..10caea2 100644 --- a/giterated-daemon/src/database_backend/handler.rs +++ b/giterated-daemon/src/database_backend/handler.rs @@ -3,9 +3,12 @@ use std::{collections::HashMap, pin::Pin, sync::Arc}; use futures_util::{future::BoxFuture, Future, FutureExt}; use giterated_models::{ error::{GetValueError, OperationError}, - model::{repository::Repository, settings::AnySetting, user::User}, - operation::{AnyObject, AnyOperation, GetValue, GiteratedObject, GiteratedOperation}, - values::{AnyValue, GetSetting, GetSettingError, SetSetting, SetSettingError}, + object::{AnyObject, GiteratedObject}, + operation::{AnyOperation, GiteratedOperation}, + repository::Repository, + settings::{AnySetting, GetSetting, GetSettingError, SetSetting, SetSettingError}, + user::User, + value::{AnyValue, GetValue}, }; use super::DatabaseBackend; diff --git a/giterated-daemon/src/database_backend/mod.rs b/giterated-daemon/src/database_backend/mod.rs index a49b169..ac4fa5c 100644 --- a/giterated-daemon/src/database_backend/mod.rs +++ b/giterated-daemon/src/database_backend/mod.rs @@ -2,11 +2,13 @@ pub mod handler; use std::{str::FromStr, sync::Arc}; -use giterated_models::{ - error::OperationError, - model::{instance::Instance, repository::Repository, user::User}, - operation::{GiteratedObject, GiteratedOperation, Object, ObjectBackend, ObjectRequestError}, -}; +use giterated_models::error::OperationError; +use giterated_models::instance::Instance; +use giterated_models::object::{GiteratedObject, Object, ObjectRequestError}; +use giterated_models::object_backend::ObjectBackend; +use giterated_models::operation::GiteratedOperation; +use giterated_models::repository::Repository; +use giterated_models::user::User; use std::fmt::Debug; use tokio::sync::Mutex; @@ -182,27 +184,22 @@ mod test { use std::{str::FromStr, sync::Arc}; use anyhow::Error; - use giterated_models::model::settings::UserDisplayName; - use giterated_models::operation::ObjectBackend; - use giterated_models::values::repository::Description; - use giterated_models::{ - model::{ - authenticated::UserAuthenticationToken, - instance::Instance, - repository::{Repository, RepositorySummary, RepositoryTreeEntry}, - settings::AnySetting, - user::User, - }, - operation::{ - instance::{ - AuthenticationTokenRequest, RegisterAccountRequest, RepositoryCreateRequest, - }, - repository::RepositoryFileInspectRequest, - GiteratedObjectValue, - }, - values::{user::DisplayName, AnyValue}, + + use giterated_models::authenticated::UserAuthenticationToken; + + use giterated_models::instance::{ + AuthenticationTokenRequest, Instance, RegisterAccountRequest, RepositoryCreateRequest, }; + use giterated_models::object_backend::ObjectBackend; + + use giterated_models::repository::{ + Description, Repository, RepositoryFileInspectRequest, RepositorySummary, + RepositoryTreeEntry, + }; + use giterated_models::settings::AnySetting; + use giterated_models::user::{DisplayName, User, UserDisplayName}; + use giterated_models::value::{AnyValue, GiteratedObjectValue}; use serde_json::Value; use tokio::sync::Mutex; diff --git a/giterated-daemon/src/federation/connections.rs b/giterated-daemon/src/federation/connections.rs index 64dc6e1..e41c311 100644 --- a/giterated-daemon/src/federation/connections.rs +++ b/giterated-daemon/src/federation/connections.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use anyhow::Error; use giterated_api::DaemonConnectionPool; -use giterated_models::model::instance::Instance; +use giterated_models::instance::Instance; #[derive(Default)] pub struct InstanceConnections { diff --git a/giterated-daemon/src/keys.rs b/giterated-daemon/src/keys.rs index e30e06b..b57d130 100644 --- a/giterated-daemon/src/keys.rs +++ b/giterated-daemon/src/keys.rs @@ -1,7 +1,8 @@ -use std::collections::HashMap; - use anyhow::Error; -use giterated_models::model::instance::Instance; + +use giterated_models::instance::Instance; + +use std::collections::HashMap; #[derive(Default)] pub struct PublicKeyCache { diff --git a/giterated-daemon/src/lib.rs b/giterated-daemon/src/lib.rs index 2f18859..de61ad9 100644 --- a/giterated-daemon/src/lib.rs +++ b/giterated-daemon/src/lib.rs @@ -3,6 +3,7 @@ use std::str::FromStr; use semver::{Version, VersionReq}; pub mod authentication; +pub mod authorization; pub mod backend; pub mod cache_backend; pub mod connection; diff --git a/giterated-daemon/src/main.rs b/giterated-daemon/src/main.rs index 4aa456b..797d5be 100644 --- a/giterated-daemon/src/main.rs +++ b/giterated-daemon/src/main.rs @@ -8,7 +8,9 @@ use giterated_daemon::{ connection::{self, wrapper::connection_wrapper}, federation::connections::InstanceConnections, }; -use giterated_models::model::instance::Instance; + +use giterated_models::instance::Instance; + use sqlx::{postgres::PgConnectOptions, ConnectOptions, PgPool}; use std::{net::SocketAddr, str::FromStr, sync::Arc}; use tokio::{ diff --git a/giterated-daemon/src/message.rs b/giterated-daemon/src/message.rs index b1434b1..6f281ca 100644 --- a/giterated-daemon/src/message.rs +++ b/giterated-daemon/src/message.rs @@ -1,11 +1,12 @@ -use std::{fmt::Debug, ops::Deref}; - use anyhow::Error; use futures_util::Future; -use giterated_models::model::{ - authenticated::{AuthenticatedPayload, AuthenticationSource, UserTokenMetadata}, - instance::Instance, - user::User, + +use giterated_models::instance::Instance; + +use giterated_models::user::User; + +use giterated_models::authenticated::{ + AuthenticatedPayload, AuthenticationSource, UserTokenMetadata, }; use jsonwebtoken::{decode, Algorithm, DecodingKey, TokenData, Validation}; use rsa::{ @@ -16,6 +17,7 @@ use rsa::{ RsaPublicKey, }; use serde::{de::DeserializeOwned, Serialize}; +use std::{fmt::Debug, ops::Deref}; use crate::connection::wrapper::ConnectionState; diff --git a/giterated-models/src/authenticated.rs b/giterated-models/src/authenticated.rs new file mode 100644 index 0000000..d5f1994 --- /dev/null +++ b/giterated-models/src/authenticated.rs @@ -0,0 +1,201 @@ +use std::fmt::Debug; + +use rsa::{ + pkcs1::DecodeRsaPrivateKey, + pss::SigningKey, + sha2::Sha256, + signature::{RandomizedSigner, SignatureEncoding}, + RsaPrivateKey, +}; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use tracing::info; + +use crate::{ + instance::Instance, + message::GiteratedMessage, + object::{AnyObject, GiteratedObject}, + operation::{AnyOperation, GiteratedOperation}, + user::User, +}; + +#[derive(Debug, Serialize, Deserialize)] +pub struct UserTokenMetadata { + pub user: User, + pub generated_for: Instance, + pub exp: u64, +} + +#[derive(Debug)] +pub struct Authenticated> { + pub source: Vec>, + pub message: GiteratedMessage, +} + +#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)] +pub struct AuthenticatedPayload { + pub source: Vec, + pub object: String, + pub operation: String, + pub payload: Vec, +} + +impl AuthenticatedPayload { + pub fn into_message(self) -> GiteratedMessage { + GiteratedMessage { + object: AnyObject(self.object), + operation: self.operation, + payload: AnyOperation(serde_json::from_slice::(&self.payload).unwrap()), + } + } +} + +pub trait AuthenticationSourceProvider: Debug { + fn authenticate(&self, payload: &Vec) -> AuthenticationSource; +} + +pub trait AuthenticationSourceProviders: Debug { + fn authenticate_all(&self, payload: &Vec) -> Vec; +} + +impl AuthenticationSourceProviders for A +where + A: AuthenticationSourceProvider, +{ + fn authenticate_all(&self, payload: &Vec) -> Vec { + vec![self.authenticate(payload)] + } +} + +impl AuthenticationSourceProviders for (A, B) +where + A: AuthenticationSourceProvider, + B: AuthenticationSourceProvider, +{ + fn authenticate_all(&self, payload: &Vec) -> Vec { + let (first, second) = self; + + vec![first.authenticate(payload), second.authenticate(payload)] + } +} + +impl> Authenticated { + pub fn new(message: GiteratedMessage) -> Self { + Self { + source: vec![], + message, + } + } + + pub fn append_authentication( + &mut self, + authentication: P, + ) { + let message_payload = serde_json::to_vec(&self.message).unwrap(); + + info!( + "Verifying payload: {}", + std::str::from_utf8(&message_payload).unwrap() + ); + + self.source + .push(Box::new(authentication) as Box); + } + + pub fn into_payload(mut self) -> AuthenticatedPayload { + let payload = bincode::serialize(&self.message.payload).unwrap(); + + AuthenticatedPayload { + object: self.message.object.to_string(), + operation: self.message.operation, + source: self + .source + .drain(..) + .map(|provider| provider.as_ref().authenticate(&payload)) + .collect::>(), + payload, + } + } +} + +mod verified {} + +#[derive(Clone, Debug)] +pub struct UserAuthenticator { + pub user: User, + pub token: UserAuthenticationToken, +} + +impl AuthenticationSourceProvider for UserAuthenticator { + fn authenticate(&self, _payload: &Vec) -> AuthenticationSource { + AuthenticationSource::User { + user: self.user.clone(), + token: self.token.clone(), + } + } +} + +#[derive(Debug, Clone)] +pub struct InstanceAuthenticator { + pub instance: Instance, + pub private_key: String, +} + +impl AuthenticationSourceProvider for InstanceAuthenticator { + fn authenticate(&self, payload: &Vec) -> AuthenticationSource { + let mut rng = rand::thread_rng(); + + let private_key = RsaPrivateKey::from_pkcs1_pem(&self.private_key).unwrap(); + let signing_key = SigningKey::::new(private_key); + let signature = signing_key.sign_with_rng(&mut rng, payload); + + AuthenticationSource::Instance { + instance: self.instance.clone(), + // TODO: Actually parse signature from private key + signature: InstanceSignature(signature.to_bytes().into_vec()), + } + } +} + +#[repr(transparent)] +#[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] +pub struct UserAuthenticationToken(String); + +impl From for UserAuthenticationToken { + fn from(value: String) -> Self { + Self(value) + } +} + +impl ToString for UserAuthenticationToken { + fn to_string(&self) -> String { + self.0.clone() + } +} + +impl AsRef for UserAuthenticationToken { + fn as_ref(&self) -> &str { + &self.0 + } +} + +#[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] +pub struct InstanceSignature(Vec); + +impl AsRef<[u8]> for InstanceSignature { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +#[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] +pub enum AuthenticationSource { + User { + user: User, + token: UserAuthenticationToken, + }, + Instance { + instance: Instance, + signature: InstanceSignature, + }, +} diff --git a/giterated-models/src/discovery.rs b/giterated-models/src/discovery.rs new file mode 100644 index 0000000..a1a032e --- /dev/null +++ b/giterated-models/src/discovery.rs @@ -0,0 +1,15 @@ +use serde::{Deserialize, Serialize}; + +use crate::{instance::Instance, repository::Repository}; + +#[derive(Clone, Hash, PartialEq, Eq, Debug, Serialize, Deserialize)] +pub enum DiscoveryItem { + Instance { + instance: Instance, + signature: Vec, + }, + Repository { + repository: Repository, + signature: Vec, + }, +} diff --git a/giterated-models/src/handshake.rs b/giterated-models/src/handshake.rs index 639d9c6..795a5f3 100644 --- a/giterated-models/src/handshake.rs +++ b/giterated-models/src/handshake.rs @@ -1,7 +1,7 @@ use semver::Version; use serde::{Deserialize, Serialize}; -use crate::model::instance::Instance; +use crate::instance::Instance; /// Sent by the initiator of a new inter-daemon connection. #[derive(Clone, Debug, Serialize, Deserialize)] diff --git a/giterated-models/src/instance/mod.rs b/giterated-models/src/instance/mod.rs new file mode 100644 index 0000000..c907ce1 --- /dev/null +++ b/giterated-models/src/instance/mod.rs @@ -0,0 +1,69 @@ +use std::{fmt::Display, str::FromStr}; + +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +mod operations; +mod values; + +pub use operations::*; +pub use values::*; + +use crate::object::GiteratedObject; + +pub struct InstanceMeta { + pub url: String, + pub public_key: String, +} + +/// An instance, defined by the URL it can be reached at. +/// +/// # Textual Format +/// An instance's textual format is its URL. +/// +/// ## Examples +/// For the instance `giterated.dev`, the following [`Instance`] initialization +/// would be valid: +/// +/// ``` +/// let instance = Instance { +/// url: String::from("giterated.dev") +/// }; +/// +/// // This is correct +/// assert_eq!(Instance::from_str("giterated.dev").unwrap(), instance); +/// ``` +#[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] +pub struct Instance { + pub url: String, +} + +impl GiteratedObject for Instance { + fn object_name() -> &'static str { + "instance" + } + + fn from_object_str(object_str: &str) -> Result { + Ok(Instance::from_str(object_str).unwrap()) + } +} + +impl Display for Instance { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.url) + } +} + +impl FromStr for Instance { + type Err = InstanceParseError; + + fn from_str(s: &str) -> Result { + Ok(Self { url: s.to_string() }) + } +} + +#[derive(Debug, Error)] +pub enum InstanceParseError { + #[error("invalid format")] + InvalidFormat, +} diff --git a/giterated-models/src/instance/operations.rs b/giterated-models/src/instance/operations.rs new file mode 100644 index 0000000..7389953 --- /dev/null +++ b/giterated-models/src/instance/operations.rs @@ -0,0 +1,179 @@ +use secrecy::Secret; +use serde::{Deserialize, Serialize}; + +use crate::{ + authenticated::UserAuthenticationToken, + error::{InstanceError, OperationError}, + object::Object, + object_backend::ObjectBackend, + operation::GiteratedOperation, + repository::{Repository, RepositoryVisibility}, + user::{Password, User}, +}; + +use super::Instance; + +/// An account registration request. +/// +/// # Authentication +/// - Instance Authentication +/// - **ONLY ACCEPTED WHEN SAME-INSTANCE** +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct RegisterAccountRequest { + pub username: String, + pub email: Option, + pub password: Secret, +} + +impl GiteratedOperation for RegisterAccountRequest { + type Success = UserAuthenticationToken; + type Failure = InstanceError; +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct RegisterAccountResponse { + pub token: String, +} + +/// An authentication token request. +/// +/// AKA Login Request +/// +/// # Authentication +/// - Instance Authentication +/// - Identifies the Instance to issue the token for +/// # Authorization +/// - Credentials ([`crate::backend::AuthBackend`]-based) +/// - Identifies the User account to issue a token for +/// - Decrypts user private key to issue to +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct AuthenticationTokenRequest { + pub instance: Instance, + pub username: String, + pub password: Secret, +} + +impl GiteratedOperation for AuthenticationTokenRequest { + type Success = UserAuthenticationToken; + type Failure = InstanceError; +} + +/// An authentication token extension request. +/// +/// # Authentication +/// - Instance Authentication +/// - Identifies the Instance to issue the token for +/// - User Authentication +/// - Authenticates the validity of the token +/// # Authorization +/// - Token-based +/// - Validates authorization using token's authenticity +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct TokenExtensionRequest { + pub token: UserAuthenticationToken, +} + +impl GiteratedOperation for TokenExtensionRequest { + type Success = Option; + type Failure = InstanceError; +} + +/// A request to create a repository. +/// +/// # Authentication +/// - Instance Authentication +/// - Used to validate User token `issued_for` +/// - User Authentication +/// - Used to source owning user +/// - Used to authorize user token against user's instance +/// # Authorization +/// - Instance Authorization +/// - Used to authorize action using User token requiring a correct `issued_for` and valid issuance from user's instance +/// - User Authorization +/// - Potential User permissions checks +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct RepositoryCreateRequest { + pub instance: Option, + pub name: String, + pub description: Option, + pub visibility: RepositoryVisibility, + pub default_branch: String, + pub owner: User, +} + +impl GiteratedOperation for RepositoryCreateRequest { + type Success = Repository; + type Failure = InstanceError; +} + +impl Object<'_, Instance, B> { + pub async fn register_account( + &mut self, + email: Option<&str>, + username: &str, + password: &Secret, + ) -> Result> { + self.request::(RegisterAccountRequest { + username: username.to_string(), + email: email.map(|s| s.to_string()), + password: password.clone(), + }) + .await + } + + pub async fn authentication_token( + &mut self, + username: &str, + password: &Secret, + ) -> Result> { + self.request::(AuthenticationTokenRequest { + instance: self.inner.clone(), + username: username.to_string(), + password: password.clone(), + }) + .await + } + + pub async fn authentication_token_for( + &mut self, + instance: &Instance, + username: &str, + password: &Secret, + ) -> Result> { + self.request::(AuthenticationTokenRequest { + instance: instance.clone(), + username: username.to_string(), + password: password.clone(), + }) + .await + } + + pub async fn token_extension( + &mut self, + token: &UserAuthenticationToken, + ) -> Result, OperationError> { + self.request::(TokenExtensionRequest { + token: token.clone(), + }) + .await + } + + pub async fn create_repository( + &mut self, + instance: &Instance, + name: &str, + visibility: RepositoryVisibility, + default_branch: &str, + owner: &User, + ) -> Result> { + self.request::(RepositoryCreateRequest { + instance: Some(instance.clone()), + name: name.to_string(), + description: None, + visibility, + default_branch: default_branch.to_string(), + owner: owner.clone(), + }) + .await + } +} diff --git a/giterated-models/src/instance/values.rs b/giterated-models/src/instance/values.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/giterated-models/src/instance/values.rs @@ -0,0 +1 @@ + diff --git a/giterated-models/src/lib.rs b/giterated-models/src/lib.rs index 87f4104..57e5bc4 100644 --- a/giterated-models/src/lib.rs +++ b/giterated-models/src/lib.rs @@ -1,5 +1,13 @@ +pub mod authenticated; +pub mod discovery; pub mod error; pub mod handshake; -pub mod model; +pub mod instance; +pub mod message; +pub mod object; +pub mod object_backend; pub mod operation; -pub mod values; +pub mod repository; +pub mod settings; +pub mod user; +pub mod value; diff --git a/giterated-models/src/message.rs b/giterated-models/src/message.rs new file mode 100644 index 0000000..e176094 --- /dev/null +++ b/giterated-models/src/message.rs @@ -0,0 +1,69 @@ +use serde::Serialize; + +use crate::{ + object::{AnyObject, GiteratedObject}, + operation::{AnyOperation, GiteratedOperation}, +}; +use std::fmt::Debug; + +#[derive(Serialize)] +#[serde(bound(deserialize = "O: GiteratedObject, V: GiteratedOperation"))] +pub struct GiteratedMessage> { + #[serde(with = "string")] + pub object: O, + pub operation: String, + pub payload: V, +} + +mod string { + use std::fmt::Display; + use std::str::FromStr; + + use serde::{de, Deserialize, Deserializer, Serializer}; + + pub fn serialize(value: &T, serializer: S) -> Result + where + T: Display, + S: Serializer, + { + serializer.collect_str(value) + } + + pub fn deserialize<'de, T, D>(deserializer: D) -> Result + where + T: FromStr, + T::Err: Display, + D: Deserializer<'de>, + { + String::deserialize(deserializer)? + .parse() + .map_err(de::Error::custom) + } +} + +impl GiteratedMessage { + pub fn try_into>( + &self, + ) -> Result, ()> { + let object = O::from_object_str(&self.object.0).map_err(|_| ())?; + let payload = serde_json::from_value::(self.payload.0.clone()).map_err(|_| ())?; + + Ok(GiteratedMessage { + object, + operation: self.operation.clone(), + payload, + }) + } +} + +impl + Debug, O: GiteratedObject + Debug> Debug + for GiteratedMessage +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("GiteratedMessage") + .field("object", &self.object) + .field("operation", &self.operation) + .field("payload", &self.payload) + .finish() + } +} diff --git a/giterated-models/src/model/authenticated.rs b/giterated-models/src/model/authenticated.rs deleted file mode 100644 index 2706c35..0000000 --- a/giterated-models/src/model/authenticated.rs +++ /dev/null @@ -1,216 +0,0 @@ -use std::fmt::Debug; - -use rsa::{ - pkcs1::DecodeRsaPrivateKey, - pss::SigningKey, - sha2::Sha256, - signature::{RandomizedSigner, SignatureEncoding}, - RsaPrivateKey, -}; -use serde::{Deserialize, Serialize}; -use serde_json::Value; -use tracing::info; - -use crate::operation::{ - AnyObject, AnyOperation, GiteratedMessage, GiteratedObject, GiteratedOperation, -}; - -use super::{instance::Instance, user::User, MessageTarget}; - -#[derive(Debug, Serialize, Deserialize)] -pub struct UserTokenMetadata { - pub user: User, - pub generated_for: Instance, - pub exp: u64, -} - -#[derive(Debug)] -pub struct Authenticated> { - pub source: Vec>, - pub message: GiteratedMessage, -} - -#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)] -pub struct AuthenticatedPayload { - pub source: Vec, - pub object: String, - pub operation: String, - pub payload: Vec, -} - -impl AuthenticatedPayload { - pub fn into_message(self) -> GiteratedMessage { - GiteratedMessage { - object: AnyObject(self.object), - operation: self.operation, - payload: AnyOperation(serde_json::from_slice::(&self.payload).unwrap()), - } - } -} - -// impl From> for AuthenticatedPayload { -// fn from(mut value: Authenticated) -> Self { -// let payload = bincode::serialize(&value.message).unwrap(); - -// AuthenticatedPayload { -// target_instance: value.target_instance, -// source: value -// .source -// .drain(..) -// .map(|provider| provider.as_ref().authenticate(&payload)) -// .collect::>(), -// message_type: value.message_type, -// payload, -// } -// } -// } - -pub trait AuthenticationSourceProvider: Debug { - fn authenticate(&self, payload: &Vec) -> AuthenticationSource; -} - -pub trait AuthenticationSourceProviders: Debug { - fn authenticate_all(&self, payload: &Vec) -> Vec; -} - -impl AuthenticationSourceProviders for A -where - A: AuthenticationSourceProvider, -{ - fn authenticate_all(&self, payload: &Vec) -> Vec { - vec![self.authenticate(payload)] - } -} - -impl AuthenticationSourceProviders for (A, B) -where - A: AuthenticationSourceProvider, - B: AuthenticationSourceProvider, -{ - fn authenticate_all(&self, payload: &Vec) -> Vec { - let (first, second) = self; - - vec![first.authenticate(payload), second.authenticate(payload)] - } -} - -impl> Authenticated { - pub fn new(message: GiteratedMessage) -> Self { - Self { - source: vec![], - message, - } - } - - pub fn append_authentication( - &mut self, - authentication: P, - ) { - let message_payload = serde_json::to_vec(&self.message).unwrap(); - - info!( - "Verifying payload: {}", - std::str::from_utf8(&message_payload).unwrap() - ); - - self.source - .push(Box::new(authentication) as Box); - } - - pub fn into_payload(mut self) -> AuthenticatedPayload { - let payload = bincode::serialize(&self.message.payload).unwrap(); - - AuthenticatedPayload { - object: self.message.object.to_string(), - operation: self.message.operation, - source: self - .source - .drain(..) - .map(|provider| provider.as_ref().authenticate(&payload)) - .collect::>(), - payload, - } - } -} - -mod verified {} - -#[derive(Clone, Debug)] -pub struct UserAuthenticator { - pub user: User, - pub token: UserAuthenticationToken, -} - -impl AuthenticationSourceProvider for UserAuthenticator { - fn authenticate(&self, _payload: &Vec) -> AuthenticationSource { - AuthenticationSource::User { - user: self.user.clone(), - token: self.token.clone(), - } - } -} - -#[derive(Debug, Clone)] -pub struct InstanceAuthenticator { - pub instance: Instance, - pub private_key: String, -} - -impl AuthenticationSourceProvider for InstanceAuthenticator { - fn authenticate(&self, payload: &Vec) -> AuthenticationSource { - let mut rng = rand::thread_rng(); - - let private_key = RsaPrivateKey::from_pkcs1_pem(&self.private_key).unwrap(); - let signing_key = SigningKey::::new(private_key); - let signature = signing_key.sign_with_rng(&mut rng, payload); - - AuthenticationSource::Instance { - instance: self.instance.clone(), - // TODO: Actually parse signature from private key - signature: InstanceSignature(signature.to_bytes().into_vec()), - } - } -} - -#[repr(transparent)] -#[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] -pub struct UserAuthenticationToken(String); - -impl From for UserAuthenticationToken { - fn from(value: String) -> Self { - Self(value) - } -} - -impl ToString for UserAuthenticationToken { - fn to_string(&self) -> String { - self.0.clone() - } -} - -impl AsRef for UserAuthenticationToken { - fn as_ref(&self) -> &str { - &self.0 - } -} - -#[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] -pub struct InstanceSignature(Vec); - -impl AsRef<[u8]> for InstanceSignature { - fn as_ref(&self) -> &[u8] { - &self.0 - } -} - -#[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] -pub enum AuthenticationSource { - User { - user: User, - token: UserAuthenticationToken, - }, - Instance { - instance: Instance, - signature: InstanceSignature, - }, -} diff --git a/giterated-models/src/model/discovery.rs b/giterated-models/src/model/discovery.rs deleted file mode 100644 index c49db64..0000000 --- a/giterated-models/src/model/discovery.rs +++ /dev/null @@ -1,15 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::model::{instance::Instance, repository::Repository}; - -#[derive(Clone, Hash, PartialEq, Eq, Debug, Serialize, Deserialize)] -pub enum DiscoveryItem { - Instance { - instance: Instance, - signature: Vec, - }, - Repository { - repository: Repository, - signature: Vec, - }, -} diff --git a/giterated-models/src/model/instance.rs b/giterated-models/src/model/instance.rs deleted file mode 100644 index b6b6f36..0000000 --- a/giterated-models/src/model/instance.rs +++ /dev/null @@ -1,63 +0,0 @@ -use std::{fmt::Display, str::FromStr}; - -use serde::{Deserialize, Serialize}; -use thiserror::Error; - -use crate::operation::GiteratedObject; - -pub struct InstanceMeta { - pub url: String, - pub public_key: String, -} - -/// An instance, defined by the URL it can be reached at. -/// -/// # Textual Format -/// An instance's textual format is its URL. -/// -/// ## Examples -/// For the instance `giterated.dev`, the following [`Instance`] initialization -/// would be valid: -/// -/// ``` -/// let instance = Instance { -/// url: String::from("giterated.dev") -/// }; -/// -/// // This is correct -/// assert_eq!(Instance::from_str("giterated.dev").unwrap(), instance); -/// ``` -#[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] -pub struct Instance { - pub url: String, -} - -impl GiteratedObject for Instance { - fn object_name() -> &'static str { - "instance" - } - - fn from_object_str(object_str: &str) -> Result { - Ok(Instance::from_str(object_str).unwrap()) - } -} - -impl Display for Instance { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(&self.url) - } -} - -impl FromStr for Instance { - type Err = InstanceParseError; - - fn from_str(s: &str) -> Result { - Ok(Self { url: s.to_string() }) - } -} - -#[derive(Debug, Error)] -pub enum InstanceParseError { - #[error("invalid format")] - InvalidFormat, -} diff --git a/giterated-models/src/model/mod.rs b/giterated-models/src/model/mod.rs deleted file mode 100644 index 08397bc..0000000 --- a/giterated-models/src/model/mod.rs +++ /dev/null @@ -1,19 +0,0 @@ -//! # Giterated Network Data Model Types -//! -//! All network data model types that are not directly associated with -//! individual requests or responses. - -use self::instance::Instance; - -pub mod authenticated; -pub mod discovery; -pub mod instance; -pub mod repository; -pub mod settings; -pub mod user; - -pub trait MessageTarget { - fn target(&self) -> Option { - todo!() - } -} diff --git a/giterated-models/src/model/repository.rs b/giterated-models/src/model/repository.rs deleted file mode 100644 index 4eb5c51..0000000 --- a/giterated-models/src/model/repository.rs +++ /dev/null @@ -1,251 +0,0 @@ -use std::fmt::{Display, Formatter}; -use std::str::FromStr; - -use serde::{Deserialize, Serialize}; - -use crate::operation::GiteratedObject; - -use super::{instance::Instance, user::User}; - -/// A repository, defined by the instance it exists on along with -/// its owner and name. -/// -/// # Textual Format -/// A repository's textual reference is defined as: -/// -/// `{owner: User}/{name: String}@{instance: Instance}` -/// -/// # Examples -/// For the repository named `foo` owned by `barson:giterated.dev` on the instance -/// `giterated.dev`, the following [`Repository`] initialization would -/// be valid: -/// -/// ``` -//# use giterated_models::model::repository::Repository; -//# use giterated_models::model::instance::Instance; -//# use giterated_models::model::user::User; -/// let repository = Repository { -/// owner: User::from_str("barson:giterated.dev").unwrap(), -/// name: String::from("foo"), -/// instance: Instance::from_str("giterated.dev").unwrap() -/// }; -/// -/// // This is correct -/// assert_eq!(Repository::from_str("barson:giterated.dev/foo@giterated.dev").unwrap(), repository); -/// ``` -#[derive(Hash, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] -pub struct Repository { - pub owner: User, - pub name: String, - /// Instance the repository is on - pub instance: Instance, -} - -impl Display for Repository { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.write_str(&format!("{}/{}@{}", self.owner, self.name, self.instance)) - } -} - -impl GiteratedObject for Repository { - fn object_name() -> &'static str { - "repository" - } - - fn from_object_str(object_str: &str) -> Result { - Ok(Repository::from_str(object_str)?) - } -} - -impl TryFrom for Repository { - type Error = RepositoryParseError; - - fn try_from(value: String) -> Result { - Self::from_str(&value) - } -} - -impl FromStr for Repository { - type Err = RepositoryParseError; - - fn from_str(s: &str) -> Result { - let mut by_ampersand = s.split('@'); - let mut path_split = by_ampersand.next().unwrap().split('/'); - - let instance = Instance::from_str(by_ampersand.next().unwrap()).unwrap(); - let owner = User::from_str(path_split.next().unwrap()).unwrap(); - let name = path_split.next().unwrap().to_string(); - - Ok(Self { - instance, - owner, - name, - }) - } -} - -#[derive(Debug, thiserror::Error)] -pub enum RepositoryParseError {} - -/// Visibility of the repository to the general eye -#[derive(PartialEq, Eq, Debug, Hash, Serialize, Deserialize, Clone, sqlx::Type)] -#[sqlx(type_name = "visibility", rename_all = "lowercase")] -pub enum RepositoryVisibility { - Public, - Unlisted, - Private, -} - -/// Implements [`Display`] for [`RepositoryVisiblity`] using [`Debug`] -impl Display for RepositoryVisibility { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{:?}", self) - } -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct RepositoryView { - /// Name of the repository - /// - /// This is different than the [`Repository`] name, - /// which may be a path. - pub name: String, - /// Owner of the Repository - pub owner: User, - /// Repository description - pub description: Option, - /// Repository visibility - pub visibility: RepositoryVisibility, - /// Default branch of the repository - pub default_branch: String, - /// Last commit made to the repository - pub latest_commit: Option, - /// Revision of the displayed tree - pub tree_rev: Option, - /// Repository tree - pub tree: Vec, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum RepositoryObjectType { - Tree, - Blob, -} - -/// Stored info for our tree entries -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct RepositoryTreeEntry { - /// Name of the tree/blob - pub name: String, - /// Type of the tree entry - pub object_type: RepositoryObjectType, - /// Git supplies us with the mode at all times, and people like it displayed. - pub mode: i32, - /// File size - pub size: Option, - /// Last commit made to the tree/blob - pub last_commit: Option, -} - -impl RepositoryTreeEntry { - // I love you Emilia <3 - pub fn new(name: &str, object_type: RepositoryObjectType, mode: i32) -> Self { - Self { - name: name.to_string(), - object_type, - mode, - size: None, - last_commit: None, - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct RepositoryTreeEntryWithCommit { - pub tree_entry: RepositoryTreeEntry, - pub commit: Commit, -} - -/// Info about a git commit -#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)] -pub struct Commit { - /// Unique commit ID - pub oid: String, - /// Shortened abbreviated OID - /// This starts at the git config's "core.abbrev" length (default 7 characters) and - /// iteratively extends to a longer string if that length is ambiguous. The - /// result will be unambiguous (at least until new objects are added to the repository). - pub short_oid: String, - /// Full commit message - pub message: Option, - /// Who created the commit - pub author: CommitSignature, - /// Who committed the commit - pub committer: CommitSignature, - /// Time when the commit happened - pub time: chrono::NaiveDateTime, -} - -/// Gets all info from [`git2::Commit`] for easy use -impl From> for Commit { - fn from(commit: git2::Commit<'_>) -> Self { - Self { - oid: commit.id().to_string(), - // This shouldn't ever fail, as we already know the object has an oid. - short_oid: commit - .as_object() - .short_id() - .unwrap() - .as_str() - .unwrap() - .to_string(), - message: commit.message().map(|message| message.to_string()), - author: commit.author().into(), - committer: commit.committer().into(), - time: chrono::NaiveDateTime::from_timestamp_opt(commit.time().seconds(), 0).unwrap(), - } - } -} - -/// Git commit signature -#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)] -pub struct CommitSignature { - pub name: Option, - pub email: Option, - pub time: chrono::NaiveDateTime, -} - -/// Converts the signature from git2 into something usable without explicit lifetimes. -impl From> for CommitSignature { - fn from(signature: git2::Signature<'_>) -> Self { - Self { - name: signature.name().map(|name| name.to_string()), - email: signature.email().map(|email| email.to_string()), - time: chrono::NaiveDateTime::from_timestamp_opt(signature.when().seconds(), 0).unwrap(), - } - } -} - -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct RepositorySummary { - pub repository: Repository, - pub owner: User, - pub visibility: RepositoryVisibility, - pub description: Option, - pub last_commit: Option, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct IssueLabel { - pub name: String, - pub color: String, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct RepositoryIssue { - pub author: User, - pub id: u64, - pub title: String, - pub contents: String, - pub labels: Vec, -} diff --git a/giterated-models/src/model/settings.rs b/giterated-models/src/model/settings.rs deleted file mode 100644 index c0b5ef4..0000000 --- a/giterated-models/src/model/settings.rs +++ /dev/null @@ -1,60 +0,0 @@ -use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use serde_json::Value; - -pub trait Setting: Serialize + DeserializeOwned { - fn name() -> &'static str; -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct AnySetting(pub Value); - -impl Setting for AnySetting { - fn name() -> &'static str { - "any" - } -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct UserBio(pub String); - -impl Setting for UserBio { - fn name() -> &'static str { - "Bio" - } -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct UserDisplayName(pub String); - -impl Setting for UserDisplayName { - fn name() -> &'static str { - "Display Name" - } -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct UserDisplayImage(pub String); - -impl Setting for UserDisplayImage { - fn name() -> &'static str { - "Profile Image" - } -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct RepositoryDescription(pub String); - -impl Setting for RepositoryDescription { - fn name() -> &'static str { - "Repository Description" - } -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct RepositoryVisibilitySetting(pub String); - -impl Setting for RepositoryVisibilitySetting { - fn name() -> &'static str { - "Repository Visibility" - } -} diff --git a/giterated-models/src/model/user.rs b/giterated-models/src/model/user.rs deleted file mode 100644 index 73ecc96..0000000 --- a/giterated-models/src/model/user.rs +++ /dev/null @@ -1,90 +0,0 @@ -use std::fmt::{Display, Formatter}; -use std::str::FromStr; - -use secrecy::{CloneableSecret, DebugSecret, SerializableSecret, Zeroize}; -use serde::{Deserialize, Serialize}; - -use crate::operation::GiteratedObject; - -use super::instance::Instance; - -/// A user, defined by its username and instance. -/// -/// # Textual Format -/// A user's textual reference is defined as: -/// -/// `{username: String}:{instance: Instance}` -/// -/// # Examples -/// For the user with the username `barson` and the instance `giterated.dev`, -/// the following [`User`] initialization would be valid: -/// -/// ``` -/// let user = User { -/// username: String::from("barson"), -/// instance: Instance::from_str("giterated.dev").unwrap() -/// }; -/// -/// // This is correct -/// assert_eq!(User::from_str("barson:giterated.dev").unwrap(), user); -/// ``` -#[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] -pub struct User { - pub username: String, - pub instance: Instance, -} - -impl GiteratedObject for User { - fn object_name() -> &'static str { - "user" - } - - fn from_object_str(object_str: &str) -> Result { - Ok(User::from_str(object_str).unwrap()) - } -} - -impl Display for User { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{}:{}", self.username, self.instance.url) - } -} - -impl From for User { - fn from(user_string: String) -> Self { - User::from_str(&user_string).unwrap() - } -} - -impl FromStr for User { - type Err = UserParseError; - - fn from_str(s: &str) -> Result { - if s.contains('/') { - return Err(UserParseError); - } - - let mut colon_split = s.split(':'); - let username = colon_split.next().unwrap().to_string(); - let instance = Instance::from_str(colon_split.next().unwrap()).unwrap(); - - Ok(Self { username, instance }) - } -} - -#[derive(thiserror::Error, Debug)] -#[error("failed to parse user")] -pub struct UserParseError; - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct Password(pub String); - -impl Zeroize for Password { - fn zeroize(&mut self) { - self.0.zeroize() - } -} - -impl SerializableSecret for Password {} -impl CloneableSecret for Password {} -impl DebugSecret for Password {} diff --git a/giterated-models/src/object.rs b/giterated-models/src/object.rs new file mode 100644 index 0000000..785d90d --- /dev/null +++ b/giterated-models/src/object.rs @@ -0,0 +1,87 @@ +use std::{ + fmt::{Debug, Display}, + marker::PhantomData, + str::FromStr, +}; + +use anyhow::Error; + +use crate::{ + error::{GetValueError, OperationError}, + object_backend::ObjectBackend, + operation::GiteratedOperation, + settings::{GetSetting, GetSettingError, SetSetting, SetSettingError, Setting}, + value::{GetValue, GiteratedObjectValue}, +}; + +mod operations; +pub use operations::*; + +#[derive(Debug, Clone)] +pub struct Object<'b, O: GiteratedObject, B: ObjectBackend + 'b + Send + Sync + Clone> { + pub(crate) inner: O, + pub(crate) backend: B, + pub(crate) _marker: PhantomData<&'b ()>, +} + +impl<'b, B: ObjectBackend + Send + Sync + Clone, O: GiteratedObject> Object<'b, O, B> { + pub fn object(&self) -> &O { + &self.inner + } + + pub unsafe fn new_unchecked(object: O, backend: B) -> Object<'b, O, B> { + Object { + inner: object, + backend, + _marker: PhantomData, + } + } +} + +pub trait GiteratedObject: Send + Display + FromStr { + fn object_name() -> &'static str; + + fn from_object_str(object_str: &str) -> Result; +} + +impl<'b, O: GiteratedObject + Clone + Debug, B: ObjectBackend> Object<'b, O, B> { + pub async fn get + Send + Debug>( + &mut self, + ) -> Result> { + self.request(GetValue { + value_name: V::value_name().to_string(), + _marker: PhantomData, + }) + .await + } + + pub async fn get_setting( + &mut self, + ) -> Result> { + self.request(GetSetting { + setting_name: S::name().to_string(), + _marker: PhantomData, + }) + .await + } + + pub async fn set_setting( + &mut self, + setting: S, + ) -> Result<(), OperationError> { + self.request(SetSetting { + setting_name: S::name().to_string(), + value: setting, + }) + .await + } + + pub async fn request + Debug>( + &mut self, + request: R, + ) -> Result> { + self.backend + .object_operation(self.inner.clone(), request) + .await + } +} diff --git a/giterated-models/src/object/operations.rs b/giterated-models/src/object/operations.rs new file mode 100644 index 0000000..0611ce3 --- /dev/null +++ b/giterated-models/src/object/operations.rs @@ -0,0 +1,53 @@ +use std::{convert::Infallible, fmt::Display, str::FromStr}; + +use serde::{Deserialize, Serialize}; + +use crate::{instance::Instance, operation::GiteratedOperation}; + +use super::GiteratedObject; + +#[derive(Debug, Serialize, Deserialize)] +pub struct ObjectRequest(pub String); + +#[derive(Serialize, Deserialize)] +pub struct ObjectResponse(pub Vec); + +impl GiteratedOperation for ObjectRequest { + type Success = ObjectResponse; + + type Failure = ObjectRequestError; +} + +#[derive(Debug, thiserror::Error, Serialize, Deserialize)] +pub enum ObjectRequestError { + #[error("error decoding the object")] + Deserialization(String), +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[repr(transparent)] +pub struct AnyObject(pub String); + +impl GiteratedObject for AnyObject { + fn object_name() -> &'static str { + "any" + } + + fn from_object_str(object_str: &str) -> Result { + Ok(Self(object_str.to_string())) + } +} + +impl Display for AnyObject { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.0) + } +} + +impl FromStr for AnyObject { + type Err = Infallible; + + fn from_str(s: &str) -> Result { + Ok(Self(s.to_owned())) + } +} diff --git a/giterated-models/src/object_backend.rs b/giterated-models/src/object_backend.rs new file mode 100644 index 0000000..abddbd8 --- /dev/null +++ b/giterated-models/src/object_backend.rs @@ -0,0 +1,24 @@ +use crate::{ + error::OperationError, + object::{GiteratedObject, Object, ObjectRequestError}, + operation::GiteratedOperation, +}; + +use std::fmt::Debug; + +#[async_trait::async_trait] +pub trait ObjectBackend: Send + Sync + Sized + Clone { + async fn object_operation( + &self, + object: O, + operation: D, + ) -> Result> + where + O: GiteratedObject + Debug, + D: GiteratedOperation + Debug; + + async fn get_object( + &self, + object_str: &str, + ) -> Result, OperationError>; +} diff --git a/giterated-models/src/operation.rs b/giterated-models/src/operation.rs new file mode 100644 index 0000000..53d949d --- /dev/null +++ b/giterated-models/src/operation.rs @@ -0,0 +1,26 @@ +use std::{any::type_name, fmt::Debug}; + +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use serde_json::Value; + +use crate::object::GiteratedObject; + +pub trait GiteratedOperation: Send + Serialize + DeserializeOwned { + type Success: Serialize + DeserializeOwned + Send; + type Failure: Serialize + DeserializeOwned + Send; + + fn operation_name() -> &'static str { + type_name::() + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(transparent)] +#[repr(transparent)] +pub struct AnyOperation(pub Value); + +impl GiteratedOperation for AnyOperation { + type Success = Vec; + + type Failure = Vec; +} diff --git a/giterated-models/src/operation/instance.rs b/giterated-models/src/operation/instance.rs deleted file mode 100644 index 141ac08..0000000 --- a/giterated-models/src/operation/instance.rs +++ /dev/null @@ -1,179 +0,0 @@ -use secrecy::Secret; -use serde::{Deserialize, Serialize}; - -use crate::{ - error::{InstanceError, OperationError}, - model::{ - authenticated::UserAuthenticationToken, - instance::Instance, - repository::{Repository, RepositoryVisibility}, - user::{Password, User}, - }, -}; - -use super::{GiteratedOperation, Object, ObjectBackend}; - -/// An account registration request. -/// -/// # Authentication -/// - Instance Authentication -/// - **ONLY ACCEPTED WHEN SAME-INSTANCE** -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct RegisterAccountRequest { - pub username: String, - pub email: Option, - pub password: Secret, -} - -impl GiteratedOperation for RegisterAccountRequest { - type Success = UserAuthenticationToken; - type Failure = InstanceError; -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct RegisterAccountResponse { - pub token: String, -} - -/// An authentication token request. -/// -/// AKA Login Request -/// -/// # Authentication -/// - Instance Authentication -/// - Identifies the Instance to issue the token for -/// # Authorization -/// - Credentials ([`crate::backend::AuthBackend`]-based) -/// - Identifies the User account to issue a token for -/// - Decrypts user private key to issue to -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct AuthenticationTokenRequest { - pub instance: Instance, - pub username: String, - pub password: Secret, -} - -impl GiteratedOperation for AuthenticationTokenRequest { - type Success = UserAuthenticationToken; - type Failure = InstanceError; -} - -/// An authentication token extension request. -/// -/// # Authentication -/// - Instance Authentication -/// - Identifies the Instance to issue the token for -/// - User Authentication -/// - Authenticates the validity of the token -/// # Authorization -/// - Token-based -/// - Validates authorization using token's authenticity -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct TokenExtensionRequest { - pub token: UserAuthenticationToken, -} - -impl GiteratedOperation for TokenExtensionRequest { - type Success = Option; - type Failure = InstanceError; -} - -/// A request to create a repository. -/// -/// # Authentication -/// - Instance Authentication -/// - Used to validate User token `issued_for` -/// - User Authentication -/// - Used to source owning user -/// - Used to authorize user token against user's instance -/// # Authorization -/// - Instance Authorization -/// - Used to authorize action using User token requiring a correct `issued_for` and valid issuance from user's instance -/// - User Authorization -/// - Potential User permissions checks -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct RepositoryCreateRequest { - pub instance: Option, - pub name: String, - pub description: Option, - pub visibility: RepositoryVisibility, - pub default_branch: String, - pub owner: User, -} - -impl GiteratedOperation for RepositoryCreateRequest { - type Success = Repository; - type Failure = InstanceError; -} - -impl Object<'_, Instance, B> { - pub async fn register_account( - &mut self, - email: Option<&str>, - username: &str, - password: &Secret, - ) -> Result> { - self.request::(RegisterAccountRequest { - username: username.to_string(), - email: email.map(|s| s.to_string()), - password: password.clone(), - }) - .await - } - - pub async fn authentication_token( - &mut self, - username: &str, - password: &Secret, - ) -> Result> { - self.request::(AuthenticationTokenRequest { - instance: self.inner.clone(), - username: username.to_string(), - password: password.clone(), - }) - .await - } - - pub async fn authentication_token_for( - &mut self, - instance: &Instance, - username: &str, - password: &Secret, - ) -> Result> { - self.request::(AuthenticationTokenRequest { - instance: instance.clone(), - username: username.to_string(), - password: password.clone(), - }) - .await - } - - pub async fn token_extension( - &mut self, - token: &UserAuthenticationToken, - ) -> Result, OperationError> { - self.request::(TokenExtensionRequest { - token: token.clone(), - }) - .await - } - - pub async fn create_repository( - &mut self, - instance: &Instance, - name: &str, - visibility: RepositoryVisibility, - default_branch: &str, - owner: &User, - ) -> Result> { - self.request::(RepositoryCreateRequest { - instance: Some(instance.clone()), - name: name.to_string(), - description: None, - visibility, - default_branch: default_branch.to_string(), - owner: owner.clone(), - }) - .await - } -} diff --git a/giterated-models/src/operation/mod.rs b/giterated-models/src/operation/mod.rs deleted file mode 100644 index 58765c9..0000000 --- a/giterated-models/src/operation/mod.rs +++ /dev/null @@ -1,288 +0,0 @@ -use std::{ - any::type_name, - convert::Infallible, - fmt::{Debug, Display}, - marker::PhantomData, - str::FromStr, -}; - -use anyhow::Error; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use serde_json::Value; - -use crate::{ - error::{GetValueError, OperationError}, - model::{instance::Instance, settings::Setting, MessageTarget}, - values::{GetSetting, GetSettingError, SetSetting, SetSettingError}, -}; - -pub mod instance; -pub mod repository; -pub mod user; - -pub trait GiteratedObject: Send + Display + FromStr { - fn object_name() -> &'static str; - - fn from_object_str(object_str: &str) -> Result; -} - -pub trait GiteratedOperation: Send + Serialize + DeserializeOwned { - type Success: Serialize + DeserializeOwned + Send; - type Failure: Serialize + DeserializeOwned + Send; - - fn operation_name() -> &'static str { - type_name::() - } -} - -pub trait GiteratedObjectValue: Serialize + DeserializeOwned { - type Object: GiteratedObject; - - fn value_name() -> &'static str; -} - -#[derive(Debug, Clone)] -pub struct Object<'b, O: GiteratedObject, B: ObjectBackend + 'b + Send + Sync + Clone> { - inner: O, - backend: B, - _marker: PhantomData<&'b ()>, -} - -#[async_trait::async_trait] -pub trait ObjectBackend: Send + Sync + Sized + Clone { - async fn object_operation + Debug>( - &self, - object: O, - operation: D, - ) -> Result>; - - async fn get_object( - &self, - object_str: &str, - ) -> Result, OperationError>; -} - -impl<'b, B: ObjectBackend + Send + Sync + Clone, O: GiteratedObject> Object<'b, O, B> { - pub unsafe fn new_unchecked(object: O, backend: B) -> Object<'b, O, B> { - Object { - inner: object, - backend, - _marker: PhantomData, - } - } -} - -// impl<'b, O: GiteratedObject, B: ObjectBackend> Object<'b, O, B> { -// pub unsafe fn new_unchecked(value: O, backend: Arc) -> Object<'b, O, B> { -// todo!() -// } -// } - -impl<'b, O: GiteratedObject + Clone + Debug, B: ObjectBackend + Debug + Send + Sync + Clone> - Object<'b, O, B> -{ - // pub async fn get + Send>( - // &self, - // ) -> Result> { - // let operation: GetValue = GetValue { - // value_name: V::value_name().to_string(), - // _marker: PhantomData, - // }; - - // let _message: GiteratedMessage = GiteratedMessage { - // object: self.inner.clone(), - // operation: operation.operation_name().to_string(), - // payload: operation, - // }; - - // todo!() - // } - - // pub fn request + Debug>( - // &mut self, - // request: R, - // ) -> Result { - // self.backend.object_operation(self.inner.clone(), request); - - // todo!() - // } -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct GetValue { - pub value_name: String, - _marker: PhantomData, -} - -impl + Send> GiteratedOperation - for GetValue -{ - fn operation_name() -> &'static str { - "get_value" - } - type Success = V; - type Failure = GetValueError; -} - -#[derive(Serialize)] -#[serde(bound(deserialize = "O: GiteratedObject, V: GiteratedOperation"))] -pub struct GiteratedMessage> { - #[serde(with = "string")] - pub object: O, - pub operation: String, - pub payload: V, -} - -mod string { - use std::fmt::Display; - use std::str::FromStr; - - use serde::{de, Deserialize, Deserializer, Serializer}; - - pub fn serialize(value: &T, serializer: S) -> Result - where - T: Display, - S: Serializer, - { - serializer.collect_str(value) - } - - pub fn deserialize<'de, T, D>(deserializer: D) -> Result - where - T: FromStr, - T::Err: Display, - D: Deserializer<'de>, - { - String::deserialize(deserializer)? - .parse() - .map_err(de::Error::custom) - } -} - -impl GiteratedMessage { - pub fn try_into>( - &self, - ) -> Result, ()> { - let object = O::from_object_str(&self.object.0).map_err(|_| ())?; - let payload = serde_json::from_value::(self.payload.0.clone()).map_err(|_| ())?; - - Ok(GiteratedMessage { - object, - operation: self.operation.clone(), - payload, - }) - } -} - -impl> MessageTarget for GiteratedMessage {} - -impl + Debug, O: GiteratedObject + Debug> Debug - for GiteratedMessage -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("GiteratedMessage") - .field("object", &self.object) - .field("operation", &self.operation) - .field("payload", &self.payload) - .finish() - } -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct ObjectRequest(pub String); - -#[derive(Serialize, Deserialize)] -pub struct ObjectResponse(pub Vec); - -impl GiteratedOperation for ObjectRequest { - type Success = ObjectResponse; - - type Failure = ObjectRequestError; -} - -#[derive(Debug, thiserror::Error, Serialize, Deserialize)] -pub enum ObjectRequestError { - #[error("error decoding the object")] - Deserialization(String), -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -#[repr(transparent)] -pub struct AnyObject(pub String); - -impl GiteratedObject for AnyObject { - fn object_name() -> &'static str { - "any" - } - - fn from_object_str(object_str: &str) -> Result { - Ok(Self(object_str.to_string())) - } -} - -impl Display for AnyObject { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(&self.0) - } -} - -impl FromStr for AnyObject { - type Err = Infallible; - - fn from_str(s: &str) -> Result { - Ok(Self(s.to_owned())) - } -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(transparent)] -#[repr(transparent)] -pub struct AnyOperation(pub Value); - -impl GiteratedOperation for AnyOperation { - type Success = Vec; - - type Failure = Vec; -} - -impl<'b, O: GiteratedObject + Clone + Debug, B: ObjectBackend> Object<'b, O, B> { - pub async fn get + Send + Debug>( - &mut self, - ) -> Result> { - self.request(GetValue { - value_name: V::value_name().to_string(), - _marker: PhantomData, - }) - .await - } - - pub async fn get_setting( - &mut self, - ) -> Result> { - self.request(GetSetting { - setting_name: S::name().to_string(), - _marker: PhantomData, - }) - .await - } - - pub async fn set_setting( - &mut self, - setting: S, - ) -> Result<(), OperationError> { - self.request(SetSetting { - setting_name: S::name().to_string(), - value: setting, - }) - .await - } - - pub async fn request + Debug>( - &mut self, - request: R, - ) -> Result> { - self.backend - .object_operation(self.inner.clone(), request) - .await - } -} diff --git a/giterated-models/src/operation/repository.rs b/giterated-models/src/operation/repository.rs deleted file mode 100644 index e3ccf10..0000000 --- a/giterated-models/src/operation/repository.rs +++ /dev/null @@ -1,109 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::{ - error::{OperationError, RepositoryError}, - model::repository::{IssueLabel, Repository, RepositoryIssue, RepositoryTreeEntry}, -}; - -use super::{GiteratedOperation, Object, ObjectBackend}; - -/// A request to get a repository's information. -/// -/// # Authentication -/// - Instance Authentication -/// - Validate request against the `issued_for` public key -/// - Validate User token against the user's instance's public key -/// # Authorization -/// - User Authorization -/// - Potential User permissions checks -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct RepositoryIssuesCountRequest; - -impl GiteratedOperation for RepositoryIssuesCountRequest { - type Success = u64; - type Failure = RepositoryError; -} - -/// A request to get a repository's issues count. -/// -/// # Authentication -/// - Instance Authentication -/// - Validate request against the `issued_for` public key -/// - Validate User token against the user's instance's public key -/// # Authorization -/// - User Authorization -/// - Potential User permissions checks -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct RepositoryIssueLabelsRequest; - -impl GiteratedOperation for RepositoryIssueLabelsRequest { - type Success = Vec; - type Failure = RepositoryError; -} - -/// A request to get a repository's issue labels. -/// -/// # Authentication -/// - Instance Authentication -/// - Validate request against the `issued_for` public key -/// - Validate User token against the user's instance's public key -/// # Authorization -/// - User Authorization -/// - Potential User permissions checks -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct RepositoryIssuesRequest; - -impl GiteratedOperation for RepositoryIssuesRequest { - type Success = Vec; - type Failure = RepositoryError; -} - -/// A request to inspect the tree of a repository. -/// -/// # Authentication -/// - Instance Authentication -/// - Validate request against the `issued_for` public key -/// - Validate User token against the user's instance's public key -/// # Authorization -/// - User Authorization -/// - Potential User permissions checks -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct RepositoryFileInspectRequest { - pub path: RepositoryTreeEntry, -} - -impl GiteratedOperation for RepositoryFileInspectRequest { - type Success = Vec; - type Failure = RepositoryError; -} - -impl Object<'_, Repository, B> { - pub async fn issues_count(&mut self) -> Result> { - self.request::(RepositoryIssuesCountRequest) - .await - } - - pub async fn issue_labels( - &mut self, - ) -> Result, OperationError> { - self.request::(RepositoryIssueLabelsRequest) - .await - } - - pub async fn issues( - &mut self, - ) -> Result, OperationError> { - self.request::(RepositoryIssuesRequest) - .await - } - - pub async fn inspect_files( - &mut self, - entry: &RepositoryTreeEntry, - ) -> Result, OperationError> { - self.request::(RepositoryFileInspectRequest { - path: entry.clone(), - }) - .await - } -} diff --git a/giterated-models/src/operation/user.rs b/giterated-models/src/operation/user.rs deleted file mode 100644 index f7a16b9..0000000 --- a/giterated-models/src/operation/user.rs +++ /dev/null @@ -1,32 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::{ - error::{OperationError, UserError}, - model::{instance::Instance, repository::Repository, user::User}, -}; - -use super::{GiteratedOperation, Object, ObjectBackend}; - -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct UserRepositoriesRequest { - pub instance: Instance, - pub user: User, -} - -impl GiteratedOperation for UserRepositoriesRequest { - type Success = Vec; - type Failure = UserError; -} - -impl Object<'_, User, B> { - pub async fn repositories( - &mut self, - instance: &Instance, - ) -> Result, OperationError> { - self.request::(UserRepositoriesRequest { - instance: instance.clone(), - user: self.inner.clone(), - }) - .await - } -} diff --git a/giterated-models/src/repository/mod.rs b/giterated-models/src/repository/mod.rs new file mode 100644 index 0000000..ae04ba7 --- /dev/null +++ b/giterated-models/src/repository/mod.rs @@ -0,0 +1,259 @@ +use std::fmt::{Display, Formatter}; +use std::str::FromStr; + +use serde::{Deserialize, Serialize}; + +use crate::object::GiteratedObject; + +use super::{instance::Instance, user::User}; + +mod operations; +mod settings; +mod values; + +pub use operations::*; +pub use settings::*; +pub use values::*; + +/// A repository, defined by the instance it exists on along with +/// its owner and name. +/// +/// # Textual Format +/// A repository's textual reference is defined as: +/// +/// `{owner: User}/{name: String}@{instance: Instance}` +/// +/// # Examples +/// For the repository named `foo` owned by `barson:giterated.dev` on the instance +/// `giterated.dev`, the following [`Repository`] initialization would +/// be valid: +/// +/// ``` +//# use giterated_models::model::repository::Repository; +//# use giterated_models::model::instance::Instance; +//# use giterated_models::model::user::User; +/// let repository = Repository { +/// owner: User::from_str("barson:giterated.dev").unwrap(), +/// name: String::from("foo"), +/// instance: Instance::from_str("giterated.dev").unwrap() +/// }; +/// +/// // This is correct +/// assert_eq!(Repository::from_str("barson:giterated.dev/foo@giterated.dev").unwrap(), repository); +/// ``` +#[derive(Hash, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +pub struct Repository { + pub owner: User, + pub name: String, + /// Instance the repository is on + pub instance: Instance, +} + +impl Display for Repository { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str(&format!("{}/{}@{}", self.owner, self.name, self.instance)) + } +} + +impl GiteratedObject for Repository { + fn object_name() -> &'static str { + "repository" + } + + fn from_object_str(object_str: &str) -> Result { + Ok(Repository::from_str(object_str)?) + } +} + +impl TryFrom for Repository { + type Error = RepositoryParseError; + + fn try_from(value: String) -> Result { + Self::from_str(&value) + } +} + +impl FromStr for Repository { + type Err = RepositoryParseError; + + fn from_str(s: &str) -> Result { + let mut by_ampersand = s.split('@'); + let mut path_split = by_ampersand.next().unwrap().split('/'); + + let instance = Instance::from_str(by_ampersand.next().unwrap()).unwrap(); + let owner = User::from_str(path_split.next().unwrap()).unwrap(); + let name = path_split.next().unwrap().to_string(); + + Ok(Self { + instance, + owner, + name, + }) + } +} + +#[derive(Debug, thiserror::Error)] +pub enum RepositoryParseError {} + +/// Visibility of the repository to the general eye +#[derive(PartialEq, Eq, Debug, Hash, Serialize, Deserialize, Clone, sqlx::Type)] +#[sqlx(type_name = "visibility", rename_all = "lowercase")] +pub enum RepositoryVisibility { + Public, + Unlisted, + Private, +} + +/// Implements [`Display`] for [`RepositoryVisiblity`] using [`Debug`] +impl Display for RepositoryVisibility { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self) + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct RepositoryView { + /// Name of the repository + /// + /// This is different than the [`Repository`] name, + /// which may be a path. + pub name: String, + /// Owner of the Repository + pub owner: User, + /// Repository description + pub description: Option, + /// Repository visibility + pub visibility: RepositoryVisibility, + /// Default branch of the repository + pub default_branch: String, + /// Last commit made to the repository + pub latest_commit: Option, + /// Revision of the displayed tree + pub tree_rev: Option, + /// Repository tree + pub tree: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum RepositoryObjectType { + Tree, + Blob, +} + +/// Stored info for our tree entries +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct RepositoryTreeEntry { + /// Name of the tree/blob + pub name: String, + /// Type of the tree entry + pub object_type: RepositoryObjectType, + /// Git supplies us with the mode at all times, and people like it displayed. + pub mode: i32, + /// File size + pub size: Option, + /// Last commit made to the tree/blob + pub last_commit: Option, +} + +impl RepositoryTreeEntry { + // I love you Emilia <3 + pub fn new(name: &str, object_type: RepositoryObjectType, mode: i32) -> Self { + Self { + name: name.to_string(), + object_type, + mode, + size: None, + last_commit: None, + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct RepositoryTreeEntryWithCommit { + pub tree_entry: RepositoryTreeEntry, + pub commit: Commit, +} + +/// Info about a git commit +#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)] +pub struct Commit { + /// Unique commit ID + pub oid: String, + /// Shortened abbreviated OID + /// This starts at the git config's "core.abbrev" length (default 7 characters) and + /// iteratively extends to a longer string if that length is ambiguous. The + /// result will be unambiguous (at least until new objects are added to the repository). + pub short_oid: String, + /// Full commit message + pub message: Option, + /// Who created the commit + pub author: CommitSignature, + /// Who committed the commit + pub committer: CommitSignature, + /// Time when the commit happened + pub time: chrono::NaiveDateTime, +} + +/// Gets all info from [`git2::Commit`] for easy use +impl From> for Commit { + fn from(commit: git2::Commit<'_>) -> Self { + Self { + oid: commit.id().to_string(), + // This shouldn't ever fail, as we already know the object has an oid. + short_oid: commit + .as_object() + .short_id() + .unwrap() + .as_str() + .unwrap() + .to_string(), + message: commit.message().map(|message| message.to_string()), + author: commit.author().into(), + committer: commit.committer().into(), + time: chrono::NaiveDateTime::from_timestamp_opt(commit.time().seconds(), 0).unwrap(), + } + } +} + +/// Git commit signature +#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)] +pub struct CommitSignature { + pub name: Option, + pub email: Option, + pub time: chrono::NaiveDateTime, +} + +/// Converts the signature from git2 into something usable without explicit lifetimes. +impl From> for CommitSignature { + fn from(signature: git2::Signature<'_>) -> Self { + Self { + name: signature.name().map(|name| name.to_string()), + email: signature.email().map(|email| email.to_string()), + time: chrono::NaiveDateTime::from_timestamp_opt(signature.when().seconds(), 0).unwrap(), + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct RepositorySummary { + pub repository: Repository, + pub owner: User, + pub visibility: RepositoryVisibility, + pub description: Option, + pub last_commit: Option, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct IssueLabel { + pub name: String, + pub color: String, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct RepositoryIssue { + pub author: User, + pub id: u64, + pub title: String, + pub contents: String, + pub labels: Vec, +} diff --git a/giterated-models/src/repository/operations.rs b/giterated-models/src/repository/operations.rs new file mode 100644 index 0000000..5a35493 --- /dev/null +++ b/giterated-models/src/repository/operations.rs @@ -0,0 +1,111 @@ +use serde::{Deserialize, Serialize}; + +use crate::{ + error::{OperationError, RepositoryError}, + object::Object, + object_backend::ObjectBackend, + operation::GiteratedOperation, +}; + +use super::{IssueLabel, Repository, RepositoryIssue, RepositoryTreeEntry}; + +/// A request to get a repository's information. +/// +/// # Authentication +/// - Instance Authentication +/// - Validate request against the `issued_for` public key +/// - Validate User token against the user's instance's public key +/// # Authorization +/// - User Authorization +/// - Potential User permissions checks +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct RepositoryIssuesCountRequest; + +impl GiteratedOperation for RepositoryIssuesCountRequest { + type Success = u64; + type Failure = RepositoryError; +} + +/// A request to get a repository's issues count. +/// +/// # Authentication +/// - Instance Authentication +/// - Validate request against the `issued_for` public key +/// - Validate User token against the user's instance's public key +/// # Authorization +/// - User Authorization +/// - Potential User permissions checks +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct RepositoryIssueLabelsRequest; + +impl GiteratedOperation for RepositoryIssueLabelsRequest { + type Success = Vec; + type Failure = RepositoryError; +} + +/// A request to get a repository's issue labels. +/// +/// # Authentication +/// - Instance Authentication +/// - Validate request against the `issued_for` public key +/// - Validate User token against the user's instance's public key +/// # Authorization +/// - User Authorization +/// - Potential User permissions checks +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct RepositoryIssuesRequest; + +impl GiteratedOperation for RepositoryIssuesRequest { + type Success = Vec; + type Failure = RepositoryError; +} + +/// A request to inspect the tree of a repository. +/// +/// # Authentication +/// - Instance Authentication +/// - Validate request against the `issued_for` public key +/// - Validate User token against the user's instance's public key +/// # Authorization +/// - User Authorization +/// - Potential User permissions checks +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct RepositoryFileInspectRequest { + pub path: RepositoryTreeEntry, +} + +impl GiteratedOperation for RepositoryFileInspectRequest { + type Success = Vec; + type Failure = RepositoryError; +} + +impl Object<'_, Repository, B> { + pub async fn issues_count(&mut self) -> Result> { + self.request::(RepositoryIssuesCountRequest) + .await + } + + pub async fn issue_labels( + &mut self, + ) -> Result, OperationError> { + self.request::(RepositoryIssueLabelsRequest) + .await + } + + pub async fn issues( + &mut self, + ) -> Result, OperationError> { + self.request::(RepositoryIssuesRequest) + .await + } + + pub async fn inspect_files( + &mut self, + entry: &RepositoryTreeEntry, + ) -> Result, OperationError> { + self.request::(RepositoryFileInspectRequest { + path: entry.clone(), + }) + .await + } +} diff --git a/giterated-models/src/repository/settings.rs b/giterated-models/src/repository/settings.rs new file mode 100644 index 0000000..89c117d --- /dev/null +++ b/giterated-models/src/repository/settings.rs @@ -0,0 +1,21 @@ +use serde::{Deserialize, Serialize}; + +use crate::settings::Setting; + +#[derive(Debug, Serialize, Deserialize)] +pub struct RepositoryDescription(pub String); + +impl Setting for RepositoryDescription { + fn name() -> &'static str { + "Repository Description" + } +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct RepositoryVisibilitySetting(pub String); + +impl Setting for RepositoryVisibilitySetting { + fn name() -> &'static str { + "Repository Visibility" + } +} diff --git a/giterated-models/src/repository/values.rs b/giterated-models/src/repository/values.rs new file mode 100644 index 0000000..ab5e0d7 --- /dev/null +++ b/giterated-models/src/repository/values.rs @@ -0,0 +1,45 @@ +use serde::{Deserialize, Serialize}; + +use crate::{settings::Setting, value::GiteratedObjectValue}; + +use super::{Repository, RepositoryVisibility}; + +// pub struct RepositorySetting(pub V); + +// impl + Send> GiteratedOperation +// for RepositorySetting +// { +// fn operation_name(&self) -> &'static str { +// "setting_get" +// } +// type Success = V; +// type Failure = GetValueError; +// } + +#[derive(Debug, Hash, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct Description(pub String); + +impl GiteratedObjectValue for Description { + type Object = Repository; + + fn value_name() -> &'static str { + "description" + } +} + +impl Setting for Description { + fn name() -> &'static str { + "description" + } +} + +#[derive(Debug, Hash, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct Visibility(pub RepositoryVisibility); + +impl GiteratedObjectValue for Visibility { + type Object = Repository; + + fn value_name() -> &'static str { + "visibility" + } +} diff --git a/giterated-models/src/settings/mod.rs b/giterated-models/src/settings/mod.rs new file mode 100644 index 0000000..38c009e --- /dev/null +++ b/giterated-models/src/settings/mod.rs @@ -0,0 +1,18 @@ +mod operations; + +pub use operations::*; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use serde_json::Value; + +pub trait Setting: Serialize + DeserializeOwned { + fn name() -> &'static str; +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct AnySetting(pub Value); + +impl Setting for AnySetting { + fn name() -> &'static str { + "any" + } +} diff --git a/giterated-models/src/settings/operations.rs b/giterated-models/src/settings/operations.rs new file mode 100644 index 0000000..012a90e --- /dev/null +++ b/giterated-models/src/settings/operations.rs @@ -0,0 +1,48 @@ +use std::{fmt::Debug, marker::PhantomData}; + +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use thiserror::Error; + +use crate::{object::GiteratedObject, operation::GiteratedOperation}; + +use super::Setting; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct GetSetting { + pub setting_name: String, + pub _marker: PhantomData, +} + +impl GiteratedOperation + for GetSetting +{ + fn operation_name() -> &'static str { + "get_setting" + } + + type Success = S; + + type Failure = GetSettingError; +} + +#[derive(Error, Debug, Serialize, Deserialize)] +pub enum GetSettingError {} +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(bound(deserialize = "S: Setting"))] +pub struct SetSetting { + pub setting_name: String, + pub value: S, +} + +impl GiteratedOperation for SetSetting { + fn operation_name() -> &'static str { + "set_setting" + } + + type Success = (); + + type Failure = SetSettingError; +} + +#[derive(Error, Debug, Serialize, Deserialize)] +pub enum SetSettingError {} diff --git a/giterated-models/src/user/mod.rs b/giterated-models/src/user/mod.rs new file mode 100644 index 0000000..63e1a3e --- /dev/null +++ b/giterated-models/src/user/mod.rs @@ -0,0 +1,97 @@ +mod operations; +mod settings; +mod values; + +use std::{ + fmt::{Display, Formatter}, + str::FromStr, +}; + +pub use operations::*; +use secrecy::{CloneableSecret, DebugSecret, SerializableSecret, Zeroize}; +use serde::{Deserialize, Serialize}; +pub use settings::*; +pub use values::*; + +use crate::{instance::Instance, object::GiteratedObject}; + +/// A user, defined by its username and instance. +/// +/// # Textual Format +/// A user's textual reference is defined as: +/// +/// `{username: String}:{instance: Instance}` +/// +/// # Examples +/// For the user with the username `barson` and the instance `giterated.dev`, +/// the following [`User`] initialization would be valid: +/// +/// ``` +/// let user = User { +/// username: String::from("barson"), +/// instance: Instance::from_str("giterated.dev").unwrap() +/// }; +/// +/// // This is correct +/// assert_eq!(User::from_str("barson:giterated.dev").unwrap(), user); +/// ``` +#[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] +pub struct User { + pub username: String, + pub instance: Instance, +} + +impl GiteratedObject for User { + fn object_name() -> &'static str { + "user" + } + + fn from_object_str(object_str: &str) -> Result { + Ok(User::from_str(object_str).unwrap()) + } +} + +impl Display for User { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}:{}", self.username, self.instance.url) + } +} + +impl From for User { + fn from(user_string: String) -> Self { + User::from_str(&user_string).unwrap() + } +} + +impl FromStr for User { + type Err = UserParseError; + + fn from_str(s: &str) -> Result { + if s.contains('/') { + return Err(UserParseError); + } + + let mut colon_split = s.split(':'); + let username = colon_split.next().unwrap().to_string(); + let instance = Instance::from_str(colon_split.next().unwrap()).unwrap(); + + Ok(Self { username, instance }) + } +} + +#[derive(thiserror::Error, Debug)] +#[error("failed to parse user")] +pub struct UserParseError; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Password(pub String); + +impl Zeroize for Password { + fn zeroize(&mut self) { + self.0.zeroize() + } +} + +impl SerializableSecret for Password {} +impl CloneableSecret for Password {} +impl DebugSecret for Password {} diff --git a/giterated-models/src/user/operations.rs b/giterated-models/src/user/operations.rs new file mode 100644 index 0000000..6f8bd7d --- /dev/null +++ b/giterated-models/src/user/operations.rs @@ -0,0 +1,36 @@ +use serde::{Deserialize, Serialize}; + +use crate::{ + error::{OperationError, UserError}, + instance::Instance, + object::Object, + object_backend::ObjectBackend, + operation::GiteratedOperation, + repository::Repository, +}; + +use super::User; + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct UserRepositoriesRequest { + pub instance: Instance, + pub user: User, +} + +impl GiteratedOperation for UserRepositoriesRequest { + type Success = Vec; + type Failure = UserError; +} + +impl Object<'_, User, B> { + pub async fn repositories( + &mut self, + instance: &Instance, + ) -> Result, OperationError> { + self.request::(UserRepositoriesRequest { + instance: instance.clone(), + user: self.inner.clone(), + }) + .await + } +} diff --git a/giterated-models/src/user/settings.rs b/giterated-models/src/user/settings.rs new file mode 100644 index 0000000..0d4a4da --- /dev/null +++ b/giterated-models/src/user/settings.rs @@ -0,0 +1,30 @@ +use serde::{Deserialize, Serialize}; + +use crate::settings::Setting; + +#[derive(Debug, Serialize, Deserialize)] +pub struct UserBio(pub String); + +impl Setting for UserBio { + fn name() -> &'static str { + "Bio" + } +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct UserDisplayName(pub String); + +impl Setting for UserDisplayName { + fn name() -> &'static str { + "Display Name" + } +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct UserDisplayImage(pub String); + +impl Setting for UserDisplayImage { + fn name() -> &'static str { + "Profile Image" + } +} diff --git a/giterated-models/src/user/values.rs b/giterated-models/src/user/values.rs new file mode 100644 index 0000000..b83326a --- /dev/null +++ b/giterated-models/src/user/values.rs @@ -0,0 +1,27 @@ +use serde::{Deserialize, Serialize}; + +use crate::value::GiteratedObjectValue; + +use super::User; + +#[derive(Debug, Hash, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct Bio(pub String); + +impl GiteratedObjectValue for Bio { + type Object = User; + + fn value_name() -> &'static str { + "bio" + } +} + +#[derive(Debug, Hash, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct DisplayName(pub String); + +impl GiteratedObjectValue for DisplayName { + type Object = User; + + fn value_name() -> &'static str { + "display_name" + } +} diff --git a/giterated-models/src/value.rs b/giterated-models/src/value.rs new file mode 100644 index 0000000..15b3111 --- /dev/null +++ b/giterated-models/src/value.rs @@ -0,0 +1,53 @@ +use std::{fmt::Debug, marker::PhantomData}; + +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use serde_json::Value; + +use crate::{error::GetValueError, object::GiteratedObject, operation::GiteratedOperation}; + +pub trait GiteratedObjectValue: Serialize + DeserializeOwned { + type Object: GiteratedObject; + + fn value_name() -> &'static str; +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct GetValue { + pub value_name: String, + pub(crate) _marker: PhantomData, +} + +impl + Send> GiteratedOperation + for GetValue +{ + fn operation_name() -> &'static str { + "get_value" + } + type Success = V; + type Failure = GetValueError; +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(transparent)] +pub struct AnyValue { + value: Value, + #[serde(skip)] + _marker: PhantomData, +} + +impl AnyValue { + pub unsafe fn from_raw(value: Value) -> Self { + Self { + value, + _marker: Default::default(), + } + } +} + +impl GiteratedObjectValue for AnyValue { + type Object = O; + + fn value_name() -> &'static str { + todo!() + } +} diff --git a/giterated-models/src/values/instance.rs b/giterated-models/src/values/instance.rs deleted file mode 100644 index 8b13789..0000000 --- a/giterated-models/src/values/instance.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/giterated-models/src/values/mod.rs b/giterated-models/src/values/mod.rs deleted file mode 100644 index 26f6650..0000000 --- a/giterated-models/src/values/mod.rs +++ /dev/null @@ -1,96 +0,0 @@ -use std::{fmt::Debug, marker::PhantomData}; - -use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use serde_json::Value; -use thiserror::Error; - -use crate::{ - model::settings::Setting, - operation::{GiteratedObject, GiteratedObjectValue, GiteratedOperation}, -}; - -pub mod instance; -pub mod repository; -pub mod user; - -// #[derive(Serialize, Deserialize)] -// pub struct GetRequest { -// value_name: String, -// _marker: PhantomData, -// } - -// impl + Send> GiteratedOperation -// for GetRequest -// { -// type Success = V; - -// type Failure = GetValueError; -// } - -// #[derive(Error, Debug, Serialize, Deserialize)] -// pub enum GetValueError {} - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct GetSetting { - pub setting_name: String, - pub _marker: PhantomData, -} - -impl GiteratedOperation - for GetSetting -{ - fn operation_name() -> &'static str { - "get_setting" - } - - type Success = S; - - type Failure = GetSettingError; -} - -#[derive(Error, Debug, Serialize, Deserialize)] -pub enum GetSettingError {} -#[derive(Serialize, Deserialize, Debug, Clone)] -#[serde(bound(deserialize = "S: Setting"))] -pub struct SetSetting { - pub setting_name: String, - pub value: S, -} - -impl GiteratedOperation for SetSetting { - fn operation_name() -> &'static str { - "set_setting" - } - - type Success = (); - - type Failure = SetSettingError; -} - -#[derive(Error, Debug, Serialize, Deserialize)] -pub enum SetSettingError {} - -#[derive(Debug, Clone, Deserialize, Serialize)] -#[serde(transparent)] -pub struct AnyValue { - value: Value, - #[serde(skip)] - _marker: PhantomData, -} - -impl AnyValue { - pub unsafe fn from_raw(value: Value) -> Self { - Self { - value, - _marker: Default::default(), - } - } -} - -impl GiteratedObjectValue for AnyValue { - type Object = O; - - fn value_name() -> &'static str { - todo!() - } -} diff --git a/giterated-models/src/values/repository.rs b/giterated-models/src/values/repository.rs deleted file mode 100644 index e538e34..0000000 --- a/giterated-models/src/values/repository.rs +++ /dev/null @@ -1,49 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::{ - model::{ - repository::{Repository, RepositoryVisibility}, - settings::Setting, - }, - operation::GiteratedObjectValue, -}; - -// pub struct RepositorySetting(pub V); - -// impl + Send> GiteratedOperation -// for RepositorySetting -// { -// fn operation_name(&self) -> &'static str { -// "setting_get" -// } -// type Success = V; -// type Failure = GetValueError; -// } - -#[derive(Debug, Hash, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct Description(pub String); - -impl GiteratedObjectValue for Description { - type Object = Repository; - - fn value_name() -> &'static str { - "description" - } -} - -impl Setting for Description { - fn name() -> &'static str { - "description" - } -} - -#[derive(Debug, Hash, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct Visibility(pub RepositoryVisibility); - -impl GiteratedObjectValue for Visibility { - type Object = Repository; - - fn value_name() -> &'static str { - "visibility" - } -} diff --git a/giterated-models/src/values/user.rs b/giterated-models/src/values/user.rs deleted file mode 100644 index 1b142c0..0000000 --- a/giterated-models/src/values/user.rs +++ /dev/null @@ -1,37 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::{model::user::User, operation::GiteratedObjectValue}; - -// pub struct UserSetting(pub V); - -// impl + Send> GiteratedOperation -// for UserSetting -// { -// fn operation_name(&self) -> &'static str { -// "setting_get" -// } -// type Success = V; -// type Failure = GetValueError; -// } - -#[derive(Debug, Hash, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct Bio(pub String); - -impl GiteratedObjectValue for Bio { - type Object = User; - - fn value_name() -> &'static str { - "bio" - } -} - -#[derive(Debug, Hash, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct DisplayName(pub String); - -impl GiteratedObjectValue for DisplayName { - type Object = User; - - fn value_name() -> &'static str { - "display_name" - } -}