aboutsummaryrefslogtreecommitdiff
path: root/src/report.rs
diff options
context:
space:
mode:
authorMartin Fischer <martin@push-f.com>2026-10-25 09:08:01 +0100
committerMartin Fischer <martin@push-f.com>2026-10-25 19:10:47 +0100
commit41212a7a15198e53d205252a02069a7dd6ddadeb (patch)
tree38a24eebf956b2453675ac1cefec925faf09de02 /src/report.rs
initial commit
Diffstat (limited to 'src/report.rs')
-rw-r--r--src/report.rs57
1 files changed, 57 insertions, 0 deletions
diff --git a/src/report.rs b/src/report.rs
new file mode 100644
index 0000000..d776469
--- /dev/null
+++ b/src/report.rs
@@ -0,0 +1,57 @@
+use std::{fs, io, path::Path};
+
+use atrium_api::app::bsky::richtext::facet::Main as Facet;
+use serde::{Serialize, de::DeserializeOwned};
+
+pub(crate) struct Post {
+ pub text: String,
+ pub facets: Vec<Facet>,
+ /// Images to upload before creating the post record.
+ pub images: Vec<PostImage>,
+}
+
+pub(crate) struct PostImage {
+ pub data: Vec<u8>,
+ pub alt: String,
+}
+
+pub(crate) trait Report: Sized {
+ type State: Default + DeserializeOwned + Serialize;
+
+ const STATE_FILE: &'static str;
+
+ async fn generate(state: Self::State) -> Result<(Vec<Post>, Self::State), String>;
+
+ async fn report() -> Result<(Vec<Post>, impl FnOnce() -> Result<(), String>), String> {
+ let state_path = Path::new(Self::STATE_FILE);
+ let state = load_state(state_path)?;
+ let (posts, new_state) = Self::generate(state).await?;
+ Ok((posts, move || save_state(state_path, &new_state)))
+ }
+}
+
+fn load_state<T>(path: &Path) -> Result<T, String>
+where
+ T: Default + DeserializeOwned,
+{
+ match fs::read(path) {
+ Ok(data) => serde_json::from_slice(&data)
+ .map_err(|error| format!("failed to parse {}: {error}", path.display())),
+ Err(error) if error.kind() == io::ErrorKind::NotFound => Ok(T::default()),
+ Err(error) => Err(format!("failed to read {}: {error}", path.display())),
+ }
+}
+
+fn save_state<T>(path: &Path, state: &T) -> Result<(), String>
+where
+ T: Serialize,
+{
+ let data = serde_json::to_vec_pretty(state)
+ .map_err(|error| format!("failed to serialize {}: {error}", path.display()))?;
+ let temporary_path = path.with_extension("json.tmp");
+
+ fs::write(&temporary_path, data)
+ .map_err(|error| format!("failed to write {}: {error}", temporary_path.display()))?;
+ fs::rename(&temporary_path, path)
+ .map_err(|error| format!("failed to replace {}: {error}", path.display()))
+}