1use std::path::Path;
2
3use reqwest::multipart::{Form, Part};
4use serde_json::{json, Value};
5
6use crate::{
7 error::{OriginError, Result},
8 transport::HttpTransport,
9 CognifyOptions, SearchOptions, SearchType, DEFAULT_BASE_URL,
10};
11
12#[derive(Debug, Clone)]
14pub struct GameDbClientBuilder {
15 token: String,
16 base_url: String,
17 client: Option<reqwest::Client>,
18}
19
20impl GameDbClientBuilder {
21 pub fn new(token: impl Into<String>) -> Self {
22 Self {
23 token: token.into(),
24 base_url: DEFAULT_BASE_URL.to_string(),
25 client: None,
26 }
27 }
28
29 pub fn base_url(mut self, url: impl Into<String>) -> Self {
31 self.base_url = url.into();
32 self
33 }
34
35 pub fn http_client(mut self, client: reqwest::Client) -> Self {
37 self.client = Some(client);
38 self
39 }
40
41 pub fn build(self) -> GameDbClient {
43 let transport = HttpTransport::new(self.base_url, self.token);
44 let transport = if let Some(client) = self.client {
45 transport.with_client(client)
46 } else {
47 transport
48 };
49
50 GameDbClient::from_transport(transport)
51 }
52}
53
54#[derive(Debug, Clone)]
56pub struct GameDbClient {
57 pub(crate) transport: HttpTransport,
58}
59
60impl GameDbClient {
61 pub fn new(token: impl Into<String>) -> Self {
63 Self::builder(token).build()
64 }
65
66 pub fn builder(token: impl Into<String>) -> GameDbClientBuilder {
68 GameDbClientBuilder::new(token)
69 }
70
71 pub fn from_transport(transport: HttpTransport) -> Self {
72 Self { transport }
73 }
74
75 pub async fn health(&self) -> Result<bool> {
76 self.transport.get("/health").await
77 }
78
79 pub async fn health_detailed(&self) -> Result<Value> {
80 self.transport.get("/health/detailed").await
81 }
82
83 pub async fn datasets(&self) -> Result<Value> {
84 self.transport.get("/api/v1/datasets").await
85 }
86
87 pub async fn create_dataset(&self, name: &str) -> Result<Value> {
88 self.transport
89 .post("/api/v1/datasets", &json!({ "name": name }))
90 .await
91 }
92
93 pub async fn delete_dataset(&self, id: &str) -> Result<Value> {
94 self.transport
95 .delete(&format!("/api/v1/datasets/{id}"))
96 .await
97 }
98
99 pub async fn delete_all_datasets(&self) -> Result<Value> {
100 self.transport.delete("/api/v1/datasets").await
101 }
102
103 pub async fn dataset_status(&self) -> Result<Value> {
104 self.transport.get("/api/v1/datasets/status").await
105 }
106
107 pub async fn dataset_graph(&self, id: &str) -> Result<Value> {
108 self.transport
109 .get(&format!("/api/v1/datasets/{id}/graph"))
110 .await
111 }
112
113 pub async fn dataset_data(&self, id: &str) -> Result<Value> {
114 self.transport
115 .get(&format!("/api/v1/datasets/{id}/data"))
116 .await
117 }
118
119 pub async fn delete_data(&self, dataset_id: &str, data_id: &str) -> Result<Value> {
120 self.transport
121 .delete(&format!("/api/v1/datasets/{dataset_id}/data/{data_id}"))
122 .await
123 }
124
125 pub async fn add_text(&self, dataset_name: &str, content: &str) -> Result<Value> {
126 let form = Form::new()
127 .text("datasetName", dataset_name.to_owned())
128 .part(
129 "data",
130 Part::text(content.to_owned())
131 .file_name("inline.txt")
132 .mime_str("text/plain")?,
133 );
134
135 self.transport.post_multipart("/api/v1/add", form).await
136 }
137
138 pub async fn add_file(&self, dataset_name: &str, file_path: &str) -> Result<Value> {
139 let bytes = tokio::fs::read(file_path).await?;
140 let file_name = Path::new(file_path)
141 .file_name()
142 .and_then(|name| name.to_str())
143 .ok_or_else(|| OriginError::config(format!("invalid file path: {file_path}")))?
144 .to_owned();
145
146 let form = Form::new()
147 .text("datasetName", dataset_name.to_owned())
148 .part("data", Part::bytes(bytes).file_name(file_name));
149
150 self.transport.post_multipart("/api/v1/add", form).await
151 }
152
153 pub async fn cognify(&self, options: &CognifyOptions) -> Result<Value> {
154 self.transport
155 .post("/api/v1/cognify", &json!(options))
156 .await
157 }
158
159 pub async fn search(&self, query: &str, options: Option<SearchOptions>) -> Result<Value> {
160 let options = options.unwrap_or_default();
161 let mut body = json!({
162 "query": query,
163 "search_type": options.search_type.unwrap_or(SearchType::GraphCompletion),
164 "top_k": options.top_k.unwrap_or(5),
165 });
166
167 if let Some(datasets) = options.datasets.filter(|datasets| !datasets.is_empty()) {
168 body["datasets"] = json!(datasets);
169 }
170
171 self.transport.post("/api/v1/search", &body).await
172 }
173
174 pub async fn search_history(&self) -> Result<Value> {
175 self.transport.get("/api/v1/search").await
176 }
177
178 pub async fn settings(&self) -> Result<Value> {
179 self.transport.get("/api/v1/settings").await
180 }
181
182 pub async fn save_settings(&self, settings: &Value) -> Result<Value> {
183 self.transport.post("/api/v1/settings", settings).await
184 }
185
186 pub async fn ontologies(&self) -> Result<Value> {
187 self.transport.get("/api/v1/ontologies").await
188 }
189}