fix: use our own result types instead of tenors types

add user auth
This commit is contained in:
Zomatree
2025-08-08 03:10:30 +01:00
committed by Angelo Kontaxis
parent b5cd5e30ef
commit a92152d86d
5 changed files with 119 additions and 71 deletions

View File

@@ -1,12 +1,18 @@
use std::sync::Arc;
use axum::{extract::{Query, State}, routing::get, Json, Router};
use axum::{
extract::{Query, State},
routing::get,
Json, Router,
};
use revolt_database::User;
use revolt_result::{create_error, Result};
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
use revolt_database::User;
use crate::{AppState, tenor::{Tenor, types}};
use crate::{
tenor,
types,
AppState,
};
pub async fn router() -> Router<AppState> {
Router::new()
@@ -43,7 +49,7 @@ async fn root() -> Json<RootResponse> {
struct SearchQueryParams {
pub query: String,
pub locale: String,
pub position: Option<String>
pub position: Option<String>,
}
#[utoipa::path(
@@ -56,11 +62,13 @@ struct SearchQueryParams {
async fn search(
_user: User,
Query(params): Query<SearchQueryParams>,
State(tenor): State<Tenor>,
) -> Result<Json<Arc<types::SearchResponse>>> {
State(tenor): State<tenor::Tenor>,
) -> Result<Json<types::SearchResponse>> {
// Todo
tenor.search(&params.query, &params.locale, params.position.as_deref())
tenor
.search(&params.query, &params.locale, params.position.as_deref())
.await
.map_err(|_| create_error!(InternalError))
.map(|results| results.as_ref().clone().into())
.map(Json)
}
}

View File

@@ -15,11 +15,12 @@ use crate::tenor::Tenor;
mod api;
mod tenor;
mod types;
#[derive(Clone)]
struct AppState {
pub database: Database,
pub tenor: Tenor
pub tenor: Tenor,
}
impl FromRef<AppState> for Database {
@@ -45,11 +46,15 @@ async fn main() -> Result<(), std::io::Error> {
modifiers(&SecurityAddon),
paths(
api::root,
api::search,
),
components(
schemas(
revolt_result::Error,
revolt_result::ErrorType,
types::SearchResponse,
types::MediaObject,
types::MediaResult,
)
)
)]
@@ -70,8 +75,11 @@ async fn main() -> Result<(), std::io::Error> {
let config = config().await;
let state = AppState {
database: DatabaseInfo::Auto.connect().await.expect("Unable to connect to database"),
tenor: tenor::Tenor::new(&config.api.security.tenor_key)
database: DatabaseInfo::Auto
.connect()
.await
.expect("Unable to connect to database"),
tenor: tenor::Tenor::new(&config.api.security.tenor_key),
};
// Configure Axum and router

View File

@@ -1,10 +1,10 @@
use std::{sync::Arc, time::Duration};
use reqwest::Client;
use tokio::sync::RwLock;
use lru_time_cache::LruCache;
use urlencoding::encode as url_encode;
use reqwest::Client;
use revolt_coalesced::{CoalescionService, CoalescionServiceConfig};
use tokio::sync::RwLock;
use urlencoding::encode as url_encode;
pub mod types;
@@ -20,13 +20,13 @@ pub struct Tenor {
pub key: Arc<str>,
pub client: Client,
pub coalescion: CoalescionService<String, Result<Arc<types::SearchResponse>, TenorError>>,
pub cache: Arc<RwLock<LruCache<String, Arc<types::SearchResponse>>>>
pub cache: Arc<RwLock<LruCache<String, Arc<types::SearchResponse>>>>,
}
impl Tenor {
pub fn new(key: &str) -> Self {
// 1 hour, 1k queries
let cache = LruCache::with_expiry_duration_and_capacity(Duration::from_secs(60*60), 1000);
let cache = LruCache::with_expiry_duration_and_capacity(Duration::from_secs(60 * 60), 1000);
Self {
key: Arc::from(key),
@@ -34,18 +34,23 @@ impl Tenor {
coalescion: CoalescionService::from_config(CoalescionServiceConfig {
max_concurrent: Some(100),
queue_requests: true,
max_queue: None
max_queue: None,
}),
cache: Arc::new(RwLock::new(cache)),
}
}
pub async fn search(&self, query: &str, locale: &str, position: Option<&str>) -> Result<Arc<types::SearchResponse>, TenorError> {
pub async fn search(
&self,
query: &str,
locale: &str,
position: Option<&str>,
) -> Result<Arc<types::SearchResponse>, TenorError> {
let unique_key = format!("{query}:{locale}:{position:?}");
if self.cache.read().await.contains_key(&unique_key) {
if let Some(response) = self.cache.write().await.get(&unique_key) {
return Ok(response.clone())
return Ok(response.clone());
}
}
@@ -54,7 +59,7 @@ impl Tenor {
async move {
let mut url = format!(
"{TENOR_API_BASE_URL}/search?key={}&q={}&client_key=Gifbox&locale={}&contentfilter=medium&limit=1",
"{TENOR_API_BASE_URL}/search?key={}&q={}&client_key=Gifbox&media_filter=webm,tinywebm&locale={}&contentfilter=medium&limit=1",
&self.key,
url_encode(query),
url_encode(locale),
@@ -86,44 +91,3 @@ impl Tenor {
(*res).clone()
}
}
#[cfg(test)]
mod tests {
use super::*;
use revolt_config::config;
#[tokio::test(flavor = "current_thread")]
async fn test() {
let config = config().await;
let tenor = Tenor::new(&config.api.security.tenor_key);
let results =tenor.search("amog", "en_US", None).await.unwrap();
let result = &results.results[0];
println!("{:?}", result.media_formats.iter().next().unwrap());
}
#[tokio::test(flavor = "current_thread")]
async fn test_2() {
let config = config().await;
let tenor = Tenor::new(&config.api.security.tenor_key);
let mut tasks = Vec::new();
for i in 1..1001 {
let tenor = tenor.clone();
println!("creating search task {i}");
tasks.push((i, tokio::spawn(async move {
tenor.search(&format!("amog-{i}"), "en_US", None).await
})));
};
for (i, task) in tasks {
task.await.unwrap().unwrap();
println!("Got result for {i}");
};
}
}

View File

@@ -1,12 +1,11 @@
use std::collections::HashMap;
use serde::{Serialize, Deserialize};
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct SearchResponse {
pub results: Vec<TenorResult>,
pub next: String
pub results: Vec<MediaResult>,
pub next: String,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
@@ -14,11 +13,11 @@ pub struct MediaObject {
pub url: String,
pub dims: Vec<u64>,
pub duration: f64,
pub size: f64
pub size: f64,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct TenorResult {
pub struct MediaResult {
pub created: f64,
#[serde(default)]
pub hasaudio: bool,
@@ -32,5 +31,5 @@ pub struct TenorResult {
pub hascaption: bool,
pub flags: Vec<String>,
pub bg_color: Option<String>,
pub url: String
}
pub url: String,
}

View File

@@ -0,0 +1,69 @@
use std::collections::HashMap;
use utoipa::ToSchema;
use serde::{Serialize, Deserialize};
use crate::tenor::types;
#[derive(Serialize, Deserialize, ToSchema, Clone, Debug, PartialEq)]
/// Response containing the current results and the id of the next result for pagination.
pub struct SearchResponse {
/// Current gif results.
pub results: Vec<MediaResult>,
#[serde(skip_serializing_if = "Option::is_none")]
/// Id of the next result.
pub next: Option<String>,
}
#[derive(Serialize, Deserialize, ToSchema, Clone, Debug, PartialEq)]
/// Indivual gif result.
pub struct MediaResult {
/// Unique Tenor id.
pub id: String,
/// Mapping of each file format and url of the file.
pub media_formats: HashMap<String, MediaObject>,
/// Public Tenor web url for the gif.
pub url: String
}
#[derive(Serialize, Deserialize, ToSchema, Clone, Debug, PartialEq)]
/// Represents the gif in a certain file format.
pub struct MediaObject {
/// File url of the gif in a certain format.
pub url: String,
/// Width and height of the file in px.
pub dimensions: Vec<u64>,
}
impl From<types::SearchResponse> for SearchResponse {
fn from(value: types::SearchResponse) -> Self {
Self {
results: value.results.into_iter().map(|result| result.into()).collect(),
next: if value.next.is_empty() {
None
} else {
Some(value.next)
}
}
}
}
impl From<types::MediaResult> for MediaResult {
fn from(value: types::MediaResult) -> Self {
Self {
id: value.id,
media_formats: value.media_formats.into_iter().map(|(k, v)| (k, v.into())).collect(),
url: value.url
}
}
}
impl From<types::MediaObject> for MediaObject {
fn from(value: types::MediaObject) -> Self {
Self {
url: value.url,
dimensions: value.dims
}
}
}