feat(web-server): extraire le serveur headless idea-serve du binaire Tauri (#65)
Sort le serveur HTTP/WS de `app-tauri` vers un crate `web-server` autonome,
exposant le binaire `idea-serve` qui sert le client web sans aucune dépendance
GUI. Le cœur (use cases, DTO, events) devient partagé entre deux driving
adapters : le desktop Tauri et le serveur headless.
Structure :
- `crates/web-server` : lib + bin `idea-serve`, consomme `backend::{dto, events}`.
- `crates/backend` : `dto.rs` et `events.rs` deviennent le propriétaire canonique
des DTO/events transport-neutres (arbitrage Architect : pas de crate contrat
dédié, `backend` est déjà le composition root transport-neutre).
- `crates/app-tauri` : `dto.rs` réduit à un shim de ré-export, `events.rs` réduit
au relais Tauri (souscription au bus + emit), `server.rs` vidé au profit du
crate partagé.
- `Cargo.toml`/`Cargo.lock` : `web-server` ajouté aux membres du workspace.
Frontière respectée : les DTO de `backend` ne tirent ni Tauri, ni HTTP/WS, ni
Axum/Hyper, ni UI ; `domain`/`application` n'en dépendent pas. Le frontend est
inchangé (`git diff 506d589...HEAD -- frontend` vide).
`web_server::run_embedded(...)` est le point d'extension prévu pour #68 (le
desktop consommera `web-server` comme lib plutôt que d'en forker le serveur).
QA (exécution réelle, re-vérifiée avant merge) :
- `cargo test -p backend` 41 passed · `-p web-server` 55 passed · `-p app-tauri`
35 passed, 0 échec.
- Invariant headless : `ldd target/debug/idea-serve` ne tire aucun
webkit/javascriptcore/gtk/gdk/soup/tauri/wry, là où `app-tauri` les tire bien.
- Validation live utilisateur : `idea-serve` démarre, sert le SPA, le contrôle
d'origine strict tient, accès distant OK via reverse proxy.
- Les 10 échecs `openai_compat` de `cargo test --workspace` sont préexistants sur
`506d589` (bind réseau interdit par la sandbox), hors périmètre.
Squash de `82e8e77` (commit de sûreté « état intermédiaire non figé », qui
rapatriait le travail réalisé par erreur sur la branche de #69) et de `ddbea7b`
(déduplication des DTO events, comblant l'écart annoncé par le premier). Les deux
n'avaient de sens qu'ensemble : ce commit fige le contrat que le premier laissait
explicitement ouvert. Arbre identique bit pour bit à `ddbea7b` ; historique
d'origine conservé sur `backup/ticket65-pre-squash-ddbea7b`.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -24,6 +24,7 @@ domain = { workspace = true }
|
||||
application = { workspace = true }
|
||||
infrastructure = { workspace = true }
|
||||
backend = { workspace = true }
|
||||
web-server = { workspace = true }
|
||||
tauri = { workspace = true }
|
||||
tauri-plugin-dialog = { workspace = true }
|
||||
# `io-std` gives the headless `mcp-server` bridge access to
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1743,15 +1743,6 @@ fn required_str<'a>(arguments: &'a Value, key: &str) -> Result<&'a str, TicketTo
|
||||
.ok_or_else(|| TicketToolError::new("invalid", format!("missing {key}")))
|
||||
}
|
||||
|
||||
impl ErrorDto {
|
||||
fn invalid(message: impl Into<String>) -> Self {
|
||||
Self {
|
||||
code: "INVALID".to_owned(),
|
||||
message: message.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
3495
crates/backend/src/dto.rs
Normal file
3495
crates/backend/src/dto.rs
Normal file
File diff suppressed because it is too large
Load Diff
1314
crates/backend/src/events.rs
Normal file
1314
crates/backend/src/events.rs
Normal file
File diff suppressed because it is too large
Load Diff
@ -83,6 +83,8 @@ use infrastructure::{
|
||||
VECTOR_HTTP_ENABLED, VECTOR_ONNX_ENABLED,
|
||||
};
|
||||
|
||||
pub mod dto;
|
||||
pub mod events;
|
||||
pub mod mcp_endpoint;
|
||||
pub mod openai_tools;
|
||||
pub mod stream;
|
||||
|
||||
@ -159,7 +159,7 @@ pub fn mcp_endpoint(project_id: &ProjectId) -> McpEndpoint {
|
||||
/// `/tmp/.mount_*` qui disparaît au redémarrage ⇒ `ENOENT` au respawn du pont MCP.
|
||||
/// Hors AppImage `$APPIMAGE` est absent ⇒ on retombe sur `current_exe()`. `None` si
|
||||
/// aucun des deux n'est résolvable (ne devrait pas arriver) ⇒ déclaration minimale.
|
||||
pub(crate) fn idea_exe_path() -> Option<String> {
|
||||
pub fn idea_exe_path() -> Option<String> {
|
||||
std::env::var("APPIMAGE").ok().or_else(|| {
|
||||
std::env::current_exe()
|
||||
.ok()
|
||||
|
||||
33
crates/web-server/Cargo.toml
Normal file
33
crates/web-server/Cargo.toml
Normal file
@ -0,0 +1,33 @@
|
||||
[package]
|
||||
name = "web-server"
|
||||
version = "0.3.0"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "IdeA headless HTTP/WebSocket driving adapter, reusable by desktop and server binaries."
|
||||
|
||||
[lib]
|
||||
name = "web_server"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "idea-serve"
|
||||
path = "src/bin/idea-serve.rs"
|
||||
|
||||
[dependencies]
|
||||
backend = { workspace = true }
|
||||
application = { workspace = true }
|
||||
domain = { workspace = true }
|
||||
tokio = { workspace = true, features = ["io-std", "rt", "net"] }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
uuid = { workspace = true }
|
||||
base64 = "0.22"
|
||||
bytes = "1.11"
|
||||
cookie = "0.18"
|
||||
http = "1.4"
|
||||
http-body-util = "0.1"
|
||||
|
||||
[features]
|
||||
vector-http = ["backend/vector-http"]
|
||||
vector-onnx = ["backend/vector-onnx"]
|
||||
5
crates/web-server/src/bin/idea-serve.rs
Normal file
5
crates/web-server/src/bin/idea-serve.rs
Normal file
@ -0,0 +1,5 @@
|
||||
use std::process::ExitCode;
|
||||
|
||||
fn main() -> ExitCode {
|
||||
web_server::run_from_args(std::env::args().skip(1).collect())
|
||||
}
|
||||
4140
crates/web-server/src/lib.rs
Normal file
4140
crates/web-server/src/lib.rs
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user