a
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
use axum::Router;
|
||||
|
||||
pub mod hackathon;
|
||||
pub mod registrations;
|
||||
|
||||
// Export the router function from hackathon module
|
||||
pub use hackathon::hackathon_router;
|
||||
pub use registrations::registrations_router;
|
||||
|
||||
// Main route constructor
|
||||
pub fn hackathon_protected_routes() -> Router {
|
||||
@@ -14,6 +16,7 @@ pub fn hackathon_protected_routes() -> Router {
|
||||
.nest("/hackathons", hackathon_router())
|
||||
.route("/hackathons/submissions/{id}/status", axum::routing::patch(update_submission_status))
|
||||
.route("/hackathons/{hackathon_id}/admin/results", axum::routing::get(get_admin_hackathon_results))
|
||||
.merge(registrations_router())
|
||||
}
|
||||
|
||||
// Public routes for hackathons (only listing and retrieving)
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
pub mod registration_controller;
|
||||
pub mod registration_dto;
|
||||
pub mod registration_repository;
|
||||
pub mod registration_schema;
|
||||
pub mod registration_service;
|
||||
|
||||
pub use registration_controller::*;
|
||||
pub use registration_dto::*;
|
||||
pub use registration_repository::*;
|
||||
pub use registration_schema::*;
|
||||
pub use registration_service::*;
|
||||
@@ -0,0 +1,291 @@
|
||||
use axum::{
|
||||
extract::{Extension, Path},
|
||||
http::{HeaderMap, StatusCode},
|
||||
response::Response,
|
||||
routing::{get, post, put},
|
||||
Json, Router,
|
||||
};
|
||||
use imphnen_entities::ResponseSuccessDto;
|
||||
use imphnen_libs::AppState;
|
||||
use imphnen_utils::{common_response, extract_email, make_thing_from_enum};
|
||||
use imphnen_libs::ResourceEnum;
|
||||
|
||||
use super::{
|
||||
CheckInResponseDto, RegistrationListResponseDto, RegistrationRequestDto,
|
||||
RegistrationResponseDto, RegistrationStatsDto, RegistrationsService,
|
||||
UpdateRegistrationStatusRequestDto, UpdateRegistrationStatusResponseDto,
|
||||
UserHackathonsResponseDto,
|
||||
};
|
||||
|
||||
// ============================================
|
||||
// POST /v1/hackathons/{id}/register
|
||||
// ============================================
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/v1/hackathons/{id}/register",
|
||||
tag = "registrations",
|
||||
summary = "Register for a hackathon",
|
||||
description = "Submit a registration for a hackathon. User must be authenticated.",
|
||||
params(
|
||||
("id" = String, Path, description = "Hackathon ID")
|
||||
),
|
||||
request_body = RegistrationRequestDto,
|
||||
responses(
|
||||
(status = 200, description = "Registration submitted successfully", body = ResponseSuccessDto<RegistrationResponseDto>),
|
||||
(status = 400, description = "Invalid input or validation error"),
|
||||
(status = 401, description = "Unauthorized - authentication required"),
|
||||
(status = 409, description = "User already registered for this hackathon"),
|
||||
(status = 500, description = "Internal server error"),
|
||||
),
|
||||
security(
|
||||
("bearer_auth" = [])
|
||||
)
|
||||
)]
|
||||
pub async fn post_register_hackathon(
|
||||
Extension(state): Extension<AppState>,
|
||||
headers: HeaderMap,
|
||||
Path(id): Path<String>,
|
||||
Json(data): Json<RegistrationRequestDto>,
|
||||
) -> Response {
|
||||
// Authentication
|
||||
let user_email = match extract_email(&headers) {
|
||||
Some(email) => email,
|
||||
None => return common_response(StatusCode::UNAUTHORIZED, "Unauthorized"),
|
||||
};
|
||||
|
||||
// Parse hackathon ID
|
||||
let hackathon_id = make_thing_from_enum(ResourceEnum::Hackathons, &id);
|
||||
|
||||
let service = RegistrationsService::new(&state);
|
||||
service.register_hackathon(&hackathon_id, &user_email, data).await
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// GET /v1/hackathons/{id}/registrations
|
||||
// ============================================
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/v1/hackathons/{id}/registrations",
|
||||
tag = "registrations",
|
||||
summary = "List hackathon registrations",
|
||||
description = "Get all registrations for a hackathon. Requires admin/organizer permissions. Optional status filter.",
|
||||
params(
|
||||
("id" = String, Path, description = "Hackathon ID"),
|
||||
("status" = Option<String>, Query, description = "Filter by status: pending, approved, rejected, waitlisted, cancelled")
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Registrations retrieved successfully", body = ResponseSuccessDto<RegistrationListResponseDto>),
|
||||
(status = 400, description = "Invalid input"),
|
||||
(status = 401, description = "Unauthorized - authentication required"),
|
||||
(status = 500, description = "Internal server error"),
|
||||
),
|
||||
security(
|
||||
("bearer_auth" = [])
|
||||
)
|
||||
)]
|
||||
pub async fn get_hackathon_registrations(
|
||||
Extension(state): Extension<AppState>,
|
||||
headers: HeaderMap,
|
||||
Path(id): Path<String>,
|
||||
axum::extract::Query(params): axum::extract::Query<std::collections::HashMap<String, String>>,
|
||||
) -> Response {
|
||||
// Authentication
|
||||
match extract_email(&headers) {
|
||||
Some(_) => {},
|
||||
None => return common_response(StatusCode::UNAUTHORIZED, "Unauthorized"),
|
||||
};
|
||||
|
||||
// Parse hackathon ID
|
||||
let hackathon_id = make_thing_from_enum(ResourceEnum::Hackathons, &id);
|
||||
|
||||
let status_filter = params.get("status").cloned();
|
||||
|
||||
let service = RegistrationsService::new(&state);
|
||||
service.get_hackathon_registrations(&hackathon_id, status_filter).await
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// GET /v1/users/me/hackathons
|
||||
// ============================================
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/v1/users/me/hackathons",
|
||||
tag = "registrations",
|
||||
summary = "Get my hackathon registrations",
|
||||
description = "Get all hackathons the current user has registered for.",
|
||||
responses(
|
||||
(status = 200, description = "Hackathons retrieved successfully", body = ResponseSuccessDto<UserHackathonsResponseDto>),
|
||||
(status = 401, description = "Unauthorized - authentication required"),
|
||||
(status = 500, description = "Internal server error"),
|
||||
),
|
||||
security(
|
||||
("bearer_auth" = [])
|
||||
)
|
||||
)]
|
||||
pub async fn get_my_hackathons(
|
||||
Extension(state): Extension<AppState>,
|
||||
headers: HeaderMap,
|
||||
) -> Response {
|
||||
// Authentication
|
||||
let user_email = match extract_email(&headers) {
|
||||
Some(email) => email,
|
||||
None => return common_response(StatusCode::UNAUTHORIZED, "Unauthorized"),
|
||||
};
|
||||
|
||||
let service = RegistrationsService::new(&state);
|
||||
service.get_my_hackathons(&user_email).await
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// PUT /v1/hackathons/{hackathon_id}/registrations/{registration_id}/status
|
||||
// ============================================
|
||||
#[utoipa::path(
|
||||
put,
|
||||
path = "/v1/hackathons/{hackathon_id}/registrations/{registration_id}/status",
|
||||
tag = "registrations",
|
||||
summary = "Update registration status",
|
||||
description = "Approve, reject, or update the status of a registration. Requires admin/organizer permissions.",
|
||||
params(
|
||||
("hackathon_id" = String, Path, description = "Hackathon ID"),
|
||||
("registration_id" = String, Path, description = "Registration ID")
|
||||
),
|
||||
request_body = UpdateRegistrationStatusRequestDto,
|
||||
responses(
|
||||
(status = 200, description = "Status updated successfully", body = ResponseSuccessDto<UpdateRegistrationStatusResponseDto>),
|
||||
(status = 400, description = "Invalid input or validation error"),
|
||||
(status = 401, description = "Unauthorized - authentication required"),
|
||||
(status = 404, description = "Registration not found"),
|
||||
(status = 500, description = "Internal server error"),
|
||||
),
|
||||
security(
|
||||
("bearer_auth" = [])
|
||||
)
|
||||
)]
|
||||
pub async fn put_update_registration_status(
|
||||
Extension(state): Extension<AppState>,
|
||||
headers: HeaderMap,
|
||||
Path((_hackathon_id, registration_id)): Path<(String, String)>,
|
||||
Json(data): Json<UpdateRegistrationStatusRequestDto>,
|
||||
) -> Response {
|
||||
// Authentication
|
||||
match extract_email(&headers) {
|
||||
Some(_) => {},
|
||||
None => return common_response(StatusCode::UNAUTHORIZED, "Unauthorized"),
|
||||
};
|
||||
|
||||
// Parse registration ID
|
||||
let reg_id = make_thing_from_enum(ResourceEnum::HackathonRegistrations, ®istration_id);
|
||||
|
||||
let service = RegistrationsService::new(&state);
|
||||
service.update_registration_status(®_id, data).await
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// POST /v1/hackathons/{hackathon_id}/registrations/{registration_id}/check-in
|
||||
// ============================================
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/v1/hackathons/{hackathon_id}/registrations/{registration_id}/check-in",
|
||||
tag = "registrations",
|
||||
summary = "Check-in participant",
|
||||
description = "Mark a participant as checked in for the hackathon. Requires admin/organizer permissions.",
|
||||
params(
|
||||
("hackathon_id" = String, Path, description = "Hackathon ID"),
|
||||
("registration_id" = String, Path, description = "Registration ID")
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Participant checked in successfully", body = ResponseSuccessDto<CheckInResponseDto>),
|
||||
(status = 400, description = "Invalid request - participant not approved or already checked in"),
|
||||
(status = 401, description = "Unauthorized - authentication required"),
|
||||
(status = 404, description = "Registration not found"),
|
||||
(status = 500, description = "Internal server error"),
|
||||
),
|
||||
security(
|
||||
("bearer_auth" = [])
|
||||
)
|
||||
)]
|
||||
pub async fn post_check_in_participant(
|
||||
Extension(state): Extension<AppState>,
|
||||
headers: HeaderMap,
|
||||
Path((_hackathon_id, registration_id)): Path<(String, String)>,
|
||||
) -> Response {
|
||||
// Authentication
|
||||
match extract_email(&headers) {
|
||||
Some(_) => {},
|
||||
None => return common_response(StatusCode::UNAUTHORIZED, "Unauthorized"),
|
||||
};
|
||||
|
||||
// Parse registration ID
|
||||
let reg_id = make_thing_from_enum(ResourceEnum::HackathonRegistrations, ®istration_id);
|
||||
|
||||
let service = RegistrationsService::new(&state);
|
||||
service.check_in_participant(®_id).await
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// GET /v1/hackathons/{id}/registrations/stats
|
||||
// ============================================
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/v1/hackathons/{id}/registrations/stats",
|
||||
tag = "registrations",
|
||||
summary = "Get registration statistics",
|
||||
description = "Get comprehensive statistics about hackathon registrations. Requires admin/organizer permissions.",
|
||||
params(
|
||||
("id" = String, Path, description = "Hackathon ID")
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Statistics retrieved successfully", body = ResponseSuccessDto<RegistrationStatsDto>),
|
||||
(status = 400, description = "Invalid input"),
|
||||
(status = 401, description = "Unauthorized - authentication required"),
|
||||
(status = 500, description = "Internal server error"),
|
||||
),
|
||||
security(
|
||||
("bearer_auth" = [])
|
||||
)
|
||||
)]
|
||||
pub async fn get_registration_stats(
|
||||
Extension(state): Extension<AppState>,
|
||||
headers: HeaderMap,
|
||||
Path(id): Path<String>,
|
||||
) -> Response {
|
||||
// Authentication
|
||||
match extract_email(&headers) {
|
||||
Some(_) => {},
|
||||
None => return common_response(StatusCode::UNAUTHORIZED, "Unauthorized"),
|
||||
};
|
||||
|
||||
// Parse hackathon ID
|
||||
let hackathon_id = make_thing_from_enum(ResourceEnum::Hackathons, &id);
|
||||
|
||||
let service = RegistrationsService::new(&state);
|
||||
service.get_registration_stats(&hackathon_id).await
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Router
|
||||
// ============================================
|
||||
pub fn registrations_router() -> Router {
|
||||
Router::new()
|
||||
.route(
|
||||
"/hackathons/:id/register",
|
||||
post(post_register_hackathon),
|
||||
)
|
||||
.route(
|
||||
"/hackathons/:id/registrations",
|
||||
get(get_hackathon_registrations),
|
||||
)
|
||||
.route(
|
||||
"/hackathons/:id/registrations/stats",
|
||||
get(get_registration_stats),
|
||||
)
|
||||
.route(
|
||||
"/hackathons/:hackathon_id/registrations/:registration_id/status",
|
||||
put(put_update_registration_status),
|
||||
)
|
||||
.route(
|
||||
"/hackathons/:hackathon_id/registrations/:registration_id/check-in",
|
||||
post(post_check_in_participant),
|
||||
)
|
||||
.route("/users/me/hackathons", get(get_my_hackathons))
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use utoipa::ToSchema;
|
||||
use validator::Validate;
|
||||
|
||||
use super::{ParticipantRole, RegistrationStatus};
|
||||
|
||||
// ============================================
|
||||
// Registration Request/Response DTOs
|
||||
// ============================================
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, ToSchema, Validate)]
|
||||
pub struct RegistrationRequestDto {
|
||||
pub team_id: Option<String>,
|
||||
pub role: Option<ParticipantRole>,
|
||||
|
||||
#[validate(length(max = 1000, message = "Motivation must not exceed 1000 characters"))]
|
||||
pub motivation: Option<String>,
|
||||
|
||||
pub skills: Option<Vec<String>>,
|
||||
|
||||
#[validate(custom(function = "validate_experience_level"))]
|
||||
pub experience_level: Option<String>,
|
||||
|
||||
#[validate(length(max = 100))]
|
||||
pub github_username: Option<String>,
|
||||
|
||||
#[validate(url(message = "Invalid portfolio URL"))]
|
||||
pub portfolio_url: Option<String>,
|
||||
|
||||
pub dietary_requirements: Option<String>,
|
||||
|
||||
#[validate(custom(function = "validate_tshirt_size"))]
|
||||
pub tshirt_size: Option<String>,
|
||||
|
||||
#[validate(length(max = 100))]
|
||||
pub emergency_contact_name: Option<String>,
|
||||
|
||||
#[validate(length(max = 20))]
|
||||
pub emergency_contact_phone: Option<String>,
|
||||
}
|
||||
|
||||
fn validate_experience_level(level: &str) -> Result<(), validator::ValidationError> {
|
||||
let valid_levels = ["beginner", "intermediate", "advanced"];
|
||||
if valid_levels.contains(&level) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(validator::ValidationError::new("Invalid experience level"))
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_tshirt_size(size: &str) -> Result<(), validator::ValidationError> {
|
||||
let valid_sizes = ["XS", "S", "M", "L", "XL", "XXL"];
|
||||
if valid_sizes.contains(&size) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(validator::ValidationError::new("Invalid t-shirt size"))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, ToSchema)]
|
||||
pub struct RegistrationResponseDto {
|
||||
pub id: String,
|
||||
pub hackathon_id: String,
|
||||
pub user_id: String,
|
||||
pub team_id: Option<String>,
|
||||
pub status: RegistrationStatus,
|
||||
pub role: ParticipantRole,
|
||||
pub registration_date: String,
|
||||
pub checked_in: bool,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// List Registrations DTOs
|
||||
// ============================================
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, ToSchema)]
|
||||
pub struct RegistrationListItemDto {
|
||||
pub id: String,
|
||||
pub hackathon_id: String,
|
||||
pub hackathon_name: Option<String>,
|
||||
pub user_id: String,
|
||||
pub user_fullname: Option<String>,
|
||||
pub user_email: Option<String>,
|
||||
pub team_id: Option<String>,
|
||||
pub team_name: Option<String>,
|
||||
pub status: RegistrationStatus,
|
||||
pub role: ParticipantRole,
|
||||
pub registration_date: String,
|
||||
pub checked_in: bool,
|
||||
pub check_in_time: Option<String>,
|
||||
pub experience_level: Option<String>,
|
||||
pub skills: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, ToSchema)]
|
||||
pub struct RegistrationListResponseDto {
|
||||
pub registrations: Vec<RegistrationListItemDto>,
|
||||
pub total: usize,
|
||||
pub status_filter: Option<String>,
|
||||
}
|
||||
|
||||
// Internal query DTO (fields already as String from DB)
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct RegistrationListQueryDto {
|
||||
pub id: String,
|
||||
pub hackathon_id: String,
|
||||
pub hackathon_name: Option<String>,
|
||||
pub user_id: String,
|
||||
pub user_fullname: Option<String>,
|
||||
pub user_email: Option<String>,
|
||||
pub team_id: Option<String>,
|
||||
pub team_name: Option<String>,
|
||||
pub status: RegistrationStatus,
|
||||
pub role: ParticipantRole,
|
||||
pub registration_date: String,
|
||||
pub checked_in: bool,
|
||||
pub check_in_time: Option<String>,
|
||||
pub experience_level: Option<String>,
|
||||
pub skills: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Update Status DTOs
|
||||
// ============================================
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, ToSchema, Validate)]
|
||||
pub struct UpdateRegistrationStatusRequestDto {
|
||||
pub status: RegistrationStatus,
|
||||
|
||||
#[validate(length(max = 500, message = "Reason must not exceed 500 characters"))]
|
||||
pub reason: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, ToSchema)]
|
||||
pub struct UpdateRegistrationStatusResponseDto {
|
||||
pub id: String,
|
||||
pub status: RegistrationStatus,
|
||||
pub updated_at: String,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Check-in DTOs
|
||||
// ============================================
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, ToSchema)]
|
||||
pub struct CheckInResponseDto {
|
||||
pub id: String,
|
||||
pub user_fullname: Option<String>,
|
||||
pub checked_in: bool,
|
||||
pub check_in_time: String,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Statistics DTOs
|
||||
// ============================================
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, ToSchema)]
|
||||
pub struct RegistrationStatsDto {
|
||||
pub hackathon_id: String,
|
||||
pub hackathon_name: Option<String>,
|
||||
pub total_registrations: usize,
|
||||
pub pending: usize,
|
||||
pub approved: usize,
|
||||
pub rejected: usize,
|
||||
pub waitlisted: usize,
|
||||
pub cancelled: usize,
|
||||
pub checked_in: usize,
|
||||
pub team_registrations: usize,
|
||||
pub individual_registrations: usize,
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// User's Hackathons DTOs
|
||||
// ============================================
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, ToSchema)]
|
||||
pub struct UserHackathonDto {
|
||||
pub registration_id: String,
|
||||
pub hackathon_id: String,
|
||||
pub hackathon_name: String,
|
||||
pub hackathon_description: Option<String>,
|
||||
pub start_date: String,
|
||||
pub end_date: String,
|
||||
pub status: RegistrationStatus,
|
||||
pub role: ParticipantRole,
|
||||
pub registration_date: String,
|
||||
pub checked_in: bool,
|
||||
pub team_id: Option<String>,
|
||||
pub team_name: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, ToSchema)]
|
||||
pub struct UserHackathonsResponseDto {
|
||||
pub hackathons: Vec<UserHackathonDto>,
|
||||
pub total: usize,
|
||||
}
|
||||
|
||||
// Internal query DTO
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct UserHackathonQueryDto {
|
||||
pub registration_id: String,
|
||||
pub hackathon_id: String,
|
||||
pub hackathon_name: String,
|
||||
pub hackathon_description: Option<String>,
|
||||
pub start_date: String,
|
||||
pub end_date: String,
|
||||
pub status: RegistrationStatus,
|
||||
pub role: ParticipantRole,
|
||||
pub registration_date: String,
|
||||
pub checked_in: bool,
|
||||
pub team_id: Option<String>,
|
||||
pub team_name: Option<String>,
|
||||
}
|
||||
@@ -0,0 +1,274 @@
|
||||
use super::{RegistrationListQueryDto, RegistrationSchema, RegistrationStatus, UserHackathonQueryDto};
|
||||
use imphnen_libs::AppState;
|
||||
use imphnen_utils::get_id;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use surrealdb::sql::Thing;
|
||||
|
||||
pub struct RegistrationsRepository<'a> {
|
||||
pub state: &'a AppState,
|
||||
}
|
||||
|
||||
impl<'a> RegistrationsRepository<'a> {
|
||||
pub fn new(state: &'a AppState) -> Self {
|
||||
Self { state }
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Create Registration
|
||||
// ============================================
|
||||
pub async fn create_registration(&self, registration: RegistrationSchema) -> Result<RegistrationSchema, String> {
|
||||
let db = &self.state.surrealdb_ws;
|
||||
let created: Option<RegistrationSchema> = db
|
||||
.create("hackathon_registrations")
|
||||
.content(registration)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to create registration: {}", e))?;
|
||||
|
||||
created.ok_or_else(|| "Registration creation returned None".to_string())
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Get Registration by ID
|
||||
// ============================================
|
||||
pub async fn query_registration_by_id(&self, id: &Thing) -> Result<Option<RegistrationSchema>, String> {
|
||||
let db = &self.state.surrealdb_ws;
|
||||
let record_key = get_id(id).map_err(|e| e.to_string())?;
|
||||
let registration: Option<RegistrationSchema> = db
|
||||
.select(record_key)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to fetch registration: {}", e))?;
|
||||
|
||||
Ok(registration)
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Check if User Already Registered
|
||||
// ============================================
|
||||
pub async fn check_existing_registration(
|
||||
&self,
|
||||
hackathon_id: &Thing,
|
||||
user_id: &Thing,
|
||||
) -> Result<Option<RegistrationSchema>, String> {
|
||||
let db = &self.state.surrealdb_ws;
|
||||
let query = r#"
|
||||
SELECT * FROM hackathon_registrations
|
||||
WHERE hackathon_id = $hackathon_id
|
||||
AND user_id = $user_id
|
||||
AND is_deleted = false
|
||||
LIMIT 1
|
||||
"#;
|
||||
|
||||
let mut result = db
|
||||
.query(query)
|
||||
.bind(("hackathon_id", hackathon_id.clone()))
|
||||
.bind(("user_id", user_id.clone()))
|
||||
.await
|
||||
.map_err(|e| format!("Failed to check existing registration: {}", e))?;
|
||||
|
||||
let registration: Option<RegistrationSchema> = result
|
||||
.take(0)
|
||||
.map_err(|e| format!("Failed to parse registration: {}", e))?;
|
||||
|
||||
Ok(registration)
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// List Registrations for Hackathon
|
||||
// ============================================
|
||||
pub async fn query_hackathon_registrations(
|
||||
&self,
|
||||
hackathon_id: &Thing,
|
||||
status_filter: Option<RegistrationStatus>,
|
||||
) -> Result<Vec<RegistrationListQueryDto>, String> {
|
||||
let db = &self.state.surrealdb_ws;
|
||||
|
||||
let query = if status_filter.is_some() {
|
||||
r#"
|
||||
SELECT
|
||||
id,
|
||||
hackathon_id,
|
||||
(SELECT name FROM $parent.hackathon_id)[0].name AS hackathon_name,
|
||||
user_id,
|
||||
(SELECT fullname FROM $parent.user_id)[0].fullname AS user_fullname,
|
||||
(SELECT email FROM $parent.user_id)[0].email AS user_email,
|
||||
team_id,
|
||||
(SELECT name FROM $parent.team_id)[0].name AS team_name,
|
||||
status,
|
||||
role,
|
||||
registration_date,
|
||||
checked_in,
|
||||
check_in_time,
|
||||
experience_level,
|
||||
skills
|
||||
FROM hackathon_registrations
|
||||
WHERE hackathon_id = $hackathon_id
|
||||
AND status = $status
|
||||
AND is_deleted = false
|
||||
ORDER BY registration_date DESC
|
||||
"#
|
||||
} else {
|
||||
r#"
|
||||
SELECT
|
||||
id,
|
||||
hackathon_id,
|
||||
(SELECT name FROM $parent.hackathon_id)[0].name AS hackathon_name,
|
||||
user_id,
|
||||
(SELECT fullname FROM $parent.user_id)[0].fullname AS user_fullname,
|
||||
(SELECT email FROM $parent.user_id)[0].email AS user_email,
|
||||
team_id,
|
||||
(SELECT name FROM $parent.team_id)[0].name AS team_name,
|
||||
status,
|
||||
role,
|
||||
registration_date,
|
||||
checked_in,
|
||||
check_in_time,
|
||||
experience_level,
|
||||
skills
|
||||
FROM hackathon_registrations
|
||||
WHERE hackathon_id = $hackathon_id
|
||||
AND is_deleted = false
|
||||
ORDER BY registration_date DESC
|
||||
"#
|
||||
};
|
||||
|
||||
let hackathon_id_clone = hackathon_id.clone();
|
||||
let mut result = if let Some(status_val) = status_filter {
|
||||
db.query(query)
|
||||
.bind(("hackathon_id", hackathon_id_clone))
|
||||
.bind(("status", status_val))
|
||||
.await
|
||||
} else {
|
||||
db.query(query)
|
||||
.bind(("hackathon_id", hackathon_id_clone))
|
||||
.await
|
||||
}
|
||||
.map_err(|e| format!("Failed to query hackathon registrations: {}", e))?;
|
||||
|
||||
let registrations: Vec<RegistrationListQueryDto> = result
|
||||
.take(0)
|
||||
.map_err(|e| format!("Failed to parse registrations: {}", e))?;
|
||||
|
||||
Ok(registrations)
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Get User's Hackathon Registrations
|
||||
// ============================================
|
||||
pub async fn query_user_hackathons(&self, user_id: &Thing) -> Result<Vec<UserHackathonQueryDto>, String> {
|
||||
let db = &self.state.surrealdb_ws;
|
||||
let query = r#"
|
||||
SELECT
|
||||
id AS registration_id,
|
||||
hackathon_id,
|
||||
(SELECT name FROM $parent.hackathon_id)[0].name AS hackathon_name,
|
||||
(SELECT description FROM $parent.hackathon_id)[0].description AS hackathon_description,
|
||||
(SELECT start_date FROM $parent.hackathon_id)[0].start_date AS start_date,
|
||||
(SELECT end_date FROM $parent.hackathon_id)[0].end_date AS end_date,
|
||||
status,
|
||||
role,
|
||||
registration_date,
|
||||
checked_in,
|
||||
team_id,
|
||||
(SELECT name FROM $parent.team_id)[0].name AS team_name
|
||||
FROM hackathon_registrations
|
||||
WHERE user_id = $user_id
|
||||
AND is_deleted = false
|
||||
ORDER BY registration_date DESC
|
||||
"#;
|
||||
|
||||
let user_id_clone = user_id.clone();
|
||||
let mut result = db
|
||||
.query(query)
|
||||
.bind(("user_id", user_id_clone))
|
||||
.await
|
||||
.map_err(|e| format!("Failed to query user hackathons: {}", e))?;
|
||||
|
||||
let hackathons: Vec<UserHackathonQueryDto> = result
|
||||
.take(0)
|
||||
.map_err(|e| format!("Failed to parse user hackathons: {}", e))?;
|
||||
|
||||
Ok(hackathons)
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Get Registration Statistics
|
||||
// ============================================
|
||||
pub async fn query_registration_stats(&self, hackathon_id: &Thing) -> Result<RegistrationStatsQueryDto, String> {
|
||||
let db = &self.state.surrealdb_ws;
|
||||
let query = r#"
|
||||
LET $hackathon = (SELECT name FROM $hackathon_id)[0].name;
|
||||
LET $regs = (SELECT * FROM hackathon_registrations WHERE hackathon_id = $hackathon_id AND is_deleted = false);
|
||||
RETURN {
|
||||
hackathon_id: $hackathon_id,
|
||||
hackathon_name: $hackathon,
|
||||
total_registrations: count($regs),
|
||||
pending: count($regs[WHERE status = 'pending']),
|
||||
approved: count($regs[WHERE status = 'approved']),
|
||||
rejected: count($regs[WHERE status = 'rejected']),
|
||||
waitlisted: count($regs[WHERE status = 'waitlisted']),
|
||||
cancelled: count($regs[WHERE status = 'cancelled']),
|
||||
checked_in: count($regs[WHERE checked_in = true]),
|
||||
team_registrations: count($regs[WHERE team_id != NONE]),
|
||||
individual_registrations: count($regs[WHERE team_id = NONE])
|
||||
};
|
||||
"#;
|
||||
|
||||
let hackathon_id_clone = hackathon_id.clone();
|
||||
let mut result = db
|
||||
.query(query)
|
||||
.bind(("hackathon_id", hackathon_id_clone))
|
||||
.await
|
||||
.map_err(|e| format!("Failed to query registration stats: {}", e))?;
|
||||
|
||||
let stats: Option<RegistrationStatsQueryDto> = result
|
||||
.take(0)
|
||||
.map_err(|e| format!("Failed to parse registration stats: {}", e))?;
|
||||
|
||||
stats.ok_or_else(|| "Stats query returned None".to_string())
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Update Registration
|
||||
// ============================================
|
||||
pub async fn update_registration(&self, id: &Thing, registration: RegistrationSchema) -> Result<RegistrationSchema, String> {
|
||||
let db = &self.state.surrealdb_ws;
|
||||
let record_key = get_id(id).map_err(|e| e.to_string())?;
|
||||
let updated: Option<RegistrationSchema> = db
|
||||
.update(record_key)
|
||||
.content(registration)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to update registration: {}", e))?;
|
||||
|
||||
updated.ok_or_else(|| "Registration update returned None".to_string())
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Delete Registration (soft delete)
|
||||
// ============================================
|
||||
pub async fn delete_registration(&self, id: &Thing) -> Result<(), String> {
|
||||
let db = &self.state.surrealdb_ws;
|
||||
let record_key = get_id(id).map_err(|e| e.to_string())?;
|
||||
let _: Option<RegistrationSchema> = db
|
||||
.delete(record_key)
|
||||
.await
|
||||
.map_err(|e| format!("Failed to delete registration: {}", e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// Helper DTO for stats query
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct RegistrationStatsQueryDto {
|
||||
pub hackathon_id: String,
|
||||
pub hackathon_name: Option<String>,
|
||||
pub total_registrations: usize,
|
||||
pub pending: usize,
|
||||
pub approved: usize,
|
||||
pub rejected: usize,
|
||||
pub waitlisted: usize,
|
||||
pub cancelled: usize,
|
||||
pub checked_in: usize,
|
||||
pub team_registrations: usize,
|
||||
pub individual_registrations: usize,
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use surrealdb::sql::Thing;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
use imphnen_libs::ResourceEnum;
|
||||
use imphnen_utils::{get_iso_date, make_thing, make_thing_from_enum};
|
||||
|
||||
use super::RegistrationRequestDto;
|
||||
|
||||
/// Registration status enum
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema, PartialEq)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum RegistrationStatus {
|
||||
Pending,
|
||||
Approved,
|
||||
Rejected,
|
||||
Waitlisted,
|
||||
Cancelled,
|
||||
}
|
||||
|
||||
/// Participant role in hackathon
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema, PartialEq)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum ParticipantRole {
|
||||
Individual,
|
||||
TeamLeader,
|
||||
TeamMember,
|
||||
}
|
||||
|
||||
/// Hackathon registration schema
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct RegistrationSchema {
|
||||
pub id: Thing,
|
||||
pub hackathon_id: Thing,
|
||||
pub user_id: Thing,
|
||||
pub team_id: Option<Thing>,
|
||||
pub status: RegistrationStatus,
|
||||
pub role: ParticipantRole,
|
||||
pub registration_date: String,
|
||||
pub approved_at: Option<String>,
|
||||
pub rejected_at: Option<String>,
|
||||
pub rejection_reason: Option<String>,
|
||||
pub checked_in: bool,
|
||||
pub check_in_time: Option<String>,
|
||||
pub notes: Option<String>,
|
||||
pub skills: Option<Vec<String>>,
|
||||
pub experience_level: Option<String>, // beginner, intermediate, advanced
|
||||
pub github_username: Option<String>,
|
||||
pub portfolio_url: Option<String>,
|
||||
pub motivation: Option<String>,
|
||||
pub dietary_requirements: Option<String>,
|
||||
pub tshirt_size: Option<String>, // XS, S, M, L, XL, XXL
|
||||
pub emergency_contact_name: Option<String>,
|
||||
pub emergency_contact_phone: Option<String>,
|
||||
pub is_deleted: bool,
|
||||
pub created_at: String,
|
||||
pub updated_at: String,
|
||||
}
|
||||
|
||||
impl RegistrationSchema {
|
||||
/// Create a new registration from request DTO
|
||||
pub fn from_request(
|
||||
hackathon_id: &Thing,
|
||||
user_id: &Thing,
|
||||
data: RegistrationRequestDto,
|
||||
) -> Result<Self, String> {
|
||||
let now = get_iso_date();
|
||||
|
||||
// Convert team_id from String to Thing if provided
|
||||
let team_id_thing = data.team_id
|
||||
.as_ref()
|
||||
.map(|id| make_thing_from_enum(ResourceEnum::Teams, id));
|
||||
|
||||
Ok(Self {
|
||||
id: make_thing(ResourceEnum::HackathonRegistrations.as_str(), &uuid::Uuid::new_v4().to_string()),
|
||||
hackathon_id: hackathon_id.clone(),
|
||||
user_id: user_id.clone(),
|
||||
team_id: team_id_thing,
|
||||
status: RegistrationStatus::Pending,
|
||||
role: data.role.unwrap_or(ParticipantRole::Individual),
|
||||
registration_date: now.clone(),
|
||||
approved_at: None,
|
||||
rejected_at: None,
|
||||
rejection_reason: None,
|
||||
checked_in: false,
|
||||
check_in_time: None,
|
||||
notes: None,
|
||||
skills: data.skills,
|
||||
experience_level: data.experience_level,
|
||||
github_username: data.github_username,
|
||||
portfolio_url: data.portfolio_url,
|
||||
motivation: data.motivation,
|
||||
dietary_requirements: data.dietary_requirements,
|
||||
tshirt_size: data.tshirt_size,
|
||||
emergency_contact_name: data.emergency_contact_name,
|
||||
emergency_contact_phone: data.emergency_contact_phone,
|
||||
is_deleted: false,
|
||||
created_at: now.clone(),
|
||||
updated_at: now,
|
||||
})
|
||||
}
|
||||
|
||||
/// Update registration status
|
||||
pub fn update_status(&mut self, status: RegistrationStatus, reason: Option<String>) {
|
||||
let now = get_iso_date();
|
||||
self.status = status.clone();
|
||||
self.updated_at = now.clone();
|
||||
|
||||
match status {
|
||||
RegistrationStatus::Approved => {
|
||||
self.approved_at = Some(now);
|
||||
self.rejected_at = None;
|
||||
self.rejection_reason = None;
|
||||
}
|
||||
RegistrationStatus::Rejected => {
|
||||
self.rejected_at = Some(now);
|
||||
self.rejection_reason = reason;
|
||||
self.approved_at = None;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Check-in participant
|
||||
pub fn check_in(&mut self) -> Result<(), String> {
|
||||
if self.status != RegistrationStatus::Approved {
|
||||
return Err("Only approved registrations can be checked in".to_string());
|
||||
}
|
||||
|
||||
if self.checked_in {
|
||||
return Err("Already checked in".to_string());
|
||||
}
|
||||
|
||||
let now = get_iso_date();
|
||||
self.checked_in = true;
|
||||
self.check_in_time = Some(now.clone());
|
||||
self.updated_at = now;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,293 @@
|
||||
use axum::response::Response;
|
||||
use axum::http::StatusCode;
|
||||
use imphnen_entities::ResponseSuccessDto;
|
||||
use imphnen_libs::AppState;
|
||||
use imphnen_utils::{
|
||||
common_response, extract_id, make_thing_from_enum, success_response, validate_request,
|
||||
};
|
||||
use surrealdb::sql::Thing;
|
||||
|
||||
use super::{
|
||||
CheckInResponseDto, RegistrationListItemDto, RegistrationListResponseDto,
|
||||
RegistrationRequestDto, RegistrationResponseDto, RegistrationSchema, RegistrationStatsDto,
|
||||
RegistrationStatus, RegistrationsRepository, UpdateRegistrationStatusRequestDto,
|
||||
UpdateRegistrationStatusResponseDto, UserHackathonDto, UserHackathonsResponseDto,
|
||||
};
|
||||
use imphnen_libs::ResourceEnum;
|
||||
|
||||
pub struct RegistrationsService<'a> {
|
||||
state: &'a AppState,
|
||||
}
|
||||
|
||||
impl<'a> RegistrationsService<'a> {
|
||||
pub fn new(state: &'a AppState) -> Self {
|
||||
Self { state }
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Register for Hackathon
|
||||
// ============================================
|
||||
pub async fn register_hackathon(
|
||||
&self,
|
||||
hackathon_id: &Thing,
|
||||
user_email: &str,
|
||||
data: RegistrationRequestDto,
|
||||
) -> Response {
|
||||
// Validate request
|
||||
if let Err((status, message)) = validate_request(&data) {
|
||||
return common_response(status, &message);
|
||||
}
|
||||
|
||||
let repository = RegistrationsRepository::new(self.state);
|
||||
|
||||
// Get user ID from email
|
||||
let user_id = make_thing_from_enum(ResourceEnum::Users, user_email);
|
||||
|
||||
// Check if hackathon exists
|
||||
// TODO: Add hackathon existence check via hackathon repository
|
||||
|
||||
// Check if user already registered
|
||||
match repository
|
||||
.check_existing_registration(hackathon_id, &user_id)
|
||||
.await
|
||||
{
|
||||
Ok(Some(_)) => {
|
||||
return common_response(StatusCode::CONFLICT, "You have already registered for this hackathon")
|
||||
}
|
||||
Ok(None) => {}
|
||||
Err(e) => return common_response(StatusCode::INTERNAL_SERVER_ERROR, &e),
|
||||
}
|
||||
|
||||
// Create registration
|
||||
let registration = match RegistrationSchema::from_request(hackathon_id, &user_id, data) {
|
||||
Ok(reg) => reg,
|
||||
Err(e) => return common_response(StatusCode::BAD_REQUEST, &e),
|
||||
};
|
||||
|
||||
match repository.create_registration(registration).await {
|
||||
Ok(created) => {
|
||||
let response = RegistrationResponseDto {
|
||||
id: extract_id(&created.id),
|
||||
hackathon_id: extract_id(&created.hackathon_id),
|
||||
user_id: extract_id(&created.user_id),
|
||||
team_id: created.team_id.as_ref().map(|t| extract_id(t)),
|
||||
status: created.status,
|
||||
role: created.role,
|
||||
registration_date: created.registration_date,
|
||||
checked_in: created.checked_in,
|
||||
message: "Registration submitted successfully. You will be notified once approved."
|
||||
.to_string(),
|
||||
};
|
||||
|
||||
success_response(ResponseSuccessDto { data: response })
|
||||
}
|
||||
Err(e) => common_response(StatusCode::INTERNAL_SERVER_ERROR, &e),
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// List Registrations for Hackathon
|
||||
// ============================================
|
||||
pub async fn get_hackathon_registrations(
|
||||
&self,
|
||||
hackathon_id: &Thing,
|
||||
status_filter: Option<String>,
|
||||
) -> Response {
|
||||
let repository = RegistrationsRepository::new(self.state);
|
||||
|
||||
// Parse status filter if provided
|
||||
let status_enum = if let Some(status_str) = &status_filter {
|
||||
match status_str.to_lowercase().as_str() {
|
||||
"pending" => Some(RegistrationStatus::Pending),
|
||||
"approved" => Some(RegistrationStatus::Approved),
|
||||
"rejected" => Some(RegistrationStatus::Rejected),
|
||||
"waitlisted" => Some(RegistrationStatus::Waitlisted),
|
||||
"cancelled" => Some(RegistrationStatus::Cancelled),
|
||||
_ => return common_response(StatusCode::BAD_REQUEST, "Invalid status filter"),
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
match repository
|
||||
.query_hackathon_registrations(hackathon_id, status_enum)
|
||||
.await
|
||||
{
|
||||
Ok(results) => {
|
||||
let registrations = results
|
||||
.into_iter()
|
||||
.map(|r| RegistrationListItemDto {
|
||||
id: r.id,
|
||||
hackathon_id: r.hackathon_id,
|
||||
hackathon_name: r.hackathon_name,
|
||||
user_id: r.user_id,
|
||||
user_fullname: r.user_fullname,
|
||||
user_email: r.user_email,
|
||||
team_id: r.team_id,
|
||||
team_name: r.team_name,
|
||||
status: r.status,
|
||||
role: r.role,
|
||||
registration_date: r.registration_date,
|
||||
checked_in: r.checked_in,
|
||||
check_in_time: r.check_in_time,
|
||||
experience_level: r.experience_level,
|
||||
skills: r.skills,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let total = registrations.len();
|
||||
let response = RegistrationListResponseDto {
|
||||
registrations,
|
||||
total,
|
||||
status_filter,
|
||||
};
|
||||
|
||||
success_response(ResponseSuccessDto { data: response })
|
||||
}
|
||||
Err(e) => common_response(StatusCode::INTERNAL_SERVER_ERROR, &e),
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Get Current User's Hackathon Registrations
|
||||
// ============================================
|
||||
pub async fn get_my_hackathons(&self, user_email: &str) -> Response {
|
||||
let repository = RegistrationsRepository::new(self.state);
|
||||
|
||||
// Get user ID from email
|
||||
let user_id = make_thing_from_enum(ResourceEnum::Users, user_email);
|
||||
|
||||
match repository.query_user_hackathons(&user_id).await {
|
||||
Ok(results) => {
|
||||
let hackathons = results
|
||||
.into_iter()
|
||||
.map(|h| UserHackathonDto {
|
||||
registration_id: h.registration_id,
|
||||
hackathon_id: h.hackathon_id,
|
||||
hackathon_name: h.hackathon_name,
|
||||
hackathon_description: h.hackathon_description,
|
||||
start_date: h.start_date,
|
||||
end_date: h.end_date,
|
||||
status: h.status,
|
||||
role: h.role,
|
||||
registration_date: h.registration_date,
|
||||
checked_in: h.checked_in,
|
||||
team_id: h.team_id,
|
||||
team_name: h.team_name,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let total = hackathons.len();
|
||||
let response = UserHackathonsResponseDto { hackathons, total };
|
||||
|
||||
success_response(ResponseSuccessDto { data: response })
|
||||
}
|
||||
Err(e) => common_response(StatusCode::INTERNAL_SERVER_ERROR, &e),
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Update Registration Status
|
||||
// ============================================
|
||||
pub async fn update_registration_status(
|
||||
&self,
|
||||
registration_id: &Thing,
|
||||
data: UpdateRegistrationStatusRequestDto,
|
||||
) -> Response {
|
||||
// Validate request
|
||||
if let Err((status, message)) = validate_request(&data) {
|
||||
return common_response(status, &message);
|
||||
}
|
||||
|
||||
let repository = RegistrationsRepository::new(self.state);
|
||||
|
||||
// Get existing registration
|
||||
let mut registration = match repository.query_registration_by_id(registration_id).await {
|
||||
Ok(Some(reg)) => reg,
|
||||
Ok(None) => return common_response(StatusCode::NOT_FOUND, "Registration not found"),
|
||||
Err(e) => return common_response(StatusCode::INTERNAL_SERVER_ERROR, &e),
|
||||
};
|
||||
|
||||
// Update status
|
||||
registration.update_status(data.status.clone(), data.reason);
|
||||
|
||||
// Save updated registration
|
||||
match repository.update_registration(registration_id, registration.clone()).await {
|
||||
Ok(updated) => {
|
||||
let status_clone = updated.status.clone();
|
||||
let response = UpdateRegistrationStatusResponseDto {
|
||||
id: extract_id(&updated.id),
|
||||
status: updated.status,
|
||||
updated_at: updated.updated_at,
|
||||
message: format!("Registration status updated to {:?}", status_clone),
|
||||
};
|
||||
|
||||
success_response(ResponseSuccessDto { data: response })
|
||||
}
|
||||
Err(e) => common_response(StatusCode::INTERNAL_SERVER_ERROR, &e),
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Check-in Participant
|
||||
// ============================================
|
||||
pub async fn check_in_participant(&self, registration_id: &Thing) -> Response {
|
||||
let repository = RegistrationsRepository::new(self.state);
|
||||
|
||||
// Get existing registration
|
||||
let mut registration = match repository.query_registration_by_id(registration_id).await {
|
||||
Ok(Some(reg)) => reg,
|
||||
Ok(None) => return common_response(StatusCode::NOT_FOUND, "Registration not found"),
|
||||
Err(e) => return common_response(StatusCode::INTERNAL_SERVER_ERROR, &e),
|
||||
};
|
||||
|
||||
// Perform check-in
|
||||
if let Err(e) = registration.check_in() {
|
||||
return common_response(StatusCode::BAD_REQUEST, &e);
|
||||
}
|
||||
|
||||
// Save updated registration
|
||||
match repository.update_registration(registration_id, registration.clone()).await {
|
||||
Ok(updated) => {
|
||||
let response = CheckInResponseDto {
|
||||
id: extract_id(&updated.id),
|
||||
user_fullname: None, // Would need to query user info
|
||||
checked_in: updated.checked_in,
|
||||
check_in_time: updated.check_in_time.unwrap_or_default(),
|
||||
message: "Participant checked in successfully".to_string(),
|
||||
};
|
||||
|
||||
success_response(ResponseSuccessDto { data: response })
|
||||
}
|
||||
Err(e) => common_response(StatusCode::INTERNAL_SERVER_ERROR, &e),
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Get Registration Statistics
|
||||
// ============================================
|
||||
pub async fn get_registration_stats(&self, hackathon_id: &Thing) -> Response {
|
||||
let repository = RegistrationsRepository::new(self.state);
|
||||
|
||||
match repository.query_registration_stats(hackathon_id).await {
|
||||
Ok(stats) => {
|
||||
let response = RegistrationStatsDto {
|
||||
hackathon_id: stats.hackathon_id,
|
||||
hackathon_name: stats.hackathon_name,
|
||||
total_registrations: stats.total_registrations,
|
||||
pending: stats.pending,
|
||||
approved: stats.approved,
|
||||
rejected: stats.rejected,
|
||||
waitlisted: stats.waitlisted,
|
||||
cancelled: stats.cancelled,
|
||||
checked_in: stats.checked_in,
|
||||
team_registrations: stats.team_registrations,
|
||||
individual_registrations: stats.individual_registrations,
|
||||
};
|
||||
|
||||
success_response(ResponseSuccessDto { data: response })
|
||||
}
|
||||
Err(e) => common_response(StatusCode::INTERNAL_SERVER_ERROR, &e),
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user