diff --git a/api/Cargo.lock b/api/Cargo.lock index 82e7260..ddeba4e 100644 --- a/api/Cargo.lock +++ b/api/Cargo.lock @@ -3381,6 +3381,7 @@ version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "873a97c3f7a67dd042bceb47d056d288424b82d4c66b0a25e1a3b34675620951" dependencies = [ + "aws-sdk-dynamodb", "base64 0.21.7", "serde", "serde_core", diff --git a/api/Cargo.toml b/api/Cargo.toml index b7ba705..7dc1d89 100644 --- a/api/Cargo.toml +++ b/api/Cargo.toml @@ -13,7 +13,7 @@ mime_guess = "2.0.5" reqwest = { version = "0.13.1", features = ["json", "query"] } sentry = { version = "0.46.1", features = ["actix", "tracing"] } serde = "1.0.228" -serde_dynamo = "4.3.0" +serde_dynamo = { version = "4.3.0", features = ["aws-sdk-dynamodb+1"] } sqlx = { version = "0.8.6", features = ["postgres", "runtime-tokio", "tls-native-tls"] } thiserror = "2.0.17" tokio = "1.49.0" diff --git a/api/src/endpoints/add_repo.rs b/api/src/endpoints/add_repo.rs index 06bc382..b5111aa 100644 --- a/api/src/endpoints/add_repo.rs +++ b/api/src/endpoints/add_repo.rs @@ -1,4 +1,4 @@ -use crate::{auth::User, error::Result, user::RepositorySchema, validate::validate_repo}; +use crate::{auth::User, error::Result, user::RepositoryDefinition, validate::validate_repo}; use actix_web::{HttpResponse, web}; use crate::AppState; @@ -6,7 +6,7 @@ use crate::AppState; pub async fn add_repo( app_state: web::Data, user: web::ReqData, - repo: web::Json, + repo: web::Json, ) -> Result { validate_repo(app_state.clone(), &repo, &user.id).await?; app_state diff --git a/api/src/user.rs b/api/src/user.rs index 29923fd..66427af 100644 --- a/api/src/user.rs +++ b/api/src/user.rs @@ -6,6 +6,7 @@ use actix_web::HttpResponse; use aws_sdk_dynamodb::types::AttributeValue; use chrono::Utc; use serde::{Deserialize, Serialize}; +use serde_dynamo::from_item; use sqlx::{PgPool, query_scalar}; pub struct UserRepository { @@ -14,8 +15,40 @@ pub struct UserRepository { table_name: String, } +#[derive(Deserialize)] +struct RepositoryDB { + sk: String, + full_name: String, + owner_id: String, + description: String, + #[serde(default)] + approved: bool, +} + +impl RepositoryDB { + fn into_repository(self) -> Option { + Some(RepositoryDefinition { + id: self.sk.strip_prefix("REPO#")?.to_string(), + full_name: self.full_name, + owner_id: self.owner_id, + description: self.description, + }) + } + + fn into_global_response(self) -> Option { + if !self.approved { + return None; + } + Some(GlobalRepositoriesResponse { + id: self.sk.strip_prefix("REPO#")?.to_string(), + full_name: self.full_name, + description: self.description, + }) + } +} + #[derive(Serialize, Deserialize)] -pub struct RepositorySchema { +pub struct RepositoryDefinition { pub id: String, pub full_name: String, #[serde(skip)] @@ -48,7 +81,7 @@ impl UserRepository { .map_err(|_| crate::error::Error::AccessToken) } - pub async fn get_repositories_user(&self, user_id: &str) -> Result> { + pub async fn get_repositories_user(&self, user_id: &str) -> Result> { let response = self .dynamodb_client .query() @@ -57,25 +90,18 @@ impl UserRepository { .expression_attribute_values(":pk", AttributeValue::S(format!("USER#{user_id}"))) .expression_attribute_values(":sk", AttributeValue::S("REPO#".into())) .send() - .await? + .await?; + + let repos = response .items() .iter() .filter_map(|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(), - }) + let dynamo_repo: RepositoryDB = from_item(item.clone()).ok()?; + dynamo_repo.into_repository() }) - .collect::>(); + .collect(); - Ok(response) + Ok(repos) } pub async fn global_repositories(&self) -> Result { @@ -87,30 +113,24 @@ impl UserRepository { .key_condition_expression("gsi1pk = :pk") .expression_attribute_values(":pk", AttributeValue::S("REPOS".into())) .send() - .await? + .await?; + + let repos: Vec = response .items() .iter() .filter_map(|item| { - if (*item.get("approved")?.as_bool().ok()?) == false { - return None; - }; - 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(), - }) + let dynamo_repo: RepositoryDB = from_item(item.clone()).ok()?; + dynamo_repo.into_global_response() }) - .collect::>(); + .collect(); - Ok(HttpResponse::Ok().json(response)) + Ok(HttpResponse::Ok().json(repos)) } - pub async fn get_approved_repository(&self, repo_id: &str) -> Result> { + pub async fn get_approved_repository( + &self, + repo_id: &str, + ) -> Result> { let response = self .dynamodb_client .query() @@ -125,17 +145,8 @@ impl UserRepository { .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(), - }) + let dynamo_repo: RepositoryDB = from_item(item.clone()).ok()?; + dynamo_repo.into_repository() }); Ok(repo) @@ -144,7 +155,7 @@ impl UserRepository { pub async fn add_repository( &self, user_id: &str, - repo: RepositorySchema, + repo: RepositoryDefinition, ) -> Result { let now = Utc::now().to_rfc3339(); let response = self diff --git a/api/src/validate.rs b/api/src/validate.rs index b681053..51dfbe0 100644 --- a/api/src/validate.rs +++ b/api/src/validate.rs @@ -2,11 +2,11 @@ use actix_web::web; use crate::AppState; use crate::error::{Error, Result}; -use crate::user::RepositorySchema; +use crate::user::RepositoryDefinition; pub async fn validate_repo( app_state: web::Data, - repo: &RepositorySchema, + repo: &RepositoryDefinition, user_id: &str, ) -> Result<()> { let token = app_state.user.get_access_token(&user_id).await?; diff --git a/src/lib/Header.svelte b/src/lib/Header.svelte index a4d7444..b267e45 100644 --- a/src/lib/Header.svelte +++ b/src/lib/Header.svelte @@ -19,7 +19,7 @@
-
+
goto('/')}>

Project Host

diff --git a/src/lib/Projects.svelte b/src/lib/Projects.svelte index 15c8dd6..880a795 100644 --- a/src/lib/Projects.svelte +++ b/src/lib/Projects.svelte @@ -18,27 +18,9 @@ staleTime: 60000 })); - const projectsQuery = createQuery(() => ({ - queryKey: ['projects', reposQuery.data], - queryFn: async () => { - const repos = reposQuery.data ?? []; - const projects = await Promise.all( - repos.map(async (repo: RepoDefinition) => { - const project = await resolveProjectData(repo); - return project; - }) - ); - return projects; - }, - enabled: !!reposQuery.data && reposQuery.data.length > 0, - staleTime: 60000 - })); - - const projects = $derived(projectsQuery.data ?? []); - const isLoading = $derived( - reposQuery.isPending || (projectsQuery.isPending && projectsQuery.isEnabled) - ); - const hasError = $derived(reposQuery.isError || projectsQuery.isError); + const projects = $derived(reposQuery.data ?? []); + const isLoading = $derived(reposQuery.isPending); + const hasError = $derived(reposQuery.isError); const isEmpty = $derived(!isLoading && !hasError && projects.length === 0); @@ -63,7 +45,7 @@
{project.name} @@ -84,7 +66,7 @@
-

{project.name}

+

{project.full_name}

{project.description}

diff --git a/src/lib/types/project.ts b/src/lib/types/project.ts index dd64da7..cd3acb1 100644 --- a/src/lib/types/project.ts +++ b/src/lib/types/project.ts @@ -9,4 +9,5 @@ export type Project = { export type RepoDefinition = { id: string; full_name: string; + description: string };