Skip to main content

hugo_server/
algolia.rs

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}