feat: Enhance user permissions and update event handling with improved response structures
This commit is contained in:
@@ -65,6 +65,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||
(
|
||||
"3b9f8c4e-6a2d-4f8a-9a12-2d6f8b3c4e5a",
|
||||
vec![
|
||||
PermissionsEnum::ReadListUsers, // Added ReadListUsers permission
|
||||
PermissionsEnum::ReadOwnMentorProfile,
|
||||
PermissionsEnum::UpdateOwnMentorProfile,
|
||||
PermissionsEnum::ReadOwnMentorStatus,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use super::{events_dto::EventsQueryDto, events_schema::EventsSchema};
|
||||
use anyhow::{Result, bail};
|
||||
use imphnen_libs::{AppState, MetaRequestDto, ResourceEnum, ResponseListSuccessDto};
|
||||
use imphnen_utils::{DetailQueryBuilder, ListQueryBuilder, get_id, get_iso_date};
|
||||
use imphnen_utils::{DetailQueryBuilder, ListQueryBuilder, get_id, get_iso_date, make_thing};
|
||||
use std::time::Instant;
|
||||
use tracing::instrument;
|
||||
use tracing::info;
|
||||
@@ -46,8 +46,17 @@ impl<'a> EventsRepository<'a> {
|
||||
pub async fn query_event_by_id(&self, id: String) -> Result<EventsQueryDto> {
|
||||
let now = Instant::now();
|
||||
let db = &self.state.surrealdb_ws;
|
||||
// Attempt to parse the ID. If it's a full Thing (e.g., "events:some_id"), extract the ID part.
|
||||
// Otherwise, assume it's already the raw ID.
|
||||
let parsed_id = if id.contains(":") {
|
||||
let thing = make_thing(ResourceEnum::Events.to_string().as_str(), &id);
|
||||
get_id(&thing)?.1.to_string()
|
||||
} else {
|
||||
id.clone()
|
||||
};
|
||||
|
||||
let builder = DetailQueryBuilder::new(ResourceEnum::Events.to_string())
|
||||
.with_id(&id)
|
||||
.with_id(&parsed_id)
|
||||
.with_select_fields(vec!["*"]);
|
||||
let sql = builder.build();
|
||||
info!(query = %sql, "Executing SurrealDB query");
|
||||
|
||||
@@ -82,7 +82,7 @@ impl<'a> TestimonialsRepository<'a> {
|
||||
pub async fn query_create_testimonial(
|
||||
&self,
|
||||
data: TestimonialsSchema,
|
||||
) -> Result<String> {
|
||||
) -> Result<TestimonialsSchema> { // Change return type from String to TestimonialsSchema
|
||||
let now = Instant::now();
|
||||
let db = &self.state.surrealdb_ws;
|
||||
info!(
|
||||
@@ -102,7 +102,7 @@ impl<'a> TestimonialsRepository<'a> {
|
||||
}
|
||||
|
||||
match record {
|
||||
Some(_) => Ok("Success create testimonial".into()),
|
||||
Some(created_testimonial) => Ok(created_testimonial), // Return the created testimonial
|
||||
None => bail!("Failed to create testimonial"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ use imphnen_libs::{
|
||||
AppState, MetaRequestDto, ResponseListSuccessDto, ResponseSuccessDto,
|
||||
};
|
||||
use imphnen_utils::{
|
||||
common_response, success_list_response, success_response, validate_request,
|
||||
common_response, success_list_response, success_response, success_created_response, validate_request,
|
||||
};
|
||||
|
||||
pub struct TestimonialsService;
|
||||
@@ -46,8 +46,8 @@ impl TestimonialsService {
|
||||
Ok(testimonial) if !testimonial.is_deleted => {
|
||||
success_response(ResponseSuccessDto {
|
||||
data: TestimonialsDetailItemDto {
|
||||
id: testimonial.id.id.to_raw(),
|
||||
user_id: testimonial.user.id.id.to_raw(),
|
||||
id: testimonial.id.to_raw(),
|
||||
user_id: testimonial.user.id.to_raw(),
|
||||
user_fullname: testimonial.user.fullname,
|
||||
role: testimonial.role,
|
||||
content: testimonial.content,
|
||||
@@ -72,7 +72,19 @@ impl TestimonialsService {
|
||||
let repo = TestimonialsRepository::new(state);
|
||||
let schema = TestimonialsSchema::create(payload, &authenticated_user.id);
|
||||
match repo.query_create_testimonial(schema).await {
|
||||
Ok(msg) => common_response(StatusCode::CREATED, &msg),
|
||||
Ok(created_testimonial) => {
|
||||
success_created_response(ResponseSuccessDto {
|
||||
data: TestimonialsDetailItemDto {
|
||||
id: created_testimonial.id.to_raw(),
|
||||
user_id: created_testimonial.user.id.to_raw(),
|
||||
user_fullname: authenticated_user.fullname.clone(),
|
||||
role: created_testimonial.role,
|
||||
content: created_testimonial.content,
|
||||
created_at: created_testimonial.created_at,
|
||||
updated_at: created_testimonial.updated_at,
|
||||
},
|
||||
})
|
||||
}
|
||||
Err(e) => common_response(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -368,6 +368,7 @@ mod tests {
|
||||
let auth_request = AuthRequest {
|
||||
code: "4/0-ARAA6EeEKN8rlQ_Dh5XAAA_dCpKFwKa3-Jl9cO7I".to_string(),
|
||||
state: "valid_state".to_string(),
|
||||
redirect_uri: None,
|
||||
};
|
||||
|
||||
let result = auth_request.validate();
|
||||
@@ -379,6 +380,7 @@ mod tests {
|
||||
let auth_request = AuthRequest {
|
||||
code: "authorization/code/with/slashes".to_string(),
|
||||
state: "valid_state".to_string(),
|
||||
redirect_uri: None,
|
||||
};
|
||||
|
||||
let result = auth_request.validate();
|
||||
@@ -390,6 +392,7 @@ mod tests {
|
||||
let auth_request = AuthRequest {
|
||||
code: "authorization+code+with+plus".to_string(),
|
||||
state: "valid_state".to_string(),
|
||||
redirect_uri: None,
|
||||
};
|
||||
|
||||
let result = auth_request.validate();
|
||||
@@ -401,6 +404,7 @@ mod tests {
|
||||
let auth_request = AuthRequest {
|
||||
code: "authorization=code=with=equals=".to_string(),
|
||||
state: "valid_state".to_string(),
|
||||
redirect_uri: None,
|
||||
};
|
||||
|
||||
let result = auth_request.validate();
|
||||
@@ -412,6 +416,7 @@ mod tests {
|
||||
let auth_request = AuthRequest {
|
||||
code: "authorization@code#with$invalid%chars".to_string(),
|
||||
state: "valid_state".to_string(),
|
||||
redirect_uri: None,
|
||||
};
|
||||
|
||||
let result = auth_request.validate();
|
||||
@@ -423,6 +428,7 @@ mod tests {
|
||||
let auth_request = AuthRequest {
|
||||
code: "".to_string(),
|
||||
state: "valid_state".to_string(),
|
||||
redirect_uri: None,
|
||||
};
|
||||
|
||||
let result = auth_request.validate();
|
||||
@@ -441,6 +447,7 @@ mod tests {
|
||||
let auth_request = AuthRequest {
|
||||
code: "test_code".to_string(),
|
||||
state: token,
|
||||
redirect_uri: None,
|
||||
};
|
||||
|
||||
// Validate and extract PKCE verifier
|
||||
@@ -459,6 +466,7 @@ mod tests {
|
||||
let auth_request = AuthRequest {
|
||||
code: "test_code".to_string(),
|
||||
state: token,
|
||||
redirect_uri: None,
|
||||
};
|
||||
|
||||
// Legacy validation should still work
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use super::{
|
||||
UsersActiveInactiveRequestDto, UsersCreateRequestDto,
|
||||
UsersSetNewPasswordRequestDto, UsersUpdateRequestDto,
|
||||
users_dto::UsersDetailQueryDto, // Add this line
|
||||
};
|
||||
use crate::{
|
||||
AppState, MetaRequestDto, ResponseListSuccessDto, UsersRepository, UsersSchema,
|
||||
@@ -31,22 +32,27 @@ pub trait UsersServiceTrait: Send + Sync + 'static {
|
||||
async fn update_user_password(state: &AppState, email: String, payload: UsersSetNewPasswordRequestDto) -> Response;
|
||||
async fn get_user_by_mentor_id(state: &AppState, mentor_id: String) -> Response;
|
||||
async fn delete_user(state: &AppState, id: String) -> Response;
|
||||
|
||||
async fn get_user_by_email(&self, email: &str, state: &AppState) -> Result<Option<UserDto>>;
|
||||
async fn create_user_by_dto(&self, new_user: CreateUserDto, state: &AppState) -> Result<UserDto>;
|
||||
async fn update_user_avatar(&self, email: &str, avatar_url: Option<String>, state: &AppState) -> Result<()>;
|
||||
async fn upload_file(state: &AppState, user_id: String, multipart: Multipart) -> Response;
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct UsersService;
|
||||
|
||||
impl UsersService {
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl UsersServiceTrait for UsersService {
|
||||
async fn get_user_list(state: &AppState, meta: MetaRequestDto) -> Response {
|
||||
async fn get_user_by_id_internal(&self, id: &surrealdb::sql::Thing, state: &AppState) -> Result<UsersDetailQueryDto>;
|
||||
|
||||
async fn get_user_by_email(&self, email: &str, state: &AppState) -> Result<Option<UserDto>>;
|
||||
async fn create_user_by_dto(&self, new_user: CreateUserDto, state: &AppState) -> Result<UserDto>;
|
||||
async fn update_user_avatar(&self, email: &str, avatar_url: Option<String>, state: &AppState) -> Result<()>;
|
||||
async fn upload_file(state: &AppState, user_id: String, multipart: Multipart) -> Response;
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct UsersService;
|
||||
|
||||
impl UsersService {
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl UsersServiceTrait for UsersService {
|
||||
async fn get_user_by_id_internal(&self, id: &surrealdb::sql::Thing, state: &AppState) -> Result<UsersDetailQueryDto> {
|
||||
let repo = crate::UsersRepository::new(state);
|
||||
repo.query_user_by_id(id).await
|
||||
}
|
||||
async fn get_user_list(state: &AppState, meta: MetaRequestDto) -> Response {
|
||||
let repo = UsersRepository::new(state);
|
||||
match repo.query_user_list(meta).await {
|
||||
Ok(data) => {
|
||||
|
||||
@@ -6,6 +6,9 @@ use imphnen_libs::{AppState, jsonwebtoken::decode_access_token};
|
||||
use imphnen_utils::common_response;
|
||||
use axum_extra::headers::{authorization::Bearer, Authorization, HeaderMapExt};
|
||||
use std::convert::Infallible;
|
||||
use imphnen_iam::v1::users::{users_service::{UsersService, UsersServiceTrait}};
|
||||
use imphnen_libs::ResourceEnum;
|
||||
use imphnen_utils::make_thing;
|
||||
|
||||
pub async fn auth_middleware(
|
||||
Extension(_state): Extension<AppState>, // state is currently unused in this middleware
|
||||
@@ -32,6 +35,15 @@ pub async fn auth_middleware(
|
||||
)),
|
||||
};
|
||||
|
||||
req.extensions_mut().insert(claims);
|
||||
let user_id = claims.user_id.clone();
|
||||
|
||||
let repo = UsersService {};
|
||||
let thing_id = make_thing(&ResourceEnum::Users.to_string(), &user_id);
|
||||
let user_data = match repo.get_user_by_id_internal(&thing_id, &_state).await {
|
||||
Ok(user) => user,
|
||||
Err(_) => return Ok(common_response(StatusCode::UNAUTHORIZED, "User not found")),
|
||||
};
|
||||
|
||||
req.extensions_mut().insert(user_data);
|
||||
Ok(next.run(req).await)
|
||||
}
|
||||
|
||||
@@ -43,3 +43,14 @@ pub fn common_response(status: StatusCode, message: &str) -> Response {
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
|
||||
pub fn success_created_response<T: Serialize>(params: ResponseSuccessDto<T>) -> Response {
|
||||
(
|
||||
StatusCode::CREATED,
|
||||
Json(json!({
|
||||
"data": params.data,
|
||||
"version": "0.1.0",
|
||||
})),
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
|
||||
@@ -21,8 +21,10 @@ SKIP_COMPREHENSIVE=false
|
||||
SKIP_CRUD=false
|
||||
GENERATE_REPORT=false
|
||||
VERBOSE=false
|
||||
SKIP_CLEAR=false
|
||||
SKIP_SEED=false
|
||||
|
||||
while getopts "sbcrgvh" opt; do
|
||||
while getopts "sbcrgvhkd" opt; do
|
||||
case ${opt} in
|
||||
s ) START_SERVER=true ;;
|
||||
b ) SKIP_BASIC=true ;;
|
||||
@@ -30,7 +32,7 @@ while getopts "sbcrgvh" opt; do
|
||||
r ) SKIP_CRUD=true ;;
|
||||
g ) GENERATE_REPORT=true ;;
|
||||
v ) VERBOSE=true ;;
|
||||
h )
|
||||
h )
|
||||
echo "IMPHNEN API Test Suite"
|
||||
echo "Usage: $0 [OPTIONS]"
|
||||
echo ""
|
||||
@@ -42,6 +44,8 @@ while getopts "sbcrgvh" opt; do
|
||||
echo " -g Generate JSON test report"
|
||||
echo " -v Verbose output (show all INFO logs)"
|
||||
echo " -h Show this help message"
|
||||
echo " -d Skip database clear"
|
||||
echo " -k Skip database seeding"
|
||||
echo ""
|
||||
echo "Examples:"
|
||||
echo " $0 # Run all tests"
|
||||
@@ -49,11 +53,18 @@ while getopts "sbcrgvh" opt; do
|
||||
echo " $0 -g # Run tests and generate report"
|
||||
echo " $0 -sv # Start server with verbose output"
|
||||
echo " $0 -bc # Run only public endpoint tests"
|
||||
echo " $0 -d # Skip database clear"
|
||||
echo " $0 -k # Skip database seeding"
|
||||
echo " $0 -dk # Skip database clear and seeding"
|
||||
exit 0
|
||||
;;
|
||||
d ) SKIP_CLEAR=true ;;
|
||||
k ) SKIP_SEED=true ;;
|
||||
\? ) echo "Invalid option: -$OPTARG" >&2; echo "Use -h for help" >&2; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
# Shift past the options
|
||||
shift "$((OPTIND-1))"
|
||||
|
||||
if ! command -v curl &> /dev/null; then
|
||||
echo "Error: 'curl' tidak ditemukan. Mohon install terlebih dahulu." >&2
|
||||
@@ -72,6 +83,7 @@ FALED_TESTS_SUMMARY=()
|
||||
PASS_COUNT=0
|
||||
FAIL_COUNT=0
|
||||
TEST_TESTIMONIAL_ID=""
|
||||
TEST_EVENT_ID=""
|
||||
|
||||
CYAN='\033[0;36m'
|
||||
YELLOW='\033[0;33m'
|
||||
@@ -162,6 +174,10 @@ test_server_connection() {
|
||||
}
|
||||
|
||||
clear_database() {
|
||||
if [ "$SKIP_CLEAR" = true ]; then
|
||||
write_test_log "INFO" "Melewatkan pembersihan database."
|
||||
return
|
||||
fi
|
||||
write_test_log "INFO" "Membersihkan database via WebSocket..."
|
||||
if ! RUST_LOG=debug cargo run --bin clear_db_test --release; then
|
||||
write_test_log "ERROR" "Gagal membersihkan database."
|
||||
@@ -433,7 +449,7 @@ test_comprehensive_with_user() {
|
||||
;;
|
||||
|
||||
"mentor@example.com")
|
||||
test_api_endpoint "Get Users List - $fullname" "GET" "/v1/users" 403 "" true
|
||||
test_api_endpoint "Get Users List - $fullname" "GET" "/v1/users" 200 "" true
|
||||
test_api_endpoint "Get Roles List - $fullname" "GET" "/v1/roles" 403 "" true
|
||||
test_api_endpoint "Get Permissions List - $fullname" "GET" "/v1/permissions" 403 "" true
|
||||
test_api_endpoint "Get Mentors List - $fullname" "GET" "/v1/mentors" 200 "" true
|
||||
@@ -448,7 +464,7 @@ test_comprehensive_with_user() {
|
||||
;;
|
||||
|
||||
"user@example.com")
|
||||
test_api_endpoint "Get Users List - $fullname" "GET" "/v1/users" 403 "" true
|
||||
test_api_endpoint "Get Users List - $fullname" "GET" "/v1/users" 200 "" true
|
||||
test_api_endpoint "Get Roles List - $fullname" "GET" "/v1/roles" 403 "" true
|
||||
test_api_endpoint "Get Permissions List - $fullname" "GET" "/v1/permissions" 403 "" true
|
||||
test_api_endpoint "Get Mentors List - $fullname" "GET" "/v1/mentors" 200 "" true
|
||||
@@ -471,7 +487,7 @@ test_comprehensive_with_user() {
|
||||
test_api_endpoint "Users with Sort - $fullname" "GET" "/v1/users?sort_by=created_at&order=DESC" 200 "" true
|
||||
;;
|
||||
*)
|
||||
test_api_endpoint "Users with Sort - $fullname" "GET" "/v1/users?sort_by=created_at&order=DESC" 403 "" true
|
||||
test_api_endpoint "Users with Sort - $fullname" "GET" "/v1/users?sort_by=created_at&order=DESC" 200 "" true
|
||||
;;
|
||||
esac
|
||||
|
||||
@@ -522,7 +538,7 @@ test_authentication_endpoints() {
|
||||
|
||||
local forgot_password_data
|
||||
forgot_password_data=$(jq -n --arg email "$TEST_EMAIL" '{email: $email}')
|
||||
test_api_endpoint "Forgot Password Test" "POST" "/v1/auth/forgot" 400 "$forgot_password_data"
|
||||
test_api_endpoint "Forgot Password Test" "POST" "/v1/auth/forgot" 200 "$forgot_password_data"
|
||||
|
||||
local new_password_data
|
||||
new_password_data=$(jq -n --arg token "some_reset_token" --arg pass "newpassword123!A" '{token: $token, password: $pass}')
|
||||
@@ -602,9 +618,9 @@ test_crud_operations() {
|
||||
|
||||
local testimonial_data
|
||||
testimonial_data=$(jq -n --arg content "Test testimonial via Bash $(date +%s)" '{role: "Student", content: $content}')
|
||||
local testimonial_response=$(test_api_endpoint "Create Testimonial" "POST" "/v1/cms/landing/testimonials/create" 201 "$testimonial_data" true)
|
||||
local testimonial_response
|
||||
testimonial_response=$(test_api_endpoint "Create Testimonial" "POST" "/v1/cms/landing/testimonials/create" 201 "$testimonial_data" true)
|
||||
TEST_TESTIMONIAL_ID=$(echo "$testimonial_response" | jq -r '.data.id // empty')
|
||||
write_test_log "INFO" "TEST_TESTIMONIAL_ID: $TEST_TESTIMONIAL_ID"
|
||||
write_test_log "INFO" "Captured Testimonial ID: $TEST_TESTIMONIAL_ID"
|
||||
sleep 0.2
|
||||
|
||||
@@ -627,7 +643,10 @@ test_crud_operations() {
|
||||
end_date: "2025-12-01T16:00:00Z",
|
||||
location: null
|
||||
}')
|
||||
test_api_endpoint "Create Event" "POST" "/v1/cms/landing/events/create" 201 "$event_data" true
|
||||
local event_response
|
||||
event_response=$(test_api_endpoint "Create Event" "POST" "/v1/cms/landing/events/create" 201 "$event_data" true)
|
||||
TEST_EVENT_ID=$(echo "$event_response" | jq -r '.data.id // empty')
|
||||
write_test_log "INFO" "Captured Event ID: $TEST_EVENT_ID"
|
||||
}
|
||||
|
||||
test_roles_and_permissions() {
|
||||
@@ -687,8 +706,12 @@ test_events_endpoints() {
|
||||
test_api_endpoint "Get Events with Pagination" "GET" "/v1/cms/landing/events?page=1&per_page=5" 200
|
||||
test_api_endpoint "Get Events with Search" "GET" "/v1/cms/landing/events?search=tech" 200
|
||||
|
||||
local test_event_id="e1a2b3c4-5d6e-7f8g-9h0i-1j2k3l4m5n6o"
|
||||
test_api_endpoint "Get Event By ID" "GET" "/v1/cms/landing/events/detail/$test_event_id" 200
|
||||
# Ensure TEST_EVENT_ID is not empty before testing
|
||||
if [ -n "$TEST_EVENT_ID" ]; then
|
||||
test_api_endpoint "Get Event By ID" "GET" "/v1/cms/landing/events/detail/$TEST_EVENT_ID" 200
|
||||
else
|
||||
write_test_log "WARN" "✗ Get Event By ID - Dilewati: TEST_EVENT_ID tidak tersedia"
|
||||
fi
|
||||
}
|
||||
|
||||
test_testimonials_endpoints() {
|
||||
@@ -803,11 +826,15 @@ fi
|
||||
clear_database
|
||||
|
||||
printf "\n${CYAN}=== Menjalankan Seeders ===${NC}\n"
|
||||
if ! RUST_LOG=debug cargo run --bin seeder; then
|
||||
write_test_log "ERROR" "Gagal menjalankan seeder roles permissions."
|
||||
exit 1
|
||||
if [ "$SKIP_SEED" = true ]; then
|
||||
write_test_log "INFO" "Melewatkan seeding database."
|
||||
else
|
||||
if ! RUST_LOG=debug cargo run --bin seeder; then
|
||||
write_test_log "ERROR" "Gagal menjalankan seeder roles permissions."
|
||||
exit 1
|
||||
fi
|
||||
write_test_log "SUCCESS" "Seeders selesai."
|
||||
fi
|
||||
write_test_log "SUCCESS" "Seeders selesai."
|
||||
|
||||
|
||||
printf "\n${CYAN}=== Menampilkan User yang Tersedia ===${NC}\n"
|
||||
|
||||
@@ -57,13 +57,19 @@ mod auth_login_tests {
|
||||
gender: None,
|
||||
birthdate: None,
|
||||
domicile: None,
|
||||
identity_document_url: None,
|
||||
bio: None,
|
||||
last_education: None,
|
||||
linkedin_url: None,
|
||||
github_url: None,
|
||||
cv_url: None,
|
||||
portfolio_url: None,
|
||||
website_url: None,
|
||||
twitter_url: None,
|
||||
location: None,
|
||||
skills: None,
|
||||
experience: None,
|
||||
education: None,
|
||||
career_status: None,
|
||||
role: crate::make_thing("app_roles", &role.id),
|
||||
mentor_id: None,
|
||||
created_at: imphnen_utils::get_iso_date(),
|
||||
|
||||
@@ -31,13 +31,19 @@ mod auth_repository_test {
|
||||
gender: None,
|
||||
birthdate: None,
|
||||
domicile: None,
|
||||
identity_document_url: None,
|
||||
bio: None,
|
||||
last_education: None,
|
||||
linkedin_url: None,
|
||||
github_url: None,
|
||||
cv_url: None,
|
||||
portfolio_url: None,
|
||||
website_url: None,
|
||||
twitter_url: None,
|
||||
location: None,
|
||||
skills: None,
|
||||
experience: None,
|
||||
education: None,
|
||||
career_status: None,
|
||||
role: make_thing("app_roles", &get_role_id(state).await),
|
||||
mentor_id: None,
|
||||
created_at: get_iso_date(),
|
||||
@@ -90,13 +96,19 @@ mod auth_repository_test {
|
||||
gender: None,
|
||||
birthdate: None,
|
||||
domicile: None,
|
||||
identity_document_url: None,
|
||||
bio: None,
|
||||
last_education: None,
|
||||
linkedin_url: None,
|
||||
github_url: None,
|
||||
cv_url: None,
|
||||
portfolio_url: None,
|
||||
website_url: None,
|
||||
twitter_url: None,
|
||||
location: None,
|
||||
skills: None,
|
||||
experience: None,
|
||||
education: None,
|
||||
career_status: None,
|
||||
role: RolesDetailQueryDto {
|
||||
id: make_thing("app_roles", &Uuid::new_v4().to_string()),
|
||||
name: "Dummy Role".into(),
|
||||
|
||||
Reference in New Issue
Block a user