Skip to content

Commit aaebacc

Browse files
chore: general cleanup
Signed-off-by: Henry <mail@henrygressmann.de>
1 parent bc079e5 commit aaebacc

62 files changed

Lines changed: 693 additions & 587 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

biome.json

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,23 @@
44
"enabled": true,
55
"actions": {
66
"recommended": true,
7-
"source": { "recommended": true, "organizeImports": "off" }
7+
"source": {
8+
"recommended": true,
9+
"organizeImports": {
10+
"level": "on",
11+
"options": {
12+
"groups": [
13+
"**/*.css",
14+
":BLANK_LINE:",
15+
[":BUN:", ":NODE:", "astro", "astro/**", "@astrojs/**", "react", "react/**", "react-dom", "react-dom/**"],
16+
["d3-*", "d3/**"],
17+
[":PACKAGE_WITH_PROTOCOL:", ":PACKAGE:"],
18+
":BLANK_LINE:",
19+
[":ALIAS:", ":PATH:"]
20+
]
21+
}
22+
}
23+
}
824
}
925
},
1026
"formatter": {
@@ -34,7 +50,13 @@
3450
"noStaticElementInteractions": "off"
3551
},
3652
"style": {
37-
"noDescendingSpecificity": "off"
53+
"noDescendingSpecificity": "off",
54+
"useImportType": {
55+
"level": "on",
56+
"options": {
57+
"style": "separatedType"
58+
}
59+
}
3860
},
3961
"complexity": {
4062
"noImportantStyles": "off"
@@ -58,6 +80,9 @@
5880
"overrides": [
5981
{
6082
"includes": ["**/*.astro"],
83+
"assist": {
84+
"enabled": false
85+
},
6186
"linter": {
6287
"rules": {
6388
"correctness": {

src/app/core.rs

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
pub mod entities;
2-
pub mod events;
3-
pub mod onboarding;
4-
pub mod projects;
1+
mod entities;
2+
mod events;
3+
mod onboarding;
4+
mod projects;
55
pub mod reports;
6-
pub mod sessions;
7-
pub mod settings;
8-
pub mod users;
6+
mod sessions;
7+
mod settings;
8+
mod users;
99

1010
pub use entities::LiwanEntities;
1111
pub use events::{LiwanEvents, PruneStats};
@@ -16,4 +16,7 @@ pub use settings::{LiwanProjectSettings, LiwanSettings};
1616
pub use users::LiwanUsers;
1717

1818
#[cfg(feature = "geoip")]
19-
pub mod geoip;
19+
mod geoip;
20+
21+
#[cfg(feature = "geoip")]
22+
pub use geoip::{LiwanGeoIP, keep_updated};

src/app/core/entities.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ impl LiwanEntities {
5252
Ok(entity.clone())
5353
}
5454

55-
/// Update an entity's projects
55+
/// Update an entity's project memberships
5656
pub fn update_projects(&self, entity_id: &str, project_ids: &[String]) -> Result<()> {
5757
let mut conn = self.pool.get()?;
5858
let tx = conn.transaction()?;
@@ -67,7 +67,7 @@ impl LiwanEntities {
6767
Ok(())
6868
}
6969

70-
/// Delete an entity (does not remove associated events)
70+
/// Delete an entity without removing associated events
7171
pub fn delete(&self, id: &str) -> Result<()> {
7272
let mut conn = self.pool.get()?;
7373
let tx = conn.transaction()?;

src/app/core/events.rs

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ impl LiwanEvents {
3939
Ok(Self { duckdb, sqlite, daily_salt: ArcSwap::new(daily_salt.into()).into(), visitor_group_rotation_hour })
4040
}
4141

42-
/// Get the visitor group salt, generating a new one after the daily local rotation time.
42+
/// Get the visitor group salt, generating a new one after the daily local rotation time
4343
pub fn get_salt(&self) -> Result<String> {
4444
let (salt, updated_at) = &**self.daily_salt.load();
4545

@@ -59,7 +59,7 @@ impl LiwanEvents {
5959
}
6060
}
6161

62-
/// Append events in batch
62+
/// Append events in a batch and update session timing fields when needed
6363
pub fn append(&self, events: impl Iterator<Item = Event>) -> Result<()> {
6464
let conn = self.duckdb.get()?;
6565
let mut first_event_time = None;
@@ -85,7 +85,7 @@ impl LiwanEvents {
8585
Ok(())
8686
}
8787

88-
/// Start processing events from the given channel. Blocks until the channel is closed.
88+
/// Start processing events from the given channel. Blocks until the channel is closed
8989
pub async fn process_events(&self, events_rx: Receiver<Event>) -> Result<()> {
9090
let (tx, rx) = tokio::sync::oneshot::channel();
9191
let events = self.clone();
@@ -139,6 +139,7 @@ impl LiwanEvents {
139139
}
140140
}
141141

142+
/// Preview or apply collection-setting pruning for a single entity
142143
pub fn prune_entity(
143144
&self,
144145
entity_id: &str,
@@ -234,14 +235,11 @@ fn should_rotate_salt(updated_at: DateTime<Utc>, rotation_hour: u8) -> bool {
234235
updated_at < latest_rotation.with_timezone(&Utc)
235236
}
236237

237-
fn count_rows<P>(conn: &Connection, sql: &str, params: P) -> DuckResult<u64>
238-
where
239-
P: duckdb::Params,
240-
{
238+
fn count_rows(conn: &Connection, sql: &str, params: impl duckdb::Params) -> DuckResult<u64> {
241239
conn.query_row(sql, params, |row| row.get(0))
242240
}
243241

244-
pub fn update_event_times(conn: &Connection, from_time: DateTime<Utc>, entities: &[String]) -> DuckResult<()> {
242+
fn update_event_times(conn: &Connection, from_time: DateTime<Utc>, entities: &[String]) -> DuckResult<()> {
245243
if entities.is_empty() {
246244
return Ok(());
247245
}

src/app/core/geoip.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ impl LiwanGeoIP {
6868
}
6969
}
7070

71-
// Lookup the IP address in the GeoIP database
71+
/// Lookup an IP address in the loaded GeoIP database
7272
pub fn lookup(&self, ip: &IpAddr) -> Result<LookupResult> {
7373
let Some(reader) = &*self.reader.load() else {
7474
return Ok(Default::default());
@@ -80,7 +80,7 @@ impl LiwanGeoIP {
8080
Ok(LookupResult { city, country_code })
8181
}
8282

83-
// Check for updates and download the latest database if available
83+
/// Check for updates and download the latest database if available
8484
pub async fn check_for_updates(&self) -> Result<()> {
8585
if self.downloading.swap(true, Ordering::Acquire) {
8686
return Ok(());
@@ -137,6 +137,7 @@ impl LiwanGeoIP {
137137
Ok(())
138138
}
139139

140+
/// Reload the database from disk (as long as no update is currently in progress)
140141
pub fn reload(&self) -> Result<()> {
141142
if self.downloading.load(Ordering::Acquire) {
142143
return Ok(());
@@ -148,6 +149,7 @@ impl LiwanGeoIP {
148149
}
149150
}
150151

152+
/// Keep the GeoIP database refreshed and reload it after local file changes
151153
pub async fn keep_updated(geoip: Arc<LiwanGeoIP>) {
152154
let path: PathBuf = geoip.path.clone();
153155
let mut last_meta = get_file_meta(&path);

src/app/core/projects.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ impl LiwanProjects {
1313
Self { pool }
1414
}
1515

16-
/// Link an entity to a project
16+
/// Replace the entities associated with a project
1717
pub fn update_entities(&self, project_id: &str, entity_ids: &[String]) -> Result<()> {
1818
let mut conn = self.pool.get()?;
1919
let tx = conn.transaction()?;
@@ -122,7 +122,7 @@ impl LiwanProjects {
122122
Ok(project.clone())
123123
}
124124

125-
/// remove the project and all associated `project_entities`
125+
/// Delete a project and its entity memberships
126126
pub fn delete(&self, id: &str) -> Result<()> {
127127
let mut conn = self.pool.get()?;
128128
let tx = conn.transaction()?;

src/app/core/reports/dimension.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use duckdb::params_from_iter;
88
use super::shared::{SESSION_DURATION_SQL, build_filter_clause, metric_aggregate_sql};
99
use super::{DateRange, Dimension, DimensionFilter, Metric, ReportTable};
1010

11+
/// Build a dimension table report for a metric
1112
pub fn dimension_report(
1213
conn: &DuckDBConn,
1314
entities: &[String],

src/app/core/reports/graph.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ fn resolve_local_day_start(timezone: Tz, date: NaiveDate) -> Result<DateTime<Utc
4747
Ok(resolved.with_timezone(&Utc))
4848
}
4949

50+
/// Split a date range into graph buckets aligned to the selected timezone
5051
pub fn build_graph_buckets(
5152
range: &DateRange,
5253
interval: GraphInterval,
@@ -96,6 +97,7 @@ pub fn build_graph_buckets(
9697
Ok(buckets)
9798
}
9899

100+
/// Build a graph report for a metric across precomputed time buckets
99101
pub fn overall_report(
100102
conn: &DuckDBConn,
101103
entities: &[String],

src/app/core/reports/mod.rs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,25 @@ pub use crate::app::models::FilterType;
1717

1818
#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, Hash, PartialEq, Eq)]
1919
pub struct DateRange {
20+
/// Start of the report range
2021
pub start: DateTime<Utc>,
22+
/// End of the report range
2123
pub end: DateTime<Utc>,
2224
}
2325

2426
impl DateRange {
27+
/// Return the immediately preceding range with the same duration
2528
pub fn prev(&self) -> Self {
2629
let duration = self.end - self.start;
2730
Self { start: self.start - duration, end: self.start }
2831
}
2932

33+
/// Return whether the range ends after the current time
3034
pub fn ends_in_future(&self) -> bool {
3135
self.end > Utc::now()
3236
}
3337

38+
/// Return the range duration
3439
pub fn duration(&self) -> chrono::Duration {
3540
self.end - self.start
3641
}
@@ -45,9 +50,13 @@ impl Display for DateRange {
4550
#[derive(Debug, Serialize, Deserialize, JsonSchema, Clone, Copy, PartialEq, Eq, Hash)]
4651
#[serde(rename_all = "snake_case")]
4752
pub enum Metric {
53+
/// Total pageviews
4854
Views,
55+
/// Distinct visitor groups
4956
UniqueVisitors,
57+
/// Percentage of sessions with one pageview
5058
BounceRate,
59+
/// Average time between pageviews in a session
5160
AvgTimeOnSite,
5261
}
5362

@@ -62,33 +71,62 @@ impl Display for Metric {
6271
}
6372
}
6473

74+
impl Metric {
75+
/// Return all report metrics in dashboard order
76+
pub const fn all() -> &'static [Self] {
77+
&[Self::Views, Self::UniqueVisitors, Self::BounceRate, Self::AvgTimeOnSite]
78+
}
79+
}
80+
81+
/// Time bucket size for graph reports
6582
#[derive(Debug, Serialize, Deserialize, JsonSchema, Clone, Copy, PartialEq, Eq, Hash)]
6683
#[serde(rename_all = "snake_case")]
6784
pub enum GraphInterval {
85+
/// Hourly buckets
6886
Hour,
87+
/// Daily buckets
6988
Day,
7089
}
7190

91+
/// Dimension selected for table reports and filters
7292
#[derive(Debug, Serialize, Deserialize, JsonSchema, Clone, Copy, Hash, Eq, PartialEq, PartialOrd, Ord)]
7393
#[serde(rename_all = "snake_case")]
7494
pub enum Dimension {
95+
/// Full tracked URL
7596
Url,
97+
/// First URL in a session
7698
UrlEntry,
99+
/// Last URL in a session
77100
UrlExit,
101+
/// Tracked hostname
78102
Fqdn,
103+
/// Tracked path
79104
Path,
105+
/// Referrer domain
80106
Referrer,
107+
/// Operating system family
81108
Platform,
109+
/// Browser family
82110
Browser,
111+
/// Device type
83112
Mobile,
113+
/// GeoIP country
84114
Country,
115+
/// GeoIP city
85116
City,
117+
/// UTM source
86118
UtmSource,
119+
/// UTM medium
87120
UtmMedium,
121+
/// UTM campaign
88122
UtmCampaign,
123+
/// UTM content
89124
UtmContent,
125+
/// UTM term
90126
UtmTerm,
127+
/// Screen width bucket
91128
ScreenWidth,
129+
/// Screen orientation
92130
Orientation,
93131
}
94132

@@ -117,25 +155,63 @@ impl Display for Dimension {
117155
}
118156
}
119157

158+
impl Dimension {
159+
/// Return all report dimensions in dashboard order
160+
pub const fn all() -> &'static [Self] {
161+
&[
162+
Self::Platform,
163+
Self::Browser,
164+
Self::Url,
165+
Self::UrlEntry,
166+
Self::UrlExit,
167+
Self::Path,
168+
Self::Mobile,
169+
Self::Referrer,
170+
Self::City,
171+
Self::Country,
172+
Self::Fqdn,
173+
Self::UtmCampaign,
174+
Self::UtmContent,
175+
Self::UtmMedium,
176+
Self::UtmSource,
177+
Self::UtmTerm,
178+
Self::ScreenWidth,
179+
Self::Orientation,
180+
]
181+
}
182+
}
183+
184+
/// One point in a graph report
120185
#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)]
121186
#[serde(rename_all = "camelCase")]
122187
pub struct ReportGraphPoint {
188+
/// Start timestamp of the graph bucket
123189
pub bin_start: DateTime<Utc>,
190+
/// Metric value for the graph bucket
124191
pub value: f64,
125192
}
126193

194+
/// Graph report points ordered by bucket start
127195
pub type ReportGraph = Vec<ReportGraphPoint>;
196+
197+
/// Dimension table values mapped to their metric value
128198
pub type ReportTable = BTreeMap<String, f64>;
129199

200+
/// Overall metric summary for a report range
130201
#[derive(Serialize, Deserialize, JsonSchema, Clone, Debug, Default)]
131202
#[serde(rename_all = "camelCase")]
132203
pub struct ReportStats {
204+
/// Total pageviews
133205
pub total_views: u64,
206+
/// Distinct visitor groups
134207
pub unique_visitors: u64,
208+
/// Bounce rate, when session metrics are available
135209
pub bounce_rate: Option<f64>,
210+
/// Average time on site, when session metrics are available
136211
pub avg_time_on_site: Option<f64>,
137212
}
138213

214+
/// Filter applied to a dashboard report query
139215
#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, Hash, Eq, PartialEq, PartialOrd, Ord)]
140216
#[serde(rename_all = "camelCase")]
141217
pub struct DimensionFilter {

0 commit comments

Comments
 (0)