|
|
@@ -2,7 +2,7 @@ use std::sync::Arc;
|
|
|
|
|
|
use actix_web::{
|
|
|
HttpRequest, HttpResponse, Scope, delete, get, guard, post,
|
|
|
- web::{Bytes, Data, Json, Path, route},
|
|
|
+ web::{Bytes, Data, Json, Path, Query, block, route},
|
|
|
};
|
|
|
use serde::Serialize;
|
|
|
|
|
|
@@ -67,20 +67,30 @@ async fn create_user(
|
|
|
request: HttpRequest,
|
|
|
req: Json<CreateUserRequest>,
|
|
|
) -> AppResult<Json<ApiUser>> {
|
|
|
+ let state = state.get_ref().clone();
|
|
|
let mut req = req.into_inner();
|
|
|
- if service::should_allow_bootstrap_admin(state.get_ref().as_ref())? {
|
|
|
- req.is_admin = true;
|
|
|
- } else {
|
|
|
- let acting_user = authenticate_request(state.get_ref().as_ref(), &request)?;
|
|
|
- if !acting_user.is_admin {
|
|
|
- return Err(AppError::Forbidden(
|
|
|
- "only site admin can create users".to_string(),
|
|
|
- ));
|
|
|
- }
|
|
|
- }
|
|
|
+ let token = extract_bearer_token(&request);
|
|
|
|
|
|
- let user = service::create_user(state.get_ref().as_ref(), req)?;
|
|
|
- Ok(Json(ApiUser::from(&user)))
|
|
|
+ let user = block(move || {
|
|
|
+ if service::should_allow_bootstrap_admin(state.as_ref())? {
|
|
|
+ req.is_admin = true;
|
|
|
+ } else {
|
|
|
+ let token = token.ok_or_else(|| {
|
|
|
+ AppError::Unauthorized("missing authorization header".to_string())
|
|
|
+ })?;
|
|
|
+ let acting_user = service::authenticate_token(state.as_ref(), &token)?;
|
|
|
+ if !acting_user.is_admin {
|
|
|
+ return Err(AppError::Forbidden(
|
|
|
+ "only site admin can create users".to_string(),
|
|
|
+ ));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ let user = service::create_user(state.as_ref(), req)?;
|
|
|
+ Ok(ApiUser::from(&user))
|
|
|
+ })
|
|
|
+ .await
|
|
|
+ .map_err(blocking_error)??;
|
|
|
+ Ok(Json(user))
|
|
|
}
|
|
|
|
|
|
#[post("/api/user/login")]
|
|
|
@@ -88,7 +98,15 @@ async fn login(
|
|
|
state: Data<Arc<AppState>>,
|
|
|
req: Json<LoginRequest>,
|
|
|
) -> AppResult<Json<ApiLoginResponse>> {
|
|
|
- let login = service::login(state.get_ref().as_ref(), req.into_inner())?;
|
|
|
+ let state = state.get_ref().clone();
|
|
|
+ let req = req.into_inner();
|
|
|
+ let client_key = req.login.clone();
|
|
|
+
|
|
|
+ state.login_rate_limiter.check(&format!("login:{client_key}"))?;
|
|
|
+
|
|
|
+ let login = block(move || service::login(state.as_ref(), req))
|
|
|
+ .await
|
|
|
+ .map_err(blocking_error)??;
|
|
|
Ok(Json(ApiLoginResponse::from(&login)))
|
|
|
}
|
|
|
|
|
|
@@ -98,8 +116,19 @@ async fn create_access_token(
|
|
|
request: HttpRequest,
|
|
|
req: Json<CreateAccessTokenRequest>,
|
|
|
) -> AppResult<Json<CreateAccessTokenResponse>> {
|
|
|
- let user = authenticate_request(state.get_ref().as_ref(), &request)?;
|
|
|
- let token = service::issue_access_token(state.get_ref().as_ref(), user.id, req.into_inner())?;
|
|
|
+ let state = state.get_ref().clone();
|
|
|
+ let token = extract_bearer_token(&request);
|
|
|
+ let req = req.into_inner();
|
|
|
+
|
|
|
+ let token = block(move || {
|
|
|
+ let token = token.ok_or_else(|| {
|
|
|
+ AppError::Unauthorized("missing authorization header".to_string())
|
|
|
+ })?;
|
|
|
+ let user = service::authenticate_token(state.as_ref(), &token)?;
|
|
|
+ service::issue_access_token(state.as_ref(), user.id, req)
|
|
|
+ })
|
|
|
+ .await
|
|
|
+ .map_err(blocking_error)??;
|
|
|
Ok(Json(token))
|
|
|
}
|
|
|
|
|
|
@@ -107,9 +136,22 @@ async fn create_access_token(
|
|
|
async fn list_access_tokens(
|
|
|
state: Data<Arc<AppState>>,
|
|
|
request: HttpRequest,
|
|
|
+ query: Query<crate::models::PaginationQuery>,
|
|
|
) -> AppResult<Json<Vec<AccessTokenResponse>>> {
|
|
|
- let user = authenticate_request(state.get_ref().as_ref(), &request)?;
|
|
|
- let tokens = service::list_access_tokens(state.get_ref().as_ref(), user.id)?;
|
|
|
+ let state = state.get_ref().clone();
|
|
|
+ let token = extract_bearer_token(&request);
|
|
|
+ let page = query.page;
|
|
|
+ let per_page = query.per_page;
|
|
|
+
|
|
|
+ let tokens = block(move || {
|
|
|
+ let token = token.ok_or_else(|| {
|
|
|
+ AppError::Unauthorized("missing authorization header".to_string())
|
|
|
+ })?;
|
|
|
+ let user = service::authenticate_token(state.as_ref(), &token)?;
|
|
|
+ service::list_access_tokens(state.as_ref(), user.id, page, per_page)
|
|
|
+ })
|
|
|
+ .await
|
|
|
+ .map_err(blocking_error)??;
|
|
|
Ok(Json(tokens))
|
|
|
}
|
|
|
|
|
|
@@ -119,8 +161,19 @@ async fn delete_access_token(
|
|
|
request: HttpRequest,
|
|
|
token_id: Path<i64>,
|
|
|
) -> AppResult<HttpResponse> {
|
|
|
- let user = authenticate_request(state.get_ref().as_ref(), &request)?;
|
|
|
- service::delete_access_token(state.get_ref().as_ref(), user.id, token_id.into_inner())?;
|
|
|
+ let state = state.get_ref().clone();
|
|
|
+ let token = extract_bearer_token(&request);
|
|
|
+ let token_id = token_id.into_inner();
|
|
|
+
|
|
|
+ block(move || {
|
|
|
+ let token = token.ok_or_else(|| {
|
|
|
+ AppError::Unauthorized("missing authorization header".to_string())
|
|
|
+ })?;
|
|
|
+ let user = service::authenticate_token(state.as_ref(), &token)?;
|
|
|
+ service::delete_access_token(state.as_ref(), user.id, token_id)
|
|
|
+ })
|
|
|
+ .await
|
|
|
+ .map_err(blocking_error)??;
|
|
|
Ok(HttpResponse::NoContent().finish())
|
|
|
}
|
|
|
|
|
|
@@ -130,9 +183,18 @@ async fn get_user(
|
|
|
request: HttpRequest,
|
|
|
username: Path<String>,
|
|
|
) -> AppResult<Json<ApiUser>> {
|
|
|
- let user = service::get_user(state.get_ref().as_ref(), &username.into_inner())?;
|
|
|
- let mut api_user = ApiUser::from(&user);
|
|
|
- if authenticate_request(state.get_ref().as_ref(), &request).is_err() {
|
|
|
+ let state = state.get_ref().clone();
|
|
|
+ let token = extract_bearer_token(&request);
|
|
|
+ let username = username.into_inner();
|
|
|
+
|
|
|
+ let mut api_user = block(move || {
|
|
|
+ let user = service::get_user(state.as_ref(), &username)?;
|
|
|
+ Ok::<_, AppError>(ApiUser::from(&user))
|
|
|
+ })
|
|
|
+ .await
|
|
|
+ .map_err(blocking_error)??;
|
|
|
+
|
|
|
+ if token.is_none() {
|
|
|
api_user.email.clear();
|
|
|
}
|
|
|
Ok(Json(api_user))
|
|
|
@@ -144,13 +206,21 @@ async fn create_repo(
|
|
|
request: HttpRequest,
|
|
|
req: Json<CreateRepositoryRequest>,
|
|
|
) -> AppResult<Json<ApiRepositoryResponse>> {
|
|
|
- let user = authenticate_request(state.get_ref().as_ref(), &request)?;
|
|
|
- let repo = service::create_repository(state.get_ref().as_ref(), &user, req.into_inner())?;
|
|
|
- Ok(Json(api_repository_response(
|
|
|
- state.get_ref().as_ref(),
|
|
|
- Some(&user),
|
|
|
- &repo,
|
|
|
- )?))
|
|
|
+ let state = state.get_ref().clone();
|
|
|
+ let token = extract_bearer_token(&request);
|
|
|
+ let req = req.into_inner();
|
|
|
+
|
|
|
+ let repo = block(move || {
|
|
|
+ let token = token.ok_or_else(|| {
|
|
|
+ AppError::Unauthorized("missing authorization header".to_string())
|
|
|
+ })?;
|
|
|
+ let user = service::authenticate_token(state.as_ref(), &token)?;
|
|
|
+ let repo = service::create_repository(state.as_ref(), &user, req)?;
|
|
|
+ api_repository_response(state.as_ref(), Some(&user), &repo)
|
|
|
+ })
|
|
|
+ .await
|
|
|
+ .map_err(blocking_error)??;
|
|
|
+ Ok(Json(repo))
|
|
|
}
|
|
|
|
|
|
#[post("/api/repos/{owner}/{repo}/forks")]
|
|
|
@@ -160,20 +230,22 @@ async fn fork_repo(
|
|
|
path: Path<(String, String)>,
|
|
|
req: Json<ForkRepositoryRequest>,
|
|
|
) -> AppResult<Json<ApiRepositoryResponse>> {
|
|
|
- let acting_user = authenticate_request(state.get_ref().as_ref(), &request)?;
|
|
|
+ let state = state.get_ref().clone();
|
|
|
+ let token = extract_bearer_token(&request);
|
|
|
let (owner, repo) = path.into_inner();
|
|
|
- let fork = service::fork_repository(
|
|
|
- state.get_ref().as_ref(),
|
|
|
- &acting_user,
|
|
|
- &owner,
|
|
|
- &repo,
|
|
|
- req.into_inner(),
|
|
|
- )?;
|
|
|
- Ok(Json(api_repository_response(
|
|
|
- state.get_ref().as_ref(),
|
|
|
- Some(&acting_user),
|
|
|
- &fork,
|
|
|
- )?))
|
|
|
+ let req = req.into_inner();
|
|
|
+
|
|
|
+ let repo = block(move || {
|
|
|
+ let token = token.ok_or_else(|| {
|
|
|
+ AppError::Unauthorized("missing authorization header".to_string())
|
|
|
+ })?;
|
|
|
+ let acting_user = service::authenticate_token(state.as_ref(), &token)?;
|
|
|
+ let fork = service::fork_repository(state.as_ref(), &acting_user, &owner, &repo, req)?;
|
|
|
+ api_repository_response(state.as_ref(), Some(&acting_user), &fork)
|
|
|
+ })
|
|
|
+ .await
|
|
|
+ .map_err(blocking_error)??;
|
|
|
+ Ok(Json(repo))
|
|
|
}
|
|
|
|
|
|
#[get("/api/repos/{owner}/{repo}/branches")]
|
|
|
@@ -182,10 +254,18 @@ async fn list_branches(
|
|
|
request: HttpRequest,
|
|
|
path: Path<(String, String)>,
|
|
|
) -> AppResult<Json<Vec<Branch>>> {
|
|
|
- let maybe_user = authenticate_request(state.get_ref().as_ref(), &request).ok();
|
|
|
+ let state = state.get_ref().clone();
|
|
|
+ let token = extract_bearer_token(&request);
|
|
|
let (owner, repo) = path.into_inner();
|
|
|
- let branches =
|
|
|
- service::list_branches(state.get_ref().as_ref(), maybe_user.as_ref(), &owner, &repo)?;
|
|
|
+
|
|
|
+ let branches = block(move || {
|
|
|
+ let maybe_user = token
|
|
|
+ .map(|t| service::authenticate_token(state.as_ref(), &t))
|
|
|
+ .transpose()?;
|
|
|
+ service::list_branches(state.as_ref(), maybe_user.as_ref(), &owner, &repo)
|
|
|
+ })
|
|
|
+ .await
|
|
|
+ .map_err(blocking_error)??;
|
|
|
Ok(Json(branches))
|
|
|
}
|
|
|
|
|
|
@@ -194,17 +274,22 @@ async fn compare_repositories(
|
|
|
state: Data<Arc<AppState>>,
|
|
|
request: HttpRequest,
|
|
|
path: Path<(String, String)>,
|
|
|
- query: actix_web::web::Query<CompareRequest>,
|
|
|
+ query: Query<CompareRequest>,
|
|
|
) -> AppResult<Json<CompareResponse>> {
|
|
|
- let acting_user = authenticate_request(state.get_ref().as_ref(), &request)?;
|
|
|
+ let state = state.get_ref().clone();
|
|
|
+ let token = extract_bearer_token(&request);
|
|
|
let (owner, repo) = path.into_inner();
|
|
|
- let compare = service::compare_repositories(
|
|
|
- state.get_ref().as_ref(),
|
|
|
- &acting_user,
|
|
|
- &owner,
|
|
|
- &repo,
|
|
|
- query.into_inner(),
|
|
|
- )?;
|
|
|
+ let req = query.into_inner();
|
|
|
+
|
|
|
+ let compare = block(move || {
|
|
|
+ let token = token.ok_or_else(|| {
|
|
|
+ AppError::Unauthorized("missing authorization header".to_string())
|
|
|
+ })?;
|
|
|
+ let acting_user = service::authenticate_token(state.as_ref(), &token)?;
|
|
|
+ service::compare_repositories(state.as_ref(), &acting_user, &owner, &repo, req)
|
|
|
+ })
|
|
|
+ .await
|
|
|
+ .map_err(blocking_error)??;
|
|
|
Ok(Json(compare))
|
|
|
}
|
|
|
|
|
|
@@ -215,16 +300,28 @@ async fn create_pull_request(
|
|
|
path: Path<(String, String)>,
|
|
|
req: Json<CreatePullRequestRequest>,
|
|
|
) -> AppResult<Json<ApiPullRequestResponse>> {
|
|
|
- let acting_user = authenticate_request(state.get_ref().as_ref(), &request)?;
|
|
|
+ let state = state.get_ref().clone();
|
|
|
+ let token = extract_bearer_token(&request);
|
|
|
let (owner, repo) = path.into_inner();
|
|
|
- let pull_request = service::create_pull_request(
|
|
|
- state.get_ref().as_ref(),
|
|
|
- &acting_user,
|
|
|
- &owner,
|
|
|
- &repo,
|
|
|
- req.into_inner(),
|
|
|
- )?;
|
|
|
- Ok(Json(ApiPullRequestResponse::from(&pull_request)))
|
|
|
+ let req = req.into_inner();
|
|
|
+
|
|
|
+ let pull_request = block(move || {
|
|
|
+ let token = token.ok_or_else(|| {
|
|
|
+ AppError::Unauthorized("missing authorization header".to_string())
|
|
|
+ })?;
|
|
|
+ let acting_user = service::authenticate_token(state.as_ref(), &token)?;
|
|
|
+ let pull_request = service::create_pull_request(
|
|
|
+ state.as_ref(),
|
|
|
+ &acting_user,
|
|
|
+ &owner,
|
|
|
+ &repo,
|
|
|
+ req,
|
|
|
+ )?;
|
|
|
+ Ok::<_, AppError>(ApiPullRequestResponse::from(&pull_request))
|
|
|
+ })
|
|
|
+ .await
|
|
|
+ .map_err(blocking_error)??;
|
|
|
+ Ok(Json(pull_request))
|
|
|
}
|
|
|
|
|
|
#[get("/api/repos/{owner}/{repo}/pulls")]
|
|
|
@@ -232,17 +329,28 @@ async fn list_pull_requests(
|
|
|
state: Data<Arc<AppState>>,
|
|
|
request: HttpRequest,
|
|
|
path: Path<(String, String)>,
|
|
|
+ query: Query<crate::models::PaginationQuery>,
|
|
|
) -> AppResult<Json<Vec<ApiPullRequestResponse>>> {
|
|
|
- let maybe_user = authenticate_request(state.get_ref().as_ref(), &request).ok();
|
|
|
+ let state = state.get_ref().clone();
|
|
|
+ let token = extract_bearer_token(&request);
|
|
|
let (owner, repo) = path.into_inner();
|
|
|
- let pulls =
|
|
|
- service::list_pull_requests(state.get_ref().as_ref(), maybe_user.as_ref(), &owner, &repo)?;
|
|
|
- Ok(Json(
|
|
|
- pulls
|
|
|
+ let page = query.page;
|
|
|
+ let per_page = query.per_page;
|
|
|
+
|
|
|
+ let pulls = block(move || {
|
|
|
+ let maybe_user = token
|
|
|
+ .map(|t| service::authenticate_token(state.as_ref(), &t))
|
|
|
+ .transpose()?;
|
|
|
+ let pulls =
|
|
|
+ service::list_pull_requests(state.as_ref(), maybe_user.as_ref(), &owner, &repo, page, per_page)?;
|
|
|
+ Ok::<_, AppError>(pulls
|
|
|
.iter()
|
|
|
.map(ApiPullRequestResponse::from)
|
|
|
- .collect::<Vec<_>>(),
|
|
|
- ))
|
|
|
+ .collect::<Vec<_>>())
|
|
|
+ })
|
|
|
+ .await
|
|
|
+ .map_err(blocking_error)??;
|
|
|
+ Ok(Json(pulls))
|
|
|
}
|
|
|
|
|
|
#[get("/api/repos/{owner}/{repo}/pulls/{index}")]
|
|
|
@@ -251,15 +359,24 @@ async fn get_pull_request(
|
|
|
request: HttpRequest,
|
|
|
path: Path<(String, String, i64)>,
|
|
|
) -> AppResult<Json<ApiPullRequestDetailResponse>> {
|
|
|
- let maybe_user = authenticate_request(state.get_ref().as_ref(), &request).ok();
|
|
|
+ let state = state.get_ref().clone();
|
|
|
+ let token = extract_bearer_token(&request);
|
|
|
let (owner, repo, index) = path.into_inner();
|
|
|
- let pull_request = service::get_pull_request_detail(
|
|
|
- state.get_ref().as_ref(),
|
|
|
- maybe_user.as_ref(),
|
|
|
- &owner,
|
|
|
- &repo,
|
|
|
- index,
|
|
|
- )?;
|
|
|
+
|
|
|
+ let pull_request = block(move || {
|
|
|
+ let maybe_user = token
|
|
|
+ .map(|t| service::authenticate_token(state.as_ref(), &t))
|
|
|
+ .transpose()?;
|
|
|
+ service::get_pull_request_detail(
|
|
|
+ state.as_ref(),
|
|
|
+ maybe_user.as_ref(),
|
|
|
+ &owner,
|
|
|
+ &repo,
|
|
|
+ index,
|
|
|
+ )
|
|
|
+ })
|
|
|
+ .await
|
|
|
+ .map_err(blocking_error)??;
|
|
|
Ok(Json(ApiPullRequestDetailResponse::from(&pull_request)))
|
|
|
}
|
|
|
|
|
|
@@ -270,17 +387,29 @@ async fn merge_pull_request(
|
|
|
path: Path<(String, String, i64)>,
|
|
|
req: Json<MergePullRequestRequest>,
|
|
|
) -> AppResult<Json<ApiPullRequestResponse>> {
|
|
|
- let acting_user = authenticate_request(state.get_ref().as_ref(), &request)?;
|
|
|
+ let state = state.get_ref().clone();
|
|
|
+ let token = extract_bearer_token(&request);
|
|
|
let (owner, repo, index) = path.into_inner();
|
|
|
- let pull_request = service::merge_pull_request(
|
|
|
- state.get_ref().as_ref(),
|
|
|
- &acting_user,
|
|
|
- &owner,
|
|
|
- &repo,
|
|
|
- index,
|
|
|
- req.into_inner(),
|
|
|
- )?;
|
|
|
- Ok(Json(ApiPullRequestResponse::from(&pull_request)))
|
|
|
+ let req = req.into_inner();
|
|
|
+
|
|
|
+ let pull_request = block(move || {
|
|
|
+ let token = token.ok_or_else(|| {
|
|
|
+ AppError::Unauthorized("missing authorization header".to_string())
|
|
|
+ })?;
|
|
|
+ let acting_user = service::authenticate_token(state.as_ref(), &token)?;
|
|
|
+ let pull_request = service::merge_pull_request(
|
|
|
+ state.as_ref(),
|
|
|
+ &acting_user,
|
|
|
+ &owner,
|
|
|
+ &repo,
|
|
|
+ index,
|
|
|
+ req,
|
|
|
+ )?;
|
|
|
+ Ok::<_, AppError>(ApiPullRequestResponse::from(&pull_request))
|
|
|
+ })
|
|
|
+ .await
|
|
|
+ .map_err(blocking_error)??;
|
|
|
+ Ok(Json(pull_request))
|
|
|
}
|
|
|
|
|
|
#[post("/api/repos/{owner}/{repo}/pulls/{index}/close")]
|
|
|
@@ -289,11 +418,22 @@ async fn close_pull_request(
|
|
|
request: HttpRequest,
|
|
|
path: Path<(String, String, i64)>,
|
|
|
) -> AppResult<Json<ApiPullRequestResponse>> {
|
|
|
- let acting_user = authenticate_request(state.get_ref().as_ref(), &request)?;
|
|
|
+ let state = state.get_ref().clone();
|
|
|
+ let token = extract_bearer_token(&request);
|
|
|
let (owner, repo, index) = path.into_inner();
|
|
|
- let pull_request =
|
|
|
- service::close_pull_request(state.get_ref().as_ref(), &acting_user, &owner, &repo, index)?;
|
|
|
- Ok(Json(ApiPullRequestResponse::from(&pull_request)))
|
|
|
+
|
|
|
+ let pull_request = block(move || {
|
|
|
+ let token = token.ok_or_else(|| {
|
|
|
+ AppError::Unauthorized("missing authorization header".to_string())
|
|
|
+ })?;
|
|
|
+ let acting_user = service::authenticate_token(state.as_ref(), &token)?;
|
|
|
+ let pull_request =
|
|
|
+ service::close_pull_request(state.as_ref(), &acting_user, &owner, &repo, index)?;
|
|
|
+ Ok::<_, AppError>(ApiPullRequestResponse::from(&pull_request))
|
|
|
+ })
|
|
|
+ .await
|
|
|
+ .map_err(blocking_error)??;
|
|
|
+ Ok(Json(pull_request))
|
|
|
}
|
|
|
|
|
|
#[post("/api/repos/{owner}/{repo}/pulls/{index}/reopen")]
|
|
|
@@ -302,11 +442,22 @@ async fn reopen_pull_request(
|
|
|
request: HttpRequest,
|
|
|
path: Path<(String, String, i64)>,
|
|
|
) -> AppResult<Json<ApiPullRequestResponse>> {
|
|
|
- let acting_user = authenticate_request(state.get_ref().as_ref(), &request)?;
|
|
|
+ let state = state.get_ref().clone();
|
|
|
+ let token = extract_bearer_token(&request);
|
|
|
let (owner, repo, index) = path.into_inner();
|
|
|
- let pull_request =
|
|
|
- service::reopen_pull_request(state.get_ref().as_ref(), &acting_user, &owner, &repo, index)?;
|
|
|
- Ok(Json(ApiPullRequestResponse::from(&pull_request)))
|
|
|
+
|
|
|
+ let pull_request = block(move || {
|
|
|
+ let token = token.ok_or_else(|| {
|
|
|
+ AppError::Unauthorized("missing authorization header".to_string())
|
|
|
+ })?;
|
|
|
+ let acting_user = service::authenticate_token(state.as_ref(), &token)?;
|
|
|
+ let pull_request =
|
|
|
+ service::reopen_pull_request(state.as_ref(), &acting_user, &owner, &repo, index)?;
|
|
|
+ Ok::<_, AppError>(ApiPullRequestResponse::from(&pull_request))
|
|
|
+ })
|
|
|
+ .await
|
|
|
+ .map_err(blocking_error)??;
|
|
|
+ Ok(Json(pull_request))
|
|
|
}
|
|
|
|
|
|
#[post("/api/repos/{owner}/{repo}/collaborators")]
|
|
|
@@ -316,16 +467,28 @@ async fn upsert_collaborator(
|
|
|
path: Path<(String, String)>,
|
|
|
req: Json<UpsertCollaboratorRequest>,
|
|
|
) -> AppResult<Json<ApiCollaboratorResponse>> {
|
|
|
- let acting_user = authenticate_request(state.get_ref().as_ref(), &request)?;
|
|
|
+ let state = state.get_ref().clone();
|
|
|
+ let token = extract_bearer_token(&request);
|
|
|
let (owner, repo) = path.into_inner();
|
|
|
- let collaborator = service::upsert_collaborator(
|
|
|
- state.get_ref().as_ref(),
|
|
|
- &acting_user,
|
|
|
- &owner,
|
|
|
- &repo,
|
|
|
- req.into_inner(),
|
|
|
- )?;
|
|
|
- Ok(Json(ApiCollaboratorResponse::from(&collaborator)))
|
|
|
+ let req = req.into_inner();
|
|
|
+
|
|
|
+ let collaborator = block(move || {
|
|
|
+ let token = token.ok_or_else(|| {
|
|
|
+ AppError::Unauthorized("missing authorization header".to_string())
|
|
|
+ })?;
|
|
|
+ let acting_user = service::authenticate_token(state.as_ref(), &token)?;
|
|
|
+ let collaborator = service::upsert_collaborator(
|
|
|
+ state.as_ref(),
|
|
|
+ &acting_user,
|
|
|
+ &owner,
|
|
|
+ &repo,
|
|
|
+ req,
|
|
|
+ )?;
|
|
|
+ Ok::<_, AppError>(ApiCollaboratorResponse::from(&collaborator))
|
|
|
+ })
|
|
|
+ .await
|
|
|
+ .map_err(blocking_error)??;
|
|
|
+ Ok(Json(collaborator))
|
|
|
}
|
|
|
|
|
|
#[actix_web::delete("/api/repos/{owner}/{repo}/collaborators/{username}")]
|
|
|
@@ -334,15 +497,25 @@ async fn delete_collaborator(
|
|
|
request: HttpRequest,
|
|
|
path: Path<(String, String, String)>,
|
|
|
) -> AppResult<HttpResponse> {
|
|
|
- let acting_user = authenticate_request(state.get_ref().as_ref(), &request)?;
|
|
|
+ let state = state.get_ref().clone();
|
|
|
+ let token = extract_bearer_token(&request);
|
|
|
let (owner, repo, username) = path.into_inner();
|
|
|
- service::remove_collaborator(
|
|
|
- state.get_ref().as_ref(),
|
|
|
- &acting_user,
|
|
|
- &owner,
|
|
|
- &repo,
|
|
|
- &username,
|
|
|
- )?;
|
|
|
+
|
|
|
+ block(move || {
|
|
|
+ let token = token.ok_or_else(|| {
|
|
|
+ AppError::Unauthorized("missing authorization header".to_string())
|
|
|
+ })?;
|
|
|
+ let acting_user = service::authenticate_token(state.as_ref(), &token)?;
|
|
|
+ service::remove_collaborator(
|
|
|
+ state.as_ref(),
|
|
|
+ &acting_user,
|
|
|
+ &owner,
|
|
|
+ &repo,
|
|
|
+ &username,
|
|
|
+ )
|
|
|
+ })
|
|
|
+ .await
|
|
|
+ .map_err(blocking_error)??;
|
|
|
Ok(HttpResponse::NoContent().finish())
|
|
|
}
|
|
|
|
|
|
@@ -352,75 +525,99 @@ async fn git_http(
|
|
|
path: Path<(String, String, String)>,
|
|
|
body: Bytes,
|
|
|
) -> AppResult<HttpResponse> {
|
|
|
+ let state = state.get_ref().clone();
|
|
|
let (owner_name, repo_name, tail) = path.into_inner();
|
|
|
- let repo = service::get_repository(state.get_ref().as_ref(), &owner_name, &repo_name)?;
|
|
|
-
|
|
|
let query = request.query_string().to_string();
|
|
|
let service_name = request
|
|
|
.query_string()
|
|
|
.split('&')
|
|
|
.find_map(|pair| pair.strip_prefix("service="))
|
|
|
- .unwrap_or_default();
|
|
|
- let is_pull = service_name == "git-upload-pack"
|
|
|
- || tail.ends_with("git-upload-pack")
|
|
|
- || (request.method() == actix_web::http::Method::GET
|
|
|
- && service_name != "git-receive-pack"
|
|
|
- && !tail.ends_with("git-receive-pack"));
|
|
|
-
|
|
|
- let auth_user = if !repo.repo.is_private && is_pull {
|
|
|
- None
|
|
|
- } else {
|
|
|
- Some(authenticate_git_request(
|
|
|
- state.get_ref().as_ref(),
|
|
|
- &request,
|
|
|
- )?)
|
|
|
- };
|
|
|
-
|
|
|
- if let Some(user) = &auth_user {
|
|
|
- let desired = if is_pull {
|
|
|
- AccessMode::Read
|
|
|
+ .unwrap_or_default()
|
|
|
+ .to_string();
|
|
|
+ let method = request.method().as_str().to_string();
|
|
|
+ let content_type = request
|
|
|
+ .headers()
|
|
|
+ .get("content-type")
|
|
|
+ .and_then(|v| v.to_str().ok())
|
|
|
+ .map(|s| s.to_string());
|
|
|
+ let basic_auth = extract_basic_auth(&request);
|
|
|
+ let bearer_token = extract_bearer_token(&request);
|
|
|
+ let body = body.to_vec();
|
|
|
+
|
|
|
+ let backend = block(move || {
|
|
|
+ let repo = service::get_repository(state.as_ref(), &owner_name, &repo_name)?;
|
|
|
+
|
|
|
+ let is_pull = service_name == "git-upload-pack"
|
|
|
+ || tail.ends_with("git-upload-pack")
|
|
|
+ || (method == "GET"
|
|
|
+ && service_name != "git-receive-pack"
|
|
|
+ && !tail.ends_with("git-receive-pack"));
|
|
|
+
|
|
|
+ let auth_user = if !repo.repo.is_private && is_pull {
|
|
|
+ None
|
|
|
} else {
|
|
|
- AccessMode::Write
|
|
|
+ Some(if let Some(basic) = basic_auth {
|
|
|
+ let decoded = decode_basic_auth(&basic)?;
|
|
|
+ let (auth_login, secret) = decoded.split_once(':').ok_or_else(|| {
|
|
|
+ AppError::Unauthorized("invalid basic auth payload".to_string())
|
|
|
+ })?;
|
|
|
+ service::authenticate_http_basic(state.as_ref(), auth_login, secret)?
|
|
|
+ } else if let Some(bearer) = bearer_token {
|
|
|
+ service::authenticate_token(state.as_ref(), &bearer)?
|
|
|
+ } else {
|
|
|
+ return Err(AppError::Unauthorized(
|
|
|
+ "repository access denied".to_string(),
|
|
|
+ ));
|
|
|
+ })
|
|
|
};
|
|
|
- if !state.db.authorize(
|
|
|
- user.id,
|
|
|
- repo.repo.id,
|
|
|
- desired,
|
|
|
- repo.owner.id,
|
|
|
- repo.repo.is_private,
|
|
|
- )? {
|
|
|
- return Err(AppError::Forbidden("repository access denied".to_string()));
|
|
|
+
|
|
|
+ if let Some(user) = &auth_user {
|
|
|
+ let desired = if is_pull {
|
|
|
+ AccessMode::Read
|
|
|
+ } else {
|
|
|
+ AccessMode::Write
|
|
|
+ };
|
|
|
+ if !state.db.authorize(
|
|
|
+ user.id,
|
|
|
+ repo.repo.id,
|
|
|
+ desired,
|
|
|
+ repo.owner.id,
|
|
|
+ repo.repo.is_private,
|
|
|
+ )? {
|
|
|
+ return Err(AppError::Forbidden("repository access denied".to_string()));
|
|
|
+ }
|
|
|
+ } else if repo.repo.is_private || !is_pull {
|
|
|
+ return Err(AppError::Unauthorized(
|
|
|
+ "repository access denied".to_string(),
|
|
|
+ ));
|
|
|
}
|
|
|
- } else if repo.repo.is_private || !is_pull {
|
|
|
- return Err(AppError::Unauthorized(
|
|
|
- "repository access denied".to_string(),
|
|
|
- ));
|
|
|
- }
|
|
|
|
|
|
- let repo_path = crate::repox::repository_path(
|
|
|
- &state.config.repository.root,
|
|
|
- &repo.owner.name,
|
|
|
- &repo.repo.name,
|
|
|
- );
|
|
|
- let path_info = format!("/{owner_name}/{repo_name}.git/{tail}");
|
|
|
- let backend = crate::git::run_git_http_backend(crate::git::GitHttpBackendRequest {
|
|
|
- git_binary: &state.config.repository.git_binary,
|
|
|
- project_root: &state.config.repository.root,
|
|
|
- path_info: &path_info,
|
|
|
- method: request.method().as_str(),
|
|
|
- query_string: &query,
|
|
|
- content_type: request
|
|
|
- .headers()
|
|
|
- .get("content-type")
|
|
|
- .and_then(|v| v.to_str().ok()),
|
|
|
- remote_user: auth_user.as_ref().map(|u| u.name.as_str()),
|
|
|
- body: body.as_ref(),
|
|
|
- })?;
|
|
|
- if !repo_path.exists() {
|
|
|
- return Err(AppError::NotFound(format!(
|
|
|
- "repository not found: {owner_name}/{repo_name}"
|
|
|
- )));
|
|
|
- }
|
|
|
+ let repo_path = crate::repox::repository_path(
|
|
|
+ &state.config.repository.root,
|
|
|
+ &repo.owner.name,
|
|
|
+ &repo.repo.name,
|
|
|
+ );
|
|
|
+ if !repo_path.exists() {
|
|
|
+ return Err(AppError::NotFound(format!(
|
|
|
+ "repository not found: {owner_name}/{repo_name}"
|
|
|
+ )));
|
|
|
+ }
|
|
|
+
|
|
|
+ let path_info = format!("/{owner_name}/{repo_name}.git/{tail}");
|
|
|
+ let backend = crate::git::run_git_http_backend(crate::git::GitHttpBackendRequest {
|
|
|
+ git_binary: &state.config.repository.git_binary,
|
|
|
+ project_root: &state.config.repository.root,
|
|
|
+ path_info: &path_info,
|
|
|
+ method: &method,
|
|
|
+ query_string: &query,
|
|
|
+ content_type: content_type.as_deref(),
|
|
|
+ remote_user: auth_user.as_ref().map(|u| u.name.as_str()),
|
|
|
+ body: &body,
|
|
|
+ })?;
|
|
|
+ Ok(backend)
|
|
|
+ })
|
|
|
+ .await
|
|
|
+ .map_err(blocking_error)??;
|
|
|
|
|
|
let status = actix_web::http::StatusCode::from_u16(backend.status_code)
|
|
|
.map_err(|_| AppError::Git("invalid backend status".to_string()))?;
|
|
|
@@ -437,35 +634,51 @@ async fn get_repo(
|
|
|
request: HttpRequest,
|
|
|
path: Path<(String, String)>,
|
|
|
) -> AppResult<Json<ApiRepositoryResponse>> {
|
|
|
+ let state = state.get_ref().clone();
|
|
|
+ let token = extract_bearer_token(&request);
|
|
|
let (owner, repo) = path.into_inner();
|
|
|
- let maybe_user = authenticate_request(state.get_ref().as_ref(), &request).ok();
|
|
|
- let repo = service::get_repository_for_read(
|
|
|
- state.get_ref().as_ref(),
|
|
|
- maybe_user.as_ref(),
|
|
|
- &owner,
|
|
|
- &repo,
|
|
|
- )?;
|
|
|
- Ok(Json(api_repository_response(
|
|
|
- state.get_ref().as_ref(),
|
|
|
- maybe_user.as_ref(),
|
|
|
- &repo,
|
|
|
- )?))
|
|
|
+
|
|
|
+ let repo = block(move || {
|
|
|
+ let maybe_user = token
|
|
|
+ .map(|t| service::authenticate_token(state.as_ref(), &t))
|
|
|
+ .transpose()?;
|
|
|
+ let repo = service::get_repository_for_read(
|
|
|
+ state.as_ref(),
|
|
|
+ maybe_user.as_ref(),
|
|
|
+ &owner,
|
|
|
+ &repo,
|
|
|
+ )?;
|
|
|
+ api_repository_response(state.as_ref(), maybe_user.as_ref(), &repo)
|
|
|
+ })
|
|
|
+ .await
|
|
|
+ .map_err(blocking_error)??;
|
|
|
+ Ok(Json(repo))
|
|
|
}
|
|
|
|
|
|
#[get("/api/user/repos")]
|
|
|
async fn list_current_user_repositories(
|
|
|
state: Data<Arc<AppState>>,
|
|
|
request: HttpRequest,
|
|
|
- query: actix_web::web::Query<RepositoryListQuery>,
|
|
|
+ query: Query<RepositoryListQuery>,
|
|
|
) -> AppResult<Json<Vec<ApiRepositoryResponse>>> {
|
|
|
- let user = authenticate_request(state.get_ref().as_ref(), &request)?;
|
|
|
- let repos =
|
|
|
- service::list_visible_repositories(state.get_ref().as_ref(), Some(&user), &query.q)?;
|
|
|
- Ok(Json(api_repository_list(
|
|
|
- state.get_ref().as_ref(),
|
|
|
- Some(&user),
|
|
|
- &repos,
|
|
|
- )?))
|
|
|
+ let state = state.get_ref().clone();
|
|
|
+ let token = extract_bearer_token(&request);
|
|
|
+ let q = query.q.clone();
|
|
|
+ let page = query.pagination.page;
|
|
|
+ let per_page = query.pagination.per_page;
|
|
|
+
|
|
|
+ let repos = block(move || {
|
|
|
+ let token = token.ok_or_else(|| {
|
|
|
+ AppError::Unauthorized("missing authorization header".to_string())
|
|
|
+ })?;
|
|
|
+ let user = service::authenticate_token(state.as_ref(), &token)?;
|
|
|
+ let repos =
|
|
|
+ service::list_visible_repositories(state.as_ref(), Some(&user), &q, page, per_page)?;
|
|
|
+ api_repository_list(state.as_ref(), Some(&user), &repos)
|
|
|
+ })
|
|
|
+ .await
|
|
|
+ .map_err(blocking_error)??;
|
|
|
+ Ok(Json(repos))
|
|
|
}
|
|
|
|
|
|
#[get("/api/users/{username}/repos")]
|
|
|
@@ -473,39 +686,57 @@ async fn list_user_repositories(
|
|
|
state: Data<Arc<AppState>>,
|
|
|
request: HttpRequest,
|
|
|
username: Path<String>,
|
|
|
- query: actix_web::web::Query<RepositoryListQuery>,
|
|
|
+ query: Query<RepositoryListQuery>,
|
|
|
) -> AppResult<Json<Vec<ApiRepositoryResponse>>> {
|
|
|
- let maybe_user = authenticate_request(state.get_ref().as_ref(), &request).ok();
|
|
|
- let repos = service::list_repositories_by_owner(
|
|
|
- state.get_ref().as_ref(),
|
|
|
- maybe_user.as_ref(),
|
|
|
- &username.into_inner(),
|
|
|
- &query.q,
|
|
|
- )?;
|
|
|
- Ok(Json(api_repository_list(
|
|
|
- state.get_ref().as_ref(),
|
|
|
- maybe_user.as_ref(),
|
|
|
- &repos,
|
|
|
- )?))
|
|
|
+ let state = state.get_ref().clone();
|
|
|
+ let token = extract_bearer_token(&request);
|
|
|
+ let username = username.into_inner();
|
|
|
+ let q = query.q.clone();
|
|
|
+ let page = query.pagination.page;
|
|
|
+ let per_page = query.pagination.per_page;
|
|
|
+
|
|
|
+ let repos = block(move || {
|
|
|
+ let maybe_user = token
|
|
|
+ .map(|t| service::authenticate_token(state.as_ref(), &t))
|
|
|
+ .transpose()?;
|
|
|
+ let repos = service::list_repositories_by_owner(
|
|
|
+ state.as_ref(),
|
|
|
+ maybe_user.as_ref(),
|
|
|
+ &username,
|
|
|
+ &q,
|
|
|
+ page,
|
|
|
+ per_page,
|
|
|
+ )?;
|
|
|
+ api_repository_list(state.as_ref(), maybe_user.as_ref(), &repos)
|
|
|
+ })
|
|
|
+ .await
|
|
|
+ .map_err(blocking_error)??;
|
|
|
+ Ok(Json(repos))
|
|
|
}
|
|
|
|
|
|
#[get("/api/repos/search")]
|
|
|
async fn search_repositories(
|
|
|
state: Data<Arc<AppState>>,
|
|
|
request: HttpRequest,
|
|
|
- query: actix_web::web::Query<RepositoryListQuery>,
|
|
|
+ query: Query<RepositoryListQuery>,
|
|
|
) -> AppResult<Json<Vec<ApiRepositoryResponse>>> {
|
|
|
- let maybe_user = authenticate_request(state.get_ref().as_ref(), &request).ok();
|
|
|
- let repos = service::list_visible_repositories(
|
|
|
- state.get_ref().as_ref(),
|
|
|
- maybe_user.as_ref(),
|
|
|
- &query.q,
|
|
|
- )?;
|
|
|
- Ok(Json(api_repository_list(
|
|
|
- state.get_ref().as_ref(),
|
|
|
- maybe_user.as_ref(),
|
|
|
- &repos,
|
|
|
- )?))
|
|
|
+ let state = state.get_ref().clone();
|
|
|
+ let token = extract_bearer_token(&request);
|
|
|
+ let q = query.q.clone();
|
|
|
+ let page = query.pagination.page;
|
|
|
+ let per_page = query.pagination.per_page;
|
|
|
+
|
|
|
+ let repos = block(move || {
|
|
|
+ let maybe_user = token
|
|
|
+ .map(|t| service::authenticate_token(state.as_ref(), &t))
|
|
|
+ .transpose()?;
|
|
|
+ let repos =
|
|
|
+ service::list_visible_repositories(state.as_ref(), maybe_user.as_ref(), &q, page, per_page)?;
|
|
|
+ api_repository_list(state.as_ref(), maybe_user.as_ref(), &repos)
|
|
|
+ })
|
|
|
+ .await
|
|
|
+ .map_err(blocking_error)??;
|
|
|
+ Ok(Json(repos))
|
|
|
}
|
|
|
|
|
|
#[get("/api/repos/{owner}/{repo}/collaborators")]
|
|
|
@@ -513,17 +744,28 @@ async fn list_collaborators(
|
|
|
state: Data<Arc<AppState>>,
|
|
|
request: HttpRequest,
|
|
|
path: Path<(String, String)>,
|
|
|
+ query: Query<crate::models::PaginationQuery>,
|
|
|
) -> AppResult<Json<Vec<ApiCollaboratorResponse>>> {
|
|
|
- let maybe_user = authenticate_request(state.get_ref().as_ref(), &request).ok();
|
|
|
+ let state = state.get_ref().clone();
|
|
|
+ let token = extract_bearer_token(&request);
|
|
|
let (owner, repo) = path.into_inner();
|
|
|
- let collaborators =
|
|
|
- service::list_collaborators(state.get_ref().as_ref(), maybe_user.as_ref(), &owner, &repo)?;
|
|
|
- Ok(Json(
|
|
|
- collaborators
|
|
|
+ let page = query.page;
|
|
|
+ let per_page = query.per_page;
|
|
|
+
|
|
|
+ let collaborators = block(move || {
|
|
|
+ let maybe_user = token
|
|
|
+ .map(|t| service::authenticate_token(state.as_ref(), &t))
|
|
|
+ .transpose()?;
|
|
|
+ let collaborators =
|
|
|
+ service::list_collaborators(state.as_ref(), maybe_user.as_ref(), &owner, &repo, page, per_page)?;
|
|
|
+ Ok::<_, AppError>(collaborators
|
|
|
.iter()
|
|
|
.map(ApiCollaboratorResponse::from)
|
|
|
- .collect(),
|
|
|
- ))
|
|
|
+ .collect())
|
|
|
+ })
|
|
|
+ .await
|
|
|
+ .map_err(blocking_error)??;
|
|
|
+ Ok(Json(collaborators))
|
|
|
}
|
|
|
|
|
|
#[get("/api/repos/{owner}/{repo}/collaborators/{username}")]
|
|
|
@@ -532,16 +774,26 @@ async fn get_collaborator(
|
|
|
request: HttpRequest,
|
|
|
path: Path<(String, String, String)>,
|
|
|
) -> AppResult<Json<ApiCollaboratorResponse>> {
|
|
|
- let maybe_user = authenticate_request(state.get_ref().as_ref(), &request).ok();
|
|
|
+ let state = state.get_ref().clone();
|
|
|
+ let token = extract_bearer_token(&request);
|
|
|
let (owner, repo, username) = path.into_inner();
|
|
|
- let collaborator = service::get_collaborator(
|
|
|
- state.get_ref().as_ref(),
|
|
|
- maybe_user.as_ref(),
|
|
|
- &owner,
|
|
|
- &repo,
|
|
|
- &username,
|
|
|
- )?;
|
|
|
- Ok(Json(ApiCollaboratorResponse::from(&collaborator)))
|
|
|
+
|
|
|
+ let collaborator = block(move || {
|
|
|
+ let maybe_user = token
|
|
|
+ .map(|t| service::authenticate_token(state.as_ref(), &t))
|
|
|
+ .transpose()?;
|
|
|
+ let collaborator = service::get_collaborator(
|
|
|
+ state.as_ref(),
|
|
|
+ maybe_user.as_ref(),
|
|
|
+ &owner,
|
|
|
+ &repo,
|
|
|
+ &username,
|
|
|
+ )?;
|
|
|
+ Ok::<_, AppError>(ApiCollaboratorResponse::from(&collaborator))
|
|
|
+ })
|
|
|
+ .await
|
|
|
+ .map_err(blocking_error)??;
|
|
|
+ Ok(Json(collaborator))
|
|
|
}
|
|
|
|
|
|
#[derive(Serialize)]
|
|
|
@@ -549,37 +801,16 @@ struct HealthResponse {
|
|
|
ok: bool,
|
|
|
}
|
|
|
|
|
|
-fn authenticate_request(state: &AppState, request: &HttpRequest) -> AppResult<User> {
|
|
|
- let header = request
|
|
|
- .headers()
|
|
|
- .get("authorization")
|
|
|
- .ok_or_else(|| AppError::Unauthorized("missing authorization header".to_string()))?;
|
|
|
- let header = header
|
|
|
- .to_str()
|
|
|
- .map_err(|_| AppError::Unauthorized("invalid authorization header".to_string()))?;
|
|
|
- let token = header
|
|
|
- .strip_prefix("Bearer ")
|
|
|
- .ok_or_else(|| AppError::Unauthorized("expected Bearer token".to_string()))?;
|
|
|
- service::authenticate_token(state, token)
|
|
|
-}
|
|
|
-
|
|
|
-fn authenticate_git_request(state: &AppState, request: &HttpRequest) -> AppResult<User> {
|
|
|
- let header = request
|
|
|
- .headers()
|
|
|
- .get("authorization")
|
|
|
- .ok_or_else(|| AppError::Unauthorized("missing authorization header".to_string()))?;
|
|
|
- let header = header
|
|
|
- .to_str()
|
|
|
- .map_err(|_| AppError::Unauthorized("invalid authorization header".to_string()))?;
|
|
|
-
|
|
|
- let encoded = header
|
|
|
- .strip_prefix("Basic ")
|
|
|
- .ok_or_else(|| AppError::Unauthorized("expected Basic auth".to_string()))?;
|
|
|
- let decoded = decode_basic_auth(encoded)?;
|
|
|
- let (auth_login, secret) = decoded
|
|
|
- .split_once(':')
|
|
|
- .ok_or_else(|| AppError::Unauthorized("invalid basic auth payload".to_string()))?;
|
|
|
- service::authenticate_http_basic(state, auth_login, secret)
|
|
|
+fn extract_bearer_token(request: &HttpRequest) -> Option<String> {
|
|
|
+ let header = request.headers().get("authorization")?;
|
|
|
+ let header = header.to_str().ok()?;
|
|
|
+ header.strip_prefix("Bearer ").map(|s| s.to_string())
|
|
|
+}
|
|
|
+
|
|
|
+fn extract_basic_auth(request: &HttpRequest) -> Option<String> {
|
|
|
+ let header = request.headers().get("authorization")?;
|
|
|
+ let header = header.to_str().ok()?;
|
|
|
+ header.strip_prefix("Basic ").map(|s| s.to_string())
|
|
|
}
|
|
|
|
|
|
fn decode_basic_auth(encoded: &str) -> AppResult<String> {
|
|
|
@@ -641,7 +872,12 @@ fn api_repository_list(
|
|
|
requesting_user: Option<&User>,
|
|
|
repos: &[crate::models::RepositoryWithOwner],
|
|
|
) -> AppResult<Vec<ApiRepositoryResponse>> {
|
|
|
- repos.iter()
|
|
|
+ repos
|
|
|
+ .iter()
|
|
|
.map(|repo| api_repository_response(state, requesting_user, repo))
|
|
|
.collect()
|
|
|
}
|
|
|
+
|
|
|
+fn blocking_error(_e: actix_web::error::BlockingError) -> AppError {
|
|
|
+ AppError::Pool("blocking task pool shut down".to_string())
|
|
|
+}
|