Compare commits
	
		
			57 Commits
		
	
	
		
			1.0.1
			...
			cab54131b1
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| cab54131b1 | |||
| 4f1a9d0865 | |||
| 31803feaa9 | |||
| aad32f9c25 | |||
| 1ea2bd6acf | |||
| a085116018 | |||
| 952a66042c | |||
| 6cf6ab5a37 | |||
| 1781318fdf | |||
| 2560962684 | |||
| dc3704f13b | |||
| af4c3c4a8f | |||
| a268e1bc4d | |||
| 77fc3699d6 | |||
| 96b3f6d4dc | |||
| 9a74c6c951 | |||
| c64fa838c9 | |||
| 8f45109e87 | |||
| d0c20aa68b | |||
| b84305428c | |||
| 560e415cb1 | |||
| 4863b1f4af | |||
| 6fa87a67d6 | |||
| e7219d2e44 | |||
| c9af4e4fc9 | |||
| 6cb4bcf4d4 | |||
| 447b6d89fb | |||
| a2a7d3d94a | |||
| 05a5da7527 | |||
| 43f62802dc | |||
| 60f6b94b41 | |||
| 5aaefafb48 | |||
| 63af408224 | |||
| a5966c8d53 | |||
| 196cb0b5c9 | |||
| ba06cd04ff | |||
| dc4f41f31c | |||
| 9ba928102c | |||
| 7d7ad0ce89 | |||
| d0ff90701f | |||
| f38482757e | |||
| 7e976d903d | |||
| 737d7f76d4 | |||
| cf0e42ff0e | |||
| d2fa9bf9ee | |||
| fb5a85311c | |||
| 288d334615 | |||
| 87f017fc42 | |||
| 43fb8dcda6 | |||
| a3b9c7cdb1 | |||
| c42b8b1bda | |||
| bdcbe94c97 | |||
| caeff985c2 | |||
| bed538793d | |||
| 602f20ad18 | |||
| 457c96b37e | |||
| a3f2b77548 | 
							
								
								
									
										10
									
								
								.drone.yml
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								.drone.yml
									
									
									
									
									
								
							| @@ -38,6 +38,7 @@ steps: | |||||||
|     - cd moneymgr_backend |     - cd moneymgr_backend | ||||||
|     - rustup component add clippy |     - rustup component add clippy | ||||||
|     - cargo clippy -- -D warnings |     - cargo clippy -- -D warnings | ||||||
|  |     - cargo clippy --example api_curl -- -D warnings | ||||||
|  |  | ||||||
| - name: backend_test | - name: backend_test | ||||||
|   image: rust |   image: rust | ||||||
| @@ -51,7 +52,7 @@ steps: | |||||||
|     - cargo test |     - cargo test | ||||||
|  |  | ||||||
|  |  | ||||||
| - name: backend_compile | - name: backend_build | ||||||
|   image: rust |   image: rust | ||||||
|   volumes: |   volumes: | ||||||
|   - name: rust_registry |   - name: rust_registry | ||||||
| @@ -67,15 +68,16 @@ steps: | |||||||
|   - cd moneymgr_backend |   - cd moneymgr_backend | ||||||
|   - mv /tmp/web_build/dist static |   - mv /tmp/web_build/dist static | ||||||
|   - cargo build --release |   - cargo build --release | ||||||
|   - ls -lah target/release/moneymgr_backend |   - cargo build --release --example api_curl | ||||||
|   - cp target/release/moneymgr_backend /tmp/release |   - ls -lah target/release/moneymgr_backend target/release/examples/api_curl | ||||||
|  |   - cp target/release/moneymgr_backend target/release/examples/api_curl /tmp/release | ||||||
|  |  | ||||||
|  |  | ||||||
| # Release | # Release | ||||||
| - name: gitea_release | - name: gitea_release | ||||||
|   image: plugins/gitea-release |   image: plugins/gitea-release | ||||||
|   depends_on: |   depends_on: | ||||||
|   - backend_compile |   - backend_build | ||||||
|   when: |   when: | ||||||
|     event: |     event: | ||||||
|       - tag |       - tag | ||||||
|   | |||||||
							
								
								
									
										38
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										38
									
								
								README.md
									
									
									
									
									
								
							| @@ -3,10 +3,46 @@ | |||||||
