feat: ability to create a task (#14)
This commit is contained in:
		| @@ -3,17 +3,9 @@ | ||||
|   <database-model serializer="dbm" dbms="POSTGRES" family-id="POSTGRES" format-version="4.53"> | ||||
|     <root id="1"> | ||||
|       <DateStyle>mdy</DateStyle> | ||||
|       <Grants>1||-9223372036854775808|c|G | ||||
| 1||10|c|G | ||||
| 1||10|C|G | ||||
| 1||10|T|G | ||||
| 4||-9223372036854775808|c|G | ||||
| 4||10|c|G | ||||
| 4||10|C|G | ||||
| 4||10|T|G</Grants> | ||||
|       <IntrospectionStateNumber>767</IntrospectionStateNumber> | ||||
|       <IntrospectionStateNumber>785</IntrospectionStateNumber> | ||||
|       <ServerVersion>16.4</ServerVersion> | ||||
|       <StartupTime>1723847104</StartupTime> | ||||
|       <StartupTime>1724062819</StartupTime> | ||||
|       <TimeZones>true ACDT | ||||
| true ACSST | ||||
| false ACST | ||||
| @@ -1412,7 +1404,7 @@ true posixrules | ||||
| 13212||10|C|G | ||||
| 13212||-9223372036854775808|U|G | ||||
| 13212||10|U|G</Grants> | ||||
|       <IntrospectionStateNumber>767</IntrospectionStateNumber> | ||||
|       <IntrospectionStateNumber>785</IntrospectionStateNumber> | ||||
|       <ObjectId>16384</ObjectId> | ||||
|       <OwnerName>app</OwnerName> | ||||
|     </database> | ||||
| @@ -4831,8 +4823,8 @@ true posixrules | ||||
|     <schema id="264" parent="3" name="public"> | ||||
|       <Comment>standard public schema</Comment> | ||||
|       <Current>1</Current> | ||||
|       <IntrospectionStateNumber>767</IntrospectionStateNumber> | ||||
|       <LastIntrospectionLocalTimestamp>2024-08-16.22:33:41</LastIntrospectionLocalTimestamp> | ||||
|       <IntrospectionStateNumber>785</IntrospectionStateNumber> | ||||
|       <LastIntrospectionLocalTimestamp>2024-08-19.17:09:45</LastIntrospectionLocalTimestamp> | ||||
|       <ObjectId>2200</ObjectId> | ||||
|       <StateNumber>524</StateNumber> | ||||
|       <OwnerName>pg_database_owner</OwnerName> | ||||
| @@ -4873,30 +4865,36 @@ true posixrules | ||||
|     </table> | ||||
|     <table id="269" parent="264" name="projects"> | ||||
|       <ObjectId>16425</ObjectId> | ||||
|       <StateNumber>762</StateNumber> | ||||
|       <StateNumber>781</StateNumber> | ||||
|       <AccessMethodId>2</AccessMethodId> | ||||
|       <OwnerName>app</OwnerName> | ||||
|     </table> | ||||
|     <argument id="270" parent="265"> | ||||
|     <table id="270" parent="264" name="tasks"> | ||||
|       <ObjectId>16446</ObjectId> | ||||
|       <StateNumber>783</StateNumber> | ||||
|       <AccessMethodId>2</AccessMethodId> | ||||
|       <OwnerName>app</OwnerName> | ||||
|     </table> | ||||
|     <argument id="271" parent="265"> | ||||
|       <ArgumentDirection>R</ArgumentDirection> | ||||
|       <StoredType>void|0s</StoredType> | ||||
|     </argument> | ||||
|     <argument id="271" parent="265" name="_tbl"> | ||||
|     <argument id="272" parent="265" name="_tbl"> | ||||
|       <Position>1</Position> | ||||
|       <StoredType>regclass|0s</StoredType> | ||||
|     </argument> | ||||
|     <argument id="272" parent="266"> | ||||
|     <argument id="273" parent="266"> | ||||
|       <ArgumentDirection>R</ArgumentDirection> | ||||
|       <StoredType>trigger|0s</StoredType> | ||||
|     </argument> | ||||
|     <column id="273" parent="268" name="version"> | ||||
|     <column id="274" parent="268" name="version"> | ||||
|       <NotNull>1</NotNull> | ||||
|       <Position>1</Position> | ||||
|       <StateNumber>743</StateNumber> | ||||
|       <StoredType>varchar(50)|0s</StoredType> | ||||
|       <TypeId>1043</TypeId> | ||||
|     </column> | ||||
|     <column id="274" parent="268" name="run_on"> | ||||
|     <column id="275" parent="268" name="run_on"> | ||||
|       <DefaultExpression>CURRENT_TIMESTAMP</DefaultExpression> | ||||
|       <NotNull>1</NotNull> | ||||
|       <Position>2</Position> | ||||
| @@ -4904,7 +4902,7 @@ true posixrules | ||||
|       <StoredType>timestamp|0s</StoredType> | ||||
|       <TypeId>1114</TypeId> | ||||
|     </column> | ||||
|     <index id="275" parent="268" name="__diesel_schema_migrations_pkey"> | ||||
|     <index id="276" parent="268" name="__diesel_schema_migrations_pkey"> | ||||
|       <ColNames>version</ColNames> | ||||
|       <NameSurrogate>1</NameSurrogate> | ||||
|       <ObjectId>16393</ObjectId> | ||||
| @@ -4916,14 +4914,14 @@ true posixrules | ||||
|       <CollationIds>100</CollationIds> | ||||
|       <CollationParentNames>pg_catalog</CollationParentNames> | ||||
|     </index> | ||||
|     <key id="276" parent="268" name="__diesel_schema_migrations_pkey"> | ||||
|     <key id="277" parent="268" name="__diesel_schema_migrations_pkey"> | ||||
|       <NameSurrogate>1</NameSurrogate> | ||||
|       <ObjectId>16394</ObjectId> | ||||
|       <Primary>1</Primary> | ||||
|       <StateNumber>743</StateNumber> | ||||
|       <UnderlyingIndexId>16393</UnderlyingIndexId> | ||||
|     </key> | ||||
|     <column id="277" parent="269" name="id"> | ||||
|     <column id="278" parent="269" name="id"> | ||||
|       <DefaultExpression>nextval('projects_id_seq'::regclass)</DefaultExpression> | ||||
|       <NotNull>1</NotNull> | ||||
|       <Position>1</Position> | ||||
| @@ -4932,14 +4930,14 @@ true posixrules | ||||
|       <SequenceId>16424</SequenceId> | ||||
|       <TypeId>23</TypeId> | ||||
|     </column> | ||||
|     <column id="278" parent="269" name="title"> | ||||
|     <column id="279" parent="269" name="title"> | ||||
|       <NotNull>1</NotNull> | ||||
|       <Position>2</Position> | ||||
|       <StateNumber>762</StateNumber> | ||||
|       <StoredType>text|0s</StoredType> | ||||
|       <TypeId>25</TypeId> | ||||
|     </column> | ||||
|     <index id="279" parent="269" name="projects_pkey"> | ||||
|     <index id="280" parent="269" name="projects_pkey"> | ||||
|       <ColNames>id</ColNames> | ||||
|       <NameSurrogate>1</NameSurrogate> | ||||
|       <ObjectId>16431</ObjectId> | ||||
| @@ -4948,12 +4946,69 @@ true posixrules | ||||
|       <Unique>1</Unique> | ||||
|       <AccessMethodId>403</AccessMethodId> | ||||
|     </index> | ||||
|     <key id="280" parent="269" name="projects_pkey"> | ||||
|     <key id="281" parent="269" name="projects_pkey"> | ||||
|       <NameSurrogate>1</NameSurrogate> | ||||
|       <ObjectId>16432</ObjectId> | ||||
|       <Primary>1</Primary> | ||||
|       <StateNumber>762</StateNumber> | ||||
|       <UnderlyingIndexId>16431</UnderlyingIndexId> | ||||
|     </key> | ||||
|     <column id="282" parent="270" name="id"> | ||||
|       <NotNull>1</NotNull> | ||||
|       <Position>1</Position> | ||||
|       <StateNumber>783</StateNumber> | ||||
|       <StoredType>integer|0s</StoredType> | ||||
|       <TypeId>23</TypeId> | ||||
|     </column> | ||||
|     <column id="283" parent="270" name="title"> | ||||
|       <NotNull>1</NotNull> | ||||
|       <Position>2</Position> | ||||
|       <StateNumber>783</StateNumber> | ||||
|       <StoredType>text|0s</StoredType> | ||||
|       <TypeId>25</TypeId> | ||||
|     </column> | ||||
|     <column id="284" parent="270" name="deadline"> | ||||
|       <Position>3</Position> | ||||
|       <StateNumber>783</StateNumber> | ||||
|       <StoredType>date|0s</StoredType> | ||||
|       <TypeId>1082</TypeId> | ||||
|     </column> | ||||
|     <column id="285" parent="270" name="category"> | ||||
|       <NotNull>1</NotNull> | ||||
|       <Position>4</Position> | ||||
|       <StateNumber>783</StateNumber> | ||||
|       <StoredType>jsonb|0s</StoredType> | ||||
|       <TypeId>3802</TypeId> | ||||
|     </column> | ||||
|     <column id="286" parent="270" name="project_id"> | ||||
|       <Position>5</Position> | ||||
|       <StateNumber>783</StateNumber> | ||||
|       <StoredType>integer|0s</StoredType> | ||||
|       <TypeId>23</TypeId> | ||||
|     </column> | ||||
|     <foreign-key id="287" parent="270" name="tasks_project_id_fkey"> | ||||
|       <ColNames>project_id</ColNames> | ||||
|       <NameSurrogate>1</NameSurrogate> | ||||
|       <ObjectId>16453</ObjectId> | ||||
|       <StateNumber>783</StateNumber> | ||||
|       <RefKeyColPositions>1</RefKeyColPositions> | ||||
|       <RefTableId>16425</RefTableId> | ||||
|     </foreign-key> | ||||
|     <index id="288" parent="270" name="tasks_pkey"> | ||||
|       <ColNames>id</ColNames> | ||||
|       <NameSurrogate>1</NameSurrogate> | ||||
|       <ObjectId>16451</ObjectId> | ||||
|       <Primary>1</Primary> | ||||
|       <StateNumber>783</StateNumber> | ||||
|       <Unique>1</Unique> | ||||
|       <AccessMethodId>403</AccessMethodId> | ||||
|     </index> | ||||
|     <key id="289" parent="270" name="tasks_pkey"> | ||||
|       <NameSurrogate>1</NameSurrogate> | ||||
|       <ObjectId>16452</ObjectId> | ||||
|       <Primary>1</Primary> | ||||
|       <StateNumber>783</StateNumber> | ||||
|       <UnderlyingIndexId>16451</UnderlyingIndexId> | ||||
|     </key> | ||||
|   </database-model> | ||||
| </dataSource> | ||||
| @@ -1,2 +1,2 @@ | ||||
| #n:public | ||||
| !<md> [767, 0, null, null, -2147483648, -2147483648] | ||||
| !<md> [785, 0, null, null, -2147483648, -2147483648] | ||||
|   | ||||
							
								
								
									
										4
									
								
								.idea/vcs.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										4
									
								
								.idea/vcs.xml
									
									
									
										generated
									
									
									
								
							| @@ -2,8 +2,8 @@ | ||||
| <project version="4"> | ||||
|   <component name="CommitMessageInspectionProfile"> | ||||
|     <profile version="1.0"> | ||||
|       <inspection_tool class="CommitFormat" enabled="true" level="ERROR" enabled_by_default="true" /> | ||||
|       <inspection_tool class="CommitNamingConvention" enabled="true" level="ERROR" enabled_by_default="true" /> | ||||
|       <inspection_tool class="CommitFormat" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|       <inspection_tool class="CommitNamingConvention" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|     </profile> | ||||
|   </component> | ||||
|   <component name="VcsDirectoryMappings"> | ||||
|   | ||||
							
								
								
									
										137
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										137
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| @@ -127,7 +127,7 @@ dependencies = [ | ||||
|  "async-trait", | ||||
|  "axum-core", | ||||
|  "axum-macros", | ||||
|  "base64", | ||||
|  "base64 0.21.7", | ||||
|  "bytes", | ||||
|  "futures-util", | ||||
|  "http 1.1.0", | ||||
| @@ -211,6 +211,12 @@ version = "0.21.7" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" | ||||
|  | ||||
| [[package]] | ||||
| name = "base64" | ||||
| version = "0.22.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" | ||||
|  | ||||
| [[package]] | ||||
| name = "bincode" | ||||
| version = "1.3.3" | ||||
| @@ -335,6 +341,7 @@ dependencies = [ | ||||
|  "iana-time-zone", | ||||
|  "js-sys", | ||||
|  "num-traits", | ||||
|  "serde", | ||||
|  "wasm-bindgen", | ||||
|  "windows-targets", | ||||
| ] | ||||
| @@ -499,7 +506,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" | ||||
| dependencies = [ | ||||
|  "cfg-if", | ||||
|  "hashbrown", | ||||
|  "hashbrown 0.14.5", | ||||
|  "lock_api", | ||||
|  "once_cell", | ||||
|  "parking_lot_core", | ||||
| @@ -511,6 +518,16 @@ version = "2.6.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" | ||||
|  | ||||
| [[package]] | ||||
| name = "deranged" | ||||
| version = "0.3.11" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" | ||||
| dependencies = [ | ||||
|  "powerfmt", | ||||
|  "serde", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "diesel" | ||||
| version = "2.2.2" | ||||
| @@ -519,9 +536,11 @@ checksum = "bf97ee7261bb708fa3402fa9c17a54b70e90e3cb98afb3dc8999d5512cb03f94" | ||||
| dependencies = [ | ||||
|  "bitflags", | ||||
|  "byteorder", | ||||
|  "chrono", | ||||
|  "diesel_derives", | ||||
|  "itoa", | ||||
|  "pq-sys", | ||||
|  "serde_json", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @@ -647,7 +666,7 @@ dependencies = [ | ||||
|  "anymap", | ||||
|  "async-trait", | ||||
|  "axum", | ||||
|  "base64", | ||||
|  "base64 0.21.7", | ||||
|  "bytes", | ||||
|  "ciborium", | ||||
|  "dioxus-cli-config", | ||||
| @@ -1400,6 +1419,12 @@ dependencies = [ | ||||
|  "crunchy", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "hashbrown" | ||||
| version = "0.12.3" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" | ||||
|  | ||||
| [[package]] | ||||
| name = "hashbrown" | ||||
| version = "0.14.5" | ||||
| @@ -1428,6 +1453,12 @@ version = "0.3.9" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" | ||||
|  | ||||
| [[package]] | ||||
| name = "hex" | ||||
| version = "0.4.3" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" | ||||
|  | ||||
| [[package]] | ||||
| name = "http" | ||||
| version = "0.2.12" | ||||
| @@ -1564,6 +1595,17 @@ dependencies = [ | ||||
|  "unicode-normalization", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "indexmap" | ||||
| version = "1.9.3" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" | ||||
| dependencies = [ | ||||
|  "autocfg", | ||||
|  "hashbrown 0.12.3", | ||||
|  "serde", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "indexmap" | ||||
| version = "2.4.0" | ||||
| @@ -1571,7 +1613,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" | ||||
| dependencies = [ | ||||
|  "equivalent", | ||||
|  "hashbrown", | ||||
|  "hashbrown 0.14.5", | ||||
|  "serde", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @@ -1580,7 +1623,7 @@ version = "0.7.5" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "04e8e537b529b8674e97e9fb82c10ff168a290ac3867a0295f112061ffbca1ef" | ||||
| dependencies = [ | ||||
|  "hashbrown", | ||||
|  "hashbrown 0.14.5", | ||||
|  "parking_lot", | ||||
| ] | ||||
|  | ||||
| @@ -1695,7 +1738,7 @@ version = "0.12.4" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "37ee39891760e7d94734f6f63fedc29a2e4a152f836120753a72503f09fcf904" | ||||
| dependencies = [ | ||||
|  "hashbrown", | ||||
|  "hashbrown 0.14.5", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @@ -1780,6 +1823,12 @@ dependencies = [ | ||||
|  "winapi", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "num-conv" | ||||
| version = "0.1.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" | ||||
|  | ||||
| [[package]] | ||||
| name = "num-traits" | ||||
| version = "0.2.19" | ||||
| @@ -1861,7 +1910,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" | ||||
| dependencies = [ | ||||
|  "fixedbitset", | ||||
|  "indexmap", | ||||
|  "indexmap 2.4.0", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @@ -1907,6 +1956,12 @@ dependencies = [ | ||||
|  "futures-io", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "powerfmt" | ||||
| version = "0.2.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" | ||||
|  | ||||
| [[package]] | ||||
| name = "ppv-lite86" | ||||
| version = "0.2.20" | ||||
| @@ -2199,6 +2254,36 @@ dependencies = [ | ||||
|  "serde", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "serde_with" | ||||
| version = "3.9.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "69cecfa94848272156ea67b2b1a53f20fc7bc638c4a46d2f8abde08f05f4b857" | ||||
| dependencies = [ | ||||
|  "base64 0.22.1", | ||||
|  "chrono", | ||||
|  "hex", | ||||
|  "indexmap 1.9.3", | ||||
|  "indexmap 2.4.0", | ||||
|  "serde", | ||||
|  "serde_derive", | ||||
|  "serde_json", | ||||
|  "serde_with_macros", | ||||
|  "time", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "serde_with_macros" | ||||
| version = "3.9.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "a8fee4991ef4f274617a51ad4af30519438dacb2f56ac773b08a1922ff743350" | ||||
| dependencies = [ | ||||
|  "darling", | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "syn 2.0.74", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "server_fn" | ||||
| version = "0.6.14" | ||||
| @@ -2442,6 +2527,37 @@ dependencies = [ | ||||
|  "once_cell", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "time" | ||||
| version = "0.3.36" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" | ||||
| dependencies = [ | ||||
|  "deranged", | ||||
|  "itoa", | ||||
|  "num-conv", | ||||
|  "powerfmt", | ||||
|  "serde", | ||||
|  "time-core", | ||||
|  "time-macros", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "time-core" | ||||
| version = "0.1.2" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" | ||||
|  | ||||
| [[package]] | ||||
| name = "time-macros" | ||||
| version = "0.2.18" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" | ||||
| dependencies = [ | ||||
|  "num-conv", | ||||
|  "time-core", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "tinyvec" | ||||
| version = "1.8.0" | ||||
| @@ -2467,11 +2583,16 @@ checksum = "c7c4ceeeca15c8384bbc3e011dbd8fccb7f068a440b752b7d9b32ceb0ca0e2e8" | ||||
| name = "todo-baggins" | ||||
| version = "0.1.0" | ||||
| dependencies = [ | ||||
|  "chrono", | ||||
|  "diesel", | ||||
|  "dioxus", | ||||
|  "dioxus-logger", | ||||
|  "dotenvy", | ||||
|  "serde", | ||||
|  "serde_json", | ||||
|  "serde_with", | ||||
|  "tracing", | ||||
|  "tracing-wasm", | ||||
|  "validator", | ||||
| ] | ||||
|  | ||||
| @@ -2538,7 +2659,7 @@ dependencies = [ | ||||
|  "futures-core", | ||||
|  "futures-sink", | ||||
|  "futures-util", | ||||
|  "hashbrown", | ||||
|  "hashbrown 0.14.5", | ||||
|  "pin-project-lite", | ||||
|  "tokio", | ||||
| ] | ||||
|   | ||||
| @@ -7,7 +7,8 @@ edition = "2021" | ||||
| # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||||
|  | ||||
| [dependencies] | ||||
| diesel = { version = "2.2.2", features = ["postgres"] } | ||||
| chrono = { version = "0.4.38", features = ["serde"] } | ||||
| diesel = { version = "2.2.2", features = ["chrono", "postgres", "postgres_backend", "serde_json"] } | ||||
|  | ||||
| dioxus = { version = "0.5", features = ["fullstack", "router"] } | ||||
|  | ||||
| @@ -16,6 +17,10 @@ dioxus-logger = "0.5.1" | ||||
| dotenvy = "0.15.7" | ||||
| serde = "1.0.208" | ||||
| validator = { version = "0.18.1", features = ["derive"] } | ||||
| serde_json = "1.0.125" | ||||
| tracing = "0.1.40" | ||||
| tracing-wasm = "0.2.1" | ||||
| serde_with = { version = "3.9.0", features = ["chrono_0_4"] } | ||||
|  | ||||
| [features] | ||||
| default = [] | ||||
|   | ||||
							
								
								
									
										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") | ||||
| ); | ||||
|  | ||||
| @@ -6,6 +6,9 @@ use dioxus::prelude::*; | ||||
| #[component] | ||||
| pub(crate) fn App() -> Element { | ||||
|     rsx! { | ||||
|         Router::<Route> {} | ||||
|         div { | ||||
|             class: "min-h-screen text-white bg-neutral-800", | ||||
|             Router::<Route> {} | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -2,10 +2,12 @@ use crate::components::project_form::ProjectForm; | ||||
| use dioxus::core_macro::rsx; | ||||
| use dioxus::dioxus_core::Element; | ||||
| use dioxus::prelude::*; | ||||
| use crate::components::task_form::TaskForm; | ||||
|  | ||||
| #[component] | ||||
| pub(crate) fn Home() -> Element { | ||||
|     rsx! { | ||||
|         ProjectForm {} | ||||
|         TaskForm {} | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
| pub(crate) mod app; | ||||
| pub(crate) mod home; | ||||
| pub(crate) mod project_form; | ||||
| pub(crate) mod task_form; | ||||
|   | ||||
| @@ -19,6 +19,7 @@ pub(crate) fn ProjectForm() -> Element { | ||||
|             input { | ||||
|                 r#type: "text", | ||||
|                 name: "title", | ||||
|                 required: true, | ||||
|                 placeholder: "title" | ||||
|             } | ||||
|             button { | ||||
|   | ||||
							
								
								
									
										221
									
								
								src/components/task_form.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										221
									
								
								src/components/task_form.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,221 @@ | ||||
| use chrono::Duration; | ||||
| use crate::models::category::{CalendarTime, Category}; | ||||
| use crate::models::task::NewTask; | ||||
| use crate::server::projects::get_projects; | ||||
| use crate::server::tasks::create_task; | ||||
| use dioxus::core_macro::{component, rsx}; | ||||
| use dioxus::dioxus_core::Element; | ||||
| use dioxus::prelude::*; | ||||
|  | ||||
| #[component] | ||||
| pub(crate) fn TaskForm() -> Element { | ||||
|     let categories = vec![ | ||||
|         Category::Inbox, | ||||
|         Category::SomedayMaybe, | ||||
|         Category::WaitingFor(String::new()), | ||||
|         Category::NextSteps, | ||||
|         Category::Calendar { | ||||
|             date: chrono::Local::now().date_naive(), | ||||
|             reoccurance_interval: None, | ||||
|             time: None, | ||||
|         }, | ||||
|         Category::LongTerm, | ||||
|     ]; | ||||
|     let projects = use_server_future(get_projects)?.unwrap().unwrap(); | ||||
|  | ||||
|     let mut selected_category_index = use_signal::<usize>(|| 0); | ||||
|     let mut category_calendar_is_reoccurring = use_signal::<bool>(|| false); | ||||
|     let mut category_calendar_has_time = use_signal::<bool>(|| false); | ||||
|     let mut category_calendar_has_reminder = use_signal::<bool>(|| false); | ||||
|  | ||||
|     rsx! { | ||||
|         form { | ||||
|             onsubmit: move |event| { | ||||
|                 let categories = categories.clone(); | ||||
|                 async move { | ||||
|                     let new_task = NewTask::new( | ||||
|                         event.values().get("title").unwrap().as_value(), | ||||
|                         event.values().get("deadline").unwrap().as_value().parse().ok(), | ||||
|                         match &categories[ | ||||
|                             event.values().get("category_index").unwrap() | ||||
|                             .as_value().parse::<usize>().unwrap() | ||||
|                         ] { | ||||
|                             Category::WaitingFor(_) => Category::WaitingFor( | ||||
|                                 event.values().get("category_waiting_for").unwrap() | ||||
|                                 .as_value() | ||||
|                             ), | ||||
|                             Category::Calendar { .. } => Category::Calendar { | ||||
|                                 date: event.values().get("category_calendar_date").unwrap() | ||||
|                                 .as_value().parse().unwrap(), | ||||
|                                 reoccurance_interval: | ||||
|                                 event.values().get("category_calendar_is_reoccurring").map( | ||||
|                                     |_| Duration::days( | ||||
|                                         event.values().get("category_calendar_reoccurance_interval") | ||||
|                                         .unwrap().as_value().parse().unwrap() | ||||
|                                     ) | ||||
|                                 ), | ||||
|                                 time: event.values().get("category_calendar_time").unwrap() | ||||
|                                     .as_value().parse().ok().map(|time| | ||||
|                                     CalendarTime::new( | ||||
|                                         time, | ||||
|                                         event.values().get("category_calendar_has_reminder").map( | ||||
|                                             |_| Duration::minutes( | ||||
|                                                 event.values() | ||||
|                                                 .get("category_calendar_reminder_offset").unwrap() | ||||
|                                                 .as_value().parse().unwrap() | ||||
|                                             ) | ||||
|                                         ) | ||||
|                                     ) | ||||
|                                 ) | ||||
|                             }, | ||||
|                             category => category.clone() | ||||
|                         }, | ||||
|                         event.values().get("project_id").unwrap() | ||||
|                         .as_value().parse::<i32>().ok().filter(|&id| id > 0), | ||||
|                     ); | ||||
|                     let _ = create_task(new_task).await; | ||||
|                 } | ||||
|             }, | ||||
|             class: "p-4 flex flex-col gap-4", | ||||
|             input { | ||||
|                 r#type: "text", | ||||
|                 name: "title", | ||||
|                 required: true, | ||||
|                 placeholder: "title", | ||||
|                 class: "p-2 bg-neutral-700 rounded", | ||||
|             }, | ||||
|             select { | ||||
|                 name: "category_index", | ||||
|                 oninput: move |event| { | ||||
|                     selected_category_index.set(event.value().parse().unwrap()); | ||||
|                 }, | ||||
|                 class: "p-2 bg-neutral-700 rounded", | ||||
|                 option { | ||||
|                     value: 0, | ||||
|                     "inbox" | ||||
|                 }, | ||||
|                 option { | ||||
|                     value: 1, | ||||
|                     "someday maybe" | ||||
|                 }, | ||||
|                 option { | ||||
|                     value: 2, | ||||
|                     "waiting for" | ||||
|                 }, | ||||
|                 option { | ||||
|                     value: 3, | ||||
|                     "next steps" | ||||
|                 }, | ||||
|                 option { | ||||
|                     value: 4, | ||||
|                     "calendar" | ||||
|                 }, | ||||
|                 option { | ||||
|                     value: 5, | ||||
|                     "long term" | ||||
|                 }, | ||||
|             }, | ||||
|             match categories[selected_category_index()] { | ||||
|                 Category::WaitingFor(_) => rsx !{ | ||||
|                     input { | ||||
|                         r#type: "text", | ||||
|                         name: "category_waiting_for", | ||||
|                         required: true, | ||||
|                         class: "p-2 bg-neutral-700 rounded", | ||||
|                     }, | ||||
|                 }, | ||||
|                 Category::Calendar { .. } => rsx !{ | ||||
|                     input { | ||||
|                         r#type: "date", | ||||
|                         name: "category_calendar_date", | ||||
|                         required: true, | ||||
|                         class: "p-2 bg-neutral-700 rounded", | ||||
|                     }, | ||||
|                     div { | ||||
|                         input { | ||||
|                             r#type: "checkbox", | ||||
|                             name: "category_calendar_is_reoccurring", | ||||
|                             id: "category_calendar_is_reoccurring", | ||||
|                             onchange: move |event| { | ||||
|                                 category_calendar_is_reoccurring.set(event.checked()); | ||||
|                             } | ||||
|                         }, | ||||
|                         label { | ||||
|                             r#for: "category_calendar_is_reoccurring", | ||||
|                             " is reoccurring" | ||||
|                         } | ||||
|                     }, | ||||
|                     if category_calendar_is_reoccurring() { | ||||
|                         input { | ||||
|                             r#type: "number", | ||||
|                             name: "category_calendar_reoccurance_interval", | ||||
|                             required: true, | ||||
|                             min: 1, | ||||
|                             placeholder: "reoccurance interval (days)", | ||||
|                             class: "p-2 bg-neutral-700 rounded", | ||||
|                         } | ||||
|                     }, | ||||
|                     input { | ||||
|                         r#type: "time", | ||||
|                         name: "category_calendar_time", | ||||
|                         class: "p-2 bg-neutral-700 rounded", | ||||
|                         oninput: move |event| { | ||||
|                             category_calendar_has_time.set(!event.value().is_empty()); | ||||
|                         } | ||||
|                     }, | ||||
|                     if category_calendar_has_time() { | ||||
|                         div { | ||||
|                             input { | ||||
|                                 r#type: "checkbox", | ||||
|                                 name: "category_calendar_has_reminder", | ||||
|                                 value: 0, | ||||
|                                 id: "category_calendar_has_reminder", | ||||
|                                 onchange: move |event| { | ||||
|                                     category_calendar_has_reminder.set(event.checked()); | ||||
|                                 } | ||||
|                             }, | ||||
|                             label { | ||||
|                                 r#for: "category_calendar_has_reminder", | ||||
|                                 " set a reminder" | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                     if category_calendar_has_reminder() { | ||||
|                         input { | ||||
|                             r#type: "number", | ||||
|                             name: "category_calendar_reminder_offset", | ||||
|                             required: true, | ||||
|                             min: 0, | ||||
|                             placeholder: "reminder offset (minutes)", | ||||
|                             class: "p-2 bg-neutral-700 rounded", | ||||
|                         } | ||||
|                     } | ||||
|                 }, | ||||
|                 _ => None | ||||
|             }, | ||||
|             input { | ||||
|                 r#type: "date", | ||||
|                 name: "deadline", | ||||
|                 class: "p-2 bg-neutral-700 rounded", | ||||
|             }, | ||||
|             select { | ||||
|                 name: "project_id", | ||||
|                 class: "p-2 bg-neutral-700 rounded", | ||||
|                 option { | ||||
|                     value: 0, | ||||
|                     "none" | ||||
|                 }, | ||||
|                 for project in projects { | ||||
|                     option { | ||||
|                         value: project.id().to_string(), | ||||
|                         {project.title()} | ||||
|                     } | ||||
|                 } | ||||
|             }, | ||||
|             button { | ||||
|                 r#type: "submit", | ||||
|                 "create" | ||||
|             } | ||||
|         } | ||||
| } | ||||
| } | ||||
| @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize}; | ||||
| use std::fmt::Display; | ||||
| use std::str::FromStr; | ||||
|  | ||||
| #[derive(Serialize, Deserialize, Debug)] | ||||
| #[derive(Serialize, Deserialize, Clone, Debug)] | ||||
| pub enum Error { | ||||
|     ServerInternal, | ||||
| } | ||||
|   | ||||
| @@ -1,7 +1,9 @@ | ||||
| use std::fmt::Display; | ||||
| use std::str::FromStr; | ||||
| use serde::Deserialize; | ||||
| use serde_with::serde_derive::Serialize; | ||||
|  | ||||
| #[derive(Debug)] | ||||
| #[derive(Serialize, Deserialize, Clone, Debug)] | ||||
| pub struct ErrorVec<T> { | ||||
|     errors: Vec<T>, | ||||
| } | ||||
| @@ -37,7 +39,7 @@ impl<T: Display> Display for ErrorVec<T> { | ||||
| impl<T> FromStr for ErrorVec<T> { | ||||
|     type Err = (); | ||||
|  | ||||
|     fn from_str(s: &str) -> Result<Self, Self::Err> { | ||||
|     fn from_str(_: &str) -> Result<Self, Self::Err> { | ||||
|         Ok(ErrorVec { errors: Vec::new() }) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -12,8 +12,8 @@ pub enum ProjectCreateError { | ||||
| } | ||||
|  | ||||
| impl From<ValidationErrors> for ErrorVec<ProjectCreateError> { | ||||
|     fn from(e: ValidationErrors) -> Self { | ||||
|         e.errors() | ||||
|     fn from(validation_errors: ValidationErrors) -> Self { | ||||
|         validation_errors.errors() | ||||
|             .iter() | ||||
|             .flat_map(|(&field, error_kind)| match field { | ||||
|                 "title" => match error_kind { | ||||
| @@ -22,30 +22,30 @@ impl From<ValidationErrors> for ErrorVec<ProjectCreateError> { | ||||
|                         .map(|validation_error| validation_error.code.as_ref()) | ||||
|                         .map(|code| match code { | ||||
|                             "title_length" => ProjectCreateError::TitleLengthInvalid, | ||||
|                             _ => panic!("unexpected validation error code: {code}"), | ||||
|                             _ => panic!("Unexpected validation error code: `{code}`."), | ||||
|                         }) | ||||
|                         .collect::<Vec<ProjectCreateError>>(), | ||||
|                     _ => panic!("unexpected validation error kind"), | ||||
|                     _ => panic!("Unexpected validation error kind."), | ||||
|                 }, | ||||
|                 _ => panic!("unexpected validation field name: {field}"), | ||||
|                 _ => panic!("Unexpected validation field name: `{field}`."), | ||||
|             }) | ||||
|             .collect::<Vec<ProjectCreateError>>() | ||||
|             .into() | ||||
|     } | ||||
| } | ||||
|  | ||||
| // has to be implemented for Dioxus server functions | ||||
| // Has to be implemented for Dioxus server functions. | ||||
| impl Display for ProjectCreateError { | ||||
|     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||||
|         write!(f, "{:?}", self) | ||||
|     } | ||||
| } | ||||
|  | ||||
| // has to be implemented for Dioxus server functions | ||||
| // Has to be implemented for Dioxus server functions. | ||||
| impl FromStr for ProjectCreateError { | ||||
|     type Err = (); | ||||
|  | ||||
|     fn from_str(_: &str) -> Result<Self, Self::Err> { | ||||
|         Ok(ProjectCreateError::TitleLengthInvalid) | ||||
|         Ok(ProjectCreateError::Error(Error::ServerInternal)) | ||||
|     } | ||||
| } | ||||
|   | ||||
							
								
								
									
										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(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" => 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::Error(Error::ServerInternal)) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										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; | ||||
|   | ||||
| @@ -6,7 +6,7 @@ use validator::Validate; | ||||
| const TITLE_LENGTH_MIN: u64 = 1; | ||||
| const TITLE_LENGTH_MAX: u64 = 255; | ||||
|  | ||||
| #[derive(Queryable, Selectable, Serialize, Deserialize, Debug)] | ||||
| #[derive(Queryable, Selectable, Serialize, Deserialize, Clone, Debug)] | ||||
| #[diesel(table_name = crate::schema::projects)] | ||||
| #[diesel(check_for_backend(diesel::pg::Pg))] | ||||
| pub struct Project { | ||||
|   | ||||
							
								
								
									
										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, | ||||
| ); | ||||
|   | ||||
| @@ -1,2 +1,3 @@ | ||||
| mod database_connection; | ||||
| pub(crate) mod projects; | ||||
| pub(crate) mod tasks; | ||||
|   | ||||
| @@ -3,23 +3,21 @@ use crate::errors::error_vec::ErrorVec; | ||||
| use crate::errors::project_create_error::ProjectCreateError; | ||||
| use crate::models::project::{NewProject, Project}; | ||||
| use crate::server::database_connection::establish_database_connection; | ||||
| use diesel::{RunQueryDsl, SelectableHelper}; | ||||
| use diesel::{QueryDsl, RunQueryDsl, SelectableHelper}; | ||||
| use dioxus::prelude::*; | ||||
| use validator::Validate; | ||||
|  | ||||
| #[server] | ||||
| pub(crate) async fn create_project( | ||||
|     new_project: NewProject, | ||||
| ) -> Result<Project, ServerFnError<ErrorVec<ProjectCreateError>>> { | ||||
| pub(crate) async fn create_project(new_project: NewProject) | ||||
|                                    -> Result<Project, ServerFnError<ErrorVec<ProjectCreateError>>> { | ||||
|     use crate::schema::projects; | ||||
|  | ||||
|     new_project | ||||
|         .validate() | ||||
|     new_project.validate() | ||||
|         .map_err::<ErrorVec<ProjectCreateError>, _>(|errors| errors.into())?; | ||||
|  | ||||
|     let mut connection = establish_database_connection() | ||||
|         .map_err::<ErrorVec<ProjectCreateError>, _>( | ||||
|             |_| vec![ProjectCreateError::Error(Error::ServerInternal), ].into() | ||||
|             |_| vec![ProjectCreateError::Error(Error::ServerInternal)].into() | ||||
|         )?; | ||||
|  | ||||
|     let new_project = diesel::insert_into(projects::table) | ||||
| @@ -27,8 +25,28 @@ pub(crate) async fn create_project( | ||||
|         .returning(Project::as_returning()) | ||||
|         .get_result(&mut connection) | ||||
|         .map_err::<ErrorVec<ProjectCreateError>, _>( | ||||
|             |_| vec![ProjectCreateError::Error(Error::ServerInternal), ].into() | ||||
|             |_| vec![ProjectCreateError::Error(Error::ServerInternal)].into() | ||||
|         )?; | ||||
|  | ||||
|     Ok(new_project) | ||||
| } | ||||
|  | ||||
| #[server] | ||||
| pub(crate) async fn get_projects() | ||||
|     -> Result<Vec<Project>, ServerFnError<ErrorVec<Error>>> { | ||||
|     use crate::schema::projects::dsl::*; | ||||
|  | ||||
|     let mut connection = establish_database_connection() | ||||
|         .map_err::<ErrorVec<Error>, _>( | ||||
|             |_| vec![Error::ServerInternal].into() | ||||
|         )?; | ||||
|  | ||||
|     let results = projects | ||||
|         .select(Project::as_select()) | ||||
|         .load::<Project>(&mut connection) | ||||
|         .map_err::<ErrorVec<Error>, _>( | ||||
|             |_| vec![Error::ServerInternal].into() | ||||
|         )?; | ||||
|  | ||||
|     Ok(results) | ||||
| } | ||||
|   | ||||
							
								
								
									
										45
									
								
								src/server/tasks.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								src/server/tasks.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,45 @@ | ||||
| use crate::errors::error::Error; | ||||
| use crate::errors::error_vec::ErrorVec; | ||||
| use crate::models::task::{NewTask, Task}; | ||||
| use crate::server::database_connection::establish_database_connection; | ||||
| use diesel::{RunQueryDsl, SelectableHelper}; | ||||
| use dioxus::prelude::*; | ||||
| use validator::Validate; | ||||
| use crate::errors::task_create_error::TaskCreateError; | ||||
|  | ||||
| #[server] | ||||
| pub(crate) async fn create_task(new_task: NewTask) | ||||
|                                 -> Result<Task, ServerFnError<ErrorVec<TaskCreateError>>> { | ||||
|     use crate::schema::tasks; | ||||
|  | ||||
|     new_task.validate() | ||||
|         .map_err::<ErrorVec<TaskCreateError>, _>(|errors| errors.into())?; | ||||
|  | ||||
|     let mut connection = establish_database_connection() | ||||
|         .map_err::<ErrorVec<TaskCreateError>, _>( | ||||
|             |_| vec![TaskCreateError::Error(Error::ServerInternal)].into() | ||||
|         )?; | ||||
|  | ||||
|     let new_task = diesel::insert_into(tasks::table) | ||||
|         .values(&new_task) | ||||
|         .returning(Task::as_returning()) | ||||
|         .get_result(&mut connection) | ||||
|         .map_err::<ErrorVec<TaskCreateError>, _>(|error| { | ||||
|             let error = match error { | ||||
|                 diesel::result::Error::DatabaseError( | ||||
|                     diesel::result::DatabaseErrorKind::ForeignKeyViolation, info | ||||
|                 ) => { | ||||
|                     match info.constraint_name() {  | ||||
|                         Some("tasks_project_id_fkey") => TaskCreateError::ProjectNotFound, | ||||
|                         _ => TaskCreateError::Error(Error::ServerInternal) | ||||
|                     } | ||||
|                 }, | ||||
|                 _ => { | ||||
|                     TaskCreateError::Error(Error::ServerInternal) | ||||
|                 } | ||||
|             }; | ||||
|             vec![error].into() | ||||
|         })?; | ||||
|  | ||||
|     Ok(new_task) | ||||
| } | ||||
| @@ -1,8 +1,15 @@ | ||||
| /* stylelint-disable */ | ||||
|  | ||||
| /* noinspection CssInvalidAtRule */ | ||||
| @tailwind base; | ||||
| /* noinspection CssInvalidAtRule */ | ||||
| @tailwind components; | ||||
| /* noinspection CssInvalidAtRule */ | ||||
| @tailwind utilities; | ||||
|  | ||||
| html, body, #main { | ||||
|     /* noinspection CssInvalidAtRule */ | ||||
|     @apply min-h-screen; | ||||
| } | ||||
|  | ||||
| /* stylelint-enable */ | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Matouš Volf
					Matouš Volf