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();