chore: checkpoint current workspace changes
This commit is contained in:
parent
cb36e2fa7d
commit
91534d74c0
34 changed files with 240 additions and 97 deletions
|
|
@ -41,6 +41,6 @@ prost = "0.13"
|
||||||
sqlx = { version = "0.8", features = ["runtime-tokio-rustls", "postgres", "uuid", "chrono", "json"] }
|
sqlx = { version = "0.8", features = ["runtime-tokio-rustls", "postgres", "uuid", "chrono", "json"] }
|
||||||
uuid = { version = "1", features = ["serde", "v4"] }
|
uuid = { version = "1", features = ["serde", "v4"] }
|
||||||
chrono = { version = "0.4", features = ["serde"] }
|
chrono = { version = "0.4", features = ["serde"] }
|
||||||
lettre = { version = "0.11", features = ["tokio1-rustls-tls", "serde"] }
|
lettre = { version = "0.11", default-features = false, features = ["tokio1-rustls-tls", "smtp-transport", "builder", "serde"] }
|
||||||
redis = { version = "0.27", features = ["tokio-comp"] }
|
redis = { version = "0.27", features = ["tokio-comp", "connection-manager"] }
|
||||||
|
async-trait = "0.1"
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ axum = { workspace = true }
|
||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
tower-http = { version = "0.6", features = ["proxy", "cors"] }
|
tower-http = { version = "0.6", features = ["cors"] }
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
tracing-subscriber = { workspace = true }
|
tracing-subscriber = { workspace = true }
|
||||||
reqwest = { version = "0.12", features = ["json", "stream"] }
|
reqwest = { version = "0.12", features = ["json", "stream"] }
|
||||||
|
|
|
||||||
|
|
@ -193,7 +193,7 @@ async fn main() {
|
||||||
let cors = build_cors();
|
let cors = build_cors();
|
||||||
|
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route("/api/*path", any(proxy_handler))
|
.route("/api/{*path}", any(proxy_handler))
|
||||||
.route("/health", any(|| async { "Gateway OK" }))
|
.route("/health", any(|| async { "Gateway OK" }))
|
||||||
.layer(cors)
|
.layer(cors)
|
||||||
.with_state(services);
|
.with_state(services);
|
||||||
|
|
|
||||||
|
|
@ -19,5 +19,5 @@ lettre = { workspace = true }
|
||||||
contracts = { path = "../../crates/contracts" }
|
contracts = { path = "../../crates/contracts" }
|
||||||
cache = { path = "../../crates/cache" }
|
cache = { path = "../../crates/cache" }
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
|
anyhow = { workspace = true }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,16 +15,16 @@ use uuid::Uuid;
|
||||||
pub fn router() -> Router<AppState> {
|
pub fn router() -> Router<AppState> {
|
||||||
Router::new()
|
Router::new()
|
||||||
.route("/", get(list_pending))
|
.route("/", get(list_pending))
|
||||||
.route("/profiles/company/:user_id/approve", post(approve_company_profile))
|
.route("/profiles/company/{user_id}/approve", post(approve_company_profile))
|
||||||
.route("/profiles/company/:user_id/reject", post(reject_company_profile))
|
.route("/profiles/company/{user_id}/reject", post(reject_company_profile))
|
||||||
.route("/profiles/customer/:user_id/approve", post(approve_customer_profile))
|
.route("/profiles/customer/{user_id}/approve", post(approve_customer_profile))
|
||||||
.route("/profiles/customer/:user_id/reject", post(reject_customer_profile))
|
.route("/profiles/customer/{user_id}/reject", post(reject_customer_profile))
|
||||||
.route("/profiles/professional/:role_key/:user_id/approve", post(approve_professional_profile))
|
.route("/profiles/professional/{role_key}/{user_id}/approve", post(approve_professional_profile))
|
||||||
.route("/profiles/professional/:role_key/:user_id/reject", post(reject_professional_profile))
|
.route("/profiles/professional/{role_key}/{user_id}/reject", post(reject_professional_profile))
|
||||||
.route("/jobs/:id/approve", post(approve_job))
|
.route("/jobs/{id}/approve", post(approve_job))
|
||||||
.route("/jobs/:id/reject", post(reject_job))
|
.route("/jobs/{id}/reject", post(reject_job))
|
||||||
.route("/requirements/:id/approve", post(approve_requirement))
|
.route("/requirements/{id}/approve", post(approve_requirement))
|
||||||
.route("/requirements/:id/reject", post(reject_requirement))
|
.route("/requirements/{id}/reject", post(reject_requirement))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
|
|
@ -269,18 +269,18 @@ async fn list_pending(
|
||||||
Json(serde_json::json!({
|
Json(serde_json::json!({
|
||||||
"jobs": jobs,
|
"jobs": jobs,
|
||||||
"requirements": requirements,
|
"requirements": requirements,
|
||||||
"profiles": {
|
"profiles_summary": {
|
||||||
"company": company_profiles,
|
"company": company_profiles.len(),
|
||||||
"customer": customer_profiles,
|
"customer": customer_profiles.len(),
|
||||||
"photographer": photographer_profiles,
|
"photographer": photographer_profiles.len(),
|
||||||
"makeup_artist": makeup_profiles,
|
"makeup_artist": makeup_profiles.len(),
|
||||||
"tutor": tutor_profiles,
|
"tutor": tutor_profiles.len(),
|
||||||
"developer": developer_profiles,
|
"developer": developer_profiles.len(),
|
||||||
"video_editor": video_editor_profiles,
|
"video_editor": video_editor_profiles.len(),
|
||||||
"graphic_designer": graphic_designer_profiles,
|
"graphic_designer": graphic_designer_profiles.len(),
|
||||||
"social_media_manager": social_media_manager_profiles,
|
"social_media_manager": social_media_manager_profiles.len(),
|
||||||
"fitness_trainer": fitness_trainer_profiles,
|
"fitness_trainer": fitness_trainer_profiles.len(),
|
||||||
"catering_services": catering_profiles
|
"catering_services": catering_profiles.len()
|
||||||
},
|
},
|
||||||
"pagination": { "page": page, "limit": limit }
|
"pagination": { "page": page, "limit": limit }
|
||||||
})),
|
})),
|
||||||
|
|
|
||||||
|
|
@ -17,21 +17,21 @@ use uuid::Uuid;
|
||||||
pub fn onboarding_router() -> Router<AppState> {
|
pub fn onboarding_router() -> Router<AppState> {
|
||||||
Router::new()
|
Router::new()
|
||||||
.route("/", get(list_onboarding_configs).post(create_onboarding_config))
|
.route("/", get(list_onboarding_configs).post(create_onboarding_config))
|
||||||
.route("/:role_id", get(get_active_onboarding_config))
|
.route("/{role_id}", get(get_active_onboarding_config))
|
||||||
.route("/by-key/:role_key", get(get_onboarding_config_by_key))
|
.route("/by-key/{role_key}", get(get_onboarding_config_by_key))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dashboard_router() -> Router<AppState> {
|
pub fn dashboard_router() -> Router<AppState> {
|
||||||
Router::new()
|
Router::new()
|
||||||
.route("/", get(list_dashboard_configs).post(create_dashboard_config))
|
.route("/", get(list_dashboard_configs).post(create_dashboard_config))
|
||||||
.route("/:role_id", get(get_active_dashboard_config))
|
.route("/{role_id}", get(get_active_dashboard_config))
|
||||||
.route("/by-key/:role_key", get(get_dashboard_config_by_key))
|
.route("/by-key/{role_key}", get(get_dashboard_config_by_key))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn runtime_router() -> Router<AppState> {
|
pub fn runtime_router() -> Router<AppState> {
|
||||||
Router::new()
|
Router::new()
|
||||||
.route("/", get(get_my_runtime_config).post(create_runtime_config))
|
.route("/", get(get_my_runtime_config).post(create_runtime_config))
|
||||||
.route("/:role_id", get(get_active_runtime_config))
|
.route("/{role_id}", get(get_active_runtime_config))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_my_runtime_config(
|
async fn get_my_runtime_config(
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ pub fn router() -> Router<AppState> {
|
||||||
Router::new()
|
Router::new()
|
||||||
.route("/", get(list_notifications))
|
.route("/", get(list_notifications))
|
||||||
.route("/unread-count", get(unread_count))
|
.route("/unread-count", get(unread_count))
|
||||||
.route("/:id/read", patch(mark_read))
|
.route("/{id}/read", patch(mark_read))
|
||||||
.route("/read-all", patch(mark_all_read))
|
.route("/read-all", patch(mark_all_read))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -83,8 +83,8 @@ async fn get_state(
|
||||||
let response = match onboarding_state {
|
let response = match onboarding_state {
|
||||||
Some(s) => serde_json::json!({
|
Some(s) => serde_json::json!({
|
||||||
"status": s.status,
|
"status": s.status,
|
||||||
"currentStep": s.progress_json.as_ref()
|
"currentStep": s.progress_json
|
||||||
.and_then(|p| p.get("step"))
|
.get("step")
|
||||||
.and_then(|v| v.as_i64())
|
.and_then(|v| v.as_i64())
|
||||||
.unwrap_or(0),
|
.unwrap_or(0),
|
||||||
"progress": s.progress_json,
|
"progress": s.progress_json,
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ use db::models::role::{CreateRolePayload, RoleRepository};
|
||||||
pub fn router() -> Router<AppState> {
|
pub fn router() -> Router<AppState> {
|
||||||
Router::new()
|
Router::new()
|
||||||
.route("/", get(list_roles).post(create_role))
|
.route("/", get(list_roles).post(create_role))
|
||||||
.route("/:key", get(get_role_by_key))
|
.route("/{key}", get(get_role_by_key))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_role(
|
async fn create_role(
|
||||||
|
|
|
||||||
|
|
@ -13,4 +13,5 @@ chrono = { workspace = true }
|
||||||
uuid = { workspace = true }
|
uuid = { workspace = true }
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
axum = { workspace = true }
|
axum = { workspace = true }
|
||||||
|
async-trait = { workspace = true }
|
||||||
db = { path = "../db" }
|
db = { path = "../db" }
|
||||||
|
|
|
||||||
|
|
@ -2,34 +2,39 @@ use axum::{
|
||||||
extract::FromRequestParts,
|
extract::FromRequestParts,
|
||||||
http::{request::Parts, StatusCode},
|
http::{request::Parts, StatusCode},
|
||||||
};
|
};
|
||||||
use axum::async_trait;
|
use std::future::Future;
|
||||||
|
|
||||||
pub struct RequireAuth(pub crate::jwt::Claims);
|
pub struct RequireAuth(pub crate::jwt::Claims);
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl<S> FromRequestParts<S> for RequireAuth
|
impl<S> FromRequestParts<S> for RequireAuth
|
||||||
where
|
where
|
||||||
S: Send + Sync,
|
S: Send + Sync,
|
||||||
{
|
{
|
||||||
type Rejection = (StatusCode, &'static str);
|
type Rejection = (StatusCode, &'static str);
|
||||||
|
|
||||||
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
|
fn from_request_parts(
|
||||||
let auth_header = parts
|
parts: &mut Parts,
|
||||||
|
_state: &S,
|
||||||
|
) -> impl Future<Output = Result<Self, Self::Rejection>> + Send {
|
||||||
|
let token = parts
|
||||||
.headers
|
.headers
|
||||||
.get("Authorization")
|
.get("Authorization")
|
||||||
.and_then(|value| value.to_str().ok())
|
.and_then(|value| value.to_str().ok())
|
||||||
.filter(|value| value.starts_with("Bearer "));
|
.filter(|value| value.starts_with("Bearer "))
|
||||||
|
.map(|header| header.trim_start_matches("Bearer ").to_string());
|
||||||
|
|
||||||
let token = match auth_header {
|
async move {
|
||||||
Some(header) => header.trim_start_matches("Bearer "),
|
let token = match token {
|
||||||
None => return Err((StatusCode::UNAUTHORIZED, "Missing Bearer token")),
|
Some(token) => token,
|
||||||
};
|
None => return Err((StatusCode::UNAUTHORIZED, "Missing Bearer token")),
|
||||||
|
};
|
||||||
|
|
||||||
let jwt_secret = std::env::var("JWT_SECRET").expect("JWT_SECRET must be set");
|
let jwt_secret = std::env::var("JWT_SECRET").expect("JWT_SECRET must be set");
|
||||||
|
|
||||||
match crate::jwt::verify_access_token(token, &jwt_secret) {
|
match crate::jwt::verify_access_token(&token, &jwt_secret) {
|
||||||
Ok(claims) => Ok(RequireAuth(claims)),
|
Ok(claims) => Ok(RequireAuth(claims)),
|
||||||
Err(_) => Err((StatusCode::UNAUTHORIZED, "Invalid or expired token")),
|
Err(_) => Err((StatusCode::UNAUTHORIZED, "Invalid or expired token")),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
2
crates/cache/src/jobs.rs
vendored
2
crates/cache/src/jobs.rs
vendored
|
|
@ -36,7 +36,7 @@ pub async fn invalidate_marketplace(
|
||||||
let pattern = format!("jobs:marketplace:{profession}:*");
|
let pattern = format!("jobs:marketplace:{profession}:*");
|
||||||
let keys: Vec<String> = redis.keys(pattern).await?;
|
let keys: Vec<String> = redis.keys(pattern).await?;
|
||||||
if !keys.is_empty() {
|
if !keys.is_empty() {
|
||||||
redis.del(keys).await?;
|
redis.del::<_, ()>(keys).await?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
2
crates/cache/src/otp.rs
vendored
2
crates/cache/src/otp.rs
vendored
|
|
@ -43,7 +43,7 @@ pub async fn record_resend(redis: &mut RedisPool, user_id: &str) -> Result<(), r
|
||||||
let count: i64 = redis.incr(&key, 1i64).await?;
|
let count: i64 = redis.incr(&key, 1i64).await?;
|
||||||
// Only set expiry on first increment so window is fixed from first request
|
// Only set expiry on first increment so window is fixed from first request
|
||||||
if count == 1 {
|
if count == 1 {
|
||||||
redis.expire(&key, RESEND_WINDOW_SECS).await?;
|
redis.expire::<_, ()>(&key, RESEND_WINDOW_SECS).await?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
2
crates/cache/src/rate_limit.rs
vendored
2
crates/cache/src/rate_limit.rs
vendored
|
|
@ -24,7 +24,7 @@ pub async fn check(
|
||||||
let key = format!("rate:{namespace}:{identifier}");
|
let key = format!("rate:{namespace}:{identifier}");
|
||||||
let count: i64 = redis.incr(&key, 1i64).await?;
|
let count: i64 = redis.incr(&key, 1i64).await?;
|
||||||
if count == 1 {
|
if count == 1 {
|
||||||
redis.expire(&key, window_secs).await?;
|
redis.expire::<_, ()>(&key, window_secs).await?;
|
||||||
}
|
}
|
||||||
Ok(count <= max)
|
Ok(count <= max)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,8 @@ tracing = { workspace = true }
|
||||||
uuid = { workspace = true }
|
uuid = { workspace = true }
|
||||||
chrono = { workspace = true }
|
chrono = { workspace = true }
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
|
sqlx = { workspace = true }
|
||||||
|
async-trait = { workspace = true }
|
||||||
jsonwebtoken = "9.3"
|
jsonwebtoken = "9.3"
|
||||||
db = { path = "../db" }
|
db = { path = "../db" }
|
||||||
cache = { path = "../cache" }
|
cache = { path = "../cache" }
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ use axum::{
|
||||||
};
|
};
|
||||||
use jsonwebtoken::{decode, DecodingKey, Validation, Algorithm};
|
use jsonwebtoken::{decode, DecodingKey, Validation, Algorithm};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::future::Future;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
// ── JWT Claims ────────────────────────────────────────────────────────────────
|
// ── JWT Claims ────────────────────────────────────────────────────────────────
|
||||||
|
|
@ -31,49 +32,54 @@ pub struct AuthUser {
|
||||||
pub claims: Claims,
|
pub claims: Claims,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[axum::async_trait]
|
|
||||||
impl<S> FromRequestParts<S> for AuthUser
|
impl<S> FromRequestParts<S> for AuthUser
|
||||||
where
|
where
|
||||||
S: Send + Sync,
|
S: Send + Sync,
|
||||||
{
|
{
|
||||||
type Rejection = AuthError;
|
type Rejection = AuthError;
|
||||||
|
|
||||||
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
|
fn from_request_parts(
|
||||||
// 1. Extract Authorization header
|
parts: &mut Parts,
|
||||||
|
_state: &S,
|
||||||
|
) -> impl Future<Output = Result<Self, Self::Rejection>> + Send {
|
||||||
let auth_header = parts
|
let auth_header = parts
|
||||||
.headers
|
.headers
|
||||||
.get("Authorization")
|
.get("Authorization")
|
||||||
.and_then(|v| v.to_str().ok())
|
.and_then(|v| v.to_str().ok())
|
||||||
.ok_or(AuthError::MissingToken)?;
|
.map(str::to_string);
|
||||||
|
|
||||||
// 2. Strip "Bearer " prefix
|
async move {
|
||||||
let token = auth_header
|
let auth_header = auth_header.ok_or(AuthError::MissingToken)?;
|
||||||
|
|
||||||
|
// 2. Strip "Bearer " prefix
|
||||||
|
let token = auth_header
|
||||||
.strip_prefix("Bearer ")
|
.strip_prefix("Bearer ")
|
||||||
.ok_or(AuthError::InvalidToken)?;
|
.ok_or(AuthError::InvalidToken)?;
|
||||||
|
|
||||||
// 3. Decode & verify
|
// 3. Decode & verify
|
||||||
let jwt_secret = std::env::var("JWT_SECRET")
|
let jwt_secret = std::env::var("JWT_SECRET")
|
||||||
.expect("JWT_SECRET must be set — refusing to start with insecure default");
|
.expect("JWT_SECRET must be set — refusing to start with insecure default");
|
||||||
|
|
||||||
let token_data = decode::<Claims>(
|
let token_data = decode::<Claims>(
|
||||||
token,
|
token,
|
||||||
&DecodingKey::from_secret(jwt_secret.as_bytes()),
|
&DecodingKey::from_secret(jwt_secret.as_bytes()),
|
||||||
&Validation::new(Algorithm::HS256),
|
&Validation::new(Algorithm::HS256),
|
||||||
)
|
)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
tracing::debug!("JWT decode error: {}", e);
|
tracing::debug!("JWT decode error: {}", e);
|
||||||
AuthError::InvalidToken
|
AuthError::InvalidToken
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// 4. Parse user_id as UUID
|
// 4. Parse user_id as UUID
|
||||||
let user_id = Uuid::parse_str(&token_data.claims.sub)
|
let user_id = Uuid::parse_str(&token_data.claims.sub)
|
||||||
.map_err(|_| AuthError::InvalidToken)?;
|
.map_err(|_| AuthError::InvalidToken)?;
|
||||||
|
|
||||||
Ok(AuthUser {
|
Ok(AuthUser {
|
||||||
user_id,
|
user_id,
|
||||||
email: token_data.claims.email.clone(),
|
email: token_data.claims.email.clone(),
|
||||||
claims: token_data.claims,
|
claims: token_data.claims,
|
||||||
})
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -142,7 +142,10 @@ async fn send_lead_request(
|
||||||
)
|
)
|
||||||
.into_response()
|
.into_response()
|
||||||
}
|
}
|
||||||
Err(e) => return (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
|
Err(e) => {
|
||||||
|
tracing::error!("Failed to check profile approval: {}", e);
|
||||||
|
return (StatusCode::INTERNAL_SERVER_ERROR, "Failed to validate profile approval").into_response();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Deduplication: one lead per requirement per professional (24 h) ────────
|
// ── Deduplication: one lead per requirement per professional (24 h) ────────
|
||||||
|
|
|
||||||
|
|
@ -204,6 +204,126 @@ ALTER TABLE services
|
||||||
ALTER TABLE lead_requests
|
ALTER TABLE lead_requests
|
||||||
ADD COLUMN IF NOT EXISTS professional_user_id UUID REFERENCES users(id) ON DELETE CASCADE;
|
ADD COLUMN IF NOT EXISTS professional_user_id UUID REFERENCES users(id) ON DELETE CASCADE;
|
||||||
|
|
||||||
|
-- Backfill columns when legacy minimal profile tables already exist.
|
||||||
|
-- This keeps migrations idempotent while upgrading old schemas to the new profile shape.
|
||||||
|
ALTER TABLE photographer_profiles
|
||||||
|
ADD COLUMN IF NOT EXISTS display_name VARCHAR(255) NOT NULL DEFAULT '',
|
||||||
|
ADD COLUMN IF NOT EXISTS bio TEXT,
|
||||||
|
ADD COLUMN IF NOT EXISTS location VARCHAR(255),
|
||||||
|
ADD COLUMN IF NOT EXISTS specialties TEXT[] DEFAULT '{}',
|
||||||
|
ADD COLUMN IF NOT EXISTS camera_brands TEXT[] DEFAULT '{}',
|
||||||
|
ADD COLUMN IF NOT EXISTS studio_available BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
ADD COLUMN IF NOT EXISTS outdoor_shoots BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
ADD COLUMN IF NOT EXISTS travel_radius_km INTEGER DEFAULT 50,
|
||||||
|
ADD COLUMN IF NOT EXISTS starting_price_inr INTEGER DEFAULT 0,
|
||||||
|
ADD COLUMN IF NOT EXISTS status VARCHAR(50) NOT NULL DEFAULT 'PENDING',
|
||||||
|
ADD COLUMN IF NOT EXISTS rejection_reason TEXT,
|
||||||
|
ADD COLUMN IF NOT EXISTS approved_at TIMESTAMPTZ;
|
||||||
|
|
||||||
|
ALTER TABLE tutor_profiles
|
||||||
|
ADD COLUMN IF NOT EXISTS display_name VARCHAR(255) NOT NULL DEFAULT '',
|
||||||
|
ADD COLUMN IF NOT EXISTS bio TEXT,
|
||||||
|
ADD COLUMN IF NOT EXISTS location VARCHAR(255),
|
||||||
|
ADD COLUMN IF NOT EXISTS subjects TEXT[] DEFAULT '{}',
|
||||||
|
ADD COLUMN IF NOT EXISTS board_types TEXT[] DEFAULT '{}',
|
||||||
|
ADD COLUMN IF NOT EXISTS qualification VARCHAR(255),
|
||||||
|
ADD COLUMN IF NOT EXISTS teaches_online BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
ADD COLUMN IF NOT EXISTS teaches_offline BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
ADD COLUMN IF NOT EXISTS hourly_rate_inr INTEGER DEFAULT 0,
|
||||||
|
ADD COLUMN IF NOT EXISTS status VARCHAR(50) NOT NULL DEFAULT 'PENDING',
|
||||||
|
ADD COLUMN IF NOT EXISTS rejection_reason TEXT,
|
||||||
|
ADD COLUMN IF NOT EXISTS approved_at TIMESTAMPTZ;
|
||||||
|
|
||||||
|
ALTER TABLE makeup_artist_profiles
|
||||||
|
ADD COLUMN IF NOT EXISTS display_name VARCHAR(255) NOT NULL DEFAULT '',
|
||||||
|
ADD COLUMN IF NOT EXISTS location VARCHAR(255),
|
||||||
|
ADD COLUMN IF NOT EXISTS specializations TEXT[] DEFAULT '{}',
|
||||||
|
ADD COLUMN IF NOT EXISTS kit_brands TEXT[] DEFAULT '{}',
|
||||||
|
ADD COLUMN IF NOT EXISTS home_service BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
ADD COLUMN IF NOT EXISTS studio_available BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
ADD COLUMN IF NOT EXISTS starting_price_inr INTEGER DEFAULT 0,
|
||||||
|
ADD COLUMN IF NOT EXISTS status VARCHAR(50) NOT NULL DEFAULT 'PENDING',
|
||||||
|
ADD COLUMN IF NOT EXISTS rejection_reason TEXT,
|
||||||
|
ADD COLUMN IF NOT EXISTS approved_at TIMESTAMPTZ;
|
||||||
|
|
||||||
|
ALTER TABLE developer_profiles
|
||||||
|
ADD COLUMN IF NOT EXISTS display_name VARCHAR(255) NOT NULL DEFAULT '',
|
||||||
|
ADD COLUMN IF NOT EXISTS location VARCHAR(255),
|
||||||
|
ADD COLUMN IF NOT EXISTS tech_stack TEXT[] DEFAULT '{}',
|
||||||
|
ADD COLUMN IF NOT EXISTS github_url VARCHAR(500),
|
||||||
|
ADD COLUMN IF NOT EXISTS portfolio_url VARCHAR(500),
|
||||||
|
ADD COLUMN IF NOT EXISTS availability VARCHAR(50) DEFAULT 'FULL_TIME',
|
||||||
|
ADD COLUMN IF NOT EXISTS hourly_rate_inr INTEGER DEFAULT 0,
|
||||||
|
ADD COLUMN IF NOT EXISTS remote_ok BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
ADD COLUMN IF NOT EXISTS status VARCHAR(50) NOT NULL DEFAULT 'PENDING',
|
||||||
|
ADD COLUMN IF NOT EXISTS rejection_reason TEXT,
|
||||||
|
ADD COLUMN IF NOT EXISTS approved_at TIMESTAMPTZ;
|
||||||
|
|
||||||
|
ALTER TABLE video_editor_profiles
|
||||||
|
ADD COLUMN IF NOT EXISTS display_name VARCHAR(255) NOT NULL DEFAULT '',
|
||||||
|
ADD COLUMN IF NOT EXISTS location VARCHAR(255),
|
||||||
|
ADD COLUMN IF NOT EXISTS software_skills TEXT[] DEFAULT '{}',
|
||||||
|
ADD COLUMN IF NOT EXISTS style_tags TEXT[] DEFAULT '{}',
|
||||||
|
ADD COLUMN IF NOT EXISTS turnaround_days INTEGER DEFAULT 7,
|
||||||
|
ADD COLUMN IF NOT EXISTS reel_url VARCHAR(500),
|
||||||
|
ADD COLUMN IF NOT EXISTS starting_price_inr INTEGER DEFAULT 0,
|
||||||
|
ADD COLUMN IF NOT EXISTS status VARCHAR(50) NOT NULL DEFAULT 'PENDING',
|
||||||
|
ADD COLUMN IF NOT EXISTS rejection_reason TEXT,
|
||||||
|
ADD COLUMN IF NOT EXISTS approved_at TIMESTAMPTZ;
|
||||||
|
|
||||||
|
ALTER TABLE graphic_designer_profiles
|
||||||
|
ADD COLUMN IF NOT EXISTS display_name VARCHAR(255) NOT NULL DEFAULT '',
|
||||||
|
ADD COLUMN IF NOT EXISTS location VARCHAR(255),
|
||||||
|
ADD COLUMN IF NOT EXISTS design_tools TEXT[] DEFAULT '{}',
|
||||||
|
ADD COLUMN IF NOT EXISTS style_tags TEXT[] DEFAULT '{}',
|
||||||
|
ADD COLUMN IF NOT EXISTS brand_experience BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
ADD COLUMN IF NOT EXISTS portfolio_url VARCHAR(500),
|
||||||
|
ADD COLUMN IF NOT EXISTS starting_price_inr INTEGER DEFAULT 0,
|
||||||
|
ADD COLUMN IF NOT EXISTS status VARCHAR(50) NOT NULL DEFAULT 'PENDING',
|
||||||
|
ADD COLUMN IF NOT EXISTS rejection_reason TEXT,
|
||||||
|
ADD COLUMN IF NOT EXISTS approved_at TIMESTAMPTZ;
|
||||||
|
|
||||||
|
ALTER TABLE social_media_manager_profiles
|
||||||
|
ADD COLUMN IF NOT EXISTS display_name VARCHAR(255) NOT NULL DEFAULT '',
|
||||||
|
ADD COLUMN IF NOT EXISTS location VARCHAR(255),
|
||||||
|
ADD COLUMN IF NOT EXISTS platforms TEXT[] DEFAULT '{}',
|
||||||
|
ADD COLUMN IF NOT EXISTS industries TEXT[] DEFAULT '{}',
|
||||||
|
ADD COLUMN IF NOT EXISTS content_types TEXT[] DEFAULT '{}',
|
||||||
|
ADD COLUMN IF NOT EXISTS avg_follower_growth_pct INTEGER DEFAULT 0,
|
||||||
|
ADD COLUMN IF NOT EXISTS starting_price_inr INTEGER DEFAULT 0,
|
||||||
|
ADD COLUMN IF NOT EXISTS status VARCHAR(50) NOT NULL DEFAULT 'PENDING',
|
||||||
|
ADD COLUMN IF NOT EXISTS rejection_reason TEXT,
|
||||||
|
ADD COLUMN IF NOT EXISTS approved_at TIMESTAMPTZ;
|
||||||
|
|
||||||
|
ALTER TABLE fitness_trainer_profiles
|
||||||
|
ADD COLUMN IF NOT EXISTS display_name VARCHAR(255) NOT NULL DEFAULT '',
|
||||||
|
ADD COLUMN IF NOT EXISTS location VARCHAR(255),
|
||||||
|
ADD COLUMN IF NOT EXISTS disciplines TEXT[] DEFAULT '{}',
|
||||||
|
ADD COLUMN IF NOT EXISTS certifications TEXT[] DEFAULT '{}',
|
||||||
|
ADD COLUMN IF NOT EXISTS online_sessions BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
ADD COLUMN IF NOT EXISTS home_visits BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
ADD COLUMN IF NOT EXISTS gym_based BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
ADD COLUMN IF NOT EXISTS per_session_rate_inr INTEGER DEFAULT 0,
|
||||||
|
ADD COLUMN IF NOT EXISTS status VARCHAR(50) NOT NULL DEFAULT 'PENDING',
|
||||||
|
ADD COLUMN IF NOT EXISTS rejection_reason TEXT,
|
||||||
|
ADD COLUMN IF NOT EXISTS approved_at TIMESTAMPTZ;
|
||||||
|
|
||||||
|
ALTER TABLE catering_service_profiles
|
||||||
|
ADD COLUMN IF NOT EXISTS business_name VARCHAR(255) NOT NULL DEFAULT '',
|
||||||
|
ADD COLUMN IF NOT EXISTS location VARCHAR(255),
|
||||||
|
ADD COLUMN IF NOT EXISTS cuisine_types TEXT[] DEFAULT '{}',
|
||||||
|
ADD COLUMN IF NOT EXISTS event_types TEXT[] DEFAULT '{}',
|
||||||
|
ADD COLUMN IF NOT EXISTS min_guests INTEGER DEFAULT 10,
|
||||||
|
ADD COLUMN IF NOT EXISTS max_guests INTEGER DEFAULT 500,
|
||||||
|
ADD COLUMN IF NOT EXISTS has_setup_team BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
ADD COLUMN IF NOT EXISTS has_serving_staff BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
ADD COLUMN IF NOT EXISTS price_per_head_inr INTEGER DEFAULT 0,
|
||||||
|
ADD COLUMN IF NOT EXISTS status VARCHAR(50) NOT NULL DEFAULT 'PENDING',
|
||||||
|
ADD COLUMN IF NOT EXISTS rejection_reason TEXT,
|
||||||
|
ADD COLUMN IF NOT EXISTS approved_at TIMESTAMPTZ;
|
||||||
|
|
||||||
|
ALTER TABLE lead_requests
|
||||||
|
ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW();
|
||||||
-- Indexes
|
-- Indexes
|
||||||
CREATE INDEX IF NOT EXISTS idx_photographer_profiles_status ON photographer_profiles(status);
|
CREATE INDEX IF NOT EXISTS idx_photographer_profiles_status ON photographer_profiles(status);
|
||||||
CREATE INDEX IF NOT EXISTS idx_tutor_profiles_status ON tutor_profiles(status);
|
CREATE INDEX IF NOT EXISTS idx_tutor_profiles_status ON tutor_profiles(status);
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
CREATE TABLE IF NOT EXISTS reviews (
|
CREATE TABLE IF NOT EXISTS reviews (
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
lead_request_id UUID NOT NULL REFERENCES lead_requests(id) ON DELETE CASCADE UNIQUE,
|
lead_request_id UUID NOT NULL REFERENCES lead_requests(id) ON DELETE CASCADE UNIQUE,
|
||||||
customer_id UUID NOT NULL REFERENCES customers(id) ON DELETE CASCADE,
|
customer_id UUID NOT NULL REFERENCES customer_profiles(id) ON DELETE CASCADE,
|
||||||
professional_id UUID NOT NULL REFERENCES professionals(id) ON DELETE CASCADE,
|
professional_id UUID NOT NULL REFERENCES professionals(id) ON DELETE CASCADE,
|
||||||
rating SMALLINT NOT NULL CHECK (rating >= 1 AND rating <= 5),
|
rating SMALLINT NOT NULL CHECK (rating >= 1 AND rating <= 5),
|
||||||
comment TEXT,
|
comment TEXT,
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ impl CateringServiceRepository {
|
||||||
sqlx::query_as!(
|
sqlx::query_as!(
|
||||||
CateringServiceProfile,
|
CateringServiceProfile,
|
||||||
r#"SELECT id, user_id, business_name, bio, location,
|
r#"SELECT id, user_id, business_name, bio, location,
|
||||||
custom_data as "custom_data: Option<serde_json::Value>",
|
custom_data,
|
||||||
status, created_at, updated_at
|
status, created_at, updated_at
|
||||||
FROM catering_service_profiles WHERE user_id = $1"#,
|
FROM catering_service_profiles WHERE user_id = $1"#,
|
||||||
user_id
|
user_id
|
||||||
|
|
@ -52,7 +52,7 @@ impl CateringServiceRepository {
|
||||||
custom_data = EXCLUDED.custom_data,
|
custom_data = EXCLUDED.custom_data,
|
||||||
updated_at = NOW()
|
updated_at = NOW()
|
||||||
RETURNING id, user_id, business_name, bio, location,
|
RETURNING id, user_id, business_name, bio, location,
|
||||||
custom_data as "custom_data: Option<serde_json::Value>",
|
custom_data,
|
||||||
status, created_at, updated_at"#,
|
status, created_at, updated_at"#,
|
||||||
user_id, p.business_name, p.bio, p.location, p.custom_data
|
user_id, p.business_name, p.bio, p.location, p.custom_data
|
||||||
).fetch_one(pool).await
|
).fetch_one(pool).await
|
||||||
|
|
|
||||||
|
|
@ -183,7 +183,7 @@ impl ConfigRepository {
|
||||||
r#"
|
r#"
|
||||||
UPDATE dashboard_configs
|
UPDATE dashboard_configs
|
||||||
SET is_active = false
|
SET is_active = false
|
||||||
WHERE role_id = $1 AND audience = $2 AND is_active = true
|
WHERE role_id = $1 AND audience = $2::text AND is_active = true
|
||||||
"#,
|
"#,
|
||||||
payload.role_id,
|
payload.role_id,
|
||||||
payload.audience
|
payload.audience
|
||||||
|
|
@ -198,9 +198,9 @@ impl ConfigRepository {
|
||||||
INSERT INTO dashboard_configs (role_id, audience, config_json, version, is_active)
|
INSERT INTO dashboard_configs (role_id, audience, config_json, version, is_active)
|
||||||
VALUES (
|
VALUES (
|
||||||
$1,
|
$1,
|
||||||
$2,
|
$2::text,
|
||||||
$3,
|
$3,
|
||||||
COALESCE((SELECT MAX(version) FROM dashboard_configs WHERE role_id = $1 AND audience = $2), 0) + 1,
|
COALESCE((SELECT MAX(version) FROM dashboard_configs WHERE role_id = $1 AND audience = $2::text), 0) + 1,
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
RETURNING id, role_id, audience, config_json, version, is_active, updated_at
|
RETURNING id, role_id, audience, config_json, version, is_active, updated_at
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ pub struct CustomerProfile {
|
||||||
pub phone: Option<String>,
|
pub phone: Option<String>,
|
||||||
pub city: Option<String>,
|
pub city: Option<String>,
|
||||||
pub area: Option<String>,
|
pub area: Option<String>,
|
||||||
pub preferred_professions: Vec<String>,
|
pub preferred_professions: Option<Vec<String>>,
|
||||||
pub active_requirement_count: i32,
|
pub active_requirement_count: i32,
|
||||||
pub status: String,
|
pub status: String,
|
||||||
pub bio: Option<String>,
|
pub bio: Option<String>,
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ impl DeveloperRepository {
|
||||||
sqlx::query_as!(
|
sqlx::query_as!(
|
||||||
DeveloperProfile,
|
DeveloperProfile,
|
||||||
r#"SELECT id, user_id, display_name, bio, location,
|
r#"SELECT id, user_id, display_name, bio, location,
|
||||||
custom_data as "custom_data: Option<serde_json::Value>",
|
custom_data,
|
||||||
status, created_at, updated_at
|
status, created_at, updated_at
|
||||||
FROM developer_profiles WHERE user_id = $1"#,
|
FROM developer_profiles WHERE user_id = $1"#,
|
||||||
user_id
|
user_id
|
||||||
|
|
@ -50,7 +50,7 @@ impl DeveloperRepository {
|
||||||
custom_data = EXCLUDED.custom_data,
|
custom_data = EXCLUDED.custom_data,
|
||||||
updated_at = NOW()
|
updated_at = NOW()
|
||||||
RETURNING id, user_id, display_name, bio, location,
|
RETURNING id, user_id, display_name, bio, location,
|
||||||
custom_data as "custom_data: Option<serde_json::Value>",
|
custom_data,
|
||||||
status, created_at, updated_at"#,
|
status, created_at, updated_at"#,
|
||||||
user_id, p.display_name, p.bio, p.location, p.custom_data
|
user_id, p.display_name, p.bio, p.location, p.custom_data
|
||||||
).fetch_one(pool).await
|
).fetch_one(pool).await
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ impl FitnessTrainerRepository {
|
||||||
sqlx::query_as!(
|
sqlx::query_as!(
|
||||||
FitnessTrainerProfile,
|
FitnessTrainerProfile,
|
||||||
r#"SELECT id, user_id, display_name, bio, location,
|
r#"SELECT id, user_id, display_name, bio, location,
|
||||||
custom_data as "custom_data: Option<serde_json::Value>",
|
custom_data,
|
||||||
status, created_at, updated_at
|
status, created_at, updated_at
|
||||||
FROM fitness_trainer_profiles WHERE user_id = $1"#,
|
FROM fitness_trainer_profiles WHERE user_id = $1"#,
|
||||||
user_id
|
user_id
|
||||||
|
|
@ -50,7 +50,7 @@ impl FitnessTrainerRepository {
|
||||||
custom_data = EXCLUDED.custom_data,
|
custom_data = EXCLUDED.custom_data,
|
||||||
updated_at = NOW()
|
updated_at = NOW()
|
||||||
RETURNING id, user_id, display_name, bio, location,
|
RETURNING id, user_id, display_name, bio, location,
|
||||||
custom_data as "custom_data: Option<serde_json::Value>",
|
custom_data,
|
||||||
status, created_at, updated_at"#,
|
status, created_at, updated_at"#,
|
||||||
user_id, p.display_name, p.bio, p.location, p.custom_data
|
user_id, p.display_name, p.bio, p.location, p.custom_data
|
||||||
).fetch_one(pool).await
|
).fetch_one(pool).await
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ impl GraphicDesignerRepository {
|
||||||
sqlx::query_as!(
|
sqlx::query_as!(
|
||||||
GraphicDesignerProfile,
|
GraphicDesignerProfile,
|
||||||
r#"SELECT id, user_id, display_name, bio, location,
|
r#"SELECT id, user_id, display_name, bio, location,
|
||||||
custom_data as "custom_data: Option<serde_json::Value>",
|
custom_data,
|
||||||
status, created_at, updated_at
|
status, created_at, updated_at
|
||||||
FROM graphic_designer_profiles WHERE user_id = $1"#,
|
FROM graphic_designer_profiles WHERE user_id = $1"#,
|
||||||
user_id
|
user_id
|
||||||
|
|
@ -50,7 +50,7 @@ impl GraphicDesignerRepository {
|
||||||
custom_data = EXCLUDED.custom_data,
|
custom_data = EXCLUDED.custom_data,
|
||||||
updated_at = NOW()
|
updated_at = NOW()
|
||||||
RETURNING id, user_id, display_name, bio, location,
|
RETURNING id, user_id, display_name, bio, location,
|
||||||
custom_data as "custom_data: Option<serde_json::Value>",
|
custom_data,
|
||||||
status, created_at, updated_at"#,
|
status, created_at, updated_at"#,
|
||||||
user_id, p.display_name, p.bio, p.location, p.custom_data
|
user_id, p.display_name, p.bio, p.location, p.custom_data
|
||||||
).fetch_one(pool).await
|
).fetch_one(pool).await
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ pub struct Job {
|
||||||
pub salary_min: Option<i32>,
|
pub salary_min: Option<i32>,
|
||||||
pub salary_max: Option<i32>,
|
pub salary_max: Option<i32>,
|
||||||
pub experience_years: Option<i32>,
|
pub experience_years: Option<i32>,
|
||||||
pub skills: Vec<String>,
|
pub skills: Option<Vec<String>>,
|
||||||
pub status: String, // DRAFT, PENDING_APPROVAL, LIVE, EXPIRED, CLOSED, REJECTED
|
pub status: String, // DRAFT, PENDING_APPROVAL, LIVE, EXPIRED, CLOSED, REJECTED
|
||||||
pub rejection_reason: Option<String>,
|
pub rejection_reason: Option<String>,
|
||||||
pub expires_at: Option<DateTime<Utc>>,
|
pub expires_at: Option<DateTime<Utc>>,
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,8 @@ pub struct JobSeekerProfile {
|
||||||
pub full_name: Option<String>,
|
pub full_name: Option<String>,
|
||||||
pub location: Option<String>,
|
pub location: Option<String>,
|
||||||
pub summary: Option<String>,
|
pub summary: Option<String>,
|
||||||
pub experience_years: i32,
|
pub experience_years: Option<i32>,
|
||||||
pub skills: Vec<String>,
|
pub skills: Option<Vec<String>>,
|
||||||
pub resume_url: Option<String>,
|
pub resume_url: Option<String>,
|
||||||
pub active_application_count: i32,
|
pub active_application_count: i32,
|
||||||
pub status: String,
|
pub status: String,
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,8 @@ pub struct LeadRequest {
|
||||||
pub expires_at: DateTime<Utc>,
|
pub expires_at: DateTime<Utc>,
|
||||||
pub requested_at: DateTime<Utc>,
|
pub requested_at: DateTime<Utc>,
|
||||||
pub resolved_at: Option<DateTime<Utc>>,
|
pub resolved_at: Option<DateTime<Utc>>,
|
||||||
|
pub professional_user_id: Option<Uuid>,
|
||||||
|
pub updated_at: DateTime<Utc>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ impl MakeupArtistRepository {
|
||||||
sqlx::query_as!(
|
sqlx::query_as!(
|
||||||
MakeupArtistProfile,
|
MakeupArtistProfile,
|
||||||
r#"SELECT id, user_id, display_name, bio, location,
|
r#"SELECT id, user_id, display_name, bio, location,
|
||||||
custom_data as "custom_data: Option<serde_json::Value>",
|
custom_data,
|
||||||
status, created_at, updated_at
|
status, created_at, updated_at
|
||||||
FROM makeup_artist_profiles WHERE user_id = $1"#,
|
FROM makeup_artist_profiles WHERE user_id = $1"#,
|
||||||
user_id
|
user_id
|
||||||
|
|
@ -50,7 +50,7 @@ impl MakeupArtistRepository {
|
||||||
custom_data = EXCLUDED.custom_data,
|
custom_data = EXCLUDED.custom_data,
|
||||||
updated_at = NOW()
|
updated_at = NOW()
|
||||||
RETURNING id, user_id, display_name, bio, location,
|
RETURNING id, user_id, display_name, bio, location,
|
||||||
custom_data as "custom_data: Option<serde_json::Value>",
|
custom_data,
|
||||||
status, created_at, updated_at"#,
|
status, created_at, updated_at"#,
|
||||||
user_id, p.display_name, p.bio, p.location, p.custom_data
|
user_id, p.display_name, p.bio, p.location, p.custom_data
|
||||||
).fetch_one(pool).await
|
).fetch_one(pool).await
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ impl PhotographerRepository {
|
||||||
sqlx::query_as!(
|
sqlx::query_as!(
|
||||||
PhotographerProfile,
|
PhotographerProfile,
|
||||||
r#"SELECT id, user_id, display_name, bio, location,
|
r#"SELECT id, user_id, display_name, bio, location,
|
||||||
custom_data as "custom_data: Option<serde_json::Value>",
|
custom_data,
|
||||||
status, created_at, updated_at
|
status, created_at, updated_at
|
||||||
FROM photographer_profiles WHERE user_id = $1"#,
|
FROM photographer_profiles WHERE user_id = $1"#,
|
||||||
user_id
|
user_id
|
||||||
|
|
@ -50,7 +50,7 @@ impl PhotographerRepository {
|
||||||
custom_data = EXCLUDED.custom_data,
|
custom_data = EXCLUDED.custom_data,
|
||||||
updated_at = NOW()
|
updated_at = NOW()
|
||||||
RETURNING id, user_id, display_name, bio, location,
|
RETURNING id, user_id, display_name, bio, location,
|
||||||
custom_data as "custom_data: Option<serde_json::Value>",
|
custom_data,
|
||||||
status, created_at, updated_at"#,
|
status, created_at, updated_at"#,
|
||||||
user_id, p.display_name, p.bio, p.location, p.custom_data
|
user_id, p.display_name, p.bio, p.location, p.custom_data
|
||||||
).fetch_one(pool).await
|
).fetch_one(pool).await
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,8 @@ pub struct PortfolioItem {
|
||||||
pub tags: Option<Vec<String>>,
|
pub tags: Option<Vec<String>>,
|
||||||
pub created_at: DateTime<Utc>,
|
pub created_at: DateTime<Utc>,
|
||||||
pub updated_at: DateTime<Utc>,
|
pub updated_at: DateTime<Utc>,
|
||||||
|
pub user_id: Option<Uuid>,
|
||||||
|
pub profession_key: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, FromRow)]
|
#[derive(Debug, Serialize, Deserialize, FromRow)]
|
||||||
|
|
@ -41,6 +43,8 @@ pub struct Service {
|
||||||
pub is_active: bool,
|
pub is_active: bool,
|
||||||
pub created_at: DateTime<Utc>,
|
pub created_at: DateTime<Utc>,
|
||||||
pub updated_at: DateTime<Utc>,
|
pub updated_at: DateTime<Utc>,
|
||||||
|
pub user_id: Option<Uuid>,
|
||||||
|
pub profession_key: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, FromRow)]
|
#[derive(Debug, Serialize, Deserialize, FromRow)]
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ impl SocialMediaManagerRepository {
|
||||||
sqlx::query_as!(
|
sqlx::query_as!(
|
||||||
SocialMediaManagerProfile,
|
SocialMediaManagerProfile,
|
||||||
r#"SELECT id, user_id, display_name, bio, location,
|
r#"SELECT id, user_id, display_name, bio, location,
|
||||||
custom_data as "custom_data: Option<serde_json::Value>",
|
custom_data,
|
||||||
status, created_at, updated_at
|
status, created_at, updated_at
|
||||||
FROM social_media_manager_profiles WHERE user_id = $1"#,
|
FROM social_media_manager_profiles WHERE user_id = $1"#,
|
||||||
user_id
|
user_id
|
||||||
|
|
@ -50,7 +50,7 @@ impl SocialMediaManagerRepository {
|
||||||
custom_data = EXCLUDED.custom_data,
|
custom_data = EXCLUDED.custom_data,
|
||||||
updated_at = NOW()
|
updated_at = NOW()
|
||||||
RETURNING id, user_id, display_name, bio, location,
|
RETURNING id, user_id, display_name, bio, location,
|
||||||
custom_data as "custom_data: Option<serde_json::Value>",
|
custom_data,
|
||||||
status, created_at, updated_at"#,
|
status, created_at, updated_at"#,
|
||||||
user_id, p.display_name, p.bio, p.location, p.custom_data
|
user_id, p.display_name, p.bio, p.location, p.custom_data
|
||||||
).fetch_one(pool).await
|
).fetch_one(pool).await
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ impl TutorRepository {
|
||||||
sqlx::query_as!(
|
sqlx::query_as!(
|
||||||
TutorProfile,
|
TutorProfile,
|
||||||
r#"SELECT id, user_id, display_name, bio, location,
|
r#"SELECT id, user_id, display_name, bio, location,
|
||||||
custom_data as "custom_data: Option<serde_json::Value>",
|
custom_data,
|
||||||
status, created_at, updated_at
|
status, created_at, updated_at
|
||||||
FROM tutor_profiles WHERE user_id = $1"#,
|
FROM tutor_profiles WHERE user_id = $1"#,
|
||||||
user_id
|
user_id
|
||||||
|
|
@ -50,7 +50,7 @@ impl TutorRepository {
|
||||||
custom_data = EXCLUDED.custom_data,
|
custom_data = EXCLUDED.custom_data,
|
||||||
updated_at = NOW()
|
updated_at = NOW()
|
||||||
RETURNING id, user_id, display_name, bio, location,
|
RETURNING id, user_id, display_name, bio, location,
|
||||||
custom_data as "custom_data: Option<serde_json::Value>",
|
custom_data,
|
||||||
status, created_at, updated_at"#,
|
status, created_at, updated_at"#,
|
||||||
user_id, p.display_name, p.bio, p.location, p.custom_data
|
user_id, p.display_name, p.bio, p.location, p.custom_data
|
||||||
).fetch_one(pool).await
|
).fetch_one(pool).await
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ impl VideoEditorRepository {
|
||||||
sqlx::query_as!(
|
sqlx::query_as!(
|
||||||
VideoEditorProfile,
|
VideoEditorProfile,
|
||||||
r#"SELECT id, user_id, display_name, bio, location,
|
r#"SELECT id, user_id, display_name, bio, location,
|
||||||
custom_data as "custom_data: Option<serde_json::Value>",
|
custom_data,
|
||||||
status, created_at, updated_at
|
status, created_at, updated_at
|
||||||
FROM video_editor_profiles WHERE user_id = $1"#,
|
FROM video_editor_profiles WHERE user_id = $1"#,
|
||||||
user_id
|
user_id
|
||||||
|
|
@ -50,7 +50,7 @@ impl VideoEditorRepository {
|
||||||
custom_data = EXCLUDED.custom_data,
|
custom_data = EXCLUDED.custom_data,
|
||||||
updated_at = NOW()
|
updated_at = NOW()
|
||||||
RETURNING id, user_id, display_name, bio, location,
|
RETURNING id, user_id, display_name, bio, location,
|
||||||
custom_data as "custom_data: Option<serde_json::Value>",
|
custom_data,
|
||||||
status, created_at, updated_at"#,
|
status, created_at, updated_at"#,
|
||||||
user_id, p.display_name, p.bio, p.location, p.custom_data
|
user_id, p.display_name, p.bio, p.location, p.custom_data
|
||||||
).fetch_one(pool).await
|
).fetch_one(pool).await
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue