Something went wrong. Try again.
A simple to-do app focused on tasks that can be completed within a specific time span. taskline.tobinio.dev
Something went wrong. Try again.
Rust
at rust-backend
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182use std::ops::Add;
use chrono::{DateTime, Days, NaiveDate, Utc};use serde::{Deserialize, Deserializer, Serialize};use uuid::Uuid;
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]pub struct Todo { pub uuid: Uuid, pub title: String, pub note: String, pub time: Option<Time>, pub tags: Vec<Uuid>, pub category: Option<Uuid>, pub checks: Vec<NaiveDate>,}
impl Todo { pub fn is_today(&self) -> bool { let Some(time) = &self.time else { return false; };
let range = time.get_time_range();
for check in &self.checks { if range.start <= *check && *check <= range.end { return false; } }
let now = chrono::Local::now().naive_local().date(); if range.start <= now && now <= range.end { return true; }
false }}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]pub struct TimePoint { pub time: DateTime<Utc>,}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]pub struct TimeRange { pub start: NaiveDate, pub end: NaiveDate,}
const fn default_start() -> u64 { 6}
const fn default_end() -> u64 { 0}
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]#[serde(tag = "mode")]pub enum TimeRecurring { #[serde(rename = "daily")] Daily {},
#[serde(rename = "weekly")] Weekly { #[serde(default = "default_start")] start: u64, #[serde(default = "default_end")] end: u64, },}
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]#[serde(tag = "type")]pub enum Time { #[serde(rename = "range")] Range { #[serde(flatten)] inner: TimeRange, },
#[serde(rename = "recurring")] Recurring { #[serde(flatten)] inner: TimeRecurring, },
#[serde(rename = "point")] Point { #[serde(flatten)] inner: TimePoint, },}
impl Time { pub fn get_time_range(&self) -> TimeRange { match self { Time::Range { inner } => inner.clone(), Time::Recurring { inner } => match inner { TimeRecurring::Daily {} => { let now = chrono::Local::now();
TimeRange { start: now.date_naive(), end: now.date_naive(), } } TimeRecurring::Weekly { start, end } => { let now = chrono::Local::now();
TimeRange { start: now.add(Days::new(*start)).date_naive(), end: now.add(Days::new(*end)).date_naive(), } } }, Time::Point { inner } => TimeRange { start: inner.time.date_naive(), end: inner.time.date_naive(), }, } }}
#[derive(Debug, Clone, PartialEq, Eq)]pub struct HexColor(String);
impl HexColor { pub fn as_str(&self) -> &str { &self.0 }
pub fn from_text(s: &str) -> Option<Self> { if is_valid_hex_color(s) { Some(HexColor(s.to_string())) } else { None } }}
impl<'de> Deserialize<'de> for HexColor { fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: Deserializer<'de>, { let s = String::deserialize(deserializer)?; HexColor::from_text(&s).ok_or(serde::de::Error::custom("invalid hex color")) }}
impl Serialize for HexColor { fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: serde::Serializer, { self.0.serialize(serializer) }}
fn is_valid_hex_color(s: &str) -> bool { let s = s.strip_prefix('#').unwrap_or(s); matches!(s.len(), 3 | 6) && s.chars().all(|c| c.is_ascii_hexdigit())}
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]pub struct Tag { pub uuid: Uuid, pub name: String, pub color: HexColor,}
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]pub struct Category { pub uuid: Uuid, pub name: String, pub color: HexColor, pub icon: String,}