JavaScript is disabled, refresh for a better experience. ambee/giterated

ambee/giterated

Git repository hosting, collaboration, and discovery for the Fediverse.

Add repository settings

Amber - ⁨2⁩ years ago

parent: tbd commit: ⁨f8eaf38

Showing ⁨⁨13⁩ changed files⁩ with ⁨⁨258⁩ insertions⁩ and ⁨⁨95⁩ deletions⁩

giterated-daemon/migrations/20230905192721_repository_settings.sql

View file
@@ -0,0 +1,8 @@
1 CREATE TABLE IF NOT EXISTS repository_settings
2 (
3 repository TEXT NOT NULL,
4 name TEXT NOT NULL,
5 value TEXT NOT NULL
6 );
7
8 CREATE UNIQUE INDEX unique_per_repository ON repository_settings (repository, name);
8 \ No newline at end of file

giterated-daemon/src/backend/mod.rs

View file
@@ -1,6 +1,7 @@
1 1 pub mod discovery;
2 2 pub mod git;
3 3 pub mod github;
4 pub mod settings;
4 5 pub mod user;
5 6
6 7 use anyhow::Error;
@@ -26,7 +27,7 @@ use giterated_models::{
26 27 },
27 28 },
28 29 model::{
29 repository::{RepositorySummary, RepositoryView},
30 repository::{Repository, RepositorySummary, RepositoryView},
30 31 user::User,
31 32 },
32 33 };
@@ -100,11 +101,21 @@ pub trait UserBackend: AuthBackend {
100 101
101 102 async fn bio(&mut self, request: UserBioRequest) -> Result<UserBioResponse, Error>;
102 103 async fn exists(&mut self, user: &User) -> Result<bool, Error>;
104 }
105
106 #[async_trait::async_trait]
107 pub trait SettingsBackend: Send + Sync {
108 async fn user_get(&mut self, user: &User) -> Result<Vec<(String, Value)>, Error>;
109 async fn user_write(&mut self, user: &User, settings: &[(String, String)])
110 -> Result<(), Error>;
103 111
104 async fn settings(&mut self, user: &User) -> Result<Vec<(String, Value)>, Error>;
105 async fn write_settings(
112 async fn repository_get(
106 113 &mut self,
107 user: &User,
114 repository: &Repository,
115 ) -> Result<Vec<(String, Value)>, Error>;
116 async fn repository_write(
117 &mut self,
118 repository: &Repository,
108 119 settings: &[(String, String)],
109 120 ) -> Result<(), Error>;
110 121 }

giterated-daemon/src/backend/settings.rs

View file
@@ -0,0 +1,113 @@
1 use anyhow::Error;
2 use futures_util::StreamExt;
3 use giterated_models::model::{repository::Repository, user::User};
4 use serde_json::Value;
5 use sqlx::{Either, PgPool};
6
7 use super::SettingsBackend;
8
9 pub struct DatabaseSettings {
10 pub pg_pool: PgPool,
11 }
12
13 #[async_trait::async_trait]
14 impl SettingsBackend for DatabaseSettings {
15 async fn user_get(&mut self, user: &User) -> Result<Vec<(String, Value)>, Error> {
16 let settings = sqlx::query_as!(
17 UserSettingRow,
18 r#"SELECT * FROM user_settings WHERE username = $1"#,
19 user.username
20 )
21 .fetch_many(&self.pg_pool)
22 .filter_map(|result| async move {
23 if let Ok(Either::Right(row)) = result {
24 Some(row)
25 } else {
26 None
27 }
28 })
29 .filter_map(|row| async move {
30 if let Ok(value) = serde_json::from_str(&row.value) {
31 Some((row.name, value))
32 } else {
33 None
34 }
35 })
36 .collect::<Vec<_>>()
37 .await;
38
39 Ok(settings)
40 }
41 async fn user_write(
42 &mut self,
43 user: &User,
44 settings: &[(String, String)],
45 ) -> Result<(), Error> {
46 for (name, value) in settings {
47 sqlx::query!("INSERT INTO user_settings VALUES ($1, $2, $3) ON CONFLICT (username, name) DO UPDATE SET value = $3",
48 user.username, name, value)
49 .execute(&self.pg_pool).await?;
50 }
51
52 Ok(())
53 }
54
55 async fn repository_get(
56 &mut self,
57 repository: &Repository,
58 ) -> Result<Vec<(String, Value)>, Error> {
59 let settings = sqlx::query_as!(
60 RepositorySettingRow,
61 r#"SELECT * FROM repository_settings WHERE repository = $1"#,
62 repository.to_string()
63 )
64 .fetch_many(&self.pg_pool)
65 .filter_map(|result| async move {
66 if let Ok(Either::Right(row)) = result {
67 Some(row)
68 } else {
69 None
70 }
71 })
72 .filter_map(|row| async move {
73 if let Ok(value) = serde_json::from_str(&row.value) {
74 Some((row.name, value))
75 } else {
76 None
77 }
78 })
79 .collect::<Vec<_>>()
80 .await;
81
82 Ok(settings)
83 }
84 async fn repository_write(
85 &mut self,
86 repository: &Repository,
87 settings: &[(String, String)],
88 ) -> Result<(), Error> {
89 for (name, value) in settings {
90 sqlx::query!("INSERT INTO repository_settings VALUES ($1, $2, $3) ON CONFLICT (repository, name) DO UPDATE SET value = $3",
91 repository.to_string(), name, value)
92 .execute(&self.pg_pool).await?;
93 }
94
95 Ok(())
96 }
97 }
98
99 #[allow(unused)]
100 #[derive(Debug, sqlx::FromRow)]
101 struct UserSettingRow {
102 pub username: String,
103 pub name: String,
104 pub value: String,
105 }
106
107 #[allow(unused)]
108 #[derive(Debug, sqlx::FromRow)]
109 struct RepositorySettingRow {
110 pub repository: String,
111 pub name: String,
112 pub value: String,
113 }

giterated-daemon/src/backend/user.rs

View file
@@ -28,18 +28,19 @@ use rsa::{
28 28 rand_core::OsRng,
29 29 RsaPrivateKey, RsaPublicKey,
30 30 };
31 use serde_json::Value;
32 use sqlx::{Either, PgPool};
31
32 use sqlx::PgPool;
33 33 use tokio::sync::Mutex;
34 34
35 35 use crate::authentication::AuthenticationTokenGranter;
36 36
37 use super::{AuthBackend, UserBackend};
37 use super::{AuthBackend, SettingsBackend, UserBackend};
38 38
39 39 pub struct UserAuth {
40 40 pub pg_pool: PgPool,
41 41 pub this_instance: Instance,
42 42 pub auth_granter: Arc<Mutex<AuthenticationTokenGranter>>,
43 pub settings_provider: Arc<Mutex<dyn SettingsBackend>>,
43 44 }
44 45
45 46 impl UserAuth {
@@ -47,11 +48,13 @@ impl UserAuth {
47 48 pool: PgPool,
48 49 this_instance: &Instance,
49 50 granter: Arc<Mutex<AuthenticationTokenGranter>>,
51 settings_provider: Arc<Mutex<dyn SettingsBackend>>,
50 52 ) -> Self {
51 53 Self {
52 54 pg_pool: pool,
53 55 this_instance: this_instance.clone(),
54 56 auth_granter: granter,
57 settings_provider,
55 58 }
56 59 }
57 60 }
@@ -62,7 +65,9 @@ impl UserBackend for UserAuth {
62 65 &mut self,
63 66 request: UserDisplayNameRequest,
64 67 ) -> Result<UserDisplayNameResponse, Error> {
65 let settings = self.settings(&request.user).await?;
68 let mut settings_backend = self.settings_provider.lock().await;
69 let settings = settings_backend.user_get(&request.user).await?;
70 drop(settings_backend);
66 71
67 72 let name = settings
68 73 .iter()
@@ -83,7 +88,10 @@ impl UserBackend for UserAuth {
83 88 &mut self,
84 89 request: UserDisplayImageRequest,
85 90 ) -> Result<UserDisplayImageResponse, anyhow::Error> {
86 let settings = self.settings(&request.user).await?;
91 let mut settings_backend: tokio::sync::MutexGuard<'_, dyn SettingsBackend> =
92 self.settings_provider.lock().await;
93 let settings = settings_backend.user_get(&request.user).await?;
94 drop(settings_backend);
87 95
88 96 let image = settings
89 97 .iter()
@@ -101,7 +109,9 @@ impl UserBackend for UserAuth {
101 109 }
102 110
103 111 async fn bio(&mut self, request: UserBioRequest) -> Result<UserBioResponse, Error> {
104 let settings = self.settings(&request.user).await?;
112 let mut settings_backend = self.settings_provider.lock().await;
113 let settings = settings_backend.user_get(&request.user).await?;
114 drop(settings_backend);
105 115
106 116 let bio = settings
107 117 .iter()
@@ -126,47 +136,6 @@ impl UserBackend for UserAuth {
126 136 .await
127 137 .is_err())
128 138 }
129
130 async fn settings(&mut self, user: &User) -> Result<Vec<(String, Value)>, Error> {
131 let settings = sqlx::query_as!(
132 UserSettingRow,
133 r#"SELECT * FROM user_settings WHERE username = $1"#,
134 user.username
135 )
136 .fetch_many(&self.pg_pool)
137 .filter_map(|result| async move {
138 if let Ok(Either::Right(row)) = result {
139 Some(row)
140 } else {
141 None
142 }
143 })
144 .filter_map(|row| async move {
145 if let Ok(value) = serde_json::from_str(&row.value) {
146 Some((row.name, value))
147 } else {
148 None
149 }
150 })
151 .collect::<Vec<_>>()
152 .await;
153
154 Ok(settings)
155 }
156
157 async fn write_settings(
158 &mut self,
159 user: &User,
160 settings: &[(String, String)],
161 ) -> Result<(), Error> {
162 for (name, value) in settings {
163 sqlx::query!("INSERT INTO user_settings VALUES ($1, $2, $3) ON CONFLICT (username, name) DO UPDATE SET value = $3",
164 user.username, name, value)
165 .execute(&self.pg_pool).await?;
166 }
167
168 Ok(())
169 }
170 139 }
171 140
172 141 #[async_trait::async_trait]
@@ -268,11 +237,3 @@ struct UserRow {
268 237 pub public_key: String,
269 238 pub enc_private_key: Vec<u8>,
270 239 }
271
272 #[allow(unused)]
273 #[derive(Debug, sqlx::FromRow)]
274 struct UserSettingRow {
275 pub username: String,
276 pub name: String,
277 pub value: String,
278 }

giterated-daemon/src/connection/forwarded.rs

View file
@@ -1,10 +1,9 @@
1 1 use futures_util::{SinkExt, StreamExt};
2 2 use giterated_api::DaemonConnectionPool;
3 3 use giterated_models::{
4 messages::error::ConnectionError,
5 model::authenticated::{Authenticated, AuthenticatedPayload},
4 messages::error::ConnectionError, model::authenticated::AuthenticatedPayload,
6 5 };
7 use serde::Serialize;
6
8 7 use tokio_tungstenite::tungstenite::Message;
9 8
10 9 pub async fn wrap_forwarded(pool: &DaemonConnectionPool, message: AuthenticatedPayload) -> Message {

giterated-daemon/src/connection/handshake.rs

View file
@@ -1,4 +1,4 @@
1 use std::{str::FromStr, sync::atomic::Ordering};
1 use std::sync::atomic::Ordering;
2 2
3 3 use anyhow::Error;
4 4 use giterated_models::messages::handshake::{
@@ -7,9 +7,8 @@ use giterated_models::messages::handshake::{
7 7 use semver::Version;
8 8
9 9 use crate::{
10 connection::ConnectionError,
11 message::{HandshakeMessage, Message, MessageHandler, NetworkMessage, State},
12 validate_version, version,
10 message::{HandshakeMessage, MessageHandler, NetworkMessage, State},
11 version,
13 12 };
14 13
15 14 use super::{wrapper::ConnectionState, HandlerUnhandled};
@@ -42,7 +41,7 @@ pub async fn handshake_handle(
42 41 }
43 42
44 43 async fn initiate_handshake(
45 HandshakeMessage(initiation): HandshakeMessage<InitiateHandshake>,
44 HandshakeMessage(_initiation): HandshakeMessage<InitiateHandshake>,
46 45 State(connection_state): State<ConnectionState>,
47 46 ) -> Result<(), HandshakeError> {
48 47 info!("meow!");
@@ -82,7 +81,7 @@ async fn initiate_handshake(
82 81 }
83 82
84 83 async fn handshake_response(
85 HandshakeMessage(initiation): HandshakeMessage<HandshakeResponse>,
84 HandshakeMessage(_initiation): HandshakeMessage<HandshakeResponse>,
86 85 State(connection_state): State<ConnectionState>,
87 86 ) -> Result<(), HandshakeError> {
88 87 connection_state
@@ -115,7 +114,7 @@ async fn handshake_response(
115 114 }
116 115
117 116 async fn handshake_finalize(
118 HandshakeMessage(finalize): HandshakeMessage<HandshakeFinalize>,
117 HandshakeMessage(_finalize): HandshakeMessage<HandshakeFinalize>,
119 118 State(connection_state): State<ConnectionState>,
120 119 ) -> Result<(), HandshakeError> {
121 120 connection_state.handshaked.store(true, Ordering::SeqCst);

giterated-daemon/src/connection/user.rs

View file
@@ -138,10 +138,9 @@ async fn user_settings(
138 138 return Err(UserError::InvalidUser(request.user));
139 139 }
140 140
141 let mut user_backend = connection_state.user_backend.lock().await;
142 let mut settings = user_backend.settings(&request.user).await?;
143
144 drop(user_backend);
141 let mut settings_backend = connection_state.settings_backend.lock().await;
142 let mut settings = settings_backend.user_get(&requesting_user).await?;
143 drop(settings_backend);
145 144
146 145 let response = UserSettingsResponse {
147 146 settings: settings.drain(..).collect(),
@@ -161,12 +160,11 @@ async fn write_user_settings(
161 160 return Err(UserError::InvalidUser(request.user));
162 161 }
163 162
164 let mut user_backend = connection_state.user_backend.lock().await;
165 user_backend
166 .write_settings(&request.user, &request.settings)
163 let mut settings_backend = connection_state.settings_backend.lock().await;
164 settings_backend
165 .user_write(&request.user, &request.settings)
167 166 .await?;
168
169 drop(user_backend);
167 drop(settings_backend);
170 168
171 169 let response = UserWriteSettingsResponse {};
172 170

giterated-daemon/src/connection/wrapper.rs

View file
@@ -1,5 +1,4 @@
1 1 use std::{
2 collections::HashMap,
3 2 net::SocketAddr,
4 3 sync::{
5 4 atomic::{AtomicBool, Ordering},
@@ -11,23 +10,17 @@ use anyhow::Error;
11 10 use futures_util::{SinkExt, StreamExt};
12 11 use giterated_models::{
13 12 messages::error::ConnectionError,
14 model::{
15 authenticated::{Authenticated, AuthenticatedPayload},
16 instance::Instance,
17 },
13 model::{authenticated::AuthenticatedPayload, instance::Instance},
18 14 };
19 use rsa::RsaPublicKey;
15
20 16 use serde::Serialize;
21 use serde_json::Value;
22 use tokio::{
23 net::TcpStream,
24 sync::{Mutex, RwLock},
25 };
17
18 use tokio::{net::TcpStream, sync::Mutex};
26 19 use tokio_tungstenite::{tungstenite::Message, WebSocketStream};
27 20
28 21 use crate::{
29 22 authentication::AuthenticationTokenGranter,
30 backend::{RepositoryBackend, UserBackend},
23 backend::{RepositoryBackend, SettingsBackend, UserBackend},
31 24 connection::forwarded::wrap_forwarded,
32 25 federation::connections::InstanceConnections,
33 26 keys::PublicKeyCache,
@@ -45,6 +38,7 @@ pub async fn connection_wrapper(
45 38 repository_backend: Arc<Mutex<dyn RepositoryBackend + Send>>,
46 39 user_backend: Arc<Mutex<dyn UserBackend + Send>>,
47 40 auth_granter: Arc<Mutex<AuthenticationTokenGranter>>,
41 settings_backend: Arc<Mutex<dyn SettingsBackend>>,
48 42 addr: SocketAddr,
49 43 instance: impl ToOwned<Owned = Instance>,
50 44 instance_connections: Arc<Mutex<InstanceConnections>>,
@@ -55,6 +49,7 @@ pub async fn connection_wrapper(
55 49 repository_backend,
56 50 user_backend,
57 51 auth_granter,
52 settings_backend,
58 53 addr,
59 54 instance: instance.to_owned(),
60 55 handshaked: Arc::new(AtomicBool::new(false)),
@@ -179,6 +174,7 @@ pub struct ConnectionState {
179 174 pub repository_backend: Arc<Mutex<dyn RepositoryBackend + Send>>,
180 175 pub user_backend: Arc<Mutex<dyn UserBackend + Send>>,
181 176 pub auth_granter: Arc<Mutex<AuthenticationTokenGranter>>,
177 pub settings_backend: Arc<Mutex<dyn SettingsBackend>>,
182 178 pub addr: SocketAddr,
183 179 pub instance: Instance,
184 180 pub handshaked: Arc<AtomicBool>,

giterated-daemon/src/main.rs

View file
@@ -2,7 +2,9 @@ use anyhow::Error;
2 2 use connection::{Connections, RawConnection};
3 3 use giterated_daemon::{
4 4 authentication::AuthenticationTokenGranter,
5 backend::{git::GitBackend, user::UserAuth, RepositoryBackend, UserBackend},
5 backend::{
6 git::GitBackend, settings::DatabaseSettings, user::UserAuth, RepositoryBackend, UserBackend,
7 },
6 8 connection::{self, wrapper::connection_wrapper},
7 9 federation::connections::InstanceConnections,
8 10 };
@@ -46,6 +48,10 @@ async fn main() -> Result<(), Error> {
46 48 sqlx::migrate!().run(&db_pool).await?;
47 49 info!("Connected");
48 50
51 let settings = Arc::new(Mutex::new(DatabaseSettings {
52 pg_pool: db_pool.clone(),
53 }));
54
49 55 let repository_backend: Arc<Mutex<dyn RepositoryBackend + Send>> =
50 56 Arc::new(Mutex::new(GitBackend {
51 57 pg_pool: db_pool.clone(),
@@ -66,6 +72,7 @@ async fn main() -> Result<(), Error> {
66 72 db_pool.clone(),
67 73 &Instance::from_str("giterated.dev").unwrap(),
68 74 token_granter.clone(),
75 settings.clone(),
69 76 )));
70 77
71 78 info!("Connected");
@@ -106,6 +113,7 @@ async fn main() -> Result<(), Error> {
106 113 repository_backend.clone(),
107 114 user_backend.clone(),
108 115 token_granter.clone(),
116 settings.clone(),
109 117 address,
110 118 Instance::from_str("giterated.dev").unwrap(),
111 119 instance_connections.clone(),

giterated-daemon/src/message.rs

View file
@@ -1,9 +1,9 @@
1 use std::{collections::HashMap, fmt::Debug, ops::Deref};
1 use std::{fmt::Debug, ops::Deref};
2 2
3 3 use anyhow::Error;
4 4 use futures_util::Future;
5 5 use giterated_models::model::{
6 authenticated::{Authenticated, AuthenticatedPayload, AuthenticationSource, UserTokenMetadata},
6 authenticated::{AuthenticatedPayload, AuthenticationSource, UserTokenMetadata},
7 7 instance::Instance,
8 8 user::User,
9 9 };
@@ -16,7 +16,6 @@ use rsa::{
16 16 RsaPublicKey,
17 17 };
18 18 use serde::{de::DeserializeOwned, Serialize};
19 use serde_json::Value;
20 19
21 20 use crate::connection::wrapper::ConnectionState;
22 21

giterated-models/src/messages/repository.rs

View file
@@ -1,6 +1,10 @@
1 use std::collections::HashMap;
2
3 use serde::de::DeserializeOwned;
1 4 use serde::{Deserialize, Serialize};
2 5
3 6 use crate::model::repository::RepositoryVisibility;
7 use crate::model::settings::Setting;
4 8 use crate::model::{
5 9 repository::{Commit, Repository, RepositoryTreeEntry},
6 10 user::User,
@@ -144,3 +148,62 @@ pub struct RepositoryInfoRequest {
144 148 /// Tree path being requested
145 149 pub path: Option<String>,
146 150 }
151
152
153 #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
154 pub struct RepositorySettingsRequest {
155 pub repository: Repository,
156 }
157
158 #[derive(Clone, Debug, PartialEq, Eq, Serialize, Default, Deserialize)]
159 pub struct RepositorySettingsResponse {
160 pub settings: HashMap<String, serde_json::Value>,
161 }
162
163 impl RepositorySettingsResponse {
164 pub fn try_get<S: Setting + DeserializeOwned>(&self) -> Option<S> {
165 let setting_member = self
166 .settings
167 .iter()
168 .filter(|(key, _)| key.as_str() == S::name())
169 .next()?;
170
171 serde_json::from_value(setting_member.1.clone()).ok()
172 }
173 }
174
175 #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
176 pub struct RepositoryWriteSettingsRequest {
177 pub repository: Repository,
178 pub settings: Vec<(String, String)>,
179 }
180
181 impl RepositoryWriteSettingsRequest {
182 pub fn new(repository: impl ToOwned<Owned = Repository>) -> Self {
183 Self {
184 repository: repository.to_owned(),
185 settings: Default::default(),
186 }
187 }
188
189 pub fn set<S: Setting>(&mut self, setting: S) {
190 self.settings.push((
191 S::name().to_string(),
192 serde_json::to_string(&setting).unwrap(),
193 ));
194 }
195 pub fn try_get<S: Setting + DeserializeOwned>(&self) -> Option<S> {
196 let setting_member = self
197 .settings
198 .iter()
199 .filter(|(key, _)| key.as_str() == S::name())
200 .next()?;
201
202 serde_json::from_str(&setting_member.1).ok()
203 }
204 }
205
206 #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
207 pub struct UserWriteSettingsResponse {
208 // IDK?
209 }

giterated-models/src/model/authenticated.rs

View file
@@ -1,4 +1,4 @@
1 use std::{any::type_name, fmt::Debug, ops::Deref};
1 use std::{any::type_name, fmt::Debug};
2 2
3 3 use rsa::{
4 4 pkcs1::DecodeRsaPrivateKey,

giterated-models/src/model/repository.rs

View file
@@ -42,6 +42,14 @@ impl ToString for Repository {
42 42 }
43 43 }
44 44
45 impl TryFrom<String> for Repository {
46 type Error = ();
47
48 fn try_from(value: String) -> Result<Self, Self::Error> {
49 Self::from_str(&value)
50 }
51 }
52
45 53 impl FromStr for Repository {
46 54 type Err = ();
47 55