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

ambee/giterated

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

Singular tag request

Emilia - ⁨1⁩ year ago

parent: tbd commit: ⁨d239950

⁨giterated-daemon/src/database_backend/mod.rs⁩ - ⁨6568⁩ 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 BranchStaleAfter, 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_branch, repository_get_branches, repository_get_statistics, repository_get_tag,
30 repository_get_tags, repository_info, 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 .value_setting::<Repository, BranchStaleAfter>();
81
82 // builder.value(repository_latest_commit);
83
84 builder
85 .operation(user_get_repositories)
86 .operation(instance_registration_request)
87 .operation(instance_authentication_request)
88 .operation(instance_create_repository_request)
89 .operation(repository_info)
90 .operation(repository_get_statistics)
91 .operation(repository_file_from_id)
92 .operation(repository_file_from_path)
93 .operation(repository_last_commit_of_file)
94 .operation(repository_commit_by_id)
95 .operation(repository_diff)
96 .operation(repository_diff_patch)
97 .operation(repository_commit_before)
98 .operation(repository_get_branches)
99 .operation(repository_get_branch)
100 .operation(repository_get_tags)
101 .operation(repository_get_tag);
102
103 builder
104 }
105 }
106
107 impl Debug for DatabaseBackend {
108 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
109 f.debug_struct("DatabaseBackend").finish()
110 }
111 }
112
113 #[async_trait::async_trait]
114 impl MetadataProvider for DatabaseBackend {
115 fn provides_for(&self, object: &dyn Any) -> bool {
116 object.is::<Instance>() || object.is::<Repository>() || object.is::<User>()
117 }
118
119 async fn write(
120 &self,
121 object: AnyObject,
122 _object_meta: &ObjectMeta,
123 setting: AnySetting,
124 setting_meta: &SettingMeta,
125 ) -> Result<(), anyhow::Error> {
126 if let Some(repository) = object.downcast_ref::<Repository>() {
127 sqlx::query!("INSERT INTO repository_settings VALUES ($1, $2, $3) ON CONFLICT (repository, name) DO UPDATE SET value = $3",
128 repository.to_string(), setting_meta.name, serde_json::to_string(&(setting_meta.serialize)(setting).unwrap())?)
129 .execute(&self.pool).await?;
130
131 Ok(())
132 } else if let Some(user) = object.downcast_ref::<User>() {
133 sqlx::query!("INSERT INTO user_settings VALUES ($1, $2, $3) ON CONFLICT (username, name) DO UPDATE SET value = $3",
134 user.username, setting_meta.name, serde_json::to_string(&(setting_meta.serialize)(setting).unwrap())?)
135 .execute(&self.pool).await?;
136
137 Ok(())
138 } else {
139 unreachable!()
140 }
141 }
142
143 async fn read(
144 &self,
145 object: AnyObject,
146 _object_meta: &ObjectMeta,
147 setting_meta: &SettingMeta,
148 ) -> Result<Value, anyhow::Error> {
149 if let Some(repository) = object.downcast_ref::<Repository>() {
150 let row = sqlx::query_as!(
151 RepositorySettingRow,
152 "SELECT * FROM repository_settings WHERE repository = $1 AND name = $2",
153 repository.to_string(),
154 setting_meta.name
155 )
156 .fetch_one(&self.pool)
157 .await?;
158
159 let setting =
160 serde_json::from_str(&row.value).context("deserializing setting from database")?;
161
162 Ok(setting)
163 } else if let Some(user) = object.downcast_ref::<User>() {
164 info!("User for {}", setting_meta.name);
165 let row = sqlx::query_as!(
166 UserSettingRow,
167 "SELECT * FROM user_settings WHERE username = $1 AND name = $2",
168 user.username,
169 setting_meta.name
170 )
171 .fetch_one(&self.pool)
172 .await?;
173
174 let setting =
175 serde_json::from_str(&row.value).context("deserializing setting from database")?;
176
177 Ok(setting)
178 } else {
179 unreachable!()
180 }
181 }
182 }
183