|  |  | ||||||
| Open Source web-based personal expenses tool. | Open Source web-based personal expenses tool. | ||||||
|  |  | ||||||
|  | **Note :** This project does not handle authentication itself. Instead, it relies on OpenID to achieve users authentication. | ||||||
|  |  | ||||||
|  | ## Setup prod env | ||||||
|  | 1. Install prerequisites: | ||||||
|  |    1. docker | ||||||
|  |    2. docker compose | ||||||
|  |    3. git | ||||||
|  |  | ||||||
|  | 2. Clone this git repository: | ||||||
|  | ```bash | ||||||
|  | git clone https://gitea.communiquons.org/pierre/MoneyMgr | ||||||
|  | cd MoneyMgr/docker_prod | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | 3. Copy and adapt env values | ||||||
|  | ```bash | ||||||
|  | cp .env.sample .env | ||||||
|  | nano .env | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | 4. Create required directories: | ||||||
|  |  | ||||||
|  | ```bash | ||||||
|  | mkdir -p storage/{db,redis-data,redis-conf,minio} | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | 5. Start containers | ||||||
|  |  | ||||||
|  | ```bash | ||||||
|  | docker compose up | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | 6. Checkout http://localhost:8000/ | ||||||
|  |  | ||||||
|  | > The default credentials are `admin` / `admin` | ||||||
|  |  | ||||||
| ## Setup dev env | ## Setup dev env | ||||||
| 1. Install prerequisites: | 1. Install prerequisites: | ||||||
|    1. docker |    1. docker | ||||||
|    2. docker-compose |    2. docker compose | ||||||
|    3. rust |    3. rust | ||||||
|    4. node |    4. node | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										10
									
								
								docker_prod/.env.sample
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								docker_prod/.env.sample
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | |||||||
|  | MINIO_ROOT_USER=rootuser | ||||||
|  | MINIO_ROOT_PASSWORD=rootpassword | ||||||
|  | DB_USER=db_user | ||||||
|  | DB_PASSWORD=db_password | ||||||
|  | REDIS_PASS=redis_password | ||||||
|  | WEBSITE_ORIGIN=http://localhost:8000 | ||||||
|  | APP_SECRET=secretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecret | ||||||
|  | AUTH_SECRET_KEY=secretsecretsecretsecretsecretsecretsecretsecretsecretsecretsecret | ||||||
|  | OIDC_CLIENT_ID=bar | ||||||
|  | OIDC_CLIENT_SECRET=foo | ||||||
							
								
								
									
										3
									
								
								docker_prod/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								docker_prod/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | |||||||
|  | .env | ||||||
|  | storage | ||||||
|  | auth/users.json | ||||||
							
								
								
									
										5
									
								
								docker_prod/auth/clients.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								docker_prod/auth/clients.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | - id: ${OIDC_CLIENT_ID} | ||||||
|  |   name: MoneyMgr | ||||||
|  |   description: Money management tool | ||||||
|  |   secret: ${OIDC_CLIENT_SECRET} | ||||||
|  |   redirect_uri: ${APP_ORIGIN}/oidc_cb | ||||||
							
								
								
									
										79
									
								
								docker_prod/docker-compose.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								docker_prod/docker-compose.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,79 @@ | |||||||
