use async_graphql::{ ComplexObject, Context, EmptyMutation, EmptySubscription, ID, Object, Schema, SimpleObject, }; use serde_json::json; use uuid::Uuid; use crate::features::RepoService; pub type RepoSchema = Schema; pub fn build_schema(svc: RepoService) -> RepoSchema { let root = RootQuery(svc); Schema::build(root.clone(), EmptyMutation, EmptySubscription) .data(root) .finish() } #[derive(Clone)] pub struct RootQuery(pub RepoService); mod dto { use async_graphql::SimpleObject; #[derive(SimpleObject, Clone)] pub struct Permission { pub admin: bool, pub push: bool, pub pull: bool, } #[derive(SimpleObject, Clone)] pub struct InternalTracker { pub enable_time_tracker: bool, pub allow_only_contributors_to_track_time: bool, pub enable_issue_dependencies: bool, } #[derive(SimpleObject, Clone)] pub struct ExternalTracker { pub external_tracker_url: String, pub external_tracker_format: String, pub external_tracker_style: String, } #[derive(SimpleObject, Clone)] pub struct ExternalWiki { pub external_wiki_url: String, } #[derive(SimpleObject, Clone)] pub struct ChannelSummary { pub id: String, pub name: String, pub display_name: Option, } #[derive(SimpleObject, Clone)] pub struct SpaceSummary { pub id: String, pub name: String, pub display_name: Option, } #[derive(SimpleObject, Clone)] #[graphql(complex)] pub struct RepoView { pub id: String, pub channel_id: String, pub name: String, pub lower_name: String, pub description: Option, pub website: Option, pub default_branch: String, pub enable_lfs: bool, pub is_private: bool, pub is_archived: bool, pub is_fork: bool, pub is_template: bool, pub is_mirror: bool, pub empty: bool, pub size: i64, pub git_size: i64, pub lfs_size: i64, pub nums_watch: i32, pub nums_star: i32, pub nums_pr: i32, pub nums_commits: i32, pub nums_branches: i32, pub nums_tags: i32, pub allow_rebase: bool, pub allow_rebase_explicit: bool, pub allow_squash_merge: bool, pub allow_merge_commits: bool, pub allow_fast_forward_only_merge: bool, pub allow_manual_merge: bool, pub autodetect_manual_merge: bool, pub allow_merge_update: bool, pub allow_rebase_update: bool, pub has_code: bool, pub has_issues: bool, pub has_wiki: bool, pub has_pull_requests: bool, pub has_projects: bool, pub has_releases: bool, pub has_packages: bool, pub has_actions: bool, pub ignore_whitespace_conflicts: bool, pub full_name: String, pub html_url: String, pub url: String, pub link: String, pub ssh_url: String, pub clone_url: String, pub original_url: String, pub stars_count: i32, pub default_target_branch: Option, pub archived_at: Option, pub updated_at: String, pub mirror_interval: Option, pub created_at: String, pub last_pushed_at: Option, pub storage_limit_size: Option, pub avatar_url: Option, pub primary_language: Option, pub license_name: Option, pub object_format: String, pub topics: Vec, pub licenses: Vec, pub channel: Option, pub space: Option, } } pub use dto::{ ChannelSummary, ExternalTracker, ExternalWiki, InternalTracker, Permission, RepoView, SpaceSummary, }; #[Object] impl RootQuery { async fn repo( &self, ctx: &Context<'_>, channel_id: ID, repo_id: ID, ) -> async_graphql::Result { let _ = ctx; let cid = parse_uuid(&channel_id)?; let rid = parse_uuid(&repo_id)?; let view = self .0 .get_repo(actor_id(), cid, rid) .await .map_err(|e| async_graphql::Error::new(format!("{e:?}")))?; Ok(to_gql_repo(view)) } async fn repos( &self, _ctx: &Context<'_>, q: Option, channel_id: Option, limit: Option, ) -> async_graphql::Result> { let q = crate::features::lifecycle::ListRepoQuery { channel_id: channel_id.map(|x| parse_uuid(&x)).transpose()?, owner_user_id: None, is_private: None, is_archived: None, q, limit: limit.map(|x| x as u64), offset: None, }; let page = self .0 .list_repos(actor_id(), q) .await .map_err(|e| async_graphql::Error::new(format!("{e:?}")))?; Ok(page.items.into_iter().map(to_gql_repo).collect()) } } #[ComplexObject] impl RepoView { async fn metadata(&self) -> String { let _ = self; json!({ "source": "lib/repo" }).to_string() } } fn parse_uuid(id: &ID) -> async_graphql::Result { Uuid::parse_str(id.as_str()) .map_err(|e| async_graphql::Error::new(format!("invalid uuid: {e}"))) } fn actor_id() -> Uuid { Uuid::nil() } fn to_gql_repo(view: crate::features::lifecycle::RepoView) -> RepoView { let full_name = format!("channel/{}/{}", view.channel_id, view.name); let name = view.name.clone(); RepoView { id: view.id.to_string(), channel_id: view.channel_id.to_string(), name, lower_name: view.lower_name, description: view.description, website: view.website.clone(), default_branch: view.default_branch, enable_lfs: view.enable_lfs, is_private: view.is_private, is_archived: view.is_archived, is_fork: view.is_fork, is_template: view.is_template, is_mirror: view.is_mirror, empty: false, size: view.size, git_size: view.git_size, lfs_size: view.lfs_size, nums_watch: view.nums_watch, nums_star: view.nums_star, nums_pr: view.nums_pr, nums_commits: view.nums_commits, nums_branches: view.nums_branches, nums_tags: view.nums_tags, allow_rebase: view.allow_rebase, allow_rebase_explicit: view.allow_rebase_explicit, allow_squash_merge: view.allow_squash_merge, allow_merge_commits: view.allow_merge_commits, allow_fast_forward_only_merge: false, allow_manual_merge: false, autodetect_manual_merge: false, allow_merge_update: false, allow_rebase_update: false, has_code: true, has_issues: false, has_wiki: false, has_pull_requests: false, has_projects: false, has_releases: false, has_packages: false, has_actions: false, ignore_whitespace_conflicts: false, full_name, html_url: String::new(), url: String::new(), link: String::new(), ssh_url: String::new(), clone_url: String::new(), original_url: String::new(), stars_count: view.nums_star, default_target_branch: None, archived_at: view.archived_at, updated_at: view .last_pushed_at .clone() .unwrap_or_else(|| view.created_at.clone()), mirror_interval: None, created_at: view.created_at, last_pushed_at: view.last_pushed_at, storage_limit_size: view.storage_limit_size, avatar_url: view.avatar_url, primary_language: view.primary_language, license_name: view.license_name.clone(), object_format: view.object_format.clone(), topics: Vec::new(), licenses: view.license_name.into_iter().collect(), channel: None, space: None, } }