diff --git a/projects/aazam/.gitignore b/projects/aazam/.gitignore new file mode 100644 index 0000000..d744ce2 --- /dev/null +++ b/projects/aazam/.gitignore @@ -0,0 +1,3 @@ +/target + +Cargo.lock diff --git a/projects/aazam/Cargo.toml b/projects/aazam/Cargo.toml new file mode 100644 index 0000000..ba0c184 --- /dev/null +++ b/projects/aazam/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "aazam" +version = "0.1.0" +edition = "2021" + +[dependencies] +actix-web = "4.0" +async-graphql = "5.0" +async-graphql-actix-web = "5.0" +moka = { version = "0.9", features = ["future"] } +reqwest = { version = "0.11", features = ["json"] } +serde = { version = "1.0", features = ["derive"] } + +[workspace] +members = [] \ No newline at end of file diff --git a/projects/aazam/run.sh b/projects/aazam/run.sh new file mode 100755 index 0000000..d8688f6 --- /dev/null +++ b/projects/aazam/run.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +cargo run -p aazam --release \ No newline at end of file diff --git a/projects/aazam/src/main.rs b/projects/aazam/src/main.rs new file mode 100644 index 0000000..e5e4c0b --- /dev/null +++ b/projects/aazam/src/main.rs @@ -0,0 +1,215 @@ +use actix_web::{web, App, HttpResponse, HttpServer, Responder}; +use async_graphql::{ + http::GraphiQLSource, Context, EmptyMutation, EmptySubscription, Object, Schema, SimpleObject, +}; +use async_graphql_actix_web::{GraphQLRequest, GraphQLResponse}; +use moka::future::Cache; +use reqwest::Client; +use serde::{Deserialize, Serialize}; +use std::sync::Arc; +use std::time::Duration; + +const BASE_URL: &str = "http://localhost:3000"; +const CACHE_MAX_CAPACITY: u64 = 10_000; +const CACHE_TIME_TO_LIVE: Duration = Duration::from_secs(60 * 5); // 5 minutes + +#[derive(Serialize, Deserialize, Clone, Debug)] +struct Post { + id: i32, + user_id: i32, + title: Option, + body: Option, +} + +#[derive(SimpleObject, Serialize, Deserialize, Clone, Debug)] +struct User { + id: i32, + name: Option, + username: Option, + email: Option, + address: Option
, + phone: Option, + website: Option, +} + +#[derive(SimpleObject, Serialize, Deserialize, Clone, Debug)] +struct Address { + zipcode: Option, + geo: Option, +} + +#[derive(SimpleObject, Serialize, Deserialize, Clone, Debug)] +struct Geo { + lat: Option, + lng: Option, +} + +struct Query; + +#[Object] +impl Query { + async fn posts(&self, ctx: &Context<'_>) -> async_graphql::Result> { + let cache = ctx.data::>>>().unwrap(); + let client = ctx.data::().unwrap(); + + if let Some(posts) = cache.get("all_posts") { + return Ok(posts); + } + + let posts: Vec = client + .get(&format!("{}/posts", BASE_URL)) + .send() + .await? + .json() + .await?; + + cache.insert("all_posts".to_string(), posts.clone()).await; + Ok(posts) + } + + async fn post(&self, ctx: &Context<'_>, id: i32) -> async_graphql::Result> { + let cache = ctx.data::>>().unwrap(); + let client = ctx.data::().unwrap(); + + let cache_key = format!("post_{}", id); + if let Some(post) = cache.get(&cache_key) { + return Ok(Some(post)); + } + + let post: Option = client + .get(&format!("{}/posts/{}", BASE_URL, id)) + .send() + .await? + .json() + .await?; + + if let Some(post) = &post { + cache.insert(cache_key, post.clone()).await; + } + Ok(post) + } + + async fn users(&self, ctx: &Context<'_>) -> async_graphql::Result> { + let cache = ctx.data::>>>().unwrap(); + let client = ctx.data::().unwrap(); + + if let Some(users) = cache.get("all_users") { + return Ok(users); + } + + let users: Vec = client + .get(&format!("{}/users", BASE_URL)) + .send() + .await? + .json() + .await?; + + cache.insert("all_users".to_string(), users.clone()).await; + Ok(users) + } + + async fn user(&self, ctx: &Context<'_>, id: i32) -> async_graphql::Result> { + let cache = ctx.data::>>().unwrap(); + let client = ctx.data::().unwrap(); + + let cache_key = format!("user_{}", id); + if let Some(user) = cache.get(&cache_key) { + return Ok(Some(user)); + } + + let user: Option = client + .get(&format!("{}/users/{}", BASE_URL, id)) + .send() + .await? + .json() + .await?; + + if let Some(user) = &user { + cache.insert(cache_key, user.clone()).await; + } + Ok(user) + } +} + +#[Object] +impl Post { + async fn id(&self) -> i32 { + self.id + } + + #[graphql(name = "userId")] + async fn user_id(&self) -> i32 { + self.user_id + } + + async fn title(&self) -> &Option { + &self.title + } + + async fn body(&self) -> &Option { + &self.body + } + + async fn user(&self, ctx: &Context<'_>) -> async_graphql::Result> { + let query = Query; + query.user(ctx, self.user_id).await + } +} + +#[actix_web::main] +async fn main() -> std::io::Result<()> { + let client = Client::builder() + .timeout(Duration::from_secs(30)) + .build() + .expect("Failed to create HTTP client"); + + let post_cache: Cache = Cache::builder() + .max_capacity(CACHE_MAX_CAPACITY) + .time_to_live(CACHE_TIME_TO_LIVE) + .build(); + + let posts_cache: Cache> = Cache::builder() + .max_capacity(CACHE_MAX_CAPACITY) + .time_to_live(CACHE_TIME_TO_LIVE) + .build(); + + let user_cache: Cache = Cache::builder() + .max_capacity(CACHE_MAX_CAPACITY) + .time_to_live(CACHE_TIME_TO_LIVE) + .build(); + + let users_cache: Cache> = Cache::builder() + .max_capacity(CACHE_MAX_CAPACITY) + .time_to_live(CACHE_TIME_TO_LIVE) + .build(); + + let schema = Schema::build(Query, EmptyMutation, EmptySubscription) + .data(client) + .data(Arc::new(post_cache)) + .data(Arc::new(posts_cache)) + .data(Arc::new(user_cache)) + .data(Arc::new(users_cache)) + .finish(); + + println!("GraphiQL IDE: http://localhost:8000"); + + HttpServer::new(move || { + App::new() + .app_data(web::Data::new(schema.clone())) + .service(web::resource("/").to(graphiql)) + .service(web::resource("/graphql").to(graphql)) + }) + .bind("127.0.0.1:8000")? + .run() + .await +} + +async fn graphiql() -> impl Responder { + HttpResponse::Ok() + .content_type("text/html; charset=utf-8") + .body(GraphiQLSource::build().endpoint("/graphql").finish()) +} + +async fn graphql(schema: web::Data>, req: GraphQLRequest) -> GraphQLResponse { + schema.execute(req.into_inner()).await.into() +} \ No newline at end of file