diff --git a/imphnen-backend/src/bin/seed_hackathons.rs b/imphnen-backend/src/bin/seed_hackathons.rs index 95a58ea..363aa86 100644 --- a/imphnen-backend/src/bin/seed_hackathons.rs +++ b/imphnen-backend/src/bin/seed_hackathons.rs @@ -216,6 +216,7 @@ async fn main() -> Result<(), Box> { theme, rules, prizes, + previous_winners: None, organizers, is_deleted: false, created_at: Some(get_iso_date()), diff --git a/imphnen-backend/src/bin/seed_test_submission.rs b/imphnen-backend/src/bin/seed_test_submission.rs index 57fd438..0d1c0f1 100644 --- a/imphnen-backend/src/bin/seed_test_submission.rs +++ b/imphnen-backend/src/bin/seed_test_submission.rs @@ -1,7 +1,7 @@ use chrono::{DateTime, Utc}; use imphnen_hackathon::v1::hackathon::hackathon_schema::{ - HackathonSchema, HackathonEventsSchema, HackathonTimelineSchema, HackathonSubmissionsSchema, - HackathonStatus, HackathonEventType, HackathonPhase, SubmissionStatus, Prize + HackathonSchema, HackathonTimelineSchema, HackathonSubmissionsSchema, + HackathonStatus, HackathonPhase, SubmissionStatus, Prize }; use imphnen_iam::{UsersSchema, v1::teams::TeamsSchema}; use imphnen_utils::{get_iso_date, hash_password}; @@ -259,6 +259,7 @@ async fn main() -> Result<(), Box> { theme, rules, prizes, + previous_winners: None, organizers, is_deleted: false, created_at: Some(get_iso_date()), diff --git a/imphnen-hackathon/src/v1/hackathon/hackathon_repository.rs b/imphnen-hackathon/src/v1/hackathon/hackathon_repository.rs index 2018ee3..f8b2698 100644 --- a/imphnen-hackathon/src/v1/hackathon/hackathon_repository.rs +++ b/imphnen-hackathon/src/v1/hackathon/hackathon_repository.rs @@ -266,7 +266,7 @@ impl<'a> HackathonRepository<'a> { let builder = QueryListBuilder::new(&self.state.surrealdb_ws, &table, &meta) .with_condition("is_deleted = false") - .with_condition(&format!("hackathon_id = app_hackathons:{}", hackathon_id)) + .with_condition(&format!("hackathon_id = type::thing('app_hackathons', '{}')", hackathon_id)) .search_field("title") .select_fields(vec!["*"]); @@ -394,7 +394,7 @@ impl<'a> HackathonRepository<'a> { let builder = QueryListBuilder::new(&self.state.surrealdb_ws, &table, &meta) .with_condition("is_deleted = false") - .with_condition(&format!("hackathon_id = app_hackathons:{}", hackathon_id)) + .with_condition(&format!("hackathon_id = type::thing('app_hackathons', '{}')", hackathon_id)) .search_field("title") .select_fields(vec!["*"]); @@ -517,7 +517,7 @@ impl<'a> HackathonRepository<'a> { let builder = QueryListBuilder::new(&self.state.surrealdb_ws, &table, &meta) .with_condition("is_deleted = false") - .with_condition(&format!("hackathon_id = app_hackathons:{}", hackathon_id)) + .with_condition(&format!("hackathon_id = type::thing('app_hackathons', '{}')", hackathon_id)) .search_field("project_name") .select_fields(vec!["*"]); diff --git a/imphnen-iam/src/v1/teams/teams_dto.rs b/imphnen-iam/src/v1/teams/teams_dto.rs index 34f4d1c..77b4d92 100644 --- a/imphnen-iam/src/v1/teams/teams_dto.rs +++ b/imphnen-iam/src/v1/teams/teams_dto.rs @@ -2,6 +2,7 @@ use serde::{Deserialize, Serialize}; use surrealdb::sql::Thing; use utoipa::ToSchema; use validator::Validate; +use imphnen_entities::users::UsersDetailQueryDto; #[derive(Clone, Debug, Serialize, Deserialize, ToSchema, Validate)] pub struct TeamsCreateRequestDto { @@ -219,13 +220,13 @@ pub struct TeamsDetailQueryDto { pub created_at: String, pub updated_at: String, } - #[derive(Clone, Debug, Serialize, Deserialize)] pub struct TeamsListQueryDto { pub id: Thing, pub name: String, pub description: Option, pub leader_id: Thing, + pub leader: Option, pub is_open: bool, pub max_members: Option, pub skills_required: Option>, @@ -353,11 +354,19 @@ impl TeamsListItemDto { impl TeamsListQueryDto { pub fn into_list_item_dto(self) -> TeamsListItemDto { - TeamsListItemDto { - id: self.id.id.to_raw(), - name: self.name, - description: self.description, - leader: TeamMemberDto { + let leader_dto = if let Some(leader_user) = self.leader { + TeamMemberDto { + id: String::new(), + user_id: leader_user.id.id.to_raw(), + fullname: leader_user.fullname, + email: Some(leader_user.email), + avatar: leader_user.avatar, + role: "leader".to_string(), + skills: leader_user.skills, + joined_at: self.created_at.clone(), + } + } else { + TeamMemberDto { id: String::new(), user_id: self.leader_id.id.to_raw(), fullname: String::new(), @@ -366,7 +375,14 @@ impl TeamsListQueryDto { role: "leader".to_string(), skills: None, joined_at: self.created_at.clone(), - }, + } + }; + + TeamsListItemDto { + id: self.id.id.to_raw(), + name: self.name, + description: self.description, + leader: leader_dto, is_open: self.is_open, current_member_count: 1, max_members: self.max_members, diff --git a/imphnen-iam/src/v1/teams/teams_repository.rs b/imphnen-iam/src/v1/teams/teams_repository.rs index 4f69d7f..7024bc9 100644 --- a/imphnen-iam/src/v1/teams/teams_repository.rs +++ b/imphnen-iam/src/v1/teams/teams_repository.rs @@ -38,6 +38,7 @@ impl<'a> TeamsRepository<'a> { .with_condition("is_deleted = false AND is_active = true") .search_field("name") .select_fields(vec!["*"]) + .fetch_fields(vec![]) .build() .await?; let elapsed = now.elapsed(); diff --git a/imphnen-iam/src/v1/teams/teams_service.rs b/imphnen-iam/src/v1/teams/teams_service.rs index c117a1c..236aa3b 100644 --- a/imphnen-iam/src/v1/teams/teams_service.rs +++ b/imphnen-iam/src/v1/teams/teams_service.rs @@ -135,7 +135,8 @@ impl TeamsServiceTrait for TeamsService { let thing_id = make_thing_from_enum(ResourceEnum::Teams, &id); match repo.query_team_by_id(&thing_id).await { Ok(team) if !team.is_deleted => { - let members = repo.query_team_members(&team.id).await.unwrap_or_default(); + let team_thing = make_thing_from_enum(ResourceEnum::Teams, &team.id.id.to_raw()); + let members = repo.query_team_members(&team_thing).await.unwrap_or_default(); let members_len = members.len(); // For public team details, only show sensitive info if user is authenticated and part of the team @@ -201,7 +202,8 @@ impl TeamsServiceTrait for TeamsService { let thing_id = make_thing_from_enum(ResourceEnum::Teams, &id); match repo.query_team_by_id(&thing_id).await { Ok(team) if !team.is_deleted => { - let members = repo.query_team_members(&team.id).await.unwrap_or_default(); + let team_thing = make_thing_from_enum(ResourceEnum::Teams, &team.id.id.to_raw()); + let members = repo.query_team_members(&team_thing).await.unwrap_or_default(); let members_len = members.len(); // For member team details, include all information including members list @@ -279,7 +281,14 @@ impl TeamsServiceTrait for TeamsService { Box::pin(async move { let repo = TeamsRepository::new(&state); match repo.query_team_list(meta).await { - Ok(data) => { + Ok(mut data) => { + // Calculate actual member count for each team + for team in &mut data.data { + let team_thing = make_thing_from_enum(ResourceEnum::Teams, &team.id); + let members = repo.query_team_members(&team_thing).await.unwrap_or_default(); + team.current_member_count = members.len() as i32 + 1; // +1 for leader + } + let response = ResponseListSuccessDto { data: data.data, meta: data.meta, @@ -301,7 +310,8 @@ impl TeamsServiceTrait for TeamsService { let thing_id = make_thing_from_enum(ResourceEnum::Teams, &id); match repo.query_team_by_id(&thing_id).await { Ok(team) if !team.is_deleted => { - let members = repo.query_team_members(&team.id).await.unwrap_or_default(); + let team_thing = make_thing_from_enum(ResourceEnum::Teams, &team.id.id.to_raw()); + let members = repo.query_team_members(&team_thing).await.unwrap_or_default(); let members_len = members.len(); // For public team details, only show sensitive info if user is authenticated and part of the team @@ -794,7 +804,8 @@ impl TeamsServiceTrait for TeamsService { let thing_id = make_thing_from_enum(ResourceEnum::Teams, &id); match repo.query_team_by_id(&thing_id).await { Ok(team) if !team.is_deleted => { - let members = repo.query_team_members(&team.id).await.unwrap_or_default(); + let team_thing = make_thing_from_enum(ResourceEnum::Teams, &team.id.id.to_raw()); + let members = repo.query_team_members(&team_thing).await.unwrap_or_default(); let mut member_dtos = Vec::new(); for member in members { diff --git a/imphnen-utils/src/query_builder.rs b/imphnen-utils/src/query_builder.rs index 22d26d4..57e2a0b 100644 --- a/imphnen-utils/src/query_builder.rs +++ b/imphnen-utils/src/query_builder.rs @@ -44,6 +44,13 @@ impl ListQueryBuilder { builder } + pub fn with_additional_conditions(mut self, additional_conditions: &[String]) -> Self { + for condition in additional_conditions { + self.conditions.push(condition.clone()); + } + self + } + pub fn new(resource: impl Into) -> Self { Self { resource: resource.into(), diff --git a/imphnen-utils/src/query_list.rs b/imphnen-utils/src/query_list.rs index 6777304..4f5d033 100644 --- a/imphnen-utils/src/query_list.rs +++ b/imphnen-utils/src/query_list.rs @@ -76,7 +76,7 @@ impl<'a> QueryListBuilder<'a> { &self.search_field, self.select_fields, self.fetch_fields, - ); + ).with_additional_conditions(&self.conditions); let data_sql = data_query_builder.build(); // --- Count Query --- @@ -86,7 +86,7 @@ impl<'a> QueryListBuilder<'a> { &self.search_field, None, // No select fields for count None, // No fetch fields for count - ); + ).with_additional_conditions(&self.conditions); let count_sql = count_query_builder.build_count(); // Combine both queries into a single query string within a transaction for a single database call @@ -129,6 +129,11 @@ impl<'a> QueryListBuilder<'a> { let count_result: Vec = response.take(1)?; // Second result is the count let total = count_result.first().map(|c| c.count); + // Debug logging + if std::env::var("RUST_ENV").unwrap_or_else(|_| "development".to_string()) == "development" { + println!("QueryListBuilder: data length = {}, total from count = {:?}", raw.len(), total); + println!("Combined SQL: {}", combined_sql); + } Ok(ResponseListSuccessDto { data: raw, diff --git a/tests/src/hackathon/hackathon_repository_test.rs b/tests/src/hackathon/hackathon_repository_test.rs index 08e04e2..30a182d 100644 --- a/tests/src/hackathon/hackathon_repository_test.rs +++ b/tests/src/hackathon/hackathon_repository_test.rs @@ -30,6 +30,7 @@ mod tests { theme: Some("AI/ML".to_string()), rules: Some("No cheating".to_string()), prizes: Some(vec![]), + previous_winners: None, organizers: vec!["user-1".to_string()], }; @@ -60,6 +61,7 @@ mod tests { theme: None, rules: None, prizes: None, + previous_winners: None, organizers: vec!["user-1".to_string()], }; @@ -104,6 +106,7 @@ mod tests { theme: None, rules: None, prizes: None, + previous_winners: None, organizers: vec!["user-1".to_string()], }; @@ -117,6 +120,7 @@ mod tests { theme: None, rules: None, prizes: None, + previous_winners: None, organizers: vec!["user-2".to_string()], }; @@ -156,6 +160,7 @@ mod tests { theme: None, rules: None, prizes: None, + previous_winners: None, organizers: vec!["user-1".to_string()], }; @@ -173,6 +178,7 @@ mod tests { theme: Some("Updated Theme".to_string()), rules: None, prizes: None, + previous_winners: None, organizers: None, }; @@ -205,6 +211,7 @@ mod tests { theme: None, rules: None, prizes: None, + previous_winners: None, organizers: vec!["user-1".to_string()], }; @@ -237,6 +244,7 @@ mod tests { theme: None, rules: None, prizes: None, + previous_winners: None, organizers: vec!["user-1".to_string()], }; @@ -284,6 +292,7 @@ mod tests { theme: None, rules: None, prizes: None, + previous_winners: None, organizers: vec!["user-1".to_string()], }; @@ -347,6 +356,7 @@ mod tests { theme: None, rules: None, prizes: None, + previous_winners: None, organizers: vec!["user-1".to_string()], }; @@ -411,6 +421,7 @@ mod tests { theme: None, rules: None, prizes: None, + previous_winners: None, organizers: vec!["user-1".to_string()], }; @@ -457,6 +468,7 @@ mod tests { theme: None, rules: None, prizes: None, + previous_winners: None, organizers: vec!["user-1".to_string()], }; @@ -503,6 +515,7 @@ mod tests { theme: None, rules: None, prizes: None, + previous_winners: None, organizers: vec!["user-1".to_string()], }; @@ -562,6 +575,7 @@ mod tests { theme: None, rules: None, prizes: None, + previous_winners: None, organizers: vec!["user-1".to_string()], }; @@ -622,6 +636,7 @@ mod tests { theme: None, rules: None, prizes: None, + previous_winners: None, organizers: vec!["user-1".to_string()], }; @@ -666,6 +681,7 @@ mod tests { theme: None, rules: None, prizes: None, + previous_winners: None, organizers: vec!["user-1".to_string()], }; @@ -711,6 +727,7 @@ mod tests { theme: None, rules: None, prizes: None, + previous_winners: None, organizers: vec!["user-1".to_string()], }; @@ -768,6 +785,7 @@ mod tests { theme: None, rules: None, prizes: None, + previous_winners: None, organizers: vec!["user-1".to_string()], }; @@ -828,6 +846,7 @@ mod tests { theme: None, rules: None, prizes: None, + previous_winners: None, organizers: vec!["user-1".to_string()], }; @@ -874,6 +893,7 @@ mod tests { theme: None, rules: None, prizes: None, + previous_winners: None, organizers: vec!["user-1".to_string()], }; @@ -916,6 +936,7 @@ mod tests { theme: None, rules: None, prizes: None, + previous_winners: None, organizers: None, }; @@ -1061,6 +1082,7 @@ mod tests { theme: None, rules: None, prizes: None, + previous_winners: None, organizers: vec!["user-1".to_string()], }; @@ -1108,6 +1130,7 @@ mod tests { theme: None, rules: None, prizes: None, + previous_winners: None, organizers: vec!["user-1".to_string()], }; diff --git a/tests/src/hackathon/hackathon_service_test.rs b/tests/src/hackathon/hackathon_service_test.rs index b8f186f..7e805bb 100644 --- a/tests/src/hackathon/hackathon_service_test.rs +++ b/tests/src/hackathon/hackathon_service_test.rs @@ -27,6 +27,7 @@ mod tests { theme: Some("AI/ML".to_string()), rules: Some("Be nice".to_string()), prizes: Some(vec![]), + previous_winners: None, organizers: vec!["user-1".to_string()], }; @@ -52,6 +53,7 @@ mod tests { theme: None, rules: None, prizes: None, + previous_winners: None, organizers: vec!["user-1".to_string()], }; @@ -77,6 +79,7 @@ mod tests { theme: None, rules: None, prizes: None, + previous_winners: None, organizers: vec!["user-1".to_string()], }; @@ -102,6 +105,7 @@ mod tests { theme: None, rules: None, prizes: None, + previous_winners: None, organizers: vec![], // Empty organizers }; @@ -127,6 +131,7 @@ mod tests { theme: None, rules: None, prizes: None, + previous_winners: None, organizers: vec!["user-1".to_string()], }; @@ -153,6 +158,7 @@ mod tests { theme: None, rules: None, prizes: None, + previous_winners: None, organizers: vec!["user-1".to_string()], }; @@ -196,6 +202,7 @@ mod tests { theme: None, rules: None, prizes: None, + previous_winners: None, organizers: vec!["user-1".to_string()], }; @@ -209,6 +216,7 @@ mod tests { theme: None, rules: None, prizes: None, + previous_winners: None, organizers: vec!["user-2".to_string()], }; @@ -247,6 +255,7 @@ mod tests { theme: None, rules: None, prizes: None, + previous_winners: None, organizers: vec!["user-1".to_string()], }; @@ -265,6 +274,7 @@ mod tests { theme: Some("Updated Theme".to_string()), rules: None, prizes: None, + previous_winners: None, organizers: None, }; @@ -292,6 +302,7 @@ mod tests { theme: None, rules: None, prizes: None, + previous_winners: None, organizers: vec!["user-1".to_string()], }; @@ -310,6 +321,7 @@ mod tests { theme: None, rules: None, prizes: None, + previous_winners: None, organizers: None, }; @@ -336,6 +348,7 @@ mod tests { theme: None, rules: None, prizes: None, + previous_winners: None, organizers: vec!["user-1".to_string()], }; @@ -370,6 +383,7 @@ mod tests { theme: None, rules: None, prizes: None, + previous_winners: None, organizers: vec!["user-1".to_string()], }; @@ -413,6 +427,7 @@ mod tests { theme: None, rules: None, prizes: None, + previous_winners: None, organizers: vec!["user-1".to_string()], }; @@ -480,6 +495,7 @@ mod tests { theme: None, rules: None, prizes: None, + previous_winners: None, organizers: vec!["user-1".to_string()], }; @@ -522,6 +538,7 @@ mod tests { theme: None, rules: None, prizes: None, + previous_winners: None, organizers: vec!["user-1".to_string()], }; @@ -563,6 +580,7 @@ mod tests { theme: None, rules: None, prizes: None, + previous_winners: None, organizers: vec!["user-1".to_string()], }; @@ -624,6 +642,7 @@ mod tests { theme: None, rules: None, prizes: None, + previous_winners: None, organizers: vec!["user-1".to_string()], }; @@ -666,6 +685,7 @@ mod tests { theme: Some("Edge Cases".to_string()), rules: Some("No rules".to_string()), prizes: Some(vec![]), + previous_winners: None, organizers: vec!["user-1".to_string()], }; @@ -691,6 +711,7 @@ mod tests { theme: Some("Scalability".to_string()), rules: Some("Scale rules".to_string()), prizes: Some(vec![]), + previous_winners: None, organizers: vec!["user-1".to_string()], }; @@ -715,6 +736,7 @@ mod tests { theme: None, rules: None, prizes: None, + previous_winners: None, organizers: vec!["user-1".to_string()], }; @@ -740,6 +762,7 @@ mod tests { theme: None, rules: None, prizes: None, + previous_winners: None, organizers: vec!["user-1".to_string()], }; @@ -765,6 +788,7 @@ mod tests { theme: None, rules: None, prizes: None, + previous_winners: None, organizers: vec!["user-1".to_string()], }; @@ -790,6 +814,7 @@ mod tests { theme: None, rules: None, prizes: None, + previous_winners: None, organizers: vec!["user-1".to_string()], }; @@ -815,6 +840,7 @@ mod tests { theme: None, rules: None, prizes: None, + previous_winners: None, organizers: vec!["user-1".to_string()], }; @@ -839,7 +865,8 @@ async fn test_create_hackathon_service_validation_error_start_date_in_past() { theme: None, rules: None, prizes: None, - organizers: vec!["user-1".to_string()], + previous_winners: None, + organizers: vec!["user-1".to_string()], }; let result = HackathonService::create_hackathon(request, &app_state).await; @@ -864,6 +891,7 @@ async fn test_create_hackathon_service_validation_error_start_date_in_past() { theme: None, rules: None, prizes: None, + previous_winners: None, organizers: vec!["user-1".to_string()], }; @@ -888,6 +916,7 @@ async fn test_create_hackathon_service_validation_error_start_date_in_past() { theme: None, rules: None, prizes: None, + previous_winners: None, organizers: vec!["user-1".to_string(), "user-1".to_string()], // Duplicate organizers }; @@ -927,6 +956,7 @@ async fn test_create_hackathon_service_validation_error_start_date_in_past() { description: Some("Winner prize".to_string()), value: Some("$1000".to_string()), }]), + previous_winners: None, organizers: vec!["user-1".to_string()], }; @@ -945,6 +975,7 @@ async fn test_create_hackathon_service_validation_error_start_date_in_past() { theme: None, rules: None, prizes: None, + previous_winners: None, organizers: None, }; @@ -973,6 +1004,7 @@ async fn test_create_hackathon_service_validation_error_start_date_in_past() { theme: None, rules: None, prizes: None, + previous_winners: None, organizers: None, }; @@ -1011,6 +1043,7 @@ async fn test_create_hackathon_service_validation_error_start_date_in_past() { theme: None, rules: None, prizes: None, + previous_winners: None, organizers: vec!["user-1".to_string()], }; @@ -1054,6 +1087,7 @@ async fn test_create_hackathon_service_validation_error_start_date_in_past() { theme: None, rules: None, prizes: None, + previous_winners: None, organizers: vec!["user-1".to_string()], }; @@ -1097,6 +1131,7 @@ async fn test_create_hackathon_service_validation_error_start_date_in_past() { theme: None, rules: None, prizes: None, + previous_winners: None, organizers: vec!["user-1".to_string()], }; @@ -1140,6 +1175,7 @@ async fn test_create_hackathon_service_validation_error_start_date_in_past() { theme: None, rules: None, prizes: None, + previous_winners: None, organizers: vec!["user-1".to_string()], }; @@ -1181,6 +1217,7 @@ async fn test_create_hackathon_service_validation_error_start_date_in_past() { theme: None, rules: None, prizes: None, + previous_winners: None, organizers: vec!["user-1".to_string()], }; @@ -1243,6 +1280,7 @@ async fn test_create_hackathon_service_validation_error_start_date_in_past() { theme: None, rules: None, prizes: None, + previous_winners: None, organizers: vec!["user-1".to_string()], }; @@ -1283,6 +1321,7 @@ async fn test_create_hackathon_service_validation_error_start_date_in_past() { theme: None, rules: None, prizes: None, + previous_winners: None, organizers: vec!["user-1".to_string()], }; @@ -1321,6 +1360,7 @@ async fn test_create_hackathon_service_validation_error_start_date_in_past() { theme: None, rules: None, prizes: None, + previous_winners: None, organizers: vec!["user-1".to_string()], }; @@ -1372,6 +1412,7 @@ async fn test_create_hackathon_service_validation_error_start_date_in_past() { theme: None, rules: None, prizes: None, + previous_winners: None, organizers: vec!["user-1".to_string()], }; let _ = HackathonService::create_hackathon(request, &app_state).await; @@ -1411,6 +1452,7 @@ async fn test_create_hackathon_service_validation_error_start_date_in_past() { theme: None, rules: None, prizes: None, + previous_winners: None, organizers: vec!["user-1".to_string()], }; @@ -1424,6 +1466,7 @@ async fn test_create_hackathon_service_validation_error_start_date_in_past() { theme: None, rules: None, prizes: None, + previous_winners: None, organizers: vec!["user-2".to_string()], };