use std::sync::Arc;
use axum::extract::{Path, Query, State};
use axum::Json;
use serde::Deserialize;
use serde_json::{json, Value};
use tuitbot_core::storage::target_accounts;
use crate::account::{require_mutate, AccountContext};
use crate::error::ApiError;
use crate::state::AppState;
pub async fn list_targets(
State(state): State<Arc<AppState>>,
ctx: AccountContext,
) -> Result<Json<Value>, ApiError> {
let accounts =
target_accounts::get_enriched_target_accounts_for(&state.db, &ctx.account_id).await?;
Ok(Json(json!(accounts)))
}
#[derive(Deserialize)]
pub struct AddTargetRequest {
pub username: String,
}
pub async fn add_target(
State(state): State<Arc<AppState>>,
ctx: AccountContext,
Json(body): Json<AddTargetRequest>,
) -> Result<Json<Value>, ApiError> {
require_mutate(&ctx)?;
let username = body.username.trim().trim_start_matches('@');
if username.is_empty() {
return Err(ApiError::BadRequest("username is required".to_string()));
}
if let Some(existing) =
target_accounts::get_target_account_by_username_for(&state.db, &ctx.account_id, username)
.await?
{
if existing.status == "active" {
return Err(ApiError::Conflict(format!(
"target account @{username} already exists"
)));
}
}
target_accounts::upsert_target_account_for(&state.db, &ctx.account_id, username, username)
.await?;
Ok(Json(
json!({"status": "added", "username": username.to_string()}),
))
}
pub async fn remove_target(
State(state): State<Arc<AppState>>,
ctx: AccountContext,
Path(username): Path<String>,
) -> Result<Json<Value>, ApiError> {
require_mutate(&ctx)?;
let removed =
target_accounts::deactivate_target_account_for(&state.db, &ctx.account_id, &username)
.await?;
if !removed {
return Err(ApiError::NotFound(format!(
"active target account @{username} not found"
)));
}
Ok(Json(json!({"status": "removed", "username": username})))
}
#[derive(Deserialize)]
pub struct TimelineQuery {
pub limit: Option<i64>,
}
pub async fn target_timeline(
State(state): State<Arc<AppState>>,
ctx: AccountContext,
Path(username): Path<String>,
Query(params): Query<TimelineQuery>,
) -> Result<Json<Value>, ApiError> {
let limit = params.limit.unwrap_or(50).min(200);
let items =
target_accounts::get_target_timeline_for(&state.db, &ctx.account_id, &username, limit)
.await?;
Ok(Json(json!(items)))
}
pub async fn target_stats(
State(state): State<Arc<AppState>>,
ctx: AccountContext,
Path(username): Path<String>,
) -> Result<Json<Value>, ApiError> {
let stats =
target_accounts::get_target_stats_for(&state.db, &ctx.account_id, &username).await?;
match stats {
Some(s) => Ok(Json(json!(s))),
None => Err(ApiError::NotFound(format!(
"active target account @{username} not found"
))),
}
}