refactor!: setup file proxy for projects
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
use std::{env, sync::Arc};
|
||||
use std::env;
|
||||
|
||||
use crate::error::Result;
|
||||
use jsonwebtoken::{
|
||||
|
||||
@@ -6,9 +6,11 @@ use crate::AppState;
|
||||
pub async fn add_repo(
|
||||
app_state: web::Data<AppState>,
|
||||
user: web::ReqData<User>,
|
||||
payload: web::Json<RepositorySchema>,
|
||||
repo: web::Json<RepositorySchema>,
|
||||
) -> Result<HttpResponse> {
|
||||
let repo = payload.into_inner();
|
||||
validate_repo(app_state.clone(), &repo).await?;
|
||||
app_state.user.add_repository(&user.id, repo).await
|
||||
validate_repo(app_state.clone(), &repo, &user.id).await?;
|
||||
app_state
|
||||
.user
|
||||
.add_repository(&user.id, repo.into_inner())
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -24,9 +24,8 @@ pub struct Repository {
|
||||
|
||||
pub async fn get_repos(
|
||||
app_state: web::Data<AppState>,
|
||||
req: web::ReqData<User>,
|
||||
user: web::ReqData<User>,
|
||||
) -> Result<HttpResponse> {
|
||||
let user = req.into_inner();
|
||||
let token = app_state.user.get_access_token(&user.id).await?;
|
||||
|
||||
let response = app_state
|
||||
@@ -38,7 +37,7 @@ pub async fn get_repos(
|
||||
response.error_for_status_ref()?;
|
||||
let added_ids = app_state
|
||||
.user
|
||||
.get_repositories(&user.id)
|
||||
.get_repositories_user(&user.id)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|r| r.id)
|
||||
@@ -47,11 +46,9 @@ pub async fn get_repos(
|
||||
.json::<Vec<Repository>>()
|
||||
.await?
|
||||
.into_iter()
|
||||
.filter_map(|mut r| {
|
||||
(!r.private).then(|| {
|
||||
r.added = added_ids.contains(&r.id.to_string());
|
||||
r
|
||||
})
|
||||
.map(|mut r| {
|
||||
r.added = added_ids.contains(&r.id.to_string());
|
||||
r
|
||||
})
|
||||
.collect::<Vec<Repository>>();
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
pub mod add_repo;
|
||||
pub mod get_repos;
|
||||
pub mod global_repos;
|
||||
pub mod proxy_file;
|
||||
pub mod search_repos;
|
||||
|
||||
47
api/src/endpoints/proxy_file.rs
Normal file
47
api/src/endpoints/proxy_file.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
use actix_web::{HttpResponse, web};
|
||||
|
||||
use crate::{
|
||||
AppState,
|
||||
error::{Error, Result},
|
||||
};
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
pub struct Params {
|
||||
repo_id: String,
|
||||
file: String,
|
||||
}
|
||||
|
||||
pub async fn proxy_file(
|
||||
app_state: web::Data<AppState>,
|
||||
path: web::Path<Params>,
|
||||
) -> Result<HttpResponse> {
|
||||
let repo = app_state
|
||||
.user
|
||||
.get_approved_repository(&path.repo_id)
|
||||
.await?
|
||||
.ok_or(Error::NotFound)?;
|
||||
let token = app_state.user.get_access_token(&repo.owner_id).await?;
|
||||
|
||||
let url = format!(
|
||||
"https://raw.githubusercontent.com/{}/HEAD/dist/{}",
|
||||
repo.full_name, path.file
|
||||
);
|
||||
|
||||
let response = app_state
|
||||
.reqwest_client
|
||||
.get(&url)
|
||||
.bearer_auth(token)
|
||||
.send()
|
||||
.await?;
|
||||
response.error_for_status_ref()?;
|
||||
|
||||
let bytes = response.bytes().await?;
|
||||
let mime = mime_guess::from_path(&path.file)
|
||||
.first_or_octet_stream()
|
||||
.to_string();
|
||||
|
||||
Ok(HttpResponse::Ok()
|
||||
.content_type(mime)
|
||||
.insert_header(("Cache-Control", "public, max-age=3600"))
|
||||
.body(bytes))
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{auth::User, endpoints::get_repos::Repository, error::Result};
|
||||
use actix_web::{
|
||||
HttpRequest, HttpResponse,
|
||||
HttpResponse,
|
||||
web::{self, ReqData},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -20,9 +20,8 @@ struct SearchResponse {
|
||||
pub async fn search_repos(
|
||||
app_state: web::Data<AppState>,
|
||||
query: web::Query<SearchQuery>,
|
||||
req: ReqData<User>,
|
||||
user: ReqData<User>,
|
||||
) -> Result<HttpResponse> {
|
||||
let user = req.into_inner();
|
||||
let token = app_state.user.get_access_token(&user.id).await?;
|
||||
|
||||
let search_query = format!("user:{} {} fork:true", user.name, query.q);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use actix_web::{HttpResponse, ResponseError, http::StatusCode};
|
||||
use actix_web::{HttpResponse, ResponseError};
|
||||
use aws_sdk_dynamodb::error::SdkError;
|
||||
use serde::Serialize;
|
||||
use thiserror::Error;
|
||||
@@ -25,6 +25,8 @@ pub enum Error {
|
||||
AlreadyExists,
|
||||
#[error("validation failed: {0}")]
|
||||
ValidationFailed(String),
|
||||
#[error("not found")]
|
||||
NotFound,
|
||||
}
|
||||
|
||||
impl<E: std::fmt::Debug> From<SdkError<E>> for Error {
|
||||
@@ -49,9 +51,10 @@ impl ResponseError for Error {
|
||||
Error::AlreadyExists => HttpResponse::BadRequest().json(ErrorResponse {
|
||||
error: "item already exists".to_string(),
|
||||
}),
|
||||
Error::ValidationFailed(msg) => HttpResponse::BadRequest().json(ErrorResponse {
|
||||
error: msg.clone(),
|
||||
}),
|
||||
Error::ValidationFailed(msg) => {
|
||||
HttpResponse::BadRequest().json(ErrorResponse { error: msg.clone() })
|
||||
}
|
||||
Error::NotFound => HttpResponse::NotFound().finish(),
|
||||
_ => HttpResponse::InternalServerError().finish(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,6 +89,10 @@ async fn run() -> std::io::Result<()> {
|
||||
.route(
|
||||
"/repos",
|
||||
web::get().to(endpoints::global_repos::global_repos),
|
||||
)
|
||||
.route(
|
||||
"/repos/{repo_id}/files/{file:.*}",
|
||||
web::get().to(endpoints::proxy_file::proxy_file),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -18,6 +18,16 @@ pub struct UserRepository {
|
||||
pub struct RepositorySchema {
|
||||
pub id: String,
|
||||
pub full_name: String,
|
||||
#[serde(skip)]
|
||||
pub owner_id: String,
|
||||
pub description: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct GlobalRepositoriesResponse {
|
||||
pub id: String,
|
||||
pub full_name: String,
|
||||
pub description: String,
|
||||
}
|
||||
|
||||
impl UserRepository {
|
||||
@@ -38,7 +48,7 @@ impl UserRepository {
|
||||
.map_err(|_| crate::error::Error::AccessToken)
|
||||
}
|
||||
|
||||
pub async fn get_repositories(&self, user_id: &str) -> Result<Vec<RepositorySchema>> {
|
||||
pub async fn get_repositories_user(&self, user_id: &str) -> Result<Vec<RepositorySchema>> {
|
||||
let response = self
|
||||
.dynamodb_client
|
||||
.query()
|
||||
@@ -59,6 +69,8 @@ impl UserRepository {
|
||||
.strip_prefix("REPO#")?
|
||||
.to_string(),
|
||||
full_name: item.get("full_name")?.as_s().ok()?.to_string(),
|
||||
owner_id: item.get("owner_id")?.as_s().ok()?.to_string(),
|
||||
description: item.get("description")?.as_s().ok()?.to_string(),
|
||||
})
|
||||
})
|
||||
.collect::<Vec<RepositorySchema>>();
|
||||
@@ -70,7 +82,9 @@ impl UserRepository {
|
||||
let response = self
|
||||
.dynamodb_client
|
||||
.query()
|
||||
.key_condition_expression("pk = :pk")
|
||||
.table_name(&self.table_name)
|
||||
.index_name("gsi1")
|
||||
.key_condition_expression("gsi1pk = :pk")
|
||||
.expression_attribute_values(":pk", AttributeValue::S("REPOS".into()))
|
||||
.send()
|
||||
.await?
|
||||
@@ -80,13 +94,53 @@ impl UserRepository {
|
||||
if (*item.get("approved")?.as_bool().ok()?) == false {
|
||||
return None;
|
||||
};
|
||||
Some(item.get("full_name")?.as_s().ok()?.to_string())
|
||||
Some(GlobalRepositoriesResponse {
|
||||
id: item
|
||||
.get("sk")?
|
||||
.as_s()
|
||||
.ok()?
|
||||
.strip_prefix("REPO#")?
|
||||
.to_string(),
|
||||
full_name: item.get("full_name")?.as_s().ok()?.to_string(),
|
||||
description: item.get("description")?.as_s().ok()?.to_string(),
|
||||
})
|
||||
})
|
||||
.collect::<Vec<String>>();
|
||||
.collect::<Vec<GlobalRepositoriesResponse>>();
|
||||
|
||||
Ok(HttpResponse::Ok().json(response))
|
||||
}
|
||||
|
||||
pub async fn get_approved_repository(&self, repo_id: &str) -> Result<Option<RepositorySchema>> {
|
||||
let response = self
|
||||
.dynamodb_client
|
||||
.query()
|
||||
.table_name(&self.table_name)
|
||||
.index_name("gsi1")
|
||||
.key_condition_expression("gsi1pk = :pk")
|
||||
.filter_expression("sk = :sk AND approved = :approved")
|
||||
.expression_attribute_values(":pk", AttributeValue::S("REPOS".into()))
|
||||
.expression_attribute_values(":sk", AttributeValue::S(format!("REPO#{repo_id}")))
|
||||
.expression_attribute_values(":approved", AttributeValue::Bool(true))
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
let repo = response.items().first().and_then(|item| {
|
||||
Some(RepositorySchema {
|
||||
id: item
|
||||
.get("sk")?
|
||||
.as_s()
|
||||
.ok()?
|
||||
.strip_prefix("REPO#")?
|
||||
.to_string(),
|
||||
full_name: item.get("full_name")?.as_s().ok()?.to_string(),
|
||||
owner_id: item.get("owner_id")?.as_s().ok()?.to_string(),
|
||||
description: item.get("description")?.as_s().ok()?.to_string(),
|
||||
})
|
||||
});
|
||||
|
||||
Ok(repo)
|
||||
}
|
||||
|
||||
pub async fn add_repository(
|
||||
&self,
|
||||
user_id: &str,
|
||||
@@ -105,6 +159,8 @@ impl UserRepository {
|
||||
.item("gsi1sk", AttributeValue::S(now.clone()))
|
||||
.item("imported_at", AttributeValue::S(now))
|
||||
.item("approved", AttributeValue::Bool(false))
|
||||
.item("owner_id", AttributeValue::S(user_id.into()))
|
||||
.item("description", AttributeValue::S(repo.description.clone()))
|
||||
.send()
|
||||
.await;
|
||||
|
||||
|
||||
@@ -4,13 +4,19 @@ use crate::AppState;
|
||||
use crate::error::{Error, Result};
|
||||
use crate::user::RepositorySchema;
|
||||
|
||||
pub async fn validate_repo(app_state: web::Data<AppState>, repo: &RepositorySchema) -> Result<()> {
|
||||
pub async fn validate_repo(
|
||||
app_state: web::Data<AppState>,
|
||||
repo: &RepositorySchema,
|
||||
user_id: &str,
|
||||
) -> Result<()> {
|
||||
let token = app_state.user.get_access_token(&user_id).await?;
|
||||
let response = app_state
|
||||
.reqwest_client
|
||||
.get(format!(
|
||||
"https://raw.githubusercontent.com/{}/HEAD/dist/index.html",
|
||||
repo.full_name
|
||||
))
|
||||
.bearer_auth(token)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user