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

ambee/giterated

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

Utilize for GitBackend and trim end slashes in folder

Emilia - ⁨1⁩ year ago

parent: tbd commit: ⁨4b440ef

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