diff --git a/.gitignore b/.gitignore index 55b481fa..7ed3d28b 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ logs/* # Database db/activities.pace.sqlite3 +/sea-orm \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 6598ef89..58545d0b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1317,6 +1317,7 @@ dependencies = [ "pace_cli", "pace_core", "pace_error", + "pace_service", "pace_storage", "pace_time", "predicates", @@ -1336,10 +1337,19 @@ version = "0.4.5" dependencies = [ "chrono", "chrono-tz", + "clap", "dialoguer", "eyre", "getset", + "open", "pace_core", + "pace_error", + "pace_service", + "pace_time", + "serde", + "serde_derive", + "serde_json", + "tera", "tracing", "typed-builder", ] @@ -1359,7 +1369,6 @@ dependencies = [ "itertools", "merge", "once_cell", - "open", "pace_error", "pace_time", "parking_lot", @@ -1379,7 +1388,6 @@ dependencies = [ "tracing", "typed-builder", "ulid", - "wildmatch", ] [[package]] @@ -1402,6 +1410,19 @@ dependencies = [ name = "pace_server" version = "0.1.3" +[[package]] +name = "pace_service" +version = "0.1.0" +dependencies = [ + "getset", + "pace_core", + "pace_error", + "pace_time", + "tracing", + "typed-builder", + "wildmatch", +] + [[package]] name = "pace_storage" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 2892abd7..ff0a2ee4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ members = [ "crates/core", "crates/error", "crates/server", + "crates/service", "crates/storage", "crates/time", ] @@ -47,6 +48,7 @@ open = "5.1.2" pace_cli = { path = "crates/cli", version = "0" } pace_core = { path = "crates/core", version = "0" } pace_error = { path = "crates/error", version = "0" } +pace_service = { path = "crates/service", version = "0" } pace_storage = { path = "crates/storage", version = "0" } pace_time = { path = "crates/time", version = "0" } parking_lot = "0.12.1" @@ -113,6 +115,7 @@ human-panic = { workspace = true } pace_cli = { workspace = true } pace_core = { workspace = true, features = ["cli"] } pace_error = { workspace = true } +pace_service = { workspace = true } pace_storage = { workspace = true } pace_time = { workspace = true, features = ["cli", "db"] } serde = { workspace = true } diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index a15b5128..07424521 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -18,14 +18,27 @@ include = [ "src/**/*", "Cargo.toml", ] +[features] +default = ["cli"] +cli = ["clap"] +clap = ["dep:clap"] [dependencies] chrono = { workspace = true } chrono-tz = { workspace = true } +clap = { workspace = true, optional = true, features = ["env", "wrap_help", "derive"] } dialoguer = { workspace = true, features = ["fuzzy-select"] } eyre = { workspace = true } getset = { workspace = true } +open = { workspace = true } pace_core = { workspace = true } +pace_error = { workspace = true } +pace_service = { workspace = true } +pace_time = { workspace = true } +serde = { workspace = true } +serde_derive = { workspace = true } +serde_json = { workspace = true } +tera = { workspace = true } tracing = { workspace = true } typed-builder = { workspace = true } diff --git a/crates/cli/src/commands.rs b/crates/cli/src/commands.rs new file mode 100644 index 00000000..1c188309 --- /dev/null +++ b/crates/cli/src/commands.rs @@ -0,0 +1,8 @@ +pub mod adjust; +pub mod begin; +pub mod docs; +pub mod end; +pub mod hold; +pub mod now; +pub mod reflect; +pub mod resume; diff --git a/crates/core/src/commands/adjust.rs b/crates/cli/src/commands/adjust.rs similarity index 98% rename from crates/core/src/commands/adjust.rs rename to crates/cli/src/commands/adjust.rs index 37e9caaf..c7915652 100644 --- a/crates/core/src/commands/adjust.rs +++ b/crates/cli/src/commands/adjust.rs @@ -5,20 +5,18 @@ use chrono_tz::Tz; #[cfg(feature = "clap")] use clap::Parser; use getset::Getters; -use pace_time::{date_time::PaceDateTime, time_zone::PaceTimeZoneKind, Validate}; use tracing::debug; use typed_builder::TypedBuilder; -use pace_error::{ActivityLogErrorKind, PaceResult, UserMessage}; - -use crate::{ - commands::UpdateOptions, +use pace_core::{ config::PaceConfig, - domain::{category::PaceCategory, description::PaceDescription}, - prelude::PaceTagCollection, - service::activity_store::ActivityStore, + domain::{category::PaceCategory, description::PaceDescription, tag::PaceTagCollection}, + options::UpdateOptions, storage::{ActivityQuerying, ActivityStorage, ActivityWriteOps, SyncStorage}, }; +use pace_error::{ActivityLogErrorKind, PaceResult, UserMessage}; +use pace_service::activity_store::ActivityStore; +use pace_time::{date_time::PaceDateTime, time_zone::PaceTimeZoneKind, Validate}; /// `adjust` subcommand options #[derive(Debug, Clone, PartialEq, TypedBuilder, Eq, Hash, Default, Getters)] diff --git a/crates/core/src/commands/begin.rs b/crates/cli/src/commands/begin.rs similarity index 97% rename from crates/core/src/commands/begin.rs rename to crates/cli/src/commands/begin.rs index 1733ee54..7836776a 100644 --- a/crates/core/src/commands/begin.rs +++ b/crates/cli/src/commands/begin.rs @@ -5,22 +5,21 @@ use chrono_tz::Tz; #[cfg(feature = "clap")] use clap::Parser; use getset::Getters; -use pace_time::{date_time::PaceDateTime, time_zone::PaceTimeZoneKind, Validate}; use tracing::debug; -use crate::{ +use pace_core::{ config::PaceConfig, domain::{ activity::{Activity, ActivityKind}, category::PaceCategory, description::PaceDescription, + tag::PaceTagCollection, }, - prelude::PaceTagCollection, - service::activity_store::ActivityStore, storage::{ActivityStateManagement, ActivityStorage, SyncStorage}, }; - use pace_error::{PaceResult, UserMessage}; +use pace_service::activity_store::ActivityStore; +use pace_time::{date_time::PaceDateTime, time_zone::PaceTimeZoneKind, Validate}; /// `begin` subcommand options #[derive(Debug, Clone, PartialEq, Eq, Getters)] diff --git a/crates/core/src/commands/docs.rs b/crates/cli/src/commands/docs.rs similarity index 99% rename from crates/core/src/commands/docs.rs rename to crates/cli/src/commands/docs.rs index 9924b6ab..3c6b5eb3 100644 --- a/crates/core/src/commands/docs.rs +++ b/crates/cli/src/commands/docs.rs @@ -1,7 +1,7 @@ #[cfg(feature = "clap")] use clap::Parser; -use crate::{ +use pace_core::{ constants::PACE_DOCS_URL, constants::{PACE_CONFIG_DOCS_URL, PACE_DEV_DOCS_URL}, }; diff --git a/crates/core/src/commands/end.rs b/crates/cli/src/commands/end.rs similarity index 97% rename from crates/core/src/commands/end.rs rename to crates/cli/src/commands/end.rs index 5e7bbd4a..14dfb608 100644 --- a/crates/core/src/commands/end.rs +++ b/crates/cli/src/commands/end.rs @@ -5,18 +5,17 @@ use chrono_tz::Tz; #[cfg(feature = "clap")] use clap::Parser; use getset::Getters; -use pace_time::{date_time::PaceDateTime, time_zone::PaceTimeZoneKind, Validate}; use tracing::debug; use typed_builder::TypedBuilder; -use crate::{ - commands::EndOptions, +use pace_core::{ config::PaceConfig, - service::activity_store::ActivityStore, + options::EndOptions, storage::{ActivityStateManagement, ActivityStorage, SyncStorage}, }; - use pace_error::{PaceResult, UserMessage}; +use pace_service::activity_store::ActivityStore; +use pace_time::{date_time::PaceDateTime, time_zone::PaceTimeZoneKind, Validate}; /// `end` subcommand options #[derive(Debug, Clone, PartialEq, TypedBuilder, Eq, Hash, Default, Getters)] diff --git a/crates/core/src/commands/hold.rs b/crates/cli/src/commands/hold.rs similarity index 86% rename from crates/core/src/commands/hold.rs rename to crates/cli/src/commands/hold.rs index 65b791b4..57002953 100644 --- a/crates/core/src/commands/hold.rs +++ b/crates/cli/src/commands/hold.rs @@ -4,20 +4,17 @@ use chrono::{FixedOffset, NaiveTime}; use chrono_tz::Tz; #[cfg(feature = "clap")] use clap::Parser; - -use getset::Getters; -use pace_time::{date_time::PaceDateTime, time_zone::PaceTimeZoneKind, Validate}; use tracing::debug; -use typed_builder::TypedBuilder; -use crate::{ +use pace_core::{ config::PaceConfig, domain::{description::PaceDescription, intermission::IntermissionAction}, - service::activity_store::ActivityStore, + options::HoldOptions, storage::{ActivityStateManagement, ActivityStorage, SyncStorage}, }; - use pace_error::{PaceResult, UserMessage}; +use pace_service::activity_store::ActivityStore; +use pace_time::{date_time::PaceDateTime, time_zone::PaceTimeZoneKind, Validate}; /// `hold` subcommand options #[derive(Debug)] @@ -135,21 +132,3 @@ impl HoldCommandOptions { Ok(UserMessage::new(user_message)) } } - -/// Options for holding an activity -#[derive(Debug, Clone, PartialEq, TypedBuilder, Eq, Hash, Default, Getters)] -#[getset(get = "pub")] -#[non_exhaustive] -pub struct HoldOptions { - /// The action to take on the intermission - #[builder(default)] - action: IntermissionAction, - - /// The start time of the intermission - #[builder(default, setter(into))] - begin_time: PaceDateTime, - - /// The reason for holding the activity - #[builder(default, setter(into))] - reason: Option, -} diff --git a/crates/core/src/commands/now.rs b/crates/cli/src/commands/now.rs similarity index 96% rename from crates/core/src/commands/now.rs rename to crates/cli/src/commands/now.rs index feb0b852..c9c12f8f 100644 --- a/crates/core/src/commands/now.rs +++ b/crates/cli/src/commands/now.rs @@ -4,14 +4,13 @@ use std::sync::Arc; use clap::Parser; use tracing::debug; -use crate::{ +use pace_core::{ config::PaceConfig, domain::{activity::ActivityItem, filter::ActivityFilterKind}, - service::activity_store::ActivityStore, storage::{ActivityQuerying, ActivityReadOps, ActivityStorage}, }; - use pace_error::{PaceResult, UserMessage}; +use pace_service::activity_store::ActivityStore; /// `now` subcommand options #[derive(Debug)] diff --git a/crates/core/src/commands/reflect.rs b/crates/cli/src/commands/reflect.rs similarity index 92% rename from crates/core/src/commands/reflect.rs rename to crates/cli/src/commands/reflect.rs index a8ab8ecf..cc6bc983 100644 --- a/crates/core/src/commands/reflect.rs +++ b/crates/cli/src/commands/reflect.rs @@ -1,28 +1,25 @@ #[cfg(feature = "clap")] use clap::Parser; use getset::{Getters, MutGetters, Setters}; -use pace_time::{ - flags::{DateFlags, TimeFlags}, - time_frame::PaceTimeFrame, - time_zone::PaceTimeZoneKind, -}; use serde_derive::Serialize; use std::{path::PathBuf, sync::Arc}; use tracing::debug; use typed_builder::TypedBuilder; -use crate::{ +use pace_core::{ config::PaceConfig, - domain::{ - activity::ActivityKind, category::PaceCategory, filter::FilterOptions, - reflection::ReflectionsFormatKind, - }, - prelude::ActivityStorage, - service::{activity_store::ActivityStore, activity_tracker::ActivityTracker}, + domain::{activity::ActivityKind, category::PaceCategory, reflection::ReflectionsFormatKind}, + options::FilterOptions, + storage::ActivityStorage, template::{PaceReflectionTemplate, TEMPLATES}, }; - use pace_error::{PaceResult, TemplatingErrorKind, UserMessage}; +use pace_service::{activity_store::ActivityStore, activity_tracker::ActivityTracker}; +use pace_time::{ + flags::{DateFlags, TimeFlags}, + time_frame::PaceTimeFrame, + time_zone::PaceTimeZoneKind, +}; /// `reflect` subcommand options #[derive(Debug, Getters)] @@ -138,6 +135,8 @@ impl ReflectCommandOptions { date_flags, template_file, output_format, + category, + case_sensitive, // time_zone, // time_zone_offset, .. // TODO: ignore the rest of the fields for now, @@ -157,8 +156,12 @@ impl ReflectCommandOptions { debug!("Displaying reflection for time frame: {}", time_frame); - let Some(reflection) = - activity_tracker.generate_reflection(FilterOptions::from(self), time_frame)? + let filter_opts = FilterOptions::builder() + .category(category.clone()) + .case_sensitive(*case_sensitive) + .build(); + + let Some(reflection) = activity_tracker.generate_reflection(filter_opts, time_frame)? else { return Ok(UserMessage::new( "No activities found for the specified time frame", @@ -186,7 +189,6 @@ impl ReflectCommandOptions { return Ok(UserMessage::new(json)); } - Some(ReflectionsFormatKind::Template) => { let context = PaceReflectionTemplate::from(reflection).into_context(); @@ -221,6 +223,7 @@ impl ReflectCommandOptions { return Ok(UserMessage::new(templated)); } Some(ReflectionsFormatKind::Csv) => unimplemented!("CSV format not yet supported"), + _ => unimplemented!("Unsupported output format"), } } } diff --git a/crates/core/src/commands/resume.rs b/crates/cli/src/commands/resume.rs similarity index 83% rename from crates/core/src/commands/resume.rs rename to crates/cli/src/commands/resume.rs index 41d3ffb6..8dd63f86 100644 --- a/crates/core/src/commands/resume.rs +++ b/crates/cli/src/commands/resume.rs @@ -3,7 +3,6 @@ use chrono_tz::Tz; #[cfg(feature = "clap")] use clap::Parser; use getset::Getters; -use pace_time::date_time::PaceDateTime; use typed_builder::TypedBuilder; /// `resume` subcommand options @@ -56,13 +55,3 @@ impl ResumeCommandOptions { // FIXME: Inner run implementation for the resume command kept in pace-rs crate for now // FIXME: due to the dependency on pace-cli } - -/// Options for resuming an activity -#[derive(Debug, Clone, PartialEq, TypedBuilder, Eq, Hash, Default, Getters)] -#[getset(get = "pub")] -#[non_exhaustive] -pub struct ResumeOptions { - /// The resume time of the intermission - #[builder(default, setter(into))] - resume_time: Option, -} diff --git a/crates/cli/src/lib.rs b/crates/cli/src/lib.rs index 2b1d7967..89276d17 100644 --- a/crates/cli/src/lib.rs +++ b/crates/cli/src/lib.rs @@ -1,14 +1,10 @@ //! `pace_cli` contains utilities for the `pace` command-line interface /// Contains the main logic for prompting the user for input -pub(crate) mod prompt; +pub mod prompt; /// Contains the main logic for the `setup` command -pub(crate) mod setup; +pub mod setup; -pub(crate) static PACE_ART: &str = include_str!("pace.art"); +pub mod commands; -// Public API -pub use crate::{ - prompt::{confirmation_or_break, prompt_resume_activity, prompt_time_zone}, - setup::{setup_config, PathOptions}, -}; +pub(crate) static PACE_ART: &str = include_str!("pace.art"); diff --git a/crates/cli/src/prompt.rs b/crates/cli/src/prompt.rs index 4eade99a..102eb345 100644 --- a/crates/cli/src/prompt.rs +++ b/crates/cli/src/prompt.rs @@ -111,7 +111,7 @@ pub fn prompt_config_file_path( /// # Returns /// /// Returns `Ok(())` if the user confirms their choices -pub fn confirmation_or_break(prompt: &str) -> Result<()> { +pub fn confirmation_or_break_default_false(prompt: &str) -> Result<()> { let confirmation = Confirm::with_theme(&ColorfulTheme::default()) .with_prompt(prompt) .default(false) @@ -124,6 +124,33 @@ pub fn confirmation_or_break(prompt: &str) -> Result<()> { Ok(()) } +/// Prompts the user to confirm their choices or break the setup assistant +/// +/// # Arguments +/// +/// * `prompt` - The prompt to display to the user +/// +/// # Errors +/// +/// Returns an error if the wants to break the setup assistant or +/// if the prompt fails +/// +/// # Returns +/// +/// Returns `Ok(())` if the user confirms their choices +pub fn confirmation_or_break_default_true(prompt: &str) -> Result<()> { + let confirmation = Confirm::with_theme(&ColorfulTheme::default()) + .with_prompt(prompt) + .default(true) + .interact()?; + + if !confirmation { + eyre::bail!("Setup exited without changes. No changes were made."); + } + + Ok(()) +} + /// Prompts the user to select an activity to resume /// /// # Arguments diff --git a/crates/cli/src/setup.rs b/crates/cli/src/setup.rs index cff7f7ca..043f9840 100644 --- a/crates/cli/src/setup.rs +++ b/crates/cli/src/setup.rs @@ -22,8 +22,11 @@ use pace_core::{ }; use crate::{ - prompt::{prompt_activity_log_path, prompt_config_file_path}, - prompt_time_zone, PACE_ART, + prompt::{ + confirmation_or_break_default_true, prompt_activity_log_path, prompt_config_file_path, + prompt_time_zone, + }, + PACE_ART, }; #[derive(Debug, TypedBuilder, Getters)] @@ -279,33 +282,6 @@ Use Q, ESC, or Ctrl-C to exit gracefully at any time."; Ok(()) } -/// Prompts the user to confirm their choices or break the setup assistant -/// -/// # Arguments -/// -/// * `prompt` - The prompt to display to the user -/// -/// # Errors -/// -/// Returns an error if the wants to break the setup assistant or -/// if the prompt fails -/// -/// # Returns -/// -/// Returns `Ok(())` if the user confirms their choices -pub fn confirmation_or_break(prompt: &str) -> Result<()> { - let confirmation = Confirm::with_theme(&ColorfulTheme::default()) - .with_prompt(prompt) - .default(true) - .interact()?; - - if !confirmation { - eyre::bail!("Setup exited without changes. No changes were made."); - } - - Ok(()) -} - /// The `setup` commands interior for the pace application /// /// # Arguments @@ -355,7 +331,7 @@ pub fn setup_config(term: &Term, path_opts: &PathOptions) -> Result<()> { let prompt = "Do you want the files to be written?"; - confirmation_or_break(prompt)?; + confirmation_or_break_default_true(prompt)?; term.clear_screen()?; diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index dbac03be..3bf61189 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -37,7 +37,6 @@ getset = { workspace = true } itertools = { workspace = true } merge = { workspace = true } once_cell = { workspace = true } -open = { workspace = true } pace_error = { workspace = true } pace_time = { workspace = true, features = ["rusqlite"] } parking_lot = { workspace = true, features = ["deadlock_detection"] } @@ -54,7 +53,6 @@ toml = { workspace = true, features = ["indexmap", "preserve_order"] } tracing = { workspace = true } typed-builder = { workspace = true } ulid = { workspace = true, features = ["serde"] } -wildmatch = { workspace = true } [dev-dependencies] eyre = { workspace = true } diff --git a/crates/core/src/domain/activity.rs b/crates/core/src/domain/activity.rs index 10b547fb..859a7634 100644 --- a/crates/core/src/domain/activity.rs +++ b/crates/core/src/domain/activity.rs @@ -17,8 +17,8 @@ use tracing::debug; use typed_builder::TypedBuilder; use crate::domain::{ - category::PaceCategory, description::PaceDescription, id::Guid, status::ActivityStatusKind, - tag::PaceTagCollection, + category::PaceCategory, description::PaceDescription, id::ActivityGuid, + status::ActivityStatusKind, tag::PaceTagCollection, }; use pace_error::{ActivityLogErrorKind, PaceResult}; @@ -314,39 +314,6 @@ impl ActivityKindOptions { } } -/// The unique identifier of an activity -#[derive(Debug, Clone, Serialize, Deserialize, Ord, PartialEq, PartialOrd, Eq, Copy, Hash)] -pub struct ActivityGuid(Guid); - -impl ActivityGuid { - #[must_use] - pub fn new() -> Self { - Self(Guid::new()) - } - - #[must_use] - pub const fn with_id(id: Guid) -> Self { - Self(id) - } - - #[must_use] - pub const fn inner(&self) -> &Guid { - &self.0 - } -} - -impl Display for ActivityGuid { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - self.0.fmt(f) - } -} - -impl Default for ActivityGuid { - fn default() -> Self { - Self(Guid::new()) - } -} - impl Display for Activity { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let time = self.begin.and_local_timezone(&Local); diff --git a/crates/core/src/domain/activity_log.rs b/crates/core/src/domain/activity_log.rs index 157219d5..b64f2ab4 100644 --- a/crates/core/src/domain/activity_log.rs +++ b/crates/core/src/domain/activity_log.rs @@ -3,7 +3,10 @@ use rayon::iter::{FromParallelIterator, IntoParallelIterator}; use serde_derive::{Deserialize, Serialize}; use std::collections::BTreeMap; -use crate::domain::activity::{Activity, ActivityGuid, ActivityItem}; +use crate::domain::{ + activity::{Activity, ActivityItem}, + id::ActivityGuid, +}; /// The activity log entity /// diff --git a/crates/core/src/domain/category.rs b/crates/core/src/domain/category.rs index 84417ee6..2626827e 100644 --- a/crates/core/src/domain/category.rs +++ b/crates/core/src/domain/category.rs @@ -5,10 +5,7 @@ use std::{convert::Infallible, str::FromStr}; use serde_derive::{Deserialize, Serialize}; use typed_builder::TypedBuilder; -use crate::{ - config::GeneralConfig, - domain::{description::PaceDescription, id::Guid}, -}; +use crate::{config::GeneralConfig, domain::description::PaceDescription, prelude::CategoryGuid}; #[derive(Debug, Serialize, Deserialize, Clone, Eq, Hash, PartialEq, Default, PartialOrd, Ord)] pub struct PaceCategory(String); @@ -143,16 +140,6 @@ pub fn split_category_by_category_separator( } } -/// The category id -#[derive(Debug, Serialize, Deserialize, Clone, Copy)] -pub struct CategoryGuid(Guid); - -impl Default for CategoryGuid { - fn default() -> Self { - Self(Guid::new()) - } -} - impl Default for NewCategory { fn default() -> Self { Self { diff --git a/crates/core/src/domain/filter.rs b/crates/core/src/domain/filter.rs index e7d4790f..61c3c96c 100644 --- a/crates/core/src/domain/filter.rs +++ b/crates/core/src/domain/filter.rs @@ -1,13 +1,8 @@ -use getset::{Getters, MutGetters, Setters}; -use pace_time::time_range::TimeRangeOptions; -use serde_derive::Serialize; use strum::EnumIter; -use typed_builder::TypedBuilder; -use crate::{ - commands::reflect::ReflectCommandOptions, - domain::{activity::ActivityGuid, category::PaceCategory}, -}; +use pace_time::time_range::TimeRangeOptions; + +use crate::domain::id::ActivityGuid; /// Filter for activities #[derive(Debug, Clone, Copy, PartialEq, Eq, Default, EnumIter)] @@ -89,29 +84,3 @@ impl FilteredActivities { } } } - -#[derive( - Debug, TypedBuilder, Serialize, Getters, Setters, MutGetters, Clone, Eq, PartialEq, Default, -)] -#[getset(get = "pub")] -pub struct FilterOptions { - category: Option, - case_sensitive: bool, -} - -impl From for FilterOptions { - fn from(options: ReflectCommandOptions) -> Self { - Self { - category: options.category().clone(), - case_sensitive: *options.case_sensitive(), - } - } -} -impl From<&ReflectCommandOptions> for FilterOptions { - fn from(options: &ReflectCommandOptions) -> Self { - Self { - category: options.category().clone(), - case_sensitive: *options.case_sensitive(), - } - } -} diff --git a/crates/core/src/domain/id.rs b/crates/core/src/domain/id.rs index e0270533..d862fd24 100644 --- a/crates/core/src/domain/id.rs +++ b/crates/core/src/domain/id.rs @@ -1,4 +1,7 @@ -use std::{fmt::Display, str::FromStr}; +use std::{ + fmt::{Display, Formatter}, + str::FromStr, +}; use pace_error::PaceErrorKind; use serde_derive::{Deserialize, Serialize}; @@ -8,6 +11,7 @@ use ulid::Ulid; pub struct Guid(Ulid); impl Guid { + #[must_use] pub fn new() -> Self { Self(Ulid::new()) } @@ -33,7 +37,205 @@ impl FromStr for Guid { } impl Display for Guid { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "Ulid: {}", self.0.to_string()) } } + +/// The unique identifier of an activity +#[derive(Debug, Clone, Serialize, Deserialize, Ord, PartialEq, PartialOrd, Eq, Copy, Hash)] +pub struct ActivityGuid(Guid); + +impl ActivityGuid { + #[must_use] + pub fn new() -> Self { + Self(Guid::new()) + } + + #[must_use] + pub const fn with_id(id: Guid) -> Self { + Self(id) + } + + #[must_use] + pub const fn inner(&self) -> &Guid { + &self.0 + } +} + +impl Display for ActivityGuid { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} + +impl Default for ActivityGuid { + fn default() -> Self { + Self(Guid::new()) + } +} + +/// The unique identifier of a category +#[derive(Debug, Clone, Serialize, Deserialize, Ord, PartialEq, PartialOrd, Eq, Copy, Hash)] +pub struct CategoryGuid(Guid); + +impl CategoryGuid { + #[must_use] + pub fn new() -> Self { + Self(Guid::new()) + } + + #[must_use] + pub const fn with_id(id: Guid) -> Self { + Self(id) + } + + #[must_use] + pub const fn inner(&self) -> &Guid { + &self.0 + } +} + +impl Display for CategoryGuid { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} + +impl Default for CategoryGuid { + fn default() -> Self { + Self(Guid::new()) + } +} + +/// The unique identifier of a description +#[derive(Debug, Clone, Serialize, Deserialize, Ord, PartialEq, PartialOrd, Eq, Copy, Hash)] +pub struct DescriptionGuid(Guid); + +impl DescriptionGuid { + #[must_use] + pub fn new() -> Self { + Self(Guid::new()) + } + + #[must_use] + pub const fn with_id(id: Guid) -> Self { + Self(id) + } + + #[must_use] + pub const fn inner(&self) -> &Guid { + &self.0 + } +} + +impl Display for DescriptionGuid { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} + +impl Default for DescriptionGuid { + fn default() -> Self { + Self(Guid::new()) + } +} + +/// The unique identifier of an activity kind +#[derive(Debug, Clone, Serialize, Deserialize, Ord, PartialEq, PartialOrd, Eq, Copy, Hash)] +pub struct ActivityKindGuid(Guid); + +impl ActivityKindGuid { + #[must_use] + pub fn new() -> Self { + Self(Guid::new()) + } + + #[must_use] + pub const fn with_id(id: Guid) -> Self { + Self(id) + } + + #[must_use] + pub const fn inner(&self) -> &Guid { + &self.0 + } +} + +impl Display for ActivityKindGuid { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} + +impl Default for ActivityKindGuid { + fn default() -> Self { + Self(Guid::new()) + } +} + +/// The unique identifier of an activity status +#[derive(Debug, Clone, Serialize, Deserialize, Ord, PartialEq, PartialOrd, Eq, Copy, Hash)] +pub struct ActivityStatusGuid(Guid); + +impl ActivityStatusGuid { + #[must_use] + pub fn new() -> Self { + Self(Guid::new()) + } + + #[must_use] + pub const fn with_id(id: Guid) -> Self { + Self(id) + } + + #[must_use] + pub const fn inner(&self) -> &Guid { + &self.0 + } +} + +impl Display for ActivityStatusGuid { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} + +impl Default for ActivityStatusGuid { + fn default() -> Self { + Self(Guid::new()) + } +} + +/// The unique identifier of a tag +#[derive(Debug, Clone, Serialize, Deserialize, Ord, PartialEq, PartialOrd, Eq, Copy, Hash)] +pub struct TagGuid(Guid); + +impl TagGuid { + #[must_use] + pub fn new() -> Self { + Self(Guid::new()) + } + + #[must_use] + pub const fn with_id(id: Guid) -> Self { + Self(id) + } + + #[must_use] + pub const fn inner(&self) -> &Guid { + &self.0 + } +} + +impl Display for TagGuid { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} + +impl Default for TagGuid { + fn default() -> Self { + Self(Guid::new()) + } +} diff --git a/crates/core/src/domain/tag.rs b/crates/core/src/domain/tag.rs index 22d85314..d704a653 100644 --- a/crates/core/src/domain/tag.rs +++ b/crates/core/src/domain/tag.rs @@ -1,10 +1,11 @@ use typed_builder::TypedBuilder; -use ulid::Ulid; use std::{collections::HashSet, convert::Infallible, str::FromStr}; use serde_derive::{Deserialize, Serialize}; +use crate::domain::id::TagGuid; + #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Default)] pub struct PaceTagCollection(HashSet); @@ -82,15 +83,6 @@ impl std::ops::Deref for PaceTag { } } -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Hash, Clone)] -pub struct TagGuid(Ulid); - -impl Default for TagGuid { - fn default() -> Self { - Self(Ulid::new()) - } -} - #[derive(Debug, TypedBuilder, Serialize, Deserialize, PartialEq, Eq, Hash, Clone)] pub struct Tag { #[builder(default, setter(strip_option))] @@ -101,6 +93,7 @@ pub struct Tag { } impl Tag { + #[must_use] pub const fn new(guid: Option, text: String) -> Self { Self { guid, text } } diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index 45a8f746..1cdfaef1 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -1,12 +1,11 @@ //! # Pace Core -pub(crate) mod commands; -pub(crate) mod config; -pub(crate) mod domain; -pub(crate) mod service; -pub(crate) mod storage; -pub(crate) mod template; -pub(crate) mod util; +pub mod config; +pub mod domain; +pub mod options; +pub mod storage; +pub mod template; +pub mod util; // Constants pub mod constants { @@ -27,17 +26,6 @@ pub use toml; pub mod prelude { // Public Prelude API pub use crate::{ - commands::{ - adjust::AdjustCommandOptions, - begin::BeginCommandOptions, - docs::DocsCommandOptions, - end::EndCommandOptions, - hold::{HoldCommandOptions, HoldOptions}, - now::NowCommandOptions, - reflect::{ExpensiveFlags, ReflectCommandOptions}, - resume::{ResumeCommandOptions, ResumeOptions}, - DeleteOptions, EndOptions, KeywordOptions, UpdateOptions, - }, config::{ find_root_config_file_path, find_root_project_file, get_activity_log_paths, get_config_paths, get_home_activity_log_path, get_home_config_path, @@ -47,14 +35,17 @@ pub mod prelude { }, domain::{ activity::{ - Activity, ActivityEndOptions, ActivityGroup, ActivityGuid, ActivityItem, - ActivityKind, ActivityKindOptions, ActivitySession, + Activity, ActivityEndOptions, ActivityGroup, ActivityItem, ActivityKind, + ActivityKindOptions, ActivitySession, }, activity_log::ActivityLog, category::{split_category_by_category_separator, PaceCategory}, description::PaceDescription, - filter::{ActivityFilterKind, FilterOptions, FilteredActivities}, - id::Guid, + filter::{ActivityFilterKind, FilteredActivities}, + id::{ + ActivityGuid, ActivityKindGuid, ActivityStatusGuid, CategoryGuid, DescriptionGuid, + Guid, TagGuid, + }, intermission::IntermissionAction, reflection::{ Highlights, ReflectionSummary, ReflectionsFormatKind, SummaryActivityGroup, @@ -63,7 +54,6 @@ pub mod prelude { status::ActivityStatusKind, tag::{PaceTag, PaceTagCollection}, }, - service::{activity_store::ActivityStore, activity_tracker::ActivityTracker}, storage::{ ActivityQuerying, ActivityReadOps, ActivityStateManagement, ActivityStorage, ActivityWriteOps, SyncStorage, diff --git a/crates/core/src/commands.rs b/crates/core/src/options.rs similarity index 51% rename from crates/core/src/commands.rs rename to crates/core/src/options.rs index 086963d4..1baa3f1c 100644 --- a/crates/core/src/commands.rs +++ b/crates/core/src/options.rs @@ -1,21 +1,30 @@ -pub mod adjust; -pub mod begin; -pub mod docs; -pub mod end; -pub mod hold; -pub mod now; -pub mod reflect; -pub mod resume; - -use getset::Getters; +use getset::{Getters, MutGetters, Setters}; use pace_time::date_time::PaceDateTime; +use serde_derive::Serialize; use typed_builder::TypedBuilder; -use crate::{ - commands::{hold::HoldOptions, resume::ResumeOptions}, - domain::category::PaceCategory, +use crate::domain::{ + category::PaceCategory, description::PaceDescription, intermission::IntermissionAction, }; +/// Options for holding an activity +#[derive(Debug, Clone, PartialEq, TypedBuilder, Eq, Hash, Default, Getters)] +#[getset(get = "pub")] +#[non_exhaustive] +pub struct HoldOptions { + /// The action to take on the intermission + #[builder(default)] + action: IntermissionAction, + + /// The start time of the intermission + #[builder(default, setter(into))] + begin_time: PaceDateTime, + + /// The reason for holding the activity + #[builder(default, setter(into))] + reason: Option, +} + /// Options for ending an activity #[derive(Debug, Clone, PartialEq, TypedBuilder, Eq, Hash, Default, Getters)] #[getset(get = "pub")] @@ -61,3 +70,22 @@ pub struct KeywordOptions { #[builder(default, setter(into, strip_option))] category: Option, } + +/// Options for resuming an activity +#[derive(Debug, Clone, PartialEq, TypedBuilder, Eq, Hash, Default, Getters)] +#[getset(get = "pub")] +#[non_exhaustive] +pub struct ResumeOptions { + /// The resume time of the intermission + #[builder(default, setter(into))] + resume_time: Option, +} + +#[derive( + Debug, TypedBuilder, Serialize, Getters, Setters, MutGetters, Clone, Eq, PartialEq, Default, +)] +#[getset(get = "pub")] +pub struct FilterOptions { + category: Option, + case_sensitive: bool, +} diff --git a/crates/core/src/storage.rs b/crates/core/src/storage.rs index 63cbe0bb..0c57a8e1 100644 --- a/crates/core/src/storage.rs +++ b/crates/core/src/storage.rs @@ -1,26 +1,26 @@ #[cfg(feature = "rusqlite")] pub mod rusqlite; +use itertools::Itertools; use std::{ collections::BTreeMap, fmt::{self, Debug, Formatter}, }; +use tracing::debug; -use itertools::Itertools; use pace_error::{PaceOptResult, PaceResult}; use pace_time::{date::PaceDate, duration::PaceDurationRange, time_range::TimeRangeOptions}; -use tracing::debug; use crate::{ - commands::{ - hold::HoldOptions, resume::ResumeOptions, DeleteOptions, EndOptions, KeywordOptions, - UpdateOptions, - }, domain::{ - activity::{Activity, ActivityGuid, ActivityItem, ActivityKind}, + activity::{Activity, ActivityItem, ActivityKind}, filter::{ActivityFilterKind, FilteredActivities}, + id::ActivityGuid, status::ActivityStatusKind, }, + options::{ + DeleteOptions, EndOptions, HoldOptions, KeywordOptions, ResumeOptions, UpdateOptions, + }, }; impl Debug for dyn ActivityStorage { diff --git a/crates/core/src/storage/rusqlite.rs b/crates/core/src/storage/rusqlite.rs index 6fca53a8..48c9465b 100644 --- a/crates/core/src/storage/rusqlite.rs +++ b/crates/core/src/storage/rusqlite.rs @@ -7,10 +7,11 @@ use rusqlite::{types::FromSql, ToSql}; // TODO: handle PaceTagCollection use crate::{ - domain::id::Guid, - prelude::{ - ActivityGuid, ActivityKind, ActivityStatusKind, PaceCategory, PaceDescription, PaceTag, + domain::id::{ + ActivityGuid, ActivityKindGuid, ActivityStatusGuid, CategoryGuid, DescriptionGuid, Guid, + TagGuid, }, + prelude::{ActivityKind, ActivityStatusKind, PaceCategory, PaceDescription, PaceTag}, }; impl ToSql for ActivityGuid { @@ -116,3 +117,73 @@ impl FromSql for PaceTag { .map_err(|err| rusqlite::types::FromSqlError::Other(Box::new(err))) } } + +impl ToSql for ActivityKindGuid { + fn to_sql(&self) -> rusqlite::Result> { + self.inner().to_sql() + } +} + +impl FromSql for ActivityKindGuid { + fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult { + Ok(Self::with_id(Guid::from_str(value.as_str()?).map_err( + |err| rusqlite::types::FromSqlError::Other(Box::new(err)), + )?)) + } +} + +impl ToSql for ActivityStatusGuid { + fn to_sql(&self) -> rusqlite::Result> { + self.inner().to_sql() + } +} + +impl FromSql for ActivityStatusGuid { + fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult { + Ok(Self::with_id(Guid::from_str(value.as_str()?).map_err( + |err| rusqlite::types::FromSqlError::Other(Box::new(err)), + )?)) + } +} + +impl ToSql for CategoryGuid { + fn to_sql(&self) -> rusqlite::Result> { + self.inner().to_sql() + } +} + +impl FromSql for CategoryGuid { + fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult { + Ok(Self::with_id(Guid::from_str(value.as_str()?).map_err( + |err| rusqlite::types::FromSqlError::Other(Box::new(err)), + )?)) + } +} + +impl ToSql for DescriptionGuid { + fn to_sql(&self) -> rusqlite::Result> { + self.inner().to_sql() + } +} + +impl FromSql for DescriptionGuid { + fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult { + Ok(Self::with_id(Guid::from_str(value.as_str()?).map_err( + |err| rusqlite::types::FromSqlError::Other(Box::new(err)), + )?)) + } +} + +impl ToSql for TagGuid { + fn to_sql(&self) -> rusqlite::Result> { + self.inner().to_sql() + } +} + +impl FromSql for TagGuid { + fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult { + Ok(Self::with_id(Guid::from_str(value.as_str()?).map_err( + |err| rusqlite::types::FromSqlError::Other(Box::new(err)), + )?)) + } +} diff --git a/crates/core/src/template.rs b/crates/core/src/template.rs index e8193fba..ddd78dfd 100644 --- a/crates/core/src/template.rs +++ b/crates/core/src/template.rs @@ -20,6 +20,15 @@ pub static TEMPLATES: Lazy = Lazy::new(|| { }); /// Returns the human duration of the argument. +/// +/// # Errors +/// +/// Returns an error if the argument is not a valid `PaceDuration`. +/// +/// # Returns +/// +/// Returns a `Value` with the human readable duration. +#[allow(clippy::implicit_hasher)] pub fn human_duration(value: &Value, _: &HashMap) -> Result { let Ok(duration) = from_value::(value.clone()) else { return Err(Error::msg(format!( @@ -36,6 +45,7 @@ pub struct PaceReflectionTemplate { } impl PaceReflectionTemplate { + #[must_use] pub fn into_context(self) -> Context { self.context } diff --git a/crates/service/Cargo.toml b/crates/service/Cargo.toml new file mode 100644 index 00000000..07cd5144 --- /dev/null +++ b/crates/service/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "pace_service" +version = "0.1.0" +authors = { workspace = true } +categories = { workspace = true } +edition = { workspace = true } +homepage = { workspace = true } +keywords = { workspace = true } +license = { workspace = true } +repository = { workspace = true } +rust-version = { workspace = true } +description = "pace-service - service support library for pace" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +getset = { workspace = true } +pace_core = { workspace = true, features = ["db"] } +pace_error = { workspace = true } +pace_time = { workspace = true, features = ["rusqlite"] } +tracing = { workspace = true } +typed-builder = { workspace = true } +wildmatch = { workspace = true } + +[lints] +workspace = true diff --git a/crates/core/LICENSE b/crates/service/LICENSE similarity index 100% rename from crates/core/LICENSE rename to crates/service/LICENSE diff --git a/crates/core/src/service/activity_store.rs b/crates/service/src/activity_store.rs similarity index 97% rename from crates/core/src/service/activity_store.rs rename to crates/service/src/activity_store.rs index bea8dcc1..923675a1 100644 --- a/crates/core/src/service/activity_store.rs +++ b/crates/service/src/activity_store.rs @@ -10,21 +10,20 @@ use typed_builder::TypedBuilder; use wildmatch::WildMatch; -use crate::{ - commands::{ - hold::HoldOptions, resume::ResumeOptions, DeleteOptions, EndOptions, KeywordOptions, - UpdateOptions, - }, +use pace_core::{ domain::{ - activity::{ - Activity, ActivityGroup, ActivityGuid, ActivityItem, ActivityKind, ActivitySession, - }, + activity::{Activity, ActivityGroup, ActivityItem, ActivityKind, ActivitySession}, category::{self, PaceCategory}, description::PaceDescription, - filter::{ActivityFilterKind, FilterOptions, FilteredActivities}, + filter::{ActivityFilterKind, FilteredActivities}, + id::ActivityGuid, reflection::{SummaryActivityGroup, SummaryGroupByCategory}, status::ActivityStatusKind, }, + options::{ + DeleteOptions, EndOptions, FilterOptions, HoldOptions, KeywordOptions, ResumeOptions, + UpdateOptions, + }, storage::{ ActivityQuerying, ActivityReadOps, ActivityStateManagement, ActivityStorage, ActivityWriteOps, SyncStorage, diff --git a/crates/core/src/service/activity_tracker.rs b/crates/service/src/activity_tracker.rs similarity index 92% rename from crates/core/src/service/activity_tracker.rs rename to crates/service/src/activity_tracker.rs index bf9d9f4a..0aaeff56 100644 --- a/crates/core/src/service/activity_tracker.rs +++ b/crates/service/src/activity_tracker.rs @@ -3,13 +3,11 @@ use pace_time::{time_frame::PaceTimeFrame, time_range::TimeRangeOptions}; use tracing::debug; -use crate::{ - domain::{filter::FilterOptions, reflection::ReflectionSummary}, - service::activity_store::ActivityStore, -}; - +use pace_core::{domain::reflection::ReflectionSummary, options::FilterOptions}; use pace_error::PaceOptResult; +use crate::activity_store::ActivityStore; + // This struct represents the overall structure for tracking activities and their intermissions. pub struct ActivityTracker { pub store: ActivityStore, diff --git a/crates/core/src/service.rs b/crates/service/src/lib.rs similarity index 100% rename from crates/core/src/service.rs rename to crates/service/src/lib.rs diff --git a/crates/storage/src/entities/activities.rs b/crates/storage/src/entities/activities.rs index 4bba992a..34146620 100644 --- a/crates/storage/src/entities/activities.rs +++ b/crates/storage/src/entities/activities.rs @@ -1,6 +1,8 @@ use chrono::FixedOffset; use getset::Getters; -use pace_core::prelude::{ActivityGuid, ActivityItem, Guid}; +use pace_core::prelude::{ + ActivityGuid, ActivityItem, ActivityKindGuid, ActivityStatusGuid, DescriptionGuid, +}; use rusqlite::{Error, Row}; use sea_query::enum_def; use strum::EnumIter; @@ -13,13 +15,13 @@ use crate::storage::SQLiteEntity; #[enum_def] pub struct Activities { pub guid: ActivityGuid, - pub description_guid: Guid, + pub description_guid: DescriptionGuid, pub begin: chrono::DateTime, pub end: Option>, - pub kind_guid: Guid, + pub kind_guid: ActivityKindGuid, pub duration: Option, - pub status_guid: Guid, - pub parent_guid: Option, + pub status_guid: ActivityStatusGuid, + pub parent_guid: Option, } #[derive(Copy, Clone, Debug, EnumIter)] diff --git a/crates/storage/src/entities/activity_kinds.rs b/crates/storage/src/entities/activity_kinds.rs index 94497ab9..8716655e 100644 --- a/crates/storage/src/entities/activity_kinds.rs +++ b/crates/storage/src/entities/activity_kinds.rs @@ -1,5 +1,5 @@ use getset::Getters; -use pace_core::prelude::Guid; +use pace_core::prelude::ActivityKindGuid; use rusqlite::{Error, Row}; use sea_query::enum_def; use strum::EnumIter; @@ -11,7 +11,7 @@ use crate::storage::SQLiteEntity; #[getset(get = "pub")] #[enum_def] pub struct ActivityKinds { - pub guid: Guid, + pub guid: ActivityKindGuid, pub kind: String, } diff --git a/crates/storage/src/entities/activity_status.rs b/crates/storage/src/entities/activity_status.rs index 995d298c..bb412f8a 100644 --- a/crates/storage/src/entities/activity_status.rs +++ b/crates/storage/src/entities/activity_status.rs @@ -1,5 +1,5 @@ use getset::Getters; -use pace_core::prelude::Guid; +use pace_core::prelude::ActivityStatusGuid; use rusqlite::{Error, Row}; use sea_query::enum_def; use strum::EnumIter; @@ -11,7 +11,7 @@ use crate::storage::SQLiteEntity; #[getset(get = "pub")] #[enum_def] pub struct ActivityStatus { - pub guid: Guid, + pub guid: ActivityStatusGuid, pub status: String, } diff --git a/crates/storage/src/entities/categories.rs b/crates/storage/src/entities/categories.rs index 37d388d7..5bd286b3 100644 --- a/crates/storage/src/entities/categories.rs +++ b/crates/storage/src/entities/categories.rs @@ -1,5 +1,5 @@ use getset::Getters; -use pace_core::prelude::Guid; +use pace_core::prelude::CategoryGuid; use rusqlite::{Error, Row}; use sea_query::enum_def; use strum::EnumIter; @@ -11,7 +11,7 @@ use crate::storage::SQLiteEntity; #[getset(get = "pub")] #[enum_def] pub struct Categories { - pub guid: Guid, + pub guid: CategoryGuid, pub category: String, pub description: Option, } diff --git a/crates/storage/src/entities/description.rs b/crates/storage/src/entities/description.rs index c1930609..c112e081 100644 --- a/crates/storage/src/entities/description.rs +++ b/crates/storage/src/entities/description.rs @@ -1,5 +1,5 @@ use getset::Getters; -use pace_core::prelude::{Guid, PaceDescription}; +use pace_core::prelude::{DescriptionGuid, PaceDescription}; use rusqlite::{Error, Row}; use sea_query::enum_def; use strum::EnumIter; @@ -11,7 +11,7 @@ use crate::storage::SQLiteEntity; #[getset(get = "pub")] #[enum_def] pub struct Descriptions { - pub guid: Guid, + pub guid: DescriptionGuid, pub description: PaceDescription, } diff --git a/crates/storage/src/entities/tags.rs b/crates/storage/src/entities/tags.rs index d3bf2d83..69b7b084 100644 --- a/crates/storage/src/entities/tags.rs +++ b/crates/storage/src/entities/tags.rs @@ -1,5 +1,5 @@ use getset::Getters; -use pace_core::prelude::Guid; +use pace_core::prelude::TagGuid; use rusqlite::{Error, Row}; use sea_query::enum_def; use strum::EnumIter; @@ -11,7 +11,7 @@ use crate::storage::SQLiteEntity; #[getset(get = "pub")] #[enum_def] pub struct Tags { - pub guid: Guid, + pub guid: TagGuid, pub tag: String, } diff --git a/crates/storage/src/lib.rs b/crates/storage/src/lib.rs index c31792d9..bd859677 100644 --- a/crates/storage/src/lib.rs +++ b/crates/storage/src/lib.rs @@ -1,9 +1,9 @@ pub mod convert; +pub mod entities; pub mod migration; +pub mod query; pub mod storage; -pub mod entities; - use std::sync::Arc; use pace_core::prelude::{ActivityLogStorageKind, ActivityStorage, DatabaseEngineKind, PaceConfig}; diff --git a/crates/storage/src/migration/create_activities_20240325143710.rs b/crates/storage/src/migration/create_activities_20240325143710.rs index 5f6427fe..67847391 100644 --- a/crates/storage/src/migration/create_activities_20240325143710.rs +++ b/crates/storage/src/migration/create_activities_20240325143710.rs @@ -30,8 +30,16 @@ impl SQLiteMigration for Migration { .text() .not_null(), ) - .col(ColumnDef::new(ActivitiesIden::Begin).date_time().not_null()) - .col(ColumnDef::new(ActivitiesIden::End).text().null()) + .col( + ColumnDef::new(ActivitiesIden::Begin) + .timestamp_with_time_zone() + .not_null(), + ) + .col( + ColumnDef::new(ActivitiesIden::End) + .timestamp_with_time_zone() + .null(), + ) .col(ColumnDef::new(ActivitiesIden::Duration).integer().null()) .col(ColumnDef::new(ActivitiesIden::KindGuid).text().not_null()) .col(ColumnDef::new(ActivitiesIden::StatusGuid).text().not_null()) diff --git a/crates/storage/src/query.rs b/crates/storage/src/query.rs new file mode 100644 index 00000000..881ac953 --- /dev/null +++ b/crates/storage/src/query.rs @@ -0,0 +1,8 @@ +use pace_error::PaceResult; + +// Lazy loading related entities +trait LazyLoad { + fn query(&self) -> PaceResult> + where + Self: Sized; +} diff --git a/crates/storage/src/storage/file.rs b/crates/storage/src/storage/file.rs index d56906be..fde0ddce 100644 --- a/crates/storage/src/storage/file.rs +++ b/crates/storage/src/storage/file.rs @@ -5,11 +5,15 @@ use std::{ path::{Path, PathBuf}, }; -use pace_core::prelude::{ - Activity, ActivityFilterKind, ActivityGuid, ActivityItem, ActivityKind, ActivityLog, - ActivityQuerying, ActivityReadOps, ActivityStateManagement, ActivityStatusKind, - ActivityStorage, ActivityWriteOps, DeleteOptions, EndOptions, FilteredActivities, HoldOptions, - KeywordOptions, ResumeOptions, SyncStorage, UpdateOptions, +use pace_core::{ + options::{ + DeleteOptions, EndOptions, HoldOptions, KeywordOptions, ResumeOptions, UpdateOptions, + }, + prelude::{ + Activity, ActivityFilterKind, ActivityGuid, ActivityItem, ActivityKind, ActivityLog, + ActivityQuerying, ActivityReadOps, ActivityStateManagement, ActivityStatusKind, + ActivityStorage, ActivityWriteOps, FilteredActivities, SyncStorage, + }, }; use pace_error::{PaceOptResult, PaceResult, TomlFileStorageErrorKind}; use pace_time::{date::PaceDate, duration::PaceDurationRange, time_range::TimeRangeOptions}; diff --git a/crates/storage/src/storage/in_memory.rs b/crates/storage/src/storage/in_memory.rs index 779507a3..231cfc0d 100644 --- a/crates/storage/src/storage/in_memory.rs +++ b/crates/storage/src/storage/in_memory.rs @@ -11,12 +11,16 @@ use merge::Merge; use rayon::prelude::{IntoParallelRefIterator, ParallelIterator}; use tracing::debug; -use pace_core::prelude::{ - Activity, ActivityEndOptions, ActivityFilterKind, ActivityGuid, ActivityItem, ActivityKind, - ActivityKindOptions, ActivityLog, ActivityQuerying, ActivityReadOps, ActivityStateManagement, - ActivityStatusKind, ActivityStorage, ActivityWriteOps, DeleteOptions, EndOptions, - FilteredActivities, HoldOptions, KeywordOptions, PaceCategory, ResumeOptions, SyncStorage, - UpdateOptions, +use pace_core::{ + options::{ + DeleteOptions, EndOptions, HoldOptions, KeywordOptions, ResumeOptions, UpdateOptions, + }, + prelude::{ + Activity, ActivityEndOptions, ActivityFilterKind, ActivityGuid, ActivityItem, ActivityKind, + ActivityKindOptions, ActivityLog, ActivityQuerying, ActivityReadOps, + ActivityStateManagement, ActivityStatusKind, ActivityStorage, ActivityWriteOps, + FilteredActivities, PaceCategory, SyncStorage, + }, }; use pace_error::{ActivityLogErrorKind, PaceOptResult, PaceResult}; diff --git a/crates/storage/src/storage/sqlite.rs b/crates/storage/src/storage/sqlite.rs index f14169ed..a9495cae 100644 --- a/crates/storage/src/storage/sqlite.rs +++ b/crates/storage/src/storage/sqlite.rs @@ -2,17 +2,21 @@ use std::collections::BTreeMap; use itertools::Itertools; use rusqlite::Connection; +use sea_query::{Expr, Query, SqliteQueryBuilder}; +use tracing::debug; -use pace_core::prelude::{ - Activity, ActivityFilterKind, ActivityGuid, ActivityItem, ActivityKind, ActivityQuerying, - ActivityReadOps, ActivityStateManagement, ActivityStatusKind, ActivityStorage, - ActivityWriteOps, DeleteOptions, EndOptions, FilteredActivities, HoldOptions, KeywordOptions, - ResumeOptions, SyncStorage, UpdateOptions, +use pace_core::{ + options::{ + DeleteOptions, EndOptions, HoldOptions, KeywordOptions, ResumeOptions, UpdateOptions, + }, + prelude::{ + Activity, ActivityFilterKind, ActivityGuid, ActivityItem, ActivityKind, ActivityQuerying, + ActivityReadOps, ActivityStateManagement, ActivityStatusKind, ActivityStorage, + ActivityWriteOps, FilteredActivities, SyncStorage, + }, }; use pace_error::{DatabaseStorageErrorKind, PaceOptResult, PaceResult}; use pace_time::{date::PaceDate, duration::PaceDurationRange, time_range::TimeRangeOptions}; -use sea_query::{Expr, Query, SqliteQueryBuilder}; -use tracing::debug; use crate::{ convert::Convert, diff --git a/src/commands/adjust.rs b/src/commands/adjust.rs index 066fe59b..4f7e019f 100644 --- a/src/commands/adjust.rs +++ b/src/commands/adjust.rs @@ -2,12 +2,11 @@ use abscissa_core::{status_err, Application, Command, Runnable, Shutdown}; use clap::Parser; +use pace_cli::commands::adjust::AdjustCommandOptions; use pace_storage::get_storage_from_config; use crate::prelude::PACE_APP; -use pace_core::prelude::AdjustCommandOptions; - /// `adjust` subcommand #[derive(Command, Debug, Parser)] pub struct AdjustCmd { diff --git a/src/commands/begin.rs b/src/commands/begin.rs index e964c45b..e1fe2fb4 100644 --- a/src/commands/begin.rs +++ b/src/commands/begin.rs @@ -2,12 +2,11 @@ use abscissa_core::{status_err, Application, Command, Runnable, Shutdown}; use clap::Parser; +use pace_cli::commands::begin::BeginCommandOptions; use pace_storage::get_storage_from_config; use crate::prelude::PACE_APP; -use pace_core::prelude::BeginCommandOptions; - /// `begin` subcommand #[derive(Command, Debug, Parser)] pub struct BeginCmd { diff --git a/src/commands/docs.rs b/src/commands/docs.rs index 06fcbc38..5d974fbf 100644 --- a/src/commands/docs.rs +++ b/src/commands/docs.rs @@ -2,7 +2,7 @@ use abscissa_core::{status_err, Application, Command, Runnable, Shutdown}; use clap::Args; -use pace_core::prelude::DocsCommandOptions; +use pace_cli::commands::docs::DocsCommandOptions; use crate::application::PACE_APP; diff --git a/src/commands/end.rs b/src/commands/end.rs index 3fc99642..89d69c5f 100644 --- a/src/commands/end.rs +++ b/src/commands/end.rs @@ -2,12 +2,11 @@ use abscissa_core::{status_err, Application, Command, Runnable, Shutdown}; use clap::Parser; +use pace_cli::commands::end::EndCommandOptions; use pace_storage::get_storage_from_config; use crate::prelude::PACE_APP; -use pace_core::prelude::EndCommandOptions; - /// `end` subcommand #[derive(Command, Debug, Parser)] pub struct EndCmd { diff --git a/src/commands/hold.rs b/src/commands/hold.rs index 45f986be..dfccb61f 100644 --- a/src/commands/hold.rs +++ b/src/commands/hold.rs @@ -3,7 +3,7 @@ use abscissa_core::{status_err, Application, Command, Runnable, Shutdown}; use clap::Parser; -use pace_core::prelude::HoldCommandOptions; +use pace_cli::commands::hold::HoldCommandOptions; use pace_storage::get_storage_from_config; use crate::prelude::PACE_APP; diff --git a/src/commands/now.rs b/src/commands/now.rs index a6d4103c..21715723 100644 --- a/src/commands/now.rs +++ b/src/commands/now.rs @@ -2,12 +2,11 @@ use abscissa_core::{status_err, Application, Command, Runnable, Shutdown}; use clap::Parser; +use pace_cli::commands::now::NowCommandOptions; use pace_storage::get_storage_from_config; use crate::prelude::PACE_APP; -use pace_core::prelude::NowCommandOptions; - /// `now` subcommand #[derive(Command, Debug, Parser)] pub struct NowCmd { diff --git a/src/commands/reflect.rs b/src/commands/reflect.rs index bda04713..eea21713 100644 --- a/src/commands/reflect.rs +++ b/src/commands/reflect.rs @@ -6,7 +6,7 @@ use abscissa_core::{status_err, Application, Command, Runnable, Shutdown}; use clap::Parser; -use pace_core::prelude::ReflectCommandOptions; +use pace_cli::commands::reflect::ReflectCommandOptions; use pace_storage::get_storage_from_config; use crate::prelude::PACE_APP; diff --git a/src/commands/resume.rs b/src/commands/resume.rs index db617d5d..dde42447 100644 --- a/src/commands/resume.rs +++ b/src/commands/resume.rs @@ -5,12 +5,16 @@ use abscissa_core::{status_err, tracing::debug, Application, Command, Runnable, use clap::Parser; use eyre::Result; -use pace_cli::{confirmation_or_break, prompt_resume_activity}; -use pace_core::prelude::{ - ActivityQuerying, ActivityReadOps, ActivityStateManagement, ActivityStore, - ResumeCommandOptions, ResumeOptions, SyncStorage, +use pace_cli::{ + commands::resume::ResumeCommandOptions, + prompt::{confirmation_or_break_default_true, prompt_resume_activity}, +}; +use pace_core::{ + options::ResumeOptions, + prelude::{ActivityQuerying, ActivityReadOps, ActivityStateManagement, SyncStorage}, }; use pace_error::UserMessage; +use pace_service::activity_store::ActivityStore; use pace_storage::get_storage_from_config; use pace_time::{date_time::PaceDateTime, time_zone::PaceTimeZoneKind, Validate}; @@ -113,7 +117,7 @@ impl ResumeCmd { Err(recoverable_err) if recoverable_err.possible_new_activity_from_resume() => { debug!("Activity to resume: {:?}", activity_item.activity()); - confirmation_or_break( + confirmation_or_break_default_true( "We can't resume this already ended activity. Do you want to begin one with the same contents?", )?; diff --git a/src/commands/settings/set.rs b/src/commands/settings/set.rs index fe7cd7de..fe58c0a8 100644 --- a/src/commands/settings/set.rs +++ b/src/commands/settings/set.rs @@ -1,7 +1,7 @@ use abscissa_core::{Command, Runnable}; use clap::{Parser, Subcommand}; -use pace_cli::prompt_time_zone; +use pace_cli::prompt::prompt_time_zone; use pace_core::prelude::PaceConfig; use crate::prelude::PACE_APP; diff --git a/src/commands/setup/config.rs b/src/commands/setup/config.rs index c41ec2ef..e418ce5f 100644 --- a/src/commands/setup/config.rs +++ b/src/commands/setup/config.rs @@ -6,7 +6,7 @@ use abscissa_core::{status_warn, Application, Command, Runnable, Shutdown}; use clap::Parser; use dialoguer::console::Term; -use pace_cli::{setup_config, PathOptions}; +use pace_cli::setup::{setup_config, PathOptions}; use crate::prelude::PACE_APP; diff --git a/tests/integration/activity_store.rs b/tests/integration/activity_store.rs index 457d8bec..35b0874f 100644 --- a/tests/integration/activity_store.rs +++ b/tests/integration/activity_store.rs @@ -2,12 +2,15 @@ use std::sync::Arc; -use pace_core::prelude::{ - Activity, ActivityFilterKind, ActivityGuid, ActivityReadOps, ActivityStateManagement, - ActivityStatusKind, ActivityStore, ActivityWriteOps, DeleteOptions, EndOptions, HoldOptions, - PaceCategory, PaceDescription, PaceTagCollection, ResumeOptions, UpdateOptions, +use pace_core::{ + options::{DeleteOptions, EndOptions, HoldOptions, ResumeOptions, UpdateOptions}, + prelude::{ + Activity, ActivityFilterKind, ActivityGuid, ActivityReadOps, ActivityStateManagement, + ActivityStatusKind, ActivityWriteOps, PaceCategory, PaceDescription, PaceTagCollection, + }, }; use pace_error::TestResult; +use pace_service::activity_store::ActivityStore; use pace_storage::storage::in_memory::InMemoryActivityStorage; use crate::util::{ diff --git a/tests/integration/activity_tracker.rs b/tests/integration/activity_tracker.rs index 6e51c262..3c3270f2 100644 --- a/tests/integration/activity_tracker.rs +++ b/tests/integration/activity_tracker.rs @@ -1,12 +1,14 @@ //! Test the `ActivityStore` implementation with a `InMemoryStorage` backend. use chrono::NaiveDate; -use pace_core::prelude::{ActivityStore, ActivityTracker, FilterOptions}; -use pace_error::TestResult; -use pace_time::{duration::PaceDuration, time_range::TimeRangeOptions}; use rstest::rstest; use similar_asserts::assert_eq; +use pace_core::options::FilterOptions; +use pace_error::TestResult; +use pace_service::{activity_store::ActivityStore, activity_tracker::ActivityTracker}; +use pace_time::{duration::PaceDuration, time_range::TimeRangeOptions}; + use crate::util::setup_activity_store_for_activity_tracker; #[rstest] diff --git a/tests/integration/util.rs b/tests/integration/util.rs index d17daff4..26a9f1cc 100644 --- a/tests/integration/util.rs +++ b/tests/integration/util.rs @@ -5,9 +5,10 @@ use rstest::fixture; use pace_core::prelude::{ Activity, ActivityGuid, ActivityItem, ActivityKind, ActivityKindOptions, ActivityLog, - ActivityStatusKind, ActivityStore, PaceCategory, PaceDescription, PaceTagCollection, + ActivityStatusKind, PaceCategory, PaceDescription, PaceTagCollection, }; use pace_error::TestResult; +use pace_service::activity_store::ActivityStore; use pace_storage::storage::{file::TomlActivityStorage, in_memory::InMemoryActivityStorage}; use pace_time::date_time::PaceDateTime; diff --git a/tests/journey/hold_resume.rs b/tests/journey/hold_resume.rs index 41a6947e..69f5471a 100644 --- a/tests/journey/hold_resume.rs +++ b/tests/journey/hold_resume.rs @@ -1,6 +1,8 @@ -use pace_core::prelude::{ - Activity, ActivityQuerying, ActivityReadOps, ActivityStateManagement, HoldOptions, - PaceDescription, ResumeOptions, +use pace_core::{ + options::{HoldOptions, ResumeOptions}, + prelude::{ + Activity, ActivityQuerying, ActivityReadOps, ActivityStateManagement, PaceDescription, + }, }; use pace_error::TestResult; use pace_storage::storage::in_memory::InMemoryActivityStorage; diff --git a/tests/journey/start_finish_different_time_zone.rs b/tests/journey/start_finish_different_time_zone.rs index 4a2da653..6ced2a58 100644 --- a/tests/journey/start_finish_different_time_zone.rs +++ b/tests/journey/start_finish_different_time_zone.rs @@ -1,7 +1,8 @@ use chrono::FixedOffset; use eyre::OptionExt; -use pace_core::prelude::{ - Activity, ActivityReadOps, ActivityStateManagement, EndOptions, PaceDescription, +use pace_core::{ + options::EndOptions, + prelude::{Activity, ActivityReadOps, ActivityStateManagement, PaceDescription}, }; use pace_error::TestResult; use pace_storage::storage::in_memory::InMemoryActivityStorage;