feat: create a model for tasks with subtasks

This commit is contained in:
Matouš Volf 2024-09-08 22:19:53 +02:00
parent 1479957916
commit b937d0bcb5
6 changed files with 91 additions and 7 deletions

View File

@ -7,7 +7,7 @@ use validator::Validate;
const TITLE_LENGTH_MIN: u64 = 1; const TITLE_LENGTH_MIN: u64 = 1;
const TITLE_LENGTH_MAX: u64 = 255; const TITLE_LENGTH_MAX: u64 = 255;
#[derive(Queryable, Selectable, Serialize, Deserialize, PartialEq, Clone, Debug)] #[derive(Queryable, Selectable, Identifiable, Serialize, Deserialize, PartialEq, Clone, Debug)]
#[diesel(table_name = crate::schema::projects)] #[diesel(table_name = crate::schema::projects)]
#[diesel(check_for_backend(diesel::pg::Pg))] #[diesel(check_for_backend(diesel::pg::Pg))]
pub struct Project { pub struct Project {

View File

@ -1,5 +1,6 @@
use chrono::NaiveDateTime; use crate::models::task::Task;
use crate::schema::subtasks; use crate::schema::subtasks;
use chrono::NaiveDateTime;
use diesel::prelude::*; use diesel::prelude::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use validator::Validate; use validator::Validate;
@ -7,7 +8,9 @@ use validator::Validate;
const TITLE_LENGTH_MIN: u64 = 1; const TITLE_LENGTH_MIN: u64 = 1;
const TITLE_LENGTH_MAX: u64 = 255; const TITLE_LENGTH_MAX: u64 = 255;
#[derive(Queryable, Selectable, Serialize, Deserialize, PartialEq, Clone, Debug)] #[derive(Queryable, Selectable, Identifiable, Associations, Serialize, Deserialize, PartialEq,
Clone, Debug)]
#[diesel(belongs_to(Task, foreign_key = task_id))]
#[diesel(table_name = subtasks)] #[diesel(table_name = subtasks)]
#[diesel(check_for_backend(diesel::pg::Pg))] #[diesel(check_for_backend(diesel::pg::Pg))]
pub struct Subtask { pub struct Subtask {

View File

@ -4,11 +4,12 @@ use crate::schema::tasks;
use diesel::prelude::*; use diesel::prelude::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use validator::Validate; use validator::Validate;
use crate::models::subtask::Subtask;
const TITLE_LENGTH_MIN: u64 = 1; const TITLE_LENGTH_MIN: u64 = 1;
const TITLE_LENGTH_MAX: u64 = 255; const TITLE_LENGTH_MAX: u64 = 255;
#[derive(Queryable, Selectable, Serialize, Deserialize, PartialEq, Clone, Debug)] #[derive(Queryable, Selectable, Identifiable, Serialize, Deserialize, PartialEq, Clone, Debug)]
#[diesel(table_name = tasks)] #[diesel(table_name = tasks)]
#[diesel(check_for_backend(diesel::pg::Pg))] #[diesel(check_for_backend(diesel::pg::Pg))]
pub struct Task { pub struct Task {
@ -51,6 +52,26 @@ impl Task {
} }
} }
#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)]
pub struct TaskWithSubtasks {
task: Task,
subtasks: Vec<Subtask>,
}
impl TaskWithSubtasks {
pub fn new(task: Task, subtasks: Vec<Subtask>) -> Self {
Self { task, subtasks }
}
pub fn task(&self) -> &Task {
&self.task
}
pub fn subtasks(&self) -> &Vec<Subtask> {
&self.subtasks
}
}
#[derive(Insertable, Serialize, Deserialize, Validate, Clone, Debug)] #[derive(Insertable, Serialize, Deserialize, Validate, Clone, Debug)]
#[diesel(table_name = tasks)] #[diesel(table_name = tasks)]
pub struct NewTask { pub struct NewTask {

View File

@ -3,7 +3,7 @@ use crate::errors::error_vec::ErrorVec;
use crate::models::category::Category; use crate::models::category::Category;
use crate::models::project::Project; use crate::models::project::Project;
use crate::models::subtask::Subtask; use crate::models::subtask::Subtask;
use crate::models::task::Task; use crate::models::task::{Task, TaskWithSubtasks};
pub(crate) mod tasks; pub(crate) mod tasks;
pub(crate) mod projects; pub(crate) mod projects;
@ -13,6 +13,7 @@ pub(crate) mod subtasks;
pub(crate) enum QueryValue { pub(crate) enum QueryValue {
Projects(Vec<Project>), Projects(Vec<Project>),
Tasks(Vec<Task>), Tasks(Vec<Task>),
TasksWithSubtasks(Vec<TaskWithSubtasks>),
Subtasks(Vec<Subtask>), Subtasks(Vec<Subtask>),
} }
@ -26,5 +27,6 @@ pub(crate) enum QueryKey {
Projects, Projects,
Tasks, Tasks,
TasksInCategory(Category), TasksInCategory(Category),
TasksWithSubtasksInCategory(Category),
SubtasksOfTaskId(i32), SubtasksOfTaskId(i32),
} }

View File

@ -2,7 +2,7 @@ use dioxus::prelude::ServerFnError;
use dioxus_query::prelude::{use_get_query, QueryResult, UseQuery}; use dioxus_query::prelude::{use_get_query, QueryResult, UseQuery};
use crate::models::category::Category; use crate::models::category::Category;
use crate::query::{QueryErrors, QueryKey, QueryValue}; use crate::query::{QueryErrors, QueryKey, QueryValue};
use crate::server::tasks::get_tasks_in_category; use crate::server::tasks::{get_tasks_in_category, get_tasks_with_subtasks_in_category};
@ -22,3 +22,29 @@ async fn fetch_tasks_in_category(keys: Vec<QueryKey>) -> QueryResult<QueryValue,
panic!("Unexpected query keys: {:?}", keys); panic!("Unexpected query keys: {:?}", keys);
} }
} }
pub(crate) fn use_tasks_with_subtasks_in_category_query(category: Category)
-> UseQuery<QueryValue, QueryErrors, QueryKey> {
use_get_query(
[
QueryKey::TasksWithSubtasksInCategory(
category.clone()),
QueryKey::TasksInCategory(category),
QueryKey::Tasks
],
fetch_tasks_with_subtasks_in_category
)
}
async fn fetch_tasks_with_subtasks_in_category(keys: Vec<QueryKey>)
-> QueryResult<QueryValue, QueryErrors> {
if let Some(QueryKey::TasksWithSubtasksInCategory(category)) = keys.first() {
match get_tasks_with_subtasks_in_category(category.clone()).await {
Ok(tasks) => Ok(QueryValue::TasksWithSubtasks(tasks)),
Err(ServerFnError::WrappedServerError(errors)) => Err(QueryErrors::Error(errors)),
Err(error) => panic!("Unexpected error: {:?}", error)
}.into()
} else {
panic!("Unexpected query keys: {:?}", keys);
}
}

