2026-03-17 20:42:51 +01:00
|
|
|
use axum::{
|
|
|
|
|
extract::{Path, Query, State},
|
|
|
|
|
http::StatusCode,
|
|
|
|
|
response::IntoResponse,
|
|
|
|
|
routing::{delete, get, patch, post},
|
|
|
|
|
Json, Router,
|
|
|
|
|
};
|
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
|
|
|
use chrono::Utc;
|
2026-03-17 20:42:51 +01:00
|
|
|
use serde::Deserialize;
|
|
|
|
|
use uuid::Uuid;
|
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
|
|
|
use db::models::lead_request::{CreateLeadRequestPayload, LeadRequestRepository};
|
2026-03-19 00:30:23 +01:00
|
|
|
use db::models::professional::{
|
|
|
|
|
CreatePortfolioItemPayload,
|
|
|
|
|
CreateServicePayload,
|
|
|
|
|
ProfessionalRepository,
|
|
|
|
|
UpdatePortfolioItemPayload,
|
|
|
|
|
UpdateServicePayload,
|
|
|
|
|
};
|
2026-03-17 20:42:51 +01:00
|
|
|
use db::models::requirement::RequirementRepository;
|
|
|
|
|
use crate::auth_middleware::AuthUser;
|
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
|
|
|
use crate::ProfessionState;
|
2026-03-17 20:42:51 +01:00
|
|
|
|
|
|
|
|
#[derive(Deserialize)]
|
|
|
|
|
pub struct PaginationQuery {
|
|
|
|
|
pub page: Option<i64>,
|
|
|
|
|
pub limit: Option<i64>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Deserialize)]
|
|
|
|
|
pub struct LeadRequestPayload {
|
|
|
|
|
pub requirement_id: Uuid,
|
|
|
|
|
}
|
|
|
|
|
|
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
|
|
|
/// Build the shared Router that every profession service merges into its own Router.
|
|
|
|
|
/// `profession_key` must be a `'static str` matching the role key, e.g. `"PHOTOGRAPHER"`.
|
|
|
|
|
pub fn shared_routes(profession_key: &'static str) -> Router<ProfessionState> {
|
2026-03-17 20:42:51 +01:00
|
|
|
Router::new()
|
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
|
|
|
// ── Marketplace (Redis-cached) ────────────────────────────────────────
|
2026-03-17 20:42:51 +01:00
|
|
|
.route(
|
|
|
|
|
"/marketplace",
|
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
|
|
|
get(move |State(state): State<ProfessionState>, Query(q): Query<PaginationQuery>| async move {
|
|
|
|
|
let page = q.page.unwrap_or(1);
|
|
|
|
|
let limit = q.limit.unwrap_or(20);
|
|
|
|
|
|
|
|
|
|
// Try cache first
|
|
|
|
|
let mut redis = state.redis.clone();
|
|
|
|
|
if let Ok(Some(cached)) = cache::jobs::get_marketplace(&mut redis, profession_key, page, limit).await {
|
|
|
|
|
if let Ok(parsed) = serde_json::from_str::<serde_json::Value>(&cached) {
|
|
|
|
|
return (StatusCode::OK, Json(parsed)).into_response();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
match ProfessionalRepository::get_marketplace(&state.pool, profession_key, page, limit).await {
|
|
|
|
|
Ok(items) => {
|
|
|
|
|
let body = serde_json::json!({
|
|
|
|
|
"data": items,
|
|
|
|
|
"pagination": { "page": page, "limit": limit }
|
|
|
|
|
});
|
|
|
|
|
// Write to cache (best-effort)
|
|
|
|
|
let _ = cache::jobs::set_marketplace(
|
|
|
|
|
&mut redis,
|
|
|
|
|
profession_key,
|
|
|
|
|
page,
|
|
|
|
|
limit,
|
|
|
|
|
&body.to_string(),
|
|
|
|
|
).await;
|
|
|
|
|
(StatusCode::OK, Json(body)).into_response()
|
|
|
|
|
}
|
|
|
|
|
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
|
|
|
|
|
}
|
|
|
|
|
}),
|
2026-03-17 20:42:51 +01:00
|
|
|
)
|
|
|
|
|
.route("/marketplace/:id", get(get_requirement))
|
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
|
|
|
// ── Lead Requests ────────────────────────────────────────────────────
|
2026-03-19 00:30:23 +01:00
|
|
|
.route(
|
|
|
|
|
"/leads/request",
|
|
|
|
|
post(
|
|
|
|
|
move |state: State<ProfessionState>, auth: AuthUser, payload: Json<LeadRequestPayload>| async move {
|
|
|
|
|
send_lead_request(state, auth, payload, profession_key).await
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
)
|
2026-03-17 20:42:51 +01:00
|
|
|
.route("/leads/requests/me", get(my_requests))
|
|
|
|
|
.route("/leads/requests/:id", delete(cancel_request))
|
|
|
|
|
.route("/leads/accepted/me", get(accepted_leads))
|
|
|
|
|
.route("/leads/accepted/:id", get(accepted_lead_detail))
|
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
|
|
|
// ── Portfolio ────────────────────────────────────────────────────────
|
|
|
|
|
.route("/portfolio/me", get(list_portfolio).post(create_portfolio_item))
|
|
|
|
|
.route("/portfolio/me/:id", patch(update_portfolio_item).delete(delete_portfolio_item))
|
|
|
|
|
// ── Services ─────────────────────────────────────────────────────────
|
|
|
|
|
.route("/services/me", get(list_services).post(create_service))
|
|
|
|
|
.route("/services/me/:id", patch(update_service).delete(delete_service))
|
|
|
|
|
// ── Wallet ───────────────────────────────────────────────────────────
|
|
|
|
|
.route("/wallet/me", get(wallet_balance))
|
|
|
|
|
.route("/wallet/me/ledger", get(wallet_ledger))
|
|
|
|
|
.route("/wallet/me/invoices", get(wallet_invoices))
|
|
|
|
|
.route("/wallet/me/invoices/:id", get(wallet_invoice_detail))
|
2026-03-17 20:42:51 +01:00
|
|
|
}
|
|
|
|
|
|
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
|
|
|
// ── Handlers ──────────────────────────────────────────────────────────────────
|
2026-03-17 20:42:51 +01:00
|
|
|
|
|
|
|
|
async fn get_requirement(
|
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
|
|
|
State(state): State<ProfessionState>,
|
2026-03-17 20:42:51 +01:00
|
|
|
_auth: AuthUser,
|
|
|
|
|
Path(id): Path<Uuid>,
|
|
|
|
|
) -> impl IntoResponse {
|
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
|
|
|
match RequirementRepository::get_by_id(&state.pool, id).await {
|
2026-03-17 20:42:51 +01:00
|
|
|
Ok(Some(req)) if req.status == "OPEN" => (StatusCode::OK, Json(req)).into_response(),
|
|
|
|
|
Ok(Some(_)) => (StatusCode::FORBIDDEN, "Requirement is not open").into_response(),
|
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
|
|
|
Ok(None) => (StatusCode::NOT_FOUND, "Requirement not found").into_response(),
|
|
|
|
|
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
|
2026-03-17 20:42:51 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fn send_lead_request(
|
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
|
|
|
State(state): State<ProfessionState>,
|
2026-03-17 20:42:51 +01:00
|
|
|
auth: AuthUser,
|
|
|
|
|
Json(payload): Json<LeadRequestPayload>,
|
2026-03-19 00:30:23 +01:00
|
|
|
profession_key: &'static str,
|
2026-03-17 20:42:51 +01:00
|
|
|
) -> impl IntoResponse {
|
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
|
|
|
let mut redis = state.redis.clone();
|
|
|
|
|
|
|
|
|
|
// ── Rate limit: max 5 lead requests per hour per professional ─────────────
|
|
|
|
|
let allowed = cache::rate_limit::check_lead(&mut redis, &auth.user_id.to_string())
|
|
|
|
|
.await
|
|
|
|
|
.unwrap_or(true);
|
|
|
|
|
if !allowed {
|
|
|
|
|
return (StatusCode::TOO_MANY_REQUESTS, "Too many lead requests. Try again later.").into_response();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let prof = match ProfessionalRepository::get_by_user_id(&state.pool, auth.user_id).await {
|
|
|
|
|
Ok(p) => p,
|
2026-03-17 20:42:51 +01:00
|
|
|
Err(_) => return (StatusCode::NOT_FOUND, "Professional profile not found").into_response(),
|
|
|
|
|
};
|
|
|
|
|
|
2026-03-19 00:30:23 +01:00
|
|
|
match is_professional_profile_approved(&state.pool, auth.user_id, profession_key).await {
|
|
|
|
|
Ok(true) => {}
|
|
|
|
|
Ok(false) => {
|
|
|
|
|
return (
|
|
|
|
|
StatusCode::FORBIDDEN,
|
|
|
|
|
"Professional profile approval is required before sending lead requests",
|
|
|
|
|
)
|
|
|
|
|
.into_response()
|
|
|
|
|
}
|
2026-03-22 15:55:29 +01:00
|
|
|
Err(e) => {
|
|
|
|
|
tracing::error!("Failed to check profile approval: {}", e);
|
|
|
|
|
return (StatusCode::INTERNAL_SERVER_ERROR, "Failed to validate profile approval").into_response();
|
|
|
|
|
}
|
2026-03-19 00:30:23 +01:00
|
|
|
}
|
|
|
|
|
|
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
|
|
|
// ── Deduplication: one lead per requirement per professional (24 h) ────────
|
|
|
|
|
let duplicate = cache::lead::is_duplicate(
|
|
|
|
|
&mut redis,
|
|
|
|
|
&prof.id.to_string(),
|
|
|
|
|
&payload.requirement_id.to_string(),
|
|
|
|
|
)
|
|
|
|
|
.await
|
|
|
|
|
.unwrap_or(false);
|
|
|
|
|
|
|
|
|
|
if duplicate {
|
|
|
|
|
return (StatusCode::CONFLICT, "You have already sent a lead request for this requirement").into_response();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let req = match RequirementRepository::get_by_id(&state.pool, payload.requirement_id).await {
|
2026-03-17 20:42:51 +01:00
|
|
|
Ok(Some(r)) if r.status == "OPEN" => r,
|
|
|
|
|
Ok(Some(_)) => return (StatusCode::BAD_REQUEST, "Requirement is not open").into_response(),
|
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
|
|
|
_ => return (StatusCode::NOT_FOUND, "Requirement not found").into_response(),
|
2026-03-17 20:42:51 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if req.request_count >= 20 {
|
|
|
|
|
return (StatusCode::CONFLICT, "Requirement reached max requests").into_response();
|
|
|
|
|
}
|
|
|
|
|
|
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
|
|
|
let wallet = match ProfessionalRepository::get_wallet(&state.pool, auth.user_id).await {
|
|
|
|
|
Ok(w) => w,
|
|
|
|
|
Err(_) => return (StatusCode::BAD_REQUEST, "Wallet not found").into_response(),
|
2026-03-17 20:42:51 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if wallet.balance < 25 {
|
|
|
|
|
return (StatusCode::PAYMENT_REQUIRED, "Insufficient Tracecoin balance").into_response();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let db_payload = CreateLeadRequestPayload {
|
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
|
|
|
requirement_id: req.id,
|
2026-03-17 20:42:51 +01:00
|
|
|
professional_id: prof.id,
|
|
|
|
|
expires_at: Utc::now() + chrono::Duration::hours(24),
|
|
|
|
|
};
|
|
|
|
|
|
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
|
|
|
match LeadRequestRepository::create(&state.pool, db_payload).await {
|
2026-03-17 20:42:51 +01:00
|
|
|
Ok(lead) => {
|
2026-03-19 00:30:23 +01:00
|
|
|
let reserved = ProfessionalRepository::try_reserve_tracecoins(
|
|
|
|
|
&state.pool,
|
|
|
|
|
auth.user_id,
|
|
|
|
|
lead.tracecoins_reserved,
|
|
|
|
|
lead.id,
|
|
|
|
|
)
|
|
|
|
|
.await;
|
|
|
|
|
|
|
|
|
|
match reserved {
|
|
|
|
|
Ok(true) => {}
|
|
|
|
|
Ok(false) => {
|
|
|
|
|
let _ = LeadRequestRepository::update_status(&state.pool, lead.id, "CANCELLED").await;
|
|
|
|
|
return (StatusCode::PAYMENT_REQUIRED, "Insufficient Tracecoin balance").into_response();
|
|
|
|
|
}
|
|
|
|
|
Err(e) => {
|
|
|
|
|
let _ = LeadRequestRepository::update_status(&state.pool, lead.id, "CANCELLED").await;
|
|
|
|
|
return (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
|
|
|
let _ = RequirementRepository::increment_request_count(&state.pool, req.id).await;
|
|
|
|
|
// Mark dedup in Redis so this professional can't spam the same requirement
|
|
|
|
|
let _ = cache::lead::mark_sent(
|
|
|
|
|
&mut redis,
|
|
|
|
|
&prof.id.to_string(),
|
|
|
|
|
&payload.requirement_id.to_string(),
|
|
|
|
|
)
|
|
|
|
|
.await;
|
2026-03-17 20:42:51 +01:00
|
|
|
(StatusCode::CREATED, Json(lead)).into_response()
|
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
|
|
|
}
|
2026-03-17 20:42:51 +01:00
|
|
|
Err(e) => {
|
|
|
|
|
if e.to_string().contains("unique") {
|
|
|
|
|
(StatusCode::CONFLICT, "Already requested this lead").into_response()
|
|
|
|
|
} else {
|
|
|
|
|
(StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-19 00:30:23 +01:00
|
|
|
async fn is_professional_profile_approved(
|
|
|
|
|
pool: &sqlx::PgPool,
|
|
|
|
|
user_id: Uuid,
|
|
|
|
|
profession_key: &str,
|
|
|
|
|
) -> Result<bool, sqlx::Error> {
|
|
|
|
|
let status = match profession_key {
|
|
|
|
|
"PHOTOGRAPHER" => {
|
|
|
|
|
sqlx::query_scalar::<_, String>("SELECT status FROM photographer_profiles WHERE user_id = $1")
|
|
|
|
|
.bind(user_id)
|
|
|
|
|
.fetch_optional(pool)
|
|
|
|
|
.await?
|
|
|
|
|
}
|
|
|
|
|
"MAKEUP_ARTIST" => {
|
|
|
|
|
sqlx::query_scalar::<_, String>("SELECT status FROM makeup_artist_profiles WHERE user_id = $1")
|
|
|
|
|
.bind(user_id)
|
|
|
|
|
.fetch_optional(pool)
|
|
|
|
|
.await?
|
|
|
|
|
}
|
|
|
|
|
"TUTOR" => {
|
|
|
|
|
sqlx::query_scalar::<_, String>("SELECT status FROM tutor_profiles WHERE user_id = $1")
|
|
|
|
|
.bind(user_id)
|
|
|
|
|
.fetch_optional(pool)
|
|
|
|
|
.await?
|
|
|
|
|
}
|
|
|
|
|
"DEVELOPER" => {
|
|
|
|
|
sqlx::query_scalar::<_, String>("SELECT status FROM developer_profiles WHERE user_id = $1")
|
|
|
|
|
.bind(user_id)
|
|
|
|
|
.fetch_optional(pool)
|
|
|
|
|
.await?
|
|
|
|
|
}
|
|
|
|
|
"VIDEO_EDITOR" => {
|
|
|
|
|
sqlx::query_scalar::<_, String>("SELECT status FROM video_editor_profiles WHERE user_id = $1")
|
|
|
|
|
.bind(user_id)
|
|
|
|
|
.fetch_optional(pool)
|
|
|
|
|
.await?
|
|
|
|
|
}
|
|
|
|
|
"GRAPHIC_DESIGNER" => {
|
|
|
|
|
sqlx::query_scalar::<_, String>("SELECT status FROM graphic_designer_profiles WHERE user_id = $1")
|
|
|
|
|
.bind(user_id)
|
|
|
|
|
.fetch_optional(pool)
|
|
|
|
|
.await?
|
|
|
|
|
}
|
|
|
|
|
"SOCIAL_MEDIA_MANAGER" => {
|
|
|
|
|
sqlx::query_scalar::<_, String>("SELECT status FROM social_media_manager_profiles WHERE user_id = $1")
|
|
|
|
|
.bind(user_id)
|
|
|
|
|
.fetch_optional(pool)
|
|
|
|
|
.await?
|
|
|
|
|
}
|
|
|
|
|
"FITNESS_TRAINER" => {
|
|
|
|
|
sqlx::query_scalar::<_, String>("SELECT status FROM fitness_trainer_profiles WHERE user_id = $1")
|
|
|
|
|
.bind(user_id)
|
|
|
|
|
.fetch_optional(pool)
|
|
|
|
|
.await?
|
|
|
|
|
}
|
|
|
|
|
"CATERING_SERVICES" => {
|
|
|
|
|
sqlx::query_scalar::<_, String>("SELECT status FROM catering_service_profiles WHERE user_id = $1")
|
|
|
|
|
.bind(user_id)
|
|
|
|
|
.fetch_optional(pool)
|
|
|
|
|
.await?
|
|
|
|
|
}
|
|
|
|
|
_ => None,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Ok(matches!(status.as_deref(), Some("APPROVED")))
|
|
|
|
|
}
|
|
|
|
|
|
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
|
|
|
async fn list_portfolio(State(state): State<ProfessionState>, auth: AuthUser) -> impl IntoResponse {
|
|
|
|
|
match ProfessionalRepository::get_by_user_id(&state.pool, auth.user_id).await {
|
|
|
|
|
Ok(prof) => match ProfessionalRepository::get_portfolio(&state.pool, prof.id).await {
|
|
|
|
|
Ok(items) => (StatusCode::OK, Json(serde_json::json!({ "data": items }))).into_response(),
|
|
|
|
|
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
|
2026-03-17 20:42:51 +01:00
|
|
|
},
|
|
|
|
|
Err(_) => (StatusCode::NOT_FOUND, "Professional profile not found").into_response(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
|
|
|
async fn list_services(State(state): State<ProfessionState>, auth: AuthUser) -> impl IntoResponse {
|
|
|
|
|
match ProfessionalRepository::get_by_user_id(&state.pool, auth.user_id).await {
|
|
|
|
|
Ok(prof) => match ProfessionalRepository::get_services(&state.pool, prof.id).await {
|
|
|
|
|
Ok(items) => (StatusCode::OK, Json(serde_json::json!({ "data": items }))).into_response(),
|
|
|
|
|
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
|
2026-03-17 20:42:51 +01:00
|
|
|
},
|
|
|
|
|
Err(_) => (StatusCode::NOT_FOUND, "Professional profile not found").into_response(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
|
|
|
async fn wallet_balance(State(state): State<ProfessionState>, auth: AuthUser) -> impl IntoResponse {
|
|
|
|
|
match ProfessionalRepository::get_wallet(&state.pool, auth.user_id).await {
|
|
|
|
|
Ok(w) => (StatusCode::OK, Json(w)).into_response(),
|
2026-03-17 20:42:51 +01:00
|
|
|
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
|
|
|
// ── Stub handlers ─────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
async fn my_requests(
|
|
|
|
|
_s: State<ProfessionState>,
|
|
|
|
|
_a: AuthUser,
|
|
|
|
|
_q: Query<PaginationQuery>,
|
|
|
|
|
) -> impl IntoResponse {
|
|
|
|
|
(StatusCode::OK, Json(serde_json::json!({ "data": [] })))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fn cancel_request(
|
|
|
|
|
_s: State<ProfessionState>,
|
|
|
|
|
_a: AuthUser,
|
|
|
|
|
_p: Path<Uuid>,
|
|
|
|
|
) -> impl IntoResponse {
|
|
|
|
|
(StatusCode::OK, Json(serde_json::json!({ "message": "Cancelled" })))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fn accepted_leads(
|
|
|
|
|
_s: State<ProfessionState>,
|
|
|
|
|
_a: AuthUser,
|
|
|
|
|
_q: Query<PaginationQuery>,
|
|
|
|
|
) -> impl IntoResponse {
|
|
|
|
|
(StatusCode::OK, Json(serde_json::json!({ "data": [] })))
|
|
|
|
|
}
|
2026-03-17 20:42:51 +01:00
|
|
|
|
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
|
|
|
async fn accepted_lead_detail(
|
|
|
|
|
_s: State<ProfessionState>,
|
|
|
|
|
_a: AuthUser,
|
|
|
|
|
Path(id): Path<Uuid>,
|
|
|
|
|
) -> impl IntoResponse {
|
|
|
|
|
(StatusCode::OK, Json(serde_json::json!({ "id": id.to_string() })))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fn create_portfolio_item(
|
2026-03-19 00:30:23 +01:00
|
|
|
State(state): State<ProfessionState>,
|
|
|
|
|
auth: AuthUser,
|
|
|
|
|
Json(payload): Json<CreatePortfolioItemPayload>,
|
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
|
|
|
) -> impl IntoResponse {
|
2026-03-19 00:30:23 +01:00
|
|
|
let prof = match ProfessionalRepository::get_by_user_id(&state.pool, auth.user_id).await {
|
|
|
|
|
Ok(p) => p,
|
|
|
|
|
Err(_) => return (StatusCode::NOT_FOUND, "Professional profile not found").into_response(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
match ProfessionalRepository::create_portfolio_item(&state.pool, prof.id, payload).await {
|
|
|
|
|
Ok(item) => (StatusCode::CREATED, Json(item)).into_response(),
|
|
|
|
|
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
|
|
|
|
|
}
|
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fn update_portfolio_item(
|
2026-03-19 00:30:23 +01:00
|
|
|
State(state): State<ProfessionState>,
|
|
|
|
|
auth: AuthUser,
|
|
|
|
|
Path(id): Path<Uuid>,
|
|
|
|
|
Json(payload): Json<UpdatePortfolioItemPayload>,
|
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
|
|
|
) -> impl IntoResponse {
|
2026-03-19 00:30:23 +01:00
|
|
|
let prof = match ProfessionalRepository::get_by_user_id(&state.pool, auth.user_id).await {
|
|
|
|
|
Ok(p) => p,
|
|
|
|
|
Err(_) => return (StatusCode::NOT_FOUND, "Professional profile not found").into_response(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
match ProfessionalRepository::update_portfolio_item(&state.pool, prof.id, id, payload).await {
|
|
|
|
|
Ok(Some(item)) => (StatusCode::OK, Json(item)).into_response(),
|
|
|
|
|
Ok(None) => (StatusCode::NOT_FOUND, "Portfolio item not found").into_response(),
|
|
|
|
|
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
|
|
|
|
|
}
|
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fn delete_portfolio_item(
|
2026-03-19 00:30:23 +01:00
|
|
|
State(state): State<ProfessionState>,
|
|
|
|
|
auth: AuthUser,
|
|
|
|
|
Path(id): Path<Uuid>,
|
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
|
|
|
) -> impl IntoResponse {
|
2026-03-19 00:30:23 +01:00
|
|
|
let prof = match ProfessionalRepository::get_by_user_id(&state.pool, auth.user_id).await {
|
|
|
|
|
Ok(p) => p,
|
|
|
|
|
Err(_) => return (StatusCode::NOT_FOUND, "Professional profile not found").into_response(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
match ProfessionalRepository::delete_portfolio_item(&state.pool, prof.id, id).await {
|
|
|
|
|
Ok(true) => (StatusCode::OK, Json(serde_json::json!({ "message": "Deleted" }))).into_response(),
|
|
|
|
|
Ok(false) => (StatusCode::NOT_FOUND, "Portfolio item not found").into_response(),
|
|
|
|
|
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
|
|
|
|
|
}
|
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fn create_service(
|
2026-03-19 00:30:23 +01:00
|
|
|
State(state): State<ProfessionState>,
|
|
|
|
|
auth: AuthUser,
|
|
|
|
|
Json(payload): Json<CreateServicePayload>,
|
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
|
|
|
) -> impl IntoResponse {
|
2026-03-19 00:30:23 +01:00
|
|
|
let prof = match ProfessionalRepository::get_by_user_id(&state.pool, auth.user_id).await {
|
|
|
|
|
Ok(p) => p,
|
|
|
|
|
Err(_) => return (StatusCode::NOT_FOUND, "Professional profile not found").into_response(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
match ProfessionalRepository::create_service(&state.pool, prof.id, payload).await {
|
|
|
|
|
Ok(svc) => (StatusCode::CREATED, Json(svc)).into_response(),
|
|
|
|
|
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
|
|
|
|
|
}
|
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fn update_service(
|
2026-03-19 00:30:23 +01:00
|
|
|
State(state): State<ProfessionState>,
|
|
|
|
|
auth: AuthUser,
|
|
|
|
|
Path(id): Path<Uuid>,
|
|
|
|
|
Json(payload): Json<UpdateServicePayload>,
|
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
|
|
|
) -> impl IntoResponse {
|
2026-03-19 00:30:23 +01:00
|
|
|
let prof = match ProfessionalRepository::get_by_user_id(&state.pool, auth.user_id).await {
|
|
|
|
|
Ok(p) => p,
|
|
|
|
|
Err(_) => return (StatusCode::NOT_FOUND, "Professional profile not found").into_response(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
match ProfessionalRepository::update_service(&state.pool, prof.id, id, payload).await {
|
|
|
|
|
Ok(Some(svc)) => (StatusCode::OK, Json(svc)).into_response(),
|
|
|
|
|
Ok(None) => (StatusCode::NOT_FOUND, "Service not found").into_response(),
|
|
|
|
|
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
|
|
|
|
|
}
|
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fn delete_service(
|
2026-03-19 00:30:23 +01:00
|
|
|
State(state): State<ProfessionState>,
|
|
|
|
|
auth: AuthUser,
|
|
|
|
|
Path(id): Path<Uuid>,
|
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
|
|
|
) -> impl IntoResponse {
|
2026-03-19 00:30:23 +01:00
|
|
|
let prof = match ProfessionalRepository::get_by_user_id(&state.pool, auth.user_id).await {
|
|
|
|
|
Ok(p) => p,
|
|
|
|
|
Err(_) => return (StatusCode::NOT_FOUND, "Professional profile not found").into_response(),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
match ProfessionalRepository::delete_service(&state.pool, prof.id, id).await {
|
|
|
|
|
Ok(true) => (StatusCode::OK, Json(serde_json::json!({ "message": "Deleted" }))).into_response(),
|
|
|
|
|
Ok(false) => (StatusCode::NOT_FOUND, "Service not found").into_response(),
|
|
|
|
|
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
|
|
|
|
|
}
|
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fn wallet_ledger(
|
2026-03-19 00:30:23 +01:00
|
|
|
State(state): State<ProfessionState>,
|
|
|
|
|
auth: AuthUser,
|
|
|
|
|
Query(q): Query<PaginationQuery>,
|
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
|
|
|
) -> impl IntoResponse {
|
2026-03-19 00:30:23 +01:00
|
|
|
let page = q.page.unwrap_or(1);
|
|
|
|
|
let limit = q.limit.unwrap_or(20);
|
|
|
|
|
|
|
|
|
|
match ProfessionalRepository::list_wallet_ledger(&state.pool, auth.user_id, page, limit).await {
|
|
|
|
|
Ok(items) => (StatusCode::OK, Json(serde_json::json!({
|
|
|
|
|
"data": items,
|
|
|
|
|
"pagination": { "page": page, "limit": limit }
|
|
|
|
|
}))).into_response(),
|
|
|
|
|
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
|
|
|
|
|
}
|
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fn wallet_invoices(
|
2026-03-19 00:30:23 +01:00
|
|
|
State(state): State<ProfessionState>,
|
|
|
|
|
auth: AuthUser,
|
|
|
|
|
Query(q): Query<PaginationQuery>,
|
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
|
|
|
) -> impl IntoResponse {
|
2026-03-19 00:30:23 +01:00
|
|
|
let page = q.page.unwrap_or(1);
|
|
|
|
|
let limit = q.limit.unwrap_or(20);
|
|
|
|
|
|
|
|
|
|
match ProfessionalRepository::list_wallet_invoices(&state.pool, auth.user_id, page, limit).await {
|
|
|
|
|
Ok(items) => (StatusCode::OK, Json(serde_json::json!({
|
|
|
|
|
"data": items,
|
|
|
|
|
"pagination": { "page": page, "limit": limit }
|
|
|
|
|
}))).into_response(),
|
|
|
|
|
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
|
|
|
|
|
}
|
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fn wallet_invoice_detail(
|
2026-03-19 00:30:23 +01:00
|
|
|
State(state): State<ProfessionState>,
|
|
|
|
|
auth: AuthUser,
|
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
|
|
|
Path(id): Path<Uuid>,
|
|
|
|
|
) -> impl IntoResponse {
|
2026-03-19 00:30:23 +01:00
|
|
|
match ProfessionalRepository::get_invoice_by_id_for_user(&state.pool, auth.user_id, id).await {
|
|
|
|
|
Ok(Some(inv)) => (StatusCode::OK, Json(inv)).into_response(),
|
|
|
|
|
Ok(None) => (StatusCode::NOT_FOUND, "Invoice not found").into_response(),
|
|
|
|
|
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
|
|
|
|
|
}
|
feat: add Redis for OTP, auth tokens, rate limiting, lead dedup and marketplace cache
- Add crates/cache with client, otp, rate_limit, token, lead, jobs modules
- OTP tokens stored in Redis (15-min TTL, single-use GETDEL on verify)
- Refresh tokens stored in Redis (30-day TTL) — removed DB storage
- Password reset tokens stored in Redis (1-hour TTL, single-use)
- Rate limiting: register (10/hr), login (10/15min), OTP resend (3/hr), lead (5/hr), job post (20/hr)
- Lead request deduplication: 24-hour Redis lock per professional+requirement pair
- Marketplace listings cached in Redis (5-min TTL per profession+page+limit)
- Add ProfessionState{pool, redis} to contracts crate, replacing bare PgPool in all 9 profession apps
- All profession handlers and main.rs updated to use ProfessionState
- REDIS_URL env var (default: redis://127.0.0.1:6379) used across all services
- Fix profession model struct name mangling in 6 handlers (MakeupArtistRepository etc.)
- Add custom_data JSONB migration for all 9 profession profile tables
- Add onboarding_state model and repository (save_progress, complete, is_complete)
- Add onboarding handler accepting roleKey:String (not role_id:UUID) for frontend compat
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:58:42 +01:00
|
|
|
}
|