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:
Ashwin Kumar 2026-04-06 19:08:00 +02:00
parent 13643ffb1b
commit 2c0c979c91
18 changed files with 664 additions and 9 deletions

View 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())),
}
}

View file

@ -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);

View 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())),
}
}

View file

@ -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);

View 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())),
}
}

View file

@ -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);

View file

@ -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") {

View 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())),
}
}

View file

@ -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);

View file

@ -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);

View 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())),
}
}

View file

@ -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);

View 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())),
}
}

View file

@ -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
View 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())),
}
}

View file

@ -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);

View 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())),
}
}

View file

@ -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);