Skip to main content

cloudillo_search/
admin.rs

1// SPDX-FileCopyrightText: Szilárd Hajba
2// SPDX-License-Identifier: LGPL-3.0-or-later
3
4//! `POST /api/search/reindex` — rebuilding one tenant's index by hand.
5//!
6//! The index maintains itself: every write path asks for the object it just
7//! wrote to be re-indexed, and [`crate::reindex`] sweeps weekly and on startup.
8//! This exists for the cases where waiting is not acceptable — an owner who
9//! suspects a write path forgot its `search_index_object` call and wants the
10//! answer now rather than on Sunday.
11//!
12//! Gated by `require_leader` in `cloudillo/src/routes/protected.rs`: rebuilding
13//! your own tenant's index is an ordinary owner operation, but a sweep re-reads
14//! every file, profile and action of that tenant, which is not something an
15//! ordinary member should be able to start. The scope is always the calling
16//! tenant — the whole-node sweep ([`crate::reindex::ReindexScope::All`]) is
17//! reachable only from the weekly recurring task, never from a request.
18//!
19//! The 202 only says the sweep was scheduled. The outcome arrives separately, as
20//! a `SEARCH_REINDEX_DONE` message broadcast to the tenant's WebSocket bus
21//! connections when the sweep ends — see [`crate::reindex`].
22
23use axum::{Json, extract::State, http::StatusCode};
24use cloudillo_types::types::ApiResponse;
25use serde::Serialize;
26
27use crate::{
28	prelude::*,
29	reindex::{ReindexScope, ReindexTask},
30};
31
32#[derive(Debug, Serialize)]
33#[serde(rename_all = "camelCase")]
34pub struct ReindexResponse {
35	/// Scheduler task id, so the run can be found in `tasks` and in the logs.
36	pub task_id: u64,
37	/// The tenant that will be swept.
38	pub scope: String,
39	/// The extraction revision the sweep will stamp on what it rebuilds.
40	pub index_rev: u32,
41}
42
43/// POST /api/search/reindex — rebuild the calling tenant's full-text index.
44///
45/// Scheduled rather than run inline: a sweep is proportional to the tenant's
46/// data and would hold the request open for minutes. It runs unconditionally —
47/// this is not the startup path, so the stored `search.index_rev` does not gate
48/// it — and logs what it touched at `info` when it finishes.
49///
50/// The same counts also go to the caller: the task pushes `SEARCH_REINDEX_DONE`
51/// to this tenant's bus connections when it finishes, so the client need not poll
52/// a `taskId` that has no endpoint behind it. A failure sends one message too, on
53/// the first failed attempt, and then stays silent for the nine retries.
54///
55/// The scheduler's key dedup means calling this repeatedly coalesces into one
56/// pending run per tenant rather than queueing a sweep per request.
57#[axum::debug_handler]
58pub async fn post_reindex(
59	State(app): State<App>,
60	tn_id: TnId,
61) -> ClResult<(StatusCode, Json<ApiResponse<ReindexResponse>>)> {
62	let scope = ReindexScope::Tenant { tn_id };
63	let key = format!("search.reindex:{}", tn_id.0);
64
65	// `.now()` rather than a delay: an owner asking for this wants it started,
66	// and the scheduler still runs it off the request task.
67	let task_id = app
68		.scheduler
69		.task(std::sync::Arc::new(ReindexTask { scope }))
70		.key(key)
71		.with_retry(cloudillo_core::scheduler::RetryPolicy::default())
72		.now()
73		.await?;
74
75	info!(tn_id = %tn_id, %task_id, "Search reindex requested");
76
77	Ok((
78		StatusCode::ACCEPTED,
79		Json(ApiResponse::new(ReindexResponse {
80			task_id,
81			scope: tn_id.to_string(),
82			index_rev: crate::INDEX_REV,
83		})),
84	))
85}
86
87// vim: ts=4