1
0
This commit is contained in:
2025-04-03 04:25:33 +03:00
parent bc9d31c385
commit ce4bb78315
14 changed files with 541 additions and 71 deletions

258
Cargo.lock generated
View File

@@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
version = 4
[[package]]
name = "addr2line"
@@ -96,7 +96,7 @@ checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.60",
"syn 2.0.100",
]
[[package]]
@@ -191,7 +191,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.60",
"syn 2.0.100",
]
[[package]]
@@ -384,6 +384,41 @@ dependencies = [
"typenum",
]
[[package]]
name = "darling"
version = "0.20.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"
dependencies = [
"darling_core",
"darling_macro",
]
[[package]]
name = "darling_core"
version = "0.20.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim",
"syn 2.0.100",
]
[[package]]
name = "darling_macro"
version = "0.20.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
dependencies = [
"darling_core",
"quote",
"syn 2.0.100",
]
[[package]]
name = "data-encoding"
version = "2.5.0"
@@ -399,6 +434,37 @@ dependencies = [
"powerfmt",
]
[[package]]
name = "derive_builder"
version = "0.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947"
dependencies = [
"derive_builder_macro",
]
[[package]]
name = "derive_builder_core"
version = "0.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8"
dependencies = [
"darling",
"proc-macro2",
"quote",
"syn 2.0.100",
]
[[package]]
name = "derive_builder_macro"
version = "0.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c"
dependencies = [
"derive_builder_core",
"syn 2.0.100",
]
[[package]]
name = "derive_more"
version = "0.99.17"
@@ -581,7 +647,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.60",
"syn 2.0.100",
]
[[package]]
@@ -643,6 +709,23 @@ version = "0.28.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
[[package]]
name = "handlebars"
version = "6.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "759e2d5aea3287cb1190c8ec394f42866cb5bf74fcbf213f354e3c856ea26098"
dependencies = [
"derive_builder",
"log",
"num-order",
"pest",
"pest_derive",
"serde",
"serde_json",
"thiserror 2.0.12",
"walkdir",
]
[[package]]
name = "hashbrown"
version = "0.12.3"
@@ -811,6 +894,12 @@ dependencies = [
"cc",
]
[[package]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "idna"
version = "0.5.0"
@@ -1018,6 +1107,7 @@ dependencies = [
"derive_more",
"figment",
"futures",
"handlebars",
"jsonwebtoken",
"serde",
"serde_json",
@@ -1075,6 +1165,21 @@ dependencies = [
"num-traits",
]
[[package]]
name = "num-modular"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17bb261bf36fa7d83f4c294f834e91256769097b3cb505d44831e0a179ac647f"
[[package]]
name = "num-order"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "537b596b97c40fcf8056d153049eb22f481c17ebce72a513ec9286e4986d1bb6"
dependencies = [
"num-modular",
]
[[package]]
name = "num-traits"
version = "0.2.18"
@@ -1200,7 +1305,7 @@ dependencies = [
"proc-macro2",
"proc-macro2-diagnostics",
"quote",
"syn 2.0.60",
"syn 2.0.100",
]
[[package]]
@@ -1219,6 +1324,51 @@ version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
name = "pest"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "198db74531d58c70a361c42201efde7e2591e976d518caf7662a47dc5720e7b6"
dependencies = [
"memchr",
"thiserror 2.0.12",
"ucd-trie",
]
[[package]]
name = "pest_derive"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d725d9cfd79e87dccc9341a2ef39d1b6f6353d68c4b33c177febbe1a402c97c5"
dependencies = [
"pest",
"pest_generator",
]
[[package]]
name = "pest_generator"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db7d01726be8ab66ab32f9df467ae8b1148906685bbe75c82d1e65d7f5b3f841"
dependencies = [
"pest",
"pest_meta",
"proc-macro2",
"quote",
"syn 2.0.100",
]
[[package]]
name = "pest_meta"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f9f832470494906d1fca5329f8ab5791cc60beb230c74815dff541cbd2b5ca0"
dependencies = [
"once_cell",
"pest",
"sha2",
]
[[package]]
name = "pin-project"
version = "1.1.5"
@@ -1236,7 +1386,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.60",
"syn 2.0.100",
]
[[package]]
@@ -1271,9 +1421,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "proc-macro2"
version = "1.0.81"
version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba"
checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84"
dependencies = [
"unicode-ident",
]
@@ -1286,7 +1436,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.60",
"syn 2.0.100",
"version_check",
"yansi",
]
@@ -1356,7 +1506,7 @@ checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891"
dependencies = [
"getrandom",
"libredox",
"thiserror",
"thiserror 1.0.59",
]
[[package]]
@@ -1481,6 +1631,15 @@ version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1"
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
@@ -1520,7 +1679,7 @@ checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.60",
"syn 2.0.100",
]
[[package]]
@@ -1613,7 +1772,7 @@ checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085"
dependencies = [
"num-bigint",
"num-traits",
"thiserror",
"thiserror 1.0.59",
"time",
]
@@ -1728,7 +1887,7 @@ dependencies = [
"sqlformat",
"sqlx-rt",
"stringprep",
"thiserror",
"thiserror 1.0.59",
"tokio-stream",
"url",
"webpki-roots",
@@ -1776,6 +1935,12 @@ dependencies = [
"unicode-normalization",
]
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "subtle"
version = "2.5.0"
@@ -1795,9 +1960,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.60"
version = "2.0.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3"
checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
dependencies = [
"proc-macro2",
"quote",
@@ -1822,7 +1987,16 @@ version = "1.0.59"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa"
dependencies = [
"thiserror-impl",
"thiserror-impl 1.0.59",
]
[[package]]
name = "thiserror"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
dependencies = [
"thiserror-impl 2.0.12",
]
[[package]]
@@ -1833,7 +2007,18 @@ checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.60",
"syn 2.0.100",
]
[[package]]
name = "thiserror-impl"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.100",
]
[[package]]
@@ -1919,7 +2104,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.60",
"syn 2.0.100",
]
[[package]]
@@ -2054,7 +2239,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf"
dependencies = [
"crossbeam-channel",
"thiserror",
"thiserror 1.0.59",
"time",
"tracing-subscriber",
]
@@ -2067,7 +2252,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.60",
"syn 2.0.100",
]
[[package]]
@@ -2124,7 +2309,7 @@ dependencies = [
"log",
"rand",
"sha1",
"thiserror",
"thiserror 1.0.59",
"url",
"utf-8",
]
@@ -2135,6 +2320,12 @@ version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
[[package]]
name = "ucd-trie"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
[[package]]
name = "uncased"
version = "0.9.10"
@@ -2224,6 +2415,16 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "walkdir"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
dependencies = [
"same-file",
"winapi-util",
]
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
@@ -2257,7 +2458,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.60",
"syn 2.0.100",
"wasm-bindgen-shared",
]
@@ -2279,7 +2480,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.60",
"syn 2.0.100",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@@ -2346,6 +2547,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
@@ -2532,5 +2742,5 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.60",
"syn 2.0.100",
]

