Merge branch 'feat/get-user-data-gql' into 'main'
GraphQL API Additions See merge request perditum/account-rs!6
This commit is contained in:
commit
68c818dfdf
6 changed files with 154 additions and 30 deletions
|
|
@ -2,11 +2,33 @@ use chrono::NaiveDateTime;
|
||||||
use juniper::{graphql_object, EmptyMutation, EmptySubscription, GraphQLObject, RootNode};
|
use juniper::{graphql_object, EmptyMutation, EmptySubscription, GraphQLObject, RootNode};
|
||||||
use rocket::response::content::RawHtml;
|
use rocket::response::content::RawHtml;
|
||||||
use rocket::State;
|
use rocket::State;
|
||||||
|
use rocket::request::{FromRequest, Outcome, Request};
|
||||||
|
use std::env;
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
// use crate::account::account::{read_basic_auth_token, read_bearer_auth_token};
|
// use crate::account::account::{read_basic_auth_token, read_bearer_auth_token};
|
||||||
use crate::nnid::oauth::TokenData;
|
use crate::nnid::oauth::TokenData;
|
||||||
use crate::Pool;
|
use crate::Pool;
|
||||||
|
|
||||||
|
pub static API_KEY: Lazy<String> = Lazy::new(|| {
|
||||||
|
env::var("GRAPHQL_API_KEY").expect("GRAPHQL_API_KEY not set")
|
||||||
|
});
|
||||||
|
|
||||||
|
#[rocket::async_trait]
|
||||||
|
impl<'r> FromRequest<'r> for Context {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
async fn from_request(req: &'r Request<'_>) -> Outcome<Self, Self::Error> {
|
||||||
|
let pool = req.rocket().state::<Pool>().cloned().unwrap(); // assume Pool is managed as state
|
||||||
|
|
||||||
|
// Grab API key from header
|
||||||
|
let api_key = req.headers().get_one("X-API-Key").map(|s| s.to_string());
|
||||||
|
|
||||||
|
Outcome::Success(Context {
|
||||||
|
pool,
|
||||||
|
api_key,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub type Schema = RootNode<
|
pub type Schema = RootNode<
|
||||||
'static,
|
'static,
|
||||||
|
|
@ -16,8 +38,12 @@ pub type Schema = RootNode<
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
|
||||||
pub struct Context(pub Pool);
|
pub struct Context {
|
||||||
impl juniper::Context for Context{}
|
pub pool: Pool,
|
||||||
|
pub api_key: Option<String>,
|
||||||
|
}
|
||||||
|
impl juniper::Context for Context {}
|
||||||
|
|
||||||
|
|
||||||
#[derive(GraphQLObject)]
|
#[derive(GraphQLObject)]
|
||||||
#[graphql(description = "Data inside of a token")]
|
#[graphql(description = "Data inside of a token")]
|
||||||
|
|
@ -27,6 +53,25 @@ struct TokenInfo {
|
||||||
title_id: Option<String>
|
title_id: Option<String>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(GraphQLObject)]
|
||||||
|
#[graphql(description = "User information from a token")]
|
||||||
|
struct UserInfo {
|
||||||
|
username: String,
|
||||||
|
account_level: i32,
|
||||||
|
nex_password: String,
|
||||||
|
mii_data: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(GraphQLObject)]
|
||||||
|
#[graphql(description = "User information from a username")]
|
||||||
|
pub struct UserInfoWithPId {
|
||||||
|
pub username: String,
|
||||||
|
pub account_level: i32,
|
||||||
|
pub nex_password: String,
|
||||||
|
pub mii_data: String,
|
||||||
|
pub pid: i32,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Query;
|
pub struct Query;
|
||||||
|
|
||||||
#[graphql_object]
|
#[graphql_object]
|
||||||
|
|
@ -47,7 +92,7 @@ impl Query {
|
||||||
"select * from tokens where pid = $1 and token_id = $2 and random = $3",
|
"select * from tokens where pid = $1 and token_id = $2 and random = $3",
|
||||||
data.pid, data.token_id, data.random
|
data.pid, data.token_id, data.random
|
||||||
).
|
).
|
||||||
fetch_one(&context.0).await.ok()?;
|
fetch_one(&context.pool).await.ok()?;
|
||||||
|
|
||||||
Some(TokenInfo{
|
Some(TokenInfo{
|
||||||
pid: data.pid,
|
pid: data.pid,
|
||||||
|
|
@ -56,7 +101,84 @@ impl Query {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn user_from_token(
|
||||||
|
token_data: String,
|
||||||
|
context: &Context,
|
||||||
|
) -> Option<UserInfo> {
|
||||||
|
let data = match TokenData::decode(&token_data) {
|
||||||
|
Some(data) => data,
|
||||||
|
None => {
|
||||||
|
eprintln!("Failed to decode token");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let user = match sqlx::query!(
|
||||||
|
"SELECT username, account_level, nex_password, mii_data FROM users WHERE pid = $1",
|
||||||
|
data.pid
|
||||||
|
)
|
||||||
|
.fetch_one(&context.pool)
|
||||||
|
.await
|
||||||
|
.ok() {
|
||||||
|
Some(user) => user,
|
||||||
|
None => {
|
||||||
|
eprintln!("No user found for PID {}", data.pid);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(UserInfo {
|
||||||
|
username: user.username,
|
||||||
|
account_level: user.account_level,
|
||||||
|
nex_password: user.nex_password,
|
||||||
|
mii_data: user.mii_data.replace('\n', "").replace('\r', ""),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn user_by_pid(pid: i32, context: &Context) -> Option<UserInfo> {
|
||||||
|
if context.api_key.as_deref() != Some(&*API_KEY) {
|
||||||
|
eprintln!("Rejected request: invalid API key");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let user = sqlx::query!(
|
||||||
|
"SELECT username, account_level, nex_password, mii_data FROM users WHERE pid = $1",
|
||||||
|
pid
|
||||||
|
)
|
||||||
|
.fetch_one(&context.pool)
|
||||||
|
.await
|
||||||
|
.ok()?;
|
||||||
|
|
||||||
|
Some(UserInfo {
|
||||||
|
username: user.username,
|
||||||
|
account_level: user.account_level,
|
||||||
|
nex_password: user.nex_password,
|
||||||
|
mii_data: user.mii_data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn user_by_username(username: String, context: &Context) -> Option<UserInfoWithPId> {
|
||||||
|
if context.api_key.as_deref() != Some(&*API_KEY) {
|
||||||
|
eprintln!("Rejected request: invalid API key");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let user = sqlx::query!(
|
||||||
|
"SELECT pid, username, account_level, nex_password, mii_data FROM users WHERE username = $1",
|
||||||
|
username,
|
||||||
|
)
|
||||||
|
.fetch_one(&context.pool)
|
||||||
|
.await
|
||||||
|
.ok()?;
|
||||||
|
|
||||||
|
Some(UserInfoWithPId {
|
||||||
|
username: user.username,
|
||||||
|
account_level: user.account_level,
|
||||||
|
nex_password: user.nex_password,
|
||||||
|
mii_data: user.mii_data,
|
||||||
|
pid: user.pid,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -76,31 +198,31 @@ impl Mutation {
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#[rocket::get("/graphiql")]
|
// #[rocket::get("/graphiql")]
|
||||||
pub fn graphiql() -> RawHtml<String> {
|
// pub fn graphiql() -> RawHtml<String> {
|
||||||
juniper_rocket::graphiql_source("/graphql", None)
|
// juniper_rocket::graphiql_source("/graphql", None)
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
|
//
|
||||||
#[rocket::get("/playground")]
|
// #[rocket::get("/playground")]
|
||||||
pub fn playground() -> RawHtml<String> {
|
// pub fn playground() -> RawHtml<String> {
|
||||||
juniper_rocket::playground_source("/graphql", None)
|
// juniper_rocket::playground_source("/graphql", None)
|
||||||
}
|
// }
|
||||||
|
|
||||||
#[rocket::get("/graphql?<request..>")]
|
#[rocket::get("/graphql?<request..>")]
|
||||||
pub async fn get_graphql(
|
pub async fn get_graphql(
|
||||||
db: &State<Context>,
|
|
||||||
request: juniper_rocket::GraphQLRequest,
|
request: juniper_rocket::GraphQLRequest,
|
||||||
schema: &State<Schema>,
|
schema: &State<Schema>,
|
||||||
|
context: Context
|
||||||
) -> juniper_rocket::GraphQLResponse {
|
) -> juniper_rocket::GraphQLResponse {
|
||||||
request.execute(schema, db).await
|
request.execute(schema, &context).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rocket::post("/graphql", data = "<request>")]
|
#[rocket::post("/graphql", data = "<request>")]
|
||||||
pub async fn post_graphql(
|
pub async fn post_graphql(
|
||||||
db: &State<Context>,
|
|
||||||
request: juniper_rocket::GraphQLRequest,
|
request: juniper_rocket::GraphQLRequest,
|
||||||
schema: &State<Schema>,
|
schema: &State<Schema>,
|
||||||
|
context: Context
|
||||||
) -> juniper_rocket::GraphQLResponse {
|
) -> juniper_rocket::GraphQLResponse {
|
||||||
request.execute(schema, db).await
|
request.execute(schema, &context).await
|
||||||
}
|
}
|
||||||
10
src/main.rs
10
src/main.rs
|
|
@ -119,6 +119,10 @@ async fn launch() -> _ {
|
||||||
env::var("S3_PASSWD").expect("S3_PASSWD not specified").into_boxed_str()
|
env::var("S3_PASSWD").expect("S3_PASSWD not specified").into_boxed_str()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
pub static CDN_URL: Lazy<Box<str>> = Lazy::new(||
|
||||||
|
env::var("CDN_URL").expect("CDN_URL not specified").into_boxed_str()
|
||||||
|
);
|
||||||
|
|
||||||
let s3_client = ClientBuilder::new(S3_URL.clone())
|
let s3_client = ClientBuilder::new(S3_URL.clone())
|
||||||
.provider(Some(Box::new(StaticProvider::new(&S3_USER, &S3_PASSWD, None))))
|
.provider(Some(Box::new(StaticProvider::new(&S3_USER, &S3_PASSWD, None))))
|
||||||
.build()
|
.build()
|
||||||
|
|
@ -137,7 +141,6 @@ async fn launch() -> _ {
|
||||||
.manage(S3ClientState {
|
.manage(S3ClientState {
|
||||||
client: Arc::new(s3_client),
|
client: Arc::new(s3_client),
|
||||||
})
|
})
|
||||||
.manage(graphql::Context(graph_pool))
|
|
||||||
.manage(Schema::new(
|
.manage(Schema::new(
|
||||||
Query,
|
Query,
|
||||||
EmptyMutation::new(),
|
EmptyMutation::new(),
|
||||||
|
|
@ -178,9 +181,8 @@ async fn launch() -> _ {
|
||||||
nnid::mapped_ids::mapped_ids,
|
nnid::mapped_ids::mapped_ids,
|
||||||
papi::login::login,
|
papi::login::login,
|
||||||
papi::user::get_user,
|
papi::user::get_user,
|
||||||
//graphql
|
// graphql::graphiql,
|
||||||
graphql::graphiql,
|
// graphql::playground,
|
||||||
graphql::playground,
|
|
||||||
graphql::get_graphql,
|
graphql::get_graphql,
|
||||||
graphql::post_graphql,
|
graphql::post_graphql,
|
||||||
])
|
])
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,5 @@
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::fs;
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::path::Path;
|
|
||||||
use chrono::{NaiveDate, NaiveDateTime};
|
use chrono::{NaiveDate, NaiveDateTime};
|
||||||
use gxhash::{gxhash32, gxhash64};
|
use gxhash::{gxhash32, gxhash64};
|
||||||
use minio::s3::builders::{ObjectContent};
|
use minio::s3::builders::{ObjectContent};
|
||||||
|
|
@ -23,7 +20,6 @@ use crate::email::send_verification_email;
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use mii::{get_image_png, get_image_tga};
|
use mii::{get_image_png, get_image_tga};
|
||||||
use minio::s3::client::Client;
|
use minio::s3::client::Client;
|
||||||
use minio::s3::args::PutObjectArgs;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
const DATABASE_ERROR: Errors = Errors{
|
const DATABASE_ERROR: Errors = Errors{
|
||||||
|
|
@ -60,7 +56,7 @@ fn get_mii_img_url_path(pid: i32, format: &str) -> String{
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_mii_img_url(pid: i32, format: &str) -> String{
|
fn get_mii_img_url(pid: i32, format: &str) -> String{
|
||||||
format!("{}/pn-boss/{}", &*S3_URL_STRING, get_mii_img_url_path(pid, format))
|
format!("{}/{}/{}", &*S3_URL_STRING, &*S3_BUCKET, get_mii_img_url_path(pid, format))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn generate_s3_images(pid: i32, mii_data: &str) {
|
pub async fn generate_s3_images(pid: i32, mii_data: &str) {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
use rocket::{State, post, FromForm, put};
|
use rocket::{State, post, FromForm, put};
|
||||||
use crate::Pool;
|
use crate::Pool;
|
||||||
use rocket::form::Form;
|
use rocket::form::Form;
|
||||||
use crate::email::send_verification_email;
|
|
||||||
use crate::error::{Error, Errors};
|
use crate::error::{Error, Errors};
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
use rocket::{post, State};
|
use rocket::{post, State};
|
||||||
use rocket::form::Form;
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use crate::Pool;
|
use crate::Pool;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,13 @@
|
||||||
|
use std::env;
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
use rocket::{get};
|
use rocket::{get};
|
||||||
use crate::account::account::{Auth};
|
use crate::account::account::{Auth};
|
||||||
use rocket::serde::json::Json;
|
use rocket::serde::json::Json;
|
||||||
|
|
||||||
|
pub static CDN_URL: Lazy<Box<str>> = Lazy::new(||
|
||||||
|
env::var("CDN_URL").expect("CDN_URL not specified").into_boxed_str()
|
||||||
|
);
|
||||||
|
|
||||||
#[derive(serde::Serialize)]
|
#[derive(serde::Serialize)]
|
||||||
struct EmailInfo {
|
struct EmailInfo {
|
||||||
address: String,
|
address: String,
|
||||||
|
|
@ -89,7 +95,7 @@ pub async fn get_user(auth: Auth<false>) -> Json<UserInfoResponse> {
|
||||||
.map(|v| v.name)
|
.map(|v| v.name)
|
||||||
.unwrap_or_else(|| "INVALID".to_string())
|
.unwrap_or_else(|| "INVALID".to_string())
|
||||||
},
|
},
|
||||||
image_url: format!("https://cdn.spfn.cc/mii/{}/normal_face.png", user.pid),
|
image_url: format!("https://{}/mii/{}/normal_face.png", &CDN_URL.to_string(), user.pid),
|
||||||
},
|
},
|
||||||
flags: FlagsInfo {
|
flags: FlagsInfo {
|
||||||
marketing: user.marketing_allowed,
|
marketing: user.marketing_allowed,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue