- Updated `async-channel` to version 2.5.0 and added new dependencies in `Cargo.lock`. - Introduced `minio` crate for file upload functionality. - Added `career_status` field to user-related DTOs and schemas. - Implemented file upload endpoint in `users_controller.rs` with multipart support. - Created `MinioService` for handling file uploads to MinIO. - Updated user seeding and test cases to accommodate new `career_status` field. - Refactored permissions guard to return user details.
398 lines
13 KiB
Rust
398 lines
13 KiB
Rust
use crate::{RolesDetailItemDto, RolesDetailQueryDto};
|
|
use lazy_static::lazy_static;
|
|
use serde::{Deserialize, Serialize};
|
|
use surrealdb::sql::Thing;
|
|
use utoipa::ToSchema;
|
|
use validator::Validate;
|
|
use crate::UsersSchema; // Import UsersSchema
|
|
|
|
lazy_static! {
|
|
static ref PASSWORD_REGEX: regex::Regex =
|
|
regex::Regex::new(r"^[A-Za-z\d@$!%*?&]{8,}$").unwrap();
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
|
|
pub struct ExperienceDto {
|
|
pub id: String,
|
|
pub company: String,
|
|
pub position: String,
|
|
pub duration: String,
|
|
pub period: String,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
|
|
pub struct EducationDto {
|
|
pub id: String,
|
|
pub institution: String,
|
|
pub degree: String,
|
|
pub field: String,
|
|
pub period: String,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
|
|
pub struct UsersActiveInactiveRequestDto {
|
|
pub is_active: bool,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
|
|
pub struct UsersSetNewPasswordRequestDto {
|
|
pub password: String,
|
|
pub old_password: String,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema, Validate)]
|
|
pub struct UsersCreateRequestDto {
|
|
#[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(regex(
|
|
path = "*PASSWORD_REGEX",
|
|
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 = 10,
|
|
message = "Phone number at least have 10 character"
|
|
))]
|
|
pub phone_number: String,
|
|
pub is_active: bool,
|
|
pub role_id: String,
|
|
pub avatar: Option<String>,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema, Validate)]
|
|
pub struct UsersUpdateRequestDto {
|
|
#[validate(
|
|
length(min = 1, message = "Email cannot be empty"),
|
|
email(message = "Email not valid")
|
|
)]
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub email: Option<String>,
|
|
#[validate(length(
|
|
min = 8,
|
|
message = "Password must have at least 8 characters"
|
|
))]
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub password: Option<String>,
|
|
#[validate(length(min = 2, message = "Fullname at least have 2 character"))]
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub fullname: Option<String>,
|
|
#[validate(length(min = 2, message = "Legal name at least have 2 character"))]
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub legal_name: Option<String>,
|
|
#[validate(length(
|
|
min = 10,
|
|
message = "Phone number at least have 10 character"
|
|
))]
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub phone_number: Option<String>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub phone_for_verification: Option<String>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub is_active: Option<bool>,
|
|
#[validate(length(min = 1, message = "Gender is required"))]
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub gender: Option<String>,
|
|
#[validate(length(min = 1, message = "Birthdate is required"))]
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub birthdate: Option<String>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub domicile: 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>,
|
|
#[validate(url(message = "Invalid portfolio URL"))]
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub portfolio_url: Option<String>,
|
|
#[validate(url(message = "Invalid website URL"))]
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub website_url: Option<String>,
|
|
#[validate(url(message = "Invalid Twitter URL"))]
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub twitter_url: Option<String>,
|
|
#[validate(length(min = 1, message = "Avatar is required"))]
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub avatar: Option<String>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub role_id: Option<String>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub location: Option<String>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub skills: Option<Vec<String>>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub experience: Option<Vec<ExperienceDto>>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub education: Option<Vec<EducationDto>>,
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub career_status: Option<String>,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
|
|
pub struct UsersDetailItemDto {
|
|
pub id: String,
|
|
pub role: RolesDetailItemDto,
|
|
pub fullname: String,
|
|
pub legal_name: Option<String>,
|
|
pub email: String,
|
|
pub avatar: Option<String>,
|
|
pub phone_number: String,
|
|
pub phone_for_verification: Option<String>,
|
|
pub is_active: bool,
|
|
pub gender: Option<String>,
|
|
pub birthdate: Option<String>,
|
|
pub domicile: Option<String>,
|
|
pub bio: Option<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 website_url: Option<String>,
|
|
pub twitter_url: Option<String>,
|
|
pub location: Option<String>,
|
|
pub skills: Option<Vec<String>>,
|
|
pub experience: Option<Vec<ExperienceDto>>,
|
|
pub education: Option<Vec<EducationDto>>,
|
|
pub career_status: Option<String>,
|
|
pub created_at: String,
|
|
pub updated_at: String,
|
|
}
|
|
|
|
impl UsersDetailItemDto {
|
|
pub fn from(dto: &UsersDetailQueryDto) -> Self { // Reverted to taking a reference
|
|
Self {
|
|
id: dto.id.id.to_raw().clone(),
|
|
role: RolesDetailItemDto::from(&dto.role),
|
|
fullname: dto.fullname.clone(),
|
|
legal_name: dto.legal_name.clone(),
|
|
email: dto.email.clone(),
|
|
avatar: dto.avatar.clone(),
|
|
phone_number: dto.phone_number.clone(), // Corrected from dto.phone.clone()
|
|
phone_for_verification: dto.phone_for_verification.clone(),
|
|
is_active: dto.is_active,
|
|
gender: dto.gender.clone(),
|
|
birthdate: dto.birthdate.clone(),
|
|
domicile: dto.domicile.clone(),
|
|
bio: dto.bio.clone(),
|
|
last_education: dto.last_education.clone(),
|
|
linkedin_url: dto.linkedin_url.clone(),
|
|
github_url: dto.github_url.clone(),
|
|
cv_url: dto.cv_url.clone(),
|
|
portfolio_url: dto.portfolio_url.clone(),
|
|
website_url: dto.website_url.clone(),
|
|
twitter_url: dto.twitter_url.clone(),
|
|
location: dto.location.clone(),
|
|
skills: dto.skills.clone(),
|
|
experience: dto.experience.clone(),
|
|
education: dto.education.clone(),
|
|
career_status: dto.career_status.clone(),
|
|
created_at: dto.created_at.clone(),
|
|
updated_at: dto.updated_at.clone(),
|
|
}
|
|
}
|
|
|
|
pub fn from_schema(schema: &UsersSchema) -> Self {
|
|
Self {
|
|
id: schema.id.id.to_raw(),
|
|
role: RolesDetailItemDto::default(), // Placeholder, role needs to be fetched
|
|
fullname: schema.fullname.clone(),
|
|
legal_name: schema.legal_name.clone(),
|
|
email: schema.email.clone(),
|
|
avatar: schema.avatar.clone(),
|
|
phone_number: schema.phone_number.clone(),
|
|
phone_for_verification: schema.phone_for_verification.clone(),
|
|
is_active: schema.is_active,
|
|
gender: schema.gender.clone(),
|
|
birthdate: schema.birthdate.clone(),
|
|
domicile: schema.domicile.clone(),
|
|
bio: schema.bio.clone(),
|
|
last_education: schema.last_education.clone(),
|
|
linkedin_url: schema.linkedin_url.clone(),
|
|
github_url: schema.github_url.clone(),
|
|
cv_url: schema.cv_url.clone(),
|
|
portfolio_url: schema.portfolio_url.clone(),
|
|
website_url: schema.website_url.clone(),
|
|
twitter_url: schema.twitter_url.clone(),
|
|
location: schema.location.clone(),
|
|
skills: schema.skills.clone(),
|
|
experience: schema.experience.clone(),
|
|
education: schema.education.clone(),
|
|
career_status: schema.career_status.clone(),
|
|
created_at: schema.created_at.clone(),
|
|
updated_at: schema.updated_at.clone(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
|
|
pub struct UsersListItemDto {
|
|
pub id: String,
|
|
pub role: String,
|
|
pub fullname: String,
|
|
pub email: String,
|
|
pub avatar: Option<String>,
|
|
pub phone_number: String,
|
|
pub is_active: bool,
|
|
pub created_at: String,
|
|
pub updated_at: String,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
pub struct UsersListQueryDto {
|
|
pub id: Thing,
|
|
pub role: RolesDetailQueryDto,
|
|
pub fullname: String,
|
|
pub email: String, // Corrected from pub pub email: String,
|
|
pub avatar: Option<String>,
|
|
pub phone_number: String,
|
|
pub is_active: bool,
|
|
pub created_at: String,
|
|
pub updated_at: String,
|
|
}
|
|
|
|
impl UsersListQueryDto {
|
|
pub fn from(self) -> UsersListItemDto {
|
|
UsersListItemDto {
|
|
id: self.id.id.to_raw(),
|
|
role: self.role.name.clone(),
|
|
fullname: self.fullname.clone(),
|
|
email: self.email.clone(),
|
|
avatar: self.avatar.clone(),
|
|
phone_number: self.phone_number.clone(),
|
|
is_active: self.is_active,
|
|
created_at: self.created_at.clone(),
|
|
updated_at: self.updated_at.clone(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
pub struct UsersDetailQueryDto {
|
|
pub id: Thing,
|
|
pub fullname: String,
|
|
pub legal_name: Option<String>,
|
|
pub email: String,
|
|
pub avatar: Option<String>,
|
|
pub phone_number: String,
|
|
pub phone_for_verification: Option<String>,
|
|
pub is_active: bool,
|
|
pub is_deleted: bool,
|
|
pub gender: Option<String>,
|
|
pub birthdate: Option<String>,
|
|
pub domicile: Option<String>,
|
|
pub bio: Option<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 website_url: Option<String>,
|
|
pub twitter_url: Option<String>,
|
|
pub location: Option<String>,
|
|
pub skills: Option<Vec<String>>,
|
|
pub experience: Option<Vec<ExperienceDto>>,
|
|
pub education: Option<Vec<EducationDto>>,
|
|
pub career_status: Option<String>,
|
|
pub password: String,
|
|
pub role: RolesDetailQueryDto,
|
|
pub created_at: String,
|
|
pub updated_at: String,
|
|
pub mentor_id: Option<Thing>,
|
|
}
|
|
|
|
impl UsersDetailQueryDto {
|
|
pub fn from(&self) -> Self {
|
|
Self {
|
|
id: self.id.clone(),
|
|
role: self.role.clone(),
|
|
fullname: self.fullname.clone(),
|
|
legal_name: self.legal_name.clone(),
|
|
email: self.email.clone(),
|
|
avatar: self.avatar.clone(),
|
|
phone_number: self.phone_number.clone(),
|
|
phone_for_verification: self.phone_for_verification.clone(),
|
|
is_active: self.is_active,
|
|
mentor_id: self.mentor_id.clone(),
|
|
gender: self.gender.clone(),
|
|
domicile: self.domicile.clone(),
|
|
bio: self.bio.clone(),
|
|
last_education: self.last_education.clone(),
|
|
linkedin_url: self.linkedin_url.clone(),
|
|
github_url: self.github_url.clone(),
|
|
cv_url: self.cv_url.clone(),
|
|
portfolio_url: self.portfolio_url.clone(),
|
|
website_url: self.website_url.clone(),
|
|
twitter_url: self.twitter_url.clone(),
|
|
location: self.location.clone(),
|
|
skills: self.skills.clone(),
|
|
experience: self.experience.clone(),
|
|
education: self.education.clone(),
|
|
career_status: self.career_status.clone(),
|
|
is_deleted: self.is_deleted,
|
|
password: self.password.clone(),
|
|
birthdate: self.birthdate.clone(),
|
|
created_at: self.created_at.clone(),
|
|
updated_at: self.updated_at.clone(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<&UsersDetailItemDto> for UsersDetailQueryDto {
|
|
fn from(dto: &UsersDetailItemDto) -> Self {
|
|
Self {
|
|
id: crate::make_thing(&imphnen_libs::ResourceEnum::Users.to_string(), &dto.id),
|
|
fullname: dto.fullname.clone(),
|
|
legal_name: dto.legal_name.clone(),
|
|
email: dto.email.clone(),
|
|
avatar: dto.avatar.clone(),
|
|
phone_number: dto.phone_number.clone(),
|
|
phone_for_verification: dto.phone_for_verification.clone(),
|
|
is_active: dto.is_active,
|
|
is_deleted: false,
|
|
gender: dto.gender.clone(),
|
|
birthdate: dto.birthdate.clone(),
|
|
domicile: dto.domicile.clone(),
|
|
bio: dto.bio.clone(),
|
|
last_education: dto.last_education.clone(),
|
|
linkedin_url: dto.linkedin_url.clone(),
|
|
github_url: dto.github_url.clone(),
|
|
cv_url: dto.cv_url.clone(),
|
|
portfolio_url: dto.portfolio_url.clone(),
|
|
website_url: dto.website_url.clone(),
|
|
twitter_url: dto.twitter_url.clone(),
|
|
location: dto.location.clone(),
|
|
skills: dto.skills.clone(),
|
|
experience: dto.experience.clone(),
|
|
education: dto.education.clone(),
|
|
career_status: dto.career_status.clone(),
|
|
password: String::new(),
|
|
role: RolesDetailQueryDto::default(),
|
|
created_at: dto.created_at.clone(),
|
|
updated_at: dto.updated_at.clone(),
|
|
mentor_id: None,
|
|
}
|
|
}
|
|
}
|