|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106 |
- use crate::ipfs::ipfs::IpfsService;
- use crate::types::comment::{CommentError, CommentMetadata};
- use crate::types::ipfs::IpfsResult;
- use crate::utils::ipfs::{
- create_file_path,
- create_storage_directory,
- read_json_via_cat,
- retrieve_content_hash,
- save_json_file,
- DEFAULT_MAX_JSON_SIZE,
- list_directory_file_hashes,
- };
- use ipfs_api_backend_actix::IpfsClient;
-
- const STORAGE_DIR: &str = "/puffpastry/comments";
- const FILE_EXTENSION: &str = "json";
-
- pub struct CommentService {
- client: IpfsClient,
- }
-
- // Implement per-proposal subdirectory saving. The input is (proposal_cid, comments_batch)
- impl IpfsService<(String, Vec<CommentMetadata>)> for CommentService {
- type Err = CommentError;
-
- async fn save(&mut self, item: (String, Vec<CommentMetadata>)) -> IpfsResult<String, Self::Err> {
- let (proposal_cid, comments) = item;
- // Allow batch save within the proposal's subdirectory.
- if comments.is_empty() {
- return Err(CommentError::from(std::io::Error::new(
- std::io::ErrorKind::InvalidInput,
- "Failed to store comment to IPFS: empty comments batch",
- )));
- }
- let mut last_cid: Option<String> = None;
- for comment in comments {
- let res = self
- .store_comment_in_proposal_dir_and_publish(&proposal_cid, comment)
- .await;
- match res {
- Ok(cid) => last_cid = Some(cid),
- Err(e) => return Err(e),
- }
- }
- match last_cid {
- Some(cid) => Ok(cid),
- None => Err(CommentError::from(std::io::Error::new(
- std::io::ErrorKind::Other,
- "Failed to store comment to IPFS",
- ))),
- }
- }
-
- async fn read(&mut self, hash: String) -> IpfsResult<(String, Vec<CommentMetadata>), Self::Err> {
- // For reading, the caller should pass a directory hash (CID). We return only the comments vector here.
- // Since IpfsService requires returning the same T, include an empty proposal id (unknown in read-by-hash).
- // Alternatively, callers should not use the proposal id from this return value.
- let comments = self.read_all_comments_in_dir(&hash).await?;
- Ok((String::new(), comments))
- }
- }
-
- impl CommentService {
- pub fn new(client: IpfsClient) -> Self {
- Self { client }
- }
-
- // Writes a single comment JSON file into the MFS comments subdirectory for the proposal,
- // then publishes the subdirectory snapshot (by retrieving the directory CID). Returns the comment file CID.
- async fn store_comment_in_proposal_dir_and_publish(
- &self,
- proposal_cid: &str,
- comment: CommentMetadata,
- ) -> IpfsResult<String, CommentError> {
- // Ensure the per-proposal storage subdirectory exists
- let proposal_dir = format!("{}/{}", STORAGE_DIR, proposal_cid);
- create_storage_directory::<CommentError>(&self.client, &proposal_dir).await?;
-
- // Create a unique file path within the proposal subdirectory and save the JSON content
- let file_path = create_file_path(&proposal_dir, FILE_EXTENSION);
- save_json_file::<CommentMetadata, CommentError>(&self.client, &file_path, &comment).await?;
-
- // Retrieve the file's CID to return to the caller
- let file_cid = retrieve_content_hash::<CommentError>(&self.client, &file_path).await?;
-
- // Publish a new snapshot of the proposal's comments subdirectory by retrieving its CID
- let _dir_cid = retrieve_content_hash::<CommentError>(&self.client, &proposal_dir).await?;
-
- Ok(file_cid)
- }
-
- async fn read_comment_file(&self, hash: &str) -> IpfsResult<CommentMetadata, CommentError> {
- read_json_via_cat::<CommentMetadata, CommentError>(&self.client, hash, DEFAULT_MAX_JSON_SIZE).await
- }
-
- // Read all comment files within a directory identified by the given hash (directory CID)
- async fn read_all_comments_in_dir(&self, dir_hash: &str) -> IpfsResult<Vec<CommentMetadata>, CommentError> {
- let file_hashes = list_directory_file_hashes::<CommentError>(&self.client, dir_hash).await?;
- let mut comments: Vec<CommentMetadata> = Vec::with_capacity(file_hashes.len());
- for fh in file_hashes {
- let comment = self.read_comment_file(&fh).await?;
- comments.push(comment);
- }
- Ok(comments)
- }
- }
|