|  | services: | ||||||
|  |   minio: | ||||||
|  |     image: minio/minio | ||||||
|  |     user: "1000" | ||||||
|  |     environment: | ||||||
|  |       - MINIO_ROOT_USER=$MINIO_ROOT_USER | ||||||
|  |       - MINIO_ROOT_PASSWORD=$MINIO_ROOT_PASSWORD | ||||||
|  |     volumes: | ||||||
|  |       - ./storage/minio:/data | ||||||
|  |     command: [ "minio", "server", "/data", "--console-address", ":9090" ] | ||||||
|  |     ports: | ||||||
|  |       - 9000:9000 | ||||||
|  |       - 9090:9090 | ||||||
|  |     expose: | ||||||
|  |       - 9000 | ||||||
|  |  | ||||||
|  |   db: | ||||||
|  |     image: postgres | ||||||
|  |     user: "1000" | ||||||
|  |     ports: | ||||||
|  |       - "5432:5432" | ||||||
|  |     expose: | ||||||
|  |       - 5432 | ||||||
|  |     environment: | ||||||
|  |       - POSTGRES_USER=$DB_USER | ||||||
|  |       - POSTGRES_PASSWORD=$DB_PASSWORD | ||||||
|  |       - POSTGRES_DB=moneymgr | ||||||
|  |     volumes: | ||||||
|  |       - ./storage/db:/var/lib/postgresql/data | ||||||
|  |  | ||||||
|  |   oidc: | ||||||
|  |     image: pierre42100/basic_oidc | ||||||
|  |     user: "1000" | ||||||
|  |     environment: | ||||||
|  |       - LISTEN_ADDRESS=0.0.0.0:9001 | ||||||
|  |       - STORAGE_PATH=/storage | ||||||
|  |       - TOKEN_KEY=$AUTH_SECRET_KEY | ||||||
|  |       - WEBSITE_ORIGIN=http://localhost:9001 | ||||||
|  |       - OIDC_CLIENT_ID=$OIDC_CLIENT_ID | ||||||
|  |       - OIDC_CLIENT_SECRET=$OIDC_CLIENT_SECRET | ||||||
|  |       - APP_ORIGIN=$WEBSITE_ORIGIN | ||||||
|  |     expose: | ||||||
|  |       - 9001 | ||||||
|  |     ports: | ||||||
|  |       - 9001:9001 | ||||||
|  |     volumes: | ||||||
|  |       - ./auth:/storage | ||||||
|  |  | ||||||
|  |   redis: | ||||||
|  |     image: redis:alpine | ||||||
|  |     user: "1000" | ||||||
|  |     command: redis-server --requirepass ${REDIS_PASS:-secretredis} | ||||||
|  |     expose: | ||||||
|  |       - 6379 | ||||||
|  |     volumes: | ||||||
|  |       - ./storage/redis-data:/data | ||||||
|  |       - ./storage/redis-conf:/usr/local/etc/redis/redis.conf | ||||||
|  |  | ||||||
|  |   moneymgr: | ||||||
|  |     image: pierre42100/moneymgr_backend | ||||||
|  |     user: "1000" | ||||||
|  |     ports: | ||||||
|  |       - 8000:8000 | ||||||
|  |     environment: | ||||||
|  |       - WEBSITE_ORIGIN=${WEBSITE_ORIGIN} | ||||||
|  |       - SECRET=${APP_SECRET} | ||||||
|  |       - DB_HOST=db | ||||||
|  |       - DB_USERNAME=$DB_USER | ||||||
|  |       - DB_PASSWORD=$DB_PASSWORD | ||||||
|  |       - DB_NAME=moneymgr | ||||||
|  |       - OIDC_CONFIGURATION_URL=http://oidc:9001/.well-known/openid-configuration | ||||||
|  |       - OIDC_PROVIDER_NAME=OIDC | ||||||
|  |       - OIDC_CLIENT_ID=$OIDC_CLIENT_ID | ||||||
|  |       - OIDC_CLIENT_SECRET=$OIDC_CLIENT_SECRET | ||||||
|  |       - S3_ENDPOINT=http://minio:9000 | ||||||
|  |       - S3_ACCESS_KEY=$MINIO_ROOT_USER | ||||||
|  |       - S3_SECRET_KEY=$MINIO_ROOT_PASSWORD | ||||||
|  |       - REDIS_HOSTNAME=redis | ||||||
|  |       - REDIS_PASSWORD=${REDIS_PASS:-secretredis} | ||||||
							
								
								
									
										38
									
								
								moneymgr_backend/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										38
									
								
								moneymgr_backend/Cargo.lock
									
									
									
										generated
									
									
									
								
							| @@ -724,9 +724,9 @@ dependencies = [ | |||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "clap" | name = "clap" | ||||||
| version = "4.5.38" | version = "4.5.40" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "ed93b9805f8ba930df42c2590f05453d5ec36cbb85d018868a5b24d31f6ac000" | checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "clap_builder", |  "clap_builder", | ||||||
|  "clap_derive", |  "clap_derive", | ||||||
| @@ -734,9 +734,9 @@ dependencies = [ | |||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "clap_builder" | name = "clap_builder" | ||||||
| version = "4.5.38" | version = "4.5.40" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "379026ff283facf611b0ea629334361c4211d1b12ee01024eec1591133b04120" | checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "anstream", |  "anstream", | ||||||
|  "anstyle", |  "anstyle", | ||||||
| @@ -746,9 +746,9 @@ dependencies = [ | |||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "clap_derive" | name = "clap_derive" | ||||||
| version = "4.5.32" | version = "4.5.40" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" | checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "heck", |  "heck", | ||||||
|  "proc-macro2", |  "proc-macro2", | ||||||
| @@ -948,9 +948,9 @@ dependencies = [ | |||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "crypto-common" | name = "crypto-common" | ||||||
| version = "0.2.0-rc.2" | version = "0.2.0-rc.3" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "170d71b5b14dec99db7739f6fc7d6ec2db80b78c3acb77db48392ccc3d8a9ea0" | checksum = "8a23fa214dea9efd4dacee5a5614646b30216ae0f05d4bb51bafb50e9da1c5be" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "hybrid-array", |  "hybrid-array", | ||||||
| ] | ] | ||||||
| @@ -1100,9 +1100,9 @@ dependencies = [ | |||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "diesel" | name = "diesel" | ||||||
| version = "2.2.10" | version = "2.2.11" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "ff3e1edb1f37b4953dd5176916347289ed43d7119cc2e6c7c3f7849ff44ea506" | checksum = "a917a9209950404d5be011c81d081a2692a822f73c3d6af586f0cab5ff50f614" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "bitflags", |  "bitflags", | ||||||
|  "byteorder", |  "byteorder", | ||||||
| @@ -1159,13 +1159,13 @@ dependencies = [ | |||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "digest" | name = "digest" | ||||||
| version = "0.11.0-pre.10" | version = "0.11.0-rc.0" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "6c478574b20020306f98d61c8ca3322d762e1ff08117422ac6106438605ea516" | checksum = "460dd7f37e4950526b54a5a6b1f41b6c8e763c58eb9a8fc8fc05ba5c2f44ca7b" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "block-buffer 0.11.0-rc.4", |  "block-buffer 0.11.0-rc.4", | ||||||
|  "const-oid 0.10.1", |  "const-oid 0.10.1", | ||||||
|  "crypto-common 0.2.0-rc.2", |  "crypto-common 0.2.0-rc.3", | ||||||
| ] | ] | ||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| @@ -2304,7 +2304,7 @@ dependencies = [ | |||||||
|  "rust_xlsxwriter", |  "rust_xlsxwriter", | ||||||
|  "serde", |  "serde", | ||||||
|  "serde_json", |  "serde_json", | ||||||
|  "sha2 0.11.0-pre.5", |  "sha2 0.11.0-rc.0", | ||||||
|  "tempfile", |  "tempfile", | ||||||
|  "thiserror 2.0.12", |  "thiserror 2.0.12", | ||||||
|  "tokio", |  "tokio", | ||||||
| @@ -3296,13 +3296,13 @@ dependencies = [ | |||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "sha2" | name = "sha2" | ||||||
| version = "0.11.0-pre.5" | version = "0.11.0-rc.0" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "19b4241d1a56954dce82cecda5c8e9c794eef6f53abe5e5216bac0a0ea71ffa7" | checksum = "aa1d2e6b3cc4e43a8258a9a3b17aa5dfd2cc5186c7024bba8a64aa65b2c71a59" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "cfg-if", |  "cfg-if", | ||||||
|  "cpufeatures", |  "cpufeatures", | ||||||
|  "digest 0.11.0-pre.10", |  "digest 0.11.0-rc.0", | ||||||
| ] | ] | ||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| @@ -3571,9 +3571,9 @@ dependencies = [ | |||||||
|  |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "tokio" | name = "tokio" | ||||||
| version = "1.45.0" | version = "1.45.1" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "2513ca694ef9ede0fb23fe71a4ee4107cb102b9dc1930f6d0fd77aae068ae165" | checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "backtrace", |  "backtrace", | ||||||
|  "bytes", |  "bytes", | ||||||
|   | |||||||
| @@ -6,31 +6,31 @@ edition = "2024" | |||||||
| [dependencies] | [dependencies] | ||||||
| env_logger = "0.11.8" | env_logger = "0.11.8" | ||||||
| log = "0.4.27" | log = "0.4.27" | ||||||
| diesel = { version = "2.2.10", features = ["postgres", "r2d2"] } | diesel = { version = "2.2.11", features = ["postgres", "r2d2"] } | ||||||
| diesel_migrations = "2.1.0" | diesel_migrations = "2.2.0" | ||||||
| clap = { version = "4.5.38", features = ["env", "derive"] } | clap = { version = "4.5.40", features = ["env", "derive"] } | ||||||
| actix-web = "4.11.0" | actix-web = "4.11.0" | ||||||
| actix-cors = "0.7.1" | actix-cors = "0.7.1" | ||||||
| actix-multipart = "0.7.2" | actix-multipart = "0.7.2" | ||||||
| actix-remote-ip = "0.1.0" | actix-remote-ip = "0.1.0" | ||||||
| actix-session = { version = "0.10.0", features = ["redis-session"] } | actix-session = { version = "0.10.1", features = ["redis-session"] } | ||||||
| actix-files = "0.6.6" | actix-files = "0.6.6" | ||||||
| lazy_static = "1.5.0" | lazy_static = "1.5.0" | ||||||
| anyhow = "1.0.98" | anyhow = "1.0.98" | ||||||
| serde = { version = "1.0.219", features = ["derive"] } | serde = { version = "1.0.219", features = ["derive"] } | ||||||
| rust-s3 = "0.36.0-beta.2" | rust-s3 = "0.36.0-beta.2" | ||||||
| thiserror = "2.0.12" | thiserror = "2.0.12" | ||||||
| tokio = "1.45.0" | tokio = "1.45.1" | ||||||
| futures-util = "0.3.31" | futures-util = "0.3.31" | ||||||
| serde_json = "1.0.140" | serde_json = "1.0.140" | ||||||
| light-openid = "1.0.4" | light-openid = "1.0.4" | ||||||
| rand = "0.9.1" | rand = "0.9.1" | ||||||
| ipnet = { version = "2.11.0", features = ["serde"] } | ipnet = { version = "2.11.0", features = ["serde"] } | ||||||
| lazy-regex = "3.4.1" | lazy-regex = "3.4.1" | ||||||
| jwt-simple = { version = "0.12.11", default-features = false, features = ["pure-rust"] } | jwt-simple = { version = "0.12.12", default-features = false, features = ["pure-rust"] } | ||||||
| mime_guess = "2.0.5" | mime_guess = "2.0.5" | ||||||
| rust-embed = { version = "8.7.2" } | rust-embed = { version = "8.7.2" } | ||||||
| sha2 = "0.11.0-pre.5" | sha2 = "0.11.0-rc.0" | ||||||
| base16ct = "0.2.0" | base16ct = "0.2.0" | ||||||
| httpdate = "1.0.3" | httpdate = "1.0.3" | ||||||
| chrono = "0.4.41" | chrono = "0.4.41" | ||||||
|   | |||||||
| @@ -14,7 +14,7 @@ use std::process::Command; | |||||||
| struct Args { | struct Args { | ||||||
|     /// URL to Money manager API |     /// URL to Money manager API | ||||||
|     #[arg(short('U'), long, env, default_value = "http://localhost:8000/api")] |     #[arg(short('U'), long, env, default_value = "http://localhost:8000/api")] | ||||||
|     matrix_gw_url: String, |     moneymgr_url: String, | ||||||
|  |  | ||||||
|     /// Token ID |     /// Token ID | ||||||
|     #[arg(short('i'), long, env)] |     #[arg(short('i'), long, env)] | ||||||
| @@ -39,7 +39,8 @@ struct Args { | |||||||
| fn main() { | fn main() { | ||||||
|     let args: Args = Args::parse(); |     let args: Args = Args::parse(); | ||||||
|  |  | ||||||
|     let full_url = format!("{}{}", args.matrix_gw_url, args.uri); |     let full_url = format!("{}{}", args.moneymgr_url, args.uri); | ||||||
|  |  | ||||||
|     log::debug!("Full URL: {full_url}"); |     log::debug!("Full URL: {full_url}"); | ||||||
|  |  | ||||||
|     let key = HS256Key::from_bytes(args.token_secret.as_bytes()); |     let key = HS256Key::from_bytes(args.token_secret.as_bytes()); | ||||||
|   | |||||||
| @@ -30,7 +30,7 @@ pub async fn create_bucket_if_required() -> anyhow::Result<()> { | |||||||
|             log::warn!("The bucket does not seem to exists, trying to create it!") |             log::warn!("The bucket does not seem to exists, trying to create it!") | ||||||
|         } |         } | ||||||
|         Err(e) => { |         Err(e) => { | ||||||
|             log::error!("Got unexpected error when querying bucket info: {}", e); |             log::error!("Got unexpected error when querying bucket info: {e}"); | ||||||
|             return Err(BucketServiceError::FailedFetchBucketInfo.into()); |             return Err(BucketServiceError::FailedFetchBucketInfo.into()); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -78,12 +78,15 @@ pub async fn finances_manager_import(auth: AuthExtractor, file: FileExtractor) - | |||||||
|  |  | ||||||
| /// Export data to a [FinancesManager](https://gitlab.com/pierre42100/cpp-financesmanager) file | /// Export data to a [FinancesManager](https://gitlab.com/pierre42100/cpp-financesmanager) file | ||||||
| pub async fn finances_manager_export(auth: AuthExtractor) -> HttpResult { | pub async fn finances_manager_export(auth: AuthExtractor) -> HttpResult { | ||||||
|     let accounts = accounts_service::get_list_user(auth.user_id()).await?; |     let mut accounts = accounts_service::get_list_user(auth.user_id()).await?; | ||||||
|  |     accounts.sort_by_key(|a| a.id()); | ||||||
|  |  | ||||||
|     let mut out = FinancesManagerFile { accounts: vec![] }; |     let mut out = FinancesManagerFile { accounts: vec![] }; | ||||||
|  |  | ||||||
|     for account in accounts { |     for account in accounts { | ||||||
|         let movements = movements_service::get_list_account(account.id()).await?; |         let mut movements = movements_service::get_list_account(account.id()).await?; | ||||||
|  |         movements.sort_by(|a, b| b.time.cmp(&a.time)); | ||||||
|  |  | ||||||
|         let mut file_account = FinancesManagerAccount { |         let mut file_account = FinancesManagerAccount { | ||||||
|             name: account.name, |             name: account.name, | ||||||
|             movements: Vec::with_capacity(movements.len()), |             movements: Vec::with_capacity(movements.len()), | ||||||
|   | |||||||
| @@ -50,7 +50,7 @@ impl FromRequest for AccountInPath { | |||||||
|             Self::load_account_from_path(&auth, account_id) |             Self::load_account_from_path(&auth, account_id) | ||||||
|                 .await |                 .await | ||||||
|                 .map_err(|e| { |                 .map_err(|e| { | ||||||
|                     log::error!("Failed to extract account ID from URL! {}", e); |                     log::error!("Failed to extract account ID from URL! {e}"); | ||||||
|                     actix_web::error::ErrorNotFound("Could not fetch account information!") |                     actix_web::error::ErrorNotFound("Could not fetch account information!") | ||||||
|                 }) |                 }) | ||||||
|         }) |         }) | ||||||
|   | |||||||
| @@ -165,13 +165,13 @@ impl FromRequest for AuthExtractor { | |||||||
|                 // Update last use (if needed) |                 // Update last use (if needed) | ||||||
|                 if token.shall_update_time_used() { |                 if token.shall_update_time_used() { | ||||||
|                     if let Err(e) = tokens_service::update_time_used(&token).await { |                     if let Err(e) = tokens_service::update_time_used(&token).await { | ||||||
|                         log::error!("Failed to refresh last usage of token! {}", e); |                         log::error!("Failed to refresh last usage of token! {e}"); | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                 // Handle tokens expiration |                 // Handle tokens expiration | ||||||
|                 if token.is_expired() { |                 if token.is_expired() { | ||||||
|                     log::error!("Attempted to use expired token! {:?}", token); |                     log::error!("Attempted to use expired token! {token:?}"); | ||||||
|                     return Err(actix_web::error::ErrorBadRequest("Token has expired!")); |                     return Err(actix_web::error::ErrorBadRequest("Token has expired!")); | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -47,7 +47,7 @@ impl FromRequest for FileIdExtractor { | |||||||
|             Self::load_file_from_path(&auth, file_id) |             Self::load_file_from_path(&auth, file_id) | ||||||
|                 .await |                 .await | ||||||
|                 .map_err(|e| { |                 .map_err(|e| { | ||||||
|                     log::error!("Failed to extract file ID from URL! {}", e); |                     log::error!("Failed to extract file ID from URL! {e}"); | ||||||
|                     actix_web::error::ErrorNotFound("Could not fetch file information!") |                     actix_web::error::ErrorNotFound("Could not fetch file information!") | ||||||
|                 }) |                 }) | ||||||
|         }) |         }) | ||||||
|   | |||||||
| @@ -50,7 +50,7 @@ impl FromRequest for InboxEntryInPath { | |||||||
|             Self::load_inbox_entry_from_path(&auth, entry_id) |             Self::load_inbox_entry_from_path(&auth, entry_id) | ||||||
|                 .await |                 .await | ||||||
|                 .map_err(|e| { |                 .map_err(|e| { | ||||||
|                     log::error!("Failed to extract inbox entry ID from URL! {}", e); |                     log::error!("Failed to extract inbox entry ID from URL! {e}"); | ||||||
|                     actix_web::error::ErrorNotFound("Could not fetch inbox entry information!") |                     actix_web::error::ErrorNotFound("Could not fetch inbox entry information!") | ||||||
|                 }) |                 }) | ||||||
|         }) |         }) | ||||||
|   | |||||||
| @@ -57,7 +57,7 @@ impl FromRequest for MovementInPath { | |||||||
|             Self::load_movement_from_path(&auth, account_id) |             Self::load_movement_from_path(&auth, account_id) | ||||||
|                 .await |                 .await | ||||||
|                 .map_err(|e| { |                 .map_err(|e| { | ||||||
|                     log::error!("Failed to extract movement ID from URL! {}", e); |                     log::error!("Failed to extract movement ID from URL! {e}"); | ||||||
|                     actix_web::error::ErrorNotFound("Could not fetch movement information!") |                     actix_web::error::ErrorNotFound("Could not fetch movement information!") | ||||||
|                 }) |                 }) | ||||||
|         }) |         }) | ||||||
|   | |||||||
							
								
								
									
										1858
									
								
								moneymgr_web/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1858
									
								
								moneymgr_web/package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -11,37 +11,37 @@ | |||||||
|   }, |   }, | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|     "@emotion/react": "^11.14.0", |     "@emotion/react": "^11.14.0", | ||||||
|     "@emotion/styled": "^11.14.0", |     "@emotion/styled": "^11.14.1", | ||||||
|     "@fontsource/roboto": "^5.2.5", |     "@fontsource/roboto": "^5.2.6", | ||||||
|     "@jsonjoy.com/base64": "^1.1.2", |     "@jsonjoy.com/base64": "^1.1.2", | ||||||
|     "@mdi/js": "^7.4.47", |     "@mdi/js": "^7.4.47", | ||||||
|     "@mdi/react": "^1.6.1", |     "@mdi/react": "^1.6.1", | ||||||
|     "@mui/icons-material": "^7.1.0", |     "@mui/icons-material": "^7.1.2", | ||||||
|     "@mui/material": "^7.1.0", |     "@mui/material": "^7.1.2", | ||||||
|     "@mui/x-charts": "^8.3.1", |     "@mui/x-charts": "^8.5.3", | ||||||
|     "@mui/x-data-grid": "^8.3.1", |     "@mui/x-data-grid": "^8.5.3", | ||||||
|     "@mui/x-date-pickers": "^8.3.1", |     "@mui/x-date-pickers": "^8.5.3", | ||||||
|     "date-and-time": "^3.6.0", |     "date-and-time": "^3.6.0", | ||||||
|     "dayjs": "^1.11.13", |     "dayjs": "^1.11.13", | ||||||
|     "filesize": "^10.1.6", |     "filesize": "^10.1.6", | ||||||
|     "qrcode.react": "^4.2.0", |     "qrcode.react": "^4.2.0", | ||||||
|     "react": "^19.1.0", |     "react": "^19.1.0", | ||||||
|     "react-dom": "^19.1.0", |     "react-dom": "^19.1.0", | ||||||
|     "react-router": "^7.6.0", |     "react-router": "^7.6.3", | ||||||
|     "react-router-dom": "^7.6.0", |     "react-router-dom": "^7.6.3", | ||||||
|     "ts-pattern": "^5.7.0" |     "ts-pattern": "^5.7.1" | ||||||
|   }, |   }, | ||||||
|   "devDependencies": { |   "devDependencies": { | ||||||
|     "@eslint/js": "^9.26.0", |     "@eslint/js": "^9.30.1", | ||||||
|     "@types/react": "^19.1.4", |     "@types/react": "^19.1.8", | ||||||
|     "@types/react-dom": "^19.1.5", |     "@types/react-dom": "^19.1.6", | ||||||
|     "@vitejs/plugin-react": "^4.4.1", |     "@vitejs/plugin-react": "^4.6.0", | ||||||
|     "eslint": "^9.26.0", |     "eslint": "^9.26.0", | ||||||
|     "eslint-plugin-react-dom": "^1.49.0", |     "eslint-plugin-react-dom": "^1.52.2", | ||||||
|     "eslint-plugin-react-hooks": "^5.1.0", |     "eslint-plugin-react-hooks": "^5.2.0", | ||||||
|     "eslint-plugin-react-refresh": "^00.4.20", |     "eslint-plugin-react-refresh": "^00.4.20", | ||||||
|     "eslint-plugin-react-x": "^1.49.0", |     "eslint-plugin-react-x": "^1.52.2", | ||||||
|     "globals": "^16.1.0", |     "globals": "^16.2.0", | ||||||
|     "typescript": "~5.8.3", |     "typescript": "~5.8.3", | ||||||
|     "typescript-eslint": "^8.32.1", |     "typescript-eslint": "^8.32.1", | ||||||
|     "vite": "^6.3.5" |     "vite": "^6.3.5" | ||||||
|   | |||||||
| @@ -31,12 +31,21 @@ export class APIClient { | |||||||
|     return URL; |     return URL; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * Get the full URL at which the backend can be contacted | ||||||
|  |    */ | ||||||
|  |   static ActualBackendURL(): string { | ||||||
|  |     const backendURL = this.backendURL(); | ||||||
|  |     if (backendURL.startsWith("/")) return `${location.origin}${backendURL}`; | ||||||
|  |     else return backendURL; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
|    * Check out whether the backend is accessed through |    * Check out whether the backend is accessed through | ||||||
|    * HTTPS or not |    * HTTPS or not | ||||||
|    */ |    */ | ||||||
|   static IsBackendSecure(): boolean { |   static IsBackendSecure(): boolean { | ||||||
|     return this.backendURL().startsWith("https"); |     return this.ActualBackendURL().startsWith("https"); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
|   | |||||||
| @@ -121,13 +121,15 @@ function MovementsTable(p: { | |||||||
|  |  | ||||||
|   const chooseAccount = useSelectAccount(); |   const chooseAccount = useSelectAccount(); | ||||||
|  |  | ||||||
|   const [labelFilter, setLabelFilter] = React.useState(""); |   const [filter, setFilter] = React.useState(""); | ||||||
|  |  | ||||||
|   const filteredList = React.useMemo(() => { |   const filteredList = React.useMemo(() => { | ||||||
|     return p.movements.filter((m) => |     return p.movements.filter( | ||||||
|       m.label.toLowerCase().includes(labelFilter.toLowerCase()) |       (m) => | ||||||
|  |         m.label.toLowerCase().includes(filter.toLowerCase()) || | ||||||
|  |         m.amount.toString().includes(filter) | ||||||
|     ); |     ); | ||||||
|   }, [p.movements, labelFilter]); |   }, [p.movements, filter]); | ||||||
|  |  | ||||||
|   const [rowSelectionModel, setRowSelectionModel] = |   const [rowSelectionModel, setRowSelectionModel] = | ||||||
|     React.useState<GridRowSelectionModel>({ type: "include", ids: new Set() }); |     React.useState<GridRowSelectionModel>({ type: "include", ids: new Set() }); | ||||||
| @@ -382,12 +384,12 @@ function MovementsTable(p: { | |||||||
|     <> |     <> | ||||||
|       <div style={{ display: "flex" }}> |       <div style={{ display: "flex" }}> | ||||||
|         <TextField |         <TextField | ||||||
|           placeholder="Filter by label" |           placeholder="Filter by label or amount" | ||||||
|           variant="standard" |           variant="standard" | ||||||
|           size="small" |           size="small" | ||||||
|           value={labelFilter} |           value={filter} | ||||||
|           onChange={(e) => { |           onChange={(e) => { | ||||||
|             setLabelFilter(e.target.value); |             setFilter(e.target.value); | ||||||
|           }} |           }} | ||||||
|           style={{ padding: "0px", flex: 1 }} |           style={{ padding: "0px", flex: 1 }} | ||||||
|         /> |         /> | ||||||
|   | |||||||
| @@ -158,7 +158,7 @@ function ImportExportModal(p: { | |||||||
|         </CardContent>{" "} |         </CardContent>{" "} | ||||||
|         <CardActions> |         <CardActions> | ||||||
|           <span style={{ flex: 1 }}> |           <span style={{ flex: 1 }}> | ||||||
|             <RouterLink to={p.exportURL}> |             <RouterLink to={p.exportURL} target="_blank"> | ||||||
|               <Button |               <Button | ||||||
|                 startIcon={<DownloadIcon />} |                 startIcon={<DownloadIcon />} | ||||||
|                 variant="outlined" |                 variant="outlined" | ||||||
|   | |||||||
| @@ -268,7 +268,7 @@ function CreatedToken(p: { token: TokenWithSecret }): React.ReactElement { | |||||||
|           <div style={{ padding: "15px", backgroundColor: "white" }}> |           <div style={{ padding: "15px", backgroundColor: "white" }}> | ||||||
|             <QRCodeCanvas |             <QRCodeCanvas | ||||||
|               value={`moneymgr://api=${encodeURIComponent( |               value={`moneymgr://api=${encodeURIComponent( | ||||||
|                 APIClient.backendURL() |                 APIClient.ActualBackendURL() | ||||||
|               )}&id=${p.token.id}&secret=${p.token.token}`} |               )}&id=${p.token.id}&secret=${p.token.token}`} | ||||||
|             /> |             /> | ||||||
|           </div> |           </div> | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| import { DatePicker } from "@mui/x-date-pickers"; | import { DateField } from "@mui/x-date-pickers"; | ||||||
| import { dateToTime, timeToDate } from "../../utils/DateUtils"; | import { dateToTime, timeToDate } from "../../utils/DateUtils"; | ||||||
| import { TextFieldVariants } from "@mui/material"; | import { TextFieldVariants } from "@mui/material"; | ||||||
|  |  | ||||||
| @@ -13,13 +13,16 @@ export function DateInput(p: { | |||||||
|   variant?: TextFieldVariants; |   variant?: TextFieldVariants; | ||||||
| }): React.ReactElement { | }): React.ReactElement { | ||||||
|   return ( |   return ( | ||||||
|     <DatePicker |     <DateField | ||||||
|       autoFocus={p.autoFocus} |       autoFocus={p.autoFocus} | ||||||
|       readOnly={p.editable === false} |       readOnly={p.editable === false} | ||||||
|       label={p.label} |       label={p.label} | ||||||
|       slotProps={{ |       slotProps={{ | ||||||
|         field: { ref: p.ref }, |         textField: { | ||||||
|         textField: { variant: p.variant ?? "standard", style: p.style }, |           ref: p.ref, | ||||||
|  |           variant: p.variant ?? "standard", | ||||||
|  |           style: p.style, | ||||||
|  |         }, | ||||||
|       }} |       }} | ||||||
|       value={timeToDate(p.value)} |       value={timeToDate(p.value)} | ||||||
|       onChange={(v) => { |       onChange={(v) => { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user