feat: Add contact fields to hackathon and submission data; update registration service to handle string ID

This commit is contained in:
MythEclipse
2025-10-28 17:34:03 +07:00
parent 02421e9bc2
commit f7ca67d720
8 changed files with 42 additions and 23 deletions
@@ -338,9 +338,17 @@ async fn main() -> Result<(), Box<dyn Error>> {
project_name: Some(project_name.into()), project_name: Some(project_name.into()),
description: Some(description.into()), description: Some(description.into()),
repository_url, repository_url,
upload_file_url: None,
demo_url, demo_url,
slides_url, slides_url,
technologies: Some(technologies), technologies: Some(technologies),
contact_instagram: None,
contact_twitter: None,
contact_linkedin: None,
contact_facebook: None,
contact_youtube: None,
contact_tiktok: None,
contact_other: None,
submission_status: Some(submission_status), submission_status: Some(submission_status),
submitted_at: Some(DateTime::parse_from_rfc3339(submitted_at)?.with_timezone(&Utc)), submitted_at: Some(DateTime::parse_from_rfc3339(submitted_at)?.with_timezone(&Utc)),
is_deleted: false, is_deleted: false,
@@ -402,9 +402,17 @@ async fn main() -> Result<(), Box<dyn Error>> {
project_name: Some(project_name.into()), project_name: Some(project_name.into()),
description: Some(description.into()), description: Some(description.into()),
repository_url, repository_url,
upload_file_url: None,
demo_url, demo_url,
slides_url, slides_url,
technologies: Some(technologies), technologies: Some(technologies),
contact_instagram: None,
contact_twitter: None,
contact_linkedin: None,
contact_facebook: None,
contact_youtube: None,
contact_tiktok: None,
contact_other: None,
submission_status: Some(submission_status), submission_status: Some(submission_status),
judge_feedback: None, judge_feedback: None,
submitted_at: Some(DateTime::parse_from_rfc3339(submitted_at)?.with_timezone(&Utc)), submitted_at: Some(DateTime::parse_from_rfc3339(submitted_at)?.with_timezone(&Utc)),
@@ -10,8 +10,7 @@ use super::hackathon_schema::SubmissionStatus;
use crate::v1::hackathon::HackathonRepository; use crate::v1::hackathon::HackathonRepository;
use crate::{AppState, ResponseSuccessDto, ErrorDto}; use crate::{AppState, ResponseSuccessDto, ErrorDto};
use imphnen_entities::{PermissionsEnum, UsersDetailQueryDto}; use imphnen_entities::{PermissionsEnum, UsersDetailQueryDto};
use imphnen_libs::{MetaRequestDto, ResponseListSuccessDto, ValidatedJson}; use imphnen_libs::{MetaRequestDto, ResponseListSuccessDto};
use imphnen_iam::require_permissions;
use axum::{ use axum::{
extract::{Extension, Path, Query}, extract::{Extension, Path, Query},
http::StatusCode, http::StatusCode,
@@ -1,17 +1,14 @@
use super::notification_dto::{ use super::notification_dto::{
DeleteNotificationResponseDto, MarkAllAsReadResponseDto, MarkAsReadResponseDto, DeleteNotificationResponseDto, MarkAllAsReadResponseDto, MarkAsReadResponseDto, NotificationListQueryDto, NotificationListResponseDto,
NotificationDto, NotificationListQueryDto, NotificationListResponseDto,
UnreadCountResponseDto, UnreadCountResponseDto,
}; };
use super::notification_service::Service; use super::notification_service::Service;
use axum::{ use axum::{
extract::{Extension, Path, Query}, extract::{Extension, Path, Query},
http::{HeaderMap, Response, StatusCode}, http::{HeaderMap, Response, StatusCode},
response::IntoResponse,
routing::{delete, get, put}, routing::{delete, get, put},
Router, body::Body, Router, body::Body,
}; };
use imphnen_entities::common_dto::ResponseSuccessDto;
use imphnen_libs::AppState; use imphnen_libs::AppState;
use imphnen_utils::{extract_email::extract_email, response_format::common_response}; use imphnen_utils::{extract_email::extract_email, response_format::common_response};
@@ -1,6 +1,6 @@
use crate::v1::notifications::notification_schema::NotificationSchema; use crate::v1::notifications::notification_schema::NotificationSchema;
use imphnen_libs::AppState; use imphnen_libs::AppState;
use imphnen_utils::{get_id, make_thing}; use imphnen_utils::get_id;
use surrealdb::sql::Thing; use surrealdb::sql::Thing;
pub struct Repository<'a> { pub struct Repository<'a> {
@@ -57,7 +57,7 @@ pub async fn post_register_hackathon(
let hackathon_id = make_thing_from_enum(ResourceEnum::Hackathons, &id); let hackathon_id = make_thing_from_enum(ResourceEnum::Hackathons, &id);
let service = RegistrationsService::new(&state); let service = RegistrationsService::new(&state);
service.register_hackathon(&hackathon_id, &user_email, data).await service.register_hackathon(&hackathon_id, &id, &user_email, data).await
} }
// ============================================ // ============================================
@@ -105,8 +105,7 @@ impl<'a> RegistrationsRepository<'a> {
WHERE hackathon_id = $hackathon_id WHERE hackathon_id = $hackathon_id
AND status = $status AND status = $status
AND is_deleted = false AND is_deleted = false
FETCH hackathon_id, user_id, team_id FETCH hackathon_id, user_id, team_id;
ORDER BY registration_date DESC
"# "#
} else { } else {
r#" r#"
@@ -129,8 +128,7 @@ impl<'a> RegistrationsRepository<'a> {
FROM hackathon_registrations FROM hackathon_registrations
WHERE hackathon_id = $hackathon_id WHERE hackathon_id = $hackathon_id
AND is_deleted = false AND is_deleted = false
FETCH hackathon_id, user_id, team_id FETCH hackathon_id, user_id, team_id;
ORDER BY registration_date DESC
"# "#
}; };
@@ -172,7 +170,7 @@ impl<'a> RegistrationsRepository<'a> {
.map_err(|e| format!("Failed to parse registrations: {}", e))?; .map_err(|e| format!("Failed to parse registrations: {}", e))?;
// Convert to full DTO with all fetched data // Convert to full DTO with all fetched data
let registrations = simple let mut registrations: Vec<RegistrationListQueryDto> = simple
.into_iter() .into_iter()
.map(|r| RegistrationListQueryDto { .map(|r| RegistrationListQueryDto {
id: r.id, id: r.id,
@@ -185,7 +183,7 @@ impl<'a> RegistrationsRepository<'a> {
team_name: r.team_name, team_name: r.team_name,
status: r.status, status: r.status,
role: r.role, role: r.role,
registration_date: r.registration_date, registration_date: r.registration_date.clone(),
checked_in: r.checked_in, checked_in: r.checked_in,
check_in_time: r.check_in_time, check_in_time: r.check_in_time,
experience_level: r.experience_level, experience_level: r.experience_level,
@@ -193,6 +191,9 @@ impl<'a> RegistrationsRepository<'a> {
}) })
.collect(); .collect();
// Sort by registration_date DESC (newest first)
registrations.sort_by(|a, b| b.registration_date.cmp(&a.registration_date));
Ok(registrations) Ok(registrations)
} }
@@ -219,8 +220,7 @@ impl<'a> RegistrationsRepository<'a> {
FROM hackathon_registrations FROM hackathon_registrations
WHERE user_id = $user_id WHERE user_id = $user_id
AND is_deleted = false AND is_deleted = false
FETCH hackathon_id, team_id FETCH hackathon_id, team_id;
ORDER BY registration_date DESC
"#; "#;
let user_id_clone = user_id.clone(); let user_id_clone = user_id.clone();
@@ -251,7 +251,7 @@ impl<'a> RegistrationsRepository<'a> {
.map_err(|e| format!("Failed to parse user hackathons: {}", e))?; .map_err(|e| format!("Failed to parse user hackathons: {}", e))?;
// Convert to full DTO with all fetched data // Convert to full DTO with all fetched data
let hackathons = simple let mut hackathons: Vec<UserHackathonQueryDto> = simple
.into_iter() .into_iter()
.map(|h| UserHackathonQueryDto { .map(|h| UserHackathonQueryDto {
registration_id: h.registration_id, registration_id: h.registration_id,
@@ -262,13 +262,16 @@ impl<'a> RegistrationsRepository<'a> {
end_date: h.end_date, end_date: h.end_date,
status: h.status, status: h.status,
role: h.role, role: h.role,
registration_date: h.registration_date, registration_date: h.registration_date.clone(),
checked_in: h.checked_in, checked_in: h.checked_in,
team_id: h.team_id, team_id: h.team_id,
team_name: h.team_name, team_name: h.team_name,
}) })
.collect(); .collect();
// Sort by registration_date DESC (newest first)
hackathons.sort_by(|a, b| b.registration_date.cmp(&a.registration_date));
Ok(hackathons) Ok(hackathons)
} }
@@ -32,6 +32,7 @@ impl<'a> RegistrationsService<'a> {
pub async fn register_hackathon( pub async fn register_hackathon(
&self, &self,
hackathon_id: &Thing, hackathon_id: &Thing,
hackathon_id_str: &str,
user_email: &str, user_email: &str,
data: RegistrationRequestDto, data: RegistrationRequestDto,
) -> Response { ) -> Response {
@@ -47,14 +48,17 @@ impl<'a> RegistrationsService<'a> {
// Check if hackathon exists // Check if hackathon exists
let hackathon_repo = HackathonRepository::new(self.state); let hackathon_repo = HackathonRepository::new(self.state);
match hackathon_repo.get_by_id(hackathon_id).await { // Use the raw string ID from the path parameter
Ok(None) => { match hackathon_repo.get_hackathon_by_id(hackathon_id_str.to_string()).await {
return common_response(StatusCode::NOT_FOUND, "Hackathon not found");
}
Err(e) => { Err(e) => {
// Method returns error if hackathon not found or is deleted
let error_msg = e.to_string();
if error_msg.contains("not found") || error_msg.contains("Hackathon not found") {
return common_response(StatusCode::NOT_FOUND, "Hackathon not found");
}
return common_response(StatusCode::INTERNAL_SERVER_ERROR, &format!("Failed to verify hackathon: {}", e)); return common_response(StatusCode::INTERNAL_SERVER_ERROR, &format!("Failed to verify hackathon: {}", e));
} }
Ok(Some(_)) => {} // Hackathon exists, continue Ok(_) => {} // Hackathon exists, continue
} }
// Check if user already registered // Check if user already registered