use model::{ repos::{repo::RepoModel, repo_history_name::RepoHistoryNameModel}, users::user::UserModel, workspace::{ wk_history_name::WkHistoryNameModel, wk_member::WkMemberModel, workspace::WorkspaceModel, }, }; use uuid::Uuid; use crate::{ AppGitState, errors::{GitError, GitResult}, }; #[derive(Clone, Debug)] pub struct DbRepoStatus { pub repo: RepoModel, pub wk: WorkspaceModel, } #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum MemberRole { Owner, Admin, Member, } impl MemberRole { pub fn can_write(self) -> bool { matches!(self, Self::Owner | Self::Admin) } } impl AppGitState { pub async fn repo( &self, wk_name: String, repo_name: String, ) -> GitResult { let wk = self.resolve_wk(&wk_name).await?; let repo = self.resolve_repo(wk.id, &repo_name).await?; Ok(DbRepoStatus { repo, wk }) } pub async fn member_check( &self, status: &DbRepoStatus, user: &UserModel, ) -> GitResult> { self.member_check_by_user_id(status, user.id).await } pub async fn member_check_by_user_id( &self, status: &DbRepoStatus, user_id: Uuid, ) -> GitResult> { let member = db::sqlx::query_as::<_, WkMemberModel>( "SELECT wk, \"user\", owner, admin, join_at, leave_at \ FROM wk_member \ WHERE wk = $1 AND \"user\" = $2 AND leave_at IS NULL", ) .bind(status.wk.id) .bind(user_id) .fetch_optional(self.db.reader()) .await?; Ok(member.map(|member| { if member.owner { MemberRole::Owner } else if member.admin { MemberRole::Admin } else { MemberRole::Member } })) } async fn resolve_wk(&self, name: &str) -> GitResult { if let Some(wk) = db::sqlx::query_as::<_, WorkspaceModel>( "SELECT id, name, description, avatar_url, created_at \ FROM workspace \ WHERE name = $1", ) .bind(&name) .fetch_optional(self.db.reader()) .await? { return Ok(wk); } let Some(history) = db::sqlx::query_as::<_, WkHistoryNameModel>( "SELECT id, wk, name, changed_by, created_at \ FROM wk_history_name \ WHERE name = $1 \ ORDER BY created_at DESC \ LIMIT 1", ) .bind(&name) .fetch_optional(self.db.reader()) .await? else { return Err(GitError::RepoNotFound); }; db::sqlx::query_as::<_, WorkspaceModel>( "SELECT id, name, description, avatar_url, created_at \ FROM workspace \ WHERE id = $1", ) .bind(history.wk) .fetch_optional(self.db.reader()) .await? .ok_or(GitError::RepoNotFound) } async fn resolve_repo(&self, wk: Uuid, name: &str) -> GitResult { if let Some(repo) = db::sqlx::query_as::<_, RepoModel>( "SELECT id, wk, name, description, default_branch, visibility, size_bytes, \ is_archived, is_template, is_mirror, created_by, created_at, updated_at, deleted_at \ FROM repo \ WHERE wk = $1 AND name = $2 AND deleted_at IS NULL", ) .bind(wk) .bind(&name) .fetch_optional(self.db.reader()) .await? { return Ok(repo); } let Some(history) = db::sqlx::query_as::<_, RepoHistoryNameModel>( "SELECT h.id, h.repo, h.name, h.changed_by, h.created_at \ FROM repo_history_name h \ INNER JOIN repo r ON h.repo = r.id \ WHERE h.name = $1 AND r.wk = $2 AND r.deleted_at IS NULL \ ORDER BY h.created_at DESC \ LIMIT 1", ) .bind(&name) .bind(wk) .fetch_optional(self.db.reader()) .await? else { return Err(GitError::RepoNotFound); }; db::sqlx::query_as::<_, RepoModel>( "SELECT id, wk, name, description, default_branch, visibility, size_bytes, \ is_archived, is_template, is_mirror, created_by, created_at, updated_at, deleted_at \ FROM repo \ WHERE id = $1 AND wk = $2 AND deleted_at IS NULL", ) .bind(history.repo) .bind(wk) .fetch_optional(self.db.reader()) .await? .ok_or(GitError::RepoNotFound) } }