feat(users): Enhance user schema and DTOs with additional fields for website, social links, location, skills, experience, and education
This commit is contained in:
@@ -65,6 +65,12 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
github_url: Some("https://github.com/user".into()),
|
github_url: Some("https://github.com/user".into()),
|
||||||
cv_url: Some("https://example.com/cv.pdf".into()),
|
cv_url: Some("https://example.com/cv.pdf".into()),
|
||||||
portfolio_url: Some("https://example.com/portfolio".into()),
|
portfolio_url: Some("https://example.com/portfolio".into()),
|
||||||
|
website_url: Some("https://example.com/website".into()),
|
||||||
|
twitter_url: Some("https://twitter.com/user".into()),
|
||||||
|
location: Some("Jakarta, Indonesia".into()),
|
||||||
|
skills: Some(vec!["JavaScript".into(), "React".into(), "Node.js".into()]),
|
||||||
|
experience: None,
|
||||||
|
education: None,
|
||||||
role: Thing::from(("app_roles", role_id)),
|
role: Thing::from(("app_roles", role_id)),
|
||||||
created_at: get_iso_date(),
|
created_at: get_iso_date(),
|
||||||
updated_at: get_iso_date(),
|
updated_at: get_iso_date(),
|
||||||
|
|||||||
@@ -114,6 +114,7 @@ pub struct MentorUpdateRequestDto {
|
|||||||
#[validate(url(message = "Invalid CV URL"))]
|
#[validate(url(message = "Invalid CV URL"))]
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub cv_url: Option<String>,
|
pub cv_url: Option<String>,
|
||||||
|
#[validate(url(message = "Invalid portfolio URL"))]
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub portfolio_url: Option<String>,
|
pub portfolio_url: Option<String>,
|
||||||
#[validate(length(min = 1, message = "At least 1 industry required"))]
|
#[validate(length(min = 1, message = "At least 1 industry required"))]
|
||||||
|
|||||||
@@ -26,5 +26,5 @@ pub fn mentors_router() -> Router {
|
|||||||
.route("/update/{id}", put(put_update_mentor))
|
.route("/update/{id}", put(put_update_mentor))
|
||||||
.route("/update", put(put_update_mentor_no_id))
|
.route("/update", put(put_update_mentor_no_id))
|
||||||
.route("/delete/{id}", delete(delete_mentor))
|
.route("/delete/{id}", delete(delete_mentor))
|
||||||
.route("/verify/{id}", put(put(put_verify_mentor)))
|
.route("/verify/{id}", put(put_verify_mentor))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
AppState, GachaItemDto, GachaItemRequestDto, GachaItemService, MessageResponseDto,
|
AppState, GachaItemDto, GachaItemRequestDto, GachaItemUpdateRequestDto, GachaItemService, MessageResponseDto,
|
||||||
MetaRequestDto, ResponseListSuccessDto, ResponseSuccessDto,
|
MetaRequestDto, ResponseListSuccessDto, ResponseSuccessDto,
|
||||||
};
|
};
|
||||||
use axum::{
|
use axum::{
|
||||||
@@ -111,7 +111,7 @@ pub async fn post_create_gacha_item(
|
|||||||
security(
|
security(
|
||||||
("Bearer" = [])
|
("Bearer" = [])
|
||||||
),
|
),
|
||||||
request_body = GachaItemRequestDto,
|
request_body = GachaItemUpdateRequestDto,
|
||||||
responses(
|
responses(
|
||||||
(status = 200, description = "Update gacha item", body = MessageResponseDto)
|
(status = 200, description = "Update gacha item", body = MessageResponseDto)
|
||||||
),
|
),
|
||||||
@@ -121,7 +121,7 @@ pub async fn put_update_gacha_item(
|
|||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
Extension(state): Extension<AppState>,
|
Extension(state): Extension<AppState>,
|
||||||
Path(id): Path<String>,
|
Path(id): Path<String>,
|
||||||
Json(payload): Json<GachaItemRequestDto>,
|
Json(payload): Json<GachaItemUpdateRequestDto>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
match permissions_guard(
|
match permissions_guard(
|
||||||
&headers,
|
&headers,
|
||||||
|
|||||||
@@ -11,6 +11,16 @@ pub struct GachaItemRequestDto {
|
|||||||
pub image_url: String,
|
pub image_url: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema, Validate)]
|
||||||
|
pub struct GachaItemUpdateRequestDto {
|
||||||
|
#[validate(length(min = 1, message = "Item name must not be empty"))]
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub name: Option<String>,
|
||||||
|
#[validate(length(min = 1, message = "Image URL must not be empty"))]
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub image_url: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
|
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||||
pub struct GachaItemDto {
|
pub struct GachaItemDto {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
|
|||||||
@@ -43,4 +43,8 @@ impl GachaItemSchema {
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn from_existing(existing: GachaItemSchema) -> Self {
|
||||||
|
existing
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
AppState, GachaItemDto, GachaItemRepository, GachaItemRequestDto, GachaItemSchema,
|
AppState, GachaItemDto, GachaItemRepository, GachaItemRequestDto, GachaItemUpdateRequestDto, GachaItemSchema,
|
||||||
MetaRequestDto, ResourceEnum, ResponseListSuccessDto, ResponseSuccessDto,
|
MetaRequestDto, ResourceEnum, ResponseListSuccessDto, ResponseSuccessDto,
|
||||||
common_response, make_thing, success_list_response, success_response,
|
common_response, make_thing, success_list_response, success_response,
|
||||||
validate_request,
|
validate_request,
|
||||||
};
|
};
|
||||||
use axum::http::StatusCode;
|
use axum::http::StatusCode;
|
||||||
use axum::response::Response;
|
use axum::response::Response;
|
||||||
|
use imphnen_utils::get_iso_date;
|
||||||
|
|
||||||
pub struct GachaItemService;
|
pub struct GachaItemService;
|
||||||
|
|
||||||
@@ -59,20 +60,33 @@ impl GachaItemService {
|
|||||||
|
|
||||||
pub async fn update_gacha_item(
|
pub async fn update_gacha_item(
|
||||||
state: &AppState,
|
state: &AppState,
|
||||||
payload: GachaItemRequestDto,
|
payload: GachaItemUpdateRequestDto,
|
||||||
id: String,
|
id: String,
|
||||||
) -> Response {
|
) -> Response {
|
||||||
if let Err((status, message)) = validate_request(&payload) {
|
if let Err((status, message)) = validate_request(&payload) {
|
||||||
return common_response(status, &message);
|
return common_response(status, &message);
|
||||||
}
|
}
|
||||||
let repo = GachaItemRepository::new(state);
|
let repo = GachaItemRepository::new(state);
|
||||||
let schema = GachaItemSchema {
|
|
||||||
id: make_thing(&ResourceEnum::GachaItems.to_string(), &id),
|
// Get current gacha item data first
|
||||||
name: payload.name,
|
let _thing_id = make_thing(&ResourceEnum::GachaItems.to_string(), &id);
|
||||||
image_url: payload.image_url,
|
let current_item = match repo.query_gacha_item_by_id(id.clone()).await {
|
||||||
..Default::default()
|
Ok(item) => item,
|
||||||
|
Err(_) => return common_response(StatusCode::NOT_FOUND, "Gacha Item not found"),
|
||||||
};
|
};
|
||||||
match repo.query_update_gacha_item(schema).await {
|
|
||||||
|
let mut updated_item = current_item;
|
||||||
|
updated_item.updated_at = Some(get_iso_date());
|
||||||
|
|
||||||
|
// Only update fields that are provided
|
||||||
|
if let Some(name) = payload.name {
|
||||||
|
updated_item.name = name;
|
||||||
|
}
|
||||||
|
if let Some(image_url) = payload.image_url {
|
||||||
|
updated_item.image_url = image_url;
|
||||||
|
}
|
||||||
|
|
||||||
|
match repo.query_update_gacha_item(updated_item).await {
|
||||||
Ok(msg) => common_response(StatusCode::OK, &msg),
|
Ok(msg) => common_response(StatusCode::OK, &msg),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
if e.to_string().contains("not found") {
|
if e.to_string().contains("not found") {
|
||||||
|
|||||||
@@ -110,6 +110,12 @@ impl<'a> AuthRepository<'a> {
|
|||||||
github_url: None,
|
github_url: None,
|
||||||
cv_url: None,
|
cv_url: None,
|
||||||
portfolio_url: None,
|
portfolio_url: None,
|
||||||
|
website_url: None,
|
||||||
|
twitter_url: None,
|
||||||
|
location: None,
|
||||||
|
skills: None,
|
||||||
|
experience: None,
|
||||||
|
education: None,
|
||||||
password: String::new(),
|
password: String::new(),
|
||||||
role: role_detail_query_dto,
|
role: role_detail_query_dto,
|
||||||
created_at: String::new(),
|
created_at: String::new(),
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use crate::{
|
|||||||
AppState, MessageResponseDto, MetaRequestDto, ResponseListSuccessDto,
|
AppState, MessageResponseDto, MetaRequestDto, ResponseListSuccessDto,
|
||||||
ResponseSuccessDto,
|
ResponseSuccessDto,
|
||||||
v1::{
|
v1::{
|
||||||
permissions_dto::{PermissionsItemDto, PermissionsRequestDto},
|
permissions_dto::{PermissionsItemDto, PermissionsRequestDto, PermissionsUpdateRequestDto},
|
||||||
permissions_service::PermissionsService,
|
permissions_service::PermissionsService,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -116,7 +116,7 @@ pub async fn post_create_permission(
|
|||||||
("Bearer" = [])
|
("Bearer" = [])
|
||||||
),
|
),
|
||||||
path = "/v1/permissions/update/{id}",
|
path = "/v1/permissions/update/{id}",
|
||||||
request_body = PermissionsRequestDto,
|
request_body = PermissionsUpdateRequestDto,
|
||||||
responses(
|
responses(
|
||||||
(status = 200, description = "Update permission", body = MessageResponseDto)
|
(status = 200, description = "Update permission", body = MessageResponseDto)
|
||||||
),
|
),
|
||||||
@@ -126,7 +126,7 @@ pub async fn put_update_permission(
|
|||||||
headers: axum::http::HeaderMap,
|
headers: axum::http::HeaderMap,
|
||||||
Extension(state): Extension<AppState>,
|
Extension(state): Extension<AppState>,
|
||||||
Path(id): Path<String>,
|
Path(id): Path<String>,
|
||||||
Json(payload): Json<PermissionsRequestDto>,
|
Json(payload): Json<PermissionsUpdateRequestDto>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
match permissions_guard(
|
match permissions_guard(
|
||||||
&headers,
|
&headers,
|
||||||
|
|||||||
@@ -9,6 +9,13 @@ pub struct PermissionsRequestDto {
|
|||||||
pub name: String,
|
pub name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema, Validate)]
|
||||||
|
pub struct PermissionsUpdateRequestDto {
|
||||||
|
#[validate(length(min = 1, message = "Permission name must not be empty"))]
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub name: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
|
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||||
pub struct PermissionsItemDto {
|
pub struct PermissionsItemDto {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use crate::{ResourceEnum, make_thing};
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use surrealdb::{Uuid, sql::Thing};
|
use surrealdb::{Uuid, sql::Thing};
|
||||||
|
|
||||||
use super::PermissionsItemDto;
|
use super::{PermissionsItemDto, PermissionsQueryDto};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct PermissionsSchema {
|
pub struct PermissionsSchema {
|
||||||
@@ -37,4 +37,14 @@ impl PermissionsSchema {
|
|||||||
updated_at: self.updated_at.clone(),
|
updated_at: self.updated_at.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn from(dto: PermissionsQueryDto) -> Self {
|
||||||
|
Self {
|
||||||
|
id: dto.id,
|
||||||
|
name: dto.name,
|
||||||
|
is_deleted: false,
|
||||||
|
created_at: dto.created_at,
|
||||||
|
updated_at: dto.updated_at,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,9 @@ use crate::{
|
|||||||
};
|
};
|
||||||
use axum::http::StatusCode;
|
use axum::http::StatusCode;
|
||||||
use axum::response::Response;
|
use axum::response::Response;
|
||||||
|
use imphnen_utils::get_iso_date;
|
||||||
|
|
||||||
use super::PermissionsRequestDto;
|
use super::{PermissionsRequestDto, PermissionsUpdateRequestDto};
|
||||||
|
|
||||||
pub struct PermissionsService;
|
pub struct PermissionsService;
|
||||||
|
|
||||||
@@ -70,21 +71,31 @@ impl PermissionsService {
|
|||||||
|
|
||||||
pub async fn update_permission(
|
pub async fn update_permission(
|
||||||
state: &AppState,
|
state: &AppState,
|
||||||
payload: PermissionsRequestDto,
|
payload: PermissionsUpdateRequestDto,
|
||||||
id: String,
|
id: String,
|
||||||
) -> Response {
|
) -> Response {
|
||||||
if let Err((status, message)) = validate_request(&payload) {
|
if let Err((status, message)) = validate_request(&payload) {
|
||||||
return common_response(status, &message);
|
return common_response(status, &message);
|
||||||
}
|
}
|
||||||
let repo = PermissionsRepository::new(state);
|
let repo = PermissionsRepository::new(state);
|
||||||
match repo
|
|
||||||
.query_update_permission(PermissionsSchema {
|
// Get current permission data first
|
||||||
id: make_thing(&ResourceEnum::Permissions.to_string(), &id),
|
let thing_id = make_thing(&ResourceEnum::Permissions.to_string(), &id);
|
||||||
name: payload.name,
|
let current_permission = match repo.query_permission_by_id(id.clone()).await {
|
||||||
..Default::default()
|
Ok(permission) => permission,
|
||||||
})
|
Err(_) => return common_response(StatusCode::NOT_FOUND, "Permission not found"),
|
||||||
.await
|
};
|
||||||
{
|
|
||||||
|
let mut updated_permission = current_permission;
|
||||||
|
updated_permission.id = thing_id;
|
||||||
|
updated_permission.updated_at = Some(get_iso_date());
|
||||||
|
|
||||||
|
// Only update fields that are provided
|
||||||
|
if let Some(name) = payload.name {
|
||||||
|
updated_permission.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
match repo.query_update_permission(updated_permission).await {
|
||||||
Ok(msg) => common_response(StatusCode::OK, &msg),
|
Ok(msg) => common_response(StatusCode::OK, &msg),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
if e.to_string().contains("not found") {
|
if e.to_string().contains("not found") {
|
||||||
|
|||||||
@@ -11,6 +11,24 @@ lazy_static! {
|
|||||||
regex::Regex::new(r"^[A-Za-z\d@$!%*?&]{8,}$").unwrap();
|
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)]
|
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||||
pub struct UsersActiveInactiveRequestDto {
|
pub struct UsersActiveInactiveRequestDto {
|
||||||
pub is_active: bool,
|
pub is_active: bool,
|
||||||
@@ -59,43 +77,74 @@ pub struct UsersUpdateRequestDto {
|
|||||||
length(min = 1, message = "Email cannot be empty"),
|
length(min = 1, message = "Email cannot be empty"),
|
||||||
email(message = "Email not valid")
|
email(message = "Email not valid")
|
||||||
)]
|
)]
|
||||||
pub email: String,
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub email: Option<String>,
|
||||||
#[validate(length(
|
#[validate(length(
|
||||||
min = 8,
|
min = 8,
|
||||||
message = "Password must have at least 8 characters"
|
message = "Password must have at least 8 characters"
|
||||||
))]
|
))]
|
||||||
pub password: String,
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub password: Option<String>,
|
||||||
#[validate(length(min = 2, message = "Fullname at least have 2 character"))]
|
#[validate(length(min = 2, message = "Fullname at least have 2 character"))]
|
||||||
pub fullname: String,
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub fullname: Option<String>,
|
||||||
#[validate(length(min = 2, message = "Legal name at least have 2 character"))]
|
#[validate(length(min = 2, message = "Legal name at least have 2 character"))]
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub legal_name: Option<String>,
|
pub legal_name: Option<String>,
|
||||||
#[validate(length(
|
#[validate(length(
|
||||||
min = 10,
|
min = 10,
|
||||||
message = "Phone number at least have 10 character"
|
message = "Phone number at least have 10 character"
|
||||||
))]
|
))]
|
||||||
pub phone_number: String,
|
#[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>,
|
pub phone_for_verification: Option<String>,
|
||||||
pub is_active: bool,
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub is_active: Option<bool>,
|
||||||
#[validate(length(min = 1, message = "Gender is required"))]
|
#[validate(length(min = 1, message = "Gender is required"))]
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub gender: Option<String>,
|
pub gender: Option<String>,
|
||||||
#[validate(length(min = 1, message = "Birthdate is required"))]
|
#[validate(length(min = 1, message = "Birthdate is required"))]
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub birthdate: Option<String>,
|
pub birthdate: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub domicile: Option<String>,
|
pub domicile: Option<String>,
|
||||||
#[validate(url(message = "Invalid identity document URL"))]
|
|
||||||
#[validate(length(min = 50, message = "Bio must be at least 50 characters"))]
|
#[validate(length(min = 50, message = "Bio must be at least 50 characters"))]
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub bio: Option<String>,
|
pub bio: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub last_education: Option<String>,
|
pub last_education: Option<String>,
|
||||||
#[validate(url(message = "Invalid LinkedIn URL"))]
|
#[validate(url(message = "Invalid LinkedIn URL"))]
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub linkedin_url: Option<String>,
|
pub linkedin_url: Option<String>,
|
||||||
#[validate(url(message = "Invalid GitHub URL"))]
|
#[validate(url(message = "Invalid GitHub URL"))]
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub github_url: Option<String>,
|
pub github_url: Option<String>,
|
||||||
#[validate(url(message = "Invalid CV URL"))]
|
#[validate(url(message = "Invalid CV URL"))]
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub cv_url: Option<String>,
|
pub cv_url: Option<String>,
|
||||||
#[validate(url(message = "Invalid portfolio URL"))]
|
#[validate(url(message = "Invalid portfolio URL"))]
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub portfolio_url: Option<String>,
|
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"))]
|
#[validate(length(min = 1, message = "Avatar is required"))]
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub avatar: Option<String>,
|
pub avatar: Option<String>,
|
||||||
pub role_id: 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>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
|
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||||
@@ -118,6 +167,12 @@ pub struct UsersDetailItemDto {
|
|||||||
pub github_url: Option<String>,
|
pub github_url: Option<String>,
|
||||||
pub cv_url: Option<String>,
|
pub cv_url: Option<String>,
|
||||||
pub portfolio_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 created_at: String,
|
pub created_at: String,
|
||||||
pub updated_at: String,
|
pub updated_at: String,
|
||||||
}
|
}
|
||||||
@@ -143,6 +198,12 @@ impl UsersDetailItemDto {
|
|||||||
github_url: dto.github_url.clone(),
|
github_url: dto.github_url.clone(),
|
||||||
cv_url: dto.cv_url.clone(),
|
cv_url: dto.cv_url.clone(),
|
||||||
portfolio_url: dto.portfolio_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(),
|
||||||
created_at: dto.created_at.clone(),
|
created_at: dto.created_at.clone(),
|
||||||
updated_at: dto.updated_at.clone(),
|
updated_at: dto.updated_at.clone(),
|
||||||
}
|
}
|
||||||
@@ -168,6 +229,12 @@ impl UsersDetailItemDto {
|
|||||||
github_url: schema.github_url.clone(),
|
github_url: schema.github_url.clone(),
|
||||||
cv_url: schema.cv_url.clone(),
|
cv_url: schema.cv_url.clone(),
|
||||||
portfolio_url: schema.portfolio_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(),
|
||||||
created_at: schema.created_at.clone(),
|
created_at: schema.created_at.clone(),
|
||||||
updated_at: schema.updated_at.clone(),
|
updated_at: schema.updated_at.clone(),
|
||||||
}
|
}
|
||||||
@@ -236,6 +303,12 @@ pub struct UsersDetailQueryDto {
|
|||||||
pub github_url: Option<String>,
|
pub github_url: Option<String>,
|
||||||
pub cv_url: Option<String>,
|
pub cv_url: Option<String>,
|
||||||
pub portfolio_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 password: String,
|
pub password: String,
|
||||||
pub role: RolesDetailQueryDto,
|
pub role: RolesDetailQueryDto,
|
||||||
pub created_at: String,
|
pub created_at: String,
|
||||||
@@ -264,6 +337,12 @@ impl UsersDetailQueryDto {
|
|||||||
github_url: self.github_url.clone(),
|
github_url: self.github_url.clone(),
|
||||||
cv_url: self.cv_url.clone(),
|
cv_url: self.cv_url.clone(),
|
||||||
portfolio_url: self.portfolio_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(),
|
||||||
is_deleted: self.is_deleted,
|
is_deleted: self.is_deleted,
|
||||||
password: self.password.clone(),
|
password: self.password.clone(),
|
||||||
birthdate: self.birthdate.clone(),
|
birthdate: self.birthdate.clone(),
|
||||||
@@ -294,6 +373,12 @@ impl From<&UsersDetailItemDto> for UsersDetailQueryDto {
|
|||||||
github_url: dto.github_url.clone(),
|
github_url: dto.github_url.clone(),
|
||||||
cv_url: dto.cv_url.clone(),
|
cv_url: dto.cv_url.clone(),
|
||||||
portfolio_url: dto.portfolio_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(),
|
||||||
password: String::new(),
|
password: String::new(),
|
||||||
role: RolesDetailQueryDto::default(),
|
role: RolesDetailQueryDto::default(),
|
||||||
created_at: dto.created_at.clone(),
|
created_at: dto.created_at.clone(),
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use super::{UsersCreateRequestDto, UsersDetailQueryDto, UsersUpdateRequestDto};
|
use super::{UsersCreateRequestDto, UsersDetailQueryDto, UsersUpdateRequestDto, ExperienceDto, EducationDto};
|
||||||
use imphnen_libs::{ResourceEnum, hash_password};
|
use imphnen_libs::{ResourceEnum, hash_password};
|
||||||
use imphnen_utils::extract_id;
|
use imphnen_utils::extract_id;
|
||||||
use imphnen_utils::{get_iso_date, make_thing};
|
use imphnen_utils::{get_iso_date, make_thing};
|
||||||
@@ -40,6 +40,18 @@ pub struct UsersSchema {
|
|||||||
pub cv_url: Option<String>,
|
pub cv_url: Option<String>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub portfolio_url: Option<String>,
|
pub portfolio_url: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub website_url: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub twitter_url: 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>>,
|
||||||
pub role: Thing,
|
pub role: Thing,
|
||||||
pub created_at: String,
|
pub created_at: String,
|
||||||
pub updated_at: String,
|
pub updated_at: String,
|
||||||
@@ -71,6 +83,12 @@ impl Default for UsersSchema {
|
|||||||
github_url: None,
|
github_url: None,
|
||||||
cv_url: None,
|
cv_url: None,
|
||||||
portfolio_url: None,
|
portfolio_url: None,
|
||||||
|
website_url: None,
|
||||||
|
twitter_url: None,
|
||||||
|
location: None,
|
||||||
|
skills: None,
|
||||||
|
experience: None,
|
||||||
|
education: None,
|
||||||
role: make_thing(
|
role: make_thing(
|
||||||
&ResourceEnum::Roles.to_string(),
|
&ResourceEnum::Roles.to_string(),
|
||||||
"5713cb37-dc02-4e87-8048-d7a41d352059",
|
"5713cb37-dc02-4e87-8048-d7a41d352059",
|
||||||
@@ -103,6 +121,12 @@ impl UsersSchema {
|
|||||||
github_url: dto.github_url,
|
github_url: dto.github_url,
|
||||||
cv_url: dto.cv_url,
|
cv_url: dto.cv_url,
|
||||||
portfolio_url: dto.portfolio_url,
|
portfolio_url: dto.portfolio_url,
|
||||||
|
website_url: dto.website_url,
|
||||||
|
twitter_url: dto.twitter_url,
|
||||||
|
location: dto.location,
|
||||||
|
skills: dto.skills,
|
||||||
|
experience: dto.experience,
|
||||||
|
education: dto.education,
|
||||||
password: dto.password,
|
password: dto.password,
|
||||||
created_at: dto.created_at,
|
created_at: dto.created_at,
|
||||||
updated_at: dto.updated_at,
|
updated_at: dto.updated_at,
|
||||||
@@ -110,32 +134,98 @@ impl UsersSchema {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(user: UsersUpdateRequestDto, id: String) -> Self {
|
pub fn update(_user: UsersUpdateRequestDto, id: String) -> Self {
|
||||||
Self {
|
Self {
|
||||||
id: make_thing(&ResourceEnum::Users.to_string(), &id),
|
id: make_thing(&ResourceEnum::Users.to_string(), &id),
|
||||||
fullname: user.fullname,
|
|
||||||
legal_name: user.legal_name,
|
|
||||||
email: user.email,
|
|
||||||
phone_number: user.phone_number,
|
|
||||||
phone_for_verification: user.phone_for_verification,
|
|
||||||
is_active: user.is_active,
|
|
||||||
gender: user.gender,
|
|
||||||
birthdate: user.birthdate,
|
|
||||||
domicile: user.domicile,
|
|
||||||
bio: user.bio,
|
|
||||||
last_education: user.last_education,
|
|
||||||
linkedin_url: user.linkedin_url,
|
|
||||||
github_url: user.github_url,
|
|
||||||
cv_url: user.cv_url,
|
|
||||||
portfolio_url: user.portfolio_url,
|
|
||||||
avatar: user.avatar,
|
|
||||||
is_deleted: false,
|
|
||||||
role: make_thing(&ResourceEnum::Roles.to_string(), &user.role_id),
|
|
||||||
updated_at: get_iso_date(),
|
updated_at: get_iso_date(),
|
||||||
|
// Set defaults for required fields - these should be overridden by actual data from DB
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn partial_update(current_user: UsersDetailQueryDto, user: UsersUpdateRequestDto) -> Self {
|
||||||
|
let mut schema = Self::from(current_user);
|
||||||
|
schema.updated_at = get_iso_date();
|
||||||
|
|
||||||
|
// Only update fields that are provided (Some)
|
||||||
|
if let Some(fullname) = user.fullname {
|
||||||
|
schema.fullname = fullname;
|
||||||
|
}
|
||||||
|
if let Some(email) = user.email {
|
||||||
|
schema.email = email;
|
||||||
|
}
|
||||||
|
if let Some(password) = user.password {
|
||||||
|
schema.password = hash_password(&password).unwrap_or_else(|_| password);
|
||||||
|
}
|
||||||
|
if let Some(phone_number) = user.phone_number {
|
||||||
|
schema.phone_number = phone_number;
|
||||||
|
}
|
||||||
|
if let Some(is_active) = user.is_active {
|
||||||
|
schema.is_active = is_active;
|
||||||
|
}
|
||||||
|
if let Some(role_id) = user.role_id {
|
||||||
|
schema.role = make_thing(&ResourceEnum::Roles.to_string(), &role_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional fields - only update if provided
|
||||||
|
if let Some(legal_name) = user.legal_name {
|
||||||
|
schema.legal_name = Some(legal_name);
|
||||||
|
}
|
||||||
|
if let Some(phone_for_verification) = user.phone_for_verification {
|
||||||
|
schema.phone_for_verification = Some(phone_for_verification);
|
||||||
|
}
|
||||||
|
if let Some(gender) = user.gender {
|
||||||
|
schema.gender = Some(gender);
|
||||||
|
}
|
||||||
|
if let Some(birthdate) = user.birthdate {
|
||||||
|
schema.birthdate = Some(birthdate);
|
||||||
|
}
|
||||||
|
if let Some(domicile) = user.domicile {
|
||||||
|
schema.domicile = Some(domicile);
|
||||||
|
}
|
||||||
|
if let Some(bio) = user.bio {
|
||||||
|
schema.bio = Some(bio);
|
||||||
|
}
|
||||||
|
if let Some(last_education) = user.last_education {
|
||||||
|
schema.last_education = Some(last_education);
|
||||||
|
}
|
||||||
|
if let Some(linkedin_url) = user.linkedin_url {
|
||||||
|
schema.linkedin_url = Some(linkedin_url);
|
||||||
|
}
|
||||||
|
if let Some(github_url) = user.github_url {
|
||||||
|
schema.github_url = Some(github_url);
|
||||||
|
}
|
||||||
|
if let Some(cv_url) = user.cv_url {
|
||||||
|
schema.cv_url = Some(cv_url);
|
||||||
|
}
|
||||||
|
if let Some(portfolio_url) = user.portfolio_url {
|
||||||
|
schema.portfolio_url = Some(portfolio_url);
|
||||||
|
}
|
||||||
|
if let Some(website_url) = user.website_url {
|
||||||
|
schema.website_url = Some(website_url);
|
||||||
|
}
|
||||||
|
if let Some(twitter_url) = user.twitter_url {
|
||||||
|
schema.twitter_url = Some(twitter_url);
|
||||||
|
}
|
||||||
|
if let Some(location) = user.location {
|
||||||
|
schema.location = Some(location);
|
||||||
|
}
|
||||||
|
if let Some(skills) = user.skills {
|
||||||
|
schema.skills = Some(skills);
|
||||||
|
}
|
||||||
|
if let Some(experience) = user.experience {
|
||||||
|
schema.experience = Some(experience);
|
||||||
|
}
|
||||||
|
if let Some(education) = user.education {
|
||||||
|
schema.education = Some(education);
|
||||||
|
}
|
||||||
|
if let Some(avatar) = user.avatar {
|
||||||
|
schema.avatar = Some(avatar);
|
||||||
|
}
|
||||||
|
|
||||||
|
schema
|
||||||
|
}
|
||||||
|
|
||||||
pub fn create(user: UsersCreateRequestDto) -> Self {
|
pub fn create(user: UsersCreateRequestDto) -> Self {
|
||||||
let password = hash_password(&user.password).unwrap();
|
let password = hash_password(&user.password).unwrap();
|
||||||
Self {
|
Self {
|
||||||
@@ -160,6 +250,12 @@ impl UsersSchema {
|
|||||||
github_url: None,
|
github_url: None,
|
||||||
cv_url: None,
|
cv_url: None,
|
||||||
portfolio_url: None,
|
portfolio_url: None,
|
||||||
|
website_url: None,
|
||||||
|
twitter_url: None,
|
||||||
|
location: None,
|
||||||
|
skills: None,
|
||||||
|
experience: None,
|
||||||
|
education: None,
|
||||||
avatar: user.avatar,
|
avatar: user.avatar,
|
||||||
is_deleted: false,
|
is_deleted: false,
|
||||||
role: make_thing(&ResourceEnum::Roles.to_string(), &user.role_id),
|
role: make_thing(&ResourceEnum::Roles.to_string(), &user.role_id),
|
||||||
|
|||||||
@@ -130,7 +130,15 @@ impl UsersServiceTrait for UsersService {
|
|||||||
if let Err((status, message)) = validate_request(&user) {
|
if let Err((status, message)) = validate_request(&user) {
|
||||||
return common_response(status, &message);
|
return common_response(status, &message);
|
||||||
}
|
}
|
||||||
let updated_user = UsersSchema::update(user, id);
|
|
||||||
|
// Get current user data first
|
||||||
|
let thing_id = make_thing(&ResourceEnum::Users.to_string(), &id);
|
||||||
|
let current_user = match repo.query_user_by_id(&thing_id).await {
|
||||||
|
Ok(user) => user,
|
||||||
|
Err(_) => return common_response(StatusCode::NOT_FOUND, "User not found"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let updated_user = UsersSchema::partial_update(current_user, user);
|
||||||
match repo.query_update_user(updated_user).await {
|
match repo.query_update_user(updated_user).await {
|
||||||
Ok(msg) => common_response(StatusCode::OK, &msg),
|
Ok(msg) => common_response(StatusCode::OK, &msg),
|
||||||
Err(e) => common_response(StatusCode::BAD_REQUEST, &e.to_string()),
|
Err(e) => common_response(StatusCode::BAD_REQUEST, &e.to_string()),
|
||||||
@@ -160,10 +168,12 @@ impl UsersServiceTrait for UsersService {
|
|||||||
Ok(user) => user,
|
Ok(user) => user,
|
||||||
Err(_) => return common_response(StatusCode::NOT_FOUND, "User not found"),
|
Err(_) => return common_response(StatusCode::NOT_FOUND, "User not found"),
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err((status, message)) = validate_request(&user) {
|
if let Err((status, message)) = validate_request(&user) {
|
||||||
return common_response(status, &message);
|
return common_response(status, &message);
|
||||||
}
|
}
|
||||||
let updated_user = UsersSchema::update(user, user_data.id.id.to_raw());
|
|
||||||
|
let updated_user = UsersSchema::partial_update(user_data, user);
|
||||||
match repo.query_update_user(updated_user).await {
|
match repo.query_update_user(updated_user).await {
|
||||||
Ok(msg) => common_response(StatusCode::OK, &msg),
|
Ok(msg) => common_response(StatusCode::OK, &msg),
|
||||||
Err(e) => common_response(StatusCode::BAD_REQUEST, &e.to_string()),
|
Err(e) => common_response(StatusCode::BAD_REQUEST, &e.to_string()),
|
||||||
|
|||||||
@@ -37,6 +37,12 @@ pub fn create_test_user(
|
|||||||
github_url: None,
|
github_url: None,
|
||||||
cv_url: None,
|
cv_url: None,
|
||||||
portfolio_url: None,
|
portfolio_url: None,
|
||||||
|
website_url: None,
|
||||||
|
twitter_url: None,
|
||||||
|
location: None,
|
||||||
|
skills: None,
|
||||||
|
experience: None,
|
||||||
|
education: None,
|
||||||
role: make_thing("app_roles", role_id),
|
role: make_thing("app_roles", role_id),
|
||||||
created_at: get_iso_date(),
|
created_at: get_iso_date(),
|
||||||
updated_at: get_iso_date(),
|
updated_at: get_iso_date(),
|
||||||
|
|||||||
@@ -167,6 +167,12 @@ pub async fn seed_users_for_test(
|
|||||||
github_url: None,
|
github_url: None,
|
||||||
cv_url: None,
|
cv_url: None,
|
||||||
portfolio_url: None,
|
portfolio_url: None,
|
||||||
|
website_url: None,
|
||||||
|
twitter_url: None,
|
||||||
|
location: None,
|
||||||
|
skills: None,
|
||||||
|
experience: None,
|
||||||
|
education: None,
|
||||||
role: Thing::from(("app_roles", role_id)),
|
role: Thing::from(("app_roles", role_id)),
|
||||||
created_at: get_iso_date(),
|
created_at: get_iso_date(),
|
||||||
updated_at: get_iso_date(),
|
updated_at: get_iso_date(),
|
||||||
|
|||||||
Reference in New Issue
Block a user