markdown.rs 7.68 KB
use std::sync::Arc;
use crate::schema::*;
use crate::error::*;
use crate::Pool;
use artshop_common::types::MarkdownDiffJson;
use artshop_common::types::MarkdownJson;
use diesel::prelude::*;
use diesel::{
    dsl::{delete, insert_into, update},
    RunQueryDsl
};
use serde::{Deserialize, Serialize};
use std::io::Write;
use diffy::{apply, create_patch, Patch};
use flate2::Compression;
use flate2::write::{DeflateEncoder, DeflateDecoder};


#[derive(Debug, Clone, Serialize, Deserialize, Queryable, Identifiable)]
pub struct Markdown {
    pub id: i32,
    pub name: String,
    pub content: String,
    pub number_of_versions: i32,
    pub date_created: String,
    pub date_updated: String,
}

#[derive(Debug, Serialize, Deserialize, Queryable, Identifiable)]
#[table_name = "markdown_diffs"]
#[primary_key(markdown_id, diff_id)]
pub struct MarkdownDiff {
    pub markdown_id: i32,
    pub diff_id: i32,
    pub diff: Vec<u8>,
    pub date_created: String,
}

#[derive(Debug, Insertable)]
#[table_name = "markdowns"]
pub struct MarkdownNew<'a> {
    pub name: &'a str,
    pub content: &'a str,
    pub number_of_versions: i32,
    pub date_created: &'a str,
    pub date_updated: &'a str,
}

#[derive(Debug, Insertable)]
#[table_name = "markdown_diffs"]
pub struct MarkdownDiffNew<'a> {
    pub markdown_id: i32,
    pub diff_id: i32,
    pub diff: &'a [u8],
    pub date_created: &'a str,
}

#[derive(Debug, Serialize, Deserialize, AsChangeset)]
#[table_name="markdowns"]
struct MarkdownChange<'a> {
    pub name: &'a str,
    pub content: &'a str,
    pub number_of_versions: i32,
    pub date_created: &'a str,
    pub date_updated: &'a str,
}

impl<'a> From<&'a MarkdownJson> for MarkdownChange<'a> {
    fn from(md: &'a MarkdownJson) -> Self {
        Self { name: md.name.as_str()
             , content: md.content.as_str()
             , number_of_versions: md.number_of_versions
             , date_created: md.date_created.as_str()
             , date_updated: md.date_updated.as_str()
        }
    }
}

impl From<&MarkdownDiff> for MarkdownDiffJson {
    fn from(md_diff: &MarkdownDiff) -> Self {
        Self { id: md_diff.diff_id
             , date_created: md_diff.date_created.to_owned()
        }
    }
}

impl MarkdownDiff {
    pub(crate) fn get_diff_as_string(&self) -> Result<String> {
        let mut decoder = DeflateDecoder::new(Vec::new());

        decoder.write_all(self.diff.as_ref())?;
        let decomp = decoder.finish()?;

        Ok(String::from(std::str::from_utf8(decomp.as_ref())?))
    }
}

pub(crate) enum _Action {
    Created(Markdown),
    Found(Markdown),
}


pub(crate) fn _create_markdown( pool: Arc<Pool>
                             , item: MarkdownJson ) -> Result<_Action> {
    use crate::schema::markdowns::dsl::*;
    let db_connection = pool.get()?;

    match markdowns . filter(name.eq(&item.name))
                    . first::<Markdown>(&db_connection)
    {
        Ok(result) => Ok(_Action::Found(result)),
        Err(_)     => {
            let now = chrono::Local::now().naive_local();
            let new_markdown = MarkdownNew {
                name:               &item.name,
                content:            &item.content,
                number_of_versions: item.number_of_versions,
                date_created:       &format!("{}", now),
                date_updated:       &format!("{}", now),
            };

            Ok(_Action::Created(db_connection.transaction(|| {
                insert_into(markdowns) . values(&new_markdown)
                                       . execute(&db_connection)?;

                markdowns . order(id.desc())
                          . first::<Markdown>(&db_connection)
            })?))
        }
    }
}

