gitdataai/lib/service/git
zhenyi 779e4eae2f feat(channel): add article feed and composer with room type support
- Add ArticleFeed component for article-based channels
- Implement ArticleComposer with draft persistence
- Add Newspaper icon for article room type
- Update ChannelPage to conditionally render article feed vs message view
- Add article-related API endpoints and models
- Reset thread view when switching rooms
- Add room type check in channel sidebar
- Update CSS to hide scrollbars globally
- Add gRPC message size limit configuration
- Fix git diff tree handling
2026-05-31 03:09:49 +08:00
..
archive.rs feat: 1.0 2026-05-30 01:38:40 +08:00
blame.rs feat: 1.0 2026-05-30 01:38:40 +08:00
blob.rs feat: 1.0 2026-05-30 01:38:40 +08:00
branch.rs feat: 1.0 2026-05-30 01:38:40 +08:00
commit_status.rs actor(visibilityref): update function visibility and formatting across modules 2026-05-30 22:54:09 +08:00
commit.rs feat: 1.0 2026-05-30 01:38:40 +08:00
compare.rs actor(visibilityref): update function visibility and formatting across modules 2026-05-30 22:54:09 +08:00
contents.rs actor(visibilityref): update function visibility and formatting across modules 2026-05-30 22:54:09 +08:00
contributor.rs feat: 1.0 2026-05-30 01:38:40 +08:00
diff.rs feat(channel): add article feed and composer with room type support 2026-05-31 03:09:49 +08:00
fork.rs feat: 1.0 2026-05-30 01:38:40 +08:00
init.rs actor(visibilityref): update function visibility and formatting across modules 2026-05-30 22:54:09 +08:00
language.rs feat: 1.0 2026-05-30 01:38:40 +08:00
mod.rs actor(visibilityref): update function visibility and formatting across modules 2026-05-30 22:54:09 +08:00
protect.rs actor(visibilityref): update function visibility and formatting across modules 2026-05-30 22:54:09 +08:00
readme.rs actor(visibilityref): update function visibility and formatting across modules 2026-05-30 22:54:09 +08:00
refs.rs actor(visibilityref): update function visibility and formatting across modules 2026-05-30 22:54:09 +08:00
release.rs actor(visibilityref): update function visibility and formatting across modules 2026-05-30 22:54:09 +08:00
repo.rs actor(visibilityref): update function visibility and formatting across modules 2026-05-30 22:54:09 +08:00
star.rs actor(visibilityref): update function visibility and formatting across modules 2026-05-30 22:54:09 +08:00
tag.rs feat: 1.0 2026-05-30 01:38:40 +08:00
tree.rs feat: 1.0 2026-05-30 01:38:40 +08:00
watch.rs actor(visibilityref): update function visibility and formatting across modules 2026-05-30 22:54:09 +08:00
webhook.rs actor(visibilityref): update function visibility and formatting across modules 2026-05-30 22:54:09 +08:00

use serde::{Deserialize, Serialize};
use session::Session;
use utoipa::ToSchema;

use crate::{AppService, error::AppError};

#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct ReadmeDto {
    pub content: String,
    pub html: String,
}

impl AppService {
    pub async fn git_repo_readme(
        &self,
        ctx: &Session,
        wk_name: &str,
        repo_name: &str,
    ) -> Result<Option<ReadmeDto>, AppError> {
        let repo = self.git_require_member(ctx, wk_name, repo_name).await?;

        let readme_names = [
            "README.md",
            "README.markdown",
            "README.txt",
            "README",
            "Readme.md",
            "readme.md",
        ];

        for name in &readme_names {
            match self
                .git_tree_entry_by_path_from_commit_for_readme(&repo.id, name)
                .await?
            {
                Some((content, oid)) => {
                    return self
                        .git_blob_load_for_readme(&repo, &content, &oid)
                        .await;
                }
                None => continue,
            }
        }

        Ok(None)
    }
}
impl AppService {
    async fn git_tree_entry_by_path_from_commit_for_readme(
        &self,
        repo_id: &uuid::Uuid,
        readme_name: &str,
    ) -> Result<Option<(String, String)>, AppError> {
        use crate::git::rpc_err;
        use git::rpc::proto as p;
        use git::rpc::proto::tree_service_client::TreeServiceClient;

        let mut client = TreeServiceClient::new(self.git.clone());
        let mut commit_client =
            git::rpc::proto::commit_service_client::CommitServiceClient::new(
                self.git.clone(),
            );
        let summary_resp = commit_client
            .commit_summary(tonic::Request::new(p::CommitSummaryRequest {
                repo_id: repo_id.to_string(),
            }))
            .await
            .map_err(rpc_err)?
            .into_inner();

        let head_commit = match summary_resp.summary.and_then(|s| s.head) {
            Some(c) => c,
            None => return Ok(None),
        };

        let tree_id = match head_commit.tree_id {
            Some(id) => id.value,
            None => return Ok(None),
        };

        let resp = client
            .tree_entry_by_path(tonic::Request::new(
                p::TreeEntryByPathRequest {
                    repo_id: repo_id.to_string(),
                    tree_oid: Some(p::ObjectId {
                        value: tree_id.clone(),
                    }),
                    path: readme_name.to_string(),
                },
            ))
            .await
            .map_err(rpc_err)?
            .into_inner();

        match resp.entry {
            Some(entry) => {
                let oid = entry.oid.map(|o| o.value).unwrap_or_default();
                if oid.is_empty() {
                    Ok(None)
                } else {
                    Ok(Some((readme_name.to_string(), oid)))
                }
            }
            None => Ok(None),
        }
    }
    async fn git_blob_load_for_readme(
        &self,
        repo: &model::repos::RepoModel,
        _path: &str,
        oid: &str,
    ) -> Result<Option<super::readme::ReadmeDto>, AppError> {
        use crate::git::rpc_err;
        use git::rpc::proto as p;
        use git::rpc::proto::blob_service_client::BlobServiceClient;

        let mut client = BlobServiceClient::new(self.git.clone());
        let resp = client
            .blob_load(tonic::Request::new(p::BlobLoadRequest {
                repo_id: repo.id.to_string(),
                id: Some(p::ObjectId {
                    value: oid.to_string(),
                }),
                path: String::new(),
            }))
            .await
            .map_err(rpc_err)?
            .into_inner();

        let content = String::from_utf8_lossy(&resp.blob).to_string();
        if content.is_empty() {
            return Ok(None);
        }

        let html = comrak::markdown_to_html(
            &content,
            &comrak::ComrakOptions::default(),
        );

        Ok(Some(super::readme::ReadmeDto { content, html }))
    }
}