use diesel::prelude::*; use gritea::client::Gritea; use juniper::{graphql_object, EmptySubscription, FieldResult, IntoFieldError, RootNode}; use uuidv7::Uuid; use crate::{db, gritea_ext::GriteaExt, worker, CONFIG}; pub mod context; pub mod error; pub mod loaders; pub mod models; pub mod scalars; pub use context::Context; pub use error::{AsyncResultIntoFieldResult, Error, QueryResultIntoFieldResult}; use loaders::TryOptionLoad; pub struct Query; #[graphql_object(context = Context)] impl Query { fn ping() -> &'static str { "pong" } fn verify_login(username: String, password: String) -> bool { username == *CONFIG.user && password == *CONFIG.password } async fn user(context: &Context, id: scalars::Uuid) -> FieldResult { match context.loaders.user.try_option_load(*id).await { Ok(Some(user)) => Ok(user), Ok(None) => Err(Error::DoesNotExist.into_field_error()), Err(_) => Err(Error::Internal.into_field_error()), } } async fn user_by_name(context: &Context, name: String) -> FieldResult { let db_conn = &mut context.get_db_conn()?; let id = match db::schema::users::table .select(db::schema::users::id) .filter(db::schema::users::name.eq(name)) .first::(db_conn) .optional() .into_field_result()? { Some(x) => x, None => return Err(Error::DoesNotExist.into_field_error()), }; context .loaders .user .try_load(id) .await .map_err(|_| Error::Internal.into_field_error()) } async fn users(context: &Context) -> FieldResult> { let db_conn = &mut context.get_db_conn()?; let ids = db::schema::users::table .select(db::schema::users::id) .load::(db_conn) .into_field_result()?; context.loaders.user.try_load_many(ids).await.map_or_else( |_| Err(Error::Internal.into_field_error()), |x| Ok(x.into_values().collect()), ) } async fn repository( context: &Context, id: scalars::Uuid, ) -> FieldResult { match context.loaders.repository.try_option_load(*id).await { Ok(Some(user)) => Ok(user), Ok(None) => Err(Error::DoesNotExist.into_field_error()), Err(_) => Err(Error::Internal.into_field_error()), } } async fn repositories(context: &Context) -> FieldResult> { let db_conn = &mut context.get_db_conn()?; let ids = db::schema::repositories::table .select(db::schema::repositories::id) .load::(db_conn) .into_field_result()?; context .loaders .repository .try_load_many(ids) .await .map_or_else( |_| Err(Error::Internal.into_field_error()), |x| Ok(x.into_values().collect()), ) } } pub struct Mutation; #[graphql_object(context = Context)] impl Mutation { async fn create_repository( context: &Context, input: models::repository::CreateRepositoryInput, ) -> FieldResult { if !context.logged_in { return Err(Error::Unauthenticated.into_field_error()); } let db_conn = &mut context.get_db_conn()?; let user_id = db::schema::users::table .select(db::schema::users::id) .filter(db::schema::users::name.eq(&input.user)) .first::(db_conn) .optional() .into_field_result()?; if let Some(id) = user_id { if diesel::select(diesel::dsl::exists( db::schema::repositories::table .filter(db::schema::repositories::user_id.eq(id)) .filter(db::schema::repositories::name.eq(&input.name)), )) .get_result::(db_conn) .into_field_result()? { return Err(Error::RepoAlreadyExists.into_field_error()); } } let escaped_user = urlencoding::encode(&input.user); let escaped_name = urlencoding::encode(&input.name); match Gritea::builder(&CONFIG.gitea_url) .token(&*CONFIG.gitea_api_token) .build() { Ok(client) => { let repo = match client.get_repo(&escaped_user, &escaped_name).await { Ok(x) => x, Err(_) => return Err(Error::ExternalRepoDoesNotExist.into_field_error()), }; if repo.private { return Err(Error::ExternalRepoDoesNotExist.into_field_error()); } let branches = match client.get_repo_branches(&escaped_user, &escaped_name).await { Ok(x) => x, Err(_) => return Err(Error::Internal.into_field_error()), }; if !branches .into_iter() .any(|x| x.name == CONFIG.gitea_pull_branch) { return Err(Error::RepoPullBranchDoesNotExist.into_field_error()); } } Err(_) => return Err(Error::Internal.into_field_error()), } let user_id = match user_id { Some(x) => x, None => diesel::insert_into(db::schema::users::table) .values(db::models::NewUser { name: &input.user }) .returning(db::schema::users::id) .get_result::(db_conn) .into_field_result()?, }; let id = diesel::insert_into(db::schema::repositories::table) .values(db::models::NewRepository { user_id, name: &input.name, }) .returning(db::schema::repositories::id) .get_result::(db_conn) .into_field_result()?; let worker_conn = context.get_worker_conn().await?; worker_conn .send_task(worker::get_repo::get_repo::new(id)) .await .into_field_result()?; context .loaders .repository .try_load(id) .await .map_err(|_| Error::Internal.into_field_error()) } async fn delete_repository(context: &Context, id: scalars::Uuid) -> FieldResult { if !context.logged_in { return Err(Error::Unauthenticated.into_field_error()); } let db_conn = &mut context.get_db_conn()?; if diesel::select(diesel::dsl::not(diesel::dsl::exists( db::schema::repositories::table.filter(db::schema::repositories::id.eq(*id)), ))) .get_result::(db_conn) .into_field_result()? { return Err(Error::DoesNotExist.into_field_error()); } let worker_conn = context.get_worker_conn().await?; worker_conn .send_task(worker::delete_repo::delete_repo::new(*id)) .await .into_field_result()?; Ok(true) } } pub type Schema = RootNode<'static, Query, Mutation, EmptySubscription>; pub fn schema() -> Schema { Schema::new(Query, Mutation, EmptySubscription::new()) }