diff --git a/giterated-daemon/migrations/20230905192721_repository_settings.sql b/giterated-daemon/migrations/20230905192721_repository_settings.sql new file mode 100644 index 0000000..da52261 --- /dev/null +++ b/giterated-daemon/migrations/20230905192721_repository_settings.sql @@ -0,0 +1,8 @@ +CREATE TABLE IF NOT EXISTS repository_settings +( + repository TEXT NOT NULL, + name TEXT NOT NULL, + value TEXT NOT NULL +); + +CREATE UNIQUE INDEX unique_per_repository ON repository_settings (repository, name); \ No newline at end of file diff --git a/giterated-daemon/src/backend/mod.rs b/giterated-daemon/src/backend/mod.rs index 93381c8..71bd3e7 100644 --- a/giterated-daemon/src/backend/mod.rs +++ b/giterated-daemon/src/backend/mod.rs @@ -1,6 +1,7 @@ pub mod discovery; pub mod git; pub mod github; +pub mod settings; pub mod user; use anyhow::Error; @@ -26,7 +27,7 @@ use giterated_models::{ }, }, model::{ - repository::{RepositorySummary, RepositoryView}, + repository::{Repository, RepositorySummary, RepositoryView}, user::User, }, }; @@ -100,11 +101,21 @@ pub trait UserBackend: AuthBackend { async fn bio(&mut self, request: UserBioRequest) -> Result; async fn exists(&mut self, user: &User) -> Result; +} + +#[async_trait::async_trait] +pub trait SettingsBackend: Send + Sync { + async fn user_get(&mut self, user: &User) -> Result, Error>; + async fn user_write(&mut self, user: &User, settings: &[(String, String)]) + -> Result<(), Error>; - async fn settings(&mut self, user: &User) -> Result, Error>; - async fn write_settings( + async fn repository_get( &mut self, - user: &User, + repository: &Repository, + ) -> Result, Error>; + async fn repository_write( + &mut self, + repository: &Repository, settings: &[(String, String)], ) -> Result<(), Error>; } diff --git a/giterated-daemon/src/backend/settings.rs b/giterated-daemon/src/backend/settings.rs new file mode 100644 index 0000000..05b4b5f --- /dev/null +++ b/giterated-daemon/src/backend/settings.rs @@ -0,0 +1,113 @@ +use anyhow::Error; +use futures_util::StreamExt; +use giterated_models::model::{repository::Repository, user::User}; +use serde_json::Value; +use sqlx::{Either, PgPool}; + +use super::SettingsBackend; + +pub struct DatabaseSettings { + pub pg_pool: PgPool, +} + +#[async_trait::async_trait] +impl SettingsBackend for DatabaseSettings { + async fn user_get(&mut self, user: &User) -> Result, Error> { + let settings = sqlx::query_as!( + UserSettingRow, + r#"SELECT * FROM user_settings WHERE username = $1"#, + user.username + ) + .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; + + Ok(settings) + } + async fn user_write( + &mut self, + user: &User, + settings: &[(String, String)], + ) -> 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?; + } + + Ok(()) + } + + async fn repository_get( + &mut self, + repository: &Repository, + ) -> Result, Error> { + 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; + + Ok(settings) + } + async fn repository_write( + &mut self, + repository: &Repository, + settings: &[(String, String)], + ) -> 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(()) + } +} + +#[allow(unused)] +#[derive(Debug, sqlx::FromRow)] +struct UserSettingRow { + pub username: String, + pub name: String, + pub value: String, +} + +#[allow(unused)] +#[derive(Debug, sqlx::FromRow)] +struct RepositorySettingRow { + pub repository: String, + pub name: String, + pub value: String, +} diff --git a/giterated-daemon/src/backend/user.rs b/giterated-daemon/src/backend/user.rs index 8457f13..d09c657 100644 --- a/giterated-daemon/src/backend/user.rs +++ b/giterated-daemon/src/backend/user.rs @@ -28,18 +28,19 @@ use rsa::{ rand_core::OsRng, RsaPrivateKey, RsaPublicKey, }; -use serde_json::Value; -use sqlx::{Either, PgPool}; + +use sqlx::PgPool; use tokio::sync::Mutex; use crate::authentication::AuthenticationTokenGranter; -use super::{AuthBackend, UserBackend}; +use super::{AuthBackend, SettingsBackend, UserBackend}; pub struct UserAuth { pub pg_pool: PgPool, pub this_instance: Instance, pub auth_granter: Arc>, + pub settings_provider: Arc>, } impl UserAuth { @@ -47,11 +48,13 @@ impl UserAuth { pool: PgPool, this_instance: &Instance, granter: Arc>, + settings_provider: Arc>, ) -> Self { Self { pg_pool: pool, this_instance: this_instance.clone(), auth_granter: granter, + settings_provider, } } } @@ -62,7 +65,9 @@ impl UserBackend for UserAuth { &mut self, request: UserDisplayNameRequest, ) -> Result { - let settings = self.settings(&request.user).await?; + let mut settings_backend = self.settings_provider.lock().await; + let settings = settings_backend.user_get(&request.user).await?; + drop(settings_backend); let name = settings .iter() @@ -83,7 +88,10 @@ impl UserBackend for UserAuth { &mut self, request: UserDisplayImageRequest, ) -> Result { - let settings = self.settings(&request.user).await?; + let mut settings_backend: tokio::sync::MutexGuard<'_, dyn SettingsBackend> = + self.settings_provider.lock().await; + let settings = settings_backend.user_get(&request.user).await?; + drop(settings_backend); let image = settings .iter() @@ -101,7 +109,9 @@ impl UserBackend for UserAuth { } async fn bio(&mut self, request: UserBioRequest) -> Result { - let settings = self.settings(&request.user).await?; + let mut settings_backend = self.settings_provider.lock().await; + let settings = settings_backend.user_get(&request.user).await?; + drop(settings_backend); let bio = settings .iter() @@ -126,47 +136,6 @@ impl UserBackend for UserAuth { .await .is_err()) } - - async fn settings(&mut self, user: &User) -> Result, Error> { - let settings = sqlx::query_as!( - UserSettingRow, - r#"SELECT * FROM user_settings WHERE username = $1"#, - user.username - ) - .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; - - Ok(settings) - } - - async fn write_settings( - &mut self, - user: &User, - settings: &[(String, String)], - ) -> 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?; - } - - Ok(()) - } } #[async_trait::async_trait] @@ -268,11 +237,3 @@ struct UserRow { pub public_key: String, pub enc_private_key: Vec, } - -#[allow(unused)] -#[derive(Debug, sqlx::FromRow)] -struct UserSettingRow { - pub username: String, - pub name: String, - pub value: String, -} diff --git a/giterated-daemon/src/connection/forwarded.rs b/giterated-daemon/src/connection/forwarded.rs index f4409ab..a7e1d5a 100644 --- a/giterated-daemon/src/connection/forwarded.rs +++ b/giterated-daemon/src/connection/forwarded.rs @@ -1,10 +1,9 @@ use futures_util::{SinkExt, StreamExt}; use giterated_api::DaemonConnectionPool; use giterated_models::{ - messages::error::ConnectionError, - model::authenticated::{Authenticated, AuthenticatedPayload}, + messages::error::ConnectionError, model::authenticated::AuthenticatedPayload, }; -use serde::Serialize; + use tokio_tungstenite::tungstenite::Message; pub async fn wrap_forwarded(pool: &DaemonConnectionPool, message: AuthenticatedPayload) -> Message { diff --git a/giterated-daemon/src/connection/handshake.rs b/giterated-daemon/src/connection/handshake.rs index 56344c0..a86b40c 100644 --- a/giterated-daemon/src/connection/handshake.rs +++ b/giterated-daemon/src/connection/handshake.rs @@ -1,4 +1,4 @@ -use std::{str::FromStr, sync::atomic::Ordering}; +use std::sync::atomic::Ordering; use anyhow::Error; use giterated_models::messages::handshake::{ @@ -7,9 +7,8 @@ use giterated_models::messages::handshake::{ use semver::Version; use crate::{ - connection::ConnectionError, - message::{HandshakeMessage, Message, MessageHandler, NetworkMessage, State}, - validate_version, version, + message::{HandshakeMessage, MessageHandler, NetworkMessage, State}, + version, }; use super::{wrapper::ConnectionState, HandlerUnhandled}; @@ -42,7 +41,7 @@ pub async fn handshake_handle( } async fn initiate_handshake( - HandshakeMessage(initiation): HandshakeMessage, + HandshakeMessage(_initiation): HandshakeMessage, State(connection_state): State, ) -> Result<(), HandshakeError> { info!("meow!"); @@ -82,7 +81,7 @@ async fn initiate_handshake( } async fn handshake_response( - HandshakeMessage(initiation): HandshakeMessage, + HandshakeMessage(_initiation): HandshakeMessage, State(connection_state): State, ) -> Result<(), HandshakeError> { connection_state @@ -115,7 +114,7 @@ async fn handshake_response( } async fn handshake_finalize( - HandshakeMessage(finalize): HandshakeMessage, + HandshakeMessage(_finalize): HandshakeMessage, State(connection_state): State, ) -> Result<(), HandshakeError> { connection_state.handshaked.store(true, Ordering::SeqCst); diff --git a/giterated-daemon/src/connection/user.rs b/giterated-daemon/src/connection/user.rs index 3704bf8..2d1cb5e 100644 --- a/giterated-daemon/src/connection/user.rs +++ b/giterated-daemon/src/connection/user.rs @@ -138,10 +138,9 @@ async fn user_settings( return Err(UserError::InvalidUser(request.user)); } - let mut user_backend = connection_state.user_backend.lock().await; - let mut settings = user_backend.settings(&request.user).await?; - - drop(user_backend); + let mut settings_backend = connection_state.settings_backend.lock().await; + let mut settings = settings_backend.user_get(&requesting_user).await?; + drop(settings_backend); let response = UserSettingsResponse { settings: settings.drain(..).collect(), @@ -161,12 +160,11 @@ async fn write_user_settings( return Err(UserError::InvalidUser(request.user)); } - let mut user_backend = connection_state.user_backend.lock().await; - user_backend - .write_settings(&request.user, &request.settings) + let mut settings_backend = connection_state.settings_backend.lock().await; + settings_backend + .user_write(&request.user, &request.settings) .await?; - - drop(user_backend); + drop(settings_backend); let response = UserWriteSettingsResponse {}; diff --git a/giterated-daemon/src/connection/wrapper.rs b/giterated-daemon/src/connection/wrapper.rs index ff086f8..01f7588 100644 --- a/giterated-daemon/src/connection/wrapper.rs +++ b/giterated-daemon/src/connection/wrapper.rs @@ -1,5 +1,4 @@ use std::{ - collections::HashMap, net::SocketAddr, sync::{ atomic::{AtomicBool, Ordering}, @@ -11,23 +10,17 @@ use anyhow::Error; use futures_util::{SinkExt, StreamExt}; use giterated_models::{ messages::error::ConnectionError, - model::{ - authenticated::{Authenticated, AuthenticatedPayload}, - instance::Instance, - }, + model::{authenticated::AuthenticatedPayload, instance::Instance}, }; -use rsa::RsaPublicKey; + use serde::Serialize; -use serde_json::Value; -use tokio::{ - net::TcpStream, - sync::{Mutex, RwLock}, -}; + +use tokio::{net::TcpStream, sync::Mutex}; use tokio_tungstenite::{tungstenite::Message, WebSocketStream}; use crate::{ authentication::AuthenticationTokenGranter, - backend::{RepositoryBackend, UserBackend}, + backend::{RepositoryBackend, SettingsBackend, UserBackend}, connection::forwarded::wrap_forwarded, federation::connections::InstanceConnections, keys::PublicKeyCache, @@ -45,6 +38,7 @@ pub async fn connection_wrapper( repository_backend: Arc>, user_backend: Arc>, auth_granter: Arc>, + settings_backend: Arc>, addr: SocketAddr, instance: impl ToOwned, instance_connections: Arc>, @@ -55,6 +49,7 @@ pub async fn connection_wrapper( repository_backend, user_backend, auth_granter, + settings_backend, addr, instance: instance.to_owned(), handshaked: Arc::new(AtomicBool::new(false)), @@ -179,6 +174,7 @@ pub struct ConnectionState { pub repository_backend: Arc>, pub user_backend: Arc>, pub auth_granter: Arc>, + pub settings_backend: Arc>, pub addr: SocketAddr, pub instance: Instance, pub handshaked: Arc, diff --git a/giterated-daemon/src/main.rs b/giterated-daemon/src/main.rs index 5a3f62e..4e805f0 100644 --- a/giterated-daemon/src/main.rs +++ b/giterated-daemon/src/main.rs @@ -2,7 +2,9 @@ use anyhow::Error; use connection::{Connections, RawConnection}; use giterated_daemon::{ authentication::AuthenticationTokenGranter, - backend::{git::GitBackend, user::UserAuth, RepositoryBackend, UserBackend}, + backend::{ + git::GitBackend, settings::DatabaseSettings, user::UserAuth, RepositoryBackend, UserBackend, + }, connection::{self, wrapper::connection_wrapper}, federation::connections::InstanceConnections, }; @@ -46,6 +48,10 @@ async fn main() -> Result<(), Error> { sqlx::migrate!().run(&db_pool).await?; info!("Connected"); + let settings = Arc::new(Mutex::new(DatabaseSettings { + pg_pool: db_pool.clone(), + })); + let repository_backend: Arc> = Arc::new(Mutex::new(GitBackend { pg_pool: db_pool.clone(), @@ -66,6 +72,7 @@ async fn main() -> Result<(), Error> { db_pool.clone(), &Instance::from_str("giterated.dev").unwrap(), token_granter.clone(), + settings.clone(), ))); info!("Connected"); @@ -106,6 +113,7 @@ async fn main() -> Result<(), Error> { repository_backend.clone(), user_backend.clone(), token_granter.clone(), + settings.clone(), address, Instance::from_str("giterated.dev").unwrap(), instance_connections.clone(), diff --git a/giterated-daemon/src/message.rs b/giterated-daemon/src/message.rs index e3641b7..ddb48e8 100644 --- a/giterated-daemon/src/message.rs +++ b/giterated-daemon/src/message.rs @@ -1,9 +1,9 @@ -use std::{collections::HashMap, fmt::Debug, ops::Deref}; +use std::{fmt::Debug, ops::Deref}; use anyhow::Error; use futures_util::Future; use giterated_models::model::{ - authenticated::{Authenticated, AuthenticatedPayload, AuthenticationSource, UserTokenMetadata}, + authenticated::{AuthenticatedPayload, AuthenticationSource, UserTokenMetadata}, instance::Instance, user::User, }; @@ -16,7 +16,6 @@ use rsa::{ RsaPublicKey, }; use serde::{de::DeserializeOwned, Serialize}; -use serde_json::Value; use crate::connection::wrapper::ConnectionState; diff --git a/giterated-models/src/messages/repository.rs b/giterated-models/src/messages/repository.rs index f7833de..a8902e9 100644 --- a/giterated-models/src/messages/repository.rs +++ b/giterated-models/src/messages/repository.rs @@ -1,6 +1,10 @@ +use std::collections::HashMap; + +use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; use crate::model::repository::RepositoryVisibility; +use crate::model::settings::Setting; use crate::model::{ repository::{Commit, Repository, RepositoryTreeEntry}, user::User, @@ -144,3 +148,62 @@ pub struct RepositoryInfoRequest { /// Tree path being requested pub path: Option, } + + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct RepositorySettingsRequest { + pub repository: Repository, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Default, Deserialize)] +pub struct RepositorySettingsResponse { + pub settings: HashMap, +} + +impl RepositorySettingsResponse { + pub fn try_get(&self) -> Option { + let setting_member = self + .settings + .iter() + .filter(|(key, _)| key.as_str() == S::name()) + .next()?; + + serde_json::from_value(setting_member.1.clone()).ok() + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct RepositoryWriteSettingsRequest { + pub repository: Repository, + pub settings: Vec<(String, String)>, +} + +impl RepositoryWriteSettingsRequest { + pub fn new(repository: impl ToOwned) -> Self { + Self { + repository: repository.to_owned(), + settings: Default::default(), + } + } + + pub fn set(&mut self, setting: S) { + self.settings.push(( + S::name().to_string(), + serde_json::to_string(&setting).unwrap(), + )); + } + pub fn try_get(&self) -> Option { + let setting_member = self + .settings + .iter() + .filter(|(key, _)| key.as_str() == S::name()) + .next()?; + + serde_json::from_str(&setting_member.1).ok() + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct UserWriteSettingsResponse { + // IDK? +} diff --git a/giterated-models/src/model/authenticated.rs b/giterated-models/src/model/authenticated.rs index c936935..b540998 100644 --- a/giterated-models/src/model/authenticated.rs +++ b/giterated-models/src/model/authenticated.rs @@ -1,4 +1,4 @@ -use std::{any::type_name, fmt::Debug, ops::Deref}; +use std::{any::type_name, fmt::Debug}; use rsa::{ pkcs1::DecodeRsaPrivateKey, diff --git a/giterated-models/src/model/repository.rs b/giterated-models/src/model/repository.rs index 6655aa4..2dba939 100644 --- a/giterated-models/src/model/repository.rs +++ b/giterated-models/src/model/repository.rs @@ -42,6 +42,14 @@ impl ToString for Repository { } } +impl TryFrom for Repository { + type Error = (); + + fn try_from(value: String) -> Result { + Self::from_str(&value) + } +} + impl FromStr for Repository { type Err = ();