mirror of
https://github.com/Xevion/xevion.dev.git
synced 2026-02-01 02:26:53 -06:00
feat: add comprehensive CLI with API client, session management, and data seeding
- Binary renamed from 'api' to 'xevion' with full CLI command structure - Authentication: login/logout with session persistence to .xevion-session - API commands: projects, tags, settings CRUD operations with JSON/table output - Serve command: run production server with configurable listen addresses - Seed command: moved from bin/ to CLI subcommand for database initialization - HTTP client abstraction supporting both TCP and Unix socket connections
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use time::{Duration, OffsetDateTime};
|
||||
|
||||
use crate::cli::client::{ApiClient, Session, check_response};
|
||||
use crate::cli::output;
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct LoginRequest {
|
||||
username: String,
|
||||
password: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct LoginResponse {
|
||||
success: bool,
|
||||
username: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct SessionResponse {
|
||||
authenticated: bool,
|
||||
username: String,
|
||||
}
|
||||
|
||||
/// Login and save session
|
||||
pub async fn login(
|
||||
mut client: ApiClient,
|
||||
username: &str,
|
||||
password: &str,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let request = LoginRequest {
|
||||
username: username.to_string(),
|
||||
password: password.to_string(),
|
||||
};
|
||||
|
||||
let response = client.post("/api/login", &request).await?;
|
||||
|
||||
// Extract session cookie from response headers
|
||||
let session_token = response
|
||||
.headers()
|
||||
.get_all("set-cookie")
|
||||
.iter()
|
||||
.find_map(|v| {
|
||||
let s = v.to_str().ok()?;
|
||||
if s.starts_with("admin_session=") {
|
||||
// Extract just the token value
|
||||
let token = s.strip_prefix("admin_session=")?.split(';').next()?;
|
||||
Some(token.to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
let response = check_response(response).await?;
|
||||
let login_response: LoginResponse = response.json().await?;
|
||||
|
||||
if !login_response.success {
|
||||
output::error("Login failed");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let session_token = session_token.ok_or("No session cookie received")?;
|
||||
|
||||
let session = Session {
|
||||
api_url: client.api_url.clone(),
|
||||
session_token,
|
||||
username: login_response.username.clone(),
|
||||
created_at: OffsetDateTime::now_utc(),
|
||||
expires_at: OffsetDateTime::now_utc() + Duration::days(7),
|
||||
};
|
||||
|
||||
client.save_session(session)?;
|
||||
|
||||
output::success(&format!("Logged in as {}", login_response.username));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Clear saved session
|
||||
pub async fn logout(mut client: ApiClient) -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Try to call logout endpoint if we have a session
|
||||
if client.is_authenticated() {
|
||||
let _ = client.post("/api/logout", &()).await;
|
||||
}
|
||||
|
||||
client.clear_session()?;
|
||||
output::success("Logged out");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check current session status
|
||||
pub async fn session(client: ApiClient, json: bool) -> Result<(), Box<dyn std::error::Error>> {
|
||||
// First check local session
|
||||
if let Some(session) = client.session() {
|
||||
// Verify with server
|
||||
let response = client.get("/api/session").await?;
|
||||
let response = check_response(response).await?;
|
||||
let session_response: SessionResponse = response.json().await?;
|
||||
|
||||
if json {
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::json!({
|
||||
"authenticated": session_response.authenticated,
|
||||
"username": session_response.username,
|
||||
"api_url": session.api_url,
|
||||
"expires_at": session.expires_at.format(&time::format_description::well_known::Rfc3339).unwrap(),
|
||||
})
|
||||
);
|
||||
} else if session_response.authenticated {
|
||||
output::print_session(&session_response.username, &session.api_url);
|
||||
} else {
|
||||
output::error("Session expired or invalid");
|
||||
}
|
||||
} else {
|
||||
if json {
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::json!({
|
||||
"authenticated": false,
|
||||
})
|
||||
);
|
||||
} else {
|
||||
output::info("Not logged in");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user