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

ambee/giterated

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

Giterated Stack `ObjectValue` and `Setting` refactor.

This refactor adds value and setting update events, as well as value getters. Additionally, the stack is now the owner of the ability to write settings into storage. This is accomplished with the `MetadataProvider` trait. This sets up the ground work for push federation, cache, and basically everything else. commit 7befc583cb3e0c6719506c550ed66ac76293413c Author: Amber <[email protected]> Date: Fri Sep 29 15:46:48 2023 -0500 Finish value and settings refactor in the stack. commit 3ac09994a0caafd1a0b95d9a781c7f202f20e75b Author: Amber <[email protected]> Date: Fri Sep 29 09:46:32 2023 -0500 Add set_setting handling back in commit 84fd31e3eae85d98fa68a28b333dbb32cde3bdb8 Author: Amber <[email protected]> Date: Wed Sep 27 06:36:31 2023 -0500 Remove some allocations from meta types commit 16c310ce3680c4a14ed35083b6a230aaecd43152 Author: Amber <[email protected]> Date: Wed Sep 27 05:35:03 2023 -0500 Add cargo metadata commit eb2520a20001bac7b21c6c3d34f62db32f0ada80 Author: Amber <[email protected]> Date: Wed Sep 27 05:26:27 2023 -0500 Refactor setting and value management to use the unified stack. Allows for tight management, inspection, and eventing of setting and value management. commit 901fe103da0fce4b40f33b0a8b64404049ae03cf Author: Amber <[email protected]> Date: Wed Sep 27 02:38:33 2023 -0500 Set up ground work for value / settings refactor

Amber - ⁨2⁩ years ago

parent: tbd commit: ⁨c377e4d

