feat: ability to create a task (#14)
This commit is contained in:
commit
86710dd3e1
@ -3,17 +3,9 @@
|
|||||||
<database-model serializer="dbm" dbms="POSTGRES" family-id="POSTGRES" format-version="4.53">
|
<database-model serializer="dbm" dbms="POSTGRES" family-id="POSTGRES" format-version="4.53">
|
||||||
<root id="1">
|
<root id="1">
|
||||||
<DateStyle>mdy</DateStyle>
|
<DateStyle>mdy</DateStyle>
|
||||||
<Grants>1||-9223372036854775808|c|G
|
<IntrospectionStateNumber>785</IntrospectionStateNumber>
|
||||||
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>
|
|
||||||
<ServerVersion>16.4</ServerVersion>
|
<ServerVersion>16.4</ServerVersion>
|
||||||
<StartupTime>1723847104</StartupTime>
|
<StartupTime>1724062819</StartupTime>
|
||||||
<TimeZones>true ACDT
|
<TimeZones>true ACDT
|
||||||
true ACSST
|
true ACSST
|
||||||
false ACST
|
false ACST
|
||||||
@ -1412,7 +1404,7 @@ true posixrules
|
|||||||
13212||10|C|G
|
13212||10|C|G
|
||||||
13212||-9223372036854775808|U|G
|
13212||-9223372036854775808|U|G
|
||||||
13212||10|U|G</Grants>
|
13212||10|U|G</Grants>
|
||||||
<IntrospectionStateNumber>767</IntrospectionStateNumber>
|
<IntrospectionStateNumber>785</IntrospectionStateNumber>
|
||||||
<ObjectId>16384</ObjectId>
|
<ObjectId>16384</ObjectId>
|
||||||
<OwnerName>app</OwnerName>
|
<OwnerName>app</OwnerName>
|
||||||
</database>
|
</database>
|
||||||
@ -4831,8 +4823,8 @@ true posixrules
|
|||||||
<schema id="264" parent="3" name="public">
|
<schema id="264" parent="3" name="public">
|
||||||
<Comment>standard public schema</Comment>
|
<Comment>standard public schema</Comment>
|
||||||
<Current>1</Current>
|
<Current>1</Current>
|
||||||
<IntrospectionStateNumber>767</IntrospectionStateNumber>
|
<IntrospectionStateNumber>785</IntrospectionStateNumber>
|
||||||
<LastIntrospectionLocalTimestamp>2024-08-16.22:33:41</LastIntrospectionLocalTimestamp>
|
<LastIntrospectionLocalTimestamp>2024-08-19.17:09:45</LastIntrospectionLocalTimestamp>
|
||||||
<ObjectId>2200</ObjectId>
|
<ObjectId>2200</ObjectId>
|
||||||
<StateNumber>524</StateNumber>
|
<StateNumber>524</StateNumber>
|
||||||
<OwnerName>pg_database_owner</OwnerName>
|
<OwnerName>pg_database_owner</OwnerName>
|
||||||
@ -4873,30 +4865,36 @@ true posixrules
|
|||||||
</table>
|
</table>
|
||||||
<table id="269" parent="264" name="projects">
|
<table id="269" parent="264" name="projects">
|
||||||
<ObjectId>16425</ObjectId>
|
<ObjectId>16425</ObjectId>
|
||||||
<StateNumber>762</StateNumber>
|
<StateNumber>781</StateNumber>
|
||||||
<AccessMethodId>2</AccessMethodId>
|
<AccessMethodId>2</AccessMethodId>
|
||||||
<OwnerName>app</OwnerName>
|
<OwnerName>app</OwnerName>
|
||||||
</table>
|
</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>
|
<ArgumentDirection>R</ArgumentDirection>
|
||||||
<StoredType>void|0s</StoredType>
|
<StoredType>void|0s</StoredType>
|
||||||
</argument>
|
</argument>
|
||||||
<argument id="271" parent="265" name="_tbl">
|
<argument id="272" parent="265" name="_tbl">
|
||||||
<Position>1</Position>
|
<Position>1</Position>
|
||||||
<StoredType>regclass|0s</StoredType>
|
<StoredType>regclass|0s</StoredType>
|
||||||
</argument>
|
</argument>
|
||||||
<argument id="272" parent="266">
|
<argument id="273" parent="266">
|
||||||
<ArgumentDirection>R</ArgumentDirection>
|
<ArgumentDirection>R</ArgumentDirection>
|
||||||
<StoredType>trigger|0s</StoredType>
|
<StoredType>trigger|0s</StoredType>
|
||||||
</argument>
|
</argument>
|
||||||
<column id="273" parent="268" name="version">
|
<column id="274" parent="268" name="version">
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
<Position>1</Position>
|
<Position>1</Position>
|
||||||
<StateNumber>743</StateNumber>
|
<StateNumber>743</StateNumber>
|
||||||
<StoredType>varchar(50)|0s</StoredType>
|
<StoredType>varchar(50)|0s</StoredType>
|
||||||
<TypeId>1043</TypeId>
|
<TypeId>1043</TypeId>
|
||||||
</column>
|
</column>
|
||||||
<column id="274" parent="268" name="run_on">
|
<column id="275" parent="268" name="run_on">
|
||||||
<DefaultExpression>CURRENT_TIMESTAMP</DefaultExpression>
|
<DefaultExpression>CURRENT_TIMESTAMP</DefaultExpression>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
<Position>2</Position>
|
<Position>2</Position>
|
||||||
@ -4904,7 +4902,7 @@ true posixrules
|
|||||||
<StoredType>timestamp|0s</StoredType>
|
<StoredType>timestamp|0s</StoredType>
|
||||||
<TypeId>1114</TypeId>
|
<TypeId>1114</TypeId>
|
||||||
</column>
|
</column>
|
||||||
<index id="275" parent="268" name="__diesel_schema_migrations_pkey">
|
<index id="276" parent="268" name="__diesel_schema_migrations_pkey">
|
||||||
<ColNames>version</ColNames>
|
<ColNames>version</ColNames>
|
||||||
<NameSurrogate>1</NameSurrogate>
|
<NameSurrogate>1</NameSurrogate>
|
||||||
<ObjectId>16393</ObjectId>
|
<ObjectId>16393</ObjectId>
|
||||||
@ -4916,14 +4914,14 @@ true posixrules
|
|||||||
<CollationIds>100</CollationIds>
|
<CollationIds>100</CollationIds>
|
||||||
<CollationParentNames>pg_catalog</CollationParentNames>
|
<CollationParentNames>pg_catalog</CollationParentNames>
|
||||||
</index>
|
</index>
|
||||||
<key id="276" parent="268" name="__diesel_schema_migrations_pkey">
|
<key id="277" parent="268" name="__diesel_schema_migrations_pkey">
|
||||||
<NameSurrogate>1</NameSurrogate>
|
<NameSurrogate>1</NameSurrogate>
|
||||||
<ObjectId>16394</ObjectId>
|
<ObjectId>16394</ObjectId>
|
||||||
<Primary>1</Primary>
|
<Primary>1</Primary>
|
||||||
<StateNumber>743</StateNumber>
|
<StateNumber>743</StateNumber>
|
||||||
<UnderlyingIndexId>16393</UnderlyingIndexId>
|
<UnderlyingIndexId>16393</UnderlyingIndexId>
|
||||||
</key>
|
</key>
|
||||||
<column id="277" parent="269" name="id">
|
<column id="278" parent="269" name="id">
|
||||||
<DefaultExpression>nextval('projects_id_seq'::regclass)</DefaultExpression>
|
<DefaultExpression>nextval('projects_id_seq'::regclass)</DefaultExpression>
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
<Position>1</Position>
|
<Position>1</Position>
|
||||||
@ -4932,14 +4930,14 @@ true posixrules
|
|||||||
<SequenceId>16424</SequenceId>
|
<SequenceId>16424</SequenceId>
|
||||||
<TypeId>23</TypeId>
|
<TypeId>23</TypeId>
|
||||||
</column>
|
</column>
|
||||||
<column id="278" parent="269" name="title">
|
<column id="279" parent="269" name="title">
|
||||||
<NotNull>1</NotNull>
|
<NotNull>1</NotNull>
|
||||||
<Position>2</Position>
|
<Position>2</Position>
|
||||||
<StateNumber>762</StateNumber>
|
<StateNumber>762</StateNumber>
|
||||||
<StoredType>text|0s</StoredType>
|
<StoredType>text|0s</StoredType>
|
||||||
<TypeId>25</TypeId>
|
<TypeId>25</TypeId>
|
||||||
</column>
|
</column>
|
||||||
<index id="279" parent="269" name="projects_pkey">
|
<index id="280" parent="269" name="projects_pkey">
|
||||||
<ColNames>id</ColNames>
|
<ColNames>id</ColNames>
|
||||||
<NameSurrogate>1</NameSurrogate>
|
<NameSurrogate>1</NameSurrogate>
|
||||||
<ObjectId>16431</ObjectId>
|
<ObjectId>16431</ObjectId>
|
||||||
@ -4948,12 +4946,69 @@ true posixrules
|
|||||||
<Unique>1</Unique>
|
<Unique>1</Unique>
|
||||||
<AccessMethodId>403</AccessMethodId>
|
<AccessMethodId>403</AccessMethodId>
|
||||||
</index>
|
</index>
|
||||||
<key id="280" parent="269" name="projects_pkey">
|
<key id="281" parent="269" name="projects_pkey">
|
||||||
<NameSurrogate>1</NameSurrogate>
|
<NameSurrogate>1</NameSurrogate>
|
||||||
<ObjectId>16432</ObjectId>
|
<ObjectId>16432</ObjectId>
|
||||||
<Primary>1</Primary>
|
<Primary>1</Primary>
|
||||||
<StateNumber>762</StateNumber>
|
<StateNumber>762</StateNumber>
|
||||||
<UnderlyingIndexId>16431</UnderlyingIndexId>
|
<UnderlyingIndexId>16431</UnderlyingIndexId>
|
||||||
</key>
|
</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>
|
</database-model>
|
||||||
</dataSource>
|
</dataSource>
|
@ -1,2 +1,2 @@
|
|||||||
#n:public
|
#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">
|
<project version="4">
|
||||||
<component name="CommitMessageInspectionProfile">
|
<component name="CommitMessageInspectionProfile">
|
||||||
<profile version="1.0">
|
<profile version="1.0">
|
||||||
<inspection_tool class="CommitFormat" 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="ERROR" enabled_by_default="true" />
|
<inspection_tool class="CommitNamingConvention" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
</profile>
|
</profile>
|
||||||
</component>
|
</component>
|
||||||
<component name="VcsDirectoryMappings">
|
<component name="VcsDirectoryMappings">
|
||||||
|
137
Cargo.lock
generated
137
Cargo.lock
generated
@ -127,7 +127,7 @@ dependencies = [
|
|||||||
"async-trait",
|
"async-trait",
|
||||||
"axum-core",
|
"axum-core",
|
||||||
"axum-macros",
|
"axum-macros",
|
||||||
"base64",
|
"base64 0.21.7",
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"http 1.1.0",
|
"http 1.1.0",
|
||||||
@ -211,6 +211,12 @@ version = "0.21.7"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
|
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base64"
|
||||||
|
version = "0.22.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bincode"
|
name = "bincode"
|
||||||
version = "1.3.3"
|
version = "1.3.3"
|
||||||
@ -335,6 +341,7 @@ dependencies = [
|
|||||||
"iana-time-zone",
|
"iana-time-zone",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
|
"serde",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"windows-targets",
|
"windows-targets",
|
||||||
]
|
]
|
||||||
@ -499,7 +506,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856"
|
checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"hashbrown",
|
"hashbrown 0.14.5",
|
||||||
"lock_api",
|
"lock_api",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"parking_lot_core",
|
"parking_lot_core",
|
||||||
@ -511,6 +518,16 @@ version = "2.6.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2"
|
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]]
|
[[package]]
|
||||||
name = "diesel"
|
name = "diesel"
|
||||||
version = "2.2.2"
|
version = "2.2.2"
|
||||||
@ -519,9 +536,11 @@ checksum = "bf97ee7261bb708fa3402fa9c17a54b70e90e3cb98afb3dc8999d5512cb03f94"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
|
"chrono",
|
||||||
"diesel_derives",
|
"diesel_derives",
|
||||||
"itoa",
|
"itoa",
|
||||||
"pq-sys",
|
"pq-sys",
|
||||||
|
"serde_json",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -647,7 +666,7 @@ dependencies = [
|
|||||||
"anymap",
|
"anymap",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"axum",
|
"axum",
|
||||||
"base64",
|
"base64 0.21.7",
|
||||||
"bytes",
|
"bytes",
|
||||||
"ciborium",
|
"ciborium",
|
||||||
"dioxus-cli-config",
|
"dioxus-cli-config",
|
||||||
@ -1400,6 +1419,12 @@ dependencies = [
|
|||||||
"crunchy",
|
"crunchy",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.12.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.14.5"
|
version = "0.14.5"
|
||||||
@ -1428,6 +1453,12 @@ version = "0.3.9"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
|
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hex"
|
||||||
|
version = "0.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "http"
|
name = "http"
|
||||||
version = "0.2.12"
|
version = "0.2.12"
|
||||||
@ -1564,6 +1595,17 @@ dependencies = [
|
|||||||
"unicode-normalization",
|
"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]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "2.4.0"
|
version = "2.4.0"
|
||||||
@ -1571,7 +1613,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c"
|
checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"hashbrown",
|
"hashbrown 0.14.5",
|
||||||
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1580,7 +1623,7 @@ version = "0.7.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "04e8e537b529b8674e97e9fb82c10ff168a290ac3867a0295f112061ffbca1ef"
|
checksum = "04e8e537b529b8674e97e9fb82c10ff168a290ac3867a0295f112061ffbca1ef"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hashbrown",
|
"hashbrown 0.14.5",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -1695,7 +1738,7 @@ version = "0.12.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "37ee39891760e7d94734f6f63fedc29a2e4a152f836120753a72503f09fcf904"
|
checksum = "37ee39891760e7d94734f6f63fedc29a2e4a152f836120753a72503f09fcf904"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hashbrown",
|
"hashbrown 0.14.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1780,6 +1823,12 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-conv"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.19"
|
version = "0.2.19"
|
||||||
@ -1861,7 +1910,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db"
|
checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fixedbitset",
|
"fixedbitset",
|
||||||
"indexmap",
|
"indexmap 2.4.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1907,6 +1956,12 @@ dependencies = [
|
|||||||
"futures-io",
|
"futures-io",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "powerfmt"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ppv-lite86"
|
name = "ppv-lite86"
|
||||||
version = "0.2.20"
|
version = "0.2.20"
|
||||||
@ -2199,6 +2254,36 @@ dependencies = [
|
|||||||
"serde",
|
"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]]
|
[[package]]
|
||||||
name = "server_fn"
|
name = "server_fn"
|
||||||
version = "0.6.14"
|
version = "0.6.14"
|
||||||
@ -2442,6 +2527,37 @@ dependencies = [
|
|||||||
"once_cell",
|
"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]]
|
[[package]]
|
||||||
name = "tinyvec"
|
name = "tinyvec"
|
||||||
version = "1.8.0"
|
version = "1.8.0"
|
||||||
@ -2467,11 +2583,16 @@ checksum = "c7c4ceeeca15c8384bbc3e011dbd8fccb7f068a440b752b7d9b32ceb0ca0e2e8"
|
|||||||
name = "todo-baggins"
|
name = "todo-baggins"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"chrono",
|
||||||
"diesel",
|
"diesel",
|
||||||
"dioxus",
|
"dioxus",
|
||||||
"dioxus-logger",
|
"dioxus-logger",
|
||||||
"dotenvy",
|
"dotenvy",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"serde_with",
|
||||||
|
"tracing",
|
||||||
|
"tracing-wasm",
|
||||||
"validator",
|
"validator",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -2538,7 +2659,7 @@ dependencies = [
|
|||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"hashbrown",
|
"hashbrown 0.14.5",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
@ -7,7 +7,8 @@ edition = "2021"
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[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"] }
|
dioxus = { version = "0.5", features = ["fullstack", "router"] }
|
||||||
|
|
||||||
@ -16,6 +17,10 @@ dioxus-logger = "0.5.1"
|
|||||||
dotenvy = "0.15.7"
|
dotenvy = "0.15.7"
|
||||||
serde = "1.0.208"
|
serde = "1.0.208"
|
||||||
validator = { version = "0.18.1", features = ["derive"] }
|
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]
|
[features]
|
||||||
default = []
|
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]
|
#[component]
|
||||||
pub(crate) fn App() -> Element {
|
pub(crate) fn App() -> Element {
|
||||||
rsx! {
|
rsx! {
|
||||||
|
div {
|
||||||
|
class: "min-h-screen text-white bg-neutral-800",
|
||||||
Router::<Route> {}
|
Router::<Route> {}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,10 +2,12 @@ use crate::components::project_form::ProjectForm;
|
|||||||
use dioxus::core_macro::rsx;
|
use dioxus::core_macro::rsx;
|
||||||
use dioxus::dioxus_core::Element;
|
use dioxus::dioxus_core::Element;
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
|
use crate::components::task_form::TaskForm;
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub(crate) fn Home() -> Element {
|
pub(crate) fn Home() -> Element {
|
||||||
rsx! {
|
rsx! {
|
||||||
ProjectForm {}
|
ProjectForm {}
|
||||||
|
TaskForm {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
pub(crate) mod app;
|
pub(crate) mod app;
|
||||||
pub(crate) mod home;
|
pub(crate) mod home;
|
||||||
pub(crate) mod project_form;
|
pub(crate) mod project_form;
|
||||||
|
pub(crate) mod task_form;
|
||||||
|
@ -19,6 +19,7 @@ pub(crate) fn ProjectForm() -> Element {
|
|||||||
input {
|
input {
|
||||||
r#type: "text",
|
r#type: "text",
|
||||||
name: "title",
|
name: "title",
|
||||||
|
required: true,
|
||||||
placeholder: "title"
|
placeholder: "title"
|
||||||
}
|
}
|
||||||
button {
|
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::fmt::Display;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
ServerInternal,
|
ServerInternal,
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use serde_with::serde_derive::Serialize;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
pub struct ErrorVec<T> {
|
pub struct ErrorVec<T> {
|
||||||
errors: Vec<T>,
|
errors: Vec<T>,
|
||||||
}
|
}
|
||||||
@ -37,7 +39,7 @@ impl<T: Display> Display for ErrorVec<T> {
|
|||||||
impl<T> FromStr for ErrorVec<T> {
|
impl<T> FromStr for ErrorVec<T> {
|
||||||
type Err = ();
|
type Err = ();
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(_: &str) -> Result<Self, Self::Err> {
|
||||||
Ok(ErrorVec { errors: Vec::new() })
|
Ok(ErrorVec { errors: Vec::new() })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
pub(crate) mod error;
|
pub(crate) mod error;
|
||||||
pub(crate) mod error_vec;
|
pub(crate) mod error_vec;
|
||||||
pub(crate) mod project_create_error;
|
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> {
|
impl From<ValidationErrors> for ErrorVec<ProjectCreateError> {
|
||||||
fn from(e: ValidationErrors) -> Self {
|
fn from(validation_errors: ValidationErrors) -> Self {
|
||||||
e.errors()
|
validation_errors.errors()
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|(&field, error_kind)| match field {
|
.flat_map(|(&field, error_kind)| match field {
|
||||||
"title" => match error_kind {
|
"title" => match error_kind {
|
||||||
@ -22,30 +22,30 @@ impl From<ValidationErrors> for ErrorVec<ProjectCreateError> {
|
|||||||
.map(|validation_error| validation_error.code.as_ref())
|
.map(|validation_error| validation_error.code.as_ref())
|
||||||
.map(|code| match code {
|
.map(|code| match code {
|
||||||
"title_length" => ProjectCreateError::TitleLengthInvalid,
|
"title_length" => ProjectCreateError::TitleLengthInvalid,
|
||||||
_ => panic!("unexpected validation error code: {code}"),
|
_ => panic!("Unexpected validation error code: `{code}`."),
|
||||||
})
|
})
|
||||||
.collect::<Vec<ProjectCreateError>>(),
|
.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>>()
|
.collect::<Vec<ProjectCreateError>>()
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// has to be implemented for Dioxus server functions
|
// Has to be implemented for Dioxus server functions.
|
||||||
impl Display for ProjectCreateError {
|
impl Display for ProjectCreateError {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(f, "{:?}", self)
|
write!(f, "{:?}", self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// has to be implemented for Dioxus server functions
|
// Has to be implemented for Dioxus server functions.
|
||||||
impl FromStr for ProjectCreateError {
|
impl FromStr for ProjectCreateError {
|
||||||
type Err = ();
|
type Err = ();
|
||||||
|
|
||||||
fn from_str(_: &str) -> Result<Self, Self::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 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_MIN: u64 = 1;
|
||||||
const TITLE_LENGTH_MAX: u64 = 255;
|
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(table_name = crate::schema::projects)]
|
||||||
#[diesel(check_for_backend(diesel::pg::Pg))]
|
#[diesel(check_for_backend(diesel::pg::Pg))]
|
||||||
pub struct Project {
|
pub struct Project {
|
||||||
|
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,
|
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;
|
mod database_connection;
|
||||||
pub(crate) mod projects;
|
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::errors::project_create_error::ProjectCreateError;
|
||||||
use crate::models::project::{NewProject, Project};
|
use crate::models::project::{NewProject, Project};
|
||||||
use crate::server::database_connection::establish_database_connection;
|
use crate::server::database_connection::establish_database_connection;
|
||||||
use diesel::{RunQueryDsl, SelectableHelper};
|
use diesel::{QueryDsl, RunQueryDsl, SelectableHelper};
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
|
|
||||||
#[server]
|
#[server]
|
||||||
pub(crate) async fn create_project(
|
pub(crate) async fn create_project(new_project: NewProject)
|
||||||
new_project: NewProject,
|
-> Result<Project, ServerFnError<ErrorVec<ProjectCreateError>>> {
|
||||||
) -> Result<Project, ServerFnError<ErrorVec<ProjectCreateError>>> {
|
|
||||||
use crate::schema::projects;
|
use crate::schema::projects;
|
||||||
|
|
||||||
new_project
|
new_project.validate()
|
||||||
.validate()
|
|
||||||
.map_err::<ErrorVec<ProjectCreateError>, _>(|errors| errors.into())?;
|
.map_err::<ErrorVec<ProjectCreateError>, _>(|errors| errors.into())?;
|
||||||
|
|
||||||
let mut connection = establish_database_connection()
|
let mut connection = establish_database_connection()
|
||||||
.map_err::<ErrorVec<ProjectCreateError>, _>(
|
.map_err::<ErrorVec<ProjectCreateError>, _>(
|
||||||
|_| vec![ProjectCreateError::Error(Error::ServerInternal), ].into()
|
|_| vec![ProjectCreateError::Error(Error::ServerInternal)].into()
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let new_project = diesel::insert_into(projects::table)
|
let new_project = diesel::insert_into(projects::table)
|
||||||
@ -27,8 +25,28 @@ pub(crate) async fn create_project(
|
|||||||
.returning(Project::as_returning())
|
.returning(Project::as_returning())
|
||||||
.get_result(&mut connection)
|
.get_result(&mut connection)
|
||||||
.map_err::<ErrorVec<ProjectCreateError>, _>(
|
.map_err::<ErrorVec<ProjectCreateError>, _>(
|
||||||
|_| vec![ProjectCreateError::Error(Error::ServerInternal), ].into()
|
|_| vec![ProjectCreateError::Error(Error::ServerInternal)].into()
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
Ok(new_project)
|
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 */
|
/* stylelint-disable */
|
||||||
|
|
||||||
/* noinspection CssInvalidAtRule */
|
/* noinspection CssInvalidAtRule */
|
||||||
@tailwind base;
|
@tailwind base;
|
||||||
/* noinspection CssInvalidAtRule */
|
/* noinspection CssInvalidAtRule */
|
||||||
@tailwind components;
|
@tailwind components;
|
||||||
/* noinspection CssInvalidAtRule */
|
/* noinspection CssInvalidAtRule */
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
|
html, body, #main {
|
||||||
|
/* noinspection CssInvalidAtRule */
|
||||||
|
@apply min-h-screen;
|
||||||
|
}
|
||||||
|
|
||||||
/* stylelint-enable */
|
/* stylelint-enable */
|
||||||
|
Loading…
x
Reference in New Issue
Block a user