diff --git a/Cargo.lock b/Cargo.lock index 044308c..0212bcf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,6 +26,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "ashpd" version = "0.8.1" @@ -364,6 +379,21 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + [[package]] name = "ciborium" version = "0.2.2" @@ -2080,6 +2110,30 @@ dependencies = [ "tracing", ] +[[package]] +name = "iana-time-zone" +version = "0.1.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +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]] name = "icu_collections" version = "1.5.0" @@ -2330,7 +2384,10 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" name = "journal" version = "0.1.0" dependencies = [ + "chrono", "dioxus", + "serde", + "serde_json", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index d863a24..9b7ebe5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,16 +7,16 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +chrono = { version = "0.4.41", features = ["serde"] } dioxus = { version = "0.6.0", features = [] } - +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0.140" [features] default = ["desktop"] web = ["dioxus/web"] desktop = ["dioxus/desktop"] mobile = ["dioxus/mobile"] -[profile] - [profile.wasm-dev] inherits = "dev" opt-level = 1 diff --git a/assets/ketamine.json b/assets/ketamine.json new file mode 100644 index 0000000..0c29903 --- /dev/null +++ b/assets/ketamine.json @@ -0,0 +1,156 @@ +{ + "canonical_name": "ketamine", + "display_name": "Ketamine", + "aliases": [ + "k", + "ket", + "kitty", + "kittens", + "special k", + "vitamin k" + ], + "categories": [ + "Dissociative", + "HabitForming", + "Common" + ], + "description": "A short acting dissociative anaesthetic and hallucinogen commonly used in emergency medicine. It is the prototypical dissociative, and is widely used at sub-anesthetic doses recreationally. Small doses are comparable with alcohol, while larger doses are immobilising and lead to psychedelic experiences: the \"K-Hole.\"", + "effects": [ + "A feeling of drunkenness and well being at low doses", + "As dose increases the user may begin to feel a disconnection from their body", + "At 'khole' doses the user may become completely disconnected from both body and mind" + ], + "combos": [ + { + "name": "2c-t-x", + "note": "", + "status": "Low Risk & Synergy" + }, + { + "name": "2c-x", + "note": "", + "status": "Low Risk & Synergy" + }, + { + "name": "5-meo-xxt", + "note": "", + "status": "Low Risk & Synergy" + }, + { + "name": "alcohol", + "note": "Both substances cause ataxia and bring a very high risk of vomiting and unconsciousness. If the user falls unconscious while under the influence there is a severe risk of vomit aspiration if they are not placed in the recovery position.", + "status": "Dangerous" + }, + { + "name": "amphetamines", + "note": "No unexpected interactions, though likely to increase blood pressure but not an issue with sensible doses. Moving around on high doses of this combination may be ill advised due to risk of physical injury.", + "status": "Caution" + }, + { + "name": "amt", + "note": "", + "status": "Low Risk & Synergy" + }, + { + "name": "benzodiazepines", + "note": "Both substances potentiate the ataxia and sedation caused by the other and can lead to unexpected loss of consciousness at high doses. While unconscious, vomit aspiration is a risk if not placed in the recovery position.", + "status": "Cuation" + }, + { + "name": "caffeine", + "note": "No unexpected interactions", + "status": "Low Risk & No Synergy" + }, + { + "name": "cannabis", + "note": "", + "status": "Low Risk & Synergy" + }, + { + "name": "cocaine", + "note": "No unexpected interactions, though likely to increase blood pressure but not an issue with sensible doses. Moving around on high doses of this combination may be ill advised due to risk of physical injury.", + "status": "Caution" + }, + { + "name": "dextromethorphan", + "note": "Both substances primarily exert their effects through NMDA antagonism. Currently, there is no evidence regarding mechanisms that might reduce these effects.", + "status": "Low Risk & Synergy" + }, + { + "name": "dmt", + "note": "", + "status": "Low Risk & Synergy" + }, + { + "name": "dox", + "note": "Ketamine and psychedelics tend to potentiate each other - go slowly.", + "status": "Low Risk & Synergy" + }, + { + "name": "ghb/gbl", + "note": "Both substances cause ataxia and bring a risk of vomiting and unconsciousness. If the user falls unconscious while under the influence there is a severe risk of vomit aspiration if they are not placed in the recovery position.", + "status": "Dangerous" + }, + { + "name": "lsd", + "note": "", + "status": "Low Risk & Synergy" + }, + { + "name": "maois", + "note": "MAO-B inhibitors appear to increase the potency of Ketamine. MAO-A inhbitors have some negative reports associated with the combination but there isn't much information available", + "status": "Caution" + }, + { + "name": "mdma", + "note": "No unexpected interactions, though likely to increase blood pressure but not an issue with sensible doses. Moving around on high doses of this combination may be ill advised due to risk of physical injury.", + "status": "Low Risk & Synergy" + }, + { + "name": "mescaline", + "note": "", + "status": "Low Risk & Synergy" + }, + { + "name": "mushrooms", + "note": "", + "status": "Low Risk & Synergy" + }, + { + "name": "mxe", + "note": "", + "status": "Low Risk & Synergy" + }, + { + "name": "nbomes", + "note": "", + "status": "Low Risk & Synergy" + }, + { + "name": "nitrous", + "note": "", + "status": "Low Risk & Synergy" + }, + { + "name": "opioids", + "note": "Both substances bring a risk of vomiting and unconsciousness. If the user falls unconscious while under the influence there is a severe risk of vomit aspiration if they are not placed in the recovery position.", + "status": "Dangerous" + }, + { + "name": "pcp", + "note": "", + "status": "Low Risk & Synergy" + }, + { + "name": "ssris", + "note": "", + "status": "Low Risk & Synergy" + }, + { + "name": "tramadol", + "note": "", + "status": "Dangerous" + } + ], + "dose_note": "NOTE: Ketamine is based on weight. These are figures for the average 150 pound male. There is no concrete dose for the \"K-Hole\" as each user is different." +} \ No newline at end of file diff --git a/assets/substances/cannabis.json b/assets/substances/cannabis.json new file mode 100644 index 0000000..068a186 --- /dev/null +++ b/assets/substances/cannabis.json @@ -0,0 +1,95 @@ +{ + "name": "Cannabis", + "nicknames": [ + "Marijuana", + "Weed" + ], + "summary": "A common and widely used psychoactive plant, which is beginning to enjoy legal status for medical and even recreational use in some parts of the world. Usually smoked or eaten, primary effects are relaxation and an affinity towards food - a state described as being 'stoned.'", + "effects": "The most important of a total of...", + "routes": [ + "Oral", + "Smoked" + ], + "dosages": { + "Smoked": { + "unit": "mg", + "threshold": "0.4", + "light_common": "2", + "common_strong": "4", + "strong": "10" + }, + "Oral": { + "unit": "mg", + "threshold": "1", + "light_common": "5", + "common_strong": "10", + "strong": "25" + } + }, + "durations": { + "Oral": { + "onset": { + "minimum_seconds": 1200, + "maximum_seconds": 3600, + "display": "20 - 60 minutes" + }, + "comeup": { + "minimum_seconds": 1800, + "maximum_seconds": 3600, + "display": "30 - 60 minutes" + }, + "peak": { + "minimum_seconds": 3600, + "maximum_seconds": 7200, + "display": "1 - 2 hours" + }, + "offset": { + "minimum_seconds": 14400, + "maximum_seconds": 21600, + "display": "4 - 6 hours" + }, + "total": { + "minimum_seconds": 14400, + "maximum_seconds": 36000, + "display": "4 - 10 hours" + }, + "after_effects": { + "minimum_seconds": 21600, + "maximum_seconds": 43200, + "display": "6 - 12 hours" + } + }, + "Smoked": { + "onset": { + "minimum_seconds": 6, + "maximum_seconds": 600, + "display": "0.1 - 10 minutes" + }, + "comeup": { + "minimum_seconds": 300, + "maximum_seconds": 600, + "display": "5 - 10 minutes" + }, + "peak": { + "minimum_seconds": 900, + "maximum_seconds": 2700, + "display": "15 - 45 minutes" + }, + "offset": { + "minimum_seconds": 10800, + "maximum_seconds": 14400, + "display": "3 - 4 hours" + }, + "total": { + "minimum_seconds": 9000, + "maximum_seconds": 18000, + "display": "2.5 - 5 hours" + }, + "after_effects": { + "minimum_seconds": 2700, + "maximum_seconds": 10800, + "display": "45 - 180 minutes" + } + } + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..36470a5 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1 @@ +pub mod pwjournal; \ No newline at end of file diff --git a/src/pwjournal.rs b/src/pwjournal.rs new file mode 100644 index 0000000..ed23fa9 --- /dev/null +++ b/src/pwjournal.rs @@ -0,0 +1,60 @@ +#![allow(non_snake_case)] + +use chrono::{serde::ts_milliseconds, DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize)] +pub struct CustomUnit { + unit: String, + isEstimate: bool, + #[serde(with = "ts_milliseconds")] + creationDate: DateTime, + administrationRoute: String, + isArchived: bool, + originalUnit: String, + estimatedDoseStandardDeviation: Option, + dose: f64, + id: u64, + unitPlural: Option, + name: String, + substanceName: String, + note: String, +} + +#[cfg(test)] +mod tests { + use chrono::NaiveDate; + + use super::*; + + #[test] + fn load_custom_unit() { + let unit_raw_json = r#" + { + "unit": "can", + "isEstimate": false, + "creationDate": 1728475485000, + "administrationRoute": "ORAL", + "isArchived": false, + "originalUnit": "mg", + "estimatedDoseStandardDeviation": null, + "dose": 150, + "id": 382940388, + "unitPlural": null, + "name": "Monster", + "substanceName": "Caffeine", + "note": "" + }"#; + + let unit: CustomUnit = + serde_json::from_str(unit_raw_json).expect("LOADING TEST CUSTOM UNIT SHOULD WORK"); + assert_eq!(unit.dose, 150 as f64); + assert_eq!(unit.unit, String::from("can")); + let time = NaiveDate::from_ymd_opt(2024, 10, 9) + .unwrap() + .and_hms_micro_opt(12, 4, 45, 0) + .unwrap() + .and_utc(); + assert_eq!(unit.creationDate, time); + } +} diff --git a/src/substance.rs b/src/substance.rs new file mode 100644 index 0000000..5a3bdec --- /dev/null +++ b/src/substance.rs @@ -0,0 +1,100 @@ +use std::{collections::HashMap, error::Error, fmt, fs::File, io::BufReader, path::Path}; + +use serde::{Serialize, Deserialize}; + +mod links; +pub use links::Link; + +mod dose; +pub use dose::Dose; + +mod duration; +pub use duration::{Duration, TimeRange}; + +mod effect; +pub use effect::Effect; + +mod administration; +pub use administration::{AdministrationType, Administration}; + +mod interaction; +pub use interaction::Interaction; + +#[derive(Serialize, Deserialize, Debug)] +pub struct Drug { + pub canonical_name: String, + pub display_name: String, + pub aliases: Vec, + pub categories: Vec, + pub description: Option, + pub effects: Vec, + pub roas: HashMap, + pub combos: Vec, + pub dose_note: Option +} + +pub fn load_substance>(path: P) -> Result> { + let file = File::open(path)?; + let reader = BufReader::new(file); + + let s = serde_json::from_reader(reader)?; + + Ok(s) +} + +#[derive(Serialize, Deserialize, Debug)] +pub enum Category { + Depressant, + HabitForming, + Tentative, + ResearchChemical, + Psychedelic, + Stimulant, + Dissociative, + Inactive, + Empathogen, + Common, + Benzodiazepine, + Opioid, + Supplement, + Nootropic, + Barbiturate, + Deliriant, + Ssri +} + +impl fmt::Display for Category { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let print_str = match self { + Category::Depressant => "depressant", + Category::HabitForming => "habit forming", + Category::Tentative => "tentative", + Category::ResearchChemical => "research chemical", + Category::Psychedelic => "psychedlic", + Category::Stimulant => "stimulant", + Category::Dissociative => "dissociative", + Category::Inactive => "inactive", + Category::Empathogen => "empathogen", + Category::Common => "common", + Category::Benzodiazepine => "benzodiazepine", + Category::Opioid => "opioid", + Category::Supplement => "supplement", + Category::Nootropic => "nootropic", + Category::Barbiturate => "barbiturate", + Category::Deliriant => "deliriant", + Category::Ssri => "SSRI", + }; + + write!(f, "{}", print_str) + } +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct Tolerance { + full: String, + half: Option, + zero: Option +} + + + diff --git a/src/substance/administration.rs b/src/substance/administration.rs new file mode 100644 index 0000000..5b99ce7 --- /dev/null +++ b/src/substance/administration.rs @@ -0,0 +1,30 @@ +use::serde::{Serialize, Deserialize}; + +use super::{Dose, Duration}; + +#[derive(PartialEq, Eq, Hash, Serialize, Deserialize, Debug)] +pub enum AdministrationType { + Oral, + Insufflated, + Smoked, + Inhaled, + Sublingual, + Buccal, + Rectal, + Transdermal, + Subcutaneous, + Intramuscular, + Intravenous +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct Administration { + dose: Dose, + duration: Duration +} + +impl Administration { + pub fn new(dose: Dose, duration: Duration) -> Self { + Self { dose, duration } + } +} \ No newline at end of file diff --git a/src/substance/duration.rs b/src/substance/duration.rs new file mode 100644 index 0000000..4426ba4 --- /dev/null +++ b/src/substance/duration.rs @@ -0,0 +1,73 @@ +use std::fmt; + +use serde::{Serialize, Deserialize}; + +#[derive(Serialize, Deserialize, Debug)] +pub struct Duration { + pub onset: DurationRange, + pub comeup: DurationRange, + pub peak: DurationRange, + pub offset: DurationRange, + pub total: DurationRange, + pub after_effects: DurationRange +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct DurationRange { + pub minimum_seconds: u32, + pub maximum_seconds: u32, + pub display: String +} + +#[derive(Serialize, Deserialize, Debug)] +pub enum DurationTimeUnits { + Seconds, + Minutes, + Hours, + Days +} + +impl fmt::Display for DurationTimeUnits { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let print_str = match self { + DurationTimeUnits::Seconds => "seconds", + DurationTimeUnits::Minutes => "minutes", + DurationTimeUnits::Hours => "hours", + DurationTimeUnits::Days => "days", + }; + + write!(f, "{}", print_str) + } +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct TimeRange { + units: DurationTimeUnits, + minimum: u32, + maximum: u32 +} + +impl TimeRange { + pub fn seconds(&self) -> (u32, u32) { + match self.units { + DurationTimeUnits::Seconds => (self.minimum, self.maximum), + DurationTimeUnits::Minutes => (self.minimum * 60, self.maximum * 60), + DurationTimeUnits::Hours => (self.minimum * 60 * 60, self.maximum * 60 * 60), + DurationTimeUnits::Days => (self.minimum * 60 * 60 * 24, self.maximum * 60 * 60 * 24) + } + } + + pub fn minimum_seconds(&self) -> u32 { + self.seconds().0 + } + + pub fn maximum_seconds(&self) -> u32 { + self.seconds().1 + } +} + +impl fmt::Display for TimeRange { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}–{} {}", self.minimum, self.maximum, self.units) + } +} \ No newline at end of file diff --git a/src/substance/effect.rs b/src/substance/effect.rs new file mode 100644 index 0000000..1690d1a --- /dev/null +++ b/src/substance/effect.rs @@ -0,0 +1,9 @@ +use serde::{Deserialize, Serialize}; + +use super::Link; + +#[derive(Serialize, Deserialize, Debug)] +pub struct Effect { + name: String, + links: Vec +} \ No newline at end of file diff --git a/src/substance/interaction.rs b/src/substance/interaction.rs new file mode 100644 index 0000000..bde1a62 --- /dev/null +++ b/src/substance/interaction.rs @@ -0,0 +1,14 @@ +use serde::{Serialize, Deserialize}; + +#[derive(Serialize, Deserialize, Debug)] +pub struct Interaction { + name: String, + note: String, + status: String +} + +impl Interaction { + pub fn new(name: &str, note: &str, status: &str) -> Self { + Self { name: name.to_owned(), note: note.to_owned(), status: status.to_owned() } + } +} \ No newline at end of file diff --git a/src/substance/links.rs b/src/substance/links.rs new file mode 100644 index 0000000..0bd0230 --- /dev/null +++ b/src/substance/links.rs @@ -0,0 +1,6 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Deserialize, Serialize, Debug)] +pub enum Link{ + PsychonautWiki(String), +} \ No newline at end of file