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,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 | +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,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() { |
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 | 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 | 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