From 2f5d3f22d493ab404254d1c29c47de5414b71672 Mon Sep 17 00:00:00 2001 From: "benjamin.747" Date: Wed, 31 Jul 2024 17:31:15 +0800 Subject: [PATCH 1/2] update lunar ui, add settings page --- gateway/src/api/oauth/github.rs | 4 +- lunar/src/app/api/fetcher.ts | 56 ++++++++++--- lunar/src/app/application-layout.tsx | 17 ++-- lunar/src/app/layout.tsx | 8 +- lunar/src/app/page.tsx | 93 +++++++++++++++------ lunar/src/app/repo/page.tsx | 34 ++++++++ lunar/src/app/settings/page.tsx | 35 ++++++++ lunar/src/components/catalyst/divider.tsx | 20 +++++ lunar/src/components/catalyst/heading.tsx | 27 +++++++ lunar/src/components/catalyst/input.tsx | 94 ++++++++++++++++++++++ lunar/src/components/catalyst/text.tsx | 40 +++++++++ moon/src/app/api/auth/github/user/route.ts | 3 +- moon/src/components/MergeList.tsx | 8 +- 13 files changed, 387 insertions(+), 52 deletions(-) create mode 100644 lunar/src/app/repo/page.tsx create mode 100644 lunar/src/app/settings/page.tsx create mode 100644 lunar/src/components/catalyst/divider.tsx create mode 100644 lunar/src/components/catalyst/heading.tsx create mode 100644 lunar/src/components/catalyst/input.tsx create mode 100644 lunar/src/components/catalyst/text.tsx diff --git a/gateway/src/api/oauth/github.rs b/gateway/src/api/oauth/github.rs index 42b0ac6e8..dfaaf9b3a 100644 --- a/gateway/src/api/oauth/github.rs +++ b/gateway/src/api/oauth/github.rs @@ -22,8 +22,8 @@ const GITHUB_API_ENDPOINT: &str = "https://api.github.com"; impl OauthHandler for GithubOauthService { fn authorize_url(&self, params: &AuthorizeParams, state: &str) -> String { let auth_url = format!( - "https://github.com/login/oauth/authorize?client_id={}&redirect_uri={}&state={}", - self.client_id, params.redirect_uri, state + "{}/login/oauth/authorize?client_id={}&redirect_uri={}&state={}", + GITHUB_ENDPOINT, self.client_id, params.redirect_uri, state ); auth_url } diff --git a/lunar/src/app/api/fetcher.ts b/lunar/src/app/api/fetcher.ts index 46712c390..c6ac2113f 100644 --- a/lunar/src/app/api/fetcher.ts +++ b/lunar/src/app/api/fetcher.ts @@ -17,20 +17,28 @@ const fetchWithToken = async (url, token) => { }); }; -// const fetcher = async url => { -// const res = await fetch(url) - -// if (!res.ok) { -// const error = new Error('An error occurred while fetching the data.') -// error.info = await res.json() -// error.status = res.status -// throw error -// } -// return res.json() -// } +export class FetchError extends Error { + info: any; + status: number; -export function useUser(token) { + constructor(message: string, info: any, status: number) { + super(message); + this.info = info; + this.status = status; + } +} + +const fetcher = async url => { + const res = await fetch(url) + if (!res.ok) { + const error = new Error('An error occurred while fetching the data.') + const errorInfo = await res.json(); + throw new FetchError('An error occurred while fetching the data.', errorInfo, res.status); + } + return res.json() +} +export function useUser(token) { const { data, error, isLoading } = useSWR(token ? [`${endpoint}/auth/github/user`, token] : null, ([url, token]) => fetchWithToken(url, token), { dedupingInterval: 300000, // The request will not be repeated for 5 minutes }) @@ -39,4 +47,26 @@ export function useUser(token) { isLoading, isError: error, } -} \ No newline at end of file +} + +export function useTreeCommitInfo(path) { + const { data, error, isLoading } = useSWR(`${endpoint}/api/v1/tree/commit-info?path=${path}`, fetcher, { + dedupingInterval: 300000, + }) + return { + tree: data, + isTreeLoading: isLoading, + isTreeError: error, + } +} + +export function useBlobContent(path) { + const { data, error, isLoading } = useSWR(`${endpoint}/api/v1/blob?path=${path}`, fetcher, { + dedupingInterval: 300000, + }) + return { + blob: data, + isBlobLoading: isLoading, + isBlobError: error, + } +} diff --git a/lunar/src/app/application-layout.tsx b/lunar/src/app/application-layout.tsx index db092ab9f..65f447be9 100644 --- a/lunar/src/app/application-layout.tsx +++ b/lunar/src/app/application-layout.tsx @@ -39,6 +39,9 @@ import { SparklesIcon, Square2StackIcon, TicketIcon, + ChatBubbleLeftRightIcon, + CodeBracketSquareIcon, + ArchiveBoxArrowDownIcon, } from '@heroicons/react/20/solid' import { usePathname } from 'next/navigation' import { useState, useEffect } from 'react' @@ -142,16 +145,20 @@ export function ApplicationLayout({ Code & Issue - - + + AI Chat - + + + Repos + + Reminder - - + + Logs diff --git a/lunar/src/app/layout.tsx b/lunar/src/app/layout.tsx index 940c2af4f..73707af75 100644 --- a/lunar/src/app/layout.tsx +++ b/lunar/src/app/layout.tsx @@ -15,9 +15,11 @@ export default function Layout({ children }: { children: React.ReactNode }) { - - {children} - + + + {children} + + ) diff --git a/lunar/src/app/page.tsx b/lunar/src/app/page.tsx index 273db7085..3df4d7095 100644 --- a/lunar/src/app/page.tsx +++ b/lunar/src/app/page.tsx @@ -1,34 +1,77 @@ 'use client' -import React, { useEffect, useState } from 'react' -import DataList from '@/components/DataList' +import { Flex, Layout } from 'antd'; +import { Skeleton } from "antd"; +import CodeTable from '../../../moon/src/components/CodeTable'; +import MergeList from '../../../moon/src/components/MergeList'; +import { useTreeCommitInfo, useBlobContent } from '@/app/api/fetcher'; +const { Content } = Layout; -export default function HomePage() { - const [repo_list, setRepoList] = useState([]); +const contentStyle: React.CSSProperties = { + textAlign: 'center', + minHeight: 500, + lineHeight: '120px', + border: 'solid', + backgroundColor: '#fff', +}; + +const layoutStyle = { + minHeight: 500, + borderRadius: 8, + overflow: 'hidden', + width: 'calc(50% - 8px)', + maxWidth: 'calc(50% - 8px)', +}; - useEffect(() => { - const fetchData = async () => { - try { - let repo_list = await getRepoList(); - setRepoList(repo_list); - } catch (error) { - console.error('Error fetching data:', error); - } - }; - fetchData(); - }, []); +const mrList = [ + { + "id": 2278111790530821, + "title": "", + "status": "open", + "open_timestamp": 1721181311, + "merge_timestamp": null + }, + { + "id": 2277296526688517, + "title": "", + "status": "merged", + "open_timestamp": 1721131551, + "merge_timestamp": 1721131565 + }, + { + "id": 2276683620876549, + "title": "", + "status": "merged", + "open_timestamp": 1721094142, + "merge_timestamp": 1721117874 + } +]; + +export default function HomePage() { + const { tree, isTreeLoading, isTreeError } = useTreeCommitInfo("/"); + const { blob, isBlobLoading, isBlobError } = useBlobContent("/README.md"); return ( -
- -
+ + + {(isTreeLoading || isBlobLoading) && + + } + { + (!isTreeLoading && !isBlobLoading) && + + } + + + {(isTreeLoading || isBlobLoading) && + + } + {(!isTreeLoading && !isBlobLoading) && + + } + {/* */} + + ) } - -async function getRepoList() { - const res = await fetch(`api/relay/repo_list`); - const response = await res.json(); - const repo_list = response.data; - return repo_list -} \ No newline at end of file diff --git a/lunar/src/app/repo/page.tsx b/lunar/src/app/repo/page.tsx new file mode 100644 index 000000000..273db7085 --- /dev/null +++ b/lunar/src/app/repo/page.tsx @@ -0,0 +1,34 @@ +'use client' + +import React, { useEffect, useState } from 'react' +import DataList from '@/components/DataList' + + +export default function HomePage() { + const [repo_list, setRepoList] = useState([]); + + useEffect(() => { + const fetchData = async () => { + try { + let repo_list = await getRepoList(); + setRepoList(repo_list); + } catch (error) { + console.error('Error fetching data:', error); + } + }; + fetchData(); + }, []); + + return ( +
+ +
+ ) +} + +async function getRepoList() { + const res = await fetch(`api/relay/repo_list`); + const response = await res.json(); + const repo_list = response.data; + return repo_list +} \ No newline at end of file diff --git a/lunar/src/app/settings/page.tsx b/lunar/src/app/settings/page.tsx new file mode 100644 index 000000000..4b43dffb4 --- /dev/null +++ b/lunar/src/app/settings/page.tsx @@ -0,0 +1,35 @@ +'use client' + +import { Button } from '@/components/catalyst/button' +import { Divider } from '@/components/catalyst/divider' +import { Heading, Subheading } from '@/components/catalyst/heading' +import { Input } from '@/components/catalyst/input' +import { Text } from '@/components/catalyst/text' + +export default function Settings() { + return ( +
+ Settings + + +
+
+ ZTM Server IP Address + This will be restart ZTM server. +
+
+ +
+
+ + + +
+ + +
+ + ) +} diff --git a/lunar/src/components/catalyst/divider.tsx b/lunar/src/components/catalyst/divider.tsx new file mode 100644 index 000000000..dd84b1007 --- /dev/null +++ b/lunar/src/components/catalyst/divider.tsx @@ -0,0 +1,20 @@ +import clsx from 'clsx' + +export function Divider({ + soft = false, + className, + ...props +}: { soft?: boolean } & React.ComponentPropsWithoutRef<'hr'>) { + return ( +
+ ) +} diff --git a/lunar/src/components/catalyst/heading.tsx b/lunar/src/components/catalyst/heading.tsx new file mode 100644 index 000000000..5ccc9d134 --- /dev/null +++ b/lunar/src/components/catalyst/heading.tsx @@ -0,0 +1,27 @@ +import clsx from 'clsx' + +type HeadingProps = { level?: 1 | 2 | 3 | 4 | 5 | 6 } & React.ComponentPropsWithoutRef< + 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' +> + +export function Heading({ className, level = 1, ...props }: HeadingProps) { + let Element: `h${typeof level}` = `h${level}` + + return ( + + ) +} + +export function Subheading({ className, level = 2, ...props }: HeadingProps) { + let Element: `h${typeof level}` = `h${level}` + + return ( + + ) +} diff --git a/lunar/src/components/catalyst/input.tsx b/lunar/src/components/catalyst/input.tsx new file mode 100644 index 000000000..03addaa33 --- /dev/null +++ b/lunar/src/components/catalyst/input.tsx @@ -0,0 +1,94 @@ +import * as Headless from '@headlessui/react' +import clsx from 'clsx' +import React, { forwardRef } from 'react' + +export function InputGroup({ children }: React.ComponentPropsWithoutRef<'span'>) { + return ( + [data-slot=icon]]:pointer-events-none [&>[data-slot=icon]]:absolute [&>[data-slot=icon]]:top-3 [&>[data-slot=icon]]:z-10 [&>[data-slot=icon]]:size-5 sm:[&>[data-slot=icon]]:top-2.5 sm:[&>[data-slot=icon]]:size-4', + '[&>[data-slot=icon]:first-child]:left-3 sm:[&>[data-slot=icon]:first-child]:left-2.5 [&>[data-slot=icon]:last-child]:right-3 sm:[&>[data-slot=icon]:last-child]:right-2.5', + '[&>[data-slot=icon]]:text-zinc-500 dark:[&>[data-slot=icon]]:text-zinc-400' + )} + > + {children} + + ) +} + +const dateTypes = ['date', 'datetime-local', 'month', 'time', 'week'] +type DateType = (typeof dateTypes)[number] + +export const Input = forwardRef(function Input( + { + className, + ...props + }: { + className?: string + type?: 'email' | 'number' | 'password' | 'search' | 'tel' | 'text' | 'url' | DateType + } & Omit, + ref: React.ForwardedRef +) { + return ( + + + + ) +}) diff --git a/lunar/src/components/catalyst/text.tsx b/lunar/src/components/catalyst/text.tsx new file mode 100644 index 000000000..906f7a698 --- /dev/null +++ b/lunar/src/components/catalyst/text.tsx @@ -0,0 +1,40 @@ +import clsx from 'clsx' +import { Link } from './link' + +export function Text({ className, ...props }: React.ComponentPropsWithoutRef<'p'>) { + return ( +

+ ) +} + +export function TextLink({ className, ...props }: React.ComponentPropsWithoutRef) { + return ( + + ) +} + +export function Strong({ className, ...props }: React.ComponentPropsWithoutRef<'strong'>) { + return +} + +export function Code({ className, ...props }: React.ComponentPropsWithoutRef<'code'>) { + return ( + + ) +} diff --git a/moon/src/app/api/auth/github/user/route.ts b/moon/src/app/api/auth/github/user/route.ts index c8cdbb676..e5c0ca266 100644 --- a/moon/src/app/api/auth/github/user/route.ts +++ b/moon/src/app/api/auth/github/user/route.ts @@ -2,8 +2,6 @@ import { cookies } from "next/headers"; -export const revalidate = 0 - const endpoint = process.env.NEXT_PUBLIC_API_URL; export async function GET(request: Request) { @@ -11,6 +9,7 @@ export async function GET(request: Request) { const access_token = cookieStore.get('access_token'); const res = await fetch(`${endpoint}/auth/github/user`, { + next: { revalidate: 300 }, headers: { 'Authorization': `Bearer ${access_token?.value}`, 'Content-Type': 'application/json', diff --git a/moon/src/components/MergeList.tsx b/moon/src/components/MergeList.tsx index d7eeb8e54..4c16d1ba0 100644 --- a/moon/src/components/MergeList.tsx +++ b/moon/src/components/MergeList.tsx @@ -9,7 +9,7 @@ interface MrInfoItem { title: string, status: string, open_timestamp: number, - merge_timestamp: number, + merge_timestamp: number | null, } interface MergeListProps { @@ -34,7 +34,11 @@ const MergeList: React.FC = ({ mrList }) => { case 'open': return `MergeRequest opened on ${format(fromUnixTime(Number(item.open_timestamp)), 'MMM d')} by Admin`; case 'merged': - return `MergeRequest by Admin was merged ${formatDistance(fromUnixTime(item.merge_timestamp), new Date(), { addSuffix: true })}`; + if (item.merge_timestamp !== null) { + return `MergeRequest by Admin was merged ${formatDistance(fromUnixTime(item.merge_timestamp), new Date(), { addSuffix: true })}`; + } else { + return ""; + } case 'closed': return closed; } From 15c4f84a4d69f875250d9ba458da0c53fc3e09fc Mon Sep 17 00:00:00 2001 From: "benjamin.747" Date: Thu, 1 Aug 2024 18:16:02 +0800 Subject: [PATCH 2/2] add repo exist check in protocol --- ceres/Cargo.toml | 1 - ceres/src/http/mod.rs | 2 - ceres/src/lib.rs | 1 - ceres/src/protocol/mod.rs | 34 +++-- ceres/src/protocol/smart.rs | 20 +-- common/src/errors.rs | 11 ++ gateway/Cargo.toml | 2 + .../src/git_protocol/http.rs | 127 +++++++++++------- gateway/src/git_protocol/mod.rs | 3 +- gateway/src/git_protocol/ssh.rs | 11 +- gateway/src/https_server.rs | 14 +- 11 files changed, 140 insertions(+), 86 deletions(-) delete mode 100644 ceres/src/http/mod.rs rename ceres/src/http/handler.rs => gateway/src/git_protocol/http.rs (59%) diff --git a/ceres/Cargo.toml b/ceres/Cargo.toml index cacc82afc..7f79dc49b 100644 --- a/ceres/Cargo.toml +++ b/ceres/Cargo.toml @@ -20,7 +20,6 @@ anyhow = { workspace = true } tokio = { workspace = true, features = ["net"] } tokio-stream = { workspace = true } axum = { workspace = true } -async-stream = { workspace = true } tracing = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } diff --git a/ceres/src/http/mod.rs b/ceres/src/http/mod.rs deleted file mode 100644 index 3841cc80c..000000000 --- a/ceres/src/http/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ - -pub mod handler; \ No newline at end of file diff --git a/ceres/src/lib.rs b/ceres/src/lib.rs index 94cd55038..e0c3bda5c 100644 --- a/ceres/src/lib.rs +++ b/ceres/src/lib.rs @@ -1,5 +1,4 @@ pub mod api_service; -pub mod http; pub mod lfs; pub mod pack; pub mod protocol; diff --git a/ceres/src/protocol/mod.rs b/ceres/src/protocol/mod.rs index 739daee3b..0b8f35795 100644 --- a/ceres/src/protocol/mod.rs +++ b/ceres/src/protocol/mod.rs @@ -2,7 +2,10 @@ use core::fmt; use std::{path::PathBuf, str::FromStr, sync::Arc}; use callisto::db_enums::RefType; -use common::{errors::MegaError, utils::ZERO_ID}; +use common::{ + errors::{MegaError, ProtocolError}, + utils::ZERO_ID, +}; use jupiter::context::Context; use venus::{import_repo::import_refs::RefCommand, import_repo::repo::Repo}; @@ -16,8 +19,7 @@ pub struct SmartProtocol { pub capabilities: Vec, pub path: PathBuf, pub command_list: Vec, - // only needed in ssh protocal - pub service_type: ServiceType, + pub service_type: Option, pub context: Context, } @@ -125,7 +127,7 @@ impl SmartProtocol { capabilities: Vec::new(), path, command_list: Vec::new(), - service_type: ServiceType::ReceivePack, + service_type: None, context, } } @@ -137,29 +139,35 @@ impl SmartProtocol { capabilities: Vec::new(), path: PathBuf::new(), command_list: Vec::new(), - service_type: ServiceType::ReceivePack, + service_type: None, context, } } - pub async fn pack_handler(&self) -> Arc { + pub async fn pack_handler(&self) -> Result, ProtocolError> { let import_dir = self.context.config.monorepo.import_dir.clone(); if self.path.starts_with(import_dir.clone()) && self.path != import_dir { let storage = self.context.services.git_db_storage.clone(); - let path_str = self.path.to_str().unwrap(); let model = storage.find_git_repo_exact_match(path_str).await.unwrap(); let repo = if let Some(repo) = model { repo.into() } else { - let repo = Repo::new(self.path.clone(), false); - storage.save_git_repo(repo.clone()).await.unwrap(); - repo + match self.service_type.unwrap() { + ServiceType::UploadPack => { + return Err(ProtocolError::NotFound("Repository not found.".to_owned())) + } + ServiceType::ReceivePack => { + let repo = Repo::new(self.path.clone(), false); + storage.save_git_repo(repo.clone()).await.unwrap(); + repo + } + } }; - Arc::new(ImportRepo { + Ok(Arc::new(ImportRepo { context: self.context.clone(), repo, - }) + })) } else { let mut res = MonoRepo { context: self.context.clone(), @@ -175,7 +183,7 @@ impl SmartProtocol { res.from_hash = Some(command.old_id.clone()); res.to_hash = Some(command.new_id.clone()); } - Arc::new(res) + Ok(Arc::new(res)) } } } diff --git a/ceres/src/protocol/smart.rs b/ceres/src/protocol/smart.rs index c71094c5c..d9e55fc55 100644 --- a/ceres/src/protocol/smart.rs +++ b/ceres/src/protocol/smart.rs @@ -6,6 +6,7 @@ use futures::Stream; use tokio_stream::wrappers::ReceiverStream; use callisto::db_enums::RefType; +use common::errors::ProtocolError; use crate::protocol::ZERO_ID; use crate::protocol::{ @@ -57,10 +58,10 @@ impl SmartProtocol { /// Tracing information is logged regarding the response packet line stream. /// /// Finally, the constructed packet line stream is returned. - pub async fn git_info_refs(&self) -> BytesMut { - let pack_handler = self.pack_handler().await; + pub async fn git_info_refs(&self) -> Result { + let pack_handler = self.pack_handler().await?; - let service_type = self.service_type; + let service_type = self.service_type.unwrap(); // The stream MUST include capability declarations behind a NUL on the first ref. let (head_hash, git_refs) = pack_handler.head_hash().await; @@ -82,14 +83,14 @@ impl SmartProtocol { } let pkt_line_stream = self.build_smart_reply(&ref_list, service_type.to_string()); tracing::debug!("git_info_refs response: {:?}", pkt_line_stream); - pkt_line_stream + Ok(pkt_line_stream) } pub async fn git_upload_pack( &mut self, upload_request: &mut Bytes, - ) -> Result<(ReceiverStream>, BytesMut)> { - let pack_handler = self.pack_handler().await; + ) -> Result<(ReceiverStream>, BytesMut), ProtocolError> { + let pack_handler = self.pack_handler().await?; let mut want: Vec = Vec::new(); let mut have: Vec = Vec::new(); @@ -207,10 +208,10 @@ impl SmartProtocol { pub async fn git_receive_pack_stream( &mut self, data_stream: Pin> + Send>>, - ) -> Result { + ) -> Result { // After receiving the pack data from the sender, the receiver sends a report let mut report_status = BytesMut::new(); - let pack_handler = self.pack_handler().await; + let pack_handler = self.pack_handler().await?; //1. unpack progress let receiver = pack_handler .unpack_stream(&self.context.config.pack, data_stream) @@ -223,7 +224,8 @@ impl SmartProtocol { let handle = tokio::runtime::Handle::current(); handle.block_on(async { ph_clone.handle_receiver(receiver).await }) }) - .await.unwrap(); + .await + .unwrap(); // write "unpack ok\n to report" add_pkt_line_string(&mut report_status, "unpack ok\n".to_owned()); diff --git a/common/src/errors.rs b/common/src/errors.rs index c715f2bd4..82ab537ec 100644 --- a/common/src/errors.rs +++ b/common/src/errors.rs @@ -75,5 +75,16 @@ pub enum GitLFSError { GeneralError(String), } + +#[derive(Debug, Error)] +pub enum ProtocolError { + #[error("{0}")] + IO(#[from] std::io::Error), + #[error("Authentication failed: {0}")] + Deny(String), + #[error("Repository not found: {0}")] + NotFound(String), +} + #[cfg(test)] mod tests {} diff --git a/gateway/Cargo.toml b/gateway/Cargo.toml index 8317beed3..cc53b8326 100644 --- a/gateway/Cargo.toml +++ b/gateway/Cargo.toml @@ -39,6 +39,8 @@ tower-http = { workspace = true, features = [ ] } axum-extra = { workspace = true, features = ["typed-header"]} tokio = { workspace = true, features = ["net"] } +tokio-stream = { workspace = true } +async-stream = { workspace = true } reqwest = { workspace = true, features = ["json"] } uuid = { workspace = true, features = ["v4"] } regex = "1.10.4" diff --git a/ceres/src/http/handler.rs b/gateway/src/git_protocol/http.rs similarity index 59% rename from ceres/src/http/handler.rs rename to gateway/src/git_protocol/http.rs index 7e8522628..30d19b8ee 100644 --- a/ceres/src/http/handler.rs +++ b/gateway/src/git_protocol/http.rs @@ -1,18 +1,17 @@ -use std::collections::HashMap; use std::convert::Infallible; use anyhow::Result; use axum::body::Body; -use axum::http::response::Builder; -use axum::http::{Request, Response, StatusCode}; +use axum::http::{HeaderValue, Request, Response, StatusCode}; use bytes::{Bytes, BytesMut}; +use common::errors::ProtocolError; use futures::{stream, TryStreamExt}; use tokio::io::AsyncReadExt; use tokio_stream::StreamExt; use common::model::GetParams; -use crate::protocol::{smart, ServiceType, SmartProtocol}; +use ceres::protocol::{smart, ServiceType, SmartProtocol}; // # Discovering Reference // HTTP clients that support the "smart" protocol (or both the "smart" and "dumb" protocols) MUST @@ -25,11 +24,18 @@ pub async fn git_info_refs( mut pack_protocol: SmartProtocol, ) -> Result, (StatusCode, String)> { let service_name = params.service.unwrap(); - pack_protocol.service_type = service_name.parse::().unwrap(); - let resp = build_res_header(format!("application/x-{}-advertisement", service_name)); - let pkt_line_stream = pack_protocol.git_info_refs().await; - let body = Body::from(pkt_line_stream.freeze()); - Ok(resp.body(body).unwrap()) + pack_protocol.service_type = Some(service_name.parse::().unwrap()); + + let response = match pack_protocol.git_info_refs().await { + Ok(pkt_line_stream) => Response::builder() + .body(Body::from(pkt_line_stream.freeze())) + .unwrap(), + Err(err) => return Ok(handler_err(err)), + }; + + let content_type = format!("application/x-{}-advertisement", service_name); + let response = add_default_header(content_type, response); + Ok(response) } /// # Handles a Git upload pack request and prepares the response. @@ -66,37 +72,44 @@ pub async fn git_upload_pack( .await .unwrap(); tracing::debug!("bytes from client: {:?}", upload_request); - let (mut send_pack_data, protocol_buf) = pack_protocol + let response = match pack_protocol .git_upload_pack(&mut upload_request.freeze()) .await - .unwrap(); - - let resp = build_res_header("application/x-git-upload-pack-result".to_owned()); - - let body_stream = async_stream::stream! { - tracing::info!("send ack/nak message buf: {:?}", &protocol_buf); - yield Ok::<_, Infallible>(Bytes::copy_from_slice(&protocol_buf)); - // send packdata with sideband64k - while let Some(chunk) = send_pack_data.next().await { - let mut reader = chunk.as_slice(); - loop { - let mut temp = BytesMut::new(); - temp.reserve(65500); - let length = reader.read_buf(&mut temp).await.unwrap(); - if length == 0 { - break; + { + Ok((mut send_pack_data, protocol_buf)) => { + let body_stream = async_stream::stream! { + tracing::info!("send ack/nak message buf: {:?}", &protocol_buf); + yield Ok::<_, Infallible>(Bytes::copy_from_slice(&protocol_buf)); + // send packdata with sideband64k + while let Some(chunk) = send_pack_data.next().await { + let mut reader = chunk.as_slice(); + loop { + let mut temp = BytesMut::new(); + temp.reserve(65500); + let length = reader.read_buf(&mut temp).await.unwrap(); + if length == 0 { + break; + } + let bytes_out = pack_protocol.build_side_band_format(temp, length); + // tracing::info!("send pack file: length: {:?}", bytes_out.len()); + yield Ok::<_, Infallible>(bytes_out.freeze()); + } } - let bytes_out = pack_protocol.build_side_band_format(temp, length); - // tracing::info!("send pack file: length: {:?}", bytes_out.len()); - yield Ok::<_, Infallible>(bytes_out.freeze()); - } + let bytes_out = Bytes::from_static(smart::PKT_LINE_END_MARKER); + tracing::info!("send back pkt-flush line '0000', actually: {:?}", bytes_out); + yield Ok::<_, Infallible>(bytes_out); + }; + Response::builder() + .body(Body::from_stream(body_stream)) + .unwrap() } - let bytes_out = Bytes::from_static(smart::PKT_LINE_END_MARKER); - tracing::info!("send back pkt-flush line '0000', actually: {:?}", bytes_out); - yield Ok::<_, Infallible>(bytes_out); + Err(err) => return Ok(handler_err(err)), }; - let resp = resp.body(Body::from_stream(body_stream)).unwrap(); - Ok(resp) + let response = add_default_header( + String::from("application/x-git-upload-pack-result"), + response, + ); + Ok(response) } /// Handles the Git receive-pack protocol for receiving and processing data from a client. @@ -139,9 +152,12 @@ pub async fn git_receive_pack( } } tracing::info!("report status:{:?}", report_status); - let resp = build_res_header("application/x-git-receive-pack-result".to_owned()); - let resp = resp.body(Body::from(report_status)).unwrap(); - Ok(resp) + let response = Response::builder().body(Body::from(report_status)).unwrap(); + let response = add_default_header( + String::from("application/x-git-receive-pack-result"), + response, + ); + Ok(response) } // Function to find the subsequence in a slice @@ -152,19 +168,34 @@ fn search_subsequence(chunk: &[u8], search: &[u8]) -> Option { /// # Build Response headers for Smart Server. /// Clients MUST NOT reuse or revalidate a cached response. /// Servers MUST include sufficient Cache-Control headers to prevent caching of the response. -pub fn build_res_header(content_type: String) -> Builder { - let mut headers = HashMap::new(); - headers.insert("Content-Type".to_string(), content_type); - headers.insert( - "Cache-Control".to_string(), - "no-cache, max-age=0, must-revalidate".to_string(), +fn add_default_header(content_type: String, mut response: Response) -> Response { + response.headers_mut().insert( + "Content-Type", + HeaderValue::from_str(&content_type).unwrap(), + ); + response.headers_mut().insert( + "Cache-Control", + HeaderValue::from_static("no-cache, max-age=0, must-revalidate"), ); - let mut resp = Response::builder(); + response +} - for (key, val) in headers { - resp = resp.header(&key, val); +fn handler_err(err: ProtocolError) -> Response { + match err { + ProtocolError::NotFound(err) => Response::builder() + .status(StatusCode::NOT_FOUND) + .body(Body::from(err.to_string())) + .unwrap(), + ProtocolError::Deny(err) => Response::builder() + .status(StatusCode::UNAUTHORIZED) + .body(Body::from(err.to_string())) + .unwrap(), + _ => Response::builder() + .status(StatusCode::INTERNAL_SERVER_ERROR) + .body(Body::empty()) + .unwrap(), } - resp } + #[cfg(test)] mod tests {} diff --git a/gateway/src/git_protocol/mod.rs b/gateway/src/git_protocol/mod.rs index 6bc36a3c3..0550318fb 100644 --- a/gateway/src/git_protocol/mod.rs +++ b/gateway/src/git_protocol/mod.rs @@ -1 +1,2 @@ -pub mod ssh; \ No newline at end of file +pub mod ssh; +pub mod http; \ No newline at end of file diff --git a/gateway/src/git_protocol/ssh.rs b/gateway/src/git_protocol/ssh.rs index 8110d2008..c6d220b19 100644 --- a/gateway/src/git_protocol/ssh.rs +++ b/gateway/src/git_protocol/ssh.rs @@ -90,8 +90,9 @@ impl server::Handler for SshServer { ); match command[0] { "git-upload-pack" | "git-receive-pack" => { - smart_protocol.service_type = ServiceType::from_str(command[0]).unwrap(); - let res = smart_protocol.git_info_refs().await; + smart_protocol.service_type = Some(ServiceType::from_str(command[0]).unwrap()); + // TODO handler ProtocolError + let res = smart_protocol.git_info_refs().await.unwrap(); self.smart_protocol = Some(smart_protocol); session.data(channel, res.to_vec().into()); session.channel_success(channel); @@ -161,8 +162,8 @@ impl server::Handler for SshServer { // String::from_utf8_lossy(data), data.len() ); - - match smart_protocol.service_type { + let service_type = smart_protocol.service_type.unwrap(); + match service_type { ServiceType::UploadPack => { self.handle_upload_pack(channel, data, session).await; } @@ -180,7 +181,7 @@ impl server::Handler for SshServer { session: &mut Session, ) -> Result<(), Self::Error> { if let Some(smart_protocol) = self.smart_protocol.as_mut() { - if smart_protocol.service_type == ServiceType::ReceivePack { + if smart_protocol.service_type.unwrap() == ServiceType::ReceivePack { self.handle_receive_pack(channel, session).await; }; } diff --git a/gateway/src/https_server.rs b/gateway/src/https_server.rs index 96a008151..6d0cad362 100644 --- a/gateway/src/https_server.rs +++ b/gateway/src/https_server.rs @@ -23,7 +23,7 @@ use tower_http::decompression::RequestDecompressionLayer; use tower_http::trace::TraceLayer; use ceres::lfs::LfsConfig; -use ceres::protocol::{SmartProtocol, TransportProtocol}; +use ceres::protocol::{ServiceType, SmartProtocol, TransportProtocol}; use common::config::Config; use common::enums::ZtmType; use common::model::{CommonOptions, GetParams}; @@ -199,7 +199,7 @@ async fn get_method_router( state.context.clone(), TransportProtocol::Http, ); - return ceres::http::handler::git_info_refs(params, pack_protocol).await; + return crate::git_protocol::http::git_info_refs(params, pack_protocol).await; } else if Regex::new(r"/ztm/repo_provide$") .unwrap() .is_match(uri.path()) @@ -247,22 +247,24 @@ async fn post_method_router( .unwrap() .is_match(uri.path()) { - let pack_protocol = SmartProtocol::new( + let mut pack_protocol = SmartProtocol::new( remove_git_suffix(uri, "/git-upload-pack"), state.context.clone(), TransportProtocol::Http, ); - ceres::http::handler::git_upload_pack(req, pack_protocol).await + pack_protocol.service_type = Some(ServiceType::UploadPack); + crate::git_protocol::http::git_upload_pack(req, pack_protocol).await } else if Regex::new(r"/git-receive-pack$") .unwrap() .is_match(uri.path()) { - let pack_protocol = SmartProtocol::new( + let mut pack_protocol = SmartProtocol::new( remove_git_suffix(uri, "/git-receive-pack"), state.context.clone(), TransportProtocol::Http, ); - ceres::http::handler::git_receive_pack(req, pack_protocol).await + pack_protocol.service_type = Some(ServiceType::ReceivePack); + crate::git_protocol::http::git_receive_pack(req, pack_protocol).await } else { Err(( StatusCode::NOT_FOUND,