Add comprehensive tests for mentor repository and authentication
- Implemented tests for creating, retrieving, updating, and deleting mentors in `mentor_repository_test.rs`. - Added tests for user authentication, including successful login, invalid email formats, and inactive users in `auth_login_tests.rs`. - Created a mock test environment setup in `mock_test.rs` to facilitate database operations during tests. - Updated module structure to include new test files for mentors and authentication. - Ensured cleanup of the database after tests to maintain isolation and prevent side effects.
This commit is contained in:
@@ -1,14 +1,2 @@
|
||||
pub fn add(left: u64, right: u64) -> u64 {
|
||||
left + right
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let result = add(2, 2);
|
||||
assert_eq!(result, 4);
|
||||
}
|
||||
}
|
||||
pub mod v1;
|
||||
pub use v1::*;
|
||||
|
||||
@@ -0,0 +1,356 @@
|
||||
use super::{
|
||||
MentorDetailResponseDto, MentorListResponseDto, MentorUpdateRequestDto,
|
||||
MentorUserRegisterRequestDto, MentorVerifyRequestDto, MentorsService,
|
||||
};
|
||||
use crate::v1::mentors::mentors_dto::MentorRegisterResponseDto;
|
||||
use ::axum::{
|
||||
extract::{Extension, Json, Path, Query},
|
||||
http::HeaderMap,
|
||||
response::{IntoResponse, Response},
|
||||
};
|
||||
use imphnen_entities::*;
|
||||
use imphnen_iam::{PermissionsEnum, permissions_guard};
|
||||
use imphnen_utils::extract_email;
|
||||
use serde_json::json;
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/v1/mentors/register",
|
||||
request_body = MentorUserRegisterRequestDto,
|
||||
responses(
|
||||
(status = 200, description = "Mentor registered successfully", body = MentorRegisterResponseDto),
|
||||
(status = 400, description = "Bad request - validation error"),
|
||||
(status = 409, description = "Conflict - user already has mentor profile"),
|
||||
(status = 500, description = "Internal server error")
|
||||
),
|
||||
tag = "Mentors"
|
||||
)]
|
||||
pub async fn post_register_mentor(
|
||||
Extension(app_state): Extension<AppState>,
|
||||
Json(dto): Json<MentorUserRegisterRequestDto>,
|
||||
) -> Response {
|
||||
MentorsService::register_mentor(&app_state, dto).await
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/v1/mentors",
|
||||
params(
|
||||
("page" = Option<u64>, Query, description = "Page number"),
|
||||
("per_page" = Option<u64>, Query, description = "Items per page"),
|
||||
("search" = Option<String>, Query, description = "Search query"),
|
||||
("sort_by" = Option<String>, Query, description = "Sort by field"),
|
||||
("order" = Option<String>, Query, description = "Sort order (ASC/DESC)"),
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Get list of mentors", body = Vec<MentorListResponseDto>),
|
||||
(status = 500, description = "Internal server error")
|
||||
),
|
||||
tag = "Mentors",
|
||||
security(
|
||||
("Bearer" = [])
|
||||
)
|
||||
)]
|
||||
pub async fn get_mentor_list(
|
||||
headers: HeaderMap,
|
||||
Extension(app_state): Extension<AppState>,
|
||||
Query(meta): Query<MetaRequestDto>,
|
||||
) -> Response {
|
||||
match permissions_guard(
|
||||
&headers,
|
||||
app_state.clone(),
|
||||
vec![PermissionsEnum::ReadListMentors],
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(_) => MentorsService::get_mentor_list(&app_state, meta).await,
|
||||
Err(response) => response,
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/v1/mentors/detail/{id}",
|
||||
params(
|
||||
("id" = String, Path, description = "Mentor ID")
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Get mentor by ID", body = MentorDetailResponseDto),
|
||||
(status = 404, description = "Mentor not found"),
|
||||
(status = 500, description = "Internal server error")
|
||||
),
|
||||
tag = "Mentors",
|
||||
security(
|
||||
("Bearer" = [])
|
||||
)
|
||||
)]
|
||||
pub async fn get_mentor_by_id(
|
||||
headers: HeaderMap,
|
||||
Extension(app_state): Extension<AppState>,
|
||||
Path(id): Path<String>,
|
||||
) -> Response {
|
||||
match permissions_guard(
|
||||
&headers,
|
||||
app_state.clone(),
|
||||
vec![PermissionsEnum::ReadDetailMentors],
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(_) => MentorsService::get_mentor_by_id(&app_state, &id).await,
|
||||
Err(response) => response,
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
put,
|
||||
path = "/v1/mentors/update/{id}",
|
||||
params(
|
||||
("id" = String, Path, description = "Mentor ID")
|
||||
),
|
||||
request_body = MentorUpdateRequestDto,
|
||||
responses(
|
||||
(status = 200, description = "Mentor updated successfully", body = MentorDetailResponseDto),
|
||||
(status = 400, description = "Bad request - validation error"),
|
||||
(status = 404, description = "Mentor not found"),
|
||||
(status = 500, description = "Internal server error")
|
||||
),
|
||||
tag = "Mentors - Admin",
|
||||
security(
|
||||
("Bearer" = [])
|
||||
)
|
||||
)]
|
||||
pub async fn put_update_mentor(
|
||||
headers: HeaderMap,
|
||||
Extension(app_state): Extension<AppState>,
|
||||
Path(id): Path<String>,
|
||||
Json(dto): Json<MentorUpdateRequestDto>,
|
||||
) -> Response {
|
||||
match permissions_guard(
|
||||
&headers,
|
||||
app_state.clone(),
|
||||
vec![PermissionsEnum::UpdateMentors],
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(_) => MentorsService::update_mentor(&app_state, &id, dto).await,
|
||||
Err(response) => response,
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
delete,
|
||||
path = "/v1/mentors/delete/{id}",
|
||||
params(
|
||||
("id" = String, Path, description = "Mentor ID")
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Mentor deleted successfully"),
|
||||
(status = 404, description = "Mentor not found"),
|
||||
(status = 500, description = "Internal server error")
|
||||
),
|
||||
tag = "Mentors - Admin",
|
||||
security(
|
||||
("Bearer" = [])
|
||||
)
|
||||
)]
|
||||
pub async fn delete_mentor(
|
||||
headers: HeaderMap,
|
||||
Extension(app_state): Extension<AppState>,
|
||||
Path(id): Path<String>,
|
||||
) -> Response {
|
||||
match permissions_guard(
|
||||
&headers,
|
||||
app_state.clone(),
|
||||
vec![PermissionsEnum::DeleteMentors],
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(_) => MentorsService::delete_mentor(&app_state, &id).await,
|
||||
Err(response) => response,
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
put,
|
||||
path = "/v1/mentors/verify/{id}",
|
||||
params(
|
||||
("id" = String, Path, description = "Mentor ID")
|
||||
),
|
||||
request_body = MentorVerifyRequestDto,
|
||||
responses(
|
||||
(status = 200, description = "Mentor verified successfully", body = MentorDetailResponseDto),
|
||||
(status = 400, description = "Bad request - validation error"),
|
||||
(status = 404, description = "Mentor not found"),
|
||||
(status = 500, description = "Internal server error")
|
||||
),
|
||||
tag = "Mentors - Admin",
|
||||
security(
|
||||
("Bearer" = [])
|
||||
)
|
||||
)]
|
||||
pub async fn put_verify_mentor(
|
||||
headers: HeaderMap,
|
||||
Extension(app_state): Extension<AppState>,
|
||||
Path(id): Path<String>,
|
||||
Json(dto): Json<MentorVerifyRequestDto>,
|
||||
) -> Response {
|
||||
match permissions_guard(
|
||||
&headers,
|
||||
app_state.clone(),
|
||||
vec![PermissionsEnum::VerifyMentors],
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(_) => MentorsService::verify_mentor(&app_state, &id, dto).await,
|
||||
Err(response) => response,
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/v1/mentors/me",
|
||||
responses(
|
||||
(status = 200, description = "Current user's mentor profile", body = MentorDetailResponseDto),
|
||||
(status = 401, description = "Unauthorized - invalid token"),
|
||||
(status = 403, description = "Mentor profile not found for current user"),
|
||||
(status = 500, description = "Internal server error")
|
||||
),
|
||||
tag = "Mentors",
|
||||
security(
|
||||
("Bearer" = [])
|
||||
)
|
||||
)]
|
||||
pub async fn get_mentor_me(
|
||||
Extension(app_state): Extension<AppState>,
|
||||
headers: HeaderMap,
|
||||
) -> Response {
|
||||
match permissions_guard(
|
||||
&headers,
|
||||
app_state.clone(),
|
||||
vec![PermissionsEnum::ReadOwnMentorProfile],
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
let email = match extract_email(&headers) {
|
||||
Some(email) => email,
|
||||
None => {
|
||||
return (
|
||||
axum::http::StatusCode::UNAUTHORIZED,
|
||||
Json(json!({
|
||||
"error": "Unauthorized",
|
||||
"message": "Token tidak valid"
|
||||
})),
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
};
|
||||
MentorsService::get_mentor_me(&app_state, &email).await
|
||||
}
|
||||
Err(response) => response,
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
put,
|
||||
path = "/v1/mentors/update/me",
|
||||
request_body = MentorUpdateRequestDto,
|
||||
responses(
|
||||
(status = 200, description = "Mentor profile updated successfully", body = MentorDetailResponseDto),
|
||||
(status = 400, description = "Bad request - validation error"),
|
||||
(status = 401, description = "Unauthorized - invalid token"),
|
||||
(status = 404, description = "Mentor profile not found"),
|
||||
(status = 500, description = "Internal server error")
|
||||
),
|
||||
tag = "Mentors",
|
||||
security(
|
||||
("Bearer" = [])
|
||||
)
|
||||
)]
|
||||
pub async fn put_update_mentor_me(
|
||||
Extension(app_state): Extension<AppState>,
|
||||
headers: HeaderMap,
|
||||
Json(dto): Json<MentorUpdateRequestDto>,
|
||||
) -> Response {
|
||||
match permissions_guard(
|
||||
&headers,
|
||||
app_state.clone(),
|
||||
vec![PermissionsEnum::UpdateOwnMentorProfile],
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
let email = match extract_email(&headers) {
|
||||
Some(email) => email,
|
||||
None => {
|
||||
return imphnen_utils::common_response(
|
||||
axum::http::StatusCode::UNAUTHORIZED,
|
||||
"Token tidak valid",
|
||||
);
|
||||
}
|
||||
};
|
||||
MentorsService::update_mentor_me(&app_state, &email, dto).await
|
||||
}
|
||||
Err(response) => response,
|
||||
}
|
||||
}
|
||||
#[utoipa::path(
|
||||
put,
|
||||
path = "/v1/mentors/update",
|
||||
request_body = MentorUpdateRequestDto,
|
||||
responses(
|
||||
(status = 400, description = "Bad request - Mentor ID is required for update"),
|
||||
),
|
||||
tag = "Mentors - Admin"
|
||||
)]
|
||||
pub async fn put_update_mentor_no_id() -> Response {
|
||||
imphnen_utils::common_response(
|
||||
axum::http::StatusCode::BAD_REQUEST,
|
||||
"Mentor ID is required for update",
|
||||
)
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/v1/mentors/status",
|
||||
responses(
|
||||
(status = 200, description = "Mentor application status", body = String),
|
||||
(status = 401, description = "Unauthorized - invalid token"),
|
||||
(status = 403, description = "No mentor application found for current user"),
|
||||
(status = 500, description = "Internal server error")
|
||||
),
|
||||
tag = "Mentors",
|
||||
security(
|
||||
("Bearer" = [])
|
||||
)
|
||||
)]
|
||||
pub async fn get_mentor_status(
|
||||
Extension(app_state): Extension<AppState>,
|
||||
headers: HeaderMap,
|
||||
) -> Response {
|
||||
match permissions_guard(
|
||||
&headers,
|
||||
app_state.clone(),
|
||||
vec![PermissionsEnum::ReadOwnMentorStatus],
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
let email = match extract_email(&headers) {
|
||||
Some(email) => email,
|
||||
None => {
|
||||
return (
|
||||
axum::http::StatusCode::UNAUTHORIZED,
|
||||
Json(json!({
|
||||
"error": "Unauthorized",
|
||||
"message": "Token tidak valid"
|
||||
})),
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
};
|
||||
MentorsService::get_mentor_status(&app_state, &email).await
|
||||
}
|
||||
Err(response) => response,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,479 @@
|
||||
use crate::v1::mentors::MentorSchema;
|
||||
use imphnen_utils::extract_id;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use surrealdb::sql::Thing;
|
||||
use utoipa::ToSchema;
|
||||
use validator::Validate;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||
pub struct MentorListResponseDto {
|
||||
pub id: String,
|
||||
pub fullname: Option<String>,
|
||||
pub email: Option<String>,
|
||||
pub status: String,
|
||||
pub created_at: String,
|
||||
pub updated_at: String,
|
||||
}
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct MentorDetailWithUserDto {
|
||||
pub id: Thing,
|
||||
pub user_id: Thing,
|
||||
pub fullname: Option<String>,
|
||||
pub email: Option<String>,
|
||||
pub legal_name: String,
|
||||
pub identity_document_url: String,
|
||||
pub phone_for_verification: String,
|
||||
pub bio: String,
|
||||
pub linkedin_url: Option<String>,
|
||||
pub github_url: Option<String>,
|
||||
pub cv_url: Option<String>,
|
||||
pub industries: Vec<String>,
|
||||
pub expertise: Vec<String>,
|
||||
pub languages: Vec<String>,
|
||||
pub current_company: String,
|
||||
pub current_role: String,
|
||||
pub years_of_experience: i32,
|
||||
pub topics_of_interest: Vec<String>,
|
||||
pub preferred_mentee_level: Vec<String>,
|
||||
pub preferred_mentoring_formats: Vec<String>,
|
||||
pub availability_commitment: String,
|
||||
pub mentoring_rate: MentoringRate,
|
||||
pub status: String,
|
||||
pub is_deleted: bool,
|
||||
pub created_at: String,
|
||||
pub updated_at: String,
|
||||
}
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||
pub struct MentorDetailResponseDto {
|
||||
pub id: String,
|
||||
pub user_id: String,
|
||||
pub fullname: Option<String>,
|
||||
pub email: Option<String>,
|
||||
pub legal_name: String,
|
||||
pub identity_document_url: String,
|
||||
pub phone_for_verification: String,
|
||||
pub bio: String,
|
||||
pub linkedin_url: Option<String>,
|
||||
pub github_url: Option<String>,
|
||||
pub cv_url: Option<String>,
|
||||
pub industries: Vec<String>,
|
||||
pub expertise: Vec<String>,
|
||||
pub languages: Vec<String>,
|
||||
pub current_company: String,
|
||||
pub current_role: String,
|
||||
pub years_of_experience: i32,
|
||||
pub topics_of_interest: Vec<String>,
|
||||
pub preferred_mentee_level: Vec<String>,
|
||||
pub preferred_mentoring_formats: Vec<String>,
|
||||
pub availability_commitment: String,
|
||||
pub mentoring_rate: MentoringRate,
|
||||
pub status: String,
|
||||
pub created_at: String,
|
||||
pub updated_at: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema, Validate)]
|
||||
pub struct MentorRegisterResponseDto {
|
||||
pub id: String,
|
||||
pub user_id: String,
|
||||
pub email: Option<String>,
|
||||
pub status: String,
|
||||
pub created_at: String,
|
||||
pub updated_at: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema, Validate)]
|
||||
pub struct MentorUpdateRequestDto {
|
||||
#[validate(length(
|
||||
min = 3,
|
||||
message = "Legal name must be at least 3 characters"
|
||||
))]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub legal_name: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub gender: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub domicile: Option<String>,
|
||||
#[validate(url(message = "Invalid identity document URL"))]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub identity_document_url: Option<String>,
|
||||
#[validate(length(
|
||||
min = 10,
|
||||
max = 15,
|
||||
message = "Phone must be 10-15 characters"
|
||||
))]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub phone_for_verification: Option<String>,
|
||||
#[validate(length(min = 50, message = "Bio must be at least 50 characters"))]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub bio: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub last_education: Option<String>,
|
||||
#[validate(url(message = "Invalid LinkedIn URL"))]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub linkedin_url: Option<String>,
|
||||
#[validate(url(message = "Invalid GitHub URL"))]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub github_url: Option<String>,
|
||||
#[validate(url(message = "Invalid CV URL"))]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub cv_url: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub portfolio_url: Option<String>,
|
||||
#[validate(length(min = 1, message = "At least 1 industry required"))]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub industries: Option<Vec<String>>,
|
||||
#[validate(length(min = 1, message = "At least 1 expertise required"))]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub expertise: Option<Vec<String>>,
|
||||
#[validate(length(min = 1, message = "At least 1 language required"))]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub languages: Option<Vec<String>>,
|
||||
#[validate(length(min = 1, message = "Current company required"))]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub current_company: Option<String>,
|
||||
#[validate(length(min = 1, message = "Current role required"))]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub current_role: Option<String>,
|
||||
#[validate(range(min = 2, message = "At least 2 years of experience required"))]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub years_of_experience: Option<i32>,
|
||||
#[validate(length(min = 1, message = "At least 1 topic required"))]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub topics_of_interest: Option<Vec<String>>,
|
||||
#[validate(length(min = 1, message = "At least 1 mentee level required"))]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub preferred_mentee_level: Option<Vec<String>>,
|
||||
#[validate(length(min = 1, message = "At least 1 mentoring format required"))]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub preferred_mentoring_formats: Option<Vec<String>>,
|
||||
#[validate(length(
|
||||
min = 5,
|
||||
message = "Availability commitment must be at least 5 characters"
|
||||
))]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub availability_commitment: Option<String>,
|
||||
#[validate(range(min = 1, message = "Amount must be at least 1"))]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub mentoring_rate_amount: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema, Validate)]
|
||||
pub struct MentorUserRegisterRequestDto {
|
||||
#[validate(
|
||||
length(min = 1, message = "Email cannot be empty"),
|
||||
email(message = "Email not valid")
|
||||
)]
|
||||
pub email: String,
|
||||
#[validate(length(
|
||||
min = 8,
|
||||
message = "Password must have at least 8 characters"
|
||||
))]
|
||||
#[validate(custom(
|
||||
function = "imphnen_iam::auth_dto::validate_password_complexity",
|
||||
message = "Password must include uppercase, lowercase, number, and special character"
|
||||
))]
|
||||
pub password: String,
|
||||
#[validate(length(min = 2, message = "Fullname at least have 2 character"))]
|
||||
pub fullname: String,
|
||||
#[validate(length(min = 1, message = "Phone number is required"))]
|
||||
pub phone_number: String,
|
||||
#[validate(nested)]
|
||||
pub identity_and_verification: IdentityAndVerification,
|
||||
#[validate(nested)]
|
||||
pub professional_profile: ProfessionalProfile,
|
||||
#[validate(nested)]
|
||||
pub mentoring_logistics: MentoringLogistics,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema, Validate)]
|
||||
pub struct MentorRegisterFromTokenRequestDto {
|
||||
#[validate(nested)]
|
||||
pub identity_and_verification: IdentityAndVerification,
|
||||
#[validate(nested)]
|
||||
pub professional_profile: ProfessionalProfile,
|
||||
#[validate(nested)]
|
||||
pub mentoring_logistics: MentoringLogistics,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema, Validate)]
|
||||
pub struct IdentityAndVerification {
|
||||
#[validate(length(
|
||||
min = 3,
|
||||
message = "Legal name must be at least 3 characters"
|
||||
))]
|
||||
pub legal_name: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub gender: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub domicile: Option<String>,
|
||||
#[validate(url(message = "Invalid identity document URL"))]
|
||||
pub identity_document_url: String,
|
||||
#[validate(length(
|
||||
min = 10,
|
||||
max = 15,
|
||||
message = "Phone must be 10-15 characters"
|
||||
))]
|
||||
pub phone_for_verification: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema, Validate)]
|
||||
pub struct ProfessionalProfile {
|
||||
#[validate(length(min = 50, message = "Bio must be at least 50 characters"))]
|
||||
pub bio: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub last_education: Option<String>,
|
||||
#[validate(url(message = "Invalid LinkedIn URL"))]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub linkedin_url: Option<String>,
|
||||
#[validate(url(message = "Invalid GitHub URL"))]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub github_url: Option<String>,
|
||||
#[validate(url(message = "Invalid CV URL"))]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub cv_url: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub portfolio_url: Option<String>,
|
||||
#[validate(length(min = 1, message = "At least 1 industry required"))]
|
||||
pub industries: Vec<String>,
|
||||
#[validate(length(min = 1, message = "At least 1 expertise required"))]
|
||||
pub expertise: Vec<String>,
|
||||
#[validate(length(min = 1, message = "At least 1 language required"))]
|
||||
pub languages: Vec<String>,
|
||||
#[validate(length(min = 1, message = "Current company required"))]
|
||||
pub current_company: String,
|
||||
#[validate(length(min = 1, message = "Current role required"))]
|
||||
pub current_role: String,
|
||||
#[validate(range(min = 2, message = "At least 2 years of experience required"))]
|
||||
pub years_of_experience: i32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema, Validate)]
|
||||
pub struct MentoringLogistics {
|
||||
#[validate(length(min = 1, message = "At least 1 topic required"))]
|
||||
pub topics_of_interest: Vec<String>,
|
||||
#[validate(length(min = 1, message = "At least 1 mentee level required"))]
|
||||
pub preferred_mentee_level: Vec<String>,
|
||||
#[validate(length(min = 1, message = "At least 1 mentoring format required"))]
|
||||
pub preferred_mentoring_formats: Vec<String>,
|
||||
#[validate(length(
|
||||
min = 5,
|
||||
message = "Availability commitment must be at least 5 characters"
|
||||
))]
|
||||
pub availability_commitment: String,
|
||||
#[validate(range(min = 1, message = "Amount must be at least 1"))]
|
||||
pub mentoring_rate_amount: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema, Validate, Default)]
|
||||
pub struct MentoringRate {
|
||||
#[validate(range(min = 1, message = "Amount must be at least 1"))]
|
||||
pub amount: u64,
|
||||
#[validate(length(min = 1, message = "Currency is required"))]
|
||||
pub currency: String,
|
||||
#[validate(length(min = 1, message = "Per duration is required"))]
|
||||
pub per_duration: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct MentorInsertDto {
|
||||
pub id: Thing,
|
||||
pub user_id: Option<Thing>,
|
||||
pub email: Option<String>,
|
||||
pub legal_name: String,
|
||||
pub gender: Option<String>,
|
||||
pub domicile: Option<String>,
|
||||
pub identity_document_url: String,
|
||||
pub phone_for_verification: String,
|
||||
pub bio: String,
|
||||
pub last_education: Option<String>,
|
||||
pub linkedin_url: Option<String>,
|
||||
pub github_url: Option<String>,
|
||||
pub cv_url: Option<String>,
|
||||
pub portfolio_url: Option<String>,
|
||||
pub industries: Vec<String>,
|
||||
pub expertise: Vec<String>,
|
||||
pub languages: Vec<String>,
|
||||
pub current_company: String,
|
||||
pub current_role: String,
|
||||
pub years_of_experience: i32,
|
||||
pub topics_of_interest: Vec<String>,
|
||||
pub preferred_mentee_level: Vec<String>,
|
||||
pub preferred_mentoring_formats: Vec<String>,
|
||||
pub availability_commitment: String,
|
||||
pub mentoring_rate: MentoringRate,
|
||||
pub status: String,
|
||||
pub is_deleted: bool,
|
||||
pub created_at: String,
|
||||
pub updated_at: String,
|
||||
}
|
||||
|
||||
impl From<MentorSchema> for MentorInsertDto {
|
||||
fn from(schema: MentorSchema) -> Self {
|
||||
MentorInsertDto {
|
||||
id: schema.id,
|
||||
user_id: schema.user_id,
|
||||
email: schema.email,
|
||||
legal_name: schema.legal_name,
|
||||
gender: schema.gender,
|
||||
domicile: schema.domicile,
|
||||
identity_document_url: schema.identity_document_url,
|
||||
phone_for_verification: schema.phone_for_verification,
|
||||
bio: schema.bio,
|
||||
last_education: schema.last_education,
|
||||
linkedin_url: schema.linkedin_url,
|
||||
github_url: schema.github_url,
|
||||
cv_url: schema.cv_url,
|
||||
portfolio_url: schema.portfolio_url,
|
||||
industries: schema.industries,
|
||||
expertise: schema.expertise,
|
||||
languages: schema.languages,
|
||||
current_company: schema.current_company,
|
||||
current_role: schema.current_role,
|
||||
years_of_experience: schema.years_of_experience,
|
||||
topics_of_interest: schema.topics_of_interest,
|
||||
preferred_mentee_level: schema.preferred_mentee_level,
|
||||
preferred_mentoring_formats: schema.preferred_mentoring_formats,
|
||||
availability_commitment: schema.availability_commitment,
|
||||
mentoring_rate: schema.mentoring_rate,
|
||||
status: schema.status,
|
||||
is_deleted: schema.is_deleted,
|
||||
created_at: schema.created_at,
|
||||
updated_at: schema.updated_at,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema, Validate)]
|
||||
pub struct MentorVerifyRequestDto {
|
||||
#[validate(length(min = 1, message = "Status is required"))]
|
||||
pub status: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct MentorDetailQueryDto {
|
||||
pub id: Thing,
|
||||
pub user_id: Thing,
|
||||
pub fullname: Option<String>,
|
||||
pub email: Option<String>,
|
||||
pub legal_name: String,
|
||||
pub gender: Option<String>,
|
||||
pub domicile: Option<String>,
|
||||
pub identity_document_url: String,
|
||||
pub phone_for_verification: String,
|
||||
pub bio: String,
|
||||
pub last_education: Option<String>,
|
||||
pub linkedin_url: Option<String>,
|
||||
pub github_url: Option<String>,
|
||||
pub cv_url: Option<String>,
|
||||
pub portfolio_url: Option<String>,
|
||||
pub industries: Vec<String>,
|
||||
pub expertise: Vec<String>,
|
||||
pub languages: Vec<String>,
|
||||
pub current_company: String,
|
||||
pub current_role: String,
|
||||
pub years_of_experience: i32,
|
||||
pub topics_of_interest: Vec<String>,
|
||||
pub preferred_mentee_level: Vec<String>,
|
||||
pub preferred_mentoring_formats: Vec<String>,
|
||||
pub availability_commitment: String,
|
||||
pub mentoring_rate: MentoringRate,
|
||||
pub status: String,
|
||||
pub is_deleted: bool,
|
||||
pub created_at: String,
|
||||
pub updated_at: String,
|
||||
}
|
||||
|
||||
impl From<MentorDetailQueryDto> for MentorListResponseDto {
|
||||
fn from(dto: MentorDetailQueryDto) -> Self {
|
||||
Self {
|
||||
id: extract_id(&dto.id),
|
||||
fullname: dto.fullname,
|
||||
email: dto.email,
|
||||
status: dto.status,
|
||||
created_at: dto.created_at,
|
||||
updated_at: dto.updated_at,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MentorDetailQueryDto> for MentorDetailResponseDto {
|
||||
fn from(dto: MentorDetailQueryDto) -> Self {
|
||||
Self {
|
||||
id: extract_id(&dto.id),
|
||||
user_id: extract_id(&dto.user_id),
|
||||
fullname: dto.fullname,
|
||||
email: dto.email,
|
||||
legal_name: dto.legal_name,
|
||||
identity_document_url: dto.identity_document_url,
|
||||
phone_for_verification: dto.phone_for_verification,
|
||||
bio: dto.bio,
|
||||
linkedin_url: dto.linkedin_url,
|
||||
github_url: dto.github_url,
|
||||
cv_url: dto.cv_url,
|
||||
industries: dto.industries,
|
||||
expertise: dto.expertise,
|
||||
languages: dto.languages,
|
||||
current_company: dto.current_company,
|
||||
current_role: dto.current_role,
|
||||
years_of_experience: dto.years_of_experience,
|
||||
topics_of_interest: dto.topics_of_interest,
|
||||
preferred_mentee_level: dto.preferred_mentee_level,
|
||||
preferred_mentoring_formats: dto.preferred_mentoring_formats,
|
||||
availability_commitment: dto.availability_commitment,
|
||||
mentoring_rate: dto.mentoring_rate,
|
||||
status: dto.status,
|
||||
created_at: dto.created_at,
|
||||
updated_at: dto.updated_at,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<MentorSchema> for MentorRegisterResponseDto {
|
||||
fn from(schema: MentorSchema) -> Self {
|
||||
Self {
|
||||
id: schema.id.to_string(),
|
||||
user_id: schema.user_id.map(|id| extract_id(&id)).unwrap_or_default(),
|
||||
email: schema.email,
|
||||
status: schema.status,
|
||||
created_at: schema.created_at,
|
||||
updated_at: schema.updated_at,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MentorDetailWithUserDto> for MentorDetailQueryDto {
|
||||
fn from(dto: MentorDetailWithUserDto) -> Self {
|
||||
MentorDetailQueryDto {
|
||||
id: dto.id,
|
||||
user_id: dto.user_id,
|
||||
fullname: dto.fullname,
|
||||
email: dto.email,
|
||||
legal_name: dto.legal_name,
|
||||
gender: None, // Frontend form implies these are optional, not present in original MentorDetailWithUserDto
|
||||
domicile: None, // Frontend form implies these are optional, not present in original MentorDetailWithUserDto
|
||||
identity_document_url: dto.identity_document_url,
|
||||
phone_for_verification: dto.phone_for_verification,
|
||||
bio: dto.bio,
|
||||
last_education: None, // Frontend form implies these are optional, not present in original MentorDetailWithUserDto
|
||||
linkedin_url: dto.linkedin_url,
|
||||
github_url: dto.github_url,
|
||||
cv_url: dto.cv_url,
|
||||
portfolio_url: None, // Frontend form implies these are optional, not present in original MentorDetailWithUserDto
|
||||
industries: dto.industries,
|
||||
expertise: dto.expertise,
|
||||
languages: dto.languages,
|
||||
current_company: dto.current_company,
|
||||
current_role: dto.current_role,
|
||||
years_of_experience: dto.years_of_experience,
|
||||
topics_of_interest: dto.topics_of_interest,
|
||||
preferred_mentee_level: dto.preferred_mentee_level,
|
||||
preferred_mentoring_formats: dto.preferred_mentoring_formats,
|
||||
availability_commitment: dto.availability_commitment,
|
||||
mentoring_rate: dto.mentoring_rate,
|
||||
status: dto.status,
|
||||
is_deleted: dto.is_deleted,
|
||||
created_at: dto.created_at,
|
||||
updated_at: dto.updated_at,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,299 @@
|
||||
use anyhow::{Result, bail};
|
||||
use imphnen_iam::{get_id, make_thing};
|
||||
use surrealdb::sql::Thing;
|
||||
|
||||
use crate::v1::mentors::{MentorDetailWithUserDto, MentorInsertDto, MentorSchema};
|
||||
use imphnen_libs::{AppState, MetaRequestDto, ResourceEnum, ResponseListSuccessDto};
|
||||
use imphnen_utils::{DetailQueryBuilder, QueryListBuilder, get_iso_date};
|
||||
use serde_json::{Map, Value};
|
||||
use std::time::Instant;
|
||||
use tracing::instrument;
|
||||
|
||||
pub struct MentorsRepository<'a> {
|
||||
pub state: &'a AppState,
|
||||
}
|
||||
|
||||
impl<'a> MentorsRepository<'a> {
|
||||
pub fn new(state: &'a AppState) -> Self {
|
||||
Self { state }
|
||||
}
|
||||
|
||||
#[instrument(skip(self, meta), err)]
|
||||
pub async fn query_mentor_list(
|
||||
&self,
|
||||
meta: MetaRequestDto,
|
||||
) -> Result<ResponseListSuccessDto<Vec<MentorDetailWithUserDto>>> {
|
||||
let now = Instant::now();
|
||||
let db = &self.state.surrealdb_ws;
|
||||
let mentors_table = ResourceEnum::Mentors.to_string();
|
||||
let builder = QueryListBuilder::new(db, &mentors_table, &meta)
|
||||
.search_field("legal_name")
|
||||
.select_fields(vec![
|
||||
"id",
|
||||
"user_id",
|
||||
"user_id.fullname as fullname",
|
||||
"email",
|
||||
"legal_name",
|
||||
"identity_document_url",
|
||||
"phone_for_verification",
|
||||
"bio",
|
||||
"linkedin_url",
|
||||
"github_url",
|
||||
"cv_url",
|
||||
"industries",
|
||||
"expertise",
|
||||
"languages",
|
||||
"current_company",
|
||||
"current_role",
|
||||
"years_of_experience",
|
||||
"topics_of_interest",
|
||||
"preferred_mentee_level",
|
||||
"preferred_mentoring_formats",
|
||||
"availability_commitment",
|
||||
"mentoring_rate",
|
||||
"status",
|
||||
"is_deleted",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
]);
|
||||
let result = builder.build().await?;
|
||||
let elapsed = now.elapsed();
|
||||
if std::env::var("RUST_ENV").unwrap_or_else(|_| "development".to_string())
|
||||
== "development"
|
||||
{
|
||||
println!("Query 'query_mentor_list' took: {elapsed:.2?}");
|
||||
}
|
||||
let data = result.data.into_iter().collect();
|
||||
Ok(ResponseListSuccessDto {
|
||||
data,
|
||||
meta: result.meta,
|
||||
})
|
||||
}
|
||||
|
||||
#[instrument(skip(self, email, include_deleted), err)]
|
||||
pub async fn query_mentor_by_email(
|
||||
&self,
|
||||
email: String,
|
||||
include_deleted: bool,
|
||||
) -> Result<MentorDetailWithUserDto> {
|
||||
let now = Instant::now();
|
||||
let db = &self.state.surrealdb_ws;
|
||||
let mut builder = DetailQueryBuilder::new(ResourceEnum::Mentors.to_string())
|
||||
.with_where("email", Some(email.clone()))
|
||||
.with_select_fields(vec![
|
||||
"id",
|
||||
"user_id",
|
||||
"user_id.fullname as fullname",
|
||||
"email",
|
||||
"legal_name",
|
||||
"identity_document_url",
|
||||
"phone_for_verification",
|
||||
"bio",
|
||||
"linkedin_url",
|
||||
"github_url",
|
||||
"cv_url",
|
||||
"industries",
|
||||
"expertise",
|
||||
"languages",
|
||||
"current_company",
|
||||
"current_role",
|
||||
"years_of_experience",
|
||||
"topics_of_interest",
|
||||
"preferred_mentee_level",
|
||||
"preferred_mentoring_formats",
|
||||
"availability_commitment",
|
||||
"mentoring_rate",
|
||||
"status",
|
||||
"is_deleted",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
]);
|
||||
|
||||
if !include_deleted {
|
||||
builder = builder.with_condition("is_deleted = false");
|
||||
}
|
||||
|
||||
let sql = builder.build();
|
||||
let mentor_opt: Option<MentorDetailWithUserDto> =
|
||||
builder.apply_bindings(db.query(sql)).await?.take(0)?;
|
||||
let elapsed = now.elapsed();
|
||||
if std::env::var("RUST_ENV").unwrap_or_else(|_| "development".to_string())
|
||||
== "development"
|
||||
{
|
||||
println!("Query 'query_mentor_by_email' took: {elapsed:.2?}");
|
||||
}
|
||||
let Some(mentor) = mentor_opt else {
|
||||
bail!("Mentor not found");
|
||||
};
|
||||
Ok(mentor)
|
||||
}
|
||||
|
||||
#[instrument(skip(self, id, include_deleted), err)]
|
||||
pub async fn query_mentor_by_id(
|
||||
&self,
|
||||
id: &Thing,
|
||||
include_deleted: bool,
|
||||
) -> Result<MentorDetailWithUserDto> {
|
||||
let now = Instant::now();
|
||||
let db = &self.state.surrealdb_ws;
|
||||
let mut builder = DetailQueryBuilder::new(ResourceEnum::Mentors.to_string())
|
||||
.with_id(get_id(id)?.1)
|
||||
.with_select_fields(vec![
|
||||
"id",
|
||||
"user_id",
|
||||
"user_id.fullname as fullname",
|
||||
"email",
|
||||
"legal_name",
|
||||
"identity_document_url",
|
||||
"phone_for_verification",
|
||||
"bio",
|
||||
"linkedin_url",
|
||||
"github_url",
|
||||
"cv_url",
|
||||
"industries",
|
||||
"expertise",
|
||||
"languages",
|
||||
"current_company",
|
||||
"current_role",
|
||||
"years_of_experience",
|
||||
"topics_of_interest",
|
||||
"preferred_mentee_level",
|
||||
"preferred_mentoring_formats",
|
||||
"availability_commitment",
|
||||
"mentoring_rate",
|
||||
"status",
|
||||
"is_deleted",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
]);
|
||||
|
||||
if !include_deleted {
|
||||
builder = builder.with_condition("is_deleted = false");
|
||||
}
|
||||
|
||||
let sql = builder.build();
|
||||
let mentor_opt: Option<MentorDetailWithUserDto> =
|
||||
builder.apply_bindings(db.query(sql)).await?.take(0)?;
|
||||
let elapsed = now.elapsed();
|
||||
if std::env::var("RUST_ENV").unwrap_or_else(|_| "development".to_string())
|
||||
== "development"
|
||||
{
|
||||
println!("Query 'query_mentor_by_id' took: {elapsed:.2?}");
|
||||
}
|
||||
let Some(mentor) = mentor_opt else {
|
||||
bail!("Mentor not found in database");
|
||||
};
|
||||
if mentor.is_deleted && !include_deleted {
|
||||
bail!("Mentor has been deleted");
|
||||
}
|
||||
Ok(mentor)
|
||||
}
|
||||
|
||||
#[instrument(skip(self, data), err)]
|
||||
pub async fn query_create_mentor(&self, data: MentorSchema) -> Result<String> {
|
||||
let now = Instant::now();
|
||||
let db = &self.state.surrealdb_ws;
|
||||
let dto: MentorInsertDto = data.into();
|
||||
let record: Option<MentorSchema> = db
|
||||
.create(ResourceEnum::Mentors.to_string())
|
||||
.content(dto.clone())
|
||||
.await?;
|
||||
let elapsed = now.elapsed();
|
||||
if std::env::var("RUST_ENV").unwrap_or_else(|_| "development".to_string())
|
||||
== "development"
|
||||
{
|
||||
println!("Query 'query_create_mentor' took: {elapsed:.2?}");
|
||||
}
|
||||
match record {
|
||||
Some(mentor) => {
|
||||
let id_str = mentor.id.id.to_raw();
|
||||
let _user = format!("{:?}", mentor.user_id);
|
||||
Ok(id_str)
|
||||
}
|
||||
None => {
|
||||
bail!("Failed to create mentor")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(skip(self, data), err)]
|
||||
pub async fn query_update_mentor(&self, data: MentorSchema) -> Result<String> {
|
||||
let now = Instant::now();
|
||||
let db = &self.state.surrealdb_ws;
|
||||
let id_ref = &data.id;
|
||||
let record_key = get_id(id_ref)?;
|
||||
let _existing = self.query_mentor_by_id(id_ref, false).await?;
|
||||
|
||||
let mut merged_data_json: Map<String, Value> =
|
||||
serde_json::to_value(data.clone())
|
||||
.map_err(|e| anyhow::anyhow!("Failed to serialize MentorSchema: {}", e))?
|
||||
.as_object()
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
|
||||
merged_data_json.remove("id");
|
||||
merged_data_json.remove("user_id");
|
||||
merged_data_json.remove("created_at");
|
||||
|
||||
merged_data_json.insert("updated_at".to_string(), Value::String(get_iso_date()));
|
||||
|
||||
let record: Option<MentorSchema> =
|
||||
db.update(record_key).merge(merged_data_json).await?;
|
||||
let elapsed = now.elapsed();
|
||||
if std::env::var("RUST_ENV").unwrap_or_else(|_| "development".to_string())
|
||||
== "development"
|
||||
{
|
||||
println!("Query 'query_update_mentor' took: {elapsed:.2?}");
|
||||
}
|
||||
match record {
|
||||
Some(_) => Ok("Success update mentor".into()),
|
||||
None => {
|
||||
bail!("Failed to update mentor")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(skip(self, id), err)]
|
||||
pub async fn query_delete_mentor(&self, id: String) -> Result<String> {
|
||||
let now = Instant::now();
|
||||
let db = &self.state.surrealdb_ws;
|
||||
let thing = make_thing(ResourceEnum::Mentors.to_string().as_str(), &id);
|
||||
let record_key = get_id(&thing)?;
|
||||
|
||||
let mentor_to_delete_res = self.query_mentor_by_id(&thing, true).await;
|
||||
|
||||
let _mentor_to_delete = match mentor_to_delete_res {
|
||||
Ok(mentor) => {
|
||||
if mentor.is_deleted {
|
||||
bail!("Mentor is already soft deleted");
|
||||
}
|
||||
mentor
|
||||
}
|
||||
Err(e) => {
|
||||
if e.to_string().contains("Mentor has been deleted") {
|
||||
bail!("Mentor is already soft deleted");
|
||||
} else {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let mut patch = Map::new();
|
||||
patch.insert("is_deleted".to_string(), Value::Bool(true));
|
||||
patch.insert("updated_at".to_string(), Value::String(get_iso_date()));
|
||||
|
||||
let record: Option<MentorSchema> = db.update(record_key).merge(patch).await?;
|
||||
let elapsed = now.elapsed();
|
||||
if std::env::var("RUST_ENV").unwrap_or_else(|_| "development".to_string())
|
||||
== "development"
|
||||
{
|
||||
println!("Query 'query_delete_mentor' took: {elapsed:.2?}");
|
||||
}
|
||||
match record {
|
||||
Some(_) => Ok("Success soft delete mentor".into()),
|
||||
None => {
|
||||
bail!("Failed to soft delete mentor")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
use super::{
|
||||
IdentityAndVerification, MentorDetailQueryDto, MentorUpdateRequestDto,
|
||||
MentoringLogistics, MentoringRate, ProfessionalProfile,
|
||||
};
|
||||
use imphnen_libs::ResourceEnum;
|
||||
use imphnen_utils::{get_iso_date, make_thing};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use surrealdb::{Uuid, sql::Thing};
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct MentorSchema {
|
||||
pub id: Thing,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub user_id: Option<Thing>,
|
||||
pub email: Option<String>,
|
||||
pub legal_name: String,
|
||||
pub gender: Option<String>,
|
||||
pub domicile: Option<String>,
|
||||
pub identity_document_url: String,
|
||||
pub phone_for_verification: String,
|
||||
pub bio: String,
|
||||
pub last_education: Option<String>,
|
||||
pub linkedin_url: Option<String>,
|
||||
pub github_url: Option<String>,
|
||||
pub cv_url: Option<String>,
|
||||
pub portfolio_url: Option<String>,
|
||||
pub industries: Vec<String>,
|
||||
pub expertise: Vec<String>,
|
||||
pub languages: Vec<String>,
|
||||
pub current_company: String,
|
||||
pub current_role: String,
|
||||
pub years_of_experience: i32,
|
||||
pub topics_of_interest: Vec<String>,
|
||||
pub preferred_mentee_level: Vec<String>,
|
||||
pub preferred_mentoring_formats: Vec<String>,
|
||||
pub availability_commitment: String,
|
||||
pub mentoring_rate: MentoringRate,
|
||||
pub status: String,
|
||||
pub is_deleted: bool,
|
||||
pub created_at: String,
|
||||
pub updated_at: String,
|
||||
}
|
||||
|
||||
impl Default for MentorSchema {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
id: make_thing(
|
||||
ResourceEnum::Mentors.to_string().as_str(),
|
||||
&Uuid::new_v4().to_string(),
|
||||
),
|
||||
user_id: Some(make_thing(
|
||||
ResourceEnum::Users.to_string().as_str(),
|
||||
&Uuid::new_v4().to_string(),
|
||||
)),
|
||||
email: None,
|
||||
legal_name: String::new(),
|
||||
gender: None,
|
||||
domicile: None,
|
||||
identity_document_url: String::new(),
|
||||
phone_for_verification: String::new(),
|
||||
bio: String::new(),
|
||||
last_education: None,
|
||||
linkedin_url: None,
|
||||
github_url: None,
|
||||
cv_url: None,
|
||||
portfolio_url: None,
|
||||
industries: Vec::new(),
|
||||
expertise: Vec::new(),
|
||||
languages: Vec::new(),
|
||||
current_company: String::new(),
|
||||
current_role: String::new(),
|
||||
years_of_experience: 0,
|
||||
topics_of_interest: Vec::new(),
|
||||
preferred_mentee_level: Vec::new(),
|
||||
preferred_mentoring_formats: Vec::new(),
|
||||
availability_commitment: String::new(),
|
||||
mentoring_rate: MentoringRate {
|
||||
amount: 0,
|
||||
currency: "IDR".to_string(),
|
||||
per_duration: "hour".to_string(),
|
||||
},
|
||||
status: "pending".to_string(),
|
||||
is_deleted: false,
|
||||
created_at: get_iso_date(),
|
||||
updated_at: get_iso_date(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MentorSchema {
|
||||
pub fn create(
|
||||
identity_and_verification: IdentityAndVerification,
|
||||
professional_profile: ProfessionalProfile,
|
||||
mentoring_logistics: MentoringLogistics,
|
||||
user_id_raw: String,
|
||||
email_str: String,
|
||||
) -> Self {
|
||||
Self {
|
||||
id: make_thing(
|
||||
&ResourceEnum::Mentors.to_string(),
|
||||
&Uuid::new_v4().to_string(),
|
||||
),
|
||||
user_id: Some(make_thing(&ResourceEnum::Users.to_string(), &user_id_raw)),
|
||||
email: Some(email_str),
|
||||
legal_name: identity_and_verification.legal_name,
|
||||
gender: identity_and_verification.gender,
|
||||
domicile: identity_and_verification.domicile,
|
||||
identity_document_url: identity_and_verification.identity_document_url,
|
||||
phone_for_verification: identity_and_verification.phone_for_verification,
|
||||
bio: professional_profile.bio,
|
||||
last_education: professional_profile.last_education,
|
||||
linkedin_url: professional_profile.linkedin_url,
|
||||
github_url: professional_profile.github_url,
|
||||
cv_url: professional_profile.cv_url,
|
||||
portfolio_url: professional_profile.portfolio_url,
|
||||
industries: professional_profile.industries,
|
||||
expertise: professional_profile.expertise,
|
||||
languages: professional_profile.languages,
|
||||
current_company: professional_profile.current_company,
|
||||
current_role: professional_profile.current_role,
|
||||
years_of_experience: professional_profile.years_of_experience,
|
||||
topics_of_interest: mentoring_logistics.topics_of_interest,
|
||||
preferred_mentee_level: mentoring_logistics.preferred_mentee_level,
|
||||
preferred_mentoring_formats: mentoring_logistics.preferred_mentoring_formats,
|
||||
availability_commitment: mentoring_logistics.availability_commitment,
|
||||
mentoring_rate: MentoringRate {
|
||||
amount: mentoring_logistics.mentoring_rate_amount,
|
||||
currency: "IDR".to_string(),
|
||||
per_duration: "hour".to_string(),
|
||||
},
|
||||
status: "pending".to_string(),
|
||||
is_deleted: false,
|
||||
created_at: get_iso_date(),
|
||||
updated_at: get_iso_date(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from(dto: MentorDetailQueryDto) -> Self {
|
||||
Self {
|
||||
id: dto.id,
|
||||
user_id: Some(dto.user_id),
|
||||
email: dto.email,
|
||||
legal_name: dto.legal_name,
|
||||
gender: dto.gender,
|
||||
domicile: dto.domicile,
|
||||
identity_document_url: dto.identity_document_url,
|
||||
phone_for_verification: dto.phone_for_verification,
|
||||
bio: dto.bio,
|
||||
last_education: dto.last_education,
|
||||
linkedin_url: dto.linkedin_url,
|
||||
github_url: dto.github_url,
|
||||
cv_url: dto.cv_url,
|
||||
portfolio_url: dto.portfolio_url,
|
||||
industries: dto.industries,
|
||||
expertise: dto.expertise,
|
||||
languages: dto.languages,
|
||||
current_company: dto.current_company,
|
||||
current_role: dto.current_role,
|
||||
years_of_experience: dto.years_of_experience,
|
||||
topics_of_interest: dto.topics_of_interest,
|
||||
preferred_mentee_level: dto.preferred_mentee_level,
|
||||
preferred_mentoring_formats: dto.preferred_mentoring_formats,
|
||||
availability_commitment: dto.availability_commitment,
|
||||
mentoring_rate: dto.mentoring_rate,
|
||||
status: dto.status,
|
||||
is_deleted: dto.is_deleted,
|
||||
created_at: dto.created_at,
|
||||
updated_at: dto.updated_at,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(mut self, dto: MentorUpdateRequestDto) -> Self {
|
||||
// Update fields only if they are Some(value), otherwise preserve current value
|
||||
if let Some(val) = dto.legal_name {
|
||||
self.legal_name = val;
|
||||
}
|
||||
if let Some(val) = dto.gender {
|
||||
self.gender = Some(val);
|
||||
}
|
||||
if let Some(val) = dto.domicile {
|
||||
self.domicile = Some(val);
|
||||
}
|
||||
if let Some(val) = dto.identity_document_url {
|
||||
self.identity_document_url = val;
|
||||
}
|
||||
if let Some(val) = dto.phone_for_verification {
|
||||
self.phone_for_verification = val;
|
||||
}
|
||||
if let Some(val) = dto.bio {
|
||||
self.bio = val;
|
||||
}
|
||||
if let Some(val) = dto.last_education {
|
||||
self.last_education = Some(val);
|
||||
}
|
||||
if let Some(val) = dto.linkedin_url {
|
||||
self.linkedin_url = Some(val);
|
||||
}
|
||||
if let Some(val) = dto.github_url {
|
||||
self.github_url = Some(val);
|
||||
}
|
||||
if let Some(val) = dto.cv_url {
|
||||
self.cv_url = Some(val);
|
||||
}
|
||||
if let Some(val) = dto.portfolio_url {
|
||||
self.portfolio_url = Some(val);
|
||||
}
|
||||
if let Some(val) = dto.industries {
|
||||
self.industries = val;
|
||||
}
|
||||
if let Some(val) = dto.expertise {
|
||||
self.expertise = val;
|
||||
}
|
||||
if let Some(val) = dto.languages {
|
||||
self.languages = val;
|
||||
}
|
||||
if let Some(val) = dto.current_company {
|
||||
self.current_company = val;
|
||||
}
|
||||
if let Some(val) = dto.current_role {
|
||||
self.current_role = val;
|
||||
}
|
||||
if let Some(val) = dto.years_of_experience {
|
||||
self.years_of_experience = val;
|
||||
}
|
||||
if let Some(val) = dto.topics_of_interest {
|
||||
self.topics_of_interest = val;
|
||||
}
|
||||
if let Some(val) = dto.preferred_mentee_level {
|
||||
self.preferred_mentee_level = val;
|
||||
}
|
||||
if let Some(val) = dto.preferred_mentoring_formats {
|
||||
self.preferred_mentoring_formats = val;
|
||||
}
|
||||
if let Some(val) = dto.availability_commitment {
|
||||
self.availability_commitment = val;
|
||||
}
|
||||
if let Some(val) = dto.mentoring_rate_amount {
|
||||
self.mentoring_rate.amount = val;
|
||||
}
|
||||
|
||||
self.updated_at = get_iso_date();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn update_status(mut self, status: String) -> Self {
|
||||
self.status = status;
|
||||
self.updated_at = get_iso_date();
|
||||
self
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,386 @@
|
||||
use crate::v1::mentors::{
|
||||
MentorDetailQueryDto, MentorDetailResponseDto, MentorListResponseDto,
|
||||
MentorRegisterResponseDto, MentorSchema, MentorUpdateRequestDto,
|
||||
MentorUserRegisterRequestDto, MentorVerifyRequestDto, MentorsRepository,
|
||||
};
|
||||
use axum::http::StatusCode;
|
||||
use axum::response::Response;
|
||||
use imphnen_entities::{
|
||||
AppState, MetaRequestDto, ResponseListSuccessDto, ResponseSuccessDto,
|
||||
};
|
||||
use imphnen_iam::{
|
||||
AuthRepository, RolesEnum, RolesRepository, UsersRepository, UsersSchema,
|
||||
};
|
||||
use imphnen_libs::ResourceEnum;
|
||||
use imphnen_utils::{
|
||||
common_response, success_list_response, success_response, validate_request,
|
||||
};
|
||||
use surrealdb::Uuid;
|
||||
use surrealdb::sql::Thing;
|
||||
use tracing::error;
|
||||
|
||||
pub struct MentorsService;
|
||||
|
||||
impl MentorsService {
|
||||
pub async fn register_mentor(
|
||||
state: &AppState,
|
||||
dto: MentorUserRegisterRequestDto,
|
||||
) -> Response {
|
||||
if let Err((status, message)) = validate_request(&dto) {
|
||||
return common_response(status, &message);
|
||||
}
|
||||
|
||||
let user_repo = UsersRepository::new(state);
|
||||
let mentor_repo = MentorsRepository::new(state);
|
||||
let role_repo = RolesRepository::new(state);
|
||||
let auth_repo = AuthRepository::new(state);
|
||||
|
||||
let user_email = dto.email.clone();
|
||||
let mut _user_to_update: Option<UsersSchema> = None;
|
||||
|
||||
let existing_user_result =
|
||||
user_repo.query_user_by_email(user_email.clone()).await;
|
||||
|
||||
let user_id;
|
||||
let final_user_email = user_email.clone();
|
||||
|
||||
if let Ok(user_detail_query_dto) = existing_user_result {
|
||||
if mentor_repo
|
||||
.query_mentor_by_email(user_email.clone(), false)
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
return common_response(
|
||||
StatusCode::CONFLICT,
|
||||
"Mentor profile already exists for this user",
|
||||
);
|
||||
}
|
||||
|
||||
let mut user_schema = UsersSchema::from(user_detail_query_dto.clone());
|
||||
|
||||
user_schema.fullname = dto.fullname.clone();
|
||||
user_schema.phone_number = dto.phone_number.clone();
|
||||
user_schema.updated_at = imphnen_utils::get_iso_date();
|
||||
|
||||
let hashed_password = match imphnen_utils::hash_password(&dto.password) {
|
||||
Ok(hash) => hash,
|
||||
Err(_e) => {
|
||||
error!(
|
||||
"Failed to hash password during update for {}: {}",
|
||||
final_user_email, _e
|
||||
);
|
||||
return common_response(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
"Failed to hash password",
|
||||
);
|
||||
}
|
||||
};
|
||||
user_schema.password = hashed_password;
|
||||
|
||||
let mentor_role = match role_repo
|
||||
.query_role_by_name(RolesEnum::Mentor.to_string())
|
||||
.await
|
||||
{
|
||||
Ok(role) => role,
|
||||
Err(_e) => {
|
||||
return common_response(StatusCode::BAD_REQUEST, "Mentor Role Not Found");
|
||||
}
|
||||
};
|
||||
user_schema.role =
|
||||
imphnen_utils::make_thing(&ResourceEnum::Roles.to_string(), &mentor_role.id);
|
||||
user_schema.is_active = false;
|
||||
|
||||
if let Err(_err) = user_repo.query_update_user(user_schema.clone()).await {
|
||||
error!(
|
||||
"Failed to update existing user {} to mentor role: {}",
|
||||
final_user_email, _err
|
||||
);
|
||||
return common_response(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
&_err.to_string(),
|
||||
);
|
||||
}
|
||||
user_id = user_schema.id.clone();
|
||||
} else {
|
||||
let mentor_role = match role_repo
|
||||
.query_role_by_name(RolesEnum::Mentor.to_string())
|
||||
.await
|
||||
{
|
||||
Ok(role) => role,
|
||||
Err(_e) => {
|
||||
return common_response(StatusCode::BAD_REQUEST, "Mentor Role Not Found");
|
||||
}
|
||||
};
|
||||
|
||||
let hashed_password = match imphnen_utils::hash_password(&dto.password) {
|
||||
Ok(hash) => hash,
|
||||
Err(_e) => {
|
||||
error!(
|
||||
"Failed to hash password for new user {}: {}",
|
||||
final_user_email, _e
|
||||
);
|
||||
return common_response(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
"Failed to hash password",
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
let new_user_schema = UsersSchema {
|
||||
id: imphnen_utils::make_thing(
|
||||
&ResourceEnum::Users.to_string(),
|
||||
&Uuid::new_v4().to_string(),
|
||||
),
|
||||
email: dto.email.clone(),
|
||||
fullname: dto.fullname.clone(),
|
||||
password: hashed_password,
|
||||
phone_number: dto.phone_number.clone(),
|
||||
created_at: imphnen_utils::get_iso_date(),
|
||||
updated_at: imphnen_utils::get_iso_date(),
|
||||
role: imphnen_utils::make_thing(
|
||||
&ResourceEnum::Roles.to_string(),
|
||||
&mentor_role.id,
|
||||
),
|
||||
is_active: false,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
user_id = new_user_schema.id.clone();
|
||||
|
||||
match user_repo.query_create_user(new_user_schema).await {
|
||||
Ok(_) => {}
|
||||
Err(_err) => {
|
||||
error!("Failed to create new user {}: {}", final_user_email, _err);
|
||||
return common_response(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
&_err.to_string(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let otp = imphnen_utils::generate_otp::OtpManager::generate_otp();
|
||||
|
||||
match auth_repo
|
||||
.query_store_otp(final_user_email.clone(), otp)
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
let message = format!("your otp code is {otp}");
|
||||
if let Err(_err) =
|
||||
imphnen_utils::send_email(&final_user_email, "OTP Verification", &message)
|
||||
{
|
||||
error!("Failed to send OTP email to {}: {}", final_user_email, _err);
|
||||
return common_response(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
&_err.to_string(),
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(_err) => {
|
||||
error!("Failed to store OTP for {}: {}", final_user_email, _err);
|
||||
return common_response(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
&_err.to_string(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let mentor_schema = MentorSchema::create(
|
||||
dto.identity_and_verification,
|
||||
dto.professional_profile,
|
||||
dto.mentoring_logistics,
|
||||
user_id.to_raw(),
|
||||
final_user_email.clone(),
|
||||
);
|
||||
|
||||
match mentor_repo.query_create_mentor(mentor_schema.clone()).await {
|
||||
Ok(mentor_profile_id) => {
|
||||
let user_after_mentor_creation_dto = user_repo
|
||||
.query_user_by_email(final_user_email.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
let mut user_after_mentor_creation_schema =
|
||||
UsersSchema::from(user_after_mentor_creation_dto);
|
||||
user_after_mentor_creation_schema = user_after_mentor_creation_schema
|
||||
.update_mentor_id(Some(mentor_profile_id));
|
||||
if let Err(_e) = user_repo
|
||||
.query_update_user(user_after_mentor_creation_schema)
|
||||
.await
|
||||
{
|
||||
error!(
|
||||
"Failed to update user's mentor_id for {}: {}",
|
||||
final_user_email, _e
|
||||
);
|
||||
return common_response(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
&_e.to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
let response_dto = MentorRegisterResponseDto::from(mentor_schema);
|
||||
success_response(ResponseSuccessDto { data: response_dto })
|
||||
}
|
||||
Err(_e) => {
|
||||
error!(
|
||||
"Failed to create mentor profile for {}: {}",
|
||||
final_user_email, _e
|
||||
);
|
||||
common_response(StatusCode::INTERNAL_SERVER_ERROR, &_e.to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_mentor_list(state: &AppState, meta: MetaRequestDto) -> Response {
|
||||
let repo = MentorsRepository::new(state);
|
||||
match repo.query_mentor_list(meta).await {
|
||||
Ok(result) => {
|
||||
let data: Vec<MentorListResponseDto> = result
|
||||
.data
|
||||
.into_iter()
|
||||
.map(MentorDetailQueryDto::from)
|
||||
.map(MentorListResponseDto::from)
|
||||
.collect();
|
||||
success_list_response(ResponseListSuccessDto {
|
||||
data,
|
||||
meta: result.meta,
|
||||
})
|
||||
}
|
||||
Err(_e) => common_response(StatusCode::INTERNAL_SERVER_ERROR, &_e.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_mentor_by_id(state: &AppState, id: &str) -> Response {
|
||||
let repo = MentorsRepository::new(state);
|
||||
let thing_id = Thing::from((ResourceEnum::Mentors.to_string().as_str(), id));
|
||||
match repo.query_mentor_by_id(&thing_id, false).await {
|
||||
Ok(mentor) => {
|
||||
let dto = MentorDetailResponseDto::from(MentorDetailQueryDto::from(mentor));
|
||||
success_response(ResponseSuccessDto { data: dto })
|
||||
}
|
||||
Err(_e) => common_response(StatusCode::NOT_FOUND, &_e.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn update_mentor(
|
||||
state: &AppState,
|
||||
id: &str,
|
||||
dto: MentorUpdateRequestDto,
|
||||
) -> Response {
|
||||
if let Err((status, message)) = validate_request(&dto) {
|
||||
return common_response(status, &message);
|
||||
}
|
||||
let repo = MentorsRepository::new(state);
|
||||
let thing_id = Thing::from((ResourceEnum::Mentors.to_string().as_str(), id));
|
||||
let existing_mentor = match repo.query_mentor_by_id(&thing_id, false).await {
|
||||
Ok(mentor) => mentor,
|
||||
Err(_e) => return common_response(StatusCode::NOT_FOUND, &_e.to_string()),
|
||||
};
|
||||
|
||||
let mut schema = MentorSchema::from(MentorDetailQueryDto::from(existing_mentor));
|
||||
schema = schema.update(dto);
|
||||
|
||||
match repo.query_update_mentor(schema).await {
|
||||
Ok(_) => {
|
||||
let updated_mentor =
|
||||
repo.query_mentor_by_id(&thing_id, false).await.unwrap();
|
||||
let response_dto =
|
||||
MentorDetailResponseDto::from(MentorDetailQueryDto::from(updated_mentor));
|
||||
success_response(ResponseSuccessDto { data: response_dto })
|
||||
}
|
||||
Err(_e) => common_response(StatusCode::INTERNAL_SERVER_ERROR, &_e.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn delete_mentor(state: &AppState, id: &str) -> Response {
|
||||
let repo = MentorsRepository::new(state);
|
||||
match repo.query_delete_mentor(id.to_string()).await {
|
||||
Ok(msg) => common_response(StatusCode::OK, &msg),
|
||||
Err(_e) => common_response(StatusCode::NOT_FOUND, &_e.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_mentor_me(state: &AppState, email: &str) -> Response {
|
||||
let repo = MentorsRepository::new(state);
|
||||
match repo.query_mentor_by_email(email.to_string(), false).await {
|
||||
Ok(mentor) => {
|
||||
let dto = MentorDetailResponseDto::from(MentorDetailQueryDto::from(mentor));
|
||||
success_response(ResponseSuccessDto { data: dto })
|
||||
}
|
||||
Err(_e) => common_response(
|
||||
StatusCode::FORBIDDEN,
|
||||
"Mentor profile not found for current user",
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn update_mentor_me(
|
||||
state: &AppState,
|
||||
email: &str,
|
||||
dto: MentorUpdateRequestDto,
|
||||
) -> Response {
|
||||
if let Err((status, message)) = validate_request(&dto) {
|
||||
return common_response(status, &message);
|
||||
}
|
||||
let repo = MentorsRepository::new(state);
|
||||
let existing_mentor =
|
||||
match repo.query_mentor_by_email(email.to_string(), false).await {
|
||||
Ok(mentor) => mentor,
|
||||
Err(_e) => return common_response(StatusCode::FORBIDDEN, &_e.to_string()),
|
||||
};
|
||||
|
||||
let mut schema = MentorSchema::from(MentorDetailQueryDto::from(existing_mentor));
|
||||
schema = schema.update(dto);
|
||||
|
||||
match repo.query_update_mentor(schema).await {
|
||||
Ok(_) => {
|
||||
let updated_mentor = repo
|
||||
.query_mentor_by_email(email.to_string(), false)
|
||||
.await
|
||||
.unwrap();
|
||||
let response_dto =
|
||||
MentorDetailResponseDto::from(MentorDetailQueryDto::from(updated_mentor));
|
||||
success_response(ResponseSuccessDto { data: response_dto })
|
||||
}
|
||||
Err(_e) => common_response(StatusCode::INTERNAL_SERVER_ERROR, &_e.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_mentor_status(state: &AppState, email: &str) -> Response {
|
||||
let repo = MentorsRepository::new(state);
|
||||
match repo.query_mentor_by_email(email.to_string(), false).await {
|
||||
Ok(mentor) => common_response(StatusCode::OK, &mentor.status),
|
||||
Err(_e) => common_response(
|
||||
StatusCode::FORBIDDEN,
|
||||
"No mentor application found for current user",
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn verify_mentor(
|
||||
state: &AppState,
|
||||
id: &str,
|
||||
dto: MentorVerifyRequestDto,
|
||||
) -> Response {
|
||||
let repo = MentorsRepository::new(state);
|
||||
let thing_id = Thing::from((ResourceEnum::Mentors.to_string().as_str(), id));
|
||||
let existing_mentor = match repo.query_mentor_by_id(&thing_id, false).await {
|
||||
Ok(mentor) => mentor,
|
||||
Err(_e) => return common_response(StatusCode::NOT_FOUND, &_e.to_string()),
|
||||
};
|
||||
|
||||
let mut schema = MentorSchema::from(MentorDetailQueryDto::from(existing_mentor));
|
||||
schema = schema.update_status(dto.status);
|
||||
|
||||
match repo.query_update_mentor(schema).await {
|
||||
Ok(_) => {
|
||||
let updated_mentor =
|
||||
repo.query_mentor_by_id(&thing_id, false).await.unwrap();
|
||||
let response_dto =
|
||||
MentorDetailResponseDto::from(MentorDetailQueryDto::from(updated_mentor));
|
||||
success_response(ResponseSuccessDto { data: response_dto })
|
||||
}
|
||||
Err(_e) => common_response(StatusCode::INTERNAL_SERVER_ERROR, &_e.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
use axum::{
|
||||
Router,
|
||||
routing::{delete, get, post, put},
|
||||
};
|
||||
|
||||
pub mod mentors_controller;
|
||||
pub mod mentors_dto;
|
||||
pub mod mentors_repository;
|
||||
pub mod mentors_schema;
|
||||
pub mod mentors_service;
|
||||
|
||||
pub use mentors_controller::*;
|
||||
pub use mentors_dto::*;
|
||||
pub use mentors_repository::*;
|
||||
pub use mentors_schema::*;
|
||||
pub use mentors_service::*;
|
||||
|
||||
pub fn mentors_router() -> Router {
|
||||
Router::new()
|
||||
.route("/", get(get_mentor_list))
|
||||
.route("/register", post(post_register_mentor))
|
||||
.route("/me", get(get_mentor_me))
|
||||
.route("/update/me", put(put_update_mentor_me))
|
||||
.route("/status", get(get_mentor_status))
|
||||
.route("/detail/{id}", get(get_mentor_by_id))
|
||||
.route("/update/{id}", put(put_update_mentor))
|
||||
.route("/update", put(put_update_mentor_no_id))
|
||||
.route("/delete/{id}", delete(delete_mentor))
|
||||
.route("/verify/{id}", put(put(put_verify_mentor)))
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
use axum::Router;
|
||||
|
||||
pub mod mentors;
|
||||
|
||||
pub fn dimentorin_router() -> Router {
|
||||
Router::new().nest("/mentors", mentors::mentors_router())
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
Reference in New Issue
Block a user