From a4bff93c86acb71a171dd4e1b2ae97be1b673387 Mon Sep 17 00:00:00 2001 From: Andreev Gregory Date: Tue, 13 May 2025 01:29:44 +0300 Subject: [PATCH] Fixed syntax, fixed tests --- .gitignore | 3 +- Cargo.lock | 204 +++++++++++++++++++-- Cargo.toml | 6 +- assets/HypertextPages/login.mtgott.html | 13 ++ assets/initial_layout.toml | 14 ++ assets/text/en-US.json5 | 6 - assets/text/ru-RU.json5 | 6 - mtgott/src/charclasses.rs | 7 +- mtgott/src/parser.rs | 41 +++-- mtgott/src/stdlib.rs | 1 + mtgott/tests/april_24.rs | 12 +- src/bin/check.rs | 6 + src/bin/initialize.rs | 6 + src/database_operations.rs | 227 ++++++++++++++++++++++++ src/lib.rs | 18 +- 15 files changed, 518 insertions(+), 52 deletions(-) create mode 100644 assets/HypertextPages/login.mtgott.html create mode 100644 assets/initial_layout.toml delete mode 100644 assets/text/en-US.json5 delete mode 100644 assets/text/ru-RU.json5 create mode 100644 src/bin/check.rs create mode 100644 src/bin/initialize.rs create mode 100644 src/database_operations.rs diff --git a/.gitignore b/.gitignore index be68079..9308a2f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ target .idea src/bin/sandbox.rs -sandbox \ No newline at end of file +sandbox +db/ \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index f26fbb8..61d73fd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -32,6 +32,17 @@ dependencies = [ "libc", ] +[[package]] +name = "async-trait" +version = "0.1.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "autocfg" version = "1.4.0" @@ -107,6 +118,27 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "bumpalo" version = "3.17.0" @@ -154,18 +186,65 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + [[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -208,6 +287,16 @@ dependencies = [ "pin-utils", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "gimli" version = "0.31.1" @@ -216,9 +305,27 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "hashbrown" -version = "0.15.2" +version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashlink" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +dependencies = [ + "hashbrown", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "http" @@ -357,6 +464,16 @@ version = "0.2.171" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" +[[package]] +name = "libsqlite3-sys" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "947e6816f7825b2b45027c2c32e7085da9934defa535de4a6a46b10a4d5257fa" +dependencies = [ + "pkg-config", + "vcpkg", +] + [[package]] name = "log" version = "0.4.27" @@ -447,6 +564,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + [[package]] name = "proc-macro2" version = "1.0.94" @@ -465,6 +588,20 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rusqlite" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a22715a5d6deef63c637207afbe68d0c72c3f8d0022d7cf9714c442d6157606b" +dependencies = [ + "bitflags", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -546,6 +683,30 @@ dependencies = [ "serde", ] +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha256" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f880fc8562bdeb709793f00eb42a2ad0e672c4f883bbe59122b926eca935c8f6" +dependencies = [ + "async-trait", + "bytes", + "hex", + "sha2", + "tokio", +] + [[package]] name = "shlex" version = "1.3.0" @@ -592,6 +753,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" dependencies = [ "backtrace", + "bytes", "libc", "mio", "pin-project-lite", @@ -613,9 +775,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.21" +version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900f6c86a685850b1bc9f6223b20125115ee3f31e01207d81655bbcc0aea9231" +checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae" dependencies = [ "serde", "serde_spanned", @@ -634,9 +796,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.25" +version = "0.22.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10558ed0bd2a1562e630926a2d1f0b98c827da99fabd3fe20920a59642504485" +checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" dependencies = [ "indexmap", "serde", @@ -648,9 +810,9 @@ dependencies = [ [[package]] name = "toml_write" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28391a4201ba7eb1984cfeb6862c0b3ea2cfe23332298967c749dddc0d6cd976" +checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076" [[package]] name = "tower" @@ -700,12 +862,30 @@ dependencies = [ "once_cell", ] +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -904,9 +1084,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.7.7" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cb8234a863ea0e8cd7284fcdd4f145233eb00fee02bbdd9861aec44e6477bc5" +checksum = "9e27d6ad3dac991091e4d35de9ba2d2d00647c5d0fc26c5496dee55984ae111b" dependencies = [ "memchr", ] @@ -916,8 +1096,12 @@ name = "yyyi_ru" version = "0.1.0" dependencies = [ "axum", + "base64", "chrono", "mtgott", + "rusqlite", + "serde", + "sha256", "tokio", "toml", ] diff --git a/Cargo.toml b/Cargo.toml index a8224d9..a5b4ce0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,5 +7,9 @@ edition = "2024" axum = "0.8.3" tokio = { version = "1.44.1", features = ["rt-multi-thread"] } mtgott = { path = "./mtgott" } -toml="0.8.21" chrono = "0.4.40" +rusqlite = "0.35.0" +sha256 = "1.6.0" +toml = { version = "0.8.22" } +serde = { version = "1.0.219", features = [ "derive" ] } +base64 = "0.22.1" diff --git a/assets/HypertextPages/login.mtgott.html b/assets/HypertextPages/login.mtgott.html new file mode 100644 index 0000000..487f671 --- /dev/null +++ b/assets/HypertextPages/login.mtgott.html @@ -0,0 +1,13 @@ +{@ body $ @} +{%let pres = missing-text[$lang].index %} +
+
+ +
+
+ +
+
+{%} +{@} +{$ main = d: base.main d.lang missing-text[d.lang].index.title (this.body d) $} \ No newline at end of file diff --git a/assets/initial_layout.toml b/assets/initial_layout.toml new file mode 100644 index 0000000..df389ca --- /dev/null +++ b/assets/initial_layout.toml @@ -0,0 +1,14 @@ +[[users]] +name="Гриша" +role=0 + +[[users]] +name="Скибидист" +role=2 + +[[channels]] +# Ofcourse, this is a name for admin, names of channels on html pages CAN be translated +name="Блог" + +[[channels]] +name="Содержательное" diff --git a/assets/text/en-US.json5 b/assets/text/en-US.json5 deleted file mode 100644 index b7a775b..0000000 --- a/assets/text/en-US.json5 +++ /dev/null @@ -1,6 +0,0 @@ -{ - index: { - title: "Gregory's title page", - about_me_header: "About me", - } -} \ No newline at end of file diff --git a/assets/text/ru-RU.json5 b/assets/text/ru-RU.json5 deleted file mode 100644 index bd3acba..0000000 --- a/assets/text/ru-RU.json5 +++ /dev/null @@ -1,6 +0,0 @@ -{ - index: { - title: "Гришина заглавная страничка", - about_me_header: "Обо мне", - } -} \ No newline at end of file diff --git a/mtgott/src/charclasses.rs b/mtgott/src/charclasses.rs index d7992d1..d4464f1 100644 --- a/mtgott/src/charclasses.rs +++ b/mtgott/src/charclasses.rs @@ -14,7 +14,11 @@ pub fn is_digit(ch: char) -> bool { pub fn is_normal_word_constituent(ch: char) -> bool { ('0'..='9').contains(&ch) || ('a'..='z').contains(&ch) || ('A'..='Z').contains(&ch) - || "<>=-_+/|&~!^*".contains(ch) + || "-_".contains(ch) +} + +pub fn is_queer_word_constituent(ch: char) -> bool { + is_normal_word_constituent(ch) || "%<>=+/|&~!^*".contains(ch) } pub fn is_normal_word(s: &str) -> bool { @@ -26,6 +30,7 @@ pub fn escape_for_html(s: &str) -> String { .replace("'", "'").replace("\"", """) } +// use on normal words only pub fn is_special_name(s: &str) -> bool { s.chars().any(|ch| "+'/|&~!^%*".contains(ch)) || s.ends_with("-") || s == "this" } diff --git a/mtgott/src/parser.rs b/mtgott/src/parser.rs index 46ba724..575f84a 100644 --- a/mtgott/src/parser.rs +++ b/mtgott/src/parser.rs @@ -3,6 +3,7 @@ use crate::charclasses::{is_special_name, is_digit, is_lnspace, is_normal_word_constituent, is_whitespace}; use std::fmt::{self, Display, Formatter}; use std::error::Error; +use charclasses::is_queer_word_constituent; #[derive(Debug, PartialEq, Clone)] pub struct NewLambdaExpression { @@ -177,13 +178,20 @@ impl<'a> Parser<'a> { } } - fn is_word_ahead(&self) -> bool { + fn is_normal_word_ahead(&self) -> bool { match self.here() { Some(ch) => is_normal_word_constituent(ch), None => false, } } + fn is_queer_word_ahead(&self) -> bool { + match self.here() { + Some(ch) => is_queer_word_constituent(ch) && !self.is_ahead("%}"), + None => false, + } + } + fn advance(&mut self) { self.p += self.text[self.p..].chars().next().unwrap().len_utf8(); } @@ -191,10 +199,8 @@ impl<'a> Parser<'a> { fn skip_whitespace(&mut self) { loop { match self.here() { - Some(ch ) => if !is_whitespace(ch) { - break - } else { self.advance(); } - None => break + Some(ch ) if is_whitespace(ch) => self.advance(), + _ => break } } } @@ -202,10 +208,17 @@ impl<'a> Parser<'a> { fn skip_normal_word(&mut self){ loop { match self.here() { - Some(ch ) => if !is_normal_word_constituent(ch) { - break - } else { self.advance(); } - None => break + Some(ch ) if is_normal_word_constituent(ch) => self.advance(), + _ => break + } + } + } + + fn skip_queer_word(&mut self){ + while !self.is_ahead("%}"){ + match self.here() { + Some(ch ) if is_queer_word_constituent(ch) => self.advance(), + _ => break } } } @@ -297,7 +310,7 @@ impl<'a> Parser<'a> { if self.is_char_ahead('$') { arg_names.push("$"); self.p += 1; - if self.is_word_ahead() { + if self.is_normal_word_ahead() { return Err(FileParsingError::new(leave_space_between_dollar_argument_and_other_arguments, self.p - 1, self.next_p())); } @@ -754,7 +767,7 @@ impl<'a> Parser<'a> { loop { if self.is_digit_ahead() { self.p += 1; - } else if self.is_word_ahead() { + } else if self.is_normal_word_ahead() { return Err(self.new_unexpected_char_error(cant_start_word_immediately_after_digit)) } else { return match self.text[p1..self.p].parse::() { @@ -763,9 +776,9 @@ impl<'a> Parser<'a> { }; } } - } else if self.is_word_ahead() { + } else if self.is_queer_word_ahead() { let p1 = self.p; - self.skip_normal_word(); + self.skip_queer_word(); let toplevel_name = &self.text[p1..self.p]; let p2 = self.p; self.skip_whitespace(); @@ -815,7 +828,7 @@ impl<'a> Parser<'a> { Some(n) => Expression::Local(n), None => return Err(self.new_unexpected_char_error(cant_use_dollar_in_expression_of_element_without_dollar_argument)), }; - let bg: Expression = if self.is_word_ahead() { + let bg: Expression = if self.is_normal_word_ahead() { let p1 = self.p; self.skip_normal_word(); let dollar_level_name = &self.text[p1..self.p]; diff --git a/mtgott/src/stdlib.rs b/mtgott/src/stdlib.rs index 6b0cb8d..a4391f3 100644 --- a/mtgott/src/stdlib.rs +++ b/mtgott/src/stdlib.rs @@ -172,6 +172,7 @@ pub fn add_entire_stdlib_to_root(root: &mut SharedValue) -> Result<(), Box, + channels: Vec, +} + +macro_rules! bail { + ($($arg:tt)*) => {{ + eprintln!($($arg)*); + std::process::exit(1); + }}; +} + +pub fn initialization(){ + let initial_layout_txt: String = match fs::read(INITIAL_LAYOUT_TOML) { + Ok(s) => String::from_utf8(s).expect("Need utf-8"), + Err(e) => bail!("Can't read {INITIAL_LAYOUT_TOML:?}: {e:?}") + }; + let initial_layout: InitialLayout = toml::from_str(&initial_layout_txt) + .expect("Incorrect initialization layout"); + + if let Err(e) = fs::create_dir_all(&FILES_DIR) { + bail!("Failed to create directory {:?}: {}", &FILES_DIR, e); + } + + let connection: Connection = match Connection::open(&SQLITE_FILE) { + Ok(conn) => conn, + Err(e) => return bail!("Failed to open database {:?}: {}", &SQLITE_FILE, e) + }; + + // Schemas for database + let schemas = [ + "CREATE TABLE IF NOT EXISTS `users` ( + id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, + password TEXT NOT NULL, role INTEGER NOT NULL);", + "CREATE TABLE IF NOT EXISTS `channels` ( + id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL);", + "CREATE TABLE IF NOT EXISTS `messages` ( + id INTEGER PRIMARY KEY AUTOINCREMENT, text TEXT NOT NULL, + user_id INTEGER NOT NULL, channel_id INTEGER NOT NULL, time TEXT NOT NULL, + FOREIGN KEY(user_id) REFERENCES USERS(id), + FOREIGN KEY(channel_id) REFERENCES CHANNELS(id));", + "CREATE TABLE IF NOT EXISTS `files` ( + storage_name TEXT PRIMARY KEY, name TEXT NOT NULL, content_type TEXT NOT NULL, + image_width INTEGER, image_height INTEGER);", + ]; + + for sql in &schemas { + if let Err(e) = connection.execute(sql, params![]) { + return bail!("Failed to create table: {}", e) + } + } + + for user in initial_layout.users { + print!("Password for {}: ", user.name); + io::stdout().flush().unwrap(); + let mut password = String::new(); + std::io::stdin().read_line(&mut password).expect("reading password"); + print!("Repeat password for {}: ", user.name); + io::stdout().flush().unwrap(); + let mut repeat_password = String::new(); + std::io::stdin().read_line(&mut repeat_password).expect("reading password"); + if password != repeat_password { + panic!("Passwords do not match"); + } + if password.len() < 4 { + panic!("Password too short") + } + let hash: String = sha256::digest(password); + connection.execute( + "INSERT INTO `users` (`name`, `password`, `role`) VALUES (?1, ?2, ?3)", + params![user.name, hash, user.role] + ).expect("sql error"); + } + + for channel in initial_layout.channels { + connection.execute( + "INSERT INTO `channels` (`name`) VALUES (?1)", + (channel.name,) + ).expect("sql error"); + } + + println!("Initialization complete"); +} + +pub fn integrity_check() { + // [ TEST ] Presence of directory files + if !Path::new(FILES_DIR).is_dir() { + return bail!("Directory {:?} is missing", &FILES_DIR); + } + + // [ TEST ] can open database + let connection: Connection = match Connection::open(&SQLITE_FILE) { + Ok(conn) => conn, + Err(e) => bail!("Failed to open database {:?}: {}", &SQLITE_FILE, e) + }; + + // [ TEST ] Presence of tables USERS and CHANNELS + let mut stmt = connection.prepare(" + SELECT 'users' AS name + UNION SELECT 'channels' + UNION SELECT 'messages' + UNION SELECT 'files' + EXCEPT + SELECT name FROM sqlite_master WHERE type = 'table'" + ).expect("sql prepare"); + let mut rows = stmt.query([]).expect("sql query"); + let mut missing: Vec = Vec::new(); + while let Some(row) = rows.next().expect("Failed to read sql row") { + let name = row.get::(0).expect("Failed to read sql column"); + missing.push(name); + } + if !missing.is_empty() { + bail!("Tables [{}] not present", missing.join(", ")) + } + + // 3. FILES entries vs directory database/files + // a) For each entry in FILES, file must exist + let mut stmt: Statement = connection.prepare( + "SELECT storage_name FROM `files`;" + ).expect("sql prepare"); + let mut rows = stmt.query([]).expect("sql query"); + let mut db_files = Vec::new(); + while let Some(row) = rows.next().expect("Reading sql row") { + let name = row.get::(0).expect("reading sql column"); + db_files.push(name); + } + let mut missing_real_files: Vec = Vec::new(); + for storage_name in &db_files { + let path = PathBuf::from(FILES_DIR).join(storage_name); + if !path.is_file() { + missing_real_files.push(storage_name.clone()); + } + } + + // b) For each file in directory, entry must exist + let mut missing_db_file_entries: Vec = Vec::new(); + let entries = fs::read_dir(&FILES_DIR) + .expect(format!("Filed to open {FILES_DIR:?}").as_str()); + let mut total_size_of_files: u64 = 0; + for entry in entries { + let entry = entry.expect("Failed to iterate over files in database/files"); + let file_name: String = entry.file_name().to_str() + .expect("File name is not utf-8").to_string(); + let metadata = metadata(PathBuf::from(FILES_DIR).join(&file_name)).expect("metadata"); + if !metadata.is_file() { + bail!("Not a file: {:?}", file_name); + } + total_size_of_files += metadata.len(); + if !db_files.contains(&file_name) { + missing_db_file_entries.push(file_name); + } + } + + if !missing_real_files.is_empty() || !missing_db_file_entries.is_empty() { + bail!("\ +Mismatch between FILES table and files directory: +Missing real files (present according to FILES table):\n{} +Missing FILES table entries (present in real files directory):\n{}\n", + if missing_real_files.is_empty() { "No".to_string() } else { missing_real_files.join("\n") }, + if missing_db_file_entries.is_empty() { "No".to_string() } else { missing_db_file_entries.join("\n") }) + } + + println!("Integrity OK"); + // Integrity check complete. Collecting statistics + + let msg_count: i64 = { + let mut stmt: Statement = connection.prepare( + "SELECT COUNT(*) FROM `messages`;" + ).expect("sql prepare"); + let mut rows: Rows = stmt.query([]).expect("sql query"); + rows.next().expect("reading sql row").expect("sql error") + .get::(0).expect("reading sql column") + }; + + let file_count = db_files.len(); + println!("Files count: {file_count}. Total weight of file storage: {total_size_of_files}"); + println!("Messages count: {msg_count}"); + + println!("Users:"); + let mut stmt: Statement = connection.prepare( + "SELECT id, name, role, password FROM `users`;").unwrap(); + let mut rows: Rows = stmt.query([]).unwrap(); + while let Some(row) = rows.next().unwrap() { + let id: i64 = row.get(0).unwrap(); + let name: String = row.get(1).unwrap(); + let role: i64 = row.get(2).unwrap(); + let password: String = row.get(3).unwrap(); + println!(" - {id}: {name} (role {role}) with password {password}"); + } + + println!("Channels:"); + let mut stmt = connection.prepare( + "SELECT id, name FROM `channels`;").unwrap(); + let mut rows: Rows = stmt.query([]).unwrap(); + while let Some(row) = rows.next().unwrap() { + let id: i64 = row.get(0).unwrap(); + let name: String = row.get(1).unwrap(); + println!(" - {id}: {name}"); + } +} diff --git a/src/lib.rs b/src/lib.rs index 02df0da..68f879e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,16 +1,22 @@ -use axum; +pub mod database_operations; + use std::io; +use std::rc::Rc; use std::fs; use std::collections::HashMap; -use axum::http::HeaderValue; use std::path::{Path, PathBuf}; use std::sync::Arc; -use axum::http; use mtgott::dirsearch::{search_dir, get_root_html}; use mtgott::runtime::{MTGOTT, Value}; use std::error::Error; + +use axum; +use axum::http; +use axum::http::HeaderValue; use axum::http::header::ACCEPT_LANGUAGE; -use std::rc::Rc; +use axum::extract::{Form, State}; +use axum::response::{Html, IntoResponse, Redirect}; + use chrono::{Datelike, TimeZone, Utc}; macro_rules! valarr { @@ -155,16 +161,14 @@ pub async fn run_yyyi_ru() { let assets = Arc::new(AssetsCache::load_assets(&assets_dir).unwrap()); - // build our application with a single route let app = axum::Router::new() .without_v07_checks() .route("/", axum::routing::MethodRouter::new().get(page_index)) .route("/blog", axum::routing::get(page_blog)) .route("/assets/{*path}", axum::routing::get(static_assets)) .fallback(fallback_page).with_state(assets); - // .layer(axum::Extension(templates)); - // run our app with hyper, listening globally on port 3000 + // run our app with hyper let listener = tokio::net::TcpListener::bind(address).await.unwrap(); println!("Running on http://{address}"); axum::serve(listener, app).await.unwrap();