pub mod handler; pub mod updates; use std::any::Any; use std::sync::Arc; use anyhow::Context; use giterated_models::error::OperationError; use giterated_models::instance::Instance; use giterated_models::object::{GiteratedObject, Object, ObjectRequestError}; use giterated_models::object_backend::ObjectBackend; use giterated_models::operation::GiteratedOperation; use giterated_models::repository::{DefaultBranch, Description, Repository, Visibility}; use giterated_models::user::{Bio, DisplayName, User}; use giterated_stack::provider::MetadataProvider; use giterated_stack::{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_default_branch, repository_get_latest_commit, repository_get_statistics, repository_get_value_description, repository_get_value_visibility, repository_info, repository_last_commit_of_file, user_get_repositories, user_get_value_bio, user_get_value_display_name, }; #[derive(Clone, Debug)] pub struct Foobackend {} #[async_trait::async_trait(?Send)] impl ObjectBackend for Foobackend { async fn object_operation + Debug>( &self, _object: O, _operation: &str, _payload: D, _operation_state: &StackOperationState, ) -> Result> { // We don't handle operations with this backend Err(OperationError::Unhandled) } async fn get_object( &self, _object_str: &str, _operation_state: &StackOperationState, ) -> Result, OperationError> { Err(OperationError::Unhandled) } } /// 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::(); builder .value(user_get_value_bio) .value(user_get_value_display_name) .value(repository_get_value_description) .value(repository_get_value_visibility) .value(repository_get_default_branch) .value(repository_get_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: &(dyn Any + Send + Sync), _object_meta: &ObjectMeta, setting: &(dyn Any + Send + Sync), 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: &(dyn Any + Send + Sync), _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!() } } }