use std::time::Duration;
use moka::future::Cache;
use sqlx::PgPool;
use crate::db::handlers::api_keys::ApiKeys;
use crate::types::UserId;
use super::index::CacheResult;
#[derive(Clone)]
pub struct PrincipalResolver {
pool: PgPool,
l1: Cache<String, Option<UserId>>,
}
impl PrincipalResolver {
pub fn new(pool: PgPool) -> Self {
Self::with_capacity(pool, 100_000)
}
pub fn with_capacity(pool: PgPool, max_entries: u64) -> Self {
let l1 = Cache::builder()
.max_capacity(max_entries)
.time_to_live(Duration::from_secs(3600))
.build();
Self { pool, l1 }
}
pub async fn resolve(&self, token: &str) -> CacheResult<Option<UserId>> {
if let Some(cached) = self.l1.get(token).await {
return Ok(cached);
}
let mut conn = self.pool.acquire().await?;
let user_id = ApiKeys::new(&mut conn).get_user_id_by_secret(token).await?;
self.l1.insert(token.to_string(), user_id).await;
Ok(user_id)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::api::models::users::Role;
use crate::test::utils::{create_test_api_key_for_user, create_test_user};
#[sqlx::test]
async fn resolves_validated_key_to_user(pool: PgPool) {
let user = create_test_user(&pool, Role::StandardUser).await;
let key = create_test_api_key_for_user(&pool, user.id).await;
let resolver = PrincipalResolver::new(pool);
assert_eq!(resolver.resolve(&key.secret).await.unwrap(), Some(user.id));
assert_eq!(resolver.resolve(&key.secret).await.unwrap(), Some(user.id));
}
#[sqlx::test]
async fn unknown_token_resolves_none(pool: PgPool) {
let resolver = PrincipalResolver::new(pool);
assert_eq!(resolver.resolve("not-a-real-secret").await.unwrap(), None);
}
}