View File

@@ -11,6 +11,7 @@ axum = { version = "0.7.5", features = ["macros", "ws"] }
derive_more = "0.99.17"
figment = { version = "0.10.18", features = ["env", "toml"] }
futures = "0.3.30"
handlebars = { version = "6.3.2", features = ["dir_source"] }
jsonwebtoken = "9.3.0"
serde = { version = "1.0.198", features = ["derive"] }
serde_json = "1.0.116"

View File

@@ -4,4 +4,4 @@ port = 1234
[database]
max_connections = 5
url = "postgres://postgres:123456789@localhost:5432/postgres"
url = "postgres://postgres:postgres@localhost:5432/cspbd_lab"

View File

@@ -1,11 +1,8 @@
use std::ops::Deref;
use derive_more::{Display, Error, From};
use sqlx::migrate::{Migrate, Migrator};
use std::ops::Deref;
use crate::{
config,
entity,
};
use crate::{config, entity};
static MIGRATOR: Migrator = sqlx::migrate!("./migrations");
@@ -18,8 +15,8 @@ impl Database {
pub async fn init() -> Result<Self> {
let config = config::config();
let pool = sqlx::postgres::PgPoolOptions::new()
.max_connections(config.database.max_connections);
let pool =
sqlx::postgres::PgPoolOptions::new().max_connections(config.database.max_connections);
let pool = pool
.connect(&config.database.url)
.await
@@ -30,10 +27,13 @@ impl Database {
let connection = pool.acquire().await?;
let version = connection.deref().server_version_num();
tracing::info!("Connected to postgres version {}", match version {
Some(version) => version.to_string(),
None => "unknown".to_string(),
});
tracing::info!(
"Connected to postgres version {}",
match version {
Some(version) => version.to_string(),
None => "unknown".to_string(),
}
);
}
MIGRATOR.run(&pool).await?;
@@ -104,12 +104,14 @@ impl Database {
token: &str,
expires_at: chrono::DateTime<chrono::Utc>,
) -> Result<entity::Token> {
sqlx::query(r#"INSERT INTO "tokens"("user_id", "token", "expires_at") VALUES ($1, $2, $3)"#)
.bind(id)
.bind(token)
.bind(expires_at)
.execute(&self.pool)
.await?;
sqlx::query(
r#"INSERT INTO "tokens"("user_id", "token", "expires_at") VALUES ($1, $2, $3)"#,
)
.bind(id)
.bind(token)
.bind(expires_at)
.execute(&self.pool)
.await?;
self.get_token(token).await
}
@@ -166,12 +168,14 @@ impl Database {
channel_id: entity::ShortId,
admin: bool,
) -> Result<()> {
sqlx::query(r#"INSERT INTO "channel_user"("user_id", "channel_id", "admin") VALUES ($1, $2, $3)"#)
.bind(user_id)
.bind(channel_id)
.bind(admin)
.execute(&self.pool)
.await?;
sqlx::query(
r#"INSERT INTO "channel_user"("user_id", "channel_id", "admin") VALUES ($1, $2, $3)"#,
)
.bind(user_id)
.bind(channel_id)
.bind(admin)
.execute(&self.pool)
.await?;
Ok(())
}
@@ -231,11 +235,12 @@ impl Database {
&self,
channel_id: entity::ShortId,
) -> Result<Vec<(entity::User, bool)>> {
let user_ids =
sqlx::query_as(r#"SELECT "user_id", "admin" FROM "channel_user" WHERE "channel_id" = $1"#)
.bind(channel_id)
.fetch_all(&self.pool)
.await?;
let user_ids = sqlx::query_as(
r#"SELECT "user_id", "admin" FROM "channel_user" WHERE "channel_id" = $1"#,
)
.bind(channel_id)
.fetch_all(&self.pool)
.await?;
let users = user_ids.iter().map(|(user_id, admin)| async move {
let user = self.get_user_by_id(*user_id).await;
@@ -274,14 +279,12 @@ impl Database {
// self.set_channel_last_message_id(channel_id, id).await?;
let id = sqlx::query_scalar(
"SELECT create_message_and_update_channel($1, $2, $3)"
)
.bind(channel_id)
.bind(user_id)
.bind(content)
.fetch_one(&self.pool)
.await?;
let id = sqlx::query_scalar("SELECT create_message_and_update_channel($1, $2, $3)")
.bind(channel_id)
.bind(user_id)
.bind(content)
.fetch_one(&self.pool)
.await?;
self.get_message_by_id(id).await
}
@@ -375,12 +378,13 @@ impl Database {
user_id: entity::ShortId,
channel_id: entity::ShortId,
) -> Result<entity::ChannelPermisions> {
let permissions =
sqlx::query_as(r#"SELECT * FROM "channel_user" WHERE "user_id" = $1 AND "channel_id" = $2"#)
.bind(user_id)
.bind(channel_id)
.fetch_one(&self.pool)
.await?;
let permissions = sqlx::query_as(
r#"SELECT * FROM "channel_user" WHERE "user_id" = $1 AND "channel_id" = $2"#,
)
.bind(user_id)
.bind(channel_id)
.fetch_one(&self.pool)
.await?;
Ok(permissions)
}
@@ -577,9 +581,41 @@ impl Database {
}
pub async fn any_fetch_all_query(&self, query: &str) -> Result<Vec<sqlx::postgres::PgRow>> {
let result = sqlx::query(query)
.fetch_all(&self.pool)
.await?;
let result = sqlx::query(query).fetch_all(&self.pool).await?;
Ok(result)
}
pub async fn get_report_user_stats(&self) -> Result<Vec<entity::ReportUserStat>> {
let result = sqlx::query_as(
r#"SELECT
u."username",
(SELECT COUNT(*) FROM "user_follow" WHERE "follow_id" = u."id") AS followers_count,
(SELECT COUNT(*) FROM "user_follow" WHERE "user_id" = u."id") AS following_count,
(SELECT COUNT(*) FROM "channel_user" WHERE "user_id" = u."id") AS channels_count,
(SELECT COUNT(*) FROM "message" WHERE "author_id" = u."id") AS messages_count
FROM
"user" u
"#,
)
.fetch_all(&self.pool)
.await?;
Ok(result)
}
pub async fn get_report_channel_stats(&self) -> Result<Vec<entity::ReportChannelStat>> {
let result = sqlx::query_as(
r#"SELECT
c."name",
(SELECT COUNT(*) FROM "channel_user" WHERE "channel_id" = c."id") AS members_count,
(SELECT COUNT(*) FROM "message" WHERE "channel_id" = c."id") AS messages_count
FROM
"channel" c
"#,
)
.fetch_all(&self.pool)
.await?;
Ok(result)
}

View File

@@ -7,6 +7,7 @@ mod notification;
mod secret;
mod token;
mod user;
mod report;
pub use channel::*;
pub use log::*;
@@ -15,6 +16,7 @@ pub use notification::*;
pub use secret::*;
pub use token::*;
pub use user::*;
pub use report::*;
pub type ShortId = i32;
pub type LongId = i64;

15
src/entity/report.rs Normal file
View File

@@ -0,0 +1,15 @@
#[derive(sqlx::FromRow, serde::Serialize)]
pub struct ReportUserStat {
username: String,
followers_count: i64,
following_count: i64,
channels_count: i64,
messages_count: i64,
}
#[derive(sqlx::FromRow, serde::Serialize)]
pub struct ReportChannelStat {
name: String,
members_count: i64,
messages_count: i64,
}

View File

@@ -15,8 +15,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let _guard = log::initialize()?;
let database = Database::init().await?;
let handlebars = create_handlebars()?;
let context = AppState {
database,
handlebars,
connected_users: Default::default(),
};
@@ -24,3 +26,11 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
Ok(())
}
fn create_handlebars() -> Result<handlebars::Handlebars<'static>, handlebars::TemplateError> {
let mut handlebars = handlebars::Handlebars::new();
handlebars
.register_templates_directory("./templates", handlebars::DirectorySourceOptions::default())?;
Ok(handlebars)
}

View File

@@ -13,5 +13,6 @@ pub struct WebSocketKey {
#[derive(Clone)]
pub struct AppState {
pub database: Database,
pub handlebars: handlebars::Handlebars<'static>,
pub connected_users: Arc<RwLock<HashMap<WebSocketKey, UnboundedSender<ws::Message>>>>,
}

View File

@@ -14,6 +14,8 @@ pub enum Error {
#[from]
Context(context::Error),
#[from]
Template(handlebars::RenderError),
#[from]
Database(database::Error),
#[from]
Jwt(jwt::Error),

View File

@@ -9,9 +9,9 @@ pub mod middlware;
pub mod routes;
pub mod ws;
use crate::web::routes::{lab, report};
pub use error::{Error, Result};
use tower_http::cors::{AllowHeaders, AllowMethods, AllowOrigin};
use crate::web::routes::lab;
pub async fn run(state: state::AppState) -> anyhow::Result<()> {
let config = config::config();
@@ -48,6 +48,9 @@ fn router(state: state::AppState) -> axum::Router {
// lab
.route("/lab/users", get(lab::get_all_users))
.route("/lab/any-query", post(lab::any_fetch_all_query))
.route("/report/user", get(report::get_user_report))
.route("/report/channel", get(report::get_channel_report))
.layer(tower_http::trace::TraceLayer::new_for_http())
.route_layer(axum::middleware::from_fn_with_state(
state.clone(),

View File

@@ -4,3 +4,4 @@ pub mod notification;
pub mod secret;
pub mod user;
pub mod lab;
pub mod report;

31
src/web/routes/report.rs Normal file
View File

@@ -0,0 +1,31 @@
use axum::extract::State;
use axum::response::Html;
use serde_json::json;
use crate::state::AppState;
use crate::web;
pub async fn get_user_report(
State(state): State<AppState>,
) -> web::Result<Html<String>> {
let users = state.database.get_report_user_stats().await?;
let hbs = &state.handlebars;
let html = hbs.render("user_report", &json!({
"users": users,
}))?;
Ok(Html(html))
}
pub async fn get_channel_report(
State(state): State<AppState>,
) -> web::Result<Html<String>> {
let channels = state.database.get_report_channel_stats().await?;
let hbs = &state.handlebars;
let html = hbs.render("channel_report", &json!({
"channels": channels,
}))?;
Ok(Html(html))
}

View File

@@ -0,0 +1,77 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Отчет по статистике каналов</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 20px;
line-height: 1.5;
}
h1 {
text-align: center;
color: #333;
font-size: 24px;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
}
th, td {
border: 1px solid #ddd;
padding: 10px;
text-align: left;
}
th {
background-color: #f2f2f2;
color: #333;
font-weight: bold;
}
tr:nth-child(even) {
background-color: #f9f9f9;
}
tr:hover {
background-color: #f1f1f1;
}
@media print {
body {
margin: 0;
padding: 0;
}
h1 {
margin-top: 0;
font-size: 20px;
}
table {
margin-top: 0;
}
tr:hover {
background-color: transparent;
}
}
</style>
</head>
<body>
<h1>Статистика каналов</h1>
<table>
<thead>
<tr>
<th>Название</th>
<th>Участники</th>
<th>Сообщения</th>
</tr>
</thead>
<tbody>
{{#each channels}}
<tr>
<td>{{#if this.name}}{{this.name}}{{else}}(Без названия){{/if}}</td>
<td>{{this.members_count}}</td>
<td>{{this.messages_count}}</td>
</tr>
{{/each}}
</tbody>
</table>
</body>
</html>

81
templates/user_report.hbs Normal file
View File

@@ -0,0 +1,81 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Отчет по статистике пользователей</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 20px;
line-height: 1.5;
}
h1 {
text-align: center;
color: #333;
font-size: 24px;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
}
th, td {
border: 1px solid #ddd;
padding: 10px;
text-align: left;
}
th {
background-color: #f2f2f2;
color: #333;
font-weight: bold;
}
tr:nth-child(even) {
background-color: #f9f9f9;
}
tr:hover {
background-color: #f1f1f1;
}
@media print {
body {
margin: 0;
padding: 0;
}
h1 {
margin-top: 0;
font-size: 20px;
}
table {
margin-top: 0;
}
tr:hover {
background-color: transparent;
}
}
</style>
</head>
<body>
<h1>Статистика пользователей</h1>
<table>
<thead>
<tr>
<th>Имя пользователя</th>
<th>Подписчики</th>
<th>Подписки</th>
<th>Каналы</th>
<th>Сообщения</th>
</tr>
</thead>
<tbody>
{{#each users}}
<tr>
<td>{{this.username}}</td>
<td>{{this.followers_count}}</td>
<td>{{this.following_count}}</td>
<td>{{this.channels_count}}</td>
<td>{{this.messages_count}}</td>
</tr>
{{/each}}
</tbody>
</table>
</body>
</html>