mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-09-10 03:41:52 +02:00
One table versions HTTP, gRPC and WebSocket requests alike. A version stores the model's editable content — the serialized model minus model/id/timestamps/ workspace/folder/sortPriority — and is addressed by the sha256 of that document, with (model_id, content_hash) unique. Content addressing is what makes the trigger side simple: snapshot_request can be called by a send, a window blur and an idle timer on the same unedited request and leave one row behind, so no caller has to reason about whether anything changed. Dropping bookkeeping keys is what makes it stable — moving a request between folders or re-sorting it rewrites those fields and nothing else, and neither should mint a version or show up in a diff. The same rule covers all three request types because they differ only in content fields. Responses and gRPC/WebSocket connections gain a nullable version_id. Versions are local history, so ModelVersion is deliberately absent from AnyModel: no model-change rows, no frontend store bucket, no sync, no export. Retention keeps anything a response points at, plus the newest 50 per request within 30 days; request and workspace deletes cascade.
172 lines
6.5 KiB
Rust
172 lines
6.5 KiB
Rust
use super::{conflict_free_name, merge_headers};
|
|
use crate::client_db::ClientDb;
|
|
use crate::error::Result;
|
|
use crate::models::{
|
|
AnyModel, Folder, FolderIden, HttpRequestHeader, ResolvedHttpRequestSettings, ResolvedSetting,
|
|
WebsocketRequest, WebsocketRequestIden,
|
|
};
|
|
use crate::util::UpdateSource;
|
|
use serde_json::Value;
|
|
use std::collections::BTreeMap;
|
|
|
|
impl<'a> ClientDb<'a> {
|
|
pub fn get_websocket_request(&self, id: &str) -> Result<WebsocketRequest> {
|
|
self.find_one(WebsocketRequestIden::Id, id)
|
|
}
|
|
|
|
pub fn list_websocket_requests(&self, workspace_id: &str) -> Result<Vec<WebsocketRequest>> {
|
|
self.find_many(WebsocketRequestIden::WorkspaceId, workspace_id, None)
|
|
}
|
|
|
|
pub fn list_websocket_requests_for_folder_recursive(
|
|
&self,
|
|
folder_id: &str,
|
|
) -> Result<Vec<WebsocketRequest>> {
|
|
let mut children = Vec::new();
|
|
for folder in self.find_many::<Folder>(FolderIden::FolderId, folder_id, None)? {
|
|
children.extend(self.list_websocket_requests_for_folder_recursive(&folder.id)?);
|
|
}
|
|
for request in
|
|
self.find_many::<WebsocketRequest>(WebsocketRequestIden::FolderId, folder_id, None)?
|
|
{
|
|
children.push(request);
|
|
}
|
|
Ok(children)
|
|
}
|
|
|
|
pub fn delete_websocket_request(
|
|
&self,
|
|
websocket_request: &WebsocketRequest,
|
|
source: &UpdateSource,
|
|
) -> Result<WebsocketRequest> {
|
|
self.delete_all_websocket_connections_for_request(websocket_request.id.as_str(), source)?;
|
|
self.delete_model_versions_for_model(websocket_request.id.as_str())?;
|
|
self.delete(websocket_request, source)
|
|
}
|
|
|
|
pub fn delete_websocket_request_by_id(
|
|
&self,
|
|
id: &str,
|
|
source: &UpdateSource,
|
|
) -> Result<WebsocketRequest> {
|
|
let request = self.get_websocket_request(id)?;
|
|
self.delete_websocket_request(&request, source)
|
|
}
|
|
|
|
pub fn duplicate_websocket_request(
|
|
&self,
|
|
websocket_request: &WebsocketRequest,
|
|
source: &UpdateSource,
|
|
) -> Result<WebsocketRequest> {
|
|
let mut websocket_request = websocket_request.clone();
|
|
websocket_request.id = "".to_string();
|
|
websocket_request.sort_priority = websocket_request.sort_priority + 0.001;
|
|
let sibling_names = self
|
|
.list_websocket_requests(&websocket_request.workspace_id)?
|
|
.into_iter()
|
|
.filter(|m| m.folder_id == websocket_request.folder_id)
|
|
.map(|m| m.name)
|
|
.collect::<Vec<_>>();
|
|
websocket_request.name = conflict_free_name(&websocket_request.name, &sibling_names);
|
|
self.upsert(&websocket_request, source)
|
|
}
|
|
|
|
pub fn upsert_websocket_request(
|
|
&self,
|
|
websocket_request: &WebsocketRequest,
|
|
source: &UpdateSource,
|
|
) -> Result<WebsocketRequest> {
|
|
self.upsert(websocket_request, source)
|
|
}
|
|
|
|
pub fn resolve_auth_for_websocket_request(
|
|
&self,
|
|
websocket_request: &WebsocketRequest,
|
|
) -> Result<(Option<String>, BTreeMap<String, Value>, String)> {
|
|
if let Some(at) = websocket_request.authentication_type.clone() {
|
|
return Ok((
|
|
Some(at),
|
|
websocket_request.authentication.clone(),
|
|
websocket_request.id.clone(),
|
|
));
|
|
}
|
|
|
|
if let Some(folder_id) = websocket_request.folder_id.clone() {
|
|
let folder = self.get_folder(&folder_id)?;
|
|
return self.resolve_auth_for_folder(&folder);
|
|
}
|
|
|
|
let workspace = self.get_workspace(&websocket_request.workspace_id)?;
|
|
Ok(self.resolve_auth_for_workspace(&workspace))
|
|
}
|
|
|
|
pub fn resolve_headers_for_websocket_request(
|
|
&self,
|
|
websocket_request: &WebsocketRequest,
|
|
) -> Result<Vec<HttpRequestHeader>> {
|
|
// Resolved headers should be from furthest to closest ancestor, to override logically.
|
|
let mut headers = Vec::new();
|
|
|
|
if let Some(folder_id) = websocket_request.folder_id.clone() {
|
|
let parent_folder = self.get_folder(&folder_id)?;
|
|
let mut folder_headers = self.resolve_headers_for_folder(&parent_folder)?;
|
|
headers.append(&mut folder_headers);
|
|
} else {
|
|
let workspace = self.get_workspace(&websocket_request.workspace_id)?;
|
|
let mut workspace_headers = self.resolve_headers_for_workspace(&workspace);
|
|
headers.append(&mut workspace_headers);
|
|
}
|
|
|
|
Ok(merge_headers(headers, websocket_request.headers.clone()))
|
|
}
|
|
|
|
pub fn resolve_settings_for_websocket_request(
|
|
&self,
|
|
websocket_request: &WebsocketRequest,
|
|
) -> Result<ResolvedHttpRequestSettings> {
|
|
let parent = if let Some(folder_id) = websocket_request.folder_id.clone() {
|
|
let folder = self.get_folder(&folder_id)?;
|
|
self.resolve_settings_for_folder(&folder)?
|
|
} else {
|
|
let workspace = self.get_workspace(&websocket_request.workspace_id)?;
|
|
self.resolve_settings_for_workspace(&workspace)
|
|
};
|
|
|
|
Ok(ResolvedHttpRequestSettings {
|
|
validate_certificates: if websocket_request.setting_validate_certificates.enabled {
|
|
ResolvedSetting::from_model(
|
|
websocket_request.setting_validate_certificates.value,
|
|
AnyModel::WebsocketRequest(websocket_request.clone()),
|
|
)
|
|
} else {
|
|
parent.validate_certificates
|
|
},
|
|
request_message_size: if websocket_request.setting_request_message_size.enabled {
|
|
ResolvedSetting::from_model(
|
|
websocket_request.setting_request_message_size.value,
|
|
AnyModel::WebsocketRequest(websocket_request.clone()),
|
|
)
|
|
} else {
|
|
parent.request_message_size
|
|
},
|
|
send_cookies: if websocket_request.setting_send_cookies.enabled {
|
|
ResolvedSetting::from_model(
|
|
websocket_request.setting_send_cookies.value,
|
|
AnyModel::WebsocketRequest(websocket_request.clone()),
|
|
)
|
|
} else {
|
|
parent.send_cookies
|
|
},
|
|
store_cookies: if websocket_request.setting_store_cookies.enabled {
|
|
ResolvedSetting::from_model(
|
|
websocket_request.setting_store_cookies.value,
|
|
AnyModel::WebsocketRequest(websocket_request.clone()),
|
|
)
|
|
} else {
|
|
parent.store_cookies
|
|
},
|
|
..parent
|
|
})
|
|
}
|
|
}
|