View File

@ -1,14 +1,16 @@
use chrono::{Datelike, Days, Months, NaiveDate}; use chrono::{Datelike, Days, Months, NaiveDate};
use crate::errors::error::Error; use crate::errors::error::Error;
use crate::errors::error_vec::ErrorVec; use crate::errors::error_vec::ErrorVec;
use crate::models::task::{NewTask, Task}; use crate::models::task::{NewTask, Task, TaskWithSubtasks};
use crate::server::database_connection::establish_database_connection; use crate::server::database_connection::establish_database_connection;
use diesel::{ExpressionMethods, OptionalExtension, QueryDsl, RunQueryDsl, SelectableHelper}; use diesel::{ExpressionMethods, OptionalExtension, QueryDsl, RunQueryDsl, SelectableHelper};
use dioxus::prelude::*; use dioxus::prelude::*;
use diesel::prelude::*;
use time::util::days_in_year_month; use time::util::days_in_year_month;
use validator::Validate; use validator::Validate;
use crate::errors::task_error::TaskError; use crate::errors::task_error::TaskError;
use crate::models::category::{Category, ReoccurrenceInterval}; use crate::models::category::{Category, ReoccurrenceInterval};
use crate::models::subtask::Subtask;
use crate::server::subtasks::restore_subtasks_of_task; use crate::server::subtasks::restore_subtasks_of_task;
#[server] #[server]
@ -72,6 +74,36 @@ pub(crate) async fn get_tasks_in_category(filtered_category: Category)
Ok(results) Ok(results)
} }
#[server]
pub(crate) async fn get_tasks_with_subtasks_in_category(filtered_category: Category) -> Result<
Vec<TaskWithSubtasks>,
ServerFnError<ErrorVec<Error>>
> {
use crate::schema::tasks;
let mut connection = establish_database_connection()
.map_err::<ErrorVec<Error>, _>(|_| vec![Error::ServerInternal].into())?;
let tasks_in_category = tasks::table
.filter(filtered_category.eq_sql_predicate())
.select(Task::as_select()).load(&mut connection)
.map_err::<ErrorVec<Error>, _>(|_| vec![Error::ServerInternal].into())?;
let subtasks = Subtask::belonging_to(&tasks_in_category)
.select(Subtask::as_select())
.load(&mut connection)
.map_err::<ErrorVec<Error>, _>(|_| vec![Error::ServerInternal].into())?;
let tasks_with_subtasks = subtasks
.grouped_by(&tasks_in_category)
.into_iter()
.zip(tasks_in_category)
.map(|(pages, book)| TaskWithSubtasks::new(book, pages))
.collect();
Ok(tasks_with_subtasks)
}
#[server] #[server]
pub(crate) async fn edit_task(task_id: i32, new_task: NewTask) pub(crate) async fn edit_task(task_id: i32, new_task: NewTask)
-> Result<Task, ServerFnError<ErrorVec<TaskError>>> { -> Result<Task, ServerFnError<ErrorVec<TaskError>>> {