The backend of a gist server written in Rust
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

snippet.rs 3.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. use chrono::{Local, NaiveDateTime};
  2. use crypto::digest::Digest;
  3. use crypto::sha2::Sha256;
  4. use diesel::pg::PgConnection as PGC;
  5. use diesel::prelude::*;
  6. use syntect::highlighting::ThemeSet;
  7. use syntect::html::highlighted_html_for_string;
  8. use syntect::parsing::SyntaxSet;
  9. use crate::schema::snippets;
  10. use crate::schema::snippets::columns::uuid;
  11. #[derive(Queryable, Serialize, Deserialize)]
  12. pub struct Snippet {
  13. pub id: i32,
  14. pub title: String,
  15. pub body: String,
  16. pub created_at: Option<NaiveDateTime>,
  17. pub filetype: Option<String>,
  18. pub formatted_body: String,
  19. pub uuid: String,
  20. }
  21. #[derive(Serialize, Deserialize)]
  22. pub struct ApiSnippet {
  23. pub title: String,
  24. pub body: String,
  25. pub filetype: Option<String>,
  26. }
  27. #[derive(Insertable, AsChangeset)]
  28. #[table_name = "snippets"]
  29. pub struct InsertableSnippet {
  30. pub title: String,
  31. pub body: String,
  32. pub filetype: Option<String>,
  33. pub formatted_body: String,
  34. pub uuid: String,
  35. }
  36. fn format_snippet(filetype: Option<String>, body: String) -> String {
  37. filetype.map_or(
  38. format!("<pre class='plaintext'>\n{}\n</pre>", body),
  39. |filetype| {
  40. let ps = SyntaxSet::load_defaults_newlines();
  41. let ts = ThemeSet::load_defaults();
  42. ps.find_syntax_by_extension(&filetype).map_or(
  43. format!("<pre class='plaintext'>\n{}\n</pre>", body),
  44. |syntax| {
  45. let content = htmlescape::decode_html(&body).expect("Invalid HTML");
  46. highlighted_html_for_string(
  47. &content,
  48. &ps,
  49. &syntax,
  50. &ts.themes["Solarized (light)"],
  51. )
  52. },
  53. )
  54. },
  55. )
  56. }
  57. fn generate_uuid(snippet: &ApiSnippet) -> String {
  58. let current_timestamp = Local::now().to_string();
  59. let mut hasher = Sha256::new();
  60. hasher.input_str(&format!(
  61. "{}{}{}",
  62. snippet.title, snippet.body, current_timestamp
  63. ));
  64. hasher.result_str().to_string()
  65. }
  66. pub fn get(connection: &PGC, snippet_uuid: &str) -> QueryResult<Snippet> {
  67. snippets::table
  68. .filter(uuid.eq(snippet_uuid))
  69. .first(connection)
  70. }
  71. pub fn insert(snippet: ApiSnippet, connection: &PGC) -> QueryResult<Snippet> {
  72. let body = htmlescape::encode_minimal(&snippet.body.clone());
  73. let formatted_snippet = InsertableSnippet {
  74. filetype: snippet.filetype.clone(),
  75. title: snippet.title.clone(),
  76. body: body.clone(),
  77. uuid: generate_uuid(&snippet),
  78. formatted_body: format_snippet(snippet.filetype, body),
  79. };
  80. diesel::insert_into(snippets::table)
  81. .values(formatted_snippet)
  82. .get_result(connection)
  83. }
  84. #[cfg(test)]
  85. mod tests {
  86. use super::*;
  87. #[test]
  88. fn identical_snippets_have_different_uuids() {
  89. let snippet = ApiSnippet {
  90. title: String::from("Hello world"),
  91. body: String::from("Hello world"),
  92. filetype: None,
  93. };
  94. let uuid1 = generate_uuid(&snippet);
  95. let uuid2 = generate_uuid(&snippet);
  96. assert!(uuid1 != uuid2);
  97. }
  98. }