refactor: purge Node.js, Rust is the one true backend

- Moved boosapi-rs/* to repo root
- Deleted all Node.js source (src/server/, src/web/, scripts/)
- Deleted node_modules, package.json, tsconfig, vite.config
- Updated .gitignore and AGENTS.md for Rust-only stack
- Fixed static file path for frontend dist/web/
- Dockerfile stays the same (already Rust-only)
- Kept dist/web/ for Rust static serving
This commit is contained in:
boos
2026-05-18 09:11:29 +08:00
parent 725f44699a
commit 5d29f03d50
1165 changed files with 271 additions and 323571 deletions
+160
View File
@@ -0,0 +1,160 @@
use crate::{
auth::jwt::validate_jwt,
db::models::{downstream_api_key::DownstreamApiKey, user::User},
error::{AppError, AppResult},
AppState,
};
use axum::{
extract::{Path, State},
http::{HeaderMap, StatusCode},
routing::{delete, get},
Json, Router,
};
use serde::Deserialize;
use serde_json::{json, Value};
pub fn routes() -> Router<AppState> {
Router::new()
.route("/v1/user/profile", get(get_profile).put(update_profile))
.route("/v1/user/api-keys", get(list_api_keys).post(create_api_key))
.route("/v1/user/api-keys/{id}", delete(delete_api_key))
}
#[derive(Debug, Deserialize)]
struct UpdateProfilePayload {
username: Option<String>,
email: Option<String>,
}
#[derive(Debug, Deserialize)]
struct CreateApiKeyPayload {
name: String,
description: Option<String>,
expires_at: Option<String>,
max_requests: Option<i64>,
max_cost: Option<f64>,
}
fn current_user_id(headers: &HeaderMap, state: &AppState) -> AppResult<i64> {
let auth = headers
.get("Authorization")
.and_then(|v| v.to_str().ok())
.and_then(|v| v.strip_prefix("Bearer "))
.ok_or_else(|| AppError::Unauthorized("Missing or invalid Authorization header".into()))?;
let claims = validate_jwt(&state.config.auth.jwt_secret, auth)
.map_err(|_| AppError::Unauthorized("Invalid or expired token".into()))?;
claims
.sub
.parse()
.map_err(|_| AppError::Unauthorized("Invalid user ID in token".into()))
}
async fn get_profile(State(state): State<AppState>, headers: HeaderMap) -> AppResult<Json<Value>> {
let user_id = current_user_id(&headers, &state)?;
let user: User = sqlx::query_as("SELECT * FROM users WHERE id = ?")
.bind(user_id)
.fetch_optional(&*state.db)
.await?
.ok_or_else(|| AppError::NotFound("User not found".into()))?;
Ok(Json(json!({ "user": user })))
}
async fn update_profile(
State(state): State<AppState>,
headers: HeaderMap,
Json(payload): Json<UpdateProfilePayload>,
) -> AppResult<Json<Value>> {
let user_id = current_user_id(&headers, &state)?;
let current: User = sqlx::query_as("SELECT * FROM users WHERE id = ?")
.bind(user_id)
.fetch_optional(&*state.db)
.await?
.ok_or_else(|| AppError::NotFound("User not found".into()))?;
let username = payload.username.unwrap_or(current.username);
let email = payload.email.unwrap_or(current.email);
sqlx::query(
"UPDATE users
SET username = ?, email = ?, updated_at = datetime('now')
WHERE id = ?",
)
.bind(username)
.bind(email)
.bind(user_id)
.execute(&*state.db)
.await?;
get_profile(State(state), headers).await
}
async fn list_api_keys(
State(state): State<AppState>,
headers: HeaderMap,
) -> AppResult<Json<Value>> {
let user_id = current_user_id(&headers, &state)?;
let keys: Vec<DownstreamApiKey> = sqlx::query_as(
"SELECT * FROM downstream_api_keys WHERE user_id = ? ORDER BY created_at DESC",
)
.bind(user_id)
.fetch_all(&*state.db)
.await?;
Ok(Json(json!({ "keys": keys })))
}
async fn create_api_key(
State(state): State<AppState>,
headers: HeaderMap,
Json(payload): Json<CreateApiKeyPayload>,
) -> AppResult<(StatusCode, Json<Value>)> {
let user_id = current_user_id(&headers, &state)?;
if payload.name.trim().is_empty() {
return Err(AppError::BadRequest("name is required".into()));
}
let token = format!("sk-user-{}", uuid::Uuid::new_v4().simple());
let result = sqlx::query(
"INSERT INTO downstream_api_keys
(name, key, description, enabled, expires_at, max_requests, max_cost, user_id)
VALUES (?, ?, ?, 1, ?, ?, ?, ?)",
)
.bind(payload.name)
.bind(&token)
.bind(payload.description)
.bind(payload.expires_at)
.bind(payload.max_requests)
.bind(payload.max_cost)
.bind(user_id)
.execute(&*state.db)
.await?;
let key: DownstreamApiKey = sqlx::query_as("SELECT * FROM downstream_api_keys WHERE id = ?")
.bind(result.last_insert_rowid())
.fetch_one(&*state.db)
.await?;
Ok((StatusCode::CREATED, Json(json!({ "key": key }))))
}
async fn delete_api_key(
State(state): State<AppState>,
headers: HeaderMap,
Path(id): Path<i64>,
) -> AppResult<StatusCode> {
let user_id = current_user_id(&headers, &state)?;
let result = sqlx::query("DELETE FROM downstream_api_keys WHERE id = ? AND user_id = ?")
.bind(id)
.bind(user_id)
.execute(&*state.db)
.await?;
if result.rows_affected() == 0 {
return Err(AppError::NotFound("API key not found".into()));
}
Ok(StatusCode::NO_CONTENT)
}