2026-03-17 20:42:51 +01:00
|
|
|
use chrono::{DateTime, Utc};
|
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
|
use sqlx::{FromRow, PgPool};
|
|
|
|
|
use uuid::Uuid;
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Serialize, Deserialize, FromRow)]
|
|
|
|
|
pub struct OnboardingConfigListItem {
|
|
|
|
|
pub id: Uuid,
|
|
|
|
|
pub role_id: Uuid,
|
|
|
|
|
pub role_key: String,
|
|
|
|
|
pub version: i32,
|
|
|
|
|
pub is_active: bool,
|
|
|
|
|
pub updated_at: DateTime<Utc>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Serialize, Deserialize, FromRow)]
|
|
|
|
|
pub struct OnboardingConfig {
|
|
|
|
|
pub id: Uuid,
|
|
|
|
|
pub role_id: Uuid,
|
|
|
|
|
pub schema_json: serde_json::Value,
|
|
|
|
|
pub version: i32,
|
|
|
|
|
pub is_active: bool,
|
|
|
|
|
pub updated_at: DateTime<Utc>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
|
|
|
pub struct CreateOnboardingConfigPayload {
|
|
|
|
|
pub role_id: Uuid,
|
|
|
|
|
pub schema_json: serde_json::Value,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Serialize, Deserialize, FromRow)]
|
|
|
|
|
pub struct DashboardConfigListItem {
|
|
|
|
|
pub id: Uuid,
|
|
|
|
|
pub role_id: Uuid,
|
|
|
|
|
pub role_key: String,
|
|
|
|
|
pub audience: String,
|
|
|
|
|
pub version: i32,
|
|
|
|
|
pub is_active: bool,
|
|
|
|
|
pub updated_at: DateTime<Utc>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Serialize, Deserialize, FromRow)]
|
|
|
|
|
pub struct DashboardConfig {
|
|
|
|
|
pub id: Uuid,
|
|
|
|
|
pub role_id: Uuid,
|
|
|
|
|
pub audience: String,
|
|
|
|
|
pub config_json: serde_json::Value,
|
|
|
|
|
pub version: i32,
|
|
|
|
|
pub is_active: bool,
|
|
|
|
|
pub updated_at: DateTime<Utc>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
|
|
|
pub struct CreateDashboardConfigPayload {
|
|
|
|
|
pub role_id: Uuid,
|
|
|
|
|
pub audience: String,
|
|
|
|
|
pub config_json: serde_json::Value,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Serialize, Deserialize, FromRow)]
|
|
|
|
|
pub struct RuntimeConfig {
|
|
|
|
|
pub id: Uuid,
|
|
|
|
|
pub role_id: Uuid,
|
|
|
|
|
pub config_json: serde_json::Value,
|
|
|
|
|
pub version: i32,
|
|
|
|
|
pub is_active: bool,
|
|
|
|
|
pub updated_at: DateTime<Utc>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
|
|
|
pub struct CreateRuntimeConfigPayload {
|
|
|
|
|
pub role_id: Uuid,
|
|
|
|
|
pub config_json: serde_json::Value,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub struct ConfigRepository;
|
|
|
|
|
|
|
|
|
|
impl ConfigRepository {
|
|
|
|
|
pub async fn create_onboarding_config(
|
|
|
|
|
pool: &PgPool,
|
|
|
|
|
payload: CreateOnboardingConfigPayload,
|
|
|
|
|
) -> Result<OnboardingConfig, sqlx::Error> {
|
|
|
|
|
// Soft-disable previous active configs for this role
|
|
|
|
|
sqlx::query!(
|
|
|
|
|
r#"
|
|
|
|
|
UPDATE onboarding_configs
|
|
|
|
|
SET is_active = false
|
|
|
|
|
WHERE role_id = $1 AND is_active = true
|
|
|
|
|
"#,
|
|
|
|
|
payload.role_id
|
|
|
|
|
)
|
|
|
|
|
.execute(pool)
|
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
|
|
// Insert new config
|
|
|
|
|
let config = sqlx::query_as!(
|
|
|
|
|
OnboardingConfig,
|
|
|
|
|
r#"
|
|
|
|
|
INSERT INTO onboarding_configs (role_id, schema_json, version, is_active)
|
|
|
|
|
VALUES (
|
|
|
|
|
$1,
|
|
|
|
|
$2,
|
|
|
|
|
COALESCE((SELECT MAX(version) FROM onboarding_configs WHERE role_id = $1), 0) + 1,
|
|
|
|
|
true
|
|
|
|
|
)
|
|
|
|
|
RETURNING id, role_id, schema_json, version, is_active, updated_at
|
|
|
|
|
"#,
|
|
|
|
|
payload.role_id,
|
|
|
|
|
payload.schema_json
|
|
|
|
|
)
|
|
|
|
|
.fetch_one(pool)
|
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
|
|
Ok(config)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub async fn get_active_onboarding_config(
|
|
|
|
|
pool: &PgPool,
|
|
|
|
|
role_id: Uuid,
|
|
|
|
|
) -> Result<OnboardingConfig, sqlx::Error> {
|
|
|
|
|
let config = sqlx::query_as!(
|
|
|
|
|
OnboardingConfig,
|
|
|
|
|
r#"
|
|
|
|
|
SELECT id, role_id, schema_json, version, is_active, updated_at
|
|
|
|
|
FROM onboarding_configs
|
|
|
|
|
WHERE role_id = $1 AND is_active = true
|
|
|
|
|
"#,
|
|
|
|
|
role_id
|
|
|
|
|
)
|
|
|
|
|
.fetch_one(pool)
|
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
|
|
Ok(config)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub async fn get_all_onboarding_configs(
|
|
|
|
|
pool: &PgPool,
|
|
|
|
|
) -> Result<Vec<OnboardingConfigListItem>, sqlx::Error> {
|
|
|
|
|
let configs = sqlx::query_as!(
|
|
|
|
|
OnboardingConfigListItem,
|
|
|
|
|
r#"
|
|
|
|
|
SELECT
|
|
|
|
|
c.id, c.role_id, r.key as role_key,
|
|
|
|
|
c.version, c.is_active, c.updated_at
|
|
|
|
|
FROM onboarding_configs c
|
|
|
|
|
JOIN roles r ON c.role_id = r.id
|
|
|
|
|
ORDER BY c.updated_at DESC
|
|
|
|
|
"#
|
|
|
|
|
)
|
|
|
|
|
.fetch_all(pool)
|
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
|
|
Ok(configs)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub async fn get_active_onboarding_by_role_key(
|
|
|
|
|
pool: &PgPool,
|
|
|
|
|
role_key: &str,
|
|
|
|
|
) -> Result<OnboardingConfig, sqlx::Error> {
|
|
|
|
|
let config = sqlx::query_as!(
|
|
|
|
|
OnboardingConfig,
|
|
|
|
|
r#"
|
|
|
|
|
SELECT c.id, c.role_id, c.schema_json, c.version, c.is_active, c.updated_at
|
|
|
|
|
FROM onboarding_configs c
|
|
|
|
|
JOIN roles r ON c.role_id = r.id
|
|
|
|
|
WHERE r.key = $1 AND c.is_active = true
|
|
|
|
|
"#,
|
|
|
|
|
role_key.to_uppercase()
|
|
|
|
|
)
|
|
|
|
|
.fetch_one(pool)
|
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
|
|
Ok(config)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub async fn create_dashboard_config(
|
|
|
|
|
pool: &PgPool,
|
|
|
|
|
payload: CreateDashboardConfigPayload,
|
|
|
|
|
) -> Result<DashboardConfig, sqlx::Error> {
|
|
|
|
|
// Soft-disable previous active configs for this role
|
|
|
|
|
sqlx::query!(
|
|
|
|
|
r#"
|
|
|
|
|
UPDATE dashboard_configs
|
|
|
|
|
SET is_active = false
|
2026-03-22 15:55:29 +01:00
|
|
|
WHERE role_id = $1 AND audience = $2::text AND is_active = true
|
2026-03-17 20:42:51 +01:00
|
|
|
"#,
|
|
|
|
|
payload.role_id,
|
|
|
|
|
payload.audience
|
|
|
|
|
)
|
|
|
|
|
.execute(pool)
|
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
|
|
// Insert new config
|
|
|
|
|
let config = sqlx::query_as!(
|
|
|
|
|
DashboardConfig,
|
|
|
|
|
r#"
|
|
|
|
|
INSERT INTO dashboard_configs (role_id, audience, config_json, version, is_active)
|
|
|
|
|
VALUES (
|
|
|
|
|
$1,
|
2026-03-22 15:55:29 +01:00
|
|
|
$2::text,
|
2026-03-17 20:42:51 +01:00
|
|
|
$3,
|
2026-03-22 15:55:29 +01:00
|
|
|
COALESCE((SELECT MAX(version) FROM dashboard_configs WHERE role_id = $1 AND audience = $2::text), 0) + 1,
|
2026-03-17 20:42:51 +01:00
|
|
|
true
|
|
|
|
|
)
|
|
|
|
|
RETURNING id, role_id, audience, config_json, version, is_active, updated_at
|
|
|
|
|
"#,
|
|
|
|
|
payload.role_id,
|
|
|
|
|
payload.audience,
|
|
|
|
|
payload.config_json
|
|
|
|
|
)
|
|
|
|
|
.fetch_one(pool)
|
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
|
|
Ok(config)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub async fn get_active_dashboard_config(
|
|
|
|
|
pool: &PgPool,
|
|
|
|
|
role_id: Uuid,
|
|
|
|
|
audience: &str,
|
|
|
|
|
) -> Result<DashboardConfig, sqlx::Error> {
|
|
|
|
|
let config = sqlx::query_as!(
|
|
|
|
|
DashboardConfig,
|
|
|
|
|
r#"
|
|
|
|
|
SELECT id, role_id, audience, config_json, version, is_active, updated_at
|
|
|
|
|
FROM dashboard_configs
|
|
|
|
|
WHERE role_id = $1 AND audience = $2 AND is_active = true
|
|
|
|
|
"#,
|
|
|
|
|
role_id,
|
|
|
|
|
audience
|
|
|
|
|
)
|
|
|
|
|
.fetch_one(pool)
|
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
|
|
Ok(config)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub async fn get_all_dashboard_configs(
|
|
|
|
|
pool: &PgPool,
|
|
|
|
|
) -> Result<Vec<DashboardConfigListItem>, sqlx::Error> {
|
|
|
|
|
let configs = sqlx::query_as!(
|
|
|
|
|
DashboardConfigListItem,
|
|
|
|
|
r#"
|
|
|
|
|
SELECT
|
|
|
|
|
c.id, c.role_id, r.key as role_key, c.audience,
|
|
|
|
|
c.version, c.is_active, c.updated_at
|
|
|
|
|
FROM dashboard_configs c
|
|
|
|
|
JOIN roles r ON c.role_id = r.id
|
|
|
|
|
ORDER BY c.updated_at DESC
|
|
|
|
|
"#
|
|
|
|
|
)
|
|
|
|
|
.fetch_all(pool)
|
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
|
|
Ok(configs)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub async fn get_active_dashboard_by_role_key(
|
|
|
|
|
pool: &PgPool,
|
|
|
|
|
role_key: &str,
|
|
|
|
|
audience: &str,
|
|
|
|
|
) -> Result<DashboardConfig, sqlx::Error> {
|
|
|
|
|
let config = sqlx::query_as!(
|
|
|
|
|
DashboardConfig,
|
|
|
|
|
r#"
|
|
|
|
|
SELECT c.id, c.role_id, c.audience, c.config_json, c.version, c.is_active, c.updated_at
|
|
|
|
|
FROM dashboard_configs c
|
|
|
|
|
JOIN roles r ON c.role_id = r.id
|
|
|
|
|
WHERE r.key = $1 AND c.audience = $2 AND c.is_active = true
|
|
|
|
|
"#,
|
|
|
|
|
role_key.to_uppercase(),
|
|
|
|
|
audience
|
|
|
|
|
)
|
|
|
|
|
.fetch_one(pool)
|
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
|
|
Ok(config)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub async fn create_runtime_config(
|
|
|
|
|
pool: &PgPool,
|
|
|
|
|
payload: CreateRuntimeConfigPayload,
|
|
|
|
|
) -> Result<RuntimeConfig, sqlx::Error> {
|
|
|
|
|
// Soft-disable previous active configs for this role
|
|
|
|
|
sqlx::query!(
|
|
|
|
|
r#"
|
|
|
|
|
UPDATE runtime_configs
|
|
|
|
|
SET is_active = false
|
|
|
|
|
WHERE role_id = $1 AND is_active = true
|
|
|
|
|
"#,
|
|
|
|
|
payload.role_id
|
|
|
|
|
)
|
|
|
|
|
.execute(pool)
|
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
|
|
// Insert new config
|
|
|
|
|
let config = sqlx::query_as!(
|
|
|
|
|
RuntimeConfig,
|
|
|
|
|
r#"
|
|
|
|
|
INSERT INTO runtime_configs (role_id, config_json, version, is_active)
|
|
|
|
|
VALUES (
|
|
|
|
|
$1,
|
|
|
|
|
$2,
|
|
|
|
|
COALESCE((SELECT MAX(version) FROM runtime_configs WHERE role_id = $1), 0) + 1,
|
|
|
|
|
true
|
|
|
|
|
)
|
|
|
|
|
RETURNING id, role_id, config_json, version, is_active, updated_at
|
|
|
|
|
"#,
|
|
|
|
|
payload.role_id,
|
|
|
|
|
payload.config_json
|
|
|
|
|
)
|
|
|
|
|
.fetch_one(pool)
|
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
|
|
Ok(config)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub async fn get_active_runtime_config(
|
|
|
|
|
pool: &PgPool,
|
|
|
|
|
role_id: Uuid,
|
|
|
|
|
) -> Result<RuntimeConfig, sqlx::Error> {
|
|
|
|
|
let config = sqlx::query_as!(
|
|
|
|
|
RuntimeConfig,
|
|
|
|
|
r#"
|
|
|
|
|
SELECT id, role_id, config_json, version, is_active, updated_at
|
|
|
|
|
FROM runtime_configs
|
|
|
|
|
WHERE role_id = $1 AND is_active = true
|
|
|
|
|
"#,
|
|
|
|
|
role_id
|
|
|
|
|
)
|
|
|
|
|
.fetch_one(pool)
|
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
|
|
Ok(config)
|
|
|
|
|
}
|
feat: commit remaining service files, migrations, and model updates
- gateway, companies, customers, job_seekers apps updated
- users config/mod/mail handlers
- auth middleware and jwt crate updates
- db models: user, config, mod updates
- all remaining migrations: portfolio, notifications, reviews, kb, support, coupons, onboarding states
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:59:47 +01:00
|
|
|
|
|
|
|
|
pub async fn get_active_runtime_by_role_key(
|
|
|
|
|
pool: &PgPool,
|
|
|
|
|
role_key: &str,
|
|
|
|
|
) -> Result<RuntimeConfig, sqlx::Error> {
|
|
|
|
|
let config = sqlx::query_as!(
|
|
|
|
|
RuntimeConfig,
|
|
|
|
|
r#"
|
|
|
|
|
SELECT rc.id, rc.role_id, rc.config_json, rc.version, rc.is_active, rc.updated_at
|
|
|
|
|
FROM runtime_configs rc
|
|
|
|
|
JOIN roles r ON rc.role_id = r.id
|
|
|
|
|
WHERE r.key = $1 AND rc.is_active = true
|
|
|
|
|
"#,
|
|
|
|
|
role_key.to_uppercase()
|
|
|
|
|
)
|
|
|
|
|
.fetch_one(pool)
|
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
|
|
Ok(config)
|
|
|
|
|
}
|
2026-03-17 20:42:51 +01:00
|
|
|
}
|