working cache and fetch
This commit is contained in:
parent
abb999e3f2
commit
f94f1baba5
7 changed files with 1254 additions and 36 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1 +1,2 @@
|
||||||
/target
|
/target
|
||||||
|
.env
|
||||||
|
|
|
||||||
1076
Cargo.lock
generated
1076
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
15
Cargo.toml
15
Cargo.toml
|
|
@ -6,6 +6,19 @@ edition = "2021"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
dotenvy = "0.15.7"
|
||||||
redis = "0.25.3"
|
redis = "0.25.3"
|
||||||
redis-derive = "0.1.7"
|
redis-derive = "0.1.7"
|
||||||
twitch_api = "0.7.0-rc.7"
|
reqwest = "0.12.4"
|
||||||
|
tokio = { version = "1.0.0", features = ["rt", "rt-multi-thread", "macros"] }
|
||||||
|
|
||||||
|
# found in https://github.com/twitch-rs/twitch_api/issues/405
|
||||||
|
twitch_api = { git = "https://github.com/twitch-rs/twitch_api/", features = ["client", "helix", "reqwest", "twitch_oauth2"] }
|
||||||
|
twitch_oauth2 = { git = "https://github.com/twitch-rs/twitch_api/", features = ["reqwest", "client"] }
|
||||||
|
twitch_types = { git = "https://github.com/twitch-rs/twitch_api/" }
|
||||||
|
|
||||||
|
# workaround for https://github.com/twitch-rs/twitch_api/issues/256
|
||||||
|
[patch.crates-io.twitch_types]
|
||||||
|
git = "https://github.com/twitch-rs/twitch_api"
|
||||||
|
[patch.crates-io.twitch_oauth2]
|
||||||
|
git = "https://github.com/twitch-rs/twitch_api"
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,41 @@
|
||||||
use redis::{Commands, RedisResult};
|
use redis::{Client, Commands, RedisResult};
|
||||||
|
|
||||||
|
use crate::config::RedisConfig;
|
||||||
use crate::user_data::UserData;
|
use crate::user_data::UserData;
|
||||||
|
|
||||||
pub struct AvatarCache {
|
pub struct AvatarCache {
|
||||||
redis: redis::Client,
|
redis: Client,
|
||||||
timeout: i64,
|
timeout: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AvatarCache {
|
impl AvatarCache {
|
||||||
pub(crate) fn new(redis: redis::Client, timeout: i64) -> Self {
|
pub fn new(redis: Client, timeout: i64) -> Self {
|
||||||
AvatarCache {
|
AvatarCache {
|
||||||
redis,
|
redis,
|
||||||
timeout,
|
timeout,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn connect(config: &RedisConfig) -> RedisResult<Self> {
|
||||||
|
let client = Client::open(config.url())?;
|
||||||
|
Ok(Self::new(client, config.cache_time()))
|
||||||
|
}
|
||||||
|
|
||||||
/// Get user data from Cache
|
/// Get user data from Cache
|
||||||
pub(crate) fn get_cache_by_name(&self, name: &str) -> RedisResult<UserData> {
|
pub fn get_cache_by_name(&self, name: &str) -> RedisResult<Option<UserData>> {
|
||||||
let mut con = self.redis.get_connection()?;
|
let mut con = self.redis.get_connection()?;
|
||||||
|
|
||||||
con.hgetall(UserData::redis_id_from_str(name))
|
match con.hgetall(UserData::redis_id_from_str(name)) {
|
||||||
|
Ok(v) => Ok(Some(v)),
|
||||||
|
Err(e) => {
|
||||||
|
dbg!(e);
|
||||||
|
Ok(None)
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the data as needed
|
/// Set the data as needed
|
||||||
pub(crate) fn set_cache_data(&self, user_data: &UserData) -> RedisResult<()> {
|
pub fn set_cache_data(&self, user_data: &UserData) -> RedisResult<()> {
|
||||||
let mut con = self.redis.get_connection()?;
|
let mut con = self.redis.get_connection()?;
|
||||||
|
|
||||||
redis::cmd("HSET")
|
redis::cmd("HSET")
|
||||||
|
|
@ -35,7 +48,7 @@ impl AvatarCache {
|
||||||
self.update_cache_expiry(user_data)
|
self.update_cache_expiry(user_data)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_cache_expiry(&self, user_data: &UserData) -> RedisResult<()> {
|
pub fn update_cache_expiry(&self, user_data: &UserData) -> RedisResult<()> {
|
||||||
let mut con = self.redis.get_connection()?;
|
let mut con = self.redis.get_connection()?;
|
||||||
con.expire(user_data.id_to_redis(), self.timeout)?;
|
con.expire(user_data.id_to_redis(), self.timeout)?;
|
||||||
con.expire(user_data.name_to_redis(), self.timeout)?;
|
con.expire(user_data.name_to_redis(), self.timeout)?;
|
||||||
|
|
|
||||||
69
src/config.rs
Normal file
69
src/config.rs
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
use std::error::Error;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct TwitchConfig {
|
||||||
|
client_id: String,
|
||||||
|
client_secret: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TwitchConfig {
|
||||||
|
pub fn client_id(&self) -> &str {
|
||||||
|
&self.client_id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn client_secret(&self) -> &str {
|
||||||
|
&self.client_secret
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct RedisConfig {
|
||||||
|
url: String,
|
||||||
|
cache_time: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RedisConfig {
|
||||||
|
pub fn url(&self) -> &str {
|
||||||
|
&self.url
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cache_time(&self) -> i64 {
|
||||||
|
self.cache_time
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Config {
|
||||||
|
twitch: TwitchConfig,
|
||||||
|
redis: RedisConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
pub fn from_env() -> Result<Self, Box<dyn Error>> {
|
||||||
|
dotenvy::dotenv()?;
|
||||||
|
|
||||||
|
let redis_url = std::env::var("REDIS_URL").unwrap_or("redis://127.0.0.1/".to_string());
|
||||||
|
let redis_cache_time: i64 = std::env::var("REDIS_CACHE_TIME").unwrap_or("60".to_string()).parse()?;
|
||||||
|
let twitch_client_id = std::env::var("TWITCH_CLIENT_ID")?;
|
||||||
|
let twitch_client_secret = std::env::var("TWITCH_CLIENT_SECRET")?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
twitch: TwitchConfig {
|
||||||
|
client_id: twitch_client_id,
|
||||||
|
client_secret: twitch_client_secret,
|
||||||
|
},
|
||||||
|
redis: RedisConfig {
|
||||||
|
url: redis_url,
|
||||||
|
cache_time: redis_cache_time,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn redis(&self) -> &RedisConfig {
|
||||||
|
&self.redis
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn twitch(&self) -> &TwitchConfig {
|
||||||
|
&self.twitch
|
||||||
|
}
|
||||||
|
}
|
||||||
94
src/main.rs
94
src/main.rs
|
|
@ -1,21 +1,95 @@
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use avatar_cache::AvatarCache;
|
|
||||||
use user_data::UserData;
|
use twitch_api::helix::users::get_users;
|
||||||
|
use twitch_api::twitch_oauth2::AppAccessToken;
|
||||||
|
use twitch_api::TwitchClient;
|
||||||
|
|
||||||
|
use crate::avatar_cache::AvatarCache;
|
||||||
|
use crate::config::Config;
|
||||||
|
use crate::user_data::UserData;
|
||||||
|
|
||||||
mod user_data;
|
mod user_data;
|
||||||
mod avatar_cache;
|
mod avatar_cache;
|
||||||
|
mod config;
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn Error>> {
|
#[tokio::main]
|
||||||
let client = redis::Client::open("redis://127.0.0.1/")?;
|
async fn main() -> Result<(), Box<dyn Error>> {
|
||||||
let cache = AvatarCache::new(client, 60);
|
let config = Config::from_env()?;
|
||||||
|
let cache = AvatarCache::connect(config.redis())?;
|
||||||
|
let fetch = AvatarFetch::connect(config.twitch()).await?;
|
||||||
|
|
||||||
let data = cache.get_cache_by_name("tbsliver").unwrap_or_else(|e| {
|
let target = "tbsliver";
|
||||||
println!("Cache Miss for tbsliver");
|
|
||||||
UserData::new("empty".to_string(), "empty".to_string(), "empty".to_string(), "empty".to_string())
|
|
||||||
});
|
|
||||||
|
|
||||||
cache.set_cache_data(&data)?;
|
let data = cache.get_cache_by_name(target)?;
|
||||||
|
|
||||||
|
match data {
|
||||||
|
Some(d) => {
|
||||||
|
println!("Cache hit for {}", d.name());
|
||||||
|
cache.update_cache_expiry(&d)?;
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
println!("Cache miss for {}", target);
|
||||||
|
let u = fetch.get_user_by_name(target).await?;
|
||||||
|
dbg!(u);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct AvatarFetch {
|
||||||
|
client: TwitchClient<'static, reqwest::Client>,
|
||||||
|
token: AppAccessToken,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AvatarFetch {
|
||||||
|
fn new(client: TwitchClient<'static, reqwest::Client>, token: AppAccessToken) -> Self {
|
||||||
|
AvatarFetch {
|
||||||
|
client,
|
||||||
|
token,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn connect(config: &config::TwitchConfig) -> Result<Self, Box<dyn Error>> {
|
||||||
|
let client: TwitchClient<reqwest::Client> = TwitchClient::new();
|
||||||
|
let token = AppAccessToken::get_app_access_token(
|
||||||
|
&client,
|
||||||
|
config.client_id().into(),
|
||||||
|
config.client_secret().into(),
|
||||||
|
vec![],
|
||||||
|
).await?;
|
||||||
|
Ok(Self::new(client, token))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_user_by_id(&self, id: &str) -> Result<Option<UserData>, Box<dyn Error>> {
|
||||||
|
let mut request = get_users::GetUsersRequest::new();
|
||||||
|
let ids: &[&twitch_types::UserIdRef] = &[id.into()];
|
||||||
|
request.id = ids.into();
|
||||||
|
let response: Vec<get_users::User> = self.client.helix.req_get(request, &self.token).await?.data;
|
||||||
|
|
||||||
|
Ok(self.user_from_response(response))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn user_from_response(&self, response: Vec<get_users::User>) -> Option<UserData> {
|
||||||
|
if let Some(user) = response.first() {
|
||||||
|
dbg!(user);
|
||||||
|
Some(UserData::new(
|
||||||
|
user.login.to_string(),
|
||||||
|
user.display_name.to_string(),
|
||||||
|
user.id.to_string(),
|
||||||
|
user.profile_image_url.clone().unwrap_or("".to_string()),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_user_by_name(&self, name: &str) -> Result<Option<UserData>, Box<dyn Error>> {
|
||||||
|
let mut request = get_users::GetUsersRequest::new();
|
||||||
|
let logins: &[&twitch_types::UserNameRef] = &[name.into()];
|
||||||
|
request.login = logins.into();
|
||||||
|
let response: Vec<get_users::User> = self.client.helix.req_get(request, &self.token).await?.data;
|
||||||
|
|
||||||
|
Ok(self.user_from_response(response))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,11 @@ impl UserData {
|
||||||
Self::redis_name_from_str(&self.name)
|
Self::redis_name_from_str(&self.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn id(&self) -> String {
|
pub fn id(&self) -> &str {
|
||||||
self.id.to_string()
|
&self.id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn name(&self) -> &str {
|
||||||
|
&self.name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue