Commit 53332e6bb0b000762b41e5c3207df7169eeb4f26

Authored by Georg Hopp
1 parent ebe8266b

first time database data in mogwai component

1 [workspace] 1 [workspace]
2 -members = [ "ui", "server" ] 2 +members = [ "server", "ui" ]
3 3
4 [profile.release] 4 [profile.release]
5 lto = true 5 lto = true
No preview for this file type
@@ -2,4 +2,4 @@ @@ -2,4 +2,4 @@
2 # see diesel.rs/guides/configuring-diesel-cli 2 # see diesel.rs/guides/configuring-diesel-cli
3 3
4 [print_schema] 4 [print_schema]
5 -file = "src/schema.rs" 5 +file = "server/src/schema.rs"
@@ -15,3 +15,6 @@ @@ -15,3 +15,6 @@
15 ## Free Artwork 15 ## Free Artwork
16 - [Designlooter](https://designlooter.com/ 'Designlooter') 16 - [Designlooter](https://designlooter.com/ 'Designlooter')
17 - [SVGRepo](https://www.svgrepo.com/ 'SVGRepo') 17 - [SVGRepo](https://www.svgrepo.com/ 'SVGRepo')
  18 +
  19 +## Send and async and using non Send types...
  20 +- [Rust Send Story](https://procmarco.netlify.app/blog/2021-05-04-a-story-about-async-rust-and-using-send-types/ 'Rust Send story')
  1 +-- This file should undo anything in `up.sql`
  2 +DROP INDEX "markdown_diffs_id";
  3 +DROP TABLE "markdown_diffs";
  4 +DROP TABLE "markdowns";
  1 +CREATE TABLE "markdowns" (
  2 + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
  3 + name VARCHAR(256) NOT NULL,
  4 + content TEXT NOT NULL,
  5 + number_of_versions INTEGER NOT NULL DEFAULT (1),
  6 + date_created TEXT NOT NULL,
  7 + date_updated TEXT NOT NULL
  8 +);
  9 +
  10 +-- This holds compressed reverse patches to markdown.content.
  11 +-- The markdown.content field always holds the latest version.
  12 +-- This way we can always restore every version we had in the past.
  13 +-- The date_created here should be set to the value of
  14 +-- markdown.date_updated when the patch was created. This diff_id
  15 +-- is always current last max diff_id for given markdown_id plus 1.
  16 +CREATE TABLE "markdown_diffs" (
  17 + markdown_id INTEGER NOT NULL,
  18 + diff_id INTEGER NOT NULL,
  19 + diff BLOB NOT NULL,
  20 + date_created TEXT NOT NULL,
  21 + PRIMARY KEY (markdown_id, diff_id)
  22 +);
  23 +
  24 +INSERT INTO
  25 + "markdowns"(name, content, date_created, date_updated)
  26 +VALUES
  27 + ( "md-example"
  28 + , "# Ein sehr schöner Titel
  29 +
  30 +## Ein sinnloser Text
  31 +
  32 +Hier kommt ganz viel Text der irgendwie auch was machen soll, aber Zeilen
  33 +sollen auch im <pre> Eingabefeld automatisch umbrechen.
  34 +
  35 +Ein neuner Paragraph beginnt nach einer Leerzeile.
  36 +Ein Umbruch entsteht wie gewohnt durch 2 spaces am Ende einer
  37 +Zeile.
  38 +
  39 +## Fußnoten
  40 +
  41 +Vllt. kann man sogar so was wie Fussnoten[^1] in den Markdown Text
  42 +einbinden... diese kann man dann irgendwo einbauen...
  43 +
  44 +---
  45 +
  46 +[^1]: Zum Beispiel so...
  47 +
  48 +[^2]: Oder so...
  49 +
  50 +## inline html ist im Moment auch ok.
  51 +
  52 +<pre>Lustigerweise geht auch inline html</pre>
  53 +
  54 +## Listen for fun
  55 +
  56 +- ein Liste
  57 + - mehr Liste
  58 + - diesmal als Subliste.
  59 +- und was auch immer...
  60 + 1. und nun Verschachtelt.
  61 + 1. Numeriert.
  62 + 2. huhuhu
  63 + 3. wie bitte.
  64 + 2. juhu
  65 +- noch mehr Liste
  66 +
  67 +## Preformated Text
  68 +
  69 +```Hier kommt der code```
  70 +
  71 +Und hier der Paragraph mit `inline code` der auch sehr schön aussehen kann.
  72 +
  73 +## Hervorhebungen
  74 +
  75 +Man kann Text auch sehr schön formatieren. So ist es z.B. möglich
  76 +*Worte kursiv zu stellen* oder man kann **sie auch fett schreiben**.
  77 +Als spezielles feature kann der von mir verwendete Parser auch
  78 +~~Texte durchstreichen~~.
  79 +
  80 +Nur wenn man Text <u>unterstreichen</u> will muss man auf inline html
  81 +zurückgreifen.
  82 +
  83 +## Blockquotes und horizontale Linie
  84 +
  85 +> Dies sollte jetzt als quote erkennbar sein.
  86 +>
  87 +>> Auch diese sind schachtelbar
  88 +>
  89 +> Und weiter gehts.
  90 +
  91 +---
  92 +
  93 +> Aber dies ist ein neuer quote.
  94 +
  95 +## Links
  96 +
  97 +Ein link kann inline geschrieben werden, so wie diese zu
  98 +[Heise.de](https://heise.de/ 'Heise.de') oder als Referenz am Ende des Textes
  99 +wie diese nach [Telepolis][lnk1].
  100 +
  101 +## Bilder koennte man auch einbinden.
  102 +
  103 +Wie Links lassen sich auch Bilder wie mein
  104 +![Gravatar](https://www.gravatar.com/avatar/fd016c954ec4ed3a4315eeed6c8b97b8)
  105 +in den Text ein.
  106 +
  107 +Im Fließtext sieht das allerdings ein bisschen dumm aus es sei denn man hat
  108 +entsprechend angepasste styles. Besser scheint mir daher Bilder nur zwischen
  109 +Paragraphen zu plazieren.
  110 +
  111 +![Gravatar](https://www.gravatar.com/avatar/fd016c954ec4ed3a4315eeed6c8b97b8)
  112 +
  113 +Etwas so wie hier.
  114 +
  115 +## Tabellen sollten auch gehen...
  116 +
  117 +Die folgenden Beispiele kommen von [markdown.land][lnk2]:
  118 +
  119 +| Item | Price | # In stock |
  120 +|--------------|-----------|------------|
  121 +| Juicy Apples | 1.99 | *8* |
  122 +| Bananas | **1.89** | 5234 |
  123 +
  124 +Man braucht sie nicht schön zu formatieren.
  125 +
  126 +Item | Price | # In stock
  127 +---|---|---
  128 +Juicy Apples | 1.99 | 739
  129 +Bananas | 1.89 | 6
  130 +
  131 +und die Spaltenausrichtung kann man auch einstellen:
  132 +
  133 +| Item | Price | # In stock |
  134 +|--------------|:-----:|-----------:|
  135 +| Juicy Apples | 1.99 | 739 |
  136 +| Bananas | 1.8900 | 6 |
  137 +
  138 +[lnk1]: https://heise.de/tp/ 'Telepolis'
  139 +[lnk2]: https://markdown.land/markdown-table 'markdown.land'"
  140 + , "Today"
  141 + , "Today" );
@@ -36,3 +36,5 @@ serde_json = "1.0" @@ -36,3 +36,5 @@ serde_json = "1.0"
36 anyhow = "1.0" 36 anyhow = "1.0"
37 chrono = "0.4.15" 37 chrono = "0.4.15"
38 listenfd = "0.3" 38 listenfd = "0.3"
  39 +diffy = "0.2"
  40 +flate2 = "^1.0"
@@ -6,15 +6,71 @@ mod models; @@ -6,15 +6,71 @@ mod models;
6 mod routes; 6 mod routes;
7 mod schema; 7 mod schema;
8 8
  9 +use std::io::Write;
  10 +use crate::routes::markdown::get_markdowns;
  11 +use crate::routes::other::*;
  12 +use crate::routes::user::*;
  13 +
9 use actix_web::{guard, web, App, HttpResponse, HttpServer}; 14 use actix_web::{guard, web, App, HttpResponse, HttpServer};
10 use diesel::r2d2::{self, ConnectionManager}; 15 use diesel::r2d2::{self, ConnectionManager};
11 use diesel::SqliteConnection; 16 use diesel::SqliteConnection;
  17 +use diffy::create_patch;
  18 +use flate2::Compression;
  19 +use flate2::write::{DeflateEncoder, DeflateDecoder};
12 use listenfd::ListenFd; 20 use listenfd::ListenFd;
13 21
14 pub(crate) type Pool = r2d2::Pool<ConnectionManager<SqliteConnection>>; 22 pub(crate) type Pool = r2d2::Pool<ConnectionManager<SqliteConnection>>;
15 23
  24 +const ORIGINAL :&str = r"This is a long Multiline text to
  25 +see if we can create a small patch set from
  26 +a longer text.
  27 +
  28 +This paragraph will be kept without modification. The reason I
  29 +try this is to see if the diff becomes smaller than storing a
  30 +compressed new version of this text...
  31 +
  32 +So this whole paragraph is obsolete. It will be removed in the
  33 +modified text.
  34 +
  35 +There is probably a threshold up to what amount of text is needed
  36 +to make the diff smaller.
  37 +";
  38 +
  39 +const MODIFIED :&str = r"This is a long multiline text to
  40 +see if we can create a small patch set from
  41 +a longer text.
  42 +
  43 +This paragraph will be kept without modification. The reason I
  44 +try this is to see if the diff becomes smaller than storing a
  45 +compressed new version of this text...
  46 +
  47 +This is the replacement for the previous paragraph.
  48 +
  49 +There is probably a threshold up to what amount of text is needed
  50 +to make the diff smaller.
  51 +";
  52 +
16 #[actix_rt::main] 53 #[actix_rt::main]
17 async fn main() -> std::io::Result<()> { 54 async fn main() -> std::io::Result<()> {
  55 + /* just some playing with diffy */
  56 + let patch = format!("{}", create_patch(ORIGINAL, MODIFIED));
  57 + println!("{} - {}", patch.len(), patch);
  58 +
  59 + let mut encoder = DeflateEncoder::new(Vec::new(), Compression::best());
  60 + encoder.write_all(patch.as_bytes()).unwrap();
  61 + let compressed = encoder.finish().unwrap();
  62 + println!("{} - {:?}", compressed.len(), compressed);
  63 +
  64 + let mut decoder = DeflateDecoder::new(Vec::new());
  65 + decoder.write_all(compressed.as_ref()).unwrap();
  66 + let decompressed = decoder.finish().unwrap();
  67 + let decompressed = match std::str::from_utf8(decompressed.as_ref()) {
  68 + Ok(v) => v,
  69 + Err(e) => panic!("Invalid UTF-8 sequence: {}", e),
  70 + };
  71 + println!("{} - {}", decompressed.len(), decompressed);
  72 + /* ======= */
  73 +
18 let mut listenfd = ListenFd::from_env(); 74 let mut listenfd = ListenFd::from_env();
19 75
20 dotenv::dotenv().ok(); 76 dotenv::dotenv().ok();
@@ -28,24 +84,33 @@ async fn main() -> std::io::Result<()> { @@ -28,24 +84,33 @@ async fn main() -> std::io::Result<()> {
28 App::new() . data(database_pool.clone()) 84 App::new() . data(database_pool.clone())
29 . service(actix_files::Files::new("/static", "./static")) 85 . service(actix_files::Files::new("/static", "./static"))
30 . service( web::scope("/api/v0") 86 . service( web::scope("/api/v0")
  87 + . service( web::resource("/markdowns")
  88 + . route(web::get().to(get_markdowns))
  89 + )
31 . service( web::resource("/users") 90 . service( web::resource("/users")
32 - . route(web::get().to(routes::get_users))  
33 - . route(web::put().to(routes::create_user))) 91 + . route(web::get().to(get_users))
  92 + . route(web::put().to(create_user))
  93 + )
34 . service( web::resource("/users/{id}") 94 . service( web::resource("/users/{id}")
35 - . route(web::delete().to(routes::delete_user))  
36 - . route(web::get().to(routes::get_user))  
37 - . route(web::put().to(routes::update_user)))) 95 + . route(web::delete().to(delete_user))
  96 + . route(web::get().to(get_user))
  97 + . route(web::put().to(update_user))
  98 + )
  99 + )
38 . service( web::scope("") 100 . service( web::scope("")
39 - . route("/", web::get().to(routes::root))  
40 - . route("/index", web::get().to(routes::root))  
41 - . route("/index.html", web::get().to(routes::root))  
42 - . route("/favicon", web::get().to(routes::favicon))  
43 - . route("/favicon.ico", web::get().to(routes::favicon)))  
44 - . default_service(web::resource("")  
45 - . route(web::get().to(routes::p404)) 101 + . route("/", web::get().to(root))
  102 + . route("/index", web::get().to(root))
  103 + . route("/index.html", web::get().to(root))
  104 + . route("/favicon", web::get().to(favicon))
  105 + . route("/favicon.ico", web::get().to(favicon))
  106 + )
  107 + . default_service( web::resource("")
  108 + . route( web::get().to(p404) )
46 . route( web::route() 109 . route( web::route()
47 - . guard(guard::Not(guard::Get()))  
48 - . to(HttpResponse::MethodNotAllowed))) 110 + . guard( guard::Not(guard::Get()) )
  111 + . to(HttpResponse::MethodNotAllowed)
  112 + )
  113 + )
49 }); 114 });
50 115
51 let server = match listenfd.take_tcp_listener(0).unwrap() { 116 let server = match listenfd.take_tcp_listener(0).unwrap() {
  1 +use std::sync::Arc;
  2 +use crate::schema::*;
  3 +use crate::error::Error;
  4 +use crate::Pool;
  5 +use diesel::prelude::*;
  6 +use diesel::{
  7 + dsl::{delete, insert_into, update},
  8 + RunQueryDsl
  9 +};
  10 +use serde::{Deserialize, Serialize};
  11 +
  12 +
  13 +#[derive(Debug, Serialize, Deserialize, Queryable, Identifiable)]
  14 +pub struct Markdown {
  15 + pub id: i32,
  16 + pub name: String,
  17 + pub content: String,
  18 + pub number_of_versions: i32,
  19 + pub date_created: String,
  20 + pub date_updated: String,
  21 +}
  22 +
  23 +#[derive(Debug, Insertable)]
  24 +#[table_name = "markdowns"]
  25 +pub struct MarkdownNew<'a> {
  26 + pub name: &'a str,
  27 + pub content: &'a str,
  28 + pub number_of_versions: i32,
  29 + pub date_created: &'a str,
  30 + pub date_updated: &'a str,
  31 +}
  32 +
  33 +#[derive(Debug, Serialize, Deserialize, AsChangeset)]
  34 +#[table_name="markdowns"]
  35 +pub struct MarkdownJson {
  36 + pub name: String,
  37 + pub content: String,
  38 + pub number_of_versions: i32,
  39 +}
  40 +
  41 +pub(crate) enum Action {
  42 + Created(Markdown),
  43 + Found(Markdown),
  44 +}
  45 +
  46 +
  47 +pub(crate) fn create_markdown( pool: Arc<Pool>
  48 + , item: MarkdownJson ) -> Result<Action, Error> {
  49 + use crate::schema::markdowns::dsl::*;
  50 + let db_connection = pool.get()?;
  51 +
  52 + match markdowns . filter(name.eq(&item.name))
  53 + . first::<Markdown>(&db_connection)
  54 + {
  55 + Ok(result) => Ok(Action::Found(result)),
  56 + Err(_) => {
  57 + let now = chrono::Local::now().naive_local();
  58 + let new_markdown = MarkdownNew {
  59 + name: &item.name,
  60 + content: &item.content,
  61 + number_of_versions: item.number_of_versions,
  62 + date_created: &format!("{}", now),
  63 + date_updated: &format!("{}", now),
  64 + };
  65 +
  66 + Ok(Action::Created(db_connection.transaction(|| {
  67 + insert_into(markdowns) . values(&new_markdown)
  68 + . execute(&db_connection)?;
  69 +
  70 + markdowns . order(id.desc())
  71 + . first::<Markdown>(&db_connection)
  72 + })?))
  73 + }
  74 + }
  75 +}
  76 +
  77 +pub(crate) fn get_markdowns(pool: Arc<Pool>) -> Result<Vec<Markdown>, Error>
  78 +{
  79 + use crate::schema::markdowns::dsl::*;
  80 + let db_connection = pool.get()?;
  81 + Ok(markdowns.load::<Markdown>(&db_connection)?)
  82 +}
  83 +
  84 +pub(crate) fn get_markdown( pool: Arc<Pool>
  85 + , ident: i32 ) -> Result<Markdown, Error>
  86 +{
  87 + use crate::schema::markdowns::dsl::*;
  88 + let db_connection = pool.get()?;
  89 + Ok(markdowns.find(ident).first::<Markdown>(&db_connection)?)
  90 +}
  91 +
  92 +pub(crate) fn delete_markdown( pool: Arc<Pool>
  93 + , ident: i32 ) -> Result<usize, Error>
  94 +{
  95 + use crate::schema::markdowns::dsl::*;
  96 + let db_connection = pool.get()?;
  97 + Ok(delete(markdowns.find(ident)).execute(&db_connection)?)
  98 +}
  99 +
  100 +pub(crate) fn update_markdown( pool: Arc<Pool>
  101 + , ident: i32
  102 + , item: MarkdownJson ) -> Result<Markdown, Error>
  103 +{
  104 + use crate::schema::markdowns::dsl::*;
  105 + let db_connection = pool.get()?;
  106 + let mut markdown = markdowns.find(ident).first::<Markdown>(&db_connection)?;
  107 + let now = chrono::Local::now().naive_local();
  108 +
  109 + update(markdowns.find(ident)).set(&item).execute(&db_connection)?;
  110 + markdown.name = item.name;
  111 + markdown.content = item.content;
  112 + markdown.number_of_versions = item.number_of_versions;
  113 + markdown.date_updated = format!("{}", now);
  114 +
  115 + Ok(markdown)
  116 +}
  1 +pub(crate) mod user;
  2 +pub(crate) mod markdown;
  1 +use crate::models::markdown;
  2 +use crate::Pool;
  3 +
  4 +use actix_web::{Error, HttpResponse, web};
  5 +use anyhow::Result;
  6 +
  7 +pub async fn get_markdowns(pool: web::Data<Pool>)
  8 + -> Result<HttpResponse, Error>
  9 +{
  10 + Ok(web::block(move || markdown::get_markdowns(pool.into_inner()))
  11 + . await
  12 + . map(|markdowns| HttpResponse::Ok().json(markdowns))
  13 + . map_err(|_| HttpResponse::InternalServerError())?)
  14 +}
  1 +pub(crate) mod markdown;
  2 +pub(crate) mod other;
  3 +pub(crate) mod user;
  1 +use actix_web::Error;
  2 +use anyhow::Result;
  3 +
  4 +pub async fn root() -> Result<actix_files::NamedFile, Error> {
  5 + Ok(actix_files::NamedFile::open("static/index.html")?)
  6 +}
  7 +
  8 +pub async fn p404() -> Result<actix_files::NamedFile, Error> {
  9 + Ok(actix_files::NamedFile::open("static/404.html")?)
  10 +}
  11 +
  12 +pub async fn favicon() -> Result<actix_files::NamedFile, Error> {
  13 + Ok(actix_files::NamedFile::open("static/favicon.ico")?)
  14 +}
1 -use crate::models::{self, Action}; 1 +use crate::models::user::{self, Action};
2 use crate::Pool; 2 use crate::Pool;
3 3
4 use actix_web::{Error, HttpResponse, web}; 4 use actix_web::{Error, HttpResponse, web};
5 use anyhow::Result; 5 use anyhow::Result;
6 6
7 -pub async fn root() -> Result<actix_files::NamedFile, Error> {  
8 - Ok(actix_files::NamedFile::open("static/index.html")?)  
9 -}  
10 -  
11 -pub async fn p404() -> Result<actix_files::NamedFile, Error> {  
12 - Ok(actix_files::NamedFile::open("static/404.html")?)  
13 -}  
14 -  
15 -pub async fn favicon() -> Result<actix_files::NamedFile, Error> {  
16 - Ok(actix_files::NamedFile::open("static/favicon.ico")?)  
17 -}  
18 -  
19 pub async fn create_user( pool: web::Data<Pool> 7 pub async fn create_user( pool: web::Data<Pool>
20 - , item: web::Json<models::UserJson> ) 8 + , item: web::Json<user::UserJson> )
21 -> Result<HttpResponse, Error> 9 -> Result<HttpResponse, Error>
22 { 10 {
23 let pool = pool.into_inner(); 11 let pool = pool.into_inner();
24 let item = item.into_inner(); 12 let item = item.into_inner();
25 13
26 - Ok(web::block(move || models::create_user(pool, item)) 14 + Ok(web::block(move || user::create_user(pool, item))
27 . await 15 . await
28 . map(|action| { 16 . map(|action| {
29 match action { 17 match action {
@@ -36,7 +24,7 @@ pub async fn create_user( pool: web::Data<Pool> @@ -36,7 +24,7 @@ pub async fn create_user( pool: web::Data<Pool>
36 pub async fn get_users(pool: web::Data<Pool>) 24 pub async fn get_users(pool: web::Data<Pool>)
37 -> Result<HttpResponse, Error> 25 -> Result<HttpResponse, Error>
38 { 26 {
39 - Ok(web::block(move || models::get_users(pool.into_inner())) 27 + Ok(web::block(move || user::get_users(pool.into_inner()))
40 . await 28 . await
41 . map(|users| HttpResponse::Ok().json(users)) 29 . map(|users| HttpResponse::Ok().json(users))
42 . map_err(|_| HttpResponse::InternalServerError())?) 30 . map_err(|_| HttpResponse::InternalServerError())?)
@@ -48,7 +36,7 @@ pub async fn get_user(pool: web::Data<Pool>, id: web::Path<i32>) @@ -48,7 +36,7 @@ pub async fn get_user(pool: web::Data<Pool>, id: web::Path<i32>)
48 let pool = pool.into_inner(); 36 let pool = pool.into_inner();
49 let id = id.into_inner(); 37 let id = id.into_inner();
50 38
51 - Ok(web::block(move || models::get_user(pool, id)) 39 + Ok(web::block(move || user::get_user(pool, id))
52 . await 40 . await
53 . map(|user| HttpResponse::Ok().json(user)) 41 . map(|user| HttpResponse::Ok().json(user))
54 . map_err(|_| HttpResponse::InternalServerError())?) 42 . map_err(|_| HttpResponse::InternalServerError())?)
@@ -60,7 +48,7 @@ pub async fn delete_user(pool: web::Data<Pool>, id: web::Path<i32>) @@ -60,7 +48,7 @@ pub async fn delete_user(pool: web::Data<Pool>, id: web::Path<i32>)
60 let pool = pool.into_inner(); 48 let pool = pool.into_inner();
61 let id = id.into_inner(); 49 let id = id.into_inner();
62 50
63 - Ok(web::block(move || models::delete_user(pool, id)) 51 + Ok(web::block(move || user::delete_user(pool, id))
64 . await 52 . await
65 . map(|_| HttpResponse::NoContent().finish()) 53 . map(|_| HttpResponse::NoContent().finish())
66 . map_err(|_| HttpResponse::InternalServerError())?) 54 . map_err(|_| HttpResponse::InternalServerError())?)
@@ -68,14 +56,14 @@ pub async fn delete_user(pool: web::Data<Pool>, id: web::Path<i32>) @@ -68,14 +56,14 @@ pub async fn delete_user(pool: web::Data<Pool>, id: web::Path<i32>)
68 56
69 pub async fn update_user( pool: web::Data<Pool> 57 pub async fn update_user( pool: web::Data<Pool>
70 , id: web::Path<i32> 58 , id: web::Path<i32>
71 - , item: web::Json<models::UserJson> ) 59 + , item: web::Json<user::UserJson> )
72 -> Result<HttpResponse, Error> 60 -> Result<HttpResponse, Error>
73 { 61 {
74 let pool = pool.into_inner(); 62 let pool = pool.into_inner();
75 let id = id.into_inner(); 63 let id = id.into_inner();
76 let item = item.into_inner(); 64 let item = item.into_inner();
77 65
78 - Ok(web::block(move || models::update_user(pool, id, item)) 66 + Ok(web::block(move || user::update_user(pool, id, item))
79 . await 67 . await
80 . map(|user| HttpResponse::Ok().json(user)) 68 . map(|user| HttpResponse::Ok().json(user))
81 . map_err(|_| HttpResponse::InternalServerError())?) 69 . map_err(|_| HttpResponse::InternalServerError())?)
1 table! { 1 table! {
  2 + markdown_diffs (markdown_id, diff_id) {
  3 + markdown_id -> Integer,
  4 + diff_id -> Integer,
  5 + diff -> Binary,
  6 + date_created -> Text,
  7 + }
  8 +}
  9 +
  10 +table! {
  11 + markdowns (id) {
  12 + id -> Integer,
  13 + name -> Text,
  14 + content -> Text,
  15 + number_of_versions -> Integer,
  16 + date_created -> Text,
  17 + date_updated -> Text,
  18 + }
  19 +}
  20 +
  21 +table! {
2 users (id) { 22 users (id) {
3 id -> Integer, 23 id -> Integer,
4 name -> Text, 24 name -> Text,
@@ -6,3 +26,9 @@ table! { @@ -6,3 +26,9 @@ table! {
6 date_created -> Text, 26 date_created -> Text,
7 } 27 }
8 } 28 }
  29 +
  30 +allow_tables_to_appear_in_same_query!(
  31 + markdown_diffs,
  32 + markdowns,
  33 + users,
  34 +);
@@ -22,6 +22,10 @@ @@ -22,6 +22,10 @@
22 width: 100%; 22 width: 100%;
23 overflow-y: scroll; 23 overflow-y: scroll;
24 overflow-x: hidden; 24 overflow-x: hidden;
  25 + background: #ffffff;
  26 + border-radius: .35em;
  27 + border: 2px solid #bbb;
  28 + padding: 4px;
25 } 29 }
26 30
27 .input > div:last-child { 31 .input > div:last-child {
@@ -18,7 +18,7 @@ log = "^0.4" @@ -18,7 +18,7 @@ log = "^0.4"
18 serde = { version = "^1.0", features = ["derive"] } 18 serde = { version = "^1.0", features = ["derive"] }
19 serde_json = "^1.0" 19 serde_json = "^1.0"
20 wasm-bindgen = "^0.2" 20 wasm-bindgen = "^0.2"
21 -wasm-bindgen-futures = "0.4" 21 +wasm-bindgen-futures = "^0.4"
22 22
23 # The `console_error_panic_hook` crate provides better debugging of panics by 23 # The `console_error_panic_hook` crate provides better debugging of panics by
24 # logging them with `console.error`. This is great for development, but requires 24 # logging them with `console.error`. This is great for development, but requires
@@ -26,6 +26,7 @@ wasm-bindgen-futures = "0.4" @@ -26,6 +26,7 @@ wasm-bindgen-futures = "0.4"
26 # code size when deploying. 26 # code size when deploying.
27 console_error_panic_hook = { version = "0.1.6", optional = true } 27 console_error_panic_hook = { version = "0.1.6", optional = true }
28 wee_alloc = { version = "0.4.2", optional = true } 28 wee_alloc = { version = "0.4.2", optional = true }
  29 +js-sys = "^0.3"
29 30
30 [dependencies.mogwai] 31 [dependencies.mogwai]
31 version = "^0.5" 32 version = "^0.5"
1 -pub(crate) const MD_EXAMPLE :&str = r"# Ein sehr schöner Titel 1 +pub(crate) const _MD_EXAMPLE :&str = r"# Ein sehr schöner Titel
2 2
3 ## Ein sinnloser Text 3 ## Ein sinnloser Text
4 4
1 mod data; 1 mod data;
2 2
  3 +use js_sys::JsString;
3 use log::Level; 4 use log::Level;
4 use mogwai::prelude::*; 5 use mogwai::prelude::*;
5 -use web_sys::{RequestInit, RequestMode, Request, Response, console}; 6 +use web_sys::{RequestInit, RequestMode, Request, Response};
  7 +use serde::{Deserialize, Serialize};
6 use std::panic; 8 use std::panic;
7 use wasm_bindgen::prelude::*; 9 use wasm_bindgen::prelude::*;
8 10
9 -  
10 #[derive(Clone)] 11 #[derive(Clone)]
11 enum AppLogic { 12 enum AppLogic {
12 Update, 13 Update,
@@ -23,21 +24,37 @@ fn md_to_html(source: &str) -> String { @@ -23,21 +24,37 @@ fn md_to_html(source: &str) -> String {
23 html_output 24 html_output
24 } 25 }
25 26
26 -async fn editor_logic( mut rx_logic: broadcast::Receiver<AppLogic>  
27 - , tx_view: broadcast::Sender<String>  
28 - , mut rx_dom: broadcast::Receiver<Dom> ) { 27 +#[derive(Debug, Serialize, Deserialize)]
  28 +pub struct MarkdownJson {
  29 + pub name: String,
  30 + pub content: String,
  31 + pub number_of_versions: i32,
  32 +}
  33 +
  34 +pub type MarkdownsJson = Vec<MarkdownJson>;
  35 +
  36 +async fn md_from_db() -> String {
29 let window = web_sys::window().unwrap(); 37 let window = web_sys::window().unwrap();
30 let mut opts = RequestInit::new(); 38 let mut opts = RequestInit::new();
31 opts.method("GET").mode(RequestMode::Cors); 39 opts.method("GET").mode(RequestMode::Cors);
32 - let request = Request::new_with_str_and_init("/api/v0/users", &opts).unwrap(); 40 +
  41 + let request = Request::new_with_str_and_init("/api/v0/markdowns", &opts).unwrap();
33 request.headers().set("Accept", "application/json").unwrap(); 42 request.headers().set("Accept", "application/json").unwrap();
34 43
35 let response = JsFuture::from(window.fetch_with_request(&request)) 44 let response = JsFuture::from(window.fetch_with_request(&request))
36 . await.unwrap() 45 . await.unwrap()
37 . dyn_into::<Response>().unwrap(); 46 . dyn_into::<Response>().unwrap();
38 - let data = JsFuture::from(response.json().unwrap()).await.unwrap();  
39 - console::log_1(&data); 47 + let data = String::from( JsFuture::from(response.text().unwrap())
  48 + . await.unwrap()
  49 + . dyn_into::<JsString>().unwrap() );
  50 + let data :MarkdownsJson = serde_json::from_str(data.as_str()).unwrap();
40 51
  52 + String::from(&data[0].content)
  53 +}
  54 +
  55 +async fn editor_logic( mut rx_logic: broadcast::Receiver<AppLogic>
  56 + , tx_view: broadcast::Sender<String>
  57 + , mut rx_dom: broadcast::Receiver<Dom> ) {
41 let dom = rx_dom.next().await.unwrap(); 58 let dom = rx_dom.next().await.unwrap();
42 let mut show_edit = false; 59 let mut show_edit = false;
43 60
@@ -84,8 +101,10 @@ async fn editor_logic( mut rx_logic: broadcast::Receiver<AppLogic> @@ -84,8 +101,10 @@ async fn editor_logic( mut rx_logic: broadcast::Receiver<AppLogic>
84 fn editor_view( tx_logic: broadcast::Sender<AppLogic> 101 fn editor_view( tx_logic: broadcast::Sender<AppLogic>
85 , rx_view: broadcast::Receiver<String> 102 , rx_view: broadcast::Receiver<String>
86 , tx_dom: broadcast::Sender<Dom> 103 , tx_dom: broadcast::Sender<Dom>
  104 + , init_data: &str
87 ) -> ViewBuilder<Dom> { 105 ) -> ViewBuilder<Dom> {
88 let ns = "http://www.w3.org/2000/svg"; 106 let ns = "http://www.w3.org/2000/svg";
  107 +
89 builder! { 108 builder! {
90 <div class="input" 109 <div class="input"
91 style:width="33%" 110 style:width="33%"
@@ -94,7 +113,7 @@ fn editor_view( tx_logic: broadcast::Sender<AppLogic> @@ -94,7 +113,7 @@ fn editor_view( tx_logic: broadcast::Sender<AppLogic>
94 <div contenteditable="true" 113 <div contenteditable="true"
95 style:cursor="text" 114 style:cursor="text"
96 style:display=("none", rx_view)> 115 style:display=("none", rx_view)>
97 - <pre>{data::MD_EXAMPLE}</pre> 116 + <pre>{init_data}</pre>
98 </div> 117 </div>
99 <div> 118 <div>
100 <button on:click=tx_logic . sink() 119 <button on:click=tx_logic . sink()
@@ -118,14 +137,16 @@ fn editor_view( tx_logic: broadcast::Sender<AppLogic> @@ -118,14 +137,16 @@ fn editor_view( tx_logic: broadcast::Sender<AppLogic>
118 } 137 }
119 138
120 #[wasm_bindgen(start)] 139 #[wasm_bindgen(start)]
121 -pub fn main() -> Result<(), JsValue> { 140 +pub async fn main() -> Result<(), JsValue> {
122 panic::set_hook(Box::new(console_error_panic_hook::hook)); 141 panic::set_hook(Box::new(console_error_panic_hook::hook));
123 console_log::init_with_level(Level::Trace).unwrap(); 142 console_log::init_with_level(Level::Trace).unwrap();
124 143
  144 + let data = md_from_db().await;
  145 +
125 let (tx_dom, rx_dom) = broadcast::bounded(1); 146 let (tx_dom, rx_dom) = broadcast::bounded(1);
126 let (tx_logic, rx_logic) = broadcast::bounded(1); 147 let (tx_logic, rx_logic) = broadcast::bounded(1);
127 let (tx_view, rx_view) = broadcast::bounded(1); 148 let (tx_view, rx_view) = broadcast::bounded(1);
128 - let comp = Component::from( editor_view(tx_logic, rx_view, tx_dom) ) 149 + let comp = Component::from( editor_view(tx_logic, rx_view, tx_dom, data.as_str()) )
129 . with_logic( editor_logic(rx_logic, tx_view, rx_dom) ); 150 . with_logic( editor_logic(rx_logic, tx_view, rx_dom) );
130 151
131 let page = Component::from(builder! {{comp}}); 152 let page = Component::from(builder! {{comp}});
Please register or login to post a comment