⁨giterated-daemon/src/database_backend/mod.rs⁩ - ⁨7675⁩ bytes
Raw
1 pub mod handler;
2 pub mod updates;
3
4 use std::any::Any;
5 use std::sync::Arc;
6
7 use anyhow::Context;
8 use giterated_models::error::OperationError;
9 use giterated_models::instance::Instance;
10 use giterated_models::object::{GiteratedObject, Object, ObjectRequestError};
11 use giterated_models::object_backend::ObjectBackend;
12 use giterated_models::operation::GiteratedOperation;
13 use giterated_models::repository::{DefaultBranch, Description, Repository, Visibility};
14 use giterated_models::user::{Bio, DisplayName, User};
15 use giterated_stack::provider::MetadataProvider;
16 use giterated_stack::{GiteratedStack, ObjectMeta, SubstackBuilder};
17 use giterated_stack::{SettingMeta, StackOperationState};
18 use serde_json::Value;
19 use sqlx::PgPool;
20 use std::fmt::Debug;
21 use tokio::sync::{Mutex, OnceCell};
22
23 use crate::backend::settings::{RepositorySettingRow, UserSettingRow};
24 use crate::backend::{RepositoryBackend, UserBackend};
25
26 use self::handler::{
27 instance_authentication_request, instance_create_repository_request,
28 instance_registration_request, repository_commit_before, repository_commit_by_id,
29 repository_diff, repository_diff_patch, repository_file_from_id, repository_file_from_path,
30 repository_get_branches, repository_get_default_branch, repository_get_latest_commit,
31 repository_get_statistics, repository_get_value_description, repository_get_value_visibility,
32 repository_info, repository_last_commit_of_file, user_get_repositories, user_get_value_bio,
33 user_get_value_display_name,
34 };
35
36 #[derive(Clone, Debug)]
37 pub struct Foobackend {}
38
39 #[async_trait::async_trait(?Send)]
40 impl ObjectBackend<StackOperationState> for Foobackend {
41 async fn object_operation<O: GiteratedObject + Debug, D: GiteratedOperation<O> + Debug>(
42 &self,
43 _object: O,
44 _operation: &str,
45 _payload: D,
46 _operation_state: &StackOperationState,
47 ) -> Result<D::Success, OperationError<D::Failure>> {
48 // We don't handle operations with this backend
49 Err(OperationError::Unhandled)
50 }
51
52 async fn get_object<O: GiteratedObject + Debug>(
53 &self,
54 _object_str: &str,
55 _operation_state: &StackOperationState,
56 ) -> Result<Object<StackOperationState, O, Self>, OperationError<ObjectRequestError>> {
57 Err(OperationError::Unhandled)
58 }
59 }
60
61 /// A backend implementation which attempts to resolve data from the instance's database.
62 #[derive(Clone)]
63 #[allow(unused)]
64 pub struct DatabaseBackend {
65 pub(self) our_instance: Instance,
66 pub(self) pool: PgPool,
67 pub(self) user_backend: Arc<Mutex<dyn UserBackend + Send>>,
68 pub(self) repository_backend: Arc<Mutex<dyn RepositoryBackend + Send>>,
69 pub stack: Arc<OnceCell<Arc<GiteratedStack>>>,
70 }
71
72 impl DatabaseBackend {
73 pub fn new(
74 instance: Instance,
75 user_backend: Arc<Mutex<dyn UserBackend + Send>>,
76 repository_backend: Arc<Mutex<dyn RepositoryBackend + Send>>,
77 pool: PgPool,
78 stack: Arc<OnceCell<Arc<GiteratedStack>>>,
79 ) -> Self {
80 Self {
81 our_instance: instance,
82 user_backend,
83 repository_backend,
84 pool,
85 stack,
86 }
87 }
88
89 pub fn into_substack(self) -> SubstackBuilder<Self> {
90 let mut builder = SubstackBuilder::<Self>::new(self.clone());
91
92 builder.object_metadata_provider(Box::new(self));
93
94 builder
95 .object::<Repository>()
96 .object::<User>()
97 .object::<Instance>();
98
99 // Register value settings, which are settings that directly correspond to
100 // value types.
101 builder
102 .value_setting::<User, DisplayName>()
103 .value_setting::<User, Bio>()
104 .value_setting::<Repository, Description>()
105 .value_setting::<Repository, Visibility>()
106 .value_setting::<Repository, DefaultBranch>();
107
108 builder
109 .value(user_get_value_bio)
110 .value(user_get_value_display_name)
111 .value(repository_get_value_description)
112 .value(repository_get_value_visibility)
113 .value(repository_get_default_branch)
114 .value(repository_get_latest_commit);
115
116 builder
117 .operation(user_get_repositories)
118 .operation(instance_registration_request)
119 .operation(instance_authentication_request)
120 .operation(instance_create_repository_request)
121 .operation(repository_info)
122 .operation(repository_get_statistics)
123 .operation(repository_file_from_id)
124 .operation(repository_file_from_path)
125 .operation(repository_last_commit_of_file)
126 .operation(repository_commit_by_id)
127 .operation(repository_diff)
128 .operation(repository_diff_patch)
129 .operation(repository_commit_before)
130 .operation(repository_get_branches);
131
132 builder
133 }
134 }
135
136 impl Debug for DatabaseBackend {
137 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
138 f.debug_struct("DatabaseBackend").finish()
139 }
140 }
141
142 #[async_trait::async_trait]
143 impl MetadataProvider for DatabaseBackend {
144 fn provides_for(&self, object: &dyn Any) -> bool {
145 object.is::<Instance>() || object.is::<Repository>() || object.is::<User>()
146 }
147
148 async fn write(
149 &self,
150 object: &(dyn Any + Send + Sync),
151 _object_meta: &ObjectMeta,
152 setting: &(dyn Any + Send + Sync),
153 setting_meta: &SettingMeta,
154 ) -> Result<(), anyhow::Error> {
155 if let Some(repository) = object.downcast_ref::<Repository>() {
156 sqlx::query!("INSERT INTO repository_settings VALUES ($1, $2, $3) ON CONFLICT (repository, name) DO UPDATE SET value = $3",
157 repository.to_string(), setting_meta.name, serde_json::to_string(&(setting_meta.serialize)(setting).unwrap())?)
158 .execute(&self.pool).await?;
159
160 Ok(())
161 } else if let Some(user) = object.downcast_ref::<User>() {
162 sqlx::query!("INSERT INTO user_settings VALUES ($1, $2, $3) ON CONFLICT (username, name) DO UPDATE SET value = $3",
163 user.username, setting_meta.name, serde_json::to_string(&(setting_meta.serialize)(setting).unwrap())?)
164 .execute(&self.pool).await?;
165
166 Ok(())
167 } else {
168 unreachable!()
169 }
170 }
171
172 async fn read(
173 &self,
174 object: &(dyn Any + Send + Sync),
175 _object_meta: &ObjectMeta,
176 setting_meta: &SettingMeta,
177 ) -> Result<Value, anyhow::Error> {
178 if let Some(repository) = object.downcast_ref::<Repository>() {
179 let row = sqlx::query_as!(
180 RepositorySettingRow,
181 "SELECT * FROM repository_settings WHERE repository = $1 AND name = $2",
182 repository.to_string(),
183 setting_meta.name
184 )
185 .fetch_one(&self.pool)
186 .await?;
187
188 let setting =
189 serde_json::from_str(&row.value).context("deserializing setting from database")?;
190
191 Ok(setting)
192 } else if let Some(user) = object.downcast_ref::<User>() {
193 info!("User for {}", setting_meta.name);
194 let row = sqlx::query_as!(
195 UserSettingRow,
196 "SELECT * FROM user_settings WHERE username = $1 AND name = $2",
197 user.username,
198 setting_meta.name
199 )
200 .fetch_one(&self.pool)
201 .await?;
202
203 let setting =
204 serde_json::from_str(&row.value).context("deserializing setting from database")?;
205
206 Ok(setting)
207 } else {
208 unreachable!()
209 }
210 }
211 }
212