feat: create a task model
This commit is contained in:
		
							
								
								
									
										3
									
								
								migrations/2024-08-19-105140_create_tasks/down.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								migrations/2024-08-19-105140_create_tasks/down.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| -- This file should undo anything in `up.sql` | ||||
|  | ||||
| DROP TABLE IF EXISTS "tasks"; | ||||
							
								
								
									
										11
									
								
								migrations/2024-08-19-105140_create_tasks/up.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								migrations/2024-08-19-105140_create_tasks/up.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| -- Your SQL goes here | ||||
|  | ||||
| CREATE TABLE "tasks"( | ||||
| 	"id" SERIAL NOT NULL PRIMARY KEY, | ||||
| 	"title" TEXT NOT NULL, | ||||
| 	"deadline" DATE, | ||||
| 	"category" JSONB NOT NULL, | ||||
| 	"project_id" INT4, | ||||
| 	FOREIGN KEY ("project_id") REFERENCES "projects"("id") | ||||
| ); | ||||
|  | ||||
| @@ -1,3 +1,4 @@ | ||||
| pub(crate) mod error; | ||||
| pub(crate) mod error_vec; | ||||
| pub(crate) mod project_create_error; | ||||
| pub(crate) mod task_create_error; | ||||
|   | ||||
							
								
								
									
										52
									
								
								src/errors/task_create_error.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								src/errors/task_create_error.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,52 @@ | ||||
