feat: add admin APIs for jobs, applications, and all 9 professions
- Companies service: add GET /api/admin/jobs and GET /api/admin/applications - Gateway: route /api/admin/applications to companies; add routing for all 9 profession admin endpoints - For each profession service (photographers, makeup_artists, tutors, developers, video_editors, graphic_designers, social_media_managers, fitness_trainers, catering_services): - Create admin.rs with list and detail endpoints that join with users - Update main.rs to mount admin router under /api/admin/<profession> - Admin endpoints enable cross-platform visibility of all professionals by internal staff
This commit is contained in:
parent
13643ffb1b
commit
2c0c979c91
18 changed files with 664 additions and 9 deletions
79
apps/catering_services/src/admin.rs
Normal file
79
apps/catering_services/src/admin.rs
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
use crate::AppState;
|
||||
use axum::{extract::{Path, State}, http::StatusCode, response::IntoResponse, routing::get, Json, Router};
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::Serialize;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct AdminCateringServiceList {
|
||||
pub id: Uuid,
|
||||
pub user_id: Uuid,
|
||||
pub first_name: String,
|
||||
pub last_name: String,
|
||||
pub email: String,
|
||||
pub phone: Option<String>,
|
||||
pub status: String,
|
||||
pub bio: Option<String>,
|
||||
pub experience_years: Option<i32>,
|
||||
pub custom_data: serde_json::Value,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
type AdminCateringServiceDetail = AdminCateringServiceList;
|
||||
|
||||
pub fn router() -> Router<AppState> {
|
||||
Router::new()
|
||||
.route("/", get(list_catering_services))
|
||||
.route("/{id}", get(get_catering_service))
|
||||
}
|
||||
|
||||
async fn list_catering_services(
|
||||
State(state): State<AppState>,
|
||||
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
||||
let services = sqlx::query_as!(
|
||||
AdminCateringServiceList,
|
||||
r#"
|
||||
SELECT
|
||||
c.id, c.user_id, c.bio, c.experience_years, c.custom_data,
|
||||
c.created_at, c.updated_at,
|
||||
u.first_name, u.last_name, u.email, u.phone, u.status
|
||||
FROM catering_service_profiles c
|
||||
JOIN users u ON c.user_id = u.id
|
||||
ORDER BY c.created_at DESC
|
||||
LIMIT 100
|
||||
"#
|
||||
)
|
||||
.fetch_all(&state.pool)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
|
||||
|
||||
Ok(Json(services))
|
||||
}
|
||||
|
||||
async fn get_catering_service(
|
||||
State(state): State<AppState>,
|
||||
Path(id): Path<Uuid>,
|
||||
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
||||
let service = sqlx::query_as!(
|
||||
AdminCateringServiceDetail,
|
||||
r#"
|
||||
SELECT
|
||||
c.id, c.user_id, c.bio, c.experience_years, c.custom_data,
|
||||
c.created_at, c.updated_at,
|
||||
u.first_name, u.last_name, u.email, u.phone, u.status
|
||||
FROM catering_service_profiles c
|
||||
JOIN users u ON c.user_id = u.id
|
||||
WHERE c.id = $1
|
||||
"#,
|
||||
id
|
||||
)
|
||||
.fetch_optional(&state.pool)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
|
||||
|
||||
match service {
|
||||
Some(c) => Ok(Json(c)),
|
||||
None => Err((StatusCode::NOT_FOUND, "Catering service not found".to_string())),
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
mod handlers;
|
||||
mod admin;
|
||||
|
||||
use axum::{routing::get, Router};
|
||||
use std::net::SocketAddr;
|
||||
|
|
@ -33,6 +34,7 @@ async fn main() {
|
|||
|
||||
let app = Router::new()
|
||||
.nest("/api/catering-services", handlers::router())
|
||||
.nest("/api/admin/catering-services", admin::router())
|
||||
.route("/health", get(|| async { "Catering Services OK" }))
|
||||
.with_state(state);
|
||||
|
||||
|
|
|
|||
79
apps/developers/src/admin.rs
Normal file
79
apps/developers/src/admin.rs
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
use crate::AppState;
|
||||
use axum::{extract::{Path, State}, http::StatusCode, response::IntoResponse, routing::get, Json, Router};
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::Serialize;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct AdminDeveloperList {
|
||||
pub id: Uuid,
|
||||
pub user_id: Uuid,
|
||||
pub first_name: String,
|
||||
pub last_name: String,
|
||||
pub email: String,
|
||||
pub phone: Option<String>,
|
||||
pub status: String,
|
||||
pub bio: Option<String>,
|
||||
pub experience_years: Option<i32>,
|
||||
pub custom_data: serde_json::Value,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
type AdminDeveloperDetail = AdminDeveloperList;
|
||||
|
||||
pub fn router() -> Router<AppState> {
|
||||
Router::new()
|
||||
.route("/", get(list_developers))
|
||||
.route("/{id}", get(get_developer))
|
||||
}
|
||||
|
||||
async fn list_developers(
|
||||
State(state): State<AppState>,
|
||||
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
||||
let developers = sqlx::query_as!(
|
||||
AdminDeveloperList,
|
||||
r#"
|
||||
SELECT
|
||||
d.id, d.user_id, d.bio, d.experience_years, d.custom_data,
|
||||
d.created_at, d.updated_at,
|
||||
u.first_name, u.last_name, u.email, u.phone, u.status
|
||||
FROM developer_profiles d
|
||||
JOIN users u ON d.user_id = u.id
|
||||
ORDER BY d.created_at DESC
|
||||
LIMIT 100
|
||||
"#
|
||||
)
|
||||
.fetch_all(&state.pool)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
|
||||
|
||||
Ok(Json(developers))
|
||||
}
|
||||
|
||||
async fn get_developer(
|
||||
State(state): State<AppState>,
|
||||
Path(id): Path<Uuid>,
|
||||
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
||||
let developer = sqlx::query_as!(
|
||||
AdminDeveloperDetail,
|
||||
r#"
|
||||
SELECT
|
||||
d.id, d.user_id, d.bio, d.experience_years, d.custom_data,
|
||||
d.created_at, d.updated_at,
|
||||
u.first_name, u.last_name, u.email, u.phone, u.status
|
||||
FROM developer_profiles d
|
||||
JOIN users u ON d.user_id = u.id
|
||||
WHERE d.id = $1
|
||||
"#,
|
||||
id
|
||||
)
|
||||
.fetch_optional(&state.pool)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
|
||||
|
||||
match developer {
|
||||
Some(d) => Ok(Json(d)),
|
||||
None => Err((StatusCode::NOT_FOUND, "Developer not found".to_string())),
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
mod handlers;
|
||||
mod admin;
|
||||
|
||||
use axum::{routing::get, Router};
|
||||
use std::net::SocketAddr;
|
||||
|
|
@ -33,6 +34,7 @@ async fn main() {
|
|||
|
||||
let app = Router::new()
|
||||
.nest("/api/developers", handlers::router())
|
||||
.nest("/api/admin/developers", admin::router())
|
||||
.route("/health", get(|| async { "Developers OK" }))
|
||||
.with_state(state);
|
||||
|
||||
|
|
|
|||
79
apps/fitness_trainers/src/admin.rs
Normal file
79
apps/fitness_trainers/src/admin.rs
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
use crate::AppState;
|
||||
use axum::{extract::{Path, State}, http::StatusCode, response::IntoResponse, routing::get, Json, Router};
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::Serialize;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct AdminFitnessTrainerList {
|
||||
pub id: Uuid,
|
||||
pub user_id: Uuid,
|
||||
pub first_name: String,
|
||||
pub last_name: String,
|
||||
pub email: String,
|
||||
pub phone: Option<String>,
|
||||
pub status: String,
|
||||
pub bio: Option<String>,
|
||||
pub experience_years: Option<i32>,
|
||||
pub custom_data: serde_json::Value,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
type AdminFitnessTrainerDetail = AdminFitnessTrainerList;
|
||||
|
||||
pub fn router() -> Router<AppState> {
|
||||
Router::new()
|
||||
.route("/", get(list_fitness_trainers))
|
||||
.route("/{id}", get(get_fitness_trainer))
|
||||
}
|
||||
|
||||
async fn list_fitness_trainers(
|
||||
State(state): State<AppState>,
|
||||
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
||||
let trainers = sqlx::query_as!(
|
||||
AdminFitnessTrainerList,
|
||||
r#"
|
||||
SELECT
|
||||
f.id, f.user_id, f.bio, f.experience_years, f.custom_data,
|
||||
f.created_at, f.updated_at,
|
||||
u.first_name, u.last_name, u.email, u.phone, u.status
|
||||
FROM fitness_trainer_profiles f
|
||||
JOIN users u ON f.user_id = u.id
|
||||
ORDER BY f.created_at DESC
|
||||
LIMIT 100
|
||||
"#
|
||||
)
|
||||
.fetch_all(&state.pool)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
|
||||
|
||||
Ok(Json(trainers))
|
||||
}
|
||||
|
||||
async fn get_fitness_trainer(
|
||||
State(state): State<AppState>,
|
||||
Path(id): Path<Uuid>,
|
||||
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
||||
let trainer = sqlx::query_as!(
|
||||
AdminFitnessTrainerDetail,
|
||||
r#"
|
||||
SELECT
|
||||
f.id, f.user_id, f.bio, f.experience_years, f.custom_data,
|
||||
f.created_at, f.updated_at,
|
||||
u.first_name, u.last_name, u.email, u.phone, u.status
|
||||
FROM fitness_trainer_profiles f
|
||||
JOIN users u ON f.user_id = u.id
|
||||
WHERE f.id = $1
|
||||
"#,
|
||||
id
|
||||
)
|
||||
.fetch_optional(&state.pool)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
|
||||
|
||||
match trainer {
|
||||
Some(f) => Ok(Json(f)),
|
||||
None => Err((StatusCode::NOT_FOUND, "Fitness trainer not found".to_string())),
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
mod handlers;
|
||||
mod admin;
|
||||
|
||||
use axum::{routing::get, Router};
|
||||
use std::net::SocketAddr;
|
||||
|
|
@ -33,6 +34,7 @@ async fn main() {
|
|||
|
||||
let app = Router::new()
|
||||
.nest("/api/fitness-trainers", handlers::router())
|
||||
.nest("/api/admin/fitness-trainers", admin::router())
|
||||
.route("/health", get(|| async { "Fitness Trainers OK" }))
|
||||
.with_state(state);
|
||||
|
||||
|
|
|
|||
|
|
@ -116,6 +116,7 @@ impl Services {
|
|||
|| path.starts_with("/api/pricing")
|
||||
|| path.starts_with("/api/admin/companies")
|
||||
|| path.starts_with("/api/admin/jobs")
|
||||
|| path.starts_with("/api/admin/applications")
|
||||
{
|
||||
Some(self.companies_url.clone())
|
||||
}
|
||||
|
|
@ -131,31 +132,31 @@ impl Services {
|
|||
Some(self.customers_url.clone())
|
||||
}
|
||||
// ── 9 Separate Profession Services ────────────────────────────────
|
||||
else if path.starts_with("/api/photographers") {
|
||||
else if path.starts_with("/api/photographers") || path.starts_with("/api/admin/photographers") {
|
||||
Some(self.photographers_url.clone())
|
||||
}
|
||||
else if path.starts_with("/api/makeup-artists") {
|
||||
else if path.starts_with("/api/makeup-artists") || path.starts_with("/api/admin/makeup-artists") {
|
||||
Some(self.makeup_artists_url.clone())
|
||||
}
|
||||
else if path.starts_with("/api/tutors") {
|
||||
else if path.starts_with("/api/tutors") || path.starts_with("/api/admin/tutors") {
|
||||
Some(self.tutors_url.clone())
|
||||
}
|
||||
else if path.starts_with("/api/developers") {
|
||||
else if path.starts_with("/api/developers") || path.starts_with("/api/admin/developers") {
|
||||
Some(self.developers_url.clone())
|
||||
}
|
||||
else if path.starts_with("/api/video-editors") {
|
||||
else if path.starts_with("/api/video-editors") || path.starts_with("/api/admin/video-editors") {
|
||||
Some(self.video_editors_url.clone())
|
||||
}
|
||||
else if path.starts_with("/api/graphic-designers") {
|
||||
else if path.starts_with("/api/graphic-designers") || path.starts_with("/api/admin/graphic-designers") {
|
||||
Some(self.graphic_designers_url.clone())
|
||||
}
|
||||
else if path.starts_with("/api/social-media-managers") {
|
||||
else if path.starts_with("/api/social-media-managers") || path.starts_with("/api/admin/social-media-managers") {
|
||||
Some(self.social_media_managers_url.clone())
|
||||
}
|
||||
else if path.starts_with("/api/fitness-trainers") {
|
||||
else if path.starts_with("/api/fitness-trainers") || path.starts_with("/api/admin/fitness-trainers") {
|
||||
Some(self.fitness_trainers_url.clone())
|
||||
}
|
||||
else if path.starts_with("/api/catering-services") {
|
||||
else if path.starts_with("/api/catering-services") || path.starts_with("/api/admin/catering-services") {
|
||||
Some(self.catering_services_url.clone())
|
||||
}
|
||||
else if path.starts_with("/api/ugc-content-creators") {
|
||||
|
|
|
|||
79
apps/graphic_designers/src/admin.rs
Normal file
79
apps/graphic_designers/src/admin.rs
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
use crate::AppState;
|
||||
use axum::{extract::{Path, State}, http::StatusCode, response::IntoResponse, routing::get, Json, Router};
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::Serialize;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct AdminGraphicDesignerList {
|
||||
pub id: Uuid,
|
||||
pub user_id: Uuid,
|
||||
pub first_name: String,
|
||||
pub last_name: String,
|
||||
pub email: String,
|
||||
pub phone: Option<String>,
|
||||
pub status: String,
|
||||
pub bio: Option<String>,
|
||||
pub experience_years: Option<i32>,
|
||||
pub custom_data: serde_json::Value,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
type AdminGraphicDesignerDetail = AdminGraphicDesignerList;
|
||||
|
||||
pub fn router() -> Router<AppState> {
|
||||
Router::new()
|
||||
.route("/", get(list_graphic_designers))
|
||||
.route("/{id}", get(get_graphic_designer))
|
||||
}
|
||||
|
||||
async fn list_graphic_designers(
|
||||
State(state): State<AppState>,
|
||||
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
||||
let designers = sqlx::query_as!(
|
||||
AdminGraphicDesignerList,
|
||||
r#"
|
||||
SELECT
|
||||
g.id, g.user_id, g.bio, g.experience_years, g.custom_data,
|
||||
g.created_at, g.updated_at,
|
||||
u.first_name, u.last_name, u.email, u.phone, u.status
|
||||
FROM graphic_designer_profiles g
|
||||
JOIN users u ON g.user_id = u.id
|
||||
ORDER BY g.created_at DESC
|
||||
LIMIT 100
|
||||
"#
|
||||
)
|
||||
.fetch_all(&state.pool)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
|
||||
|
||||
Ok(Json(designers))
|
||||
}
|
||||
|
||||
async fn get_graphic_designer(
|
||||
State(state): State<AppState>,
|
||||
Path(id): Path<Uuid>,
|
||||
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
||||
let designer = sqlx::query_as!(
|
||||
AdminGraphicDesignerDetail,
|
||||
r#"
|
||||
SELECT
|
||||
g.id, g.user_id, g.bio, g.experience_years, g.custom_data,
|
||||
g.created_at, g.updated_at,
|
||||
u.first_name, u.last_name, u.email, u.phone, u.status
|
||||
FROM graphic_designer_profiles g
|
||||
JOIN users u ON g.user_id = u.id
|
||||
WHERE g.id = $1
|
||||
"#,
|
||||
id
|
||||
)
|
||||
.fetch_optional(&state.pool)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
|
||||
|
||||
match designer {
|
||||
Some(g) => Ok(Json(g)),
|
||||
None => Err((StatusCode::NOT_FOUND, "Graphic designer not found".to_string())),
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
mod handlers;
|
||||
mod admin;
|
||||
|
||||
use axum::{routing::get, Router};
|
||||
use std::net::SocketAddr;
|
||||
|
|
@ -33,6 +34,7 @@ async fn main() {
|
|||
|
||||
let app = Router::new()
|
||||
.nest("/api/graphic-designers", handlers::router())
|
||||
.nest("/api/admin/graphic-designers", admin::router())
|
||||
.route("/health", get(|| async { "Graphic Designers OK" }))
|
||||
.with_state(state);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
mod handlers;
|
||||
mod admin;
|
||||
|
||||
use axum::{routing::get, Router};
|
||||
use std::net::SocketAddr;
|
||||
|
|
@ -33,6 +34,7 @@ async fn main() {
|
|||
|
||||
let app = Router::new()
|
||||
.nest("/api/makeup-artists", handlers::router())
|
||||
.nest("/api/admin/makeup-artists", admin::router())
|
||||
.route("/health", get(|| async { "Makeup Artists OK" }))
|
||||
.with_state(state);
|
||||
|
||||
|
|
|
|||
81
apps/photographers/src/admin.rs
Normal file
81
apps/photographers/src/admin.rs
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
use crate::AppState;
|
||||
use axum::{extract::{Path, State}, http::StatusCode, response::IntoResponse, routing::get, Json, Router};
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::Serialize;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct AdminPhotographerList {
|
||||
pub id: Uuid,
|
||||
pub user_id: Uuid,
|
||||
pub first_name: String,
|
||||
pub last_name: String,
|
||||
pub email: String,
|
||||
pub phone: Option<String>,
|
||||
pub status: String,
|
||||
pub bio: Option<String>,
|
||||
pub location: Option<String>,
|
||||
pub years_experience: Option<i32>,
|
||||
pub avg_rating: Option<f64>,
|
||||
pub is_verified: Option<bool>,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
type AdminPhotographerDetail = AdminPhotographerList;
|
||||
|
||||
pub fn router() -> Router<AppState> {
|
||||
Router::new()
|
||||
.route("/", get(list_photographers))
|
||||
.route("/{id}", get(get_photographer))
|
||||
}
|
||||
|
||||
async fn list_photographers(
|
||||
State(state): State<AppState>,
|
||||
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
||||
let photographers = sqlx::query_as!(
|
||||
AdminPhotographerList,
|
||||
r#"
|
||||
SELECT
|
||||
p.id, p.user_id, p.bio, p.location, p.years_experience, p.avg_rating, p.is_verified,
|
||||
p.created_at, p.updated_at,
|
||||
u.first_name, u.last_name, u.email, u.phone, u.status
|
||||
FROM photographers p
|
||||
JOIN users u ON p.user_id = u.id
|
||||
ORDER BY p.created_at DESC
|
||||
LIMIT 100
|
||||
"#
|
||||
)
|
||||
.fetch_all(&state.pool)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
|
||||
|
||||
Ok(Json(photographers))
|
||||
}
|
||||
|
||||
async fn get_photographer(
|
||||
State(state): State<AppState>,
|
||||
Path(id): Path<Uuid>,
|
||||
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
||||
let photographer = sqlx::query_as!(
|
||||
AdminPhotographerDetail,
|
||||
r#"
|
||||
SELECT
|
||||
p.id, p.user_id, p.bio, p.location, p.years_experience, p.avg_rating, p.is_verified,
|
||||
p.created_at, p.updated_at,
|
||||
u.first_name, u.last_name, u.email, u.phone, u.status
|
||||
FROM photographers p
|
||||
JOIN users u ON p.user_id = u.id
|
||||
WHERE p.id = $1
|
||||
"#,
|
||||
id
|
||||
)
|
||||
.fetch_optional(&state.pool)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
|
||||
|
||||
match photographer {
|
||||
Some(p) => Ok(Json(p)),
|
||||
None => Err((StatusCode::NOT_FOUND, "Photographer not found".to_string())),
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
mod handlers;
|
||||
mod admin;
|
||||
|
||||
use axum::{routing::get, Router};
|
||||
use std::net::SocketAddr;
|
||||
|
|
@ -33,6 +34,7 @@ async fn main() {
|
|||
|
||||
let app = Router::new()
|
||||
.nest("/api/photographers", handlers::router())
|
||||
.nest("/api/admin/photographers", admin::router())
|
||||
.route("/health", get(|| async { "Photographers OK" }))
|
||||
.with_state(state);
|
||||
|
||||
|
|
|
|||
79
apps/social_media_managers/src/admin.rs
Normal file
79
apps/social_media_managers/src/admin.rs
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
use crate::AppState;
|
||||
use axum::{extract::{Path, State}, http::StatusCode, response::IntoResponse, routing::get, Json, Router};
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::Serialize;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct AdminSocialMediaManagerList {
|
||||
pub id: Uuid,
|
||||
pub user_id: Uuid,
|
||||
pub first_name: String,
|
||||
pub last_name: String,
|
||||
pub email: String,
|
||||
pub phone: Option<String>,
|
||||
pub status: String,
|
||||
pub bio: Option<String>,
|
||||
pub experience_years: Option<i32>,
|
||||
pub custom_data: serde_json::Value,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
type AdminSocialMediaManagerDetail = AdminSocialMediaManagerList;
|
||||
|
||||
pub fn router() -> Router<AppState> {
|
||||
Router::new()
|
||||
.route("/", get(list_social_media_managers))
|
||||
.route("/{id}", get(get_social_media_manager))
|
||||
}
|
||||
|
||||
async fn list_social_media_managers(
|
||||
State(state): State<AppState>,
|
||||
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
||||
let managers = sqlx::query_as!(
|
||||
AdminSocialMediaManagerList,
|
||||
r#"
|
||||
SELECT
|
||||
s.id, s.user_id, s.bio, s.experience_years, s.custom_data,
|
||||
s.created_at, s.updated_at,
|
||||
u.first_name, u.last_name, u.email, u.phone, u.status
|
||||
FROM social_media_manager_profiles s
|
||||
JOIN users u ON s.user_id = u.id
|
||||
ORDER BY s.created_at DESC
|
||||
LIMIT 100
|
||||
"#
|
||||
)
|
||||
.fetch_all(&state.pool)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
|
||||
|
||||
Ok(Json(managers))
|
||||
}
|
||||
|
||||
async fn get_social_media_manager(
|
||||
State(state): State<AppState>,
|
||||
Path(id): Path<Uuid>,
|
||||
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
||||
let manager = sqlx::query_as!(
|
||||
AdminSocialMediaManagerDetail,
|
||||
r#"
|
||||
SELECT
|
||||
s.id, s.user_id, s.bio, s.experience_years, s.custom_data,
|
||||
s.created_at, s.updated_at,
|
||||
u.first_name, u.last_name, u.email, u.phone, u.status
|
||||
FROM social_media_manager_profiles s
|
||||
JOIN users u ON s.user_id = u.id
|
||||
WHERE s.id = $1
|
||||
"#,
|
||||
id
|
||||
)
|
||||
.fetch_optional(&state.pool)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
|
||||
|
||||
match manager {
|
||||
Some(s) => Ok(Json(s)),
|
||||
None => Err((StatusCode::NOT_FOUND, "Social media manager not found".to_string())),
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
mod handlers;
|
||||
mod admin;
|
||||
|
||||
use axum::{routing::get, Router};
|
||||
use std::net::SocketAddr;
|
||||
|
|
@ -33,6 +34,7 @@ async fn main() {
|
|||
|
||||
let app = Router::new()
|
||||
.nest("/api/social-media-managers", handlers::router())
|
||||
.nest("/api/admin/social-media-managers", admin::router())
|
||||
.route("/health", get(|| async { "Social Media Managers OK" }))
|
||||
.with_state(state);
|
||||
|
||||
|
|
|
|||
81
apps/tutors/src/admin.rs
Normal file
81
apps/tutors/src/admin.rs
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
use crate::AppState;
|
||||
use axum::{extract::{Path, State}, http::StatusCode, response::IntoResponse, routing::get, Json, Router};
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::Serialize;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct AdminTutorList {
|
||||
pub id: Uuid,
|
||||
pub user_id: Uuid,
|
||||
pub first_name: String,
|
||||
pub last_name: String,
|
||||
pub email: String,
|
||||
pub phone: Option<String>,
|
||||
pub status: String,
|
||||
pub subjects_taught: Option<Vec<String>>,
|
||||
pub education_level: Option<String>,
|
||||
pub certifications: Option<String>,
|
||||
pub years_of_experience: Option<i32>,
|
||||
pub hourly_rate: Option<i32>,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
type AdminTutorDetail = AdminTutorList;
|
||||
|
||||
pub fn router() -> Router<AppState> {
|
||||
Router::new()
|
||||
.route("/", get(list_tutors))
|
||||
.route("/{id}", get(get_tutor))
|
||||
}
|
||||
|
||||
async fn list_tutors(
|
||||
State(state): State<AppState>,
|
||||
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
||||
let tutors = sqlx::query_as!(
|
||||
AdminTutorList,
|
||||
r#"
|
||||
SELECT
|
||||
t.id, t.user_id, t.subjects_taught, t.education_level, t.certifications, t.years_of_experience, t.hourly_rate,
|
||||
t.created_at, t.updated_at,
|
||||
u.first_name, u.last_name, u.email, u.phone, u.status
|
||||
FROM tutor_profiles t
|
||||
JOIN users u ON t.user_id = u.id
|
||||
ORDER BY t.created_at DESC
|
||||
LIMIT 100
|
||||
"#
|
||||
)
|
||||
.fetch_all(&state.pool)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
|
||||
|
||||
Ok(Json(tutors))
|
||||
}
|
||||
|
||||
async fn get_tutor(
|
||||
State(state): State<AppState>,
|
||||
Path(id): Path<Uuid>,
|
||||
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
||||
let tutor = sqlx::query_as!(
|
||||
AdminTutorDetail,
|
||||
r#"
|
||||
SELECT
|
||||
t.id, t.user_id, t.subjects_taught, t.education_level, t.certifications, t.years_of_experience, t.hourly_rate,
|
||||
t.created_at, t.updated_at,
|
||||
u.first_name, u.last_name, u.email, u.phone, u.status
|
||||
FROM tutor_profiles t
|
||||
JOIN users u ON t.user_id = u.id
|
||||
WHERE t.id = $1
|
||||
"#,
|
||||
id
|
||||
)
|
||||
.fetch_optional(&state.pool)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
|
||||
|
||||
match tutor {
|
||||
Some(t) => Ok(Json(t)),
|
||||
None => Err((StatusCode::NOT_FOUND, "Tutor not found".to_string())),
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
mod handlers;
|
||||
mod admin;
|
||||
|
||||
use axum::{routing::get, Router};
|
||||
use std::net::SocketAddr;
|
||||
|
|
@ -33,6 +34,7 @@ async fn main() {
|
|||
|
||||
let app = Router::new()
|
||||
.nest("/api/tutors", handlers::router())
|
||||
.nest("/api/admin/tutors", admin::router())
|
||||
.route("/health", get(|| async { "Tutors OK" }))
|
||||
.with_state(state);
|
||||
|
||||
|
|
|
|||
79
apps/video_editors/src/admin.rs
Normal file
79
apps/video_editors/src/admin.rs
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
use crate::AppState;
|
||||
use axum::{extract::{Path, State}, http::StatusCode, response::IntoResponse, routing::get, Json, Router};
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::Serialize;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct AdminVideoEditorList {
|
||||
pub id: Uuid,
|
||||
pub user_id: Uuid,
|
||||
pub first_name: String,
|
||||
pub last_name: String,
|
||||
pub email: String,
|
||||
pub phone: Option<String>,
|
||||
pub status: String,
|
||||
pub bio: Option<String>,
|
||||
pub experience_years: Option<i32>,
|
||||
pub custom_data: serde_json::Value,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
type AdminVideoEditorDetail = AdminVideoEditorList;
|
||||
|
||||
pub fn router() -> Router<AppState> {
|
||||
Router::new()
|
||||
.route("/", get(list_video_editors))
|
||||
.route("/{id}", get(get_video_editor))
|
||||
}
|
||||
|
||||
async fn list_video_editors(
|
||||
State(state): State<AppState>,
|
||||
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
||||
let editors = sqlx::query_as!(
|
||||
AdminVideoEditorList,
|
||||
r#"
|
||||
SELECT
|
||||
v.id, v.user_id, v.bio, v.experience_years, v.custom_data,
|
||||
v.created_at, v.updated_at,
|
||||
u.first_name, u.last_name, u.email, u.phone, u.status
|
||||
FROM video_editor_profiles v
|
||||
JOIN users u ON v.user_id = u.id
|
||||
ORDER BY v.created_at DESC
|
||||
LIMIT 100
|
||||
"#
|
||||
)
|
||||
.fetch_all(&state.pool)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
|
||||
|
||||
Ok(Json(editors))
|
||||
}
|
||||
|
||||
async fn get_video_editor(
|
||||
State(state): State<AppState>,
|
||||
Path(id): Path<Uuid>,
|
||||
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
||||
let editor = sqlx::query_as!(
|
||||
AdminVideoEditorDetail,
|
||||
r#"
|
||||
SELECT
|
||||
v.id, v.user_id, v.bio, v.experience_years, v.custom_data,
|
||||
v.created_at, v.updated_at,
|
||||
u.first_name, u.last_name, u.email, u.phone, u.status
|
||||
FROM video_editor_profiles v
|
||||
JOIN users u ON v.user_id = u.id
|
||||
WHERE v.id = $1
|
||||
"#,
|
||||
id
|
||||
)
|
||||
.fetch_optional(&state.pool)
|
||||
.await
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
|
||||
|
||||
match editor {
|
||||
Some(v) => Ok(Json(v)),
|
||||
None => Err((StatusCode::NOT_FOUND, "Video editor not found".to_string())),
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
mod handlers;
|
||||
mod admin;
|
||||
|
||||
use axum::{routing::get, Router};
|
||||
use std::net::SocketAddr;
|
||||
|
|
@ -33,6 +34,7 @@ async fn main() {
|
|||
|
||||
let app = Router::new()
|
||||
.nest("/api/video-editors", handlers::router())
|
||||
.nest("/api/admin/video-editors", admin::router())
|
||||
.route("/health", get(|| async { "Video Editors OK" }))
|
||||
.with_state(state);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue