您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

7 个月前
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. use crate::ipfs::ipfs::IpfsService;
  2. use crate::types::comment::{CommentError, CommentMetadata};
  3. use crate::types::ipfs::IpfsResult;
  4. use crate::utils::ipfs::{
  5. create_file_path,
  6. create_storage_directory,
  7. read_json_via_cat,
  8. retrieve_content_hash,
  9. save_json_file,
  10. DEFAULT_MAX_JSON_SIZE,
  11. list_directory_file_hashes,
  12. };
  13. use ipfs_api_backend_actix::IpfsClient;
  14. const STORAGE_DIR: &str = "/puffpastry/comments";
  15. const FILE_EXTENSION: &str = "json";
  16. pub struct CommentService {
  17. client: IpfsClient,
  18. }
  19. // Implement per-proposal subdirectory saving. The input is (proposal_cid, comments_batch)
  20. impl IpfsService<(String, Vec<CommentMetadata>)> for CommentService {
  21. type Err = CommentError;
  22. async fn save(&mut self, item: (String, Vec<CommentMetadata>)) -> IpfsResult<String, Self::Err> {
  23. let (proposal_cid, comments) = item;
  24. // Allow batch save within the proposal's subdirectory.
  25. if comments.is_empty() {
  26. return Err(CommentError::from(std::io::Error::new(
  27. std::io::ErrorKind::InvalidInput,
  28. "Failed to store comment to IPFS: empty comments batch",
  29. )));
  30. }
  31. let mut last_cid: Option<String> = None;
  32. for comment in comments {
  33. let res = self
  34. .store_comment_in_proposal_dir_and_publish(&proposal_cid, comment)
  35. .await;
  36. match res {
  37. Ok(cid) => last_cid = Some(cid),
  38. Err(e) => return Err(e),
  39. }
  40. }
  41. match last_cid {
  42. Some(cid) => Ok(cid),
  43. None => Err(CommentError::from(std::io::Error::new(
  44. std::io::ErrorKind::Other,
  45. "Failed to store comment to IPFS",
  46. ))),
  47. }
  48. }
  49. async fn read(&mut self, hash: String) -> IpfsResult<(String, Vec<CommentMetadata>), Self::Err> {
  50. // For reading, the caller should pass a directory hash (CID). We return only the comments vector here.
  51. // Since IpfsService requires returning the same T, include an empty proposal id (unknown in read-by-hash).
  52. // Alternatively, callers should not use the proposal id from this return value.
  53. let comments = self.read_all_comments_in_dir(&hash).await?;
  54. Ok((String::new(), comments))
  55. }
  56. }
  57. impl CommentService {
  58. pub fn new(client: IpfsClient) -> Self {
  59. Self { client }
  60. }
  61. // Writes a single comment JSON file into the MFS comments subdirectory for the proposal,
  62. // then publishes the subdirectory snapshot (by retrieving the directory CID). Returns the comment file CID.
  63. async fn store_comment_in_proposal_dir_and_publish(
  64. &self,
  65. proposal_cid: &str,
  66. comment: CommentMetadata,
  67. ) -> IpfsResult<String, CommentError> {
  68. // Ensure the per-proposal storage subdirectory exists
  69. let proposal_dir = format!("{}/{}", STORAGE_DIR, proposal_cid);
  70. create_storage_directory::<CommentError>(&self.client, &proposal_dir).await?;
  71. // Create a unique file path within the proposal subdirectory and save the JSON content
  72. let file_path = create_file_path(&proposal_dir, FILE_EXTENSION);
  73. save_json_file::<CommentMetadata, CommentError>(&self.client, &file_path, &comment).await?;
  74. // Retrieve the file's CID to return to the caller
  75. let file_cid = retrieve_content_hash::<CommentError>(&self.client, &file_path).await?;
  76. // Publish a new snapshot of the proposal's comments subdirectory by retrieving its CID
  77. let _dir_cid = retrieve_content_hash::<CommentError>(&self.client, &proposal_dir).await?;
  78. Ok(file_cid)
  79. }
  80. async fn read_comment_file(&self, hash: &str) -> IpfsResult<CommentMetadata, CommentError> {
  81. read_json_via_cat::<CommentMetadata, CommentError>(&self.client, hash, DEFAULT_MAX_JSON_SIZE).await
  82. }
  83. // Read all comment files within a directory identified by the given hash (directory CID)
  84. async fn read_all_comments_in_dir(&self, dir_hash: &str) -> IpfsResult<Vec<CommentMetadata>, CommentError> {
  85. let file_hashes = list_directory_file_hashes::<CommentError>(&self.client, dir_hash).await?;
  86. let mut comments: Vec<CommentMetadata> = Vec::with_capacity(file_hashes.len());
  87. for fh in file_hashes {
  88. let comment = self.read_comment_file(&fh).await?;
  89. comments.push(comment);
  90. }
  91. Ok(comments)
  92. }
  93. }