use chrono::{DateTime, Utc};
use serde::Serialize;
use serde_json::Value;
use sqlx::postgres::{PgPool, PgRow};
use sqlx::{Row, types::Uuid};
#[derive(Debug, Clone, Serialize)]
pub struct PublicServiceRouteRecord {
pub id: String,
pub route_key: String,
pub service_key: String,
pub target_kind: String,
pub local_path_prefix: Option<String>,
pub target_url: Option<String>,
pub is_active: bool,
pub metadata: Value,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub deleted_at: Option<DateTime<Utc>>,
}
#[derive(Debug, Clone)]
pub struct SavePublicServiceRouteParams {
pub route_key: String,
pub service_key: String,
pub target_kind: String,
pub local_path_prefix: Option<String>,
pub target_url: Option<String>,
pub is_active: bool,
pub metadata: Value,
}
#[derive(Debug, Clone)]
pub struct PatchPublicServiceRouteParams {
pub target_kind: Option<String>,
pub local_path_prefix: Option<String>,
pub target_url: Option<String>,
pub is_active: Option<bool>,
pub metadata: Option<Value>,
}
fn map_route_row(row: &PgRow) -> Result<PublicServiceRouteRecord, sqlx::Error> {
Ok(PublicServiceRouteRecord {
id: row.try_get::<Uuid, _>("id")?.to_string(),
route_key: row.try_get("route_key")?,
service_key: row.try_get("service_key")?,
target_kind: row.try_get("target_kind")?,
local_path_prefix: row.try_get("local_path_prefix")?,
target_url: row.try_get("target_url")?,
is_active: row.try_get("is_active")?,
metadata: row.try_get("metadata")?,
created_at: row.try_get("created_at")?,
updated_at: row.try_get("updated_at")?,
deleted_at: row.try_get("deleted_at")?,
})
}
pub async fn list_public_service_routes(
pool: &PgPool,
) -> Result<Vec<PublicServiceRouteRecord>, sqlx::Error> {
let rows: Vec<PgRow> = sqlx::query(
r#"
SELECT
public_service_route_id AS id,
route_key,
service_key,
target_kind,
local_path_prefix,
target_url,
is_active,
metadata,
created_at,
updated_at,
deleted_at
FROM public.public_service_routes
WHERE deleted_at IS NULL
ORDER BY lower(route_key), lower(service_key)
"#,
)
.fetch_all(pool)
.await?;
rows.iter().map(map_route_row).collect()
}
pub async fn get_public_service_route_by_key(
pool: &PgPool,
route_key: &str,
service_key: &str,
) -> Result<Option<PublicServiceRouteRecord>, sqlx::Error> {
let row: Option<PgRow> = sqlx::query(
r#"
SELECT
public_service_route_id AS id,
route_key,
service_key,
target_kind,
local_path_prefix,
target_url,
is_active,
metadata,
created_at,
updated_at,
deleted_at
FROM public.public_service_routes
WHERE lower(route_key) = lower($1)
AND lower(service_key) = lower($2)
AND deleted_at IS NULL
LIMIT 1
"#,
)
.bind(route_key)
.bind(service_key)
.fetch_optional(pool)
.await?;
row.as_ref().map(map_route_row).transpose()
}
pub async fn create_public_service_route(
pool: &PgPool,
params: SavePublicServiceRouteParams,
) -> Result<PublicServiceRouteRecord, sqlx::Error> {
let row: PgRow = sqlx::query(
r#"
INSERT INTO public.public_service_routes (
public_service_route_id,
route_key,
service_key,
target_kind,
local_path_prefix,
target_url,
is_active,
metadata
)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
RETURNING
public_service_route_id AS id,
route_key,
service_key,
target_kind,
local_path_prefix,
target_url,
is_active,
metadata,
created_at,
updated_at,
deleted_at
"#,
)
.bind(Uuid::new_v4())
.bind(¶ms.route_key)
.bind(¶ms.service_key)
.bind(¶ms.target_kind)
.bind(¶ms.local_path_prefix)
.bind(¶ms.target_url)
.bind(params.is_active)
.bind(¶ms.metadata)
.fetch_one(pool)
.await?;
map_route_row(&row)
}
pub async fn patch_public_service_route(
pool: &PgPool,
route_key: &str,
service_key: &str,
params: PatchPublicServiceRouteParams,
) -> Result<Option<PublicServiceRouteRecord>, sqlx::Error> {
let row: Option<PgRow> = sqlx::query(
r#"
UPDATE public.public_service_routes
SET
target_kind = COALESCE($3, target_kind),
local_path_prefix = COALESCE($4, local_path_prefix),
target_url = COALESCE($5, target_url),
is_active = COALESCE($6, is_active),
metadata = COALESCE($7, metadata),
updated_at = now()
WHERE lower(route_key) = lower($1)
AND lower(service_key) = lower($2)
AND deleted_at IS NULL
RETURNING
public_service_route_id AS id,
route_key,
service_key,
target_kind,
local_path_prefix,
target_url,
is_active,
metadata,
created_at,
updated_at,
deleted_at
"#,
)
.bind(route_key)
.bind(service_key)
.bind(params.target_kind)
.bind(params.local_path_prefix)
.bind(params.target_url)
.bind(params.is_active)
.bind(params.metadata)
.fetch_optional(pool)
.await?;
row.as_ref().map(map_route_row).transpose()
}
pub async fn soft_delete_public_service_route(
pool: &PgPool,
route_key: &str,
service_key: &str,
) -> Result<Option<PublicServiceRouteRecord>, sqlx::Error> {
let row: Option<PgRow> = sqlx::query(
r#"
UPDATE public.public_service_routes
SET
is_active = false,
deleted_at = now(),
updated_at = now()
WHERE lower(route_key) = lower($1)
AND lower(service_key) = lower($2)
AND deleted_at IS NULL
RETURNING
public_service_route_id AS id,
route_key,
service_key,
target_kind,
local_path_prefix,
target_url,
is_active,
metadata,
created_at,
updated_at,
deleted_at
"#,
)
.bind(route_key)
.bind(service_key)
.fetch_optional(pool)
.await?;
row.as_ref().map(map_route_row).transpose()
}