use serde::{Deserialize, Serialize};
use super::{Db, Ts};
#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
pub struct IdToken {
pub id_token: String,
pub token_type: String,
pub status: String,
pub expires_at: Option<Ts>,
pub group_id: Option<String>,
#[serde(default)]
pub tariff_id: Option<String>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct AuthResult {
pub status: &'static str,
pub expires_at: Option<Ts>,
pub group_id: Option<String>,
}
pub async fn authorize(
db: &Db,
token: &str,
token_type: Option<&str>,
accept_unknown: bool,
) -> sqlx::Result<AuthResult> {
let row: Option<IdToken> = sqlx::query_as(
"SELECT id_token, token_type, status, expires_at, group_id, tariff_id FROM id_tokens \
WHERE ($2::text IS NULL AND lower(id_token) = lower($1)) OR ($2::text IS NOT NULL AND id_token = $1 AND token_type IN ($2, '*')) \
ORDER BY (token_type = '*') ASC LIMIT 1",
)
.bind(token)
.bind(token_type)
.fetch_optional(db)
.await?;
let Some(row) = row else {
return Ok(AuthResult {
status: if accept_unknown {
"Accepted"
} else {
"Unknown"
},
expires_at: None,
group_id: None,
});
};
let expired = row
.expires_at
.is_some_and(|e| e.0 < ocpp_core::DateTime::now());
let status = match row.status.as_str() {
_ if expired => "Expired",
"Accepted" => "Accepted",
"Blocked" => "Blocked",
"Expired" => "Expired",
_ => "Invalid",
};
Ok(AuthResult {
status,
expires_at: row.expires_at,
group_id: row.group_id,
})
}
pub async fn upsert(db: &Db, t: &IdToken) -> sqlx::Result<IdToken> {
sqlx::query_as(
"INSERT INTO id_tokens (id_token, token_type, status, expires_at, group_id, tariff_id) VALUES ($1, $2, $3, $4, $5, $6) \
ON CONFLICT (id_token, token_type) DO UPDATE SET status = EXCLUDED.status, expires_at = EXCLUDED.expires_at, group_id = EXCLUDED.group_id, tariff_id = EXCLUDED.tariff_id \
RETURNING id_token, token_type, status, expires_at, group_id, tariff_id",
)
.bind(&t.id_token)
.bind(&t.token_type)
.bind(&t.status)
.bind(t.expires_at)
.bind(&t.group_id)
.bind(&t.tariff_id)
.fetch_one(db)
.await
}
pub async fn delete(db: &Db, token: &str, token_type: &str) -> sqlx::Result<bool> {
Ok(
sqlx::query("DELETE FROM id_tokens WHERE id_token = $1 AND token_type = $2")
.bind(token)
.bind(token_type)
.execute(db)
.await?
.rows_affected()
> 0,
)
}
pub async fn list(db: &Db, limit: i64, offset: i64) -> sqlx::Result<Vec<IdToken>> {
sqlx::query_as("SELECT id_token, token_type, status, expires_at, group_id, tariff_id FROM id_tokens ORDER BY id_token, token_type LIMIT $1 OFFSET $2")
.bind(limit)
.bind(offset)
.fetch_all(db)
.await
}