feat(backend): add initial backend
Backend as is can query GitHub for the information that will be necessary later on in the frontend's pages. It does not cache this information yet though. Signed-off-by: Lucien Cartier-Tilet <lucien@phundrak.com>
This commit is contained in:
41
src/data.rs
Normal file
41
src/data.rs
Normal file
@@ -0,0 +1,41 @@
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Data {
|
||||
pub user: User,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct User {
|
||||
pub newest: Newest,
|
||||
pub most_starred: MostStarred,
|
||||
pub pinned: Pinned,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Newest {
|
||||
pub nodes: Vec<Node>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MostStarred {
|
||||
pub nodes: Vec<Node>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Pinned {
|
||||
pub nodes: Vec<Node>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Node {
|
||||
pub name: String,
|
||||
pub stargazer_count: i64,
|
||||
pub fork_count: i64,
|
||||
}
|
||||
112
src/main.rs
Normal file
112
src/main.rs
Normal file
@@ -0,0 +1,112 @@
|
||||
use actix_web::{error, get, web, Responder, Result};
|
||||
use derive_more::{Display, Error};
|
||||
use dotenvy::dotenv;
|
||||
use gql_client::Client;
|
||||
use std::collections::HashMap;
|
||||
|
||||
mod data;
|
||||
|
||||
#[derive(Debug, Display, Error)]
|
||||
#[display(fmt = "my error: {}", name)]
|
||||
struct MyError {
|
||||
name: String,
|
||||
}
|
||||
|
||||
struct AppState {
|
||||
github_token: String,
|
||||
}
|
||||
|
||||
impl Default for AppState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
github_token: github_token(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl error::ResponseError for MyError {}
|
||||
|
||||
fn github_token() -> String {
|
||||
std::env::var("GH_TOKEN")
|
||||
.expect("Environment variable GH_TOKEN **MUST** be set!")
|
||||
}
|
||||
|
||||
#[get("/phundrak-com/{user}")]
|
||||
async fn user_info_github(
|
||||
user: web::Path<String>,
|
||||
state: web::Data<AppState>,
|
||||
) -> Result<impl Responder> {
|
||||
let gh_token = &state.github_token;
|
||||
let body = format!(
|
||||
r#"query {{
|
||||
user(login: "{}") {{
|
||||
newest: repositories(first: 10, orderBy: {{field: UPDATED_AT, direction: DESC}}) {{
|
||||
nodes {{
|
||||
name
|
||||
stargazerCount
|
||||
forkCount
|
||||
}}
|
||||
}}
|
||||
mostStarred: repositories(first: 10, orderBy: {{field: STARGAZERS, direction: DESC}}) {{
|
||||
nodes {{
|
||||
name
|
||||
stargazerCount
|
||||
forkCount
|
||||
}}
|
||||
}}
|
||||
pinned: pinnedItems(first: 10) {{
|
||||
nodes {{
|
||||
... on Repository {{
|
||||
name
|
||||
stargazerCount
|
||||
forkCount
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
}}"#,
|
||||
user
|
||||
);
|
||||
let mut headers: HashMap<String, String> = HashMap::new();
|
||||
headers.insert("Authorization".to_string(), format!("Bearer {}", gh_token));
|
||||
headers.insert("User-Agent".to_string(), "PhunCache-App".to_string());
|
||||
let client =
|
||||
Client::new_with_headers("https://api.github.com/graphql", headers);
|
||||
client
|
||||
.query::<data::Data>(&body)
|
||||
.await
|
||||
.map(web::Json)
|
||||
.map_err(|e| {
|
||||
MyError {
|
||||
name: format!("Failed to retrieve data from GitHub: {e:?}"),
|
||||
}
|
||||
.into()
|
||||
})
|
||||
}
|
||||
|
||||
#[actix_web::main]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
use actix_web::{App, HttpServer};
|
||||
use std::env::var;
|
||||
|
||||
std::env::set_var("RUST_LOG", "debug");
|
||||
env_logger::init();
|
||||
dotenv().ok();
|
||||
human_panic::setup_panic!();
|
||||
|
||||
HttpServer::new(|| {
|
||||
App::new()
|
||||
.app_data(web::Data::new(AppState::default()))
|
||||
.service(user_info_github)
|
||||
})
|
||||
.bind((
|
||||
var("ACTIX_ADDRESS")
|
||||
.expect("Environment variable ACTIX_ADDRESS must be set!"),
|
||||
var("ACTIX_PORT")
|
||||
.expect("Environment variable ACTIX_PORT must be set!")
|
||||
.parse()
|
||||
.expect("Failed to parse value of ACTIX_PORT into a u16"),
|
||||
))?
|
||||
.run()
|
||||
.await
|
||||
}
|
||||
Reference in New Issue
Block a user