Create Initial Database Schema for Tasks. #1
8 changed files with 621 additions and 4 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
.claude/settings.local.json
|
||||
294
backend/Cargo.lock
generated
294
backend/Cargo.lock
generated
|
|
@ -23,6 +23,27 @@ version = "0.2.21"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||
|
||||
[[package]]
|
||||
name = "android-tzdata"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
|
||||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.99"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100"
|
||||
|
||||
[[package]]
|
||||
name = "atoi"
|
||||
version = "2.0.0"
|
||||
|
|
@ -102,11 +123,15 @@ dependencies = [
|
|||
name = "backend"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
"chrono",
|
||||
"serde",
|
||||
"sqlx",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -154,6 +179,12 @@ dependencies = [
|
|||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.5.0"
|
||||
|
|
@ -166,12 +197,35 @@ version = "1.10.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42bc4aea80032b7bf409b0bc7ccad88853858911b7713a8062fdc0623867bedc"
|
||||
dependencies = [
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"wasm-bindgen",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "concurrent-queue"
|
||||
version = "2.5.0"
|
||||
|
|
@ -187,6 +241,12 @@ version = "0.9.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.17"
|
||||
|
|
@ -435,7 +495,19 @@ checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
|
|||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
"wasi 0.11.1+wasi-snapshot-preview1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"wasi 0.14.2+wasi-0.2.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -586,6 +658,30 @@ dependencies = [
|
|||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.63"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
"iana-time-zone-haiku",
|
||||
"js-sys",
|
||||
"log",
|
||||
"wasm-bindgen",
|
||||
"windows-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone-haiku"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_collections"
|
||||
version = "2.0.0"
|
||||
|
|
@ -720,6 +816,16 @@ version = "1.0.15"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.77"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
|
|
@ -758,6 +864,7 @@ version = "0.30.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
|
@ -828,7 +935,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"wasi",
|
||||
"wasi 0.11.1+wasi-snapshot-preview1",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
|
|
@ -1029,6 +1136,12 @@ dependencies = [
|
|||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "5.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
|
|
@ -1056,7 +1169,7 @@ version = "0.6.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"getrandom 0.2.16",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1197,6 +1310,12 @@ dependencies = [
|
|||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "signature"
|
||||
version = "2.2.0"
|
||||
|
|
@ -1272,6 +1391,7 @@ checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6"
|
|||
dependencies = [
|
||||
"base64",
|
||||
"bytes",
|
||||
"chrono",
|
||||
"crc",
|
||||
"crossbeam-queue",
|
||||
"either",
|
||||
|
|
@ -1292,8 +1412,11 @@ dependencies = [
|
|||
"sha2",
|
||||
"smallvec",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tracing",
|
||||
"url",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1330,6 +1453,7 @@ dependencies = [
|
|||
"sqlx-postgres",
|
||||
"sqlx-sqlite",
|
||||
"syn",
|
||||
"tokio",
|
||||
"url",
|
||||
]
|
||||
|
||||
|
|
@ -1344,6 +1468,7 @@ dependencies = [
|
|||
"bitflags",
|
||||
"byteorder",
|
||||
"bytes",
|
||||
"chrono",
|
||||
"crc",
|
||||
"digest",
|
||||
"dotenvy",
|
||||
|
|
@ -1372,6 +1497,7 @@ dependencies = [
|
|||
"stringprep",
|
||||
"thiserror",
|
||||
"tracing",
|
||||
"uuid",
|
||||
"whoami",
|
||||
]
|
||||
|
||||
|
|
@ -1385,6 +1511,7 @@ dependencies = [
|
|||
"base64",
|
||||
"bitflags",
|
||||
"byteorder",
|
||||
"chrono",
|
||||
"crc",
|
||||
"dotenvy",
|
||||
"etcetera",
|
||||
|
|
@ -1409,6 +1536,7 @@ dependencies = [
|
|||
"stringprep",
|
||||
"thiserror",
|
||||
"tracing",
|
||||
"uuid",
|
||||
"whoami",
|
||||
]
|
||||
|
||||
|
|
@ -1419,6 +1547,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea"
|
||||
dependencies = [
|
||||
"atoi",
|
||||
"chrono",
|
||||
"flume",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
|
|
@ -1434,6 +1563,7 @@ dependencies = [
|
|||
"thiserror",
|
||||
"tracing",
|
||||
"url",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1548,6 +1678,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"bytes",
|
||||
"io-uring",
|
||||
"libc",
|
||||
"mio",
|
||||
|
|
@ -1570,6 +1701,17 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-stream"
|
||||
version = "0.1.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower"
|
||||
version = "0.5.2"
|
||||
|
|
@ -1706,6 +1848,17 @@ version = "1.0.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f33196643e165781c20a5ead5582283a7dacbb87855d867fbc2df3f81eddc1be"
|
||||
dependencies = [
|
||||
"getrandom 0.3.3",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "valuable"
|
||||
version = "0.1.1"
|
||||
|
|
@ -1730,12 +1883,79 @@ version = "0.11.1+wasi-snapshot-preview1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.14.2+wasi-0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
|
||||
dependencies = [
|
||||
"wit-bindgen-rt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasite"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"rustversion",
|
||||
"wasm-bindgen-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "whoami"
|
||||
version = "1.6.1"
|
||||
|
|
@ -1768,6 +1988,65 @@ version = "0.4.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.61.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
|
||||
dependencies = [
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
"windows-link",
|
||||
"windows-result",
|
||||
"windows-strings",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-implement"
|
||||
version = "0.60.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-interface"
|
||||
version = "0.59.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-strings"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
|
|
@ -1907,6 +2186,15 @@ version = "0.52.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rt"
|
||||
version = "0.39.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "writeable"
|
||||
version = "0.6.1"
|
||||
|
|
|
|||
|
|
@ -4,8 +4,13 @@ version = "0.1.0"
|
|||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.99"
|
||||
axum = "0.8.4"
|
||||
sqlx = "0.8.6"
|
||||
chrono = "0.4.41"
|
||||
serde = "1.0.219"
|
||||
sqlx = { version = "0.8.6", features = ["sqlite", "runtime-tokio", "uuid", "chrono"] }
|
||||
tokio = { version = "1.47.1", features = ["rt-multi-thread", "tracing"] }
|
||||
tracing = "0.1.41"
|
||||
tracing-subscriber = "0.3.19"
|
||||
uuid = { version = "1.18.0", features = ["v4"] }
|
||||
|
||||
|
|
|
|||
45
backend/src/database/connection.rs
Normal file
45
backend/src/database/connection.rs
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
use anyhow::Result;
|
||||
use sqlx::{SqlitePool, sqlite::SqliteConnectOptions};
|
||||
use std::str::FromStr;
|
||||
|
||||
/// Database configuration
|
||||
pub struct DatabaseConfig {
|
||||
pub database_url: String,
|
||||
}
|
||||
|
||||
impl Default for DatabaseConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
database_url: "sqlite:local.db".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a SQLx connection pool
|
||||
pub async fn create_pool(config: &DatabaseConfig) -> Result<SqlitePool> {
|
||||
let options = SqliteConnectOptions::from_str(&config.database_url)?.create_if_missing(true);
|
||||
|
||||
let pool = SqlitePool::connect_with(options).await?;
|
||||
|
||||
sqlx::migrate!("./migrations").run(&pool).await?;
|
||||
|
||||
Ok(pool)
|
||||
}
|
||||
|
||||
/// Initialize database with connection pool
|
||||
pub async fn initialize_database() -> Result<SqlitePool> {
|
||||
let config = DatabaseConfig::default();
|
||||
create_pool(&config).await
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub async fn create_test_pool() -> Result<SqlitePool> {
|
||||
let options = SqliteConnectOptions::from_str("sqlite::memory:")?.create_if_missing(true);
|
||||
|
||||
let pool = SqlitePool::connect_with(options).await?;
|
||||
|
||||
sqlx::migrate!("./migrations").run(&pool).await?;
|
||||
|
||||
Ok(pool)
|
||||
}
|
||||
|
||||
3
backend/src/database/mod.rs
Normal file
3
backend/src/database/mod.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
pub mod connection;
|
||||
|
||||
pub use connection::*;
|
||||
|
|
@ -1,5 +1,8 @@
|
|||
use axum::{Router, routing::get};
|
||||
|
||||
mod models;
|
||||
mod database;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
|
|
|||
3
backend/src/models/mod.rs
Normal file
3
backend/src/models/mod.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
mod task;
|
||||
|
||||
pub use task::*;
|
||||
269
backend/src/models/task.rs
Normal file
269
backend/src/models/task.rs
Normal file
|
|
@ -0,0 +1,269 @@
|
|||
use anyhow::{Result, bail};
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::SqlitePool;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy, sqlx::Type)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[sqlx(rename_all = "lowercase")]
|
||||
pub enum TaskStatus {
|
||||
Todo,
|
||||
Done,
|
||||
Backlog,
|
||||
}
|
||||
|
||||
#[derive(sqlx::FromRow)]
|
||||
pub struct TaskModel {
|
||||
pub id: Uuid,
|
||||
pub title: String,
|
||||
pub description: Option<String>,
|
||||
pub status: TaskStatus,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
pub completed_at: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
impl TaskModel {
|
||||
pub async fn insert(
|
||||
pool: &SqlitePool,
|
||||
title: &str,
|
||||
description: Option<&str>,
|
||||
) -> Result<TaskModel> {
|
||||
if title.len() == 0 {
|
||||
bail!("Title must not be empty");
|
||||
}
|
||||
|
||||
let id = Uuid::new_v4();
|
||||
let result = sqlx::query_as(
|
||||
"INSERT INTO tasks (id, title, description) VALUES ($1, $2, $3) RETURNING *",
|
||||
)
|
||||
.bind(id)
|
||||
.bind(title)
|
||||
.bind(description)
|
||||
.fetch_one(pool)
|
||||
.await?;
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub async fn get_by_id(pool: &SqlitePool, id: Uuid) -> Result<TaskModel> {
|
||||
let result = sqlx::query_as("SELECT * FROM tasks WHERE id = $1")
|
||||
.bind(id)
|
||||
.fetch_one(pool)
|
||||
.await?;
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub async fn update(self, pool: &SqlitePool) -> Result<TaskModel> {
|
||||
let now: DateTime<Utc> = Utc::now();
|
||||
|
||||
let _ = sqlx::query(
|
||||
"UPDATE tasks
|
||||
SET title=$1, description=$2, status=$3, updated_at=$4, completed_at=$5
|
||||
WHERE id=$6",
|
||||
)
|
||||
.bind(&self.title)
|
||||
.bind(&self.description)
|
||||
.bind(&self.status)
|
||||
.bind(now)
|
||||
.bind(&self.completed_at)
|
||||
.bind(&self.id)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
TaskModel::get_by_id(pool, self.id).await
|
||||
}
|
||||
|
||||
pub async fn delete(pool: &SqlitePool, id: Uuid) -> Result<()> {
|
||||
sqlx::query("DELETE FROM tasks WHERE id = $1")
|
||||
.bind(id)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn list_all(pool: &SqlitePool) -> Result<Vec<TaskModel>> {
|
||||
let tasks = sqlx::query_as("SELECT * FROM tasks ORDER BY created_at DESC")
|
||||
.fetch_all(pool)
|
||||
.await?;
|
||||
Ok(tasks)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::database::create_test_pool;
|
||||
|
||||
async fn setup_test_db() -> SqlitePool {
|
||||
create_test_pool()
|
||||
.await
|
||||
.expect("Failed to create test pool")
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_insert_task() {
|
||||
let pool = setup_test_db().await;
|
||||
|
||||
let title = "Test Task";
|
||||
let description = "Test Description";
|
||||
|
||||
let task = TaskModel::insert(&pool, title, Some(description))
|
||||
.await
|
||||
.expect("Failed to insert task");
|
||||
|
||||
assert_eq!(task.title, title);
|
||||
assert_eq!(task.description, Some(description.to_string()));
|
||||
assert_eq!(task.status, TaskStatus::Todo);
|
||||
assert!(task.completed_at.is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_insert_task_with_null_description() {
|
||||
let pool = setup_test_db().await;
|
||||
|
||||
let title = "Test Task";
|
||||
let description = None;
|
||||
|
||||
let task = TaskModel::insert(&pool, title, description)
|
||||
.await
|
||||
.expect("Failed to insert task");
|
||||
|
||||
assert_eq!(task.title, title);
|
||||
assert_eq!(task.description, None);
|
||||
assert_eq!(task.status, TaskStatus::Todo);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_by_id_found() {
|
||||
let pool = setup_test_db().await;
|
||||
|
||||
let title = "Test Task";
|
||||
let description = Some("Test Description");
|
||||
|
||||
let inserted_task = TaskModel::insert(&pool, title, description)
|
||||
.await
|
||||
.expect("Failed to insert task");
|
||||
|
||||
let retrieved_task = TaskModel::get_by_id(&pool, inserted_task.id)
|
||||
.await
|
||||
.expect("Failed to get task by id");
|
||||
|
||||
assert_eq!(retrieved_task.id, inserted_task.id);
|
||||
assert_eq!(retrieved_task.title, inserted_task.title);
|
||||
assert_eq!(retrieved_task.description, inserted_task.description);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_by_id_not_found() {
|
||||
let pool = setup_test_db().await;
|
||||
|
||||
let non_existent_id = Uuid::new_v4();
|
||||
let result = TaskModel::get_by_id(&pool, non_existent_id).await;
|
||||
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_update_task() {
|
||||
let pool = setup_test_db().await;
|
||||
|
||||
let title = "Original Task";
|
||||
let description = Some("Original Description");
|
||||
|
||||
let mut task = TaskModel::insert(&pool, title, description)
|
||||
.await
|
||||
.expect("Failed to insert task");
|
||||
|
||||
// Update the task
|
||||
task.title = "Updated Task".to_string();
|
||||
task.description = Some("Updated Description".to_string());
|
||||
task.status = TaskStatus::Done;
|
||||
task.completed_at = Some(Utc::now());
|
||||
|
||||
let updated_task = task.update(&pool).await.expect("Failed to update task");
|
||||
|
||||
assert_eq!(updated_task.title, "Updated Task");
|
||||
assert_eq!(
|
||||
updated_task.description,
|
||||
Some("Updated Description".to_string())
|
||||
);
|
||||
assert_eq!(updated_task.status, TaskStatus::Done);
|
||||
assert!(updated_task.completed_at.is_some());
|
||||
assert!(updated_task.updated_at > updated_task.created_at);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_delete_task() {
|
||||
let pool = setup_test_db().await;
|
||||
|
||||
let title = "Task to Delete";
|
||||
let description = Some("This will be deleted");
|
||||
|
||||
let task = TaskModel::insert(&pool, title, description)
|
||||
.await
|
||||
.expect("Failed to insert task");
|
||||
|
||||
// Delete the task
|
||||
TaskModel::delete(&pool, task.id)
|
||||
.await
|
||||
.expect("Failed to delete task");
|
||||
|
||||
// Verify it's gone
|
||||
let result = TaskModel::get_by_id(&pool, task.id).await;
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_list_all_tasks() {
|
||||
let pool = setup_test_db().await;
|
||||
|
||||
// Insert multiple tasks
|
||||
let _task1 = TaskModel::insert(&pool, "Task 1", Some("Desc 1"))
|
||||
.await
|
||||
.expect("Failed to insert task 1");
|
||||
|
||||
let _task2 = TaskModel::insert(&pool, "Task 2", None)
|
||||
.await
|
||||
.expect("Failed to insert task 2");
|
||||
|
||||
let tasks = TaskModel::list_all(&pool)
|
||||
.await
|
||||
.expect("Failed to list tasks");
|
||||
|
||||
assert_eq!(tasks.len(), 2);
|
||||
|
||||
// Check that both tasks are present (order may vary due to timing)
|
||||
let titles: Vec<&String> = tasks.iter().map(|t| &t.title).collect();
|
||||
assert!(titles.contains(&&"Task 1".to_string()));
|
||||
assert!(titles.contains(&&"Task 2".to_string()));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_task_status_serialization() {
|
||||
let pool = setup_test_db().await;
|
||||
|
||||
// Test each status
|
||||
let statuses = vec![TaskStatus::Todo, TaskStatus::Done, TaskStatus::Backlog];
|
||||
|
||||
for status in statuses {
|
||||
let mut task = TaskModel::insert(&pool, "Test Task", None)
|
||||
.await
|
||||
.expect("Failed to insert task");
|
||||
|
||||
task.status = status;
|
||||
task = task.update(&pool).await.expect("Failed to update task");
|
||||
|
||||
assert_eq!(task.status, status);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_empty_title_constraint() {
|
||||
let pool = setup_test_db().await;
|
||||
|
||||
let result = TaskModel::insert(&pool, "", None).await;
|
||||
|
||||
assert!(result.is_err());
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue