Commit 53332e6bb0b000762b41e5c3207df7169eeb4f26
1 parent
ebe8266b
first time database data in mogwai component
Showing
23 changed files
with
453 additions
and
49 deletions
No preview for this file type
... | ... | @@ -15,3 +15,6 @@ |
15 | 15 | ## Free Artwork |
16 | 16 | - [Designlooter](https://designlooter.com/ 'Designlooter') |
17 | 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 | +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" ); | ... | ... |
... | ... | @@ -6,15 +6,71 @@ mod models; |
6 | 6 | mod routes; |
7 | 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 | 14 | use actix_web::{guard, web, App, HttpResponse, HttpServer}; |
10 | 15 | use diesel::r2d2::{self, ConnectionManager}; |
11 | 16 | use diesel::SqliteConnection; |
17 | +use diffy::create_patch; | |
18 | +use flate2::Compression; | |
19 | +use flate2::write::{DeflateEncoder, DeflateDecoder}; | |
12 | 20 | use listenfd::ListenFd; |
13 | 21 | |
14 | 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 | 53 | #[actix_rt::main] |
17 | 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 | 74 | let mut listenfd = ListenFd::from_env(); |
19 | 75 | |
20 | 76 | dotenv::dotenv().ok(); |
... | ... | @@ -28,24 +84,33 @@ async fn main() -> std::io::Result<()> { |
28 | 84 | App::new() . data(database_pool.clone()) |
29 | 85 | . service(actix_files::Files::new("/static", "./static")) |
30 | 86 | . service( web::scope("/api/v0") |
87 | + . service( web::resource("/markdowns") | |
88 | + . route(web::get().to(get_markdowns)) | |
89 | + ) | |
31 | 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 | 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 | 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 | 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 | 116 | let server = match listenfd.take_tcp_listener(0).unwrap() { | ... | ... |
server/src/models/markdown.rs
0 → 100644
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 | +} | ... | ... |
server/src/models/mod.rs
0 → 100644
server/src/routes/markdown.rs
0 → 100644
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 | +} | ... | ... |
server/src/routes/mod.rs
0 → 100644
server/src/routes/other.rs
0 → 100644
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 | 2 | use crate::Pool; |
3 | 3 | |
4 | 4 | use actix_web::{Error, HttpResponse, web}; |
5 | 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 | 7 | pub async fn create_user( pool: web::Data<Pool> |
20 | - , item: web::Json<models::UserJson> ) | |
8 | + , item: web::Json<user::UserJson> ) | |
21 | 9 | -> Result<HttpResponse, Error> |
22 | 10 | { |
23 | 11 | let pool = pool.into_inner(); |
24 | 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 | 15 | . await |
28 | 16 | . map(|action| { |
29 | 17 | match action { |
... | ... | @@ -36,7 +24,7 @@ pub async fn create_user( pool: web::Data<Pool> |
36 | 24 | pub async fn get_users(pool: web::Data<Pool>) |
37 | 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 | 28 | . await |
41 | 29 | . map(|users| HttpResponse::Ok().json(users)) |
42 | 30 | . map_err(|_| HttpResponse::InternalServerError())?) |
... | ... | @@ -48,7 +36,7 @@ pub async fn get_user(pool: web::Data<Pool>, id: web::Path<i32>) |
48 | 36 | let pool = pool.into_inner(); |
49 | 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 | 40 | . await |
53 | 41 | . map(|user| HttpResponse::Ok().json(user)) |
54 | 42 | . map_err(|_| HttpResponse::InternalServerError())?) |
... | ... | @@ -60,7 +48,7 @@ pub async fn delete_user(pool: web::Data<Pool>, id: web::Path<i32>) |
60 | 48 | let pool = pool.into_inner(); |
61 | 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 | 52 | . await |
65 | 53 | . map(|_| HttpResponse::NoContent().finish()) |
66 | 54 | . map_err(|_| HttpResponse::InternalServerError())?) |
... | ... | @@ -68,14 +56,14 @@ pub async fn delete_user(pool: web::Data<Pool>, id: web::Path<i32>) |
68 | 56 | |
69 | 57 | pub async fn update_user( pool: web::Data<Pool> |
70 | 58 | , id: web::Path<i32> |
71 | - , item: web::Json<models::UserJson> ) | |
59 | + , item: web::Json<user::UserJson> ) | |
72 | 60 | -> Result<HttpResponse, Error> |
73 | 61 | { |
74 | 62 | let pool = pool.into_inner(); |
75 | 63 | let id = id.into_inner(); |
76 | 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 | 67 | . await |
80 | 68 | . map(|user| HttpResponse::Ok().json(user)) |
81 | 69 | . map_err(|_| HttpResponse::InternalServerError())?) | ... | ... |
1 | 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 | 22 | users (id) { |
3 | 23 | id -> Integer, |
4 | 24 | name -> Text, |
... | ... | @@ -6,3 +26,9 @@ table! { |
6 | 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 | +); | ... | ... |
... | ... | @@ -18,7 +18,7 @@ log = "^0.4" |
18 | 18 | serde = { version = "^1.0", features = ["derive"] } |
19 | 19 | serde_json = "^1.0" |
20 | 20 | wasm-bindgen = "^0.2" |
21 | -wasm-bindgen-futures = "0.4" | |
21 | +wasm-bindgen-futures = "^0.4" | |
22 | 22 | |
23 | 23 | # The `console_error_panic_hook` crate provides better debugging of panics by |
24 | 24 | # logging them with `console.error`. This is great for development, but requires |
... | ... | @@ -26,6 +26,7 @@ wasm-bindgen-futures = "0.4" |
26 | 26 | # code size when deploying. |
27 | 27 | console_error_panic_hook = { version = "0.1.6", optional = true } |
28 | 28 | wee_alloc = { version = "0.4.2", optional = true } |
29 | +js-sys = "^0.3" | |
29 | 30 | |
30 | 31 | [dependencies.mogwai] |
31 | 32 | version = "^0.5" | ... | ... |
1 | 1 | mod data; |
2 | 2 | |
3 | +use js_sys::JsString; | |
3 | 4 | use log::Level; |
4 | 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 | 8 | use std::panic; |
7 | 9 | use wasm_bindgen::prelude::*; |
8 | 10 | |
9 | - | |
10 | 11 | #[derive(Clone)] |
11 | 12 | enum AppLogic { |
12 | 13 | Update, |
... | ... | @@ -23,21 +24,37 @@ fn md_to_html(source: &str) -> String { |
23 | 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 | 37 | let window = web_sys::window().unwrap(); |
30 | 38 | let mut opts = RequestInit::new(); |
31 | 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 | 42 | request.headers().set("Accept", "application/json").unwrap(); |
34 | 43 | |
35 | 44 | let response = JsFuture::from(window.fetch_with_request(&request)) |
36 | 45 | . await.unwrap() |
37 | 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 | 58 | let dom = rx_dom.next().await.unwrap(); |
42 | 59 | let mut show_edit = false; |
43 | 60 | |
... | ... | @@ -84,8 +101,10 @@ async fn editor_logic( mut rx_logic: broadcast::Receiver<AppLogic> |
84 | 101 | fn editor_view( tx_logic: broadcast::Sender<AppLogic> |
85 | 102 | , rx_view: broadcast::Receiver<String> |
86 | 103 | , tx_dom: broadcast::Sender<Dom> |
104 | + , init_data: &str | |
87 | 105 | ) -> ViewBuilder<Dom> { |
88 | 106 | let ns = "http://www.w3.org/2000/svg"; |
107 | + | |
89 | 108 | builder! { |
90 | 109 | <div class="input" |
91 | 110 | style:width="33%" |
... | ... | @@ -94,7 +113,7 @@ fn editor_view( tx_logic: broadcast::Sender<AppLogic> |
94 | 113 | <div contenteditable="true" |
95 | 114 | style:cursor="text" |
96 | 115 | style:display=("none", rx_view)> |
97 | - <pre>{data::MD_EXAMPLE}</pre> | |
116 | + <pre>{init_data}</pre> | |
98 | 117 | </div> |
99 | 118 | <div> |
100 | 119 | <button on:click=tx_logic . sink() |
... | ... | @@ -118,14 +137,16 @@ fn editor_view( tx_logic: broadcast::Sender<AppLogic> |
118 | 137 | } |
119 | 138 | |
120 | 139 | #[wasm_bindgen(start)] |
121 | -pub fn main() -> Result<(), JsValue> { | |
140 | +pub async fn main() -> Result<(), JsValue> { | |
122 | 141 | panic::set_hook(Box::new(console_error_panic_hook::hook)); |
123 | 142 | console_log::init_with_level(Level::Trace).unwrap(); |
124 | 143 | |
144 | + let data = md_from_db().await; | |
145 | + | |
125 | 146 | let (tx_dom, rx_dom) = broadcast::bounded(1); |
126 | 147 | let (tx_logic, rx_logic) = broadcast::bounded(1); |
127 | 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 | 150 | . with_logic( editor_logic(rx_logic, tx_view, rx_dom) ); |
130 | 151 | |
131 | 152 | let page = Component::from(builder! {{comp}}); | ... | ... |
Please
register
or
login
to post a comment