markdown.rs 6.18 KB
use std::sync::Arc;
use crate::schema::*;
use crate::error::Error;
use crate::Pool;
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, 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"]
pub struct MarkdownJson {
    pub name: String,
    pub content: String,
    pub number_of_versions: i32,
    pub date_created: String,
    pub date_updated: String,
}

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


pub(crate) fn create_markdown( pool: Arc<Pool>
                             , item: MarkdownJson ) -> Result<Action, Error> {
    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>, Error>
{
    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, Error>
{
    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)?;

        let mut decoder = DeflateDecoder::new(Vec::new());
        for patch in result {
            decoder.write_all(patch.diff.as_ref()).unwrap();
            let decomp = decoder.reset(Vec::new()).unwrap();
            let decomp = Patch::from_str(
                std::str::from_utf8(decomp.as_ref()).unwrap()).unwrap();
            markdown.content = apply(&mut markdown.content, &decomp).unwrap();
        }
    };

    Ok(markdown)
}

pub(crate) fn delete_markdown( pool: Arc<Pool>
                             , ident: i32 ) -> Result<usize, Error>
{
    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, Error>
{
    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()).unwrap();
    let compressed = encoder.finish().unwrap();

    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(&item).execute(&db_connection)?;

        Ok(())
    }).unwrap();

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

    Ok(markdown)
}