1use eyre::{Result, bail};
2
3use atuin_client::record::sqlite_store::SqliteStore;
4use atuin_client::record::{encryption::PASETO_V4, store::Store};
5use atuin_common::record::{Host, HostId, Record, RecordId, RecordIdx};
6use record::ScriptRecord;
7use script::{SCRIPT_TAG, SCRIPT_VERSION, Script};
8
9use crate::database::Database;
10
11pub mod record;
12pub mod script;
13
14#[derive(Debug, Clone)]
15pub struct ScriptStore {
16 pub store: SqliteStore,
17 pub host_id: HostId,
18 pub encryption_key: [u8; 32],
19}
20
21impl ScriptStore {
22 pub fn new(store: SqliteStore, host_id: HostId, encryption_key: [u8; 32]) -> Self {
23 ScriptStore {
24 store,
25 host_id,
26 encryption_key,
27 }
28 }
29
30 async fn push_record(&self, record: ScriptRecord) -> Result<(RecordId, RecordIdx)> {
31 let bytes = record.serialize()?;
32 let idx = self
33 .store
34 .last(self.host_id, SCRIPT_TAG)
35 .await?
36 .map_or(0, |p| p.idx + 1);
37
38 let record = Record::builder()
39 .host(Host::new(self.host_id))
40 .version(SCRIPT_VERSION.to_string())
41 .tag(SCRIPT_TAG.to_string())
42 .idx(idx)
43 .data(bytes)
44 .build();
45
46 let id = record.id;
47
48 self.store
49 .push(&record.encrypt::<PASETO_V4>(&self.encryption_key))
50 .await?;
51
52 Ok((id, idx))
53 }
54
55 pub async fn create(&self, script: Script) -> Result<()> {
56 let record = ScriptRecord::Create(script);
57 self.push_record(record).await?;
58 Ok(())
59 }
60
61 pub async fn update(&self, script: Script) -> Result<()> {
62 let record = ScriptRecord::Update(script);
63 self.push_record(record).await?;
64 Ok(())
65 }
66
67 pub async fn delete(&self, script_id: uuid::Uuid) -> Result<()> {
68 let record = ScriptRecord::Delete(script_id);
69 self.push_record(record).await?;
70 Ok(())
71 }
72
73 pub async fn scripts(&self) -> Result<Vec<ScriptRecord>> {
74 let records = self.store.all_tagged(SCRIPT_TAG).await?;
75 let mut ret = Vec::with_capacity(records.len());
76
77 for record in records.into_iter() {
78 let script = match record.version.as_str() {
79 SCRIPT_VERSION => {
80 let decrypted = record.decrypt::<PASETO_V4>(&self.encryption_key)?;
81
82 ScriptRecord::deserialize(&decrypted.data, SCRIPT_VERSION)
83 }
84 version => bail!("unknown history version {version:?}"),
85 }?;
86
87 ret.push(script);
88 }
89
90 Ok(ret)
91 }
92
93 pub async fn build(&self, database: Database) -> Result<()> {
94 let scripts = self.scripts().await?;
96
97 for script in scripts {
98 match script {
99 ScriptRecord::Create(script) => {
100 database.save(&script).await?;
101 }
102 ScriptRecord::Update(script) => database.update(&script).await?,
103 ScriptRecord::Delete(id) => database.delete(&id.to_string()).await?,
104 }
105 }
106
107 Ok(())
108 }
109}