forked from yvv/decision
Objections « redit » / « contredit » à la place des signalements : la dernière arrivée des deux versions n'est plus votable (repli, panier, chaîne : un vote déjà scellé est gardé mais ne la fait pas entrer) ; un texte en vigueur ne l'est jamais. L'auteur répond en modifiant l'article visé (proposition liée) ou en reformulant ; la personne qui objecte lève ou réitère depuis ses messages ; sans réponse sous 3 jours, l'objection est levée. Un administrateur peut lever. Coquilles : la sienne passe tout de suite ; celle d'un autre attend l'auteur (valider / refuser), passe seule au bout de 3 jours, et l'auteur garde la main tant que le texte n'est pas en vigueur. En vigueur, seule une coquille validée corrige (auteur de la version, administrateur pour un texte semé). Deux coquilles en attente : la seconde se mesure au texte voté ; à l'application, celle qui ne colle plus est périmée. Messagerie dans l'application (boîte, non-lus, gestes, échéance), chaque personne ne lit que les siens. Échéances par le balayage existant, avec rattrapage au démarrage et horloge injectable (route de dev). Migration 0008 additive : les signalements deviennent des « contredit », sans perte. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
58 lines
2.1 KiB
Rust
58 lines
2.1 KiB
Rust
//! The deadlines of three days (2026-10-31, lot F): an objection whose author
|
|
//! answered and whose objector said nothing is lifted; a typo fix nobody
|
|
//! answered applies. Run by the adoption sweeper (every 5 minutes) and once at
|
|
//! start, so a deadline passed while the server was stopped is dealt with as
|
|
//! soon as it starts again. Tests move the clock (`AppState::clock`), never sleep.
|
|
|
|
use crate::AppState;
|
|
use crate::adoption;
|
|
use crate::error::{AppError, AppResult};
|
|
use crate::{objections, typos};
|
|
use axum::Json;
|
|
use axum::extract::State;
|
|
use serde::Deserialize;
|
|
use serde_json::{Value, json};
|
|
|
|
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
|
pub struct Swept {
|
|
/// Objections lifted at their deadline.
|
|
pub lifted: usize,
|
|
/// Typo fixes applied at their deadline.
|
|
pub applied: usize,
|
|
/// Typo fixes that could not apply: their text was gone.
|
|
pub stale: usize,
|
|
}
|
|
|
|
/// One pass: the objections, then the typo fixes, then the adoption of the
|
|
/// documents where an objection was lifted (a version may now enter into force).
|
|
pub async fn sweep(state: &AppState) -> AppResult<Swept> {
|
|
let (lifted, docs) = objections::due(state).await?;
|
|
let fixes = typos::due(state).await?;
|
|
for doc in &docs {
|
|
adoption::run(state, doc).await?;
|
|
}
|
|
Ok(Swept { lifted, applied: fixes.applied, stale: fixes.stale })
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
pub struct ClockBody {
|
|
seconds: i64,
|
|
}
|
|
|
|
/// `POST /api/dev/clock` — the end-to-end tests move the server's clock forward
|
|
/// and pass the deadlines at once. Open with the dev login only, like it.
|
|
pub async fn dev_clock(State(state): State<AppState>, Json(b): Json<ClockBody>) -> AppResult<Json<Value>> {
|
|
if !state.cfg.dev_login {
|
|
return Err(AppError::NotFound);
|
|
}
|
|
if b.seconds <= 0 || b.seconds > 400 * 86_400 {
|
|
return Err(AppError::Invalid("seconds"));
|
|
}
|
|
state.clock.advance(b.seconds);
|
|
let swept = sweep(&state).await?;
|
|
Ok(Json(json!({
|
|
"now": state.clock.now(), "offset": state.clock.offset(),
|
|
"lifted": swept.lifted, "applied": swept.applied, "stale": swept.stale,
|
|
})))
|
|
}
|