mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-20 17:02:53 -04:00
split bindings
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
# artcraft_client
|
||||
|
||||
Rust HTTP endpoint bindings used by the ArtCraft Tauri desktop app.
|
||||
|
||||
## Endpoint layout
|
||||
|
||||
- Each HTTP endpoint binding must live in its own descriptively named `.rs` file.
|
||||
This applies to both free functions and methods implemented on a shared client.
|
||||
Do not combine several routes into a catch-all file such as `login_challenges.rs`
|
||||
or `password_auth.rs`.
|
||||
- Group related bindings in a directory module. Keep `mod.rs` for module declarations;
|
||||
it must not contain endpoint implementations. For example:
|
||||
`endpoints/users/login_challenges/create_login_challenge.rs` and
|
||||
`endpoints/users/login_challenges/poll_login_challenge.rs`.
|
||||
- Keep each endpoint's route constant, request construction, and endpoint-specific
|
||||
request/response types in its file. Keep shared wire types in `artcraft_api_defs`
|
||||
or a dedicated shared type module; do not duplicate them across endpoint files.
|
||||
- Put shared HTTP transport, client setup, and common error handling in a separate
|
||||
utility module. A transport module must not accumulate endpoint bindings.
|
||||
- Update callers to import types from their new modules when splitting bindings.
|
||||
Preserve request methods, paths, headers, error behavior, and cookie handling.
|
||||
|
||||
## Validation
|
||||
|
||||
Run `cargo check --offline -p artcraft_client` for client changes. For native login
|
||||
bindings, also run `SQLX_OFFLINE=true cargo test --offline -p artcraft --lib
|
||||
login_bridge_tests`; these tests use local HTTP fixtures and temporary cookie jars.
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
use artcraft_api_defs::users::login_challenges::{CreateLoginChallengeRequest, CreateLoginChallengeResponse};
|
||||
use reqwest::Method;
|
||||
|
||||
use crate::utils::login_challenge_client::{LoginChallengeClient, LoginChallengeClientError};
|
||||
|
||||
pub const CREATE_PATH: &str = "/v1/login_challenges/create";
|
||||
|
||||
impl LoginChallengeClient {
|
||||
pub async fn create(&self) -> Result<CreateLoginChallengeResponse, LoginChallengeClientError> {
|
||||
let request = self
|
||||
.request(Method::POST, CREATE_PATH)
|
||||
.json(&CreateLoginChallengeRequest {});
|
||||
let (result, _) = self.send(request, CREATE_PATH).await?;
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
pub mod create_login_challenge;
|
||||
pub mod poll_login_challenge;
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
use artcraft_api_defs::users::login_challenges::{LoginChallengeResponse, PollLoginChallengeRequest};
|
||||
use reqwest::Method;
|
||||
|
||||
use crate::utils::login_challenge_client::{LoginChallengeClient, LoginChallengeClientError};
|
||||
|
||||
pub const POLL_PATH: &str = "/v1/login_challenges/poll";
|
||||
|
||||
// Deliberately no Debug/Serialize: the wire result contains a bearer credential.
|
||||
pub struct PollLoginChallengeResult {
|
||||
pub response: LoginChallengeResponse,
|
||||
pub session_set_cookie: Option<String>,
|
||||
}
|
||||
|
||||
impl LoginChallengeClient {
|
||||
pub async fn poll(
|
||||
&self,
|
||||
device_token: &str,
|
||||
) -> Result<PollLoginChallengeResult, LoginChallengeClientError> {
|
||||
let request = self
|
||||
.request(Method::POST, POLL_PATH)
|
||||
.json(&PollLoginChallengeRequest {
|
||||
device_token: device_token.to_owned(),
|
||||
});
|
||||
let (response, session_set_cookie) = self.send(request, POLL_PATH).await?;
|
||||
Ok(PollLoginChallengeResult {
|
||||
response,
|
||||
session_set_cookie,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1,5 @@
|
||||
pub mod login_challenges;
|
||||
pub mod password_auth;
|
||||
pub mod password_auth_response;
|
||||
pub mod password_login;
|
||||
pub mod password_signup;
|
||||
pub mod session_info;
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
use reqwest::Method;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::login_challenges::{LoginChallengeClient, LoginChallengeClientError};
|
||||
|
||||
// No Debug: these request/response types contain credentials.
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct PasswordLoginRequest {
|
||||
pub username_or_email: String,
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct PasswordSignupRequest {
|
||||
pub username: String,
|
||||
pub email_address: String,
|
||||
pub password: String,
|
||||
pub password_confirmation: String,
|
||||
pub signup_source: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct PasswordAuthResponse {
|
||||
pub success: bool,
|
||||
pub signed_session: Option<String>,
|
||||
}
|
||||
|
||||
impl LoginChallengeClient {
|
||||
pub async fn password_login(&self, request: &PasswordLoginRequest) -> Result<(PasswordAuthResponse, Option<String>), LoginChallengeClientError> {
|
||||
let path = "/v1/login";
|
||||
self.send(self.request(Method::POST, path).json(request), path).await
|
||||
}
|
||||
|
||||
pub async fn password_signup(&self, request: &PasswordSignupRequest) -> Result<(PasswordAuthResponse, Option<String>), LoginChallengeClientError> {
|
||||
let path = "/v1/create_account";
|
||||
self.send(self.request(Method::POST, path).json(request), path).await
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
use serde::Deserialize;
|
||||
|
||||
// No Debug: the response contains a bearer credential.
|
||||
#[derive(Deserialize)]
|
||||
pub struct PasswordAuthResponse {
|
||||
pub success: bool,
|
||||
pub signed_session: Option<String>,
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
use reqwest::Method;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::utils::login_challenge_client::{LoginChallengeClient, LoginChallengeClientError};
|
||||
use super::password_auth_response::PasswordAuthResponse;
|
||||
|
||||
pub const LOGIN_PATH: &str = "/v1/login";
|
||||
|
||||
// No Debug: the request contains credentials.
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct PasswordLoginRequest {
|
||||
pub username_or_email: String,
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
impl LoginChallengeClient {
|
||||
pub async fn password_login(&self, request: &PasswordLoginRequest) -> Result<(PasswordAuthResponse, Option<String>), LoginChallengeClientError> {
|
||||
self.send(self.request(Method::POST, LOGIN_PATH).json(request), LOGIN_PATH).await
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
use reqwest::Method;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::utils::login_challenge_client::{LoginChallengeClient, LoginChallengeClientError};
|
||||
use super::password_auth_response::PasswordAuthResponse;
|
||||
|
||||
pub const SIGNUP_PATH: &str = "/v1/create_account";
|
||||
|
||||
// No Debug: the request contains credentials.
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct PasswordSignupRequest {
|
||||
pub username: String,
|
||||
pub email_address: String,
|
||||
pub password: String,
|
||||
pub password_confirmation: String,
|
||||
pub signup_source: String,
|
||||
}
|
||||
|
||||
impl LoginChallengeClient {
|
||||
pub async fn password_signup(&self, request: &PasswordSignupRequest) -> Result<(PasswordAuthResponse, Option<String>), LoginChallengeClientError> {
|
||||
self.send(self.request(Method::POST, SIGNUP_PATH).json(request), SIGNUP_PATH).await
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
use artcraft_api_defs::users::session_info::SessionInfoSuccessResponse;
|
||||
use reqwest::Method;
|
||||
|
||||
use crate::utils::login_challenge_client::{LoginChallengeClient, LoginChallengeClientError};
|
||||
|
||||
pub const SESSION_PATH: &str = "/v1/session";
|
||||
|
||||
impl LoginChallengeClient {
|
||||
pub async fn session(
|
||||
&self,
|
||||
signed_session: &str,
|
||||
) -> Result<SessionInfoSuccessResponse, LoginChallengeClientError> {
|
||||
let request = self
|
||||
.request(Method::GET, SESSION_PATH)
|
||||
.header("Cookie", format!("session={signed_session}"));
|
||||
let (result, _) = self.send(request, SESSION_PATH).await?;
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
-50
@@ -1,10 +1,5 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use artcraft_api_defs::users::login_challenges::{
|
||||
CreateLoginChallengeRequest, CreateLoginChallengeResponse, LoginChallengeResponse,
|
||||
PollLoginChallengeRequest,
|
||||
};
|
||||
use artcraft_api_defs::users::session_info::SessionInfoSuccessResponse;
|
||||
use log::{info, warn};
|
||||
use reqwest::header::SET_COOKIE;
|
||||
use reqwest::{Client, Method, RequestBuilder};
|
||||
@@ -14,10 +9,6 @@ use url::Url;
|
||||
use crate::utils::api_host::ApiHost;
|
||||
use crate::utils::constants::USER_AGENT;
|
||||
|
||||
pub const CREATE_PATH: &str = "/v1/login_challenges/create";
|
||||
pub const POLL_PATH: &str = "/v1/login_challenges/poll";
|
||||
pub const SESSION_PATH: &str = "/v1/session";
|
||||
|
||||
/// Native transport for the session bridge. No request/response bodies, cookies,
|
||||
/// or challenge credentials are logged, even at debug level. Never follows redirects.
|
||||
#[derive(Clone)]
|
||||
@@ -26,12 +17,6 @@ pub struct LoginChallengeClient {
|
||||
api_host: ApiHost,
|
||||
}
|
||||
|
||||
// Deliberately no Debug/Serialize: the wire result contains a bearer credential.
|
||||
pub struct PollLoginChallengeResult {
|
||||
pub response: LoginChallengeResponse,
|
||||
pub session_set_cookie: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LoginChallengeClientError {
|
||||
pub status: Option<u16>,
|
||||
@@ -92,41 +77,6 @@ impl LoginChallengeClient {
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub async fn create(&self) -> Result<CreateLoginChallengeResponse, LoginChallengeClientError> {
|
||||
let request = self
|
||||
.request(Method::POST, CREATE_PATH)
|
||||
.json(&CreateLoginChallengeRequest {});
|
||||
let (result, _) = self.send(request, CREATE_PATH).await?;
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub async fn poll(
|
||||
&self,
|
||||
device_token: &str,
|
||||
) -> Result<PollLoginChallengeResult, LoginChallengeClientError> {
|
||||
let request = self
|
||||
.request(Method::POST, POLL_PATH)
|
||||
.json(&PollLoginChallengeRequest {
|
||||
device_token: device_token.to_owned(),
|
||||
});
|
||||
let (response, session_set_cookie) = self.send(request, POLL_PATH).await?;
|
||||
Ok(PollLoginChallengeResult {
|
||||
response,
|
||||
session_set_cookie,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn session(
|
||||
&self,
|
||||
signed_session: &str,
|
||||
) -> Result<SessionInfoSuccessResponse, LoginChallengeClientError> {
|
||||
let request = self
|
||||
.request(Method::GET, SESSION_PATH)
|
||||
.header("Cookie", format!("session={signed_session}"));
|
||||
let (result, _) = self.send(request, SESSION_PATH).await?;
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub(crate) fn request(&self, method: Method, path: &str) -> RequestBuilder {
|
||||
self
|
||||
.client
|
||||
@@ -7,3 +7,4 @@ pub (crate) mod http_get_anonymous;
|
||||
pub mod api_host;
|
||||
pub mod constants;
|
||||
pub mod status_codes;
|
||||
pub mod login_challenge_client;
|
||||
|
||||
+3
-2
@@ -3,11 +3,12 @@ use std::sync::Arc;
|
||||
|
||||
use artcraft_api_defs::users::login_challenges::{LoginChallengeFailure, LoginChallengeState};
|
||||
use artcraft_api_defs::users::session_info::SessionUserInfo;
|
||||
use artcraft_client::endpoints::users::login_challenges::{
|
||||
use artcraft_client::utils::login_challenge_client::{
|
||||
LoginChallengeClient, LoginChallengeClientError,
|
||||
};
|
||||
use artcraft_client::utils::api_host::ApiHost;
|
||||
use artcraft_client::endpoints::users::password_auth::{PasswordLoginRequest, PasswordSignupRequest};
|
||||
use artcraft_client::endpoints::users::password_login::PasswordLoginRequest;
|
||||
use artcraft_client::endpoints::users::password_signup::PasswordSignupRequest;
|
||||
use chrono::{DateTime, Utc};
|
||||
use log::{info, warn};
|
||||
use serde::Serialize;
|
||||
|
||||
Reference in New Issue
Block a user