Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
aazam-gh committed Sep 12, 2024
1 parent c3ddc59 commit 85ea7d5
Show file tree
Hide file tree
Showing 4 changed files with 236 additions and 0 deletions.
3 changes: 3 additions & 0 deletions projects/aazam/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/target

Cargo.lock
15 changes: 15 additions & 0 deletions projects/aazam/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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 = []
3 changes: 3 additions & 0 deletions projects/aazam/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/usr/bin/env bash

cargo run -p aazam --release
215 changes: 215 additions & 0 deletions projects/aazam/src/main.rs
Original file line number Diff line number Diff line change
@@ -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<String>,
body: Option<String>,
}

#[derive(SimpleObject, Serialize, Deserialize, Clone, Debug)]
struct User {
id: i32,
name: Option<String>,
username: Option<String>,
email: Option<String>,
address: Option<Address>,
phone: Option<String>,
website: Option<String>,
}

#[derive(SimpleObject, Serialize, Deserialize, Clone, Debug)]
struct Address {
zipcode: Option<String>,
geo: Option<Geo>,
}

#[derive(SimpleObject, Serialize, Deserialize, Clone, Debug)]
struct Geo {
lat: Option<f64>,
lng: Option<f64>,
}

struct Query;

#[Object]
impl Query {
async fn posts(&self, ctx: &Context<'_>) -> async_graphql::Result<Vec<Post>> {
let cache = ctx.data::<Arc<Cache<String, Vec<Post>>>>().unwrap();
let client = ctx.data::<Client>().unwrap();

if let Some(posts) = cache.get("all_posts") {
return Ok(posts);
}

let posts: Vec<Post> = 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<Option<Post>> {
let cache = ctx.data::<Arc<Cache<String, Post>>>().unwrap();
let client = ctx.data::<Client>().unwrap();

let cache_key = format!("post_{}", id);
if let Some(post) = cache.get(&cache_key) {
return Ok(Some(post));
}

let post: Option<Post> = 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<Vec<User>> {
let cache = ctx.data::<Arc<Cache<String, Vec<User>>>>().unwrap();
let client = ctx.data::<Client>().unwrap();

if let Some(users) = cache.get("all_users") {
return Ok(users);
}

let users: Vec<User> = 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<Option<User>> {
let cache = ctx.data::<Arc<Cache<String, User>>>().unwrap();
let client = ctx.data::<Client>().unwrap();

let cache_key = format!("user_{}", id);
if let Some(user) = cache.get(&cache_key) {
return Ok(Some(user));
}

let user: Option<User> = 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<String> {
&self.title
}

async fn body(&self) -> &Option<String> {
&self.body
}

async fn user(&self, ctx: &Context<'_>) -> async_graphql::Result<Option<User>> {
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<String, Post> = Cache::builder()
.max_capacity(CACHE_MAX_CAPACITY)
.time_to_live(CACHE_TIME_TO_LIVE)
.build();

let posts_cache: Cache<String, Vec<Post>> = Cache::builder()
.max_capacity(CACHE_MAX_CAPACITY)
.time_to_live(CACHE_TIME_TO_LIVE)
.build();

let user_cache: Cache<String, User> = Cache::builder()
.max_capacity(CACHE_MAX_CAPACITY)
.time_to_live(CACHE_TIME_TO_LIVE)
.build();

let users_cache: Cache<String, Vec<User>> = 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<Schema<Query, EmptyMutation, EmptySubscription>>, req: GraphQLRequest) -> GraphQLResponse {
schema.execute(req.into_inner()).await.into()
}

0 comments on commit 85ea7d5

Please sign in to comment.