| 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 TaskCreateError { | ||||
|     TitleLengthInvalid, | ||||
|     ProjectNotFound, | ||||
|     Error(Error), | ||||
| } | ||||
|  | ||||
| impl From<ValidationErrors> for ErrorVec<TaskCreateError> { | ||||
|     fn from(e: ValidationErrors) -> Self { | ||||
|         e.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" => TaskCreateError::TitleLengthInvalid, | ||||
|                             _ => panic!("Unexpected validation error code: `{code}`."), | ||||
|                         }) | ||||
|                         .collect::<Vec<TaskCreateError>>(), | ||||
|                     _ => panic!("Unexpected validation error kind."), | ||||
|                 }, | ||||
|                 _ => panic!("Unexpected validation field name: `{field}`."), | ||||
|             }) | ||||
|             .collect::<Vec<TaskCreateError>>() | ||||
|             .into() | ||||
|     } | ||||
| } | ||||
|  | ||||
| // has to be implemented for Dioxus server functions | ||||
| impl Display for TaskCreateError { | ||||
|     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 TaskCreateError { | ||||
|     type Err = (); | ||||
|  | ||||
|     fn from_str(_: &str) -> Result<Self, Self::Err> { | ||||
|         Ok(TaskCreateError::TitleLengthInvalid) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										65
									
								
								src/models/category.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								src/models/category.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,65 @@ | ||||
| use chrono::{Duration, NaiveDate, NaiveTime}; | ||||
| use diesel::deserialize::FromSql; | ||||
| use diesel::pg::{Pg, PgValue}; | ||||
| use diesel::serialize::{Output, ToSql}; | ||||
| use diesel::sql_types::Jsonb; | ||||
| use diesel::{AsExpression, FromSqlRow}; | ||||
| use serde::{Deserialize, Serialize}; | ||||
| use serde_with::DurationSeconds; | ||||
| use std::io::Write; | ||||
|  | ||||
| #[serde_with::serde_as] | ||||
| #[derive(AsExpression, FromSqlRow, Serialize, Deserialize, Clone, Debug)] | ||||
| #[diesel(sql_type = Jsonb)] | ||||
| pub enum Category { | ||||
|     Inbox, | ||||
|     SomedayMaybe, | ||||
|     WaitingFor(String), | ||||
|     NextSteps, | ||||
|     Calendar { | ||||
|         date: NaiveDate, | ||||
|         #[serde_as(as = "Option<DurationSeconds<i64>>")] | ||||
|         reoccurance_interval: Option<Duration>, | ||||
|         time: Option<CalendarTime>, | ||||
|     }, | ||||
|     LongTerm, | ||||
|     Done, | ||||
|     Trash, | ||||
| } | ||||
|  | ||||
| #[serde_with::serde_as] | ||||
| #[derive(Serialize, Deserialize, Clone, Debug)] | ||||
| pub struct CalendarTime { | ||||
|     time: NaiveTime, | ||||
|     #[serde_as(as = "Option<DurationSeconds<i64>>")] | ||||
|     reminder_offset: Option<Duration>, | ||||
| } | ||||
|  | ||||
| impl CalendarTime { | ||||
|     pub fn new(time: NaiveTime, reminder_offset: Option<Duration>) -> Self { | ||||
|         Self { time, reminder_offset } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl ToSql<Jsonb, Pg> for Category { | ||||
|     fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Pg>) -> diesel::serialize::Result { | ||||
|         let json = serde_json::to_string(self)?; | ||||
|  | ||||
|         // Prepend the JSONB version byte. | ||||
|         out.write_all(&[1])?; | ||||
|         out.write_all(json.as_bytes())?; | ||||
|  | ||||
|         Ok(diesel::serialize::IsNull::No) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl FromSql<Jsonb, Pg> for Category { | ||||
|     fn from_sql(bytes: PgValue) -> diesel::deserialize::Result<Self> { | ||||
|         let bytes = bytes.as_bytes(); | ||||
|         if bytes.is_empty() { | ||||
|             return Err("Unexpected empty bytes (missing the JSONB version number).".into()); | ||||
|         } | ||||
|         let str = std::str::from_utf8(&bytes[1..])?; | ||||
|         serde_json::from_str(str).map_err(Into::into) | ||||
|     } | ||||
| } | ||||
| @@ -1 +1,3 @@ | ||||
| pub(crate) mod project; | ||||
| pub(crate) mod category; | ||||
| pub(crate) mod task; | ||||
|   | ||||
							
								
								
									
										60
									
								
								src/models/task.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								src/models/task.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,60 @@ | ||||
| use diesel::prelude::*; | ||||
| use serde::{Deserialize, Serialize}; | ||||
| use validator::Validate; | ||||
| use crate::models::category::Category; | ||||
| use crate::schema::tasks; | ||||
|  | ||||
| const TITLE_LENGTH_MIN: u64 = 1; | ||||
| const TITLE_LENGTH_MAX: u64 = 255; | ||||
|  | ||||
| #[derive(Queryable, Selectable, Serialize, Deserialize, Clone, Debug)] | ||||
| #[diesel(table_name = crate::schema::tasks)] | ||||
| #[diesel(check_for_backend(diesel::pg::Pg))] | ||||
| pub struct Task { | ||||
|     id: i32, | ||||
|     title: String, | ||||
|     deadline: Option<chrono::NaiveDate>, | ||||
|     category: Category, | ||||
|     project_id: Option<i32>, | ||||
| } | ||||
|  | ||||
| impl Task { | ||||
|     pub fn id(&self) -> i32 { | ||||
|         self.id | ||||
|     } | ||||
|  | ||||
|     pub fn title(&self) -> &str { | ||||
|         &self.title | ||||
|     } | ||||
|  | ||||
|     pub fn deadline(&self) -> Option<chrono::NaiveDate> { | ||||
|         self.deadline | ||||
|     } | ||||
|  | ||||
|     pub fn category(&self) -> &Category { | ||||
|         &self.category | ||||
|     } | ||||
|  | ||||
|     pub fn project_id(&self) -> Option<i32> { | ||||
|         self.project_id | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Insertable, Serialize, Deserialize, Validate, Clone, Debug)] | ||||
| #[diesel(table_name = tasks)] | ||||
| pub struct NewTask { | ||||
|     #[validate(length(min = "TITLE_LENGTH_MIN", max = "TITLE_LENGTH_MAX", code = "title_length"))] | ||||
|     pub title: String, | ||||
|     pub deadline: Option<chrono::NaiveDate>, | ||||
|     pub category: Category, | ||||
|     pub project_id: Option<i32>, | ||||
| } | ||||
|  | ||||
| impl NewTask { | ||||
|     pub fn new( | ||||
|         title: String, deadline: Option<chrono::NaiveDate>, | ||||
|         category: Category, project_id: Option<i32>, | ||||
|     ) -> Self { | ||||
|         Self { title, deadline, category, project_id } | ||||
|     } | ||||
| } | ||||
| @@ -6,3 +6,20 @@ diesel::table! { | ||||
|         title -> Text, | ||||
|     } | ||||
| } | ||||
|  | ||||
| diesel::table! { | ||||
|     tasks (id) { | ||||
|         id -> Int4, | ||||
|         title -> Text, | ||||
|         deadline -> Nullable<Date>, | ||||
|         category -> Jsonb, | ||||
|         project_id -> Nullable<Int4>, | ||||
|     } | ||||
| } | ||||
|  | ||||
| diesel::joinable!(tasks -> projects (project_id)); | ||||
|  | ||||
| diesel::allow_tables_to_appear_in_same_query!( | ||||
|     projects, | ||||
|     tasks, | ||||
| ); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Matouš Volf
					Matouš Volf