pub mod handler; pub mod updates; use std::any::Any; use std::sync::Arc; use anyhow::Context; use giterated_models::instance::Instance; use giterated_models::repository::{ BranchStaleAfter, CommitBodyType, DefaultBranch, Description, Repository, Visibility, }; use giterated_models::user::{Bio, DisplayName, User}; use giterated_stack::provider::MetadataProvider; use giterated_stack::{AnyObject, AnySetting, GiteratedStack, ObjectMeta, SubstackBuilder}; use giterated_stack::{SettingMeta, StackOperationState}; use serde_json::Value; use sqlx::PgPool; use std::fmt::Debug; use tokio::sync::{Mutex, OnceCell}; use crate::backend::settings::{RepositorySettingRow, UserSettingRow}; use crate::backend::{RepositoryBackend, UserBackend}; use self::handler::{ instance_authentication_request, instance_create_repository_request, instance_registration_request, repository_commit_before, repository_commit_by_id, repository_diff, repository_diff_patch, repository_file_from_id, repository_file_from_path, repository_get_branches, repository_get_statistics, repository_info, repository_last_commit_of_file, user_get_repositories, }; /// A backend implementation which attempts to resolve data from the instance's database. #[derive(Clone)] #[allow(unused)] pub struct DatabaseBackend { pub(self) our_instance: Instance, pub(self) pool: PgPool, pub(self) user_backend: Arc>, pub(self) repository_backend: Arc>, pub stack: Arc>, } impl DatabaseBackend { pub fn new( instance: Instance, user_backend: Arc>, repository_backend: Arc>, pool: PgPool, stack: Arc>, ) -> Self { Self { our_instance: instance, user_backend, repository_backend, pool, stack, } } pub fn into_substack(self) -> SubstackBuilder { let mut builder = SubstackBuilder::::new(self.clone()); builder.object_metadata_provider(Box::new(self)); builder .object::() .object::() .object::(); // Register value settings, which are settings that directly correspond to // value types. builder .value_setting::() .value_setting::() .value_setting::() .value_setting::() .value_setting::() .value_setting::() .value_setting::(); // builder.value(repository_latest_commit); builder .operation(user_get_repositories) .operation(instance_registration_request) .operation(instance_authentication_request) .operation(instance_create_repository_request) .operation(repository_info) .operation(repository_get_statistics) .operation(repository_file_from_id) .operation(repository_file_from_path) .operation(repository_last_commit_of_file) .operation(repository_commit_by_id) .operation(repository_diff) .operation(repository_diff_patch) .operation(repository_commit_before) .operation(repository_get_branches); builder } } impl Debug for DatabaseBackend { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("DatabaseBackend").finish() } } #[async_trait::async_trait] impl MetadataProvider for DatabaseBackend { fn provides_for(&self, object: &dyn Any) -> bool { object.is::() || object.is::() || object.is::() } async fn write( &self, object: AnyObject, _object_meta: &ObjectMeta, setting: AnySetting, setting_meta: &SettingMeta, ) -> Result<(), anyhow::Error> { if let Some(repository) = object.downcast_ref::() { sqlx::query!("INSERT INTO repository_settings VALUES ($1, $2, $3) ON CONFLICT (repository, name) DO UPDATE SET value = $3", repository.to_string(), setting_meta.name, serde_json::to_string(&(setting_meta.serialize)(setting).unwrap())?) .execute(&self.pool).await?; Ok(()) } else if let Some(user) = object.downcast_ref::() { sqlx::query!("INSERT INTO user_settings VALUES ($1, $2, $3) ON CONFLICT (username, name) DO UPDATE SET value = $3", user.username, setting_meta.name, serde_json::to_string(&(setting_meta.serialize)(setting).unwrap())?) .execute(&self.pool).await?; Ok(()) } else { unreachable!() } } async fn read( &self, object: AnyObject, _object_meta: &ObjectMeta, setting_meta: &SettingMeta, ) -> Result { if let Some(repository) = object.downcast_ref::() { let row = sqlx::query_as!( RepositorySettingRow, "SELECT * FROM repository_settings WHERE repository = $1 AND name = $2", repository.to_string(), setting_meta.name ) .fetch_one(&self.pool) .await?; let setting = serde_json::from_str(&row.value).context("deserializing setting from database")?; Ok(setting) } else if let Some(user) = object.downcast_ref::() { info!("User for {}", setting_meta.name); let row = sqlx::query_as!( UserSettingRow, "SELECT * FROM user_settings WHERE username = $1 AND name = $2", user.username, setting_meta.name ) .fetch_one(&self.pool) .await?; let setting = serde_json::from_str(&row.value).context("deserializing setting from database")?; Ok(setting) } else { unreachable!() } } }