gitea_pages/backend/src/api/mod.rs

228 lines
7.4 KiB
Rust

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<models::user::User> {
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<models::user::User> {
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::<Uuid>(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<Vec<models::user::User>> {
let db_conn = &mut context.get_db_conn()?;
let ids = db::schema::users::table
.select(db::schema::users::id)
.load::<Uuid>(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<models::repository::Repository> {
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<Vec<models::repository::Repository>> {
let db_conn = &mut context.get_db_conn()?;
let ids = db::schema::repositories::table
.select(db::schema::repositories::id)
.load::<Uuid>(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<models::repository::Repository> {
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::<Uuid>(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::<bool>(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::<Uuid>(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::<Uuid>(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<bool> {
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::<bool>(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<Context>>;
pub fn schema() -> Schema {
Schema::new(Query, Mutation, EmptySubscription::new())
}