feat: ability to manage subtasks #40

Merged
matous-volf merged 6 commits from feat/subtasks into main 2024-09-08 18:38:51 +00:00
19 changed files with 799 additions and 303 deletions

View File

@ -0,0 +1,4 @@
-- This file should undo anything in `up.sql`
DROP TABLE IF EXISTS "subtasks";

View File

@ -0,0 +1,15 @@
-- Your SQL goes here
CREATE TABLE "subtasks"(
"id" SERIAL NOT NULL PRIMARY KEY,
"task_id" INT4 NOT NULL,
"title" TEXT NOT NULL,
"is_completed" BOOL NOT NULL,
"created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY ("task_id") REFERENCES "tasks"("id") ON DELETE CASCADE
);
SELECT diesel_manage_updated_at('subtasks');

View File

@ -36,11 +36,11 @@ pub(crate) fn BottomPanel(display_form: Signal<bool>) -> Element {
rsx! {
div {
class: format!(
"pointer-events-auto bg-zinc-700/50 rounded-t-xl border-t-zinc-600 border-t backdrop-blur drop-shadow-[0_-5px_10px_rgba(0,0,0,0.2)] transition-[height] duration-[500ms] ease-[cubic-bezier(0.79,0.14,0.15,0.86)] {}",
"pointer-events-auto bg-zinc-700/50 rounded-t-xl border-t-zinc-600 border-t backdrop-blur drop-shadow-[0_-5px_10px_rgba(0,0,0,0.2)] transition-[height] duration-[500ms] ease-[cubic-bezier(0.79,0.14,0.15,0.86)] overflow-y-scroll {}",
match (display_form(), current_route, navigation_expanded()) {
(false, _, false) => "h-[64px]",
(false, _, true) => "h-[128px]",
(true, Route::ProjectsPage, _) => "h-[128px]",
(false, _, false) => "h-[66px]",
(false, _, true) => "h-[130px]",
(true, Route::ProjectsPage, _) => "h-[130px]",
(true, _, _) => "h-[448px]",
}
),

View File

@ -12,3 +12,4 @@ pub(crate) mod category_input;
pub(crate) mod reoccurrence_input;
pub(crate) mod layout;
pub(crate) mod navigation_item;
pub(crate) mod subtasks_form;

View File

@ -41,8 +41,8 @@ pub(crate) fn CategoryCalendarPage() -> Element {
.format_localized(
format!(
"%A %-d. %B{}",
if date_current.year() != today_date.year() {" %Y"}
else {""}
if date_current.year() != today_date.year()
{" %Y"} else {""}
).as_str(),
Locale::en_US
)

View File

@ -0,0 +1,158 @@
use crate::models::subtask::NewSubtask;
use crate::query::subtasks::use_subtasks_of_task_query;
use crate::query::{QueryErrors, QueryKey, QueryValue};
use crate::server::subtasks::{create_subtask, delete_subtask, edit_subtask};
use dioxus::core_macro::{component, rsx};
use dioxus::dioxus_core::Element;
use dioxus::prelude::*;
use dioxus_query::prelude::{use_query_client, QueryResult};
#[component]
pub(crate) fn SubtasksForm(task_id: i32) -> Element {
let query_client = use_query_client::<QueryValue, QueryErrors, QueryKey>();
let subtasks_query = use_subtasks_of_task_query(task_id);
let mut new_title = use_signal(String::new);
rsx! {
form {
class: "flex flex-row items-center gap-3",
onsubmit: move |event| async move {
let new_subtask = NewSubtask::new(
task_id,
event.values().get("title").unwrap().as_value(),
false
);
let _ = create_subtask(new_subtask).await;
query_client.invalidate_queries(&[QueryKey::SubtasksOfTaskId(task_id)]);
new_title.set(String::new());
},
label {
r#for: "input_new_title",
class: "min-w-6 text-center",
i {
class: "fa-solid fa-list-check text-zinc-400/50"
}
}
div {
class: "grow grid grid-cols-6 gap-2",
input {
name: "title",
required: true,
value: new_title,
r#type: "text",
class: "grow py-2 px-3 col-span-5 bg-zinc-800/50 rounded-lg",
id: "input_new_title",
onchange: move |event| new_title.set(event.value())
}
button {
r#type: "submit",
class: "py-2 col-span-1 bg-zinc-800/50 rounded-lg",
i {
class: "fa-solid fa-plus"
}
}
}
}
match subtasks_query.result().value() {
QueryResult::Ok(QueryValue::Subtasks(subtasks))
| QueryResult::Loading(Some(QueryValue::Subtasks(subtasks))) => {
rsx! {
for subtask in subtasks.clone() {
div {
key: "{subtask.id()}",
class: "flex flex-row items-center gap-3",
i {
class: format!(
"{} min-w-6 text-center text-2xl text-zinc-400/50",
if subtask.is_completed() {
"fa solid fa-square-check"
} else {
"fa-regular fa-square"
}
),
onclick: {
let subtask = subtask.clone();
move |_| {
let subtask = subtask.clone();
async move {
let new_subtask = NewSubtask::new(
subtask.task_id(),
subtask.title().to_owned(),
!subtask.is_completed()
);
let _ = edit_subtask(
subtask.id(),
new_subtask
).await;
query_client.invalidate_queries(&[
QueryKey::SubtasksOfTaskId(task_id)
]);
}
}
}
}
div {
class: "grow grid grid-cols-6 gap-2",
input {
r#type: "text",
class: "grow py-2 px-3 col-span-5 bg-zinc-800/50 rounded-lg",
id: "input_new_title",
initial_value: subtask.title(),
onchange: {
let subtask = subtask.clone();
move |event| {
let subtask = subtask.clone();
async move {
let new_subtask = NewSubtask::new(
subtask.task_id(),
event.value(),
subtask.is_completed()
);
let _ = edit_subtask(
subtask.id(),
new_subtask
).await;
query_client.invalidate_queries(&[
QueryKey::SubtasksOfTaskId(task_id)
]);
}
}
}
}
button {
r#type: "button",
class: "py-2 col-span-1 bg-zinc-800/50 rounded-lg",
onclick: {
let subtask = subtask.clone();
move |_| {
let subtask = subtask.clone();
async move {
let _ = delete_subtask(subtask.id()).await;
query_client.invalidate_queries(&[
QueryKey::SubtasksOfTaskId(task_id)
]);
}
}
},
i {
class: "fa-solid fa-trash-can"
}
}
}
}
}
}
},
QueryResult::Loading(None) => rsx! {
// TODO: Add a loading indicator.
},
QueryResult::Err(errors) => rsx! {
div {
"Errors occurred: {errors:?}"
}
},
value => panic!("Unexpected query result: {value:?}")
}
}
}

View File

@ -12,6 +12,7 @@ use dioxus::core_macro::{component, rsx};
use dioxus::dioxus_core::Element;
use dioxus::prelude::*;
use dioxus_query::prelude::use_query_client;
use crate::components::subtasks_form::SubtasksForm;
const REMINDER_OFFSETS: [Option<Duration>; 17] = [
None,
@ -79,7 +80,11 @@ pub(crate) fn TaskForm(task: Option<Task>, on_successful_submit: EventHandler<()
let task_for_submit = task.clone();
rsx! {
div {
class: "p-4 flex flex-col gap-4",
form {
class: "flex flex-col gap-4",
id: "form_task",
onsubmit: move |event| {
let task = task_for_submit.clone();
async move {
@ -99,7 +104,8 @@ pub(crate) fn TaskForm(task: Option<Task>, on_successful_submit: EventHandler<()
event.values().get("category_calendar_date").unwrap()
.as_value().parse().unwrap(),
reoccurrence_interval,
event.values().get("category_calendar_reoccurrence_length")
event.values()
.get("category_calendar_reoccurrence_length")
.unwrap().as_value().parse().unwrap()
)
),
@ -109,8 +115,8 @@ pub(crate) fn TaskForm(task: Option<Task>, on_successful_submit: EventHandler<()
time,
REMINDER_OFFSETS[
event.values()
.get("category_calendar_reminder_offset_index").unwrap()
.as_value().parse::<usize>().unwrap()
.get("category_calendar_reminder_offset_index")
.unwrap().as_value().parse::<usize>().unwrap()
]
)
)
@ -132,7 +138,6 @@ pub(crate) fn TaskForm(task: Option<Task>, on_successful_submit: EventHandler<()
on_successful_submit.call(());
}
},
class: "p-4 flex flex-col gap-4",
div {
class: "flex flex-row items-center gap-3",
label {
@ -286,9 +291,11 @@ pub(crate) fn TaskForm(task: Option<Task>, on_successful_submit: EventHandler<()
disabled: category_calendar_reoccurrence_interval().is_none(),
required: true,
min: 1,
initial_value: category_calendar_reoccurrence_interval()
.map_or(String::new(), |_| reoccurrence.map_or(1, |reoccurrence|
reoccurrence.length()).to_string()),
initial_value: category_calendar_reoccurrence_interval().map_or(
String::new(),
|_| reoccurrence.map_or(1, |reoccurrence|
reoccurrence.length()).to_string()
),
class: "py-2 px-3 bg-zinc-800/50 rounded-lg col-span-2 text-right",
id: "category_calendar_reoccurrence_length"
}
@ -334,7 +341,13 @@ pub(crate) fn TaskForm(task: Option<Task>, on_successful_submit: EventHandler<()
}
},
_ => None
}
},
if let Some(task) = task.as_ref() {
SubtasksForm {
task_id: task.id()
}
}
div {
class: "flex flex-row justify-between mt-auto",
button {
@ -370,6 +383,7 @@ pub(crate) fn TaskForm(task: Option<Task>, on_successful_submit: EventHandler<()
}
}
button {
form: "form_task",
r#type: "submit",
class: "py-2 px-4 bg-zinc-300/50 rounded-lg",
i {

View File

@ -15,9 +15,7 @@ pub(crate) fn TaskList(tasks: Vec<Task>, class: Option<&'static str>) -> Element
rsx! {
div {
class: format!("flex flex-col {}", class.unwrap_or("")),
{tasks.iter().cloned().map(|task| {
let task_clone = task.clone();
rsx! {
for task in tasks.clone() {
div {
key: "{task.id()}",
class: format!(
@ -37,28 +35,39 @@ pub(crate) fn TaskList(tasks: Vec<Task>, class: Option<&'static str>) -> Element
"bg-zinc-700"
} else { "" }
),
onclick: move |_| task_being_edited.set(Some(task.clone())),
onclick: {
let task = task.clone();
move |_| task_being_edited.set(Some(task.clone()))
},
i {
class: format!(
"{} text-3xl text-zinc-500",
if *(task_clone.category()) == Category::Done {
if *(task.category()) == Category::Done {
"fa solid fa-square-check"
} else {
"fa-regular fa-square"
}
),
onclick: move |event| {
onclick: {
let task = task.clone();
move |event| {
// To prevent editing the task.
event.stop_propagation();
let task = task_clone.clone();
let task = task.clone();
async move {
let completed_task = complete_task(task.id()).await;
query_client.invalidate_queries(&[
let mut query_keys = vec![
QueryKey::Tasks,
QueryKey::TasksInCategory(
completed_task.unwrap().category().clone()
),
]);
)
];
if let Category::Calendar { reoccurrence: Some(_), .. }
= task.category() {
query_keys.push(QueryKey::SubtasksOfTaskId(task.id()));
}
query_client.invalidate_queries(&query_keys);
}
}
}
},
@ -94,7 +103,6 @@ pub(crate) fn TaskList(tasks: Vec<Task>, class: Option<&'static str>) -> Element
}
}
}
})}
}
}
}

View File

@ -2,3 +2,4 @@ pub(crate) mod error;
pub(crate) mod error_vec;
pub(crate) mod project_error;
pub(crate) mod task_error;
pub(crate) mod subtask_error;

View File

@ -0,0 +1,70 @@
use crate::errors::error::Error;
use crate::errors::error_vec::ErrorVec;
use serde::{Deserialize, Serialize};
use std::fmt::Display;
use std::str::FromStr;
use validator::{ValidationErrors, ValidationErrorsKind};
#[derive(Serialize, Deserialize, Debug)]
pub enum SubtaskError {
TitleLengthInvalid,
TaskNotFound,
Error(Error),
}
impl From<ValidationErrors> for ErrorVec<SubtaskError> {
fn from(validation_errors: ValidationErrors) -> Self {
validation_errors.errors()
.iter()
.flat_map(|(&field, error_kind)| match field {
"title" => match error_kind {
ValidationErrorsKind::Field(validation_errors) => validation_errors
.iter()
.map(|validation_error| validation_error.code.as_ref())
.map(|code| match code {
"title_length" => SubtaskError::TitleLengthInvalid,
_ => panic!("Unexpected validation error code: `{code}`."),
})
.collect::<Vec<SubtaskError>>(),
_ => panic!("Unexpected validation error kind."),
},
_ => panic!("Unexpected validation field name: `{field}`."),
})
.collect::<Vec<SubtaskError>>()
.into()
}
}
impl From<diesel::result::Error> for SubtaskError {
fn from(diesel_error: diesel::result::Error) -> Self {
match diesel_error {
diesel::result::Error::DatabaseError(
diesel::result::DatabaseErrorKind::ForeignKeyViolation, info
) => {
match info.constraint_name() {
Some("subtasks_task_id_fkey") => Self::TaskNotFound,
_ => Self::Error(Error::ServerInternal)
}
}
_ => {
Self::Error(Error::ServerInternal)
}
}
}
}
// Has to be implemented for Dioxus server functions.
impl Display for SubtaskError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
// Has to be implemented for Dioxus server functions.
impl FromStr for SubtaskError {
type Err = ();
fn from_str(_: &str) -> Result<Self, Self::Err> {
Ok(Self::Error(Error::ServerInternal))
}
}
coderabbitai[bot] commented 2024-09-08 17:59:20 +00:00 (Migrated from github.com)
Review

Comprehensive error handling with room for improvement.

The SubtaskError enum and its conversions are well-implemented to handle various error scenarios effectively.

Consider replacing panic! in the error handling code with more graceful error handling mechanisms to prevent potential crashes.

**Comprehensive error handling with room for improvement.** The `SubtaskError` enum and its conversions are well-implemented to handle various error scenarios effectively. Consider replacing `panic!` in the error handling code with more graceful error handling mechanisms to prevent potential crashes. <!-- This is an auto-generated comment by CodeRabbit -->

View File

@ -1,3 +1,4 @@
pub(crate) mod project;
pub(crate) mod category;
pub(crate) mod task;
pub(crate) mod subtask;

67
src/models/subtask.rs Normal file
View File

@ -0,0 +1,67 @@
use chrono::NaiveDateTime;
use crate::schema::subtasks;
use diesel::prelude::*;
use serde::{Deserialize, Serialize};
use validator::Validate;
const TITLE_LENGTH_MIN: u64 = 1;
const TITLE_LENGTH_MAX: u64 = 255;
#[derive(Queryable, Selectable, Serialize, Deserialize, PartialEq, Clone, Debug)]
#[diesel(table_name = subtasks)]
#[diesel(check_for_backend(diesel::pg::Pg))]
pub struct Subtask {
id: i32,
task_id: i32,
title: String,
is_completed: bool,
created_at: NaiveDateTime,
updated_at: NaiveDateTime,
}
impl Subtask {
pub fn id(&self) -> i32 {
self.id
}
pub fn task_id(&self) -> i32 {
self.task_id
}
pub fn title(&self) -> &str {
&self.title
}
pub fn is_completed(&self) -> bool {
self.is_completed
}
pub fn created_at(&self) -> NaiveDateTime {
self.created_at
}
pub fn updated_at(&self) -> NaiveDateTime {
self.updated_at
}
coderabbitai[bot] commented 2024-09-08 17:59:19 +00:00 (Migrated from github.com)
Review

Well-defined struct and methods.

The Subtask struct is well-defined with appropriate fields and ORM annotations. Methods for accessing the fields are correctly implemented.

Consider adding documentation comments for public methods to enhance code readability and maintainability.

**Well-defined struct and methods.** The `Subtask` struct is well-defined with appropriate fields and ORM annotations. Methods for accessing the fields are correctly implemented. Consider adding documentation comments for public methods to enhance code readability and maintainability. <!-- This is an auto-generated comment by CodeRabbit -->
}
#[derive(Insertable, Serialize, Deserialize, Validate, Clone, Debug)]
#[diesel(table_name = subtasks)]
pub struct NewSubtask {
pub task_id: i32,
#[validate(length(min = "TITLE_LENGTH_MIN", max = "TITLE_LENGTH_MAX", code = "title_length"))]
pub title: String,
pub is_completed: bool,
}
impl NewSubtask {
pub fn new(task_id: i32, title: String, is_completed: bool) -> Self {
Self { task_id, title, is_completed }
}
}
coderabbitai[bot] commented 2024-09-08 17:59:19 +00:00 (Migrated from github.com)
Review

Properly structured for new entries with validation.

The NewSubtask struct is well-structured for creating new subtask entries, with appropriate validations in place.

Consider adding error handling in the new method to manage validation failures gracefully.

**Properly structured for new entries with validation.** The `NewSubtask` struct is well-structured for creating new subtask entries, with appropriate validations in place. Consider adding error handling in the `new` method to manage validation failures gracefully. <!-- This is an auto-generated comment by CodeRabbit -->
impl From<Subtask> for NewSubtask {
fn from(subtask: Subtask) -> Self {
Self::new(subtask.task_id, subtask.title, subtask.is_completed)
}
}

View File

@ -9,7 +9,7 @@ const TITLE_LENGTH_MIN: u64 = 1;
const TITLE_LENGTH_MAX: u64 = 255;
#[derive(Queryable, Selectable, Serialize, Deserialize, PartialEq, Clone, Debug)]
#[diesel(table_name = crate::schema::tasks)]
#[diesel(table_name = tasks)]
#[diesel(check_for_backend(diesel::pg::Pg))]
pub struct Task {
id: i32,

View File

@ -2,15 +2,18 @@ use crate::errors::error::Error;
use crate::errors::error_vec::ErrorVec;
use crate::models::category::Category;
use crate::models::project::Project;
use crate::models::subtask::Subtask;
use crate::models::task::Task;
pub(crate) mod tasks;
pub(crate) mod projects;
pub(crate) mod subtasks;
#[derive(PartialEq, Debug)]
pub(crate) enum QueryValue {
Tasks(Vec<Task>),
Projects(Vec<Project>),
Tasks(Vec<Task>),
Subtasks(Vec<Subtask>),
}
#[derive(Debug)]
@ -20,7 +23,8 @@ pub(crate) enum QueryErrors {
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
pub(crate) enum QueryKey {
Projects,
Tasks,
TasksInCategory(Category),
Projects,
SubtasksOfTaskId(i32),
}

21
src/query/subtasks.rs Normal file
View File

@ -0,0 +1,21 @@
use crate::query::{QueryErrors, QueryKey, QueryValue};
use crate::server::subtasks::get_subtasks_of_task;
use dioxus::prelude::ServerFnError;
use dioxus_query::prelude::{use_get_query, QueryResult, UseQuery};
pub(crate) fn use_subtasks_of_task_query(task_id: i32)
-> UseQuery<QueryValue, QueryErrors, QueryKey> {
use_get_query([QueryKey::SubtasksOfTaskId(task_id)], fetch_subtasks_of_task)
}
async fn fetch_subtasks_of_task(keys: Vec<QueryKey>) -> QueryResult<QueryValue, QueryErrors> {
if let Some(QueryKey::SubtasksOfTaskId(task_id)) = keys.first() {
match get_subtasks_of_task(*task_id).await {
Ok(subtasks) => Ok(QueryValue::Subtasks(subtasks)),
Err(ServerFnError::WrappedServerError(errors)) => Err(QueryErrors::Error(errors)),
Err(error) => panic!("Unexpected error: {:?}", error)
}.into()
} else {
panic!("Unexpected query keys: {:?}", keys);
}
}
coderabbitai[bot] commented 2024-09-08 17:59:19 +00:00 (Migrated from github.com)
Review

Approved async function for fetching subtasks with a suggestion to improve error handling.

The function fetch_subtasks_of_task is crucial for fetching subtasks based on task ID. Consider handling unexpected errors more gracefully instead of using panic!, which could lead to service disruption.

Consider replacing panic! with a more graceful error handling mechanism, such as logging the error and returning a controlled error response.

**Approved async function for fetching subtasks with a suggestion to improve error handling.** The function `fetch_subtasks_of_task` is crucial for fetching subtasks based on task ID. Consider handling unexpected errors more gracefully instead of using `panic!`, which could lead to service disruption. Consider replacing `panic!` with a more graceful error handling mechanism, such as logging the error and returning a controlled error response. <!-- This is an auto-generated comment by CodeRabbit -->

View File

@ -9,6 +9,17 @@ diesel::table! {
}
}
diesel::table! {
subtasks (id) {
id -> Int4,
task_id -> Int4,
title -> Text,
is_completed -> Bool,
created_at -> Timestamp,
updated_at -> Timestamp,
}
}
diesel::table! {
tasks (id) {
id -> Int4,
@ -21,9 +32,11 @@ diesel::table! {
}
}
diesel::joinable!(subtasks -> tasks (task_id));
diesel::joinable!(tasks -> projects (project_id));
diesel::allow_tables_to_appear_in_same_query!(
projects,
subtasks,
tasks,
);

View File

@ -1,3 +1,4 @@
mod database_connection;
pub(crate) mod projects;
pub(crate) mod tasks;
pub(crate) mod subtasks;

115
src/server/subtasks.rs Normal file
View File

@ -0,0 +1,115 @@
use crate::errors::error::Error;
use crate::errors::error_vec::ErrorVec;
use crate::errors::subtask_error::SubtaskError;
use crate::models::subtask::{NewSubtask, Subtask};
use crate::server::database_connection::establish_database_connection;
use diesel::{ExpressionMethods, QueryDsl, RunQueryDsl, SelectableHelper};
use dioxus::prelude::*;
use validator::Validate;
#[server]
pub(crate) async fn create_subtask(new_subtask: NewSubtask)
-> Result<Subtask, ServerFnError<ErrorVec<SubtaskError>>> {
use crate::schema::subtasks;
new_subtask.validate()
.map_err::<ErrorVec<SubtaskError>, _>(|errors| errors.into())?;
let mut connection = establish_database_connection()
.map_err::<ErrorVec<SubtaskError>, _>(
|_| vec![SubtaskError::Error(Error::ServerInternal)].into()
)?;
let created_subtask = diesel::insert_into(subtasks::table)
.values(&new_subtask)
.returning(Subtask::as_returning())
.get_result(&mut connection)
.map_err::<ErrorVec<SubtaskError>, _>(|error| vec![error.into()].into())?;
Ok(created_subtask)
coderabbitai[bot] commented 2024-09-08 17:59:20 +00:00 (Migrated from github.com)
Review

Review of create_subtask function

The function correctly handles validation and error mapping, which are crucial for maintaining data integrity and providing meaningful error messages to the client. The use of establish_database_connection and the subsequent error handling ensure that database errors are appropriately managed.

However, consider adding more specific error handling for different types of database errors to enhance the robustness of the function. Additionally, ensure that the database interactions are optimized for performance, especially considering the potential high frequency of subtask creation in a task management application.

**Review of `create_subtask` function** The function correctly handles validation and error mapping, which are crucial for maintaining data integrity and providing meaningful error messages to the client. The use of `establish_database_connection` and the subsequent error handling ensure that database errors are appropriately managed. However, consider adding more specific error handling for different types of database errors to enhance the robustness of the function. Additionally, ensure that the database interactions are optimized for performance, especially considering the potential high frequency of subtask creation in a task management application. <!-- This is an auto-generated comment by CodeRabbit -->
}
#[server]
pub(crate) async fn get_subtasks_of_task(filtered_task_id: i32)
-> Result<Vec<Subtask>, ServerFnError<ErrorVec<Error>>> {
use crate::schema::subtasks::dsl::*;
let mut connection = establish_database_connection()
.map_err::<ErrorVec<Error>, _>(
|_| vec![Error::ServerInternal].into()
)?;
let results = subtasks
.select(Subtask::as_select())
.filter(task_id.eq(filtered_task_id))
.load::<Subtask>(&mut connection)
.map_err::<ErrorVec<Error>, _>(
|_| vec![Error::ServerInternal].into()
)?;
Ok(results)
coderabbitai[bot] commented 2024-09-08 17:59:20 +00:00 (Migrated from github.com)
Review

Review of get_subtasks_of_task function

The function is well-implemented with clear error handling and database interaction. The use of a filter on task_id ensures that only relevant subtasks are retrieved, which is efficient.

Consider adding logging for the database operations to aid in debugging and monitoring the application's performance. Additionally, review the database schema to ensure that appropriate indexes are in place for the task_id column to optimize query performance.

**Review of `get_subtasks_of_task` function** The function is well-implemented with clear error handling and database interaction. The use of a filter on `task_id` ensures that only relevant subtasks are retrieved, which is efficient. Consider adding logging for the database operations to aid in debugging and monitoring the application's performance. Additionally, review the database schema to ensure that appropriate indexes are in place for the `task_id` column to optimize query performance. <!-- This is an auto-generated comment by CodeRabbit -->
}
#[server]
pub(crate) async fn edit_subtask(subtask_id: i32, new_subtask: NewSubtask)
-> Result<Subtask, ServerFnError<ErrorVec<SubtaskError>>> {
use crate::schema::subtasks::dsl::*;
new_subtask.validate()
.map_err::<ErrorVec<SubtaskError>, _>(|errors| errors.into())?;
let mut connection = establish_database_connection()
.map_err::<ErrorVec<SubtaskError>, _>(
|_| vec![SubtaskError::Error(Error::ServerInternal)].into()
)?;
let updated_task = diesel::update(subtasks)
.filter(id.eq(subtask_id))
.set((
title.eq(new_subtask.title),
is_completed.eq(new_subtask.is_completed)
))
.returning(Subtask::as_returning())
.get_result(&mut connection)
.map_err::<ErrorVec<SubtaskError>, _>(|error| vec![error.into()].into())?;
Ok(updated_task)
coderabbitai[bot] commented 2024-09-08 17:59:20 +00:00 (Migrated from github.com)
Review

Review of edit_subtask function

The function effectively handles validation and error mapping, which are essential for maintaining data integrity. The update operation is secured by filtering on subtask_id, which prevents unauthorized modifications to other subtasks.

However, consider implementing optimistic concurrency control to handle cases where multiple users might attempt to edit the same subtask simultaneously. This would enhance the robustness and reliability of the function.

**Review of `edit_subtask` function** The function effectively handles validation and error mapping, which are essential for maintaining data integrity. The update operation is secured by filtering on `subtask_id`, which prevents unauthorized modifications to other subtasks. However, consider implementing optimistic concurrency control to handle cases where multiple users might attempt to edit the same subtask simultaneously. This would enhance the robustness and reliability of the function. <!-- This is an auto-generated comment by CodeRabbit -->
}
#[server]
pub(crate) async fn restore_subtasks_of_task(filtered_task_id: i32) -> Result<
Vec<Subtask>,
ServerFnError<ErrorVec<SubtaskError>>
> {
use crate::schema::subtasks::dsl::*;
let mut connection = establish_database_connection()
.map_err::<ErrorVec<SubtaskError>, _>(
|_| vec![SubtaskError::Error(Error::ServerInternal)].into()
)?;
let updated_subtasks = diesel::update(subtasks)
.filter(task_id.eq(filtered_task_id))
.set(is_completed.eq(false))
.returning(Subtask::as_returning())
.get_results(&mut connection)
.map_err::<ErrorVec<SubtaskError>, _>(|error| vec![error.into()].into())?;
Ok(updated_subtasks)
coderabbitai[bot] commented 2024-09-08 17:59:20 +00:00 (Migrated from github.com)
Review

Review of restore_subtasks_of_task function

The function correctly handles the restoration of subtasks to an uncompleted state, which is a useful feature for task management applications. The error handling and database interactions are appropriately managed.

However, ensure that the filter on task_id is correctly set to avoid affecting subtasks from other tasks inadvertently. Additionally, consider adding a confirmation mechanism or additional checks before performing the update to prevent accidental data modifications.

**Review of `restore_subtasks_of_task` function** The function correctly handles the restoration of subtasks to an uncompleted state, which is a useful feature for task management applications. The error handling and database interactions are appropriately managed. However, ensure that the filter on `task_id` is correctly set to avoid affecting subtasks from other tasks inadvertently. Additionally, consider adding a confirmation mechanism or additional checks before performing the update to prevent accidental data modifications. <!-- This is an auto-generated comment by CodeRabbit -->
}
// TODO: Get rid of this suppression.
//noinspection DuplicatedCode
#[server]
pub(crate) async fn delete_subtask(subtask_id: i32)
-> Result<(), ServerFnError<ErrorVec<Error>>> {
use crate::schema::subtasks::dsl::*;
let mut connection = establish_database_connection()
.map_err::<ErrorVec<Error>, _>(|_| vec![Error::ServerInternal].into())?;
diesel::delete(subtasks.filter(id.eq(subtask_id))).execute(&mut connection)
.map_err::<ErrorVec<Error>, _>(|error| vec![error.into()].into())?;
Ok(())
coderabbitai[bot] commented 2024-09-08 17:59:20 +00:00 (Migrated from github.com)
Review

Review of delete_subtask function

The function effectively handles the deletion of a specific subtask, with clear error handling and database interaction. The use of a filter on subtask_id ensures that only the intended subtask is deleted, which is crucial for maintaining data integrity.

Consider implementing soft deletion (marking subtasks as deleted without actually removing them from the database) to allow for data recovery in case of accidental deletions. This would enhance the robustness and user experience of the application.

**Review of `delete_subtask` function** The function effectively handles the deletion of a specific subtask, with clear error handling and database interaction. The use of a filter on `subtask_id` ensures that only the intended subtask is deleted, which is crucial for maintaining data integrity. Consider implementing soft deletion (marking subtasks as deleted without actually removing them from the database) to allow for data recovery in case of accidental deletions. This would enhance the robustness and user experience of the application. <!-- This is an auto-generated comment by CodeRabbit -->
}

View File

@ -9,6 +9,7 @@ use time::util::days_in_year_month;
use validator::Validate;
use crate::errors::task_error::TaskError;
use crate::models::category::{Category, ReoccurrenceInterval};
use crate::server::subtasks::restore_subtasks_of_task;
#[server]
pub(crate) async fn create_task(new_task: NewTask)
@ -127,6 +128,8 @@ pub(crate) async fn complete_task(task_id: i32) -> Result<Task, ServerFnError<Er
).unwrap()
}
}
restore_subtasks_of_task(task_id).await
.map_err::<ErrorVec<Error>, _>(|_| vec![Error::ServerInternal].into())?;
} else {
new_task.category = Category::Done;
}