gitdataai/lib/service/git/contents.rs

245 lines
7.4 KiB
Rust

use db::sqlx;
use git::rpc::{proto as p, proto::blob_service_client::BlobServiceClient};
use session::Session;
use crate::{AppService, error::AppError, git::rpc_err};
#[derive(Debug, Clone, serde::Serialize, utoipa::ToSchema)]
pub struct ContentResponse {
pub path: String,
pub name: String,
#[serde(rename = "type")]
pub content_type: String,
pub size: i64,
pub encoding: Option<String>,
pub content: Option<String>,
}
#[derive(Debug, Clone, serde::Deserialize, utoipa::ToSchema)]
pub struct CreateContent {
pub message: String,
pub content: String,
pub branch: Option<String>,
}
#[derive(Debug, Clone, serde::Deserialize, utoipa::ToSchema)]
pub struct UpdateContent {
pub message: String,
pub content: String,
pub sha: String,
pub branch: Option<String>,
}
impl AppService {
pub async fn git_contents_get_by_name(
&self,
ctx: &Session,
wk: &str,
repo: &str,
path: &str,
ref_name: Option<&str>,
) -> Result<ContentResponse, AppError> {
let _ = self.git_require_member(ctx, wk, repo).await?;
self.git_contents_get(ctx, wk, repo, path, ref_name).await
}
pub async fn git_contents_create_by_name(
&self,
ctx: &Session,
wk: &str,
repo: &str,
path: &str,
params: CreateContent,
) -> Result<ContentResponse, AppError> {
let _ = self.git_require_member(ctx, wk, repo).await?;
self.git_contents_create(ctx, wk, repo, path, params).await
}
pub async fn git_contents_update_by_name(
&self,
ctx: &Session,
wk: &str,
repo: &str,
path: &str,
params: UpdateContent,
) -> Result<ContentResponse, AppError> {
let _ = self.git_require_member(ctx, wk, repo).await?;
self.git_contents_update(ctx, wk, repo, path, params).await
}
pub async fn git_contents_delete_by_name(
&self,
ctx: &Session,
wk: &str,
repo: &str,
path: &str,
msg: &str,
sha: &str,
branch: Option<&str>,
) -> Result<(), AppError> {
let _ = self.git_require_member(ctx, wk, repo).await?;
self.git_contents_delete(ctx, wk, repo, path, msg, sha, branch)
.await
}
}
impl AppService {
pub async fn git_contents_get(
&self,
ctx: &Session,
wk_name: &str,
repo_name: &str,
path: &str,
_ref_name: Option<&str>,
) -> Result<ContentResponse, AppError> {
let repo = self.git_require_member(ctx, wk_name, repo_name).await?;
let mut blob_client = BlobServiceClient::new(self.git.clone());
let empty_oid = p::ObjectId {
value: String::new(),
};
let resp = blob_client
.blob_load(tonic::Request::new(p::BlobLoadRequest {
repo_id: repo.id.to_string(),
id: Some(empty_oid.clone()),
path: path.to_string(),
}))
.await
.map_err(rpc_err)?
.into_inner();
let is_binary = blob_client
.blob_is_binary(tonic::Request::new(p::BlobIsBinaryRequest {
repo_id: repo.id.to_string(),
id: Some(empty_oid.clone()),
}))
.await
.map(|r| r.into_inner().is_binary)
.unwrap_or(false);
let size_resp = blob_client
.blob_size(tonic::Request::new(p::BlobSizeRequest {
repo_id: repo.id.to_string(),
id: Some(empty_oid),
path: String::new(),
}))
.await
.map_err(rpc_err)?
.into_inner();
let blob_data = resp.blob;
let size = size_resp.size as i64;
let (encoding, content) = if is_binary {
(Some("base64".to_string()), Some(base64_encode(&blob_data)))
} else {
(None, Some(String::from_utf8_lossy(&blob_data).to_string()))
};
Ok(ContentResponse {
path: path.to_string(),
name: path.rsplit('/').next().unwrap_or(path).to_string(),
content_type: "file".to_string(),
size,
encoding,
content,
})
}
pub async fn git_contents_create(
&self,
ctx: &Session,
wk_name: &str,
repo_name: &str,
path: &str,
params: CreateContent,
) -> Result<ContentResponse, AppError> {
let repo = self.git_require_member(ctx, wk_name, repo_name).await?;
let user = crate::session_user(ctx)?;
let user_model = sqlx::query_as::<_, model::users::UserModel>(
"SELECT id, username, display_name, avatar_url, website_url, allow_use, can_search, \
last_sign_in_at, created_at, updated_at FROM \"user\" WHERE id = $1",
)
.bind(user)
.fetch_optional(self.db.reader())
.await
.map_err(|e| AppError::DatabaseError(e.to_string()))?
.ok_or(AppError::UserNotFound)?;
let display = user_model.display_name.clone();
let username = user_model.username.clone();
let author_name = if display.is_empty() {
username.clone()
} else {
display
};
let file_size = params.content.len() as i64;
let content_bytes = params.content.clone().into_bytes();
let mut client = p::commit_service_client::CommitServiceClient::new(
self.git.clone(),
);
let resp = client
.create_commit(tonic::Request::new(p::CreateCommitRequest {
repo_id: repo.id.to_string(),
branch: params
.branch
.unwrap_or_else(|| repo.default_branch.clone()),
message: params.message,
author_name: author_name.clone(),
author_email: format!("{username}@gitdata.ai"),
committer_name: "redpanda".to_string(),
committer_email: "redpanda@gitdata.ai".to_string(),
files: vec![p::FileChange {
path: path.to_string(),
content: content_bytes,
}],
}))
.await
.map_err(rpc_err)?
.into_inner();
let _oid = resp.oid.map(|o| o.value).unwrap_or_default();
Ok(ContentResponse {
path: path.to_string(),
name: path.rsplit('/').next().unwrap_or(path).to_string(),
content_type: "file".to_string(),
size: file_size,
encoding: None,
content: Some(params.content),
})
}
pub async fn git_contents_update(
&self,
_ctx: &Session,
_wk: &str,
_repo: &str,
_path: &str,
_params: UpdateContent,
) -> Result<ContentResponse, AppError> {
Err(AppError::InternalServerError(
"contents update not yet implemented".to_string(),
))
}
pub async fn git_contents_delete(
&self,
_ctx: &Session,
_wk: &str,
_repo: &str,
_path: &str,
_message: &str,
_sha: &str,
_branch: Option<&str>,
) -> Result<(), AppError> {
Err(AppError::InternalServerError(
"contents delete not yet implemented".to_string(),
))
}
}
fn base64_encode(data: &[u8]) -> String {
use base64::Engine as _;
base64::engine::general_purpose::STANDARD.encode(data)
}