fix: security, wallet holds, printpdf upgrade

- Upgrade printpdf 0.7 → 0.12.5 in job_seekers (fixes lopdf HIGH CVE RUSTSEC-2023-0068)
  Rewrote build_resume_pdf() for the new Op-based API; same PDF output
- Add wallet/me/holds and wallet/me/holds/{id}/release routes to profession_shared
  Backed by wallet::hold::list_for_user and wallet::hold::release
  Applies to all 9 profession services via the shared router
- Add deny.toml for cargo-deny (advisory + ban policy enforcement)
  RSA timing CVE and bincode unmaintained acknowledged with documented reasons

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Tracewebstudio Dev 2026-08-12 13:38:17 +02:00
parent ddb2110e86
commit 8423b7a0f5
8 changed files with 667 additions and 101 deletions

537
Cargo.lock generated
View file

@ -8,6 +8,17 @@ version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
[[package]]
name = "aes"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8eb277bec05f56a0e0591f155a484cbd0f4f07ff2905051a48c72f004f7ed58"
dependencies = [
"cipher",
"cpubits",
"cpufeatures 0.3.0",
]
[[package]]
name = "ahash"
version = "0.7.8"
@ -28,12 +39,56 @@ dependencies = [
"memchr",
]
[[package]]
name = "aliasable"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd"
[[package]]
name = "alloc-no-stdlib"
version = "2.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3"
[[package]]
name = "alloc-stdlib"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e76a019e91224d279006ff972f1e984179a6e9feb050adba6ce8274aef23195"
dependencies = [
"alloc-no-stdlib",
]
[[package]]
name = "allocator-api2"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]]
name = "allsorts-azul"
version = "0.17.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab097a7be305dd66b2b6917f5efb6b723129ce4b2cd11aa1a0afcc293c7dc08e"
dependencies = [
"bitflags",
"brotli-decompressor",
"encoding_rs",
"enumflags2",
"flate2",
"glyph-names",
"log",
"ouroboros",
"pathfinder_geometry",
"rustc-hash",
"tinyvec",
"ucd-trie",
"unicode-canonical-combining-class",
"unicode-general-category",
"unicode-joining-type",
]
[[package]]
name = "android_system_properties"
version = "0.1.5"
@ -618,6 +673,60 @@ dependencies = [
"tracing",
]
[[package]]
name = "azul-core"
version = "0.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab0b7a3437b72d2e458b8362eb69fd4f9f136465ab750590c398ccf97f976a9e"
dependencies = [
"azul-css",
"gl-context-loader",
"libm",
"rust-fontconfig",
]
[[package]]
name = "azul-css"
version = "0.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ca878356a1620147ee57559bdd3d7f8fd0d02925211e6f2553c6d24d39eb7d4"
dependencies = [
"azul-simplecss",
"libm",
]
[[package]]
name = "azul-layout"
version = "0.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbae4e82c24f18174095b719475bec3a39f326a0176d477699e31f07e9bec98d"
dependencies = [
"allsorts-azul",
"azul-core",
"azul-css",
"base64",
"hyphenation",
"lru",
"roxmltree",
"rust-fontconfig",
"serde",
"smallvec",
"taffy",
"thiserror",
"tinyvec",
"unicode-bidi",
"unicode-normalization",
"unicode-segmentation",
"xmlparser",
"xmlwriter",
]
[[package]]
name = "azul-simplecss"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8259ebd5ee48a37fd8931116405456fd67efbb5435b4789e45bdbccf5d7dea7e"
[[package]]
name = "backon"
version = "1.6.0"
@ -655,6 +764,15 @@ version = "1.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06"
[[package]]
name = "bincode"
version = "1.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
dependencies = [
"serde",
]
[[package]]
name = "bitflags"
version = "2.13.1"
@ -703,6 +821,15 @@ dependencies = [
"hybrid-array",
]
[[package]]
name = "block-padding"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "710f1dd022ef4e93f8a438b4ba958de7f64308434fa6a87104481645cc30068b"
dependencies = [
"hybrid-array",
]
[[package]]
name = "borsh"
version = "1.8.0"
@ -728,14 +855,13 @@ dependencies = [
]
[[package]]
name = "bstr"
version = "1.13.0"
name = "brotli-decompressor"
version = "5.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f7dc094d718f2e1c1559ad110e27eeaae14a5465d3d56dd6dbd793079fbd530"
checksum = "3a32acac15fe1967bc3986b2a6347dffc965602354ea6f450ad07e8bfd253583"
dependencies = [
"memchr",
"regex-automata",
"serde_core",
"alloc-no-stdlib",
"alloc-stdlib",
]
[[package]]
@ -823,6 +949,15 @@ dependencies = [
"uuid",
]
[[package]]
name = "cbc"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce2dc9ee5f88d11e0beb842c88b33c8a5cf0d1329c4b19494af42b07dbfe8896"
dependencies = [
"cipher",
]
[[package]]
name = "cc"
version = "1.3.0"
@ -872,6 +1007,16 @@ dependencies = [
"windows-link",
]
[[package]]
name = "cipher"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c"
dependencies = [
"crypto-common 0.2.2",
"inout",
]
[[package]]
name = "cmake"
version = "0.1.58"
@ -964,6 +1109,7 @@ dependencies = [
"storage",
"tracing",
"uuid",
"wallet",
]
[[package]]
@ -992,6 +1138,12 @@ version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "cpubits"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae"
[[package]]
name = "cpufeatures"
version = "0.2.17"
@ -1274,6 +1426,15 @@ version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
[[package]]
name = "ecb"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbfbf3db731928d6912bc3beda911d55b834cee3df6131ba79b337a1298a3fa9"
dependencies = [
"cipher",
]
[[package]]
name = "ecdsa"
version = "0.16.9"
@ -1401,6 +1562,26 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "enumflags2"
version = "0.7.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef"
dependencies = [
"enumflags2_derive",
]
[[package]]
name = "enumflags2_derive"
version = "0.7.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.119",
]
[[package]]
name = "equivalent"
version = "1.0.2"
@ -1561,6 +1742,12 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
[[package]]
name = "fst"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ab85b9b05e3978cc9a9cf8fea7f01b494e1a09ed3037e16ba39edc7a29eb61a"
[[package]]
name = "funty"
version = "2.0.0"
@ -1719,6 +1906,18 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "gl-context-loader"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ca63cb2488b847c12d1ca56034ba0f15613972c0dc0f8a957f525707567b23d"
[[package]]
name = "glyph-names"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3531d702d6c1a3ba92a5fb55a404c7b8c476c8e7ca249951077afcbe4bc807f"
[[package]]
name = "graphic_designers"
version = "0.1.0"
@ -1738,6 +1937,12 @@ dependencies = [
"uuid",
]
[[package]]
name = "grid"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b40ca9252762c466af32d0b1002e91e4e1bc5398f77455e55474deb466355ff5"
[[package]]
name = "group"
version = "0.13.0"
@ -1814,6 +2019,12 @@ dependencies = [
"hashbrown 0.15.5",
]
[[package]]
name = "heck"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "heck"
version = "0.5.0"
@ -2018,6 +2229,29 @@ dependencies = [
"windows-registry",
]
[[package]]
name = "hyphenation"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcf4dd4c44ae85155502a52c48739c8a48185d1449fff1963cffee63c28a50f0"
dependencies = [
"bincode",
"fst",
"hyphenation_commons",
"pocket-resources",
"serde",
]
[[package]]
name = "hyphenation_commons"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5febe7a2ade5c7d98eb8b75f946c046b335324b06a14ea0998271504134c05bf"
dependencies = [
"fst",
"serde",
]
[[package]]
name = "iana-time-zone"
version = "0.1.65"
@ -2155,6 +2389,16 @@ dependencies = [
"hashbrown 0.17.1",
]
[[package]]
name = "inout"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7"
dependencies = [
"block-padding",
"hybrid-array",
]
[[package]]
name = "invoice"
version = "0.1.0"
@ -2362,12 +2606,6 @@ dependencies = [
"vcpkg",
]
[[package]]
name = "linked-hash-map"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
[[package]]
name = "linux-raw-sys"
version = "0.12.1"
@ -2397,18 +2635,27 @@ checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
[[package]]
name = "lopdf"
version = "0.31.0"
version = "0.44.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07c8e1b6184b1b32ea5f72f572ebdc40e5da1d2921fa469947ff7c480ad1f85a"
checksum = "5e2ec995d822e05cabc3f06d196ee43650af3fe4fe38012cacb35e0c3d113b68"
dependencies = [
"aes",
"bitflags",
"cbc",
"ecb",
"encoding_rs",
"flate2",
"getrandom 0.4.3",
"indexmap",
"itoa",
"linked-hash-map",
"log",
"md5",
"pom",
"time",
"md-5 0.11.0",
"nom",
"rand 0.10.2",
"rangemap",
"sha2 0.11.0",
"stringprep",
"thiserror",
"weezl",
]
@ -2481,12 +2728,6 @@ dependencies = [
"digest 0.11.3",
]
[[package]]
name = "md5"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771"
[[package]]
name = "memchr"
version = "2.8.3"
@ -2530,6 +2771,16 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "mmapio"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0204e2cac68f5b2e35b7ec8cb5d906f6e58e78dad8066a30b6ee54da99bb03dd"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "multer"
version = "3.1.0"
@ -2692,21 +2943,36 @@ dependencies = [
"vcpkg",
]
[[package]]
name = "ouroboros"
version = "0.18.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e0f050db9c44b97a94723127e6be766ac5c340c48f2c4bb3ffa11713744be59"
dependencies = [
"aliasable",
"ouroboros_macro",
"static_assertions",
]
[[package]]
name = "ouroboros_macro"
version = "0.18.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c7028bdd3d43083f6d8d4d5187680d0d3560d54df4cc9d752005268b41e64d0"
dependencies = [
"heck 0.4.1",
"proc-macro2",
"proc-macro2-diagnostics",
"quote",
"syn 2.0.119",
]
[[package]]
name = "outref"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e"
[[package]]
name = "owned_ttf_parser"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "706de7e2214113d63a8238d1910463cfce781129a6f263d13fdb09ff64355ba4"
dependencies = [
"ttf-parser",
]
[[package]]
name = "p256"
version = "0.13.2"
@ -2771,6 +3037,25 @@ dependencies = [
"subtle",
]
[[package]]
name = "pathfinder_geometry"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b7e7b4ea703700ce73ebf128e1450eb69c3a8329199ffbfb9b2a0418e5ad3"
dependencies = [
"log",
"pathfinder_simd",
]
[[package]]
name = "pathfinder_simd"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4500030c302e4af1d423f36f3b958d1aecb6c04184356ed5a833bf6b60435777"
dependencies = [
"rustc_version",
]
[[package]]
name = "payments"
version = "0.1.0"
@ -2885,13 +3170,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
[[package]]
name = "pom"
version = "3.4.0"
name = "pocket-resources"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c972d8f86e943ad532d0b04e8965a749ad1d18bb981a9c7b3ae72fe7fd7744b"
dependencies = [
"bstr",
]
checksum = "c135f38778ad324d9e9ee68690bac2c1a51f340fdf96ca13e2ab3914eb2e51d8"
[[package]]
name = "postgres-protocol"
@ -2957,14 +3239,28 @@ dependencies = [
[[package]]
name = "printpdf"
version = "0.7.0"
version = "0.12.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c30a4cc87c3ca9a98f4970db158a7153f8d1ec8076e005751173c57836380b1d"
checksum = "8830a8a6c6431286333c8a64d109f7f0e7e050270c8b8eba0a4c541626059da5"
dependencies = [
"js-sys",
"allsorts-azul",
"azul-core",
"azul-css",
"azul-layout",
"base64",
"flate2",
"getrandom 0.4.3",
"lopdf",
"owned_ttf_parser",
"rust-fontconfig",
"serde",
"serde_derive",
"serde_json",
"smallvec",
"time",
"wasm-bindgen",
"wasm-bindgen-futures",
"weezl",
"xmlparser",
]
[[package]]
@ -2985,6 +3281,19 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "proc-macro2-diagnostics"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.119",
"version_check",
"yansi",
]
[[package]]
name = "ptr_meta"
version = "0.1.4"
@ -3144,6 +3453,12 @@ dependencies = [
"rand_core 0.10.1",
]
[[package]]
name = "rangemap"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "973443cf09a9c8656b574a866ab68dfa19f0867d0340648c7d2f6a71b8a8ea68"
[[package]]
name = "redis"
version = "0.27.6"
@ -3333,6 +3648,15 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "roxmltree"
version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1964b10c76125c36f8afe190065a4bf9a87bf324842c05701330bba9f1cacbb"
dependencies = [
"memchr",
]
[[package]]
name = "rsa"
version = "0.9.10"
@ -3353,6 +3677,17 @@ dependencies = [
"zeroize",
]
[[package]]
name = "rust-fontconfig"
version = "4.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87542166cd2a7b38cf2c338384d882656e2fbbdd9813017415f1d57ad35757e9"
dependencies = [
"allsorts-azul",
"mmapio",
"xmlparser",
]
[[package]]
name = "rust_decimal"
version = "1.42.1"
@ -3706,6 +4041,15 @@ version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
[[package]]
name = "slotmap"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038"
dependencies = [
"version_check",
]
[[package]]
name = "smallvec"
version = "1.15.2"
@ -3851,7 +4195,7 @@ checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b"
dependencies = [
"dotenvy",
"either",
"heck",
"heck 0.5.0",
"hex",
"once_cell",
"proc-macro2",
@ -3983,6 +4327,12 @@ version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "storage"
version = "0.1.0"
@ -4091,6 +4441,18 @@ dependencies = [
"libc",
]
[[package]]
name = "taffy"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aea22054047c16c3f34d3ac473a2170be1424b1115b2a3adcf28cfb067c88859"
dependencies = [
"arrayvec",
"grid",
"serde",
"slotmap",
]
[[package]]
name = "tap"
version = "1.0.1"
@ -4412,12 +4774,6 @@ version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
[[package]]
name = "ttf-parser"
version = "0.19.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49d64318d8311fc2668e48b63969f4343e0a85c4a109aa8460d6672e364b8bd1"
[[package]]
name = "tutors"
version = "0.1.0"
@ -4443,6 +4799,12 @@ version = "1.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
[[package]]
name = "ucd-trie"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
[[package]]
name = "ugc_content_creators"
version = "0.1.0"
@ -4474,12 +4836,30 @@ version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5"
[[package]]
name = "unicode-canonical-combining-class"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41c99d5174052d02ce765418e826597a1be18f32c114e35d9e22f92390239561"
[[package]]
name = "unicode-general-category"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b993bddc193ae5bd0d623b49ec06ac3e9312875fdae725a975c51db1cc1677f"
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "unicode-joining-type"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8d00a78170970967fdb83f9d49b92f959ab2bb829186b113e4f4604ad98e180"
[[package]]
name = "unicode-normalization"
version = "0.1.25"
@ -4495,6 +4875,12 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d"
[[package]]
name = "unicode-segmentation"
version = "1.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8"
[[package]]
name = "untrusted"
version = "0.9.0"
@ -4612,6 +4998,19 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64"
[[package]]
name = "wallet"
version = "0.1.0"
dependencies = [
"chrono",
"serde",
"serde_json",
"sqlx",
"thiserror",
"tracing",
"uuid",
]
[[package]]
name = "want"
version = "0.3.1"
@ -4742,9 +5141,9 @@ dependencies = [
[[package]]
name = "weezl"
version = "0.1.12"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88"
checksum = "d4ca08e5ef825b65b056d9efbd95c8750683f0a6d0466d02e96dc2e4e360f3d2"
[[package]]
name = "whoami"
@ -4756,6 +5155,28 @@ dependencies = [
"wasite",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-core"
version = "0.62.2"
@ -5004,6 +5425,18 @@ version = "0.13.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4"
[[package]]
name = "xmlwriter"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9"
[[package]]
name = "yansi"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
[[package]]
name = "yoke"
version = "0.8.3"

View file

@ -175,7 +175,9 @@ pub async fn run_auto_apply(pool: &PgPool) -> Result<(), Box<dyn std::error::Err
let client = Client::new();
let cutoff_time = Utc::now() - Duration::hours(24);
// Fetch job seekers who have auto-apply enabled and sufficient AI credits
// Fetch job seekers who have auto-apply enabled AND whose plan allows it.
// The "auto_apply_execute" feature gate ensures only paid subscribers run;
// free-plan users who turned the toggle on are skipped here (not penalised).
let seekers: Vec<EligibleSeeker> = sqlx::query_as(
r#"
SELECT
@ -194,11 +196,13 @@ pub async fn run_auto_apply(pool: &PgPool) -> Result<(), Box<dyn std::error::Err
INNER JOIN job_seeker_profiles js ON js.user_id = u.id
INNER JOIN ai_auto_apply_settings aas ON aas.user_id = u.id
INNER JOIN user_ai_subscriptions uas ON uas.user_id = u.id
INNER JOIN ai_plans ap ON ap.id = uas.plan_id
WHERE u.status = 'ACTIVE'
AND aas.is_enabled = true
AND uas.status = 'active'
AND NOW() >= uas.current_period_start
AND NOW() < uas.current_period_end
AND ap.allowed_features ? 'auto_apply_execute'
"#,
)
.fetch_all(pool)

View file

@ -21,6 +21,6 @@ email = { path = "../../crates/email" }
serde_json = { workspace = true }
redis = { workspace = true }
cache = { path = "../../crates/cache" }
printpdf = { workspace = true }
printpdf = "0.12.5"
anyhow = { workspace = true }

View file

@ -238,73 +238,69 @@ fn build_resume_pdf(
skills: &str,
) -> Result<Vec<u8>, Box<dyn std::error::Error + Send + Sync>> {
use printpdf::*;
use std::io::{BufWriter, Cursor};
let (doc, page1, layer1) = PdfDocument::new(
&format!("{} - Resume", name),
Mm(210.0),
Mm(297.0),
"Resume",
);
let layer = doc.get_page(page1).get_layer(layer1);
// Push a single text span at a specific (x, y) position in mm.
fn push_text(ops: &mut Vec<Op>, text: &str, size: f32, x: f32, y: f32, font: BuiltinFont) {
ops.push(Op::StartTextSection);
ops.push(Op::SetTextCursor { pos: Point::new(Mm(x), Mm(y)) });
ops.push(Op::SetFont { font: PdfFontHandle::Builtin(font), size: Pt(size) });
ops.push(Op::ShowText { items: vec![TextItem::Text(text.to_string())] });
ops.push(Op::EndTextSection);
}
let regular = doc.add_builtin_font(BuiltinFont::Helvetica)?;
let bold = doc.add_builtin_font(BuiltinFont::HelveticaBold)?;
let mut ops: Vec<Op> = Vec::new();
let left = 20.0_f32;
let mut y = 276.0_f32;
let left: Mm = Mm(20.0);
let mut y: f32 = 276.0;
// Name
layer.use_text(name, 20.0, left, Mm(y), &bold);
// Name (large, bold)
push_text(&mut ops, name, 20.0, left, y, BuiltinFont::HelveticaBold);
y -= 9.0;
// Headline
if !headline.is_empty() {
layer.use_text(headline, 11.0, left, Mm(y), &regular);
push_text(&mut ops, headline, 11.0, left, y, BuiltinFont::Helvetica);
y -= 5.0;
}
// Separator (text-based)
layer.use_text("___________________________________________________", 9.0, left, Mm(y), &regular);
// Separator
push_text(&mut ops, "___________________________________________________", 9.0, left, y, BuiltinFont::Helvetica);
y -= 8.0;
// Helper: render one section — header then wrapped body lines
let mut render_section = |label: &str, content: &str| {
if content.trim().is_empty() { return; }
if y < 20.0 { return; }
// Section header in bold
layer.use_text(label, 9.5, left, Mm(y), &bold);
// Sections: bold header then wrapped body lines
for (label, content) in [
("PROFESSIONAL SUMMARY", summary),
("SKILLS", skills),
("EDUCATION", education),
("WORK EXPERIENCE", work_experience),
] {
if content.trim().is_empty() || y < 20.0 { continue; }
push_text(&mut ops, label, 9.5, left, y, BuiltinFont::HelveticaBold);
y -= 5.5;
// Body text, word-wrapped
for line in wrap_text(content, 88) {
if y < 20.0 { break; }
if line.is_empty() {
y -= 2.5;
} else {
layer.use_text(&line, 9.5, left, Mm(y), &regular);
push_text(&mut ops, &line, 9.5, left, y, BuiltinFont::Helvetica);
y -= 5.0;
}
}
y -= 4.0;
};
render_section("PROFESSIONAL SUMMARY", summary);
render_section("SKILLS", skills);
render_section("EDUCATION", education);
render_section("WORK EXPERIENCE", work_experience);
}
// Privacy footer
layer.use_text(
push_text(
&mut ops,
"* Contact details are hidden. Use Nxtgauge to unlock and connect with this candidate.",
7.5,
left,
Mm(12.0),
&regular,
7.5, left, 12.0, BuiltinFont::Helvetica,
);
let mut buf = Vec::new();
doc.save(&mut BufWriter::new(Cursor::new(&mut buf)))?;
Ok(buf)
let page = PdfPage::new(Mm(210.0), Mm(297.0), ops);
let bytes = PdfDocument::new(&format!("{} - Resume", name))
.with_pages(vec![page])
.save(&PdfSaveOptions::default(), &mut Vec::new());
Ok(bytes)
}
async fn browse_jobs(

View file

@ -1044,12 +1044,18 @@ async fn ai_generate_job_field(
Some(w) => db::models::ai_credits::AiCreditsRepository::get_plan(&state.pool, w.plan_id).await.ok().flatten(),
None => None,
};
let daily_limit = plan.as_ref().map(|p| p.daily_action_limit).unwrap_or(0);
// Fix: return remaining daily ACTIONS (not total credit balance) so the
// frontend "AI generations left today" counter stays accurate for paid users.
let remaining_today = wallet.as_ref().zip(plan.as_ref())
.map(|(w, p)| (p.daily_action_limit as i64 - w.daily_actions_used as i64).max(0) as i32)
.unwrap_or(0);
(
StatusCode::OK,
Json(GenerateFieldResponse {
generated_text: outcome.result.trim().to_string(),
remaining_today: wallet.as_ref().map(|w| w.available_credits()).unwrap_or(0),
daily_limit: plan.map(|p| p.daily_action_limit).unwrap_or(0),
remaining_today,
daily_limit,
has_ai_pack: wallet.as_ref().map(|w| w.purchased_credits_total > 0).unwrap_or(false),
credits_charged: None,
remaining_credits: None,
@ -1179,12 +1185,18 @@ async fn ai_generate_cover_letter(
Some(w) => db::models::ai_credits::AiCreditsRepository::get_plan(&state.pool, w.plan_id).await.ok().flatten(),
None => None,
};
let daily_limit = plan.as_ref().map(|p| p.daily_action_limit).unwrap_or(0);
// Fix: return remaining daily ACTIONS (not total credit balance) so the
// frontend "AI generations left today" counter stays accurate for paid users.
let remaining_today = wallet.as_ref().zip(plan.as_ref())
.map(|(w, p)| (p.daily_action_limit as i64 - w.daily_actions_used as i64).max(0) as i32)
.unwrap_or(0);
(
StatusCode::OK,
Json(GenerateFieldResponse {
generated_text: outcome.result.trim().to_string(),
remaining_today: wallet.as_ref().map(|w| w.available_credits()).unwrap_or(0),
daily_limit: plan.map(|p| p.daily_action_limit).unwrap_or(0),
remaining_today,
daily_limit,
has_ai_pack: wallet.as_ref().map(|w| w.purchased_credits_total > 0).unwrap_or(false),
credits_charged: None,
remaining_credits: None,
@ -1310,12 +1322,18 @@ async fn ai_tailor_resume(
Some(w) => db::models::ai_credits::AiCreditsRepository::get_plan(&state.pool, w.plan_id).await.ok().flatten(),
None => None,
};
let daily_limit = plan.as_ref().map(|p| p.daily_action_limit).unwrap_or(0);
// Fix: return remaining daily ACTIONS (not total credit balance) so the
// frontend "AI generations left today" counter stays accurate for paid users.
let remaining_today = wallet.as_ref().zip(plan.as_ref())
.map(|(w, p)| (p.daily_action_limit as i64 - w.daily_actions_used as i64).max(0) as i32)
.unwrap_or(0);
(
StatusCode::OK,
Json(GenerateFieldResponse {
generated_text: outcome.result.trim().to_string(),
remaining_today: wallet.as_ref().map(|w| w.available_credits()).unwrap_or(0),
daily_limit: plan.map(|p| p.daily_action_limit).unwrap_or(0),
remaining_today,
daily_limit,
has_ai_pack: wallet.as_ref().map(|w| w.purchased_credits_total > 0).unwrap_or(false),
credits_charged: None,
remaining_credits: None,
@ -1486,7 +1504,7 @@ async fn ai_auto_apply(
let result = sqlx::query(
r#"
INSERT INTO job_applications (job_id, applicant_user_id, cover_letter, applied_via_ai)
INSERT INTO job_applications (job_id, applicant_user_id, cover_note, applied_via_ai)
VALUES ($1, $2, $3, true)
ON CONFLICT (job_id, applicant_user_id) DO NOTHING
"#
@ -1699,6 +1717,9 @@ async fn ai_usage_status(
"purchased_credits_used": sub.purchased_credits_used,
"plan_code": plan.code,
"plan_name": plan.name,
// Expose the feature list so frontends can gate premium UI
// without making a second round-trip.
"allowed_features": plan.allowed_features,
})
}
Err(e) => {

View file

@ -17,4 +17,5 @@ jsonwebtoken = { version = "10.3", features = ["rust_crypto"] }
db = { path = "../db" }
cache = { path = "../cache" }
storage = { path = "../storage" }
wallet = { path = "../wallet" }
bytes.workspace = true

View file

@ -10,6 +10,7 @@ use serde::Deserialize;
use uuid::Uuid;
use db::models::lead_request::{CreateLeadRequestPayload, LeadRequestRepository};
use db::models::tracecoin_wallet::TracecoinWalletRepository;
use wallet::hold as tracecoin_hold;
use db::models::requirement::RequirementRepository;
use db::models::professional::{
CreatePortfolioItemPayload,
@ -110,6 +111,8 @@ pub fn shared_routes(profession_key: &'static str) -> Router<ProfessionState> {
.route("/wallet/me/ledger", get(wallet_ledger))
.route("/wallet/me/invoices", get(wallet_invoices))
.route("/wallet/me/invoices/{id}", get(wallet_invoice_detail))
.route("/wallet/me/holds", get(wallet_holds))
.route("/wallet/me/holds/{id}/release", post(wallet_release_hold))
}
// ── Handlers ──────────────────────────────────────────────────────────────────
@ -801,6 +804,55 @@ async fn wallet_invoice_detail(
}
}
/// List all holds (any status) for the authenticated professional, newest first.
/// Returns `{ "data": [...], "pagination": { "page": 1, "limit": N } }`.
async fn wallet_holds(
State(state): State<ProfessionState>,
auth: AuthUser,
Query(q): Query<PaginationQuery>,
) -> impl IntoResponse {
let limit = q.limit.unwrap_or(50).clamp(1, 200) as i64;
let page = q.page.unwrap_or(1).max(1);
match tracecoin_hold::list_for_user(&state.pool, auth.user_id, limit).await {
Ok(items) => (StatusCode::OK, Json(serde_json::json!({
"data": items,
"pagination": { "page": page, "limit": limit }
}))).into_response(),
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
}
}
/// Release an ACTIVE hold owned by the authenticated professional.
/// The tracecoins are returned to the user's available balance.
async fn wallet_release_hold(
State(state): State<ProfessionState>,
auth: AuthUser,
Path(id): Path<Uuid>,
) -> impl IntoResponse {
// Verify ownership before releasing: fetch the hold and confirm user_id matches.
match tracecoin_hold::list_for_user(&state.pool, auth.user_id, 200).await {
Ok(holds) => {
let owned = holds.iter().any(|h| h.id == id);
if !owned {
return (StatusCode::NOT_FOUND, "Hold not found or not yours").into_response();
}
}
Err(e) => return (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
}
match tracecoin_hold::release(&state.pool, id).await {
Ok(()) => (StatusCode::OK, Json(serde_json::json!({
"message": "Hold released. Tracecoins returned to your available balance."
}))).into_response(),
Err(wallet::hold::HoldError::NotFound) =>
(StatusCode::NOT_FOUND, "Hold not found").into_response(),
Err(wallet::hold::HoldError::NotActive(s)) =>
(StatusCode::CONFLICT, format!("Hold is not active (status: {s})")).into_response(),
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
}
}
async fn submit_for_verification(
State(state): State<ProfessionState>,
auth: AuthUser,

59
deny.toml Normal file
View file

@ -0,0 +1,59 @@
# cargo-deny configuration for nxtgauge-backend-rust
# https://embarkstudios.github.io/cargo-deny/
#
# For this private/proprietary workspace we only enforce:
# - advisories (CVEs / RustSec)
# - bans (forbidden crates, duplicate versions)
# License compliance is skipped — our internal crates have no license field.
[graph]
targets = []
# ── Advisories (CVEs / RustSec) ──────────────────────────────────────────────
[advisories]
version = 2
db-path = "~/.cargo/advisory-db"
db-urls = ["https://github.com/rustsec/advisory-db"]
ignore = [
# RUSTSEC-2023-0071 - rsa: Marvin Attack timing side-channel — no upstream fix available.
# Impact: potential RSA key recovery via timing. Low risk: we use RSA only for JWT
# verification (public-key ops), not for decryption. Acknowledged.
"RUSTSEC-2023-0071",
# RUSTSEC-2025-0141 - bincode: unmaintained (not a vulnerability, just no active maintainer).
# bincode is used transitively; no replacement available in our dep tree.
"RUSTSEC-2025-0141",
]
# ── Bans (duplicate deps / forbidden crates) ──────────────────────────────────
[bans]
# Warn on multiple versions of the same crate.
multiple-versions = "warn"
# Crates we never want in the dependency tree.
deny = [
# Avoid the old `time` crate (use chrono / time 0.3 instead)
{ name = "time", version = "=0.1" },
]
# ── Licenses (skipped for private workspace) ──────────────────────────────────
# We do not enforce license policy here because:
# - All our own workspace crates are proprietary and unpublished.
# - Third-party license compliance is managed separately.
[licenses]
version = 2
allow = ["MIT", "Apache-2.0", "Apache-2.0 WITH LLVM-exception", "BSD-2-Clause", "BSD-3-Clause", "ISC", "Unicode-3.0", "Unicode-DFS-2016", "CC0-1.0", "Zlib", "OpenSSL"]
confidence-threshold = 0.6
[[licenses.clarify]]
name = "ring"
expression = "MIT AND ISC AND OpenSSL"
license-files = [{ path = "LICENSE", hash = 0xbd0eed23 }]
[[licenses.exceptions]]
allow = ["LicenseRef-Private"]
crate = "auth"
# ── Sources ───────────────────────────────────────────────────────────────────
[sources]
unknown-registry = "deny"
unknown-git = "deny"
allow-registry = ["https://github.com/rust-lang/crates.io-index"]