Compare commits

..

7 Commits

Author SHA1 Message Date
brian
39e656ef91 feat: axum + diesel 2025-05-23 23:50:20 +08:00
acx
7a58035d0c temp 2024-11-24 11:29:18 +08:00
acx
3fa7649877 feat: operations model 2024-11-05 00:19:27 +08:00
acx
de38e20d3a feat: batch get for transaction and transaction amount 2024-09-17 15:02:46 +08:00
acx
ceb3edba39 feat: transaction time 2024-08-15 00:56:25 +08:00
acx
2b2b895b7d feat: transaction query pagination 2024-08-12 23:37:07 +08:00
acx
dcc4c68abb feat: ignore is_delete when serializing to json 2024-08-12 23:36:42 +08:00
21 changed files with 628 additions and 176 deletions

1
.env.template Normal file
View File

@@ -0,0 +1 @@
DATABASE_URL=postgres://username:password@localhost/diesel_demo

195
Cargo.lock generated
View File

@@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo. # This file is automatically @generated by Cargo.
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 4
[[package]] [[package]]
name = "addr2line" name = "addr2line"
@@ -60,14 +60,14 @@ checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
[[package]] [[package]]
name = "axum" name = "axum"
version = "0.7.5" version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf" checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5"
dependencies = [ dependencies = [
"async-trait",
"axum-core", "axum-core",
"axum-macros", "axum-macros 0.5.0",
"bytes", "bytes",
"form_urlencoded",
"futures-util", "futures-util",
"http", "http",
"http-body", "http-body",
@@ -85,9 +85,9 @@ dependencies = [
"serde_json", "serde_json",
"serde_path_to_error", "serde_path_to_error",
"serde_urlencoded", "serde_urlencoded",
"sync_wrapper 1.0.1", "sync_wrapper",
"tokio", "tokio",
"tower", "tower 0.5.2",
"tower-layer", "tower-layer",
"tower-service", "tower-service",
"tracing", "tracing",
@@ -95,20 +95,19 @@ dependencies = [
[[package]] [[package]]
name = "axum-core" name = "axum-core"
version = "0.4.3" version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3" checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6"
dependencies = [ dependencies = [
"async-trait",
"bytes", "bytes",
"futures-util", "futures-core",
"http", "http",
"http-body", "http-body",
"http-body-util", "http-body-util",
"mime", "mime",
"pin-project-lite", "pin-project-lite",
"rustversion", "rustversion",
"sync_wrapper 0.1.2", "sync_wrapper",
"tower-layer", "tower-layer",
"tower-service", "tower-service",
"tracing", "tracing",
@@ -116,9 +115,9 @@ dependencies = [
[[package]] [[package]]
name = "axum-extra" name = "axum-extra"
version = "0.9.3" version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0be6ea09c9b96cb5076af0de2e383bd2bc0c18f827cf1967bdd353e0b910d733" checksum = "45bf463831f5131b7d3c756525b305d40f1185b688565648a92e1392ca35713d"
dependencies = [ dependencies = [
"axum", "axum",
"axum-core", "axum-core",
@@ -130,11 +129,11 @@ dependencies = [
"http-body-util", "http-body-util",
"mime", "mime",
"pin-project-lite", "pin-project-lite",
"rustversion",
"serde", "serde",
"tower", "tower 0.5.2",
"tower-layer", "tower-layer",
"tower-service", "tower-service",
"tracing",
] ]
[[package]] [[package]]
@@ -149,6 +148,17 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "axum-macros"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "backtrace" name = "backtrace"
version = "0.3.73" version = "0.3.73"
@@ -239,7 +249,7 @@ dependencies = [
"num-traits", "num-traits",
"serde", "serde",
"wasm-bindgen", "wasm-bindgen",
"windows-targets 0.52.6", "windows-targets",
] ]
[[package]] [[package]]
@@ -544,7 +554,7 @@ dependencies = [
"async-trait", "async-trait",
"axum", "axum",
"axum-extra", "axum-extra",
"axum-macros", "axum-macros 0.4.1",
"chrono", "chrono",
"deadpool-diesel", "deadpool-diesel",
"diesel", "diesel",
@@ -557,7 +567,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"tokio", "tokio",
"tower", "tower 0.4.13",
"tower-http", "tower-http",
"tracing", "tracing",
"tracing-subscriber", "tracing-subscriber",
@@ -656,6 +666,8 @@ dependencies = [
"hyper", "hyper",
"pin-project-lite", "pin-project-lite",
"tokio", "tokio",
"tower 0.4.13",
"tower-service",
] ]
[[package]] [[package]]
@@ -725,9 +737,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.155" version = "0.2.172"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
[[package]] [[package]]
name = "lock_api" name = "lock_api"
@@ -756,9 +768,9 @@ dependencies = [
[[package]] [[package]]
name = "matchit" name = "matchit"
version = "0.7.3" version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
[[package]] [[package]]
name = "memchr" name = "memchr"
@@ -783,13 +795,13 @@ dependencies = [
[[package]] [[package]]
name = "mio" name = "mio"
version = "0.8.11" version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
dependencies = [ dependencies = [
"libc", "libc",
"wasi", "wasi",
"windows-sys 0.48.0", "windows-sys",
] ]
[[package]] [[package]]
@@ -887,7 +899,7 @@ dependencies = [
"libc", "libc",
"redox_syscall", "redox_syscall",
"smallvec", "smallvec",
"windows-targets 0.52.6", "windows-targets",
] ]
[[package]] [[package]]
@@ -1068,7 +1080,7 @@ dependencies = [
"libc", "libc",
"spin", "spin",
"untrusted", "untrusted",
"windows-sys 0.52.0", "windows-sys",
] ]
[[package]] [[package]]
@@ -1213,7 +1225,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c"
dependencies = [ dependencies = [
"libc", "libc",
"windows-sys 0.52.0", "windows-sys",
] ]
[[package]] [[package]]
@@ -1245,12 +1257,6 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "sync_wrapper"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
[[package]] [[package]]
name = "sync_wrapper" name = "sync_wrapper"
version = "1.0.1" version = "1.0.1"
@@ -1320,28 +1326,27 @@ dependencies = [
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.38.0" version = "1.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" checksum = "2513ca694ef9ede0fb23fe71a4ee4107cb102b9dc1930f6d0fd77aae068ae165"
dependencies = [ dependencies = [
"backtrace", "backtrace",
"bytes", "bytes",
"libc", "libc",
"mio", "mio",
"num_cpus",
"parking_lot", "parking_lot",
"pin-project-lite", "pin-project-lite",
"signal-hook-registry", "signal-hook-registry",
"socket2", "socket2",
"tokio-macros", "tokio-macros",
"windows-sys 0.48.0", "windows-sys",
] ]
[[package]] [[package]]
name = "tokio-macros" name = "tokio-macros"
version = "2.3.0" version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -1364,6 +1369,22 @@ dependencies = [
"tracing", "tracing",
] ]
[[package]]
name = "tower"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9"
dependencies = [
"futures-core",
"futures-util",
"pin-project-lite",
"sync_wrapper",
"tokio",
"tower-layer",
"tower-service",
"tracing",
]
[[package]] [[package]]
name = "tower-http" name = "tower-http"
version = "0.5.2" version = "0.5.2"
@@ -1383,15 +1404,15 @@ dependencies = [
[[package]] [[package]]
name = "tower-layer" name = "tower-layer"
version = "0.3.2" version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
[[package]] [[package]]
name = "tower-service" name = "tower-service"
version = "0.3.2" version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
[[package]] [[package]]
name = "tracing" name = "tracing"
@@ -1579,16 +1600,7 @@ version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
dependencies = [ dependencies = [
"windows-targets 0.52.6", "windows-targets",
]
[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets 0.48.5",
] ]
[[package]] [[package]]
@@ -1597,22 +1609,7 @@ version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [ dependencies = [
"windows-targets 0.52.6", "windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
dependencies = [
"windows_aarch64_gnullvm 0.48.5",
"windows_aarch64_msvc 0.48.5",
"windows_i686_gnu 0.48.5",
"windows_i686_msvc 0.48.5",
"windows_x86_64_gnu 0.48.5",
"windows_x86_64_gnullvm 0.48.5",
"windows_x86_64_msvc 0.48.5",
] ]
[[package]] [[package]]
@@ -1621,46 +1618,28 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [ dependencies = [
"windows_aarch64_gnullvm 0.52.6", "windows_aarch64_gnullvm",
"windows_aarch64_msvc 0.52.6", "windows_aarch64_msvc",
"windows_i686_gnu 0.52.6", "windows_i686_gnu",
"windows_i686_gnullvm", "windows_i686_gnullvm",
"windows_i686_msvc 0.52.6", "windows_i686_msvc",
"windows_x86_64_gnu 0.52.6", "windows_x86_64_gnu",
"windows_x86_64_gnullvm 0.52.6", "windows_x86_64_gnullvm",
"windows_x86_64_msvc 0.52.6", "windows_x86_64_msvc",
] ]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]] [[package]]
name = "windows_aarch64_gnullvm" name = "windows_aarch64_gnullvm"
version = "0.52.6" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows_aarch64_msvc"
version = "0.52.6" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_i686_gnu"
version = "0.52.6" version = "0.52.6"
@@ -1673,48 +1652,24 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_msvc"
version = "0.52.6" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnu"
version = "0.52.6" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]] [[package]]
name = "windows_x86_64_gnullvm" name = "windows_x86_64_gnullvm"
version = "0.52.6" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]] [[package]]
name = "windows_x86_64_msvc" name = "windows_x86_64_msvc"
version = "0.52.6" version = "0.52.6"

View File

@@ -7,8 +7,8 @@ edition = "2021"
[dependencies] [dependencies]
async-trait = "0.1.81" async-trait = "0.1.81"
axum = {version = "0.7.5", features = ["macros"]} axum = {version = "0.8", features = ["macros"]}
axum-extra = { version = "0.9.3", features = ["typed-header"] } axum-extra = { version = "0.10", features = ["typed-header"] }
chrono = {version = "0.4", features = ["serde"]} chrono = {version = "0.4", features = ["serde"]}
deadpool-diesel = {version ="0.6.1", features = ["postgres"]} deadpool-diesel = {version ="0.6.1", features = ["postgres"]}
diesel = { version = "2", features = ["postgres", "chrono"] } diesel = { version = "2", features = ["postgres", "chrono"] }

View File

@@ -2,8 +2,8 @@
# see https://diesel.rs/guides/configuring-diesel-cli # see https://diesel.rs/guides/configuring-diesel-cli
[print_schema] [print_schema]
file = "src/schema.rs" file = "src/model/schema.rs"
custom_type_derives = ["diesel::query_builder::QueryId", "Clone"] custom_type_derives = ["diesel::query_builder::QueryId", "Clone"]
[migrations_directory] [migrations_directory]
dir = "/data/codes/helios-server-rs/migrations" dir = "./migrations"

View File

@@ -7,5 +7,6 @@ DROP TABLE IF EXISTS "transactions";
DROP TABLE IF EXISTS "transaction_tag_rels"; DROP TABLE IF EXISTS "transaction_tag_rels";
DROP TABLE IF EXISTS "accounts"; DROP TABLE IF EXISTS "accounts";
DROP TABLE IF EXISTS "amounts"; DROP TABLE IF EXISTS "amounts";
DROP TABLE IF EXISTS "users"; DROP TABLE IF EXISTS "users";
DROP TABLE IF EXISTS "operations";
DROP TABLE IF EXISTS "operation_snapshots";

View File

@@ -1,5 +1,4 @@
-- Your SQL goes here -- Your SQL goes here
-- Your SQL goes here
CREATE TABLE "categories" ( CREATE TABLE "categories" (
"id" BIGSERIAL PRIMARY KEY, "id" BIGSERIAL PRIMARY KEY,
"uid" BIGINT NOT NULL, "uid" BIGINT NOT NULL,
@@ -7,6 +6,7 @@ CREATE TABLE "categories" (
"name" TEXT NOT NULL, "name" TEXT NOT NULL,
"level" INT NOT NULL DEFAULT 0, "level" INT NOT NULL DEFAULT 0,
"parent_category_id" BIGINT NOT NULL DEFAULT 0, "parent_category_id" BIGINT NOT NULL DEFAULT 0,
"op_id" BIGINT NOT NULL DEFAULT 0,
"is_delete" BOOLEAN NOT NULL DEFAULT FALSE, "is_delete" BOOLEAN NOT NULL DEFAULT FALSE,
"create_at" TIMESTAMP NOT NULL DEFAULT current_timestamp, "create_at" TIMESTAMP NOT NULL DEFAULT current_timestamp,
"update_at" TIMESTAMP NOT NULL DEFAULT current_timestamp "update_at" TIMESTAMP NOT NULL DEFAULT current_timestamp
@@ -19,6 +19,7 @@ CREATE TABLE "tags" (
"name" TEXT NOT NULL, "name" TEXT NOT NULL,
"level" INT NOT NULL DEFAULT 0, "level" INT NOT NULL DEFAULT 0,
"parent_tag_id" BIGINT NOT NULL DEFAULT 0, "parent_tag_id" BIGINT NOT NULL DEFAULT 0,
"op_id" BIGINT NOT NULL DEFAULT 0,
"is_delete" BOOLEAN NOT NULL DEFAULT FALSE, "is_delete" BOOLEAN NOT NULL DEFAULT FALSE,
"create_at" TIMESTAMP NOT NULL DEFAULT current_timestamp, "create_at" TIMESTAMP NOT NULL DEFAULT current_timestamp,
"update_at" TIMESTAMP NOT NULL DEFAULT current_timestamp "update_at" TIMESTAMP NOT NULL DEFAULT current_timestamp
@@ -28,6 +29,7 @@ CREATE TABLE "books" (
"id" BIGSERIAL PRIMARY KEY, "id" BIGSERIAL PRIMARY KEY,
"uid" BIGINT NOT NULL, "uid" BIGINT NOT NULL,
"name" TEXT NOT NULL, "name" TEXT NOT NULL,
"op_id" BIGINT NOT NULL DEFAULT 0,
"is_delete" BOOLEAN NOT NULL DEFAULT FALSE, "is_delete" BOOLEAN NOT NULL DEFAULT FALSE,
"create_at" TIMESTAMP NOT NULL DEFAULT current_timestamp, "create_at" TIMESTAMP NOT NULL DEFAULT current_timestamp,
"update_at" TIMESTAMP NOT NULL DEFAULT current_timestamp "update_at" TIMESTAMP NOT NULL DEFAULT current_timestamp
@@ -39,6 +41,7 @@ CREATE TABLE "transactions" (
"book_id" BIGINT NOT NULL, "book_id" BIGINT NOT NULL,
"description" TEXT NOT NULL, "description" TEXT NOT NULL,
"category_id" BIGINT NOT NULL, "category_id" BIGINT NOT NULL,
"op_id" BIGINT NOT NULL DEFAULT 0,
"is_delete" BOOLEAN NOT NULL DEFAULT FALSE, "is_delete" BOOLEAN NOT NULL DEFAULT FALSE,
"time" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT current_timestamp, "time" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT current_timestamp,
"create_at" TIMESTAMP NOT NULL DEFAULT current_timestamp, "create_at" TIMESTAMP NOT NULL DEFAULT current_timestamp,
@@ -50,6 +53,7 @@ CREATE TABLE "transaction_tag_rels" (
"uid" BIGINT NOT NULL, "uid" BIGINT NOT NULL,
"transaction_id" BIGINT NOT NULL, "transaction_id" BIGINT NOT NULL,
"tag_id" BIGINT NOT NULL, "tag_id" BIGINT NOT NULL,
"op_id" BIGINT NOT NULL DEFAULT 0,
"is_delete" BOOLEAN NOT NULL DEFAULT FALSE, "is_delete" BOOLEAN NOT NULL DEFAULT FALSE,
"create_at" TIMESTAMP NOT NULL DEFAULT current_timestamp, "create_at" TIMESTAMP NOT NULL DEFAULT current_timestamp,
"update_at" TIMESTAMP NOT NULL DEFAULT current_timestamp "update_at" TIMESTAMP NOT NULL DEFAULT current_timestamp
@@ -60,6 +64,7 @@ CREATE TABLE "accounts" (
"uid" BIGINT NOT NULL, "uid" BIGINT NOT NULL,
"name" TEXT NOT NULL, "name" TEXT NOT NULL,
"account_type" BIGINT NOT NULL DEFAULT 0, "account_type" BIGINT NOT NULL DEFAULT 0,
"op_id" BIGINT NOT NULL DEFAULT 0,
"is_delete" BOOLEAN NOT NULL DEFAULT FALSE, "is_delete" BOOLEAN NOT NULL DEFAULT FALSE,
"create_at" TIMESTAMP NOT NULL DEFAULT current_timestamp, "create_at" TIMESTAMP NOT NULL DEFAULT current_timestamp,
"update_at" TIMESTAMP NOT NULL DEFAULT current_timestamp "update_at" TIMESTAMP NOT NULL DEFAULT current_timestamp
@@ -73,6 +78,7 @@ CREATE TABLE "amounts" (
"value" BIGINT NOT NULL DEFAULT 0, "value" BIGINT NOT NULL DEFAULT 0,
"expo" BIGINT NOT NULL DEFAULT 5, "expo" BIGINT NOT NULL DEFAULT 5,
"currency" TEXT NOT NULL DEFAULT '', "currency" TEXT NOT NULL DEFAULT '',
"op_id" BIGINT NOT NULL DEFAULT 0,
"is_delete" BOOLEAN NOT NULL DEFAULT FALSE, "is_delete" BOOLEAN NOT NULL DEFAULT FALSE,
"create_at" TIMESTAMP NOT NULL DEFAULT current_timestamp, "create_at" TIMESTAMP NOT NULL DEFAULT current_timestamp,
"update_at" TIMESTAMP NOT NULL DEFAULT current_timestamp "update_at" TIMESTAMP NOT NULL DEFAULT current_timestamp
@@ -87,3 +93,20 @@ CREATE TABLE "users" (
"create_at" TIMESTAMP NOT NULL DEFAULT current_timestamp, "create_at" TIMESTAMP NOT NULL DEFAULT current_timestamp,
"update_at" TIMESTAMP NOT NULL DEFAULT current_timestamp "update_at" TIMESTAMP NOT NULL DEFAULT current_timestamp
); );
CREATE TABLE "operations" (
"id" BIGSERIAL PRIMARY KEY,
"uid" BIGINT NOT NULL,
"entity_type" BIGINT NOT NULL,
"entity_id" BIGINT NOT NULL,
"action" BIGINT NOT NULL,
"create_at" TIMESTAMP NOT NULL DEFAULT current_timestamp,
"update_at" TIMESTAMP NOT NULL DEFAULT current_timestamp
);
CREATE TABLE "operation_snapshots" (
"id" BIGSERIAL PRIMARY KEY,
"uid" BIGINT NOT NULL,
"max_op_id" BIGINT NOT NULL,
"create_at" TIMESTAMP NOT NULL DEFAULT current_timestamp
);

View File

@@ -35,7 +35,7 @@ pub struct CreateAccountResponse {
pub fn get_nest_handlers() -> Router<crate::AppState> { pub fn get_nest_handlers() -> Router<crate::AppState> {
Router::new() Router::new()
.route("/", post(create_account).get(get_all_accounts)) .route("/", post(create_account).get(get_all_accounts))
.route("/:id", post(update_account).get(get_account)) .route("/{id}", post(update_account).get(get_account))
} }
#[debug_handler] #[debug_handler]

View File

@@ -32,7 +32,7 @@ pub struct CreateBookResponse {
pub fn get_nest_handlers() -> Router<crate::AppState> { pub fn get_nest_handlers() -> Router<crate::AppState> {
Router::new() Router::new()
.route("/", post(create_book).get(get_all_books)) .route("/", post(create_book).get(get_all_books))
.route("/:id", post(update_book).get(get_book)) .route("/{id}", post(update_book).get(get_book))
} }
#[debug_handler] #[debug_handler]

View File

@@ -1,3 +1,6 @@
use std::cell::RefCell;
use std::rc::Rc;
use std::sync::Arc;
// use std::sync::Arc; // use std::sync::Arc;
use axum::routing::{get, post}; use axum::routing::{get, post};
use axum::{ use axum::{
@@ -12,6 +15,7 @@ use serde::{Deserialize, Serialize};
// use serde_json::to_string; // use serde_json::to_string;
use crate::model::db_model; use crate::model::db_model;
use crate::model::schema; use crate::model::schema;
use crate::model::schema::categories::parent_category_id;
use crate::util; use crate::util;
// use crate::model::schema::categories::dsl::categories; // use crate::model::schema::categories::dsl::categories;
use crate::util::req::CommonResp; use crate::util::req::CommonResp;
@@ -19,28 +23,29 @@ use chrono::prelude::*;
use tracing::info; use tracing::info;
use crate::middleware::auth; use crate::middleware::auth;
use crate::middleware::auth::Claims; use crate::middleware::auth::Claims;
use crate::model::db_model::Category;
#[derive(Serialize)] use crate::util::operation::{
pub struct CreateCategoryResponse { EntityType, ENTITY_CATEGORY,
id: i64, ActionType, ACTION_CREATE, ACTION_UPDATE, ACTION_DELETE,
name: String, };
level: i32,
parent_category_id: i64,
book_id: i64,
}
pub fn get_nest_handlers() -> Router<crate::AppState> { pub fn get_nest_handlers() -> Router<crate::AppState> {
Router::new() Router::new()
.route("/", post(create_category).get(get_all_categories)) .route("/", post(create_category).get(get_all_categories))
.route("/:id", post(update_category).get(get_category)) .route("/{id}", post(update_category).get(get_category))
} }
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct CreateCategoryRequest { pub struct CreateCategoryRequest {
name: String, name: String,
level: i32, level: String,
parent_category_id: i64, parent_category_id: String,
book_id: i64, book_id: String,
}
#[derive(Serialize)]
pub struct CreateCategoryResponse {
id: i64,
} }
#[debug_handler] #[debug_handler]
@@ -48,9 +53,35 @@ pub async fn create_category(
State(app_state): State<crate::AppState>, State(app_state): State<crate::AppState>,
claims: Claims, claims: Claims,
Json(payload): Json<CreateCategoryRequest>, Json(payload): Json<CreateCategoryRequest>,
) -> Result<Json<db_model::Category>, (StatusCode, String)> { ) -> Result<Json<CreateCategoryResponse>, (StatusCode, String)> {
let uid: i64 = claims.uid.clone(); // TODO replace with actual user id. let uid: i64 = claims.uid.clone();
// let ret = CreateCategoryResponse{id: 134132413541, name: "24532452".to_string()}; let level: i32 = match payload.level.parse() {
Ok(level) => level,
Err(_) => {
return Err((
StatusCode::BAD_REQUEST,
"Invalid level".to_string(),
))
}
};
let parent_cid: i64 = match payload.parent_category_id.parse(){
Ok(id) => id,
Err(_) => {
return Err((
StatusCode::BAD_REQUEST,
"Invalid parent_category_id".to_string(),
))
}
};
let book_id: i64 = match payload.book_id.parse() {
Ok(id) => id,
Err(_) => {
return Err((
StatusCode::BAD_REQUEST,
"Invalid book_id".to_string(),
))
}
};
let conn = app_state let conn = app_state
.db .db
.get() .get()
@@ -59,22 +90,69 @@ pub async fn create_category(
let new_category = db_model::CategoryForm { let new_category = db_model::CategoryForm {
name: payload.name, name: payload.name,
uid: uid, uid: uid,
level: payload.level, level: level,
parent_category_id: payload.parent_category_id, parent_category_id: parent_cid,
book_id: payload.book_id, book_id: book_id,
}; };
let res = conn let new_operation = db_model::CreateOperation{
uid: uid,
entity_type: ENTITY_CATEGORY,
entity_id: 0,
action: ACTION_CREATE,
};
let mut create_response = CreateCategoryResponse{
id: 0,
};
// Check if book exists under current user
let book_exists = conn
.interact(move |conn| { .interact(move |conn| {
diesel::insert_into(schema::categories::table) schema::books::table
.values(&new_category) .select(diesel::dsl::exists(db_model::Category.as_select().filter(schema::books::id.eq(book_id))))
.returning(db_model::Category::as_returning()) // .filter(schema::books::uid.eq(uid))(schema::books::id.eq(book_id))))
.get_result(conn) .get_result::<bool>(conn)
}) })
.await .await
.map_err(util::req::internal_error)? .map_err(util::req::internal_error)?
.map_err(util::req::internal_error)?; .map_err(util::req::internal_error)?;
// let ret = CreateCategoryResponse{id: res.id, name: res.name};
Ok(Json(res)) if !book_exists {
return Err((StatusCode::NOT_FOUND, "Book not found for the user".to_string()));
}
let cuid = uid;
let create_result = conn
.interact(move |conn| {
conn.transaction(|conn| {
let category = diesel::insert_into(schema::categories::table)
.values(&new_category)
.returning(db_model::Category::as_returning())
.get_result(conn)?;
let operation = diesel::insert_into(schema::operations::table)
.values(&new_operation)
.returning(db_model::Operation::as_returning())
.get_result(conn)?;
diesel::update(schema::categories::table)
.filter(schema::categories::id.eq(category.id))
.filter(schema::categories::uid.eq(cuid))
.set((schema::categories::op_id.eq(operation.id)))
.execute(conn)?;
diesel::update(schema::operations::table)
.filter(schema::operations::id.eq(operation.id))
.filter(schema::operations::uid.eq(cuid))
.set((schema::operations::entity_id.eq(category.id)))
.execute(conn)?;
diesel::result::QueryResult::Ok((category.id))
})
// diesel::insert_into(schema::categories::table)
// .values(&new_category)
// .returning(db_model::Category::as_returning())
// .get_result(conn)
})
.await
.map_err(util::req::internal_error)?
.map_err(util::req::internal_error)?;
create_response.id = create_result;
Ok(Json(create_response))
} }
pub async fn update_category( pub async fn update_category(
@@ -83,8 +161,35 @@ pub async fn update_category(
claims: Claims, claims: Claims,
Json(payload): Json<CreateCategoryRequest>, Json(payload): Json<CreateCategoryRequest>,
) -> Result<Json<CommonResp>, (StatusCode, String)> { ) -> Result<Json<CommonResp>, (StatusCode, String)> {
let uid: i64 = claims.uid.clone(); // TODO replace with actual user id. let uid: i64 = claims.uid.clone();
// let ret = CreateCategoryResponse{id: 134132413541, name: "24532452".to_string()}; let level: i32 = match payload.level.parse() {
Ok(level) => level,
Err(_) => {
return Err((
StatusCode::BAD_REQUEST,
"Invalid level".to_string(),
))
}
};
let parent_cid: i64 = match payload.parent_category_id.parse(){
Ok(id) => id,
Err(_) => {
return Err((
StatusCode::BAD_REQUEST,
"Invalid parent_category_id".to_string(),
))
}
};
let book_id: i64 = match payload.book_id.parse() {
Ok(id) => id,
Err(_) => {
return Err((
StatusCode::BAD_REQUEST,
"Invalid book_id".to_string(),
))
}
};
let conn = app_state let conn = app_state
.db .db
.get() .get()
@@ -98,8 +203,8 @@ pub async fn update_category(
.filter(schema::categories::uid.eq(uid)) .filter(schema::categories::uid.eq(uid))
.set(( .set((
schema::categories::name.eq(payload.name), schema::categories::name.eq(payload.name),
schema::categories::level.eq(payload.level), schema::categories::level.eq(level),
schema::categories::parent_category_id.eq(payload.parent_category_id), schema::categories::parent_category_id.eq(parent_cid),
schema::categories::update_at.eq(now), schema::categories::update_at.eq(now),
)) ))
.execute(conn) .execute(conn)

View File

@@ -3,3 +3,4 @@ pub mod tag;
pub mod book; pub mod book;
pub mod account; pub mod account;
pub mod transaction; pub mod transaction;
pub mod operation;

81
src/ledger/operation.rs Normal file
View File

@@ -0,0 +1,81 @@
use diesel::prelude::*;
use serde::{Serialize, Deserialize};
use axum::{extract::{Path, State, Query}, http::StatusCode, Json, Router};
use axum::routing::get;
use diesel::dsl::max;
use crate::model::{db_model, schema};
use crate::middleware::auth::Claims;
use crate::model::db_model::Operation;
use crate::util;
#[derive(Serialize)]
pub struct GetOperationsResponse {
start: i64,
end: i64,
total: i64,
operations: Vec<db_model::Operation>,
}
#[derive(Deserialize)]
pub struct GetOperationsParam {
start: i64,
limit: i32,
}
pub fn get_nest_handlers() -> Router<crate::AppState> {
Router::new()
.route("/", get(get_operations))
}
// get_single_operation
pub async fn get_operations(
query_param: Query<GetOperationsParam>,
State(app_state): State<crate::AppState>,
claims: Claims,
) -> Result<Json<GetOperationsResponse>, (StatusCode, String)> {
let uid: i64 = claims.uid.clone();
let start: i64 = match query_param.start {
..0 => 0,
_ => query_param.start
};
let limit: i32 = match query_param.limit {
..0 => 0,
crate::model::req::MAX_QUERY_LIMIT.. => crate::model::req::MAX_QUERY_LIMIT,
_ => query_param.limit
};
let conn = app_state
.db
.get()
.await
.map_err(util::req::internal_error)?;
let mut res = conn
.interact(move |conn| {
schema::operations::table
.filter(schema::operations::uid.eq(uid))
.filter(schema::operations::id.ge(start))
.limit(limit as i64)
.select(Operation::as_select())
.load(conn)
})
.await
.map_err(util::req::internal_error)?
.map_err(util::req::internal_error)?;
res.sort_by(|a,b| a.id.cmp(&b.id));
let res_start: i64 = match res.first() {
Some(r) => r.id,
None => 0,
};
let res_end = match res.last() {
Some(r) => r.id,
None => 0,
};
let resp = GetOperationsResponse{
start: res_start,
end: res_end,
total: res.len() as i64,
operations: res,
};
Ok(Json(resp))
// Ok(Json(res))
}

View File

@@ -39,7 +39,7 @@ pub struct CreateTagResponse {
pub fn get_nest_handlers() -> Router<crate::AppState> { pub fn get_nest_handlers() -> Router<crate::AppState> {
Router::new() Router::new()
.route("/", post(create_tag).get(get_all_tags)) .route("/", post(create_tag).get(get_all_tags))
.route("/:id", post(update_tag).get(get_tag)) .route("/{id}", post(update_tag).get(get_tag))
} }
#[debug_handler] #[debug_handler]

View File

@@ -9,6 +9,8 @@ use axum_macros::debug_handler;
use diesel::dsl::exists; use diesel::dsl::exists;
use diesel::prelude::*; use diesel::prelude::*;
use std::fmt; use std::fmt;
use std::i64::MAX;
use chrono::ParseResult;
// use diesel::update; // use diesel::update;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
// use serde_json::to_string; // use serde_json::to_string;
@@ -19,7 +21,7 @@ use crate::util;
use crate::util::req::CommonResp; use crate::util::req::CommonResp;
use chrono::prelude::*; use chrono::prelude::*;
use tracing::info; use tracing::info;
use crate::model::req::Params; use crate::model::req::{GetAmountByTransactionRangeParams, GetAmountParams, MAX_QUERY_LIMIT};
const PAYMENT_STORE_EXPO: i64 = 5; const PAYMENT_STORE_EXPO: i64 = 5;
@@ -29,7 +31,7 @@ pub struct SubmitTransactionRequest {
book_id: i64, book_id: i64,
category_id: i64, category_id: i64,
tag_ids: Vec<i64>, tag_ids: Vec<i64>,
time: String, time: String, // RFC 3339 "2020-04-12T22:10:57+02:00"
amounts: Vec<SubmitTransactionAmountRequest>, amounts: Vec<SubmitTransactionAmountRequest>,
} }
@@ -52,15 +54,28 @@ pub struct CreateTransactionResponse {
pub amount_ids: Vec<i64>, pub amount_ids: Vec<i64>,
} }
#[derive(Deserialize)]
pub struct BatchGetTransactionRequest {
pub transaction_ids: Vec<i64>,
}
#[derive(Deserialize)]
pub struct BatchGetTransactionAmountRequest {
pub transaction_ids: Vec<i64>,
}
pub fn get_nest_handlers() -> Router<crate::AppState> { pub fn get_nest_handlers() -> Router<crate::AppState> {
Router::new() Router::new()
.route("/entry/batch_get", post(batch_get_transactions))
.route( .route(
"/entry", "/entry",
post(create_transaction) // create new transaction entry with amount post(create_transaction) // create new transaction entry with amount
.get(get_all_transactions),// get all transactions with entry .get(get_all_transactions),// get all transactions with entry
) )
.route("/entry/:id", get(get_transaction)) // get transaction entry .route("/entry/{id}", get(get_transaction)) // get transaction entry
.route("/amount", get(get_amounts_by_tid)) .route("/amount/by_transaction_id", get(get_amounts_by_tid))
.route("/amount/batch_get_by_transaction_id", post(batch_get_amounts_by_tid))
.route("/amount", get(get_all_amounts_by_tid_range))
// .route("/entry/amount/:id", post(update_amount).get(get_amount)) // require query param tid=transaction_id // .route("/entry/amount/:id", post(update_amount).get(get_amount)) // require query param tid=transaction_id
} }
// implementation, or do something in between. // implementation, or do something in between.
@@ -192,6 +207,14 @@ pub async fn create_transaction(
} }
// 2. build and insert into db // 2. build and insert into db
let datetime_tz = chrono::DateTime::parse_from_rfc3339(payload.time.as_str());
let datetime = match datetime_tz {
Ok(dt) => dt,
Err(_) => {
return Err((StatusCode::BAD_REQUEST, "invalid datetime, must be RFC 3339".to_string()))
}
};
let datetime_utc = datetime.with_timezone(&Utc);
let mut transaction_resp: CreateTransactionResponse; let mut transaction_resp: CreateTransactionResponse;
let mut amount_ids: Vec<i64> = Vec::new(); let mut amount_ids: Vec<i64> = Vec::new();
@@ -206,7 +229,7 @@ pub async fn create_transaction(
description: payload.description, description: payload.description,
category_id: payload.category_id, category_id: payload.category_id,
// time: payload // time: payload
time: Utc::now(), time: datetime_utc,
}; };
let inserted_transactions = diesel::insert_into(schema::transactions::table) let inserted_transactions = diesel::insert_into(schema::transactions::table)
.values(&new_transaction) .values(&new_transaction)
@@ -268,13 +291,23 @@ pub async fn update_transaction(
.await .await
.map_err(util::req::internal_error)?; .map_err(util::req::internal_error)?;
let now = Utc::now().naive_utc(); let now = Utc::now().naive_utc();
let datetime_tz = chrono::DateTime::parse_from_rfc3339(payload.time.as_str());
let datetime = match datetime_tz {
Ok(dt) => dt,
Err(_) => {
return Err((StatusCode::BAD_REQUEST, "invalid datetime, must be RFC 3339".to_string()))
}
};
let datetime_utc = datetime.with_timezone(&Utc);
let res = conn let res = conn
.interact(move |conn| { .interact(move |conn| {
diesel::update(schema::transactions::table) diesel::update(schema::transactions::table)
.filter(schema::transactions::id.eq(id)) .filter(schema::transactions::id.eq(id))
.filter(schema::transactions::uid.eq(uid)) .filter(schema::transactions::uid.eq(uid))
.set(( .set((
schema::transactions::category_id.eq(payload.category_id),
schema::transactions::description.eq(payload.description), schema::transactions::description.eq(payload.description),
schema::transactions::time.eq(datetime_utc),
schema::transactions::update_at.eq(now), schema::transactions::update_at.eq(now),
)) ))
.execute(conn) .execute(conn)
@@ -315,6 +348,7 @@ pub async fn get_transaction(
pub async fn get_all_transactions( pub async fn get_all_transactions(
State(app_state): State<crate::AppState>, State(app_state): State<crate::AppState>,
claims: Claims, claims: Claims,
Query(queryParams): Query<req::GetTransactionsQueryParams>,
) -> Result<Json<Vec<db_model::Transaction>>, (StatusCode, String)> { ) -> Result<Json<Vec<db_model::Transaction>>, (StatusCode, String)> {
let uid: i64 = claims.uid.clone(); let uid: i64 = claims.uid.clone();
let conn = app_state let conn = app_state
@@ -322,10 +356,27 @@ pub async fn get_all_transactions(
.get() .get()
.await .await
.map_err(util::req::internal_error)?; .map_err(util::req::internal_error)?;
let offset = match queryParams.start {
None => {0}
Some(start) => if start > 0 {start-1} else {0}
};
let limit = match queryParams.limit {
None => {1 as i32}
Some(limit_num) => {
if(limit_num > req::MAX_QUERY_LIMIT) {
req::MAX_QUERY_LIMIT
} else if(limit_num < 1) {
1 as i32
} else {
limit_num
}
}
};
let res = conn let res = conn
.interact(move |conn| { .interact(move |conn| {
schema::transactions::table schema::transactions::table.filter(schema::transactions::uid.eq(uid))
.filter(schema::transactions::uid.eq(uid)) .offset(offset)
.limit(limit as i64)
.select(db_model::Transaction::as_select()) .select(db_model::Transaction::as_select())
.load(conn) .load(conn)
}) })
@@ -335,10 +386,32 @@ pub async fn get_all_transactions(
Ok(Json(res)) Ok(Json(res))
} }
pub async fn batch_get_transactions(
State(app_state): State<crate::AppState>,
claims: Claims,
Json(payload): Json<BatchGetTransactionRequest>,
) -> Result<Json<Vec<db_model::Transaction>>, (StatusCode, String)> {
let uid = claims.uid.clone();
if payload.transaction_ids.len() == 0 {
return Err((StatusCode::BAD_REQUEST, "no transaction_id list".to_string()));
}
let conn = app_state.db.get().await.map_err(util::req::internal_error)?;
let res = conn.interact(move |conn| {
schema::transactions::table
.filter(schema::transactions::uid.eq(uid))
.filter(schema::transactions::is_delete.eq(false))
.filter(schema::transactions::id.eq_any(payload.transaction_ids))
.select(db_model::Transaction::as_select())
.load(conn)
})
.await.map_err(util::req::internal_error)?.map_err(util::req::internal_error)?;
Ok(Json(res))
}
pub async fn get_amounts_by_tid( pub async fn get_amounts_by_tid(
State(app_state): State<crate::AppState>, State(app_state): State<crate::AppState>,
claims: Claims, claims: Claims,
Query(params): Query<Params>, Query(params): Query<GetAmountParams>,
) -> Result<Json<Vec<db_model::Amount>>, (StatusCode, String)> { ) -> Result<Json<Vec<db_model::Amount>>, (StatusCode, String)> {
info!(params.transaction_id); info!(params.transaction_id);
let tid = match params.transaction_id { let tid = match params.transaction_id {
@@ -361,4 +434,70 @@ pub async fn get_amounts_by_tid(
.map_err(util::req::internal_error)? .map_err(util::req::internal_error)?
.map_err(util::req::internal_error)?; .map_err(util::req::internal_error)?;
Ok(Json(res)) Ok(Json(res))
}
pub async fn batch_get_amounts_by_tid(
State(app_state): State<crate::AppState>,
claims: Claims,
Json(payload): Json<BatchGetTransactionAmountRequest>,
) -> Result<Json<Vec<db_model::Amount>>, (StatusCode, String)> {
let uid = claims.uid.clone();
if payload.transaction_ids.len() == 0 {
return Err((StatusCode::BAD_REQUEST, "no transaction_id list".to_string()));
}
let conn = app_state.db.get().await.map_err(util::req::internal_error)?;
let res = conn.interact(move |conn| {
schema::amounts::table
.filter(schema::amounts::uid.eq(uid))
.filter(schema::amounts::is_delete.eq(false))
.filter(schema::amounts::transaction_id.eq_any(payload.transaction_ids))
.select(db_model::Amount::as_select())
.load(conn)
})
.await.map_err(util::req::internal_error)?.map_err(util::req::internal_error)?;
Ok(Json(res))
}
pub async fn get_all_amounts_by_tid_range(
State(app_state): State<crate::AppState>,
claims: Claims,
Query(params): Query<GetAmountByTransactionRangeParams>,
) -> Result<Json<Vec<db_model::Amount>>, (StatusCode, String)> {
let uid: i64 = claims.uid.clone();
let tid_from = match params.transaction_id_from {
None => {-1}
Some(id) => {id}
};
let tid_to = match params.transaction_id_to {
None => {-1}
Some(id) => {id}
};
if uid <= 0 || tid_from <= 0 || tid_to <= 0 || tid_from > tid_to {
return Err((StatusCode::BAD_REQUEST,"invalid values".to_string()));
}
let limit: i64 = match params.limit {
None => {MAX_QUERY_LIMIT as i64}
Some(i) => {
if i <= 0 {
MAX_QUERY_LIMIT as i64
} else {
i as i64
}
}
};
let conn = app_state.db.get()
.await.map_err(util::req::internal_error)?;
let res = conn.interact(move |conn| {
schema::amounts::table
.filter(schema::amounts::uid.eq(uid))
.filter(schema::amounts::is_delete.eq(false))
.filter(schema::amounts::transaction_id.ge(tid_from))
.filter(schema::amounts::transaction_id.le(tid_to))
.limit(limit)
.select(db_model::Amount::as_select())
.load(conn)
}).await
.map_err(util::req::internal_error)?
.map_err(util::req::internal_error)?;
Ok(Json(res))
} }

View File

@@ -97,6 +97,7 @@ async fn main() {
.nest("/api/v1/account", ledger::account::get_nest_handlers()) .nest("/api/v1/account", ledger::account::get_nest_handlers())
.nest("/api/v1/transaction", ledger::transaction::get_nest_handlers()) .nest("/api/v1/transaction", ledger::transaction::get_nest_handlers())
.nest("/api/v1/user", user::handler::get_nest_handlers()) .nest("/api/v1/user", user::handler::get_nest_handlers())
.nest("/api/v1/operation", ledger::operation::get_nest_handlers())
.with_state(shared_state) .with_state(shared_state)
.layer(global_layer); .layer(global_layer);

View File

@@ -1,5 +1,4 @@
use axum::{ use axum::{
async_trait,
extract::FromRequestParts, extract::FromRequestParts,
http::{ http::{
request::Parts, request::Parts,
@@ -8,6 +7,7 @@ use axum::{
Json, RequestPartsExt, Json, RequestPartsExt,
response::{IntoResponse, Response}, response::{IntoResponse, Response},
}; };
use async_trait::async_trait;
use axum_extra::{ use axum_extra::{
headers::{authorization::Bearer, Authorization}, headers::{authorization::Bearer, Authorization},
TypedHeader, TypedHeader,
@@ -81,27 +81,25 @@ impl AuthBody {
} }
} }
#[async_trait]
impl<S> FromRequestParts<S> for Claims impl<S> FromRequestParts<S> for Claims
where where
S: Send + Sync, S: Send + Sync,
{ {
type Rejection = (StatusCode, String); type Rejection = AuthError;
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> { async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
// Extract the token from the authorization header // Extract the token from the authorization header
let TypedHeader(Authorization(bearer)) = parts let TypedHeader(Authorization(bearer)) = parts
.extract::<TypedHeader<Authorization<Bearer>>>() .extract::<TypedHeader<Authorization<Bearer>>>()
.await .await
.map_err(util::req::internal_error)?; .map_err(|_| AuthError::InvalidToken)?;
// Decode the user data // Decode the user data
let token_data = decode::<Claims>(bearer.token(), &KEYS.decoding, &Validation::default()) let token_data = decode::<Claims>(bearer.token(), &KEYS.decoding, &Validation::default())
.map_err(util::req::internal_error)?; .map_err(|_| AuthError::InvalidToken)?;
Ok(token_data.claims) Ok(token_data.claims)
} }
} }
impl IntoResponse for AuthError { impl IntoResponse for AuthError {
fn into_response(self) -> Response { fn into_response(self) -> Response {
let (status, error_message) = match self { let (status, error_message) = match self {

View File

@@ -1,17 +1,21 @@
use crate::model::schema; use crate::model::schema;
use diesel::prelude::*; use diesel::prelude::*;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use crate::model::schema::operations::entity_id;
#[derive(Queryable, Selectable, serde::Serialize, serde::Deserialize)] #[derive(Queryable, Selectable, serde::Serialize, serde::Deserialize)]
#[diesel(table_name = schema::categories)] #[diesel(table_name = schema::categories)]
#[diesel(check_for_backend(diesel::pg::Pg))] #[diesel(check_for_backend(diesel::pg::Pg))]
pub struct Category { pub struct Category {
id: i64, #[serde(with = "string")]
pub id: i64,
uid: i64, uid: i64,
name: String, name: String,
level: i32, level: i32,
parent_category_id: i64, parent_category_id: i64,
book_id: i64, book_id: i64,
op_id: i64,
#[serde(skip_serializing)]
is_delete: bool, is_delete: bool,
create_at: chrono::NaiveDateTime, create_at: chrono::NaiveDateTime,
update_at: chrono::NaiveDateTime, update_at: chrono::NaiveDateTime,
@@ -37,6 +41,8 @@ pub struct Tag {
name: String, name: String,
level: i32, level: i32,
parent_tag_id: i64, parent_tag_id: i64,
op_id: i64,
#[serde(skip_serializing)]
is_delete: bool, is_delete: bool,
create_at: chrono::NaiveDateTime, create_at: chrono::NaiveDateTime,
update_at: chrono::NaiveDateTime, update_at: chrono::NaiveDateTime,
@@ -56,9 +62,12 @@ pub struct TagForm {
#[diesel(table_name = schema::books)] #[diesel(table_name = schema::books)]
#[diesel(check_for_backend(diesel::pg::Pg))] #[diesel(check_for_backend(diesel::pg::Pg))]
pub struct Book { pub struct Book {
#[serde(with = "string")]
id: i64, id: i64,
uid: i64, uid: i64,
name: String, name: String,
op_id: i64,
#[serde(skip_serializing)]
is_delete: bool, is_delete: bool,
create_at: chrono::NaiveDateTime, create_at: chrono::NaiveDateTime,
update_at: chrono::NaiveDateTime, update_at: chrono::NaiveDateTime,
@@ -79,6 +88,8 @@ pub struct Account {
uid: i64, uid: i64,
name: String, name: String,
account_type: i64, account_type: i64,
op_id: i64,
#[serde(skip_serializing)]
is_delete: bool, is_delete: bool,
create_at: chrono::NaiveDateTime, create_at: chrono::NaiveDateTime,
update_at: chrono::NaiveDateTime, update_at: chrono::NaiveDateTime,
@@ -96,12 +107,17 @@ pub struct AccountForm {
#[diesel(table_name = schema::transactions)] #[diesel(table_name = schema::transactions)]
#[diesel(check_for_backend(diesel::pg::Pg))] #[diesel(check_for_backend(diesel::pg::Pg))]
pub struct Transaction { pub struct Transaction {
#[serde(with = "string")]
pub id: i64, pub id: i64,
uid: i64, uid: i64,
#[serde(with = "string")]
pub book_id: i64, pub book_id: i64,
pub description: String, pub description: String,
#[serde(with = "string")]
pub category_id: i64, pub category_id: i64,
pub time: chrono::DateTime<Utc>, pub time: chrono::DateTime<Utc>,
pub op_id: i64,
#[serde(skip_serializing)]
is_delete: bool, is_delete: bool,
create_at: chrono::NaiveDateTime, create_at: chrono::NaiveDateTime,
update_at: chrono::NaiveDateTime, update_at: chrono::NaiveDateTime,
@@ -129,6 +145,8 @@ pub struct Amount {
value: i64, value: i64,
expo: i64, expo: i64,
currency: String, currency: String,
pub op_id: i64,
#[serde(skip_serializing)]
is_delete: bool, is_delete: bool,
create_at: chrono::NaiveDateTime, create_at: chrono::NaiveDateTime,
update_at: chrono::NaiveDateTime, update_at: chrono::NaiveDateTime,
@@ -152,6 +170,7 @@ pub struct User {
pub username: String, pub username: String,
pub password: String, pub password: String,
pub mail: String, pub mail: String,
#[serde(skip_serializing)]
pub is_delete: bool, pub is_delete: bool,
} }
@@ -162,3 +181,45 @@ pub struct UserForm {
pub password: String, pub password: String,
pub mail: String, pub mail: String,
} }
#[derive(Insertable,Queryable, Selectable, serde::Serialize)]
#[diesel(table_name = schema::operations)]
pub struct Operation {
pub id: i64,
pub uid: i64,
pub entity_type: i64,
pub entity_id: i64,
pub action: i64,
create_at: chrono::NaiveDateTime,
}
#[derive(Insertable)]
#[diesel(table_name = schema::operations)]
pub struct CreateOperation {
pub uid: i64,
pub entity_type: i64,
pub entity_id: i64,
pub action: i64,
}
mod string {
use std::fmt::Display;
use std::str::FromStr;
use serde::{de, Serializer, Deserialize, Deserializer};
pub fn serialize<T, S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
where T: Display,
S: Serializer
{
serializer.collect_str(value)
}
pub fn deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error>
where T: FromStr,
T::Err: Display,
D: Deserializer<'de>
{
String::deserialize(deserializer)?.parse().map_err(de::Error::custom)
}
}

View File

@@ -2,12 +2,25 @@ use std::fmt;
use std::str::FromStr; use std::str::FromStr;
use serde::{de, Deserialize, Deserializer}; use serde::{de, Deserialize, Deserializer};
pub const QUERY_ORDER_INCREASE:i32 = 0;
pub const QUERY_ORDER_INVERT:i32 = 1;
pub const MAX_QUERY_LIMIT:i32 =1000;
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct Params { pub struct GetAmountParams {
#[serde(default, deserialize_with="empty_string_as_none")] #[serde(default, deserialize_with="empty_string_as_none")]
pub transaction_id: Option<i64>, pub transaction_id: Option<i64>,
} }
#[derive(Debug, Deserialize)]
pub struct GetAmountByTransactionRangeParams {
pub transaction_id_from: Option<i64>,
pub transaction_id_to: Option<i64>,
pub limit: Option<i64>,
}
// Serde deserialization decorator to map empty Strings to None, // Serde deserialization decorator to map empty Strings to None,
fn empty_string_as_none<'de, D, T>(de: D) -> Result<Option<T>, D::Error> fn empty_string_as_none<'de, D, T>(de: D) -> Result<Option<T>, D::Error>
where where
@@ -21,3 +34,9 @@ where
Some(s) => FromStr::from_str(s).map_err(de::Error::custom).map(Some), Some(s) => FromStr::from_str(s).map_err(de::Error::custom).map(Some),
} }
} }
#[derive(Deserialize)]
pub struct GetTransactionsQueryParams {
pub start: Option<i64>,
pub limit: Option<i32>,
}

View File

@@ -6,6 +6,7 @@ diesel::table! {
uid -> Int8, uid -> Int8,
name -> Text, name -> Text,
account_type -> Int8, account_type -> Int8,
op_id -> Int8,
is_delete -> Bool, is_delete -> Bool,
create_at -> Timestamp, create_at -> Timestamp,
update_at -> Timestamp, update_at -> Timestamp,
@@ -21,6 +22,7 @@ diesel::table! {
value -> Int8, value -> Int8,
expo -> Int8, expo -> Int8,
currency -> Text, currency -> Text,
op_id -> Int8,
is_delete -> Bool, is_delete -> Bool,
create_at -> Timestamp, create_at -> Timestamp,
update_at -> Timestamp, update_at -> Timestamp,
@@ -32,6 +34,7 @@ diesel::table! {
id -> Int8, id -> Int8,
uid -> Int8, uid -> Int8,
name -> Text, name -> Text,
op_id -> Int8,
is_delete -> Bool, is_delete -> Bool,
create_at -> Timestamp, create_at -> Timestamp,
update_at -> Timestamp, update_at -> Timestamp,
@@ -46,12 +49,34 @@ diesel::table! {
name -> Text, name -> Text,
level -> Int4, level -> Int4,
parent_category_id -> Int8, parent_category_id -> Int8,
op_id -> Int8,
is_delete -> Bool, is_delete -> Bool,
create_at -> Timestamp, create_at -> Timestamp,
update_at -> Timestamp, update_at -> Timestamp,
} }
} }
diesel::table! {
operation_snapshots (id) {
id -> Int8,
uid -> Int8,
max_op_id -> Int8,
create_at -> Timestamp,
}
}
diesel::table! {
operations (id) {
id -> Int8,
uid -> Int8,
entity_type -> Int8,
entity_id -> Int8,
action -> Int8,
create_at -> Timestamp,
update_at -> Timestamp,
}
}
diesel::table! { diesel::table! {
tags (id) { tags (id) {
id -> Int8, id -> Int8,
@@ -60,6 +85,7 @@ diesel::table! {
name -> Text, name -> Text,
level -> Int4, level -> Int4,
parent_tag_id -> Int8, parent_tag_id -> Int8,
op_id -> Int8,
is_delete -> Bool, is_delete -> Bool,
create_at -> Timestamp, create_at -> Timestamp,
update_at -> Timestamp, update_at -> Timestamp,
@@ -72,6 +98,7 @@ diesel::table! {
uid -> Int8, uid -> Int8,
transaction_id -> Int8, transaction_id -> Int8,
tag_id -> Int8, tag_id -> Int8,
op_id -> Int8,
is_delete -> Bool, is_delete -> Bool,
create_at -> Timestamp, create_at -> Timestamp,
update_at -> Timestamp, update_at -> Timestamp,
@@ -85,6 +112,7 @@ diesel::table! {
book_id -> Int8, book_id -> Int8,
description -> Text, description -> Text,
category_id -> Int8, category_id -> Int8,
op_id -> Int8,
is_delete -> Bool, is_delete -> Bool,
time -> Timestamptz, time -> Timestamptz,
create_at -> Timestamp, create_at -> Timestamp,
@@ -109,6 +137,8 @@ diesel::allow_tables_to_appear_in_same_query!(
amounts, amounts,
books, books,
categories, categories,
operation_snapshots,
operations,
tags, tags,
transaction_tag_rels, transaction_tag_rels,
transactions, transactions,

View File

@@ -6,6 +6,7 @@ diesel::table! {
uid -> Int8, uid -> Int8,
name -> Text, name -> Text,
account_type -> Int8, account_type -> Int8,
version_v1 -> Int8,
is_delete -> Bool, is_delete -> Bool,
create_at -> Timestamp, create_at -> Timestamp,
update_at -> Timestamp, update_at -> Timestamp,
@@ -21,6 +22,7 @@ diesel::table! {
value -> Int8, value -> Int8,
expo -> Int8, expo -> Int8,
currency -> Text, currency -> Text,
version_v1 -> Int8,
is_delete -> Bool, is_delete -> Bool,
create_at -> Timestamp, create_at -> Timestamp,
update_at -> Timestamp, update_at -> Timestamp,
@@ -32,6 +34,7 @@ diesel::table! {
id -> Int8, id -> Int8,
uid -> Int8, uid -> Int8,
name -> Text, name -> Text,
version_v1 -> Int8,
is_delete -> Bool, is_delete -> Bool,
create_at -> Timestamp, create_at -> Timestamp,
update_at -> Timestamp, update_at -> Timestamp,
@@ -46,6 +49,7 @@ diesel::table! {
name -> Text, name -> Text,
level -> Int4, level -> Int4,
parent_category_id -> Int8, parent_category_id -> Int8,
version_v1 -> Int8,
is_delete -> Bool, is_delete -> Bool,
create_at -> Timestamp, create_at -> Timestamp,
update_at -> Timestamp, update_at -> Timestamp,
@@ -60,6 +64,7 @@ diesel::table! {
name -> Text, name -> Text,
level -> Int4, level -> Int4,
parent_tag_id -> Int8, parent_tag_id -> Int8,
version_v1 -> Int8,
is_delete -> Bool, is_delete -> Bool,
create_at -> Timestamp, create_at -> Timestamp,
update_at -> Timestamp, update_at -> Timestamp,
@@ -72,6 +77,7 @@ diesel::table! {
uid -> Int8, uid -> Int8,
transaction_id -> Int8, transaction_id -> Int8,
tag_id -> Int8, tag_id -> Int8,
version_v1 -> Int8,
is_delete -> Bool, is_delete -> Bool,
create_at -> Timestamp, create_at -> Timestamp,
update_at -> Timestamp, update_at -> Timestamp,
@@ -85,6 +91,7 @@ diesel::table! {
book_id -> Int8, book_id -> Int8,
description -> Text, description -> Text,
category_id -> Int8, category_id -> Int8,
version_v1 -> Int8,
is_delete -> Bool, is_delete -> Bool,
time -> Timestamptz, time -> Timestamptz,
create_at -> Timestamp, create_at -> Timestamp,
@@ -104,6 +111,18 @@ diesel::table! {
} }
} }
diesel::table! {
versions_v1 (id) {
id -> Int8,
uid -> Int8,
entity_type -> Int8,
entity_id -> Int8,
action -> Int8,
create_at -> Timestamp,
update_at -> Timestamp,
}
}
diesel::allow_tables_to_appear_in_same_query!( diesel::allow_tables_to_appear_in_same_query!(
accounts, accounts,
amounts, amounts,
@@ -113,4 +132,5 @@ diesel::allow_tables_to_appear_in_same_query!(
transaction_tag_rels, transaction_tag_rels,
transactions, transactions,
users, users,
versions_v1,
); );

View File

@@ -1,3 +1,4 @@
pub mod req; pub mod req;
pub mod pass; pub mod pass;
pub mod math; pub mod math;
pub mod operation;

16
src/util/operation.rs Normal file
View File

@@ -0,0 +1,16 @@
pub type EntityType = i64;
pub const ENTITY_CATEGORY: EntityType = 1;
pub const ENTITY_TAG: EntityType = 2;
pub const ENTITY_BOOK: EntityType = 3;
pub const ENTITY_ACCOUNT: EntityType = 4;
pub const ENTITY_TRANSACTION: EntityType = 5;
pub const ENTITY_AMOUNT: EntityType = 6;
pub type ActionType = i64;
pub const ACTION_CREATE: ActionType = 1;
pub const ACTION_UPDATE: ActionType = 2;
pub const ACTION_DELETE: ActionType = 3;