feat: add repo validation
This commit is contained in:
@@ -1,19 +1,15 @@
|
|||||||
use crate::{auth::User, error::Result, user::RepositorySchema};
|
use crate::{auth::User, error::Result, user::RepositorySchema, validate::validate_repo};
|
||||||
use actix_web::{HttpResponse, web};
|
use actix_web::{HttpResponse, web};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::AppState;
|
use crate::AppState;
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
struct AddResponse {
|
|
||||||
id: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn add_repo(
|
pub async fn add_repo(
|
||||||
app_state: web::Data<AppState>,
|
app_state: web::Data<AppState>,
|
||||||
user: web::ReqData<User>,
|
user: web::ReqData<User>,
|
||||||
payload: web::Json<RepositorySchema>,
|
payload: web::Json<RepositorySchema>,
|
||||||
) -> Result<HttpResponse> {
|
) -> Result<HttpResponse> {
|
||||||
let repo = payload.into_inner();
|
let repo = payload.into_inner();
|
||||||
|
validate_repo(app_state.clone(), &repo).await?;
|
||||||
app_state.user.add_repository(&user.id, repo).await
|
app_state.user.add_repository(&user.id, repo).await
|
||||||
}
|
}
|
||||||
|
|||||||
8
api/src/endpoints/global_repos.rs
Normal file
8
api/src/endpoints/global_repos.rs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
use crate::error::Result;
|
||||||
|
use actix_web::{HttpResponse, web};
|
||||||
|
|
||||||
|
use crate::AppState;
|
||||||
|
|
||||||
|
pub async fn global_repos(app_state: web::Data<AppState>) -> Result<HttpResponse> {
|
||||||
|
Ok(HttpResponse::Ok().finish())
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
pub mod add_repo;
|
pub mod add_repo;
|
||||||
pub mod get_repos;
|
pub mod get_repos;
|
||||||
|
pub mod global_repos;
|
||||||
pub mod search_repos;
|
pub mod search_repos;
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ pub enum Error {
|
|||||||
DynamoDB(String),
|
DynamoDB(String),
|
||||||
#[error("item already exists")]
|
#[error("item already exists")]
|
||||||
AlreadyExists,
|
AlreadyExists,
|
||||||
|
#[error("validation failed: {0}")]
|
||||||
|
ValidationFailed(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: std::fmt::Debug> From<SdkError<E>> for Error {
|
impl<E: std::fmt::Debug> From<SdkError<E>> for Error {
|
||||||
@@ -47,6 +49,9 @@ impl ResponseError for Error {
|
|||||||
Error::AlreadyExists => HttpResponse::BadRequest().json(ErrorResponse {
|
Error::AlreadyExists => HttpResponse::BadRequest().json(ErrorResponse {
|
||||||
error: "item already exists".to_string(),
|
error: "item already exists".to_string(),
|
||||||
}),
|
}),
|
||||||
|
Error::ValidationFailed(msg) => HttpResponse::BadRequest().json(ErrorResponse {
|
||||||
|
error: msg.clone(),
|
||||||
|
}),
|
||||||
_ => HttpResponse::InternalServerError().finish(),
|
_ => HttpResponse::InternalServerError().finish(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ mod endpoints;
|
|||||||
mod error;
|
mod error;
|
||||||
mod middleware;
|
mod middleware;
|
||||||
mod user;
|
mod user;
|
||||||
|
mod validate;
|
||||||
|
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
@@ -72,7 +73,8 @@ async fn run() -> std::io::Result<()> {
|
|||||||
)
|
)
|
||||||
.service(
|
.service(
|
||||||
web::scope("/api").service(
|
web::scope("/api").service(
|
||||||
web::scope("/v0").service(
|
web::scope("/v0")
|
||||||
|
.service(
|
||||||
web::scope("/user")
|
web::scope("/user")
|
||||||
.route("/repos", web::get().to(endpoints::get_repos::get_repos))
|
.route("/repos", web::get().to(endpoints::get_repos::get_repos))
|
||||||
.wrap(from_fn(middleware::protected))
|
.wrap(from_fn(middleware::protected))
|
||||||
@@ -83,6 +85,10 @@ async fn run() -> std::io::Result<()> {
|
|||||||
.wrap(from_fn(middleware::protected))
|
.wrap(from_fn(middleware::protected))
|
||||||
.route("/repo/add", web::post().to(endpoints::add_repo::add_repo))
|
.route("/repo/add", web::post().to(endpoints::add_repo::add_repo))
|
||||||
.wrap(from_fn(middleware::protected)),
|
.wrap(from_fn(middleware::protected)),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
"/repos",
|
||||||
|
web::get().to(endpoints::global_repos::global_repos),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ pub struct UserRepository {
|
|||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct RepositorySchema {
|
pub struct RepositorySchema {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub name: String,
|
pub full_name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UserRepository {
|
impl UserRepository {
|
||||||
@@ -58,7 +58,7 @@ impl UserRepository {
|
|||||||
.ok()?
|
.ok()?
|
||||||
.strip_prefix("REPO#")?
|
.strip_prefix("REPO#")?
|
||||||
.to_string(),
|
.to_string(),
|
||||||
name: item.get("full_name")?.as_s().ok()?.to_string(),
|
full_name: item.get("full_name")?.as_s().ok()?.to_string(),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.collect::<Vec<RepositorySchema>>();
|
.collect::<Vec<RepositorySchema>>();
|
||||||
@@ -66,6 +66,20 @@ impl UserRepository {
|
|||||||
Ok(response)
|
Ok(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn global_repositories(&self) -> Result<HttpResponse> {
|
||||||
|
let response = self
|
||||||
|
.dynamodb_client
|
||||||
|
.query()
|
||||||
|
.key_condition_expression("pk = :pk")
|
||||||
|
.expression_attribute_values(":pk", AttributeValue::S("REPOS".into()))
|
||||||
|
.send()
|
||||||
|
.await?
|
||||||
|
.items()
|
||||||
|
.iter()
|
||||||
|
.filter_map(|item| Some(item.get("full_name")?.as_s().ok()?.to_string()))
|
||||||
|
.collect::<Vec<String>>();
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn add_repository(
|
pub async fn add_repository(
|
||||||
&self,
|
&self,
|
||||||
user_id: &str,
|
user_id: &str,
|
||||||
@@ -79,7 +93,7 @@ impl UserRepository {
|
|||||||
.condition_expression("attribute_not_exists(sk)")
|
.condition_expression("attribute_not_exists(sk)")
|
||||||
.item("pk", AttributeValue::S(format!("USER#{user_id}")))
|
.item("pk", AttributeValue::S(format!("USER#{user_id}")))
|
||||||
.item("sk", AttributeValue::S(format!("REPO#{}", repo.id)))
|
.item("sk", AttributeValue::S(format!("REPO#{}", repo.id)))
|
||||||
.item("full_name", AttributeValue::S(repo.name.to_string()))
|
.item("full_name", AttributeValue::S(repo.full_name.to_string()))
|
||||||
.item("gsi1pk", AttributeValue::S("REPOS".into()))
|
.item("gsi1pk", AttributeValue::S("REPOS".into()))
|
||||||
.item("gsi1sk", AttributeValue::S(now.clone()))
|
.item("gsi1sk", AttributeValue::S(now.clone()))
|
||||||
.item("imported_at", AttributeValue::S(now))
|
.item("imported_at", AttributeValue::S(now))
|
||||||
|
|||||||
27
api/src/validate.rs
Normal file
27
api/src/validate.rs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
use actix_web::web;
|
||||||
|
|
||||||
|
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<()> {
|
||||||
|
let response = app_state
|
||||||
|
.reqwest_client
|
||||||
|
.get(format!(
|
||||||
|
"https://raw.githubusercontent.com/{}/HEAD/dist/index.html",
|
||||||
|
repo.full_name
|
||||||
|
))
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
match response.status() {
|
||||||
|
reqwest::StatusCode::OK => Ok(()),
|
||||||
|
reqwest::StatusCode::NOT_FOUND => Err(Error::ValidationFailed(
|
||||||
|
"dist/index.html not found in repository".to_string(),
|
||||||
|
)),
|
||||||
|
status => Err(Error::ValidationFailed(format!(
|
||||||
|
"failed to validate repository: HTTP {}",
|
||||||
|
status
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -73,11 +73,14 @@
|
|||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
id: repo.id.toString(),
|
id: repo.id.toString(),
|
||||||
name: repo.full_name
|
full_name: repo.full_name
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
const data = await response.json();
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
toast.warning('Repository already imported');
|
toast.warning(data.error);
|
||||||
|
adding = null;
|
||||||
|
return;
|
||||||
} else {
|
} else {
|
||||||
toast.success('Successfully added repository');
|
toast.success('Successfully added repository');
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user