pub(crate) fn get_markdowns(pool: Arc<Pool>) -> Result<Vec<Markdown>>
{
    use crate::schema::markdowns::dsl::*;
    let db_connection = pool.get()?;
    Ok(markdowns.load::<Markdown>(&db_connection)?)
}

pub(crate) fn get_markdown( pool:  Arc<Pool>
                          , ident: &str
                          , patch: Option<i32> ) -> Result<Markdown>
{
    use crate::schema::markdowns::dsl::*;
    use crate::schema::markdown_diffs::dsl::*;

    let db_connection = pool.get()?;

    let mut markdown = markdowns
                     . filter(name.eq(ident))
                     . first::<Markdown>(&db_connection)?;

    if let Some(patch) = patch {
        let result = markdown_diffs
                   . filter(markdown_id.eq(markdown.id))
                   . filter(diff_id.ge(patch))
                   . order(diff_id.desc())
                   . load::<MarkdownDiff>(&db_connection)?;

        for patch in result {
            let patch_data = patch.get_diff_as_string()?;
            let decomp = Patch::from_str(&patch_data)?;

            markdown.content = apply(&markdown.content, &decomp)?;
            markdown.date_updated = patch.date_created;
        }
    };

    Ok(markdown)
}

pub(crate) fn get_patches( pool: Arc<Pool>
                         , ident: &str ) -> Result<Vec<MarkdownDiffJson>> {
    use crate::schema::markdowns::dsl::*;
    use crate::schema::markdown_diffs::dsl::*;

    let db_connection = pool.get()?;

    let markdown = markdowns
                 . filter(name.eq(ident))
                 . first::<Markdown>(&db_connection)?;

    Ok ( markdown_diffs . filter(markdown_id.eq(markdown.id))
                        . order(diff_id.desc())
                        . load::<MarkdownDiff>(&db_connection)?
                        . iter()
                        . map(|d| MarkdownDiffJson::from(d))
                        . collect() )
}

pub(crate) fn _delete_markdown( pool: Arc<Pool>
                             , ident: i32 ) -> Result<usize>
{
    use crate::schema::markdowns::dsl::*;
    let db_connection = pool.get()?;
    Ok(delete(markdowns.find(ident)).execute(&db_connection)?)
}

pub(crate) fn update_markdown( pool: Arc<Pool>
                             , ident: String
                             , mut item: MarkdownJson ) -> Result<Markdown>
{
    use crate::schema::markdowns::dsl::*;
    use crate::schema::markdown_diffs::dsl::*;
    let db_connection = pool.get()?;
    let mut markdown = markdowns
                     . filter(name.eq(ident))
                     . first::<Markdown>(&db_connection)?;

    let patch = format!( "{}", create_patch( item.content.as_str()
                                           , markdown.content.as_str() ));
    let mut encoder = DeflateEncoder::new(Vec::new(), Compression::best());
    encoder.write_all(patch.as_bytes())?;
    let compressed = encoder.finish()?;

    let last_diff = match markdown_diffs . filter(markdown_id.eq(markdown.id))
                                         . order(diff_id.desc())
                                         . first::<MarkdownDiff>(&db_connection)
    {
        Ok(result) => (result.diff_id, Some(result)),
        Err(_)     => (0, None),
    };

    let now = chrono::Local::now().naive_local();
    let new_markdown_diff = MarkdownDiffNew {
        markdown_id:        markdown.id,
        diff_id:            last_diff.0 + 1,
        diff:               compressed.as_ref(),
        date_created:       markdown.date_updated.as_str(),
    };

    item.date_updated       = format!("{}", now);
    item.number_of_versions = item.number_of_versions + 1;

    db_connection.transaction::<_, Error, _>(|| {
        insert_into(markdown_diffs) . values(&new_markdown_diff)
                                    . execute(&db_connection)?;

        update(&markdown).set(MarkdownChange::from(&item)).execute(&db_connection)?;

        Ok(())
    })?;

    markdown.name               = item.name;
    markdown.content            = item.content;
    markdown.number_of_versions = item.number_of_versions;
    markdown.date_updated       = item.date_updated;

    Ok(markdown)
}