Print stats
This commit is contained in:
1 parent
ef2074f65b
commit
d38be55598
9 files changed
+296
-6
No files matched your search
Generated
+123
@@ -11,6 +11,15 @@ dependencies = [
|
|||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "android_system_properties"
|
||||||
|
version = "0.1.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.104"
|
version = "1.0.104"
|
||||||
@@ -23,6 +32,12 @@ version = "1.1.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
|
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "autocfg"
|
||||||
|
version = "1.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "axum"
|
name = "axum"
|
||||||
version = "0.8.9"
|
version = "0.8.9"
|
||||||
@@ -130,6 +145,20 @@ version = "1.0.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600"
|
checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chrono"
|
||||||
|
version = "0.4.45"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327"
|
||||||
|
dependencies = [
|
||||||
|
"iana-time-zone",
|
||||||
|
"js-sys",
|
||||||
|
"num-traits",
|
||||||
|
"serde",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cmov"
|
name = "cmov"
|
||||||
version = "0.5.4"
|
version = "0.5.4"
|
||||||
@@ -142,6 +171,12 @@ version = "0.10.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
|
checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "core-foundation-sys"
|
||||||
|
version = "0.8.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cpufeatures"
|
name = "cpufeatures"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
@@ -420,6 +455,30 @@ dependencies = [
|
|||||||
"tower-service",
|
"tower-service",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iana-time-zone"
|
||||||
|
version = "0.1.65"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
|
||||||
|
dependencies = [
|
||||||
|
"android_system_properties",
|
||||||
|
"core-foundation-sys",
|
||||||
|
"iana-time-zone-haiku",
|
||||||
|
"js-sys",
|
||||||
|
"log",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"windows-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iana-time-zone-haiku"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.18"
|
version = "1.0.18"
|
||||||
@@ -528,6 +587,15 @@ dependencies = [
|
|||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-traits"
|
||||||
|
version = "0.2.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.21.4"
|
version = "1.21.4"
|
||||||
@@ -641,6 +709,7 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"axum",
|
"axum",
|
||||||
|
"chrono",
|
||||||
"dotenvy",
|
"dotenvy",
|
||||||
"hmac",
|
"hmac",
|
||||||
"secrecy",
|
"secrecy",
|
||||||
@@ -665,6 +734,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "23f2a97da3e3873c73cb2a2e71b35c40ff95e0b1eefa8d72d8499a6928c3b5b3"
|
checksum = "23f2a97da3e3873c73cb2a2e71b35c40ff95e0b1eefa8d72d8499a6928c3b5b3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
|
"chrono",
|
||||||
"fallible-iterator",
|
"fallible-iterator",
|
||||||
"fallible-streaming-iterator",
|
"fallible-streaming-iterator",
|
||||||
"hashlink",
|
"hashlink",
|
||||||
@@ -1155,12 +1225,65 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-core"
|
||||||
|
version = "0.62.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
|
||||||
|
dependencies = [
|
||||||
|
"windows-implement",
|
||||||
|
"windows-interface",
|
||||||
|
"windows-link",
|
||||||
|
"windows-result",
|
||||||
|
"windows-strings",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-implement"
|
||||||
|
version = "0.60.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.119",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-interface"
|
||||||
|
version = "0.59.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.119",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-link"
|
name = "windows-link"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-result"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
|
||||||
|
dependencies = [
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-strings"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
|
||||||
|
dependencies = [
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.61.2"
|
version = "0.61.2"
|
||||||
|
|||||||
+2
-1
@@ -6,6 +6,7 @@ edition = "2024"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = { version = "1.0.104", features = ["backtrace"] }
|
anyhow = { version = "1.0.104", features = ["backtrace"] }
|
||||||
axum = { version = "0.8.9", features = ["macros"] }
|
axum = { version = "0.8.9", features = ["macros"] }
|
||||||
|
chrono = { version = "0.4.45", features = ["serde"] }
|
||||||
dotenvy = "0.15.7"
|
dotenvy = "0.15.7"
|
||||||
hmac = "0.13.0"
|
hmac = "0.13.0"
|
||||||
secrecy = { version = "0.10.3", features = ["serde"] }
|
secrecy = { version = "0.10.3", features = ["serde"] }
|
||||||
@@ -15,7 +16,7 @@ sha2 = "0.11.0"
|
|||||||
tempfile = "3.27.0"
|
tempfile = "3.27.0"
|
||||||
thiserror = "2.0.21"
|
thiserror = "2.0.21"
|
||||||
tokio = { version = "1.53.2", features = ["full"] }
|
tokio = { version = "1.53.2", features = ["full"] }
|
||||||
tokio-rusqlite = { version = "0.8.0", features = ["bundled", "uuid"] }
|
tokio-rusqlite = { version = "0.8.0", features = ["bundled", "uuid", "chrono"] }
|
||||||
tower = { version = "0.5.3", features = ["tracing"] }
|
tower = { version = "0.5.3", features = ["tracing"] }
|
||||||
tower-http = { version = "0.7.1", features = ["trace", "request-id"] }
|
tower-http = { version = "0.7.1", features = ["trace", "request-id"] }
|
||||||
tracing = "0.1.44"
|
tracing = "0.1.44"
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
pub mod users;
|
pub mod users;
|
||||||
pub mod syncs;
|
pub mod syncs;
|
||||||
|
pub mod stats;
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
use axum::{Json, Router, extract::State, routing};
|
||||||
|
use chrono::NaiveDate;
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::{app::AppState, app_error::AppError, auth_user::AuthUser};
|
||||||
|
|
||||||
|
pub fn stats_routes() -> Router<AppState> {
|
||||||
|
Router::new()
|
||||||
|
.route("/contributions", routing::get(get_contributions))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
pub struct ResponseContribution {
|
||||||
|
date: NaiveDate,
|
||||||
|
document: String,
|
||||||
|
progress: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip(st))]
|
||||||
|
pub async fn get_contributions(State(st) : State<AppState>, AuthUser{username} : AuthUser) -> Result<Json<Vec<ResponseContribution>>, AppError> {
|
||||||
|
let cs = st.stats
|
||||||
|
.get_contributions(&username).await?
|
||||||
|
.into_iter().map(|c| ResponseContribution{date: c.date, document: c.document_id, progress: c.progress})
|
||||||
|
.collect();
|
||||||
|
Ok(Json(cs))
|
||||||
|
}
|
||||||
+4
-2
@@ -2,12 +2,13 @@ use std::{path::Path, sync::Arc};
|
|||||||
|
|
||||||
use axum::extract::FromRef;
|
use axum::extract::FromRef;
|
||||||
|
|
||||||
use crate::{config::Config, reader::Reader, repository::{progress_sync::ProgressSync, users::Users}, writer::Writer};
|
use crate::{config::Config, reader::Reader, repository::{progress_sync::ProgressSync, stats::Stats, users::Users}, writer::Writer};
|
||||||
|
|
||||||
#[derive(Clone, FromRef)]
|
#[derive(Clone, FromRef)]
|
||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
pub users: Users,
|
pub users: Users,
|
||||||
pub sync: ProgressSync,
|
pub sync: ProgressSync,
|
||||||
|
pub stats: Stats,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppState {
|
impl AppState {
|
||||||
@@ -16,7 +17,8 @@ impl AppState {
|
|||||||
let reader = Arc::new(Reader::new(&db_path).await?);
|
let reader = Arc::new(Reader::new(&db_path).await?);
|
||||||
let users = Users::new(writer.clone(), reader.clone(), config.server_secret.clone());
|
let users = Users::new(writer.clone(), reader.clone(), config.server_secret.clone());
|
||||||
let sync = ProgressSync::new(writer.clone(), reader.clone());
|
let sync = ProgressSync::new(writer.clone(), reader.clone());
|
||||||
Ok(Self{users, sync})
|
let stats = Stats::new(reader.clone());
|
||||||
|
Ok(Self{users, sync, stats})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
+9
-1
@@ -1,7 +1,7 @@
|
|||||||
use axum::{Json, http::StatusCode, response::IntoResponse};
|
use axum::{Json, http::StatusCode, response::IntoResponse};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::repository::{progress_sync::SyncError, users::UserError};
|
use crate::repository::{progress_sync::SyncError, stats::StatsError, users::UserError};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -36,6 +36,14 @@ impl From<SyncError> for AppError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<StatsError> for AppError {
|
||||||
|
fn from(value: StatsError) -> Self {
|
||||||
|
match value {
|
||||||
|
StatsError::DatabaseFailure(error) => Self::Internal(error.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl IntoResponse for AppError {
|
impl IntoResponse for AppError {
|
||||||
fn into_response(self) -> axum::response::Response {
|
fn into_response(self) -> axum::response::Response {
|
||||||
let (status, error_code, msg) = match self {
|
let (status, error_code, msg) = match self {
|
||||||
|
|||||||
+3
-2
@@ -1,5 +1,5 @@
|
|||||||
use axum::{Json, Router, response::{IntoResponse as _, Response}, routing};
|
use axum::{Json, Router, response::{IntoResponse as _, Response}, routing};
|
||||||
use rukosync::{api::{syncs, users}, app::AppState, config::Config, };
|
use rukosync::{api::{stats, syncs, users}, app::AppState, config::Config, };
|
||||||
use tower_http::trace::TraceLayer;
|
use tower_http::trace::TraceLayer;
|
||||||
use tracing::level_filters::LevelFilter;
|
use tracing::level_filters::LevelFilter;
|
||||||
use tracing_subscriber::{EnvFilter, fmt, layer::SubscriberExt as _, util::SubscriberInitExt as _};
|
use tracing_subscriber::{EnvFilter, fmt, layer::SubscriberExt as _, util::SubscriberInitExt as _};
|
||||||
@@ -8,7 +8,7 @@ use tracing_subscriber::{EnvFilter, fmt, layer::SubscriberExt as _, util::Subscr
|
|||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> anyhow::Result<()> {
|
async fn main() -> anyhow::Result<()> {
|
||||||
let level_filter = EnvFilter::builder()
|
let level_filter = EnvFilter::builder()
|
||||||
.with_default_directive(LevelFilter::DEBUG.into())
|
.with_default_directive(LevelFilter::INFO.into())
|
||||||
.from_env_lossy();
|
.from_env_lossy();
|
||||||
tracing_subscriber::registry()
|
tracing_subscriber::registry()
|
||||||
.with(fmt::layer())
|
.with(fmt::layer())
|
||||||
@@ -25,6 +25,7 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
.route("/healthcheck", routing::get(healthcheck))
|
.route("/healthcheck", routing::get(healthcheck))
|
||||||
.nest("/users", users::users_routes())
|
.nest("/users", users::users_routes())
|
||||||
.nest("/syncs", syncs::syncs_routes())
|
.nest("/syncs", syncs::syncs_routes())
|
||||||
|
.nest("/stats", stats::stats_routes())
|
||||||
.with_state(app_state)
|
.with_state(app_state)
|
||||||
.layer(TraceLayer::new_for_http());
|
.layer(TraceLayer::new_for_http());
|
||||||
let address = format!("0.0.0.0:{}", config.port);
|
let address = format!("0.0.0.0:{}", config.port);
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
pub mod progress_sync;
|
pub mod progress_sync;
|
||||||
pub mod users;
|
pub mod users;
|
||||||
|
pub mod stats;
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use chrono::NaiveDate;
|
||||||
|
use thiserror::Error;
|
||||||
|
use tokio_rusqlite::{named_params, rusqlite};
|
||||||
|
|
||||||
|
use crate::reader::Reader;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Stats {
|
||||||
|
reader: Arc<Reader>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Github style contributions
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct Contribution {
|
||||||
|
pub date: NaiveDate,
|
||||||
|
pub start_percentage: f32,
|
||||||
|
pub end_percentage: f32,
|
||||||
|
pub progress: f32,
|
||||||
|
pub document_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum StatsError {
|
||||||
|
#[error("Database call failed {0}")]
|
||||||
|
DatabaseFailure(#[from] tokio_rusqlite::Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Stats {
|
||||||
|
pub fn new(reader: Arc<Reader>) -> Self {
|
||||||
|
Self{reader}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_contributions(&self, username : &str) -> Result<Vec<Contribution>, StatsError> {
|
||||||
|
let username = username.to_owned();
|
||||||
|
let cs = self.reader.connection.call(move |conn| -> rusqlite::Result<Vec<Contribution>> {
|
||||||
|
let mut stmt = conn.prepare("select
|
||||||
|
date(progress.timestamp, 'unixepoch') as day,
|
||||||
|
progress.document_id,
|
||||||
|
min(progress.percentage) as start_percentage,
|
||||||
|
max(progress.percentage) as end_percentage,
|
||||||
|
max(progress.percentage) - min(progress.percentage) as progress
|
||||||
|
from progress
|
||||||
|
join users on users.id = progress.user_id
|
||||||
|
where users.username = :username
|
||||||
|
group by day, progress.document_id
|
||||||
|
order by day")?;
|
||||||
|
let x = stmt.query_map(named_params!{":username": username}, |row| {
|
||||||
|
let date = row.get(0)?;
|
||||||
|
let document_id = row.get(1)?;
|
||||||
|
let start_percentage = row.get(2)?;
|
||||||
|
let end_percentage = row.get(3)?;
|
||||||
|
let progress = row.get(4)?;
|
||||||
|
Ok(Contribution{date, start_percentage, end_percentage, progress, document_id})
|
||||||
|
})?.collect::<rusqlite::Result<Vec<_>>>()?;
|
||||||
|
Ok(x)
|
||||||
|
}).await?;
|
||||||
|
Ok(cs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
|
||||||
|
use secrecy::SecretString;
|
||||||
|
use tempfile::{TempDir, tempdir};
|
||||||
|
|
||||||
|
use crate::{reader::Reader, repository::{progress_sync::{Document, ProgressSync}, stats::Stats, users::Users}, writer::Writer};
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
const USERNAME : &str = "foo@example.com";
|
||||||
|
|
||||||
|
async fn create_repositories() -> anyhow::Result<(TempDir, ProgressSync, Stats)> {
|
||||||
|
let temp_dir = tempdir()?;
|
||||||
|
let path = temp_dir.path().join("kosync.db");
|
||||||
|
let writer = Arc::new(Writer::new(&path).await?);
|
||||||
|
let reader = Arc::new(Reader::new(&path).await?);
|
||||||
|
let users = Users::new(writer.clone(), reader.clone(), SecretString::from("secret"));
|
||||||
|
let sync = ProgressSync::new(writer.clone(), reader.clone());
|
||||||
|
let stats = Stats::new(reader.clone());
|
||||||
|
users.create_user(USERNAME, SecretString::from("password")).await?;
|
||||||
|
Ok((temp_dir, sync, stats))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_empty_progress() -> anyhow::Result<()> {
|
||||||
|
let (_t, _, stats) = create_repositories().await?;
|
||||||
|
let contributions = stats.get_contributions(USERNAME).await?;
|
||||||
|
assert_eq!(contributions, vec![]);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn now() -> i64 {
|
||||||
|
chrono::Utc::now().timestamp()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_single_read() -> anyhow::Result<()> {
|
||||||
|
let (_t, sync, stats) = create_repositories().await?;
|
||||||
|
let document_id = "foo".to_string();
|
||||||
|
let doc1 = Document {
|
||||||
|
document_id: document_id.clone(),
|
||||||
|
progress: "prog".to_string(),
|
||||||
|
percentage: 0.5,
|
||||||
|
device: "kobo".to_string(),
|
||||||
|
timestamp: now(),
|
||||||
|
filename: Some("foo.epub".to_string()),
|
||||||
|
title: Some("A Book".to_string()),
|
||||||
|
author: Some("A.N. Author".to_string()),
|
||||||
|
..Document::default()
|
||||||
|
};
|
||||||
|
sync.set_progress(USERNAME, &doc1).await?;
|
||||||
|
let contributions = stats.get_contributions(USERNAME).await?;
|
||||||
|
let today = chrono::Utc::now().date_naive();
|
||||||
|
let wanted = Contribution {
|
||||||
|
date: today,
|
||||||
|
start_percentage: 0.5,
|
||||||
|
end_percentage: 0.5,
|
||||||
|
progress: 0.0,
|
||||||
|
document_id: document_id.clone()
|
||||||
|
};
|
||||||
|
assert_eq!(contributions, vec![wanted]);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user