1use std::fs;
2use std::path::Path;
3use std::time::Duration;
4
5use anyhow::Result;
6use reqwest::header::{ACCEPT, HeaderMap, HeaderValue};
7use reqwest::{Client, redirect};
8use serde::{Deserialize, Serialize};
9use tokio::runtime::Handle;
10use tokio::task;
11
12#[must_use]
13pub struct AlgoliaClient {
14 client: Client,
15 application_id: String,
16}
17
18impl AlgoliaClient {
19 pub fn build<T, E>(application_id: T, api_key: E) -> Result<Self>
20 where
21 T: AsRef<str>,
22 E: AsRef<str>,
23 {
24 let mut headers = HeaderMap::new();
25 headers.insert(ACCEPT, HeaderValue::from_static("application/json"));
26 headers.insert(
27 "x-algolia-application-id",
28 HeaderValue::from_str(application_id.as_ref())?,
29 );
30 headers.insert(
31 "x-algolia-api-key",
32 HeaderValue::from_str(api_key.as_ref())?,
33 );
34
35 let client = Client::builder()
36 .default_headers(headers)
37 .redirect(redirect::Policy::none())
38 .connect_timeout(Duration::from_secs(10))
39 .timeout(Duration::from_secs(60))
40 .build()?;
41
42 Ok(Self {
43 client,
44 application_id: application_id.as_ref().to_string(),
45 })
46 }
47
48 pub fn delete_all_records<T>(&self, index_name: T) -> Result<()>
49 where
50 T: AsRef<str>,
51 {
52 task::block_in_place(move || {
53 Handle::current().block_on(async move {
54 self.client
55 .post(format!(
56 "https://{}.algolia.net/1/indexes/{}/clear",
57 self.application_id,
58 index_name.as_ref()
59 ))
60 .send()
61 .await?
62 .error_for_status()
63 })
64 })?;
65
66 Ok(())
67 }
68
69 pub fn add_records<T, E>(&self, index_name: T, path: E) -> Result<()>
70 where
71 T: AsRef<str>,
72 E: AsRef<Path>,
73 {
74 let json = fs::read_to_string(path)?;
75 let bodys: Vec<BatchRequestBody> = sonic_rs::from_str(&json)?;
76
77 let mut batch_data = BatchData {
78 requests: Vec::new(),
79 };
80 for body in bodys {
81 batch_data.requests.push(BatchRequest {
82 action: "addObject",
83 body,
84 });
85 }
86
87 task::block_in_place(move || {
88 Handle::current().block_on(async move {
89 self.client
90 .post(format!(
91 "https://{}.algolia.net/1/indexes/{}/batch",
92 self.application_id,
93 index_name.as_ref()
94 ))
95 .json(&batch_data)
96 .send()
97 .await?
98 .error_for_status()
99 })
100 })?;
101
102 Ok(())
103 }
104}
105
106#[must_use]
107#[derive(Serialize)]
108struct BatchData {
109 requests: Vec<BatchRequest>,
110}
111
112#[must_use]
113#[derive(Serialize)]
114struct BatchRequest {
115 action: &'static str,
116 body: BatchRequestBody,
117}
118
119#[must_use]
120#[derive(Serialize, Deserialize)]
121struct BatchRequestBody {
122 #[serde(rename = "objectID")]
123 object_id: String,
124 permalink: String,
125 title: String,
126 content: String,
127 date: String,
128 updated: String,
129}