use crate::db::db::get_connection; use crate::ipfs::ipfs::IpfsService; use crate::schema::proposals; use crate::types::proposal::{Proposal, ProposalError, ProposalFile}; use crate::utils::content::extract_summary; use crate::utils::ipfs::{upload_json_and_get_hash, read_json_via_cat as util_read_json_via_cat, DEFAULT_MAX_JSON_SIZE}; use diesel::{RunQueryDsl, QueryDsl, ExpressionMethods}; use ipfs_api_backend_actix::IpfsClient; use crate::types::ipfs::IpfsResult; const STORAGE_DIR: &str = "/puffpastry/proposals"; const FILE_EXTENSION: &str = "json"; pub struct ProposalService { client: IpfsClient, } impl IpfsService for ProposalService { type Err = ProposalError; async fn save(&mut self, proposal: ProposalFile) -> IpfsResult { self.store_proposal_to_ipfs(proposal).await } async fn read(&mut self, hash: String) -> IpfsResult { self.read_proposal_file(hash).await } } impl ProposalService { pub fn new(client: IpfsClient) -> Self { Self { client } } async fn store_proposal_to_ipfs(&self, proposal: ProposalFile) -> IpfsResult { let proposal_record = Proposal::new( proposal.name.clone(), Some(extract_summary(&proposal.content)), proposal.creator.clone(), ); let hash = self.upload_to_ipfs(&proposal).await?; self.save_to_database(proposal_record.with_cid(hash.clone()).mark_as_current()).await?; // After saving the new proposal, set is_current = false for any proposal // that has previous_cid equal to this new hash { use crate::schema::proposals::dsl::{proposals as proposals_table, previous_cid as previous_cid_col, is_current as is_current_col}; let mut conn = get_connection() .map_err(|e| ProposalError::DatabaseError(e))?; // Ignore the count result; if no rows match, that's fine let _ = diesel::update(proposals_table.filter(previous_cid_col.eq(hash.clone()))) .set(is_current_col.eq(false)) .execute(&mut conn) .map_err(|e| ProposalError::DatabaseError(Box::new(e)))?; } Ok(hash) } async fn upload_to_ipfs(&self, data: &ProposalFile) -> IpfsResult { upload_json_and_get_hash::(&self.client, STORAGE_DIR, FILE_EXTENSION, data).await } async fn save_to_database(&self, proposal: Proposal) -> IpfsResult<(), ProposalError> { let mut conn = get_connection() .map_err(|e| ProposalError::DatabaseError(e))?; diesel::insert_into(proposals::table) .values(&proposal) .execute(&mut conn) .map_err(|e| ProposalError::DatabaseError(Box::new(e)))?; Ok(()) } async fn read_proposal_file(&self, hash: String) -> IpfsResult { util_read_json_via_cat::(&self.client, &hash, DEFAULT_MAX_JSON_SIZE).await } }