Fixed syntax, fixed tests

This commit is contained in:
Андреев Григорий 2025-05-13 01:29:44 +03:00
parent 4656288cf1
commit a4bff93c86
15 changed files with 518 additions and 52 deletions

1
.gitignore vendored
View File

@ -2,3 +2,4 @@ target
.idea
src/bin/sandbox.rs
sandbox
db/

204
Cargo.lock generated
View File

@ -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",
]

View File

@ -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"

View File

@ -0,0 +1,13 @@
{@ body $ @}
{%let pres = missing-text[$lang].index %}
<div class="main-container">
<form action="/login" method="POST">
<input type="hidden" name="csrf" value="{{$token}}">
<label>Username: <input name="username"></label><br>
<label>Password: <input name="password" type="password"></label><br>
<button>Login</button>
</form>
</div>
{%}
{@}
{$ main = d: base.main d.lang missing-text[d.lang].index.title (this.body d) $}

View File

@ -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="Содержательное"

View File

@ -1,6 +0,0 @@
{
index: {
title: "Gregory's title page",
about_me_header: "About me",
}
}

View File

@ -1,6 +0,0 @@
{
index: {
title: "Гришина заглавная страничка",
about_me_header: "Обо мне",
}
}

View File

@ -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("'", "&#39;").replace("\"", "&quot;")
}
// use on normal words only
pub fn is_special_name(s: &str) -> bool {
s.chars().any(|ch| "+'/|&~!^%*".contains(ch)) || s.ends_with("-") || s == "this"
}

View File

@ -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::<u64>() {
@ -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];

View File

@ -172,6 +172,7 @@ pub fn add_entire_stdlib_to_root(root: &mut SharedValue) -> Result<(), Box<dyn E
add_basic_bin_operator_to_root(root, "*", operator_product)?;
add_basic_bin_operator_to_root(root, "/", operator_division)?;
add_basic_bin_operator_to_root(root, "o/o", operator_remainder)?;
add_basic_bin_operator_to_root(root, "%", operator_remainder)?;
add_basic_bin_operator_to_root(root, "&&", operator_and)?;
add_basic_bin_operator_to_root(root, "||", operator_or)?;
add_basic_bin_operator_to_root(root, "^^", operator_xor)?;

View File

@ -127,7 +127,7 @@ fn t010(){
#[test]
fn t011(){
let i = MtgottDirContent{mtgott: vec![
FileWithPath{v_path: "file".into(), text: "{@el a @} {{a[1#}} {@} ".into()}
FileWithPath{v_path: "file".into(), text: "{@el a @} {{a[1]}} {@} ".into()}
], imtgott: vec![
FileWithPath{v_path: "index".into(), text: "{#file.el $#}".into()}
], plain: vec![]};
@ -142,7 +142,7 @@ fn t011(){
#[test]
fn t012(){
let i = MtgottDirContent{mtgott: vec![
FileWithPath{v_path: "file".into(), text: "{@el a @} {{a[1#}} {@} ".into()}
FileWithPath{v_path: "file".into(), text: "{@el a @} {{a[1]}} {@} ".into()}
], imtgott: vec![
FileWithPath{v_path: "index".into(), text: "{#file[\"el\"] $#}".into()}
], plain: vec![]};
@ -170,7 +170,7 @@ fn t013(){
fn t014(){
let i = MtgottDirContent{mtgott: vec![
], imtgott: vec![
FileWithPath{v_path: "index".into(), text: " {{ $[0 #}} {{$ [1#}} {{$[ 2#}} ".into()}
FileWithPath{v_path: "index".into(), text: " {{ $[0 ]}} {{$ [1]}} {{$[ 2]}} ".into()}
], plain: vec![]};
let r = get_root_html_from_dir_text_html(i).unwrap();
assert_gave!(r.render(
@ -182,7 +182,7 @@ fn t014(){
#[test]
fn t015(){
let i = MtgottDirContent{mtgott: vec![
FileWithPath{v_path: "help".into(), text: " {@www $ a b@} {{$[a#}} {{$[b#}} {@}".into()}
FileWithPath{v_path: "help".into(), text: " {@www $ a b@} {{$[a]}} {{$[b]}} {@}".into()}
], imtgott: vec![
FileWithPath{v_path: "index".into(), text: " {#help.www $ 2 1 #}".into()}
], plain: vec![]};
@ -242,7 +242,7 @@ fn t019(){
let i = MtgottDirContent{mtgott: vec![
FileWithPath{v_path: "__".into(), text: "{$arr = [0, 10, 20, 30]$}".into()}
], imtgott: vec![
FileWithPath{v_path: "index".into(), text: " {{ (x:y:z:w:w x z) 2 \"Lol\" __ x:z:z.arr[x#}}".into()}
FileWithPath{v_path: "index".into(), text: " {{ (x:y:z:w:w x z) 2 \"Lol\" __ x:z:z.arr[x]}}".into()}
], plain: vec![]};
let r = get_root_html_from_dir_text_html(i).unwrap();
assert_gave!(r.render(
@ -256,7 +256,7 @@ fn t020(){
let i = MtgottDirContent{mtgott: vec![
FileWithPath{v_path: "__".into(), text: "{$arr = [0, 10, 20, 30]$}".into()}
], imtgott: vec![
FileWithPath{v_path: "index".into(), text: " {{ (x:y:z:w:w x z) __ \"Lol\" 2 x:z:x.arr[z#}}".into()}
FileWithPath{v_path: "index".into(), text: " {{ (x:y:z:w:w x z) __ \"Lol\" 2 x:z:x.arr[z]}}".into()}
], plain: vec![]};
let r = get_root_html_from_dir_text_html(i).unwrap();
assert_gave!(r.render(

6
src/bin/check.rs Normal file
View File

@ -0,0 +1,6 @@
extern crate yyyi_ru;
use yyyi_ru::database_operations::integrity_check;
fn main(){
integrity_check();
}

6
src/bin/initialize.rs Normal file
View File

@ -0,0 +1,6 @@
extern crate yyyi_ru;
use yyyi_ru::database_operations::initialization;
fn main(){
initialization();
}

227
src/database_operations.rs Normal file
View File

@ -0,0 +1,227 @@
use std::{fs, io};
use std::path::{Path, PathBuf};
use rusqlite::{Connection, Row, Rows, Statement};
use rusqlite::params;
use std::error::Error;
use std::fs::metadata;
use std::io::Write;
use base64::Engine;
use serde::Deserialize;
use base64::prelude::BASE64_STANDARD;
pub const INITIAL_LAYOUT_TOML: &str = "assets/initial_layout.toml";
pub const FILES_DIR: &str = "db/files";
pub const SQLITE_FILE: &str = "db/db.db";
#[derive(Deserialize)]
struct InitialUser {
name: String,
role: i64
}
#[derive(Deserialize)]
struct InitialChannel {
name: String
}
#[derive(Deserialize)]
struct InitialLayout {
users: Vec<InitialUser>,
channels: Vec<InitialChannel>,
}
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<String> = Vec::new();
while let Some(row) = rows.next().expect("Failed to read sql row") {
let name = row.get::<usize, String>(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::<usize, String>(0).expect("reading sql column");
db_files.push(name);
}
let mut missing_real_files: Vec<String> = 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<String> = 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::<usize, i64>(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}");
}
}

View File

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