Skip to main content

tuitbot_server/routes/
targets.rs

1//! Target accounts endpoints.
2
3use std::sync::Arc;
4
5use axum::extract::{Path, State};
6use axum::Json;
7use serde::Deserialize;
8use serde_json::{json, Value};
9use tuitbot_core::storage::target_accounts;
10
11use crate::error::ApiError;
12use crate::state::AppState;
13
14/// `GET /api/targets` — list target accounts and their state.
15pub async fn list_targets(State(state): State<Arc<AppState>>) -> Result<Json<Value>, ApiError> {
16    let accounts = target_accounts::get_active_target_accounts(&state.db).await?;
17    Ok(Json(json!(accounts)))
18}
19
20/// Request body for adding a target account.
21#[derive(Deserialize)]
22pub struct AddTargetRequest {
23    /// Username of the target account (without @).
24    pub username: String,
25}
26
27/// `POST /api/targets` — add a new target account.
28pub async fn add_target(
29    State(state): State<Arc<AppState>>,
30    Json(body): Json<AddTargetRequest>,
31) -> Result<Json<Value>, ApiError> {
32    let username = body.username.trim().trim_start_matches('@');
33
34    if username.is_empty() {
35        return Err(ApiError::BadRequest("username is required".to_string()));
36    }
37
38    // Check if already exists and active.
39    if let Some(existing) =
40        target_accounts::get_target_account_by_username(&state.db, username).await?
41    {
42        if existing.status == "active" {
43            return Err(ApiError::Conflict(format!(
44                "target account @{username} already exists"
45            )));
46        }
47    }
48
49    // Use username as a placeholder account_id; the automation runtime will
50    // resolve the real X user ID when it runs target monitoring.
51    target_accounts::upsert_target_account(&state.db, username, username).await?;
52
53    Ok(Json(
54        json!({"status": "added", "username": username.to_string()}),
55    ))
56}
57
58/// `DELETE /api/targets/:username` — deactivate a target account.
59pub async fn remove_target(
60    State(state): State<Arc<AppState>>,
61    Path(username): Path<String>,
62) -> Result<Json<Value>, ApiError> {
63    let removed = target_accounts::deactivate_target_account(&state.db, &username).await?;
64
65    if !removed {
66        return Err(ApiError::NotFound(format!(
67            "active target account @{username} not found"
68        )));
69    }
70
71    Ok(Json(json!({"status": "removed", "username": username})))
72}