The backend of a gist server written in Rust
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

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. }