├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── docs ├── readme.md └── supabase.md ├── readme.md └── src ├── client.rs ├── commands ├── auth.rs ├── config.rs ├── download.rs ├── fetch.rs ├── mod.rs └── sync.rs ├── database ├── mod.rs ├── models.rs └── supabase.rs ├── files.rs ├── main.rs └── utils ├── auth.rs ├── config.rs ├── date.rs ├── log.rs ├── mod.rs ├── progress.rs └── slack.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | 3 | .env 4 | 5 | .vscode -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.21.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler" 16 | version = "1.0.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 | 20 | [[package]] 21 | name = "aho-corasick" 22 | version = "0.7.18" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" 25 | dependencies = [ 26 | "memchr", 27 | ] 28 | 29 | [[package]] 30 | name = "anstream" 31 | version = "0.6.13" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" 34 | dependencies = [ 35 | "anstyle", 36 | "anstyle-parse", 37 | "anstyle-query", 38 | "anstyle-wincon", 39 | "colorchoice", 40 | "utf8parse", 41 | ] 42 | 43 | [[package]] 44 | name = "anstyle" 45 | version = "1.0.6" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" 48 | 49 | [[package]] 50 | name = "anstyle-parse" 51 | version = "0.2.3" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" 54 | dependencies = [ 55 | "utf8parse", 56 | ] 57 | 58 | [[package]] 59 | name = "anstyle-query" 60 | version = "1.0.2" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" 63 | dependencies = [ 64 | "windows-sys 0.52.0", 65 | ] 66 | 67 | [[package]] 68 | name = "anstyle-wincon" 69 | version = "3.0.2" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" 72 | dependencies = [ 73 | "anstyle", 74 | "windows-sys 0.52.0", 75 | ] 76 | 77 | [[package]] 78 | name = "async-compression" 79 | version = "0.4.9" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "4e9eabd7a98fe442131a17c316bd9349c43695e49e730c3c8e12cfb5f4da2693" 82 | dependencies = [ 83 | "flate2", 84 | "futures-core", 85 | "memchr", 86 | "pin-project-lite", 87 | "tokio", 88 | ] 89 | 90 | [[package]] 91 | name = "async-trait" 92 | version = "0.1.52" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "061a7acccaa286c011ddc30970520b98fa40e00c9d644633fb26b5fc63a265e3" 95 | dependencies = [ 96 | "proc-macro2", 97 | "quote", 98 | "syn 1.0.86", 99 | ] 100 | 101 | [[package]] 102 | name = "autocfg" 103 | version = "1.1.0" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 106 | 107 | [[package]] 108 | name = "backtrace" 109 | version = "0.3.71" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" 112 | dependencies = [ 113 | "addr2line", 114 | "cc", 115 | "cfg-if", 116 | "libc", 117 | "miniz_oxide 0.7.2", 118 | "object", 119 | "rustc-demangle", 120 | ] 121 | 122 | [[package]] 123 | name = "base64" 124 | version = "0.22.1" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 127 | 128 | [[package]] 129 | name = "bcrypt" 130 | version = "0.15.1" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | checksum = "e65938ed058ef47d92cf8b346cc76ef48984572ade631927e9937b5ffc7662c7" 133 | dependencies = [ 134 | "base64", 135 | "blowfish", 136 | "getrandom", 137 | "subtle", 138 | "zeroize", 139 | ] 140 | 141 | [[package]] 142 | name = "bitflags" 143 | version = "1.3.2" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 146 | 147 | [[package]] 148 | name = "bitflags" 149 | version = "2.5.0" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" 152 | 153 | [[package]] 154 | name = "blowfish" 155 | version = "0.9.1" 156 | source = "registry+https://github.com/rust-lang/crates.io-index" 157 | checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7" 158 | dependencies = [ 159 | "byteorder", 160 | "cipher", 161 | ] 162 | 163 | [[package]] 164 | name = "bstr" 165 | version = "0.2.17" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" 168 | dependencies = [ 169 | "lazy_static", 170 | "memchr", 171 | "regex-automata", 172 | "serde", 173 | ] 174 | 175 | [[package]] 176 | name = "bumpalo" 177 | version = "3.9.1" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" 180 | 181 | [[package]] 182 | name = "byteorder" 183 | version = "1.4.3" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 186 | 187 | [[package]] 188 | name = "bytes" 189 | version = "1.6.0" 190 | source = "registry+https://github.com/rust-lang/crates.io-index" 191 | checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" 192 | 193 | [[package]] 194 | name = "cc" 195 | version = "1.0.96" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | checksum = "065a29261d53ba54260972629f9ca6bffa69bac13cd1fed61420f7fa68b9f8bd" 198 | dependencies = [ 199 | "jobserver", 200 | "libc", 201 | "once_cell", 202 | ] 203 | 204 | [[package]] 205 | name = "cfg-if" 206 | version = "1.0.0" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 209 | 210 | [[package]] 211 | name = "chrono" 212 | version = "0.4.19" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" 215 | dependencies = [ 216 | "libc", 217 | "num-integer", 218 | "num-traits", 219 | "time 0.1.43", 220 | "winapi", 221 | ] 222 | 223 | [[package]] 224 | name = "cipher" 225 | version = "0.4.3" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | checksum = "d1873270f8f7942c191139cb8a40fd228da6c3fd2fc376d7e92d47aa14aeb59e" 228 | dependencies = [ 229 | "crypto-common", 230 | "inout", 231 | ] 232 | 233 | [[package]] 234 | name = "clap" 235 | version = "4.5.4" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" 238 | dependencies = [ 239 | "clap_builder", 240 | "clap_derive", 241 | ] 242 | 243 | [[package]] 244 | name = "clap_builder" 245 | version = "4.5.2" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" 248 | dependencies = [ 249 | "anstream", 250 | "anstyle", 251 | "clap_lex", 252 | "strsim", 253 | ] 254 | 255 | [[package]] 256 | name = "clap_derive" 257 | version = "4.5.4" 258 | source = "registry+https://github.com/rust-lang/crates.io-index" 259 | checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" 260 | dependencies = [ 261 | "heck", 262 | "proc-macro2", 263 | "quote", 264 | "syn 2.0.60", 265 | ] 266 | 267 | [[package]] 268 | name = "clap_lex" 269 | version = "0.7.0" 270 | source = "registry+https://github.com/rust-lang/crates.io-index" 271 | checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" 272 | 273 | [[package]] 274 | name = "colorchoice" 275 | version = "1.0.0" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" 278 | 279 | [[package]] 280 | name = "console" 281 | version = "0.15.0" 282 | source = "registry+https://github.com/rust-lang/crates.io-index" 283 | checksum = "a28b32d32ca44b70c3e4acd7db1babf555fa026e385fb95f18028f88848b3c31" 284 | dependencies = [ 285 | "encode_unicode", 286 | "libc", 287 | "once_cell", 288 | "regex", 289 | "terminal_size", 290 | "unicode-width", 291 | "winapi", 292 | ] 293 | 294 | [[package]] 295 | name = "cookie" 296 | version = "0.17.0" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "7efb37c3e1ccb1ff97164ad95ac1606e8ccd35b3fa0a7d99a304c7f4a428cc24" 299 | dependencies = [ 300 | "percent-encoding", 301 | "time 0.3.36", 302 | "version_check", 303 | ] 304 | 305 | [[package]] 306 | name = "cookie_store" 307 | version = "0.20.0" 308 | source = "registry+https://github.com/rust-lang/crates.io-index" 309 | checksum = "387461abbc748185c3a6e1673d826918b450b87ff22639429c694619a83b6cf6" 310 | dependencies = [ 311 | "cookie", 312 | "idna 0.3.0", 313 | "log", 314 | "publicsuffix", 315 | "serde", 316 | "serde_derive", 317 | "serde_json", 318 | "time 0.3.36", 319 | "url", 320 | ] 321 | 322 | [[package]] 323 | name = "core-foundation" 324 | version = "0.9.3" 325 | source = "registry+https://github.com/rust-lang/crates.io-index" 326 | checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" 327 | dependencies = [ 328 | "core-foundation-sys", 329 | "libc", 330 | ] 331 | 332 | [[package]] 333 | name = "core-foundation-sys" 334 | version = "0.8.3" 335 | source = "registry+https://github.com/rust-lang/crates.io-index" 336 | checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" 337 | 338 | [[package]] 339 | name = "crc32fast" 340 | version = "1.3.2" 341 | source = "registry+https://github.com/rust-lang/crates.io-index" 342 | checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" 343 | dependencies = [ 344 | "cfg-if", 345 | ] 346 | 347 | [[package]] 348 | name = "crypto-common" 349 | version = "0.1.3" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" 352 | dependencies = [ 353 | "generic-array", 354 | "typenum", 355 | ] 356 | 357 | [[package]] 358 | name = "csv" 359 | version = "1.1.6" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1" 362 | dependencies = [ 363 | "bstr", 364 | "csv-core", 365 | "itoa 0.4.8", 366 | "ryu", 367 | "serde", 368 | ] 369 | 370 | [[package]] 371 | name = "csv-core" 372 | version = "0.1.10" 373 | source = "registry+https://github.com/rust-lang/crates.io-index" 374 | checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" 375 | dependencies = [ 376 | "memchr", 377 | ] 378 | 379 | [[package]] 380 | name = "deranged" 381 | version = "0.3.11" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" 384 | dependencies = [ 385 | "powerfmt", 386 | ] 387 | 388 | [[package]] 389 | name = "dirs" 390 | version = "5.0.1" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" 393 | dependencies = [ 394 | "dirs-sys", 395 | ] 396 | 397 | [[package]] 398 | name = "dirs-sys" 399 | version = "0.4.1" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" 402 | dependencies = [ 403 | "libc", 404 | "option-ext", 405 | "redox_users", 406 | "windows-sys 0.48.0", 407 | ] 408 | 409 | [[package]] 410 | name = "dotenv" 411 | version = "0.15.0" 412 | source = "registry+https://github.com/rust-lang/crates.io-index" 413 | checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" 414 | 415 | [[package]] 416 | name = "encode_unicode" 417 | version = "0.3.6" 418 | source = "registry+https://github.com/rust-lang/crates.io-index" 419 | checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" 420 | 421 | [[package]] 422 | name = "encoding_rs" 423 | version = "0.8.30" 424 | source = "registry+https://github.com/rust-lang/crates.io-index" 425 | checksum = "7896dc8abb250ffdda33912550faa54c88ec8b998dec0b2c55ab224921ce11df" 426 | dependencies = [ 427 | "cfg-if", 428 | ] 429 | 430 | [[package]] 431 | name = "equivalent" 432 | version = "1.0.1" 433 | source = "registry+https://github.com/rust-lang/crates.io-index" 434 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 435 | 436 | [[package]] 437 | name = "fastrand" 438 | version = "1.7.0" 439 | source = "registry+https://github.com/rust-lang/crates.io-index" 440 | checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" 441 | dependencies = [ 442 | "instant", 443 | ] 444 | 445 | [[package]] 446 | name = "flate2" 447 | version = "1.0.22" 448 | source = "registry+https://github.com/rust-lang/crates.io-index" 449 | checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f" 450 | dependencies = [ 451 | "cfg-if", 452 | "crc32fast", 453 | "libc", 454 | "miniz_oxide 0.4.4", 455 | ] 456 | 457 | [[package]] 458 | name = "fnv" 459 | version = "1.0.7" 460 | source = "registry+https://github.com/rust-lang/crates.io-index" 461 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 462 | 463 | [[package]] 464 | name = "foreign-types" 465 | version = "0.3.2" 466 | source = "registry+https://github.com/rust-lang/crates.io-index" 467 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 468 | dependencies = [ 469 | "foreign-types-shared", 470 | ] 471 | 472 | [[package]] 473 | name = "foreign-types-shared" 474 | version = "0.1.1" 475 | source = "registry+https://github.com/rust-lang/crates.io-index" 476 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 477 | 478 | [[package]] 479 | name = "form_urlencoded" 480 | version = "1.2.1" 481 | source = "registry+https://github.com/rust-lang/crates.io-index" 482 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 483 | dependencies = [ 484 | "percent-encoding", 485 | ] 486 | 487 | [[package]] 488 | name = "fuchsia-cprng" 489 | version = "0.1.1" 490 | source = "registry+https://github.com/rust-lang/crates.io-index" 491 | checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" 492 | 493 | [[package]] 494 | name = "futures" 495 | version = "0.3.21" 496 | source = "registry+https://github.com/rust-lang/crates.io-index" 497 | checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" 498 | dependencies = [ 499 | "futures-channel", 500 | "futures-core", 501 | "futures-executor", 502 | "futures-io", 503 | "futures-sink", 504 | "futures-task", 505 | "futures-util", 506 | ] 507 | 508 | [[package]] 509 | name = "futures-channel" 510 | version = "0.3.21" 511 | source = "registry+https://github.com/rust-lang/crates.io-index" 512 | checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" 513 | dependencies = [ 514 | "futures-core", 515 | "futures-sink", 516 | ] 517 | 518 | [[package]] 519 | name = "futures-core" 520 | version = "0.3.21" 521 | source = "registry+https://github.com/rust-lang/crates.io-index" 522 | checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" 523 | 524 | [[package]] 525 | name = "futures-executor" 526 | version = "0.3.21" 527 | source = "registry+https://github.com/rust-lang/crates.io-index" 528 | checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" 529 | dependencies = [ 530 | "futures-core", 531 | "futures-task", 532 | "futures-util", 533 | ] 534 | 535 | [[package]] 536 | name = "futures-io" 537 | version = "0.3.21" 538 | source = "registry+https://github.com/rust-lang/crates.io-index" 539 | checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" 540 | 541 | [[package]] 542 | name = "futures-macro" 543 | version = "0.3.21" 544 | source = "registry+https://github.com/rust-lang/crates.io-index" 545 | checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" 546 | dependencies = [ 547 | "proc-macro2", 548 | "quote", 549 | "syn 1.0.86", 550 | ] 551 | 552 | [[package]] 553 | name = "futures-sink" 554 | version = "0.3.21" 555 | source = "registry+https://github.com/rust-lang/crates.io-index" 556 | checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" 557 | 558 | [[package]] 559 | name = "futures-task" 560 | version = "0.3.21" 561 | source = "registry+https://github.com/rust-lang/crates.io-index" 562 | checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" 563 | 564 | [[package]] 565 | name = "futures-util" 566 | version = "0.3.21" 567 | source = "registry+https://github.com/rust-lang/crates.io-index" 568 | checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" 569 | dependencies = [ 570 | "futures-channel", 571 | "futures-core", 572 | "futures-io", 573 | "futures-macro", 574 | "futures-sink", 575 | "futures-task", 576 | "memchr", 577 | "pin-project-lite", 578 | "pin-utils", 579 | "slab", 580 | ] 581 | 582 | [[package]] 583 | name = "gcc" 584 | version = "0.3.55" 585 | source = "registry+https://github.com/rust-lang/crates.io-index" 586 | checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" 587 | 588 | [[package]] 589 | name = "generic-array" 590 | version = "0.14.5" 591 | source = "registry+https://github.com/rust-lang/crates.io-index" 592 | checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" 593 | dependencies = [ 594 | "typenum", 595 | "version_check", 596 | ] 597 | 598 | [[package]] 599 | name = "getrandom" 600 | version = "0.2.4" 601 | source = "registry+https://github.com/rust-lang/crates.io-index" 602 | checksum = "418d37c8b1d42553c93648be529cb70f920d3baf8ef469b74b9638df426e0b4c" 603 | dependencies = [ 604 | "cfg-if", 605 | "libc", 606 | "wasi 0.10.2+wasi-snapshot-preview1", 607 | ] 608 | 609 | [[package]] 610 | name = "gimli" 611 | version = "0.28.1" 612 | source = "registry+https://github.com/rust-lang/crates.io-index" 613 | checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" 614 | 615 | [[package]] 616 | name = "git2" 617 | version = "0.18.3" 618 | source = "registry+https://github.com/rust-lang/crates.io-index" 619 | checksum = "232e6a7bfe35766bf715e55a88b39a700596c0ccfd88cd3680b4cdb40d66ef70" 620 | dependencies = [ 621 | "bitflags 2.5.0", 622 | "libc", 623 | "libgit2-sys", 624 | "log", 625 | "openssl-probe", 626 | "openssl-sys", 627 | "url", 628 | ] 629 | 630 | [[package]] 631 | name = "h2" 632 | version = "0.4.4" 633 | source = "registry+https://github.com/rust-lang/crates.io-index" 634 | checksum = "816ec7294445779408f36fe57bc5b7fc1cf59664059096c65f905c1c61f58069" 635 | dependencies = [ 636 | "bytes", 637 | "fnv", 638 | "futures-core", 639 | "futures-sink", 640 | "futures-util", 641 | "http", 642 | "indexmap", 643 | "slab", 644 | "tokio", 645 | "tokio-util", 646 | "tracing", 647 | ] 648 | 649 | [[package]] 650 | name = "hashbrown" 651 | version = "0.14.5" 652 | source = "registry+https://github.com/rust-lang/crates.io-index" 653 | checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" 654 | 655 | [[package]] 656 | name = "heck" 657 | version = "0.5.0" 658 | source = "registry+https://github.com/rust-lang/crates.io-index" 659 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 660 | 661 | [[package]] 662 | name = "hermit-abi" 663 | version = "0.1.19" 664 | source = "registry+https://github.com/rust-lang/crates.io-index" 665 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 666 | dependencies = [ 667 | "libc", 668 | ] 669 | 670 | [[package]] 671 | name = "http" 672 | version = "1.1.0" 673 | source = "registry+https://github.com/rust-lang/crates.io-index" 674 | checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" 675 | dependencies = [ 676 | "bytes", 677 | "fnv", 678 | "itoa 1.0.1", 679 | ] 680 | 681 | [[package]] 682 | name = "http-body" 683 | version = "1.0.0" 684 | source = "registry+https://github.com/rust-lang/crates.io-index" 685 | checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" 686 | dependencies = [ 687 | "bytes", 688 | "http", 689 | ] 690 | 691 | [[package]] 692 | name = "http-body-util" 693 | version = "0.1.1" 694 | source = "registry+https://github.com/rust-lang/crates.io-index" 695 | checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" 696 | dependencies = [ 697 | "bytes", 698 | "futures-core", 699 | "http", 700 | "http-body", 701 | "pin-project-lite", 702 | ] 703 | 704 | [[package]] 705 | name = "httparse" 706 | version = "1.8.0" 707 | source = "registry+https://github.com/rust-lang/crates.io-index" 708 | checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" 709 | 710 | [[package]] 711 | name = "hyper" 712 | version = "1.3.1" 713 | source = "registry+https://github.com/rust-lang/crates.io-index" 714 | checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" 715 | dependencies = [ 716 | "bytes", 717 | "futures-channel", 718 | "futures-util", 719 | "h2", 720 | "http", 721 | "http-body", 722 | "httparse", 723 | "itoa 1.0.1", 724 | "pin-project-lite", 725 | "smallvec", 726 | "tokio", 727 | "want", 728 | ] 729 | 730 | [[package]] 731 | name = "hyper-tls" 732 | version = "0.6.0" 733 | source = "registry+https://github.com/rust-lang/crates.io-index" 734 | checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" 735 | dependencies = [ 736 | "bytes", 737 | "http-body-util", 738 | "hyper", 739 | "hyper-util", 740 | "native-tls", 741 | "tokio", 742 | "tokio-native-tls", 743 | "tower-service", 744 | ] 745 | 746 | [[package]] 747 | name = "hyper-util" 748 | version = "0.1.3" 749 | source = "registry+https://github.com/rust-lang/crates.io-index" 750 | checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" 751 | dependencies = [ 752 | "bytes", 753 | "futures-channel", 754 | "futures-util", 755 | "http", 756 | "http-body", 757 | "hyper", 758 | "pin-project-lite", 759 | "socket2", 760 | "tokio", 761 | "tower", 762 | "tower-service", 763 | "tracing", 764 | ] 765 | 766 | [[package]] 767 | name = "idna" 768 | version = "0.3.0" 769 | source = "registry+https://github.com/rust-lang/crates.io-index" 770 | checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" 771 | dependencies = [ 772 | "unicode-bidi", 773 | "unicode-normalization", 774 | ] 775 | 776 | [[package]] 777 | name = "idna" 778 | version = "0.5.0" 779 | source = "registry+https://github.com/rust-lang/crates.io-index" 780 | checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" 781 | dependencies = [ 782 | "unicode-bidi", 783 | "unicode-normalization", 784 | ] 785 | 786 | [[package]] 787 | name = "indexmap" 788 | version = "2.2.6" 789 | source = "registry+https://github.com/rust-lang/crates.io-index" 790 | checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" 791 | dependencies = [ 792 | "equivalent", 793 | "hashbrown", 794 | ] 795 | 796 | [[package]] 797 | name = "indicatif" 798 | version = "0.17.8" 799 | source = "registry+https://github.com/rust-lang/crates.io-index" 800 | checksum = "763a5a8f45087d6bcea4222e7b72c291a054edf80e4ef6efd2a4979878c7bea3" 801 | dependencies = [ 802 | "console", 803 | "instant", 804 | "number_prefix", 805 | "portable-atomic", 806 | "unicode-width", 807 | ] 808 | 809 | [[package]] 810 | name = "inout" 811 | version = "0.1.2" 812 | source = "registry+https://github.com/rust-lang/crates.io-index" 813 | checksum = "9e1f03d4ab4d5dc9ec2d219f86c15d2a15fc08239d1cd3b2d6a19717c0a2f443" 814 | dependencies = [ 815 | "generic-array", 816 | ] 817 | 818 | [[package]] 819 | name = "instant" 820 | version = "0.1.12" 821 | source = "registry+https://github.com/rust-lang/crates.io-index" 822 | checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" 823 | dependencies = [ 824 | "cfg-if", 825 | ] 826 | 827 | [[package]] 828 | name = "ipnet" 829 | version = "2.3.1" 830 | source = "registry+https://github.com/rust-lang/crates.io-index" 831 | checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9" 832 | 833 | [[package]] 834 | name = "itoa" 835 | version = "0.4.8" 836 | source = "registry+https://github.com/rust-lang/crates.io-index" 837 | checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" 838 | 839 | [[package]] 840 | name = "itoa" 841 | version = "1.0.1" 842 | source = "registry+https://github.com/rust-lang/crates.io-index" 843 | checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" 844 | 845 | [[package]] 846 | name = "jobserver" 847 | version = "0.1.31" 848 | source = "registry+https://github.com/rust-lang/crates.io-index" 849 | checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" 850 | dependencies = [ 851 | "libc", 852 | ] 853 | 854 | [[package]] 855 | name = "js-sys" 856 | version = "0.3.56" 857 | source = "registry+https://github.com/rust-lang/crates.io-index" 858 | checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04" 859 | dependencies = [ 860 | "wasm-bindgen", 861 | ] 862 | 863 | [[package]] 864 | name = "lazy_static" 865 | version = "1.4.0" 866 | source = "registry+https://github.com/rust-lang/crates.io-index" 867 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 868 | 869 | [[package]] 870 | name = "libc" 871 | version = "0.2.154" 872 | source = "registry+https://github.com/rust-lang/crates.io-index" 873 | checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" 874 | 875 | [[package]] 876 | name = "libgit2-sys" 877 | version = "0.16.2+1.7.2" 878 | source = "registry+https://github.com/rust-lang/crates.io-index" 879 | checksum = "ee4126d8b4ee5c9d9ea891dd875cfdc1e9d0950437179104b183d7d8a74d24e8" 880 | dependencies = [ 881 | "cc", 882 | "libc", 883 | "libssh2-sys", 884 | "libz-sys", 885 | "openssl-sys", 886 | "pkg-config", 887 | ] 888 | 889 | [[package]] 890 | name = "libssh2-sys" 891 | version = "0.3.0" 892 | source = "registry+https://github.com/rust-lang/crates.io-index" 893 | checksum = "2dc8a030b787e2119a731f1951d6a773e2280c660f8ec4b0f5e1505a386e71ee" 894 | dependencies = [ 895 | "cc", 896 | "libc", 897 | "libz-sys", 898 | "openssl-sys", 899 | "pkg-config", 900 | "vcpkg", 901 | ] 902 | 903 | [[package]] 904 | name = "libz-sys" 905 | version = "1.1.3" 906 | source = "registry+https://github.com/rust-lang/crates.io-index" 907 | checksum = "de5435b8549c16d423ed0c03dbaafe57cf6c3344744f1242520d59c9d8ecec66" 908 | dependencies = [ 909 | "cc", 910 | "libc", 911 | "pkg-config", 912 | "vcpkg", 913 | ] 914 | 915 | [[package]] 916 | name = "lock_api" 917 | version = "0.4.6" 918 | source = "registry+https://github.com/rust-lang/crates.io-index" 919 | checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b" 920 | dependencies = [ 921 | "scopeguard", 922 | ] 923 | 924 | [[package]] 925 | name = "log" 926 | version = "0.4.21" 927 | source = "registry+https://github.com/rust-lang/crates.io-index" 928 | checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" 929 | 930 | [[package]] 931 | name = "memchr" 932 | version = "2.7.2" 933 | source = "registry+https://github.com/rust-lang/crates.io-index" 934 | checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" 935 | 936 | [[package]] 937 | name = "mime" 938 | version = "0.3.16" 939 | source = "registry+https://github.com/rust-lang/crates.io-index" 940 | checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" 941 | 942 | [[package]] 943 | name = "miniz_oxide" 944 | version = "0.4.4" 945 | source = "registry+https://github.com/rust-lang/crates.io-index" 946 | checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" 947 | dependencies = [ 948 | "adler", 949 | "autocfg", 950 | ] 951 | 952 | [[package]] 953 | name = "miniz_oxide" 954 | version = "0.7.2" 955 | source = "registry+https://github.com/rust-lang/crates.io-index" 956 | checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" 957 | dependencies = [ 958 | "adler", 959 | ] 960 | 961 | [[package]] 962 | name = "mio" 963 | version = "0.8.11" 964 | source = "registry+https://github.com/rust-lang/crates.io-index" 965 | checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" 966 | dependencies = [ 967 | "libc", 968 | "wasi 0.11.0+wasi-snapshot-preview1", 969 | "windows-sys 0.48.0", 970 | ] 971 | 972 | [[package]] 973 | name = "native-tls" 974 | version = "0.2.11" 975 | source = "registry+https://github.com/rust-lang/crates.io-index" 976 | checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" 977 | dependencies = [ 978 | "lazy_static", 979 | "libc", 980 | "log", 981 | "openssl", 982 | "openssl-probe", 983 | "openssl-sys", 984 | "schannel", 985 | "security-framework", 986 | "security-framework-sys", 987 | "tempfile", 988 | ] 989 | 990 | [[package]] 991 | name = "num-conv" 992 | version = "0.1.0" 993 | source = "registry+https://github.com/rust-lang/crates.io-index" 994 | checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" 995 | 996 | [[package]] 997 | name = "num-integer" 998 | version = "0.1.44" 999 | source = "registry+https://github.com/rust-lang/crates.io-index" 1000 | checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" 1001 | dependencies = [ 1002 | "autocfg", 1003 | "num-traits", 1004 | ] 1005 | 1006 | [[package]] 1007 | name = "num-traits" 1008 | version = "0.2.14" 1009 | source = "registry+https://github.com/rust-lang/crates.io-index" 1010 | checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" 1011 | dependencies = [ 1012 | "autocfg", 1013 | ] 1014 | 1015 | [[package]] 1016 | name = "num_cpus" 1017 | version = "1.13.1" 1018 | source = "registry+https://github.com/rust-lang/crates.io-index" 1019 | checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" 1020 | dependencies = [ 1021 | "hermit-abi", 1022 | "libc", 1023 | ] 1024 | 1025 | [[package]] 1026 | name = "number_prefix" 1027 | version = "0.4.0" 1028 | source = "registry+https://github.com/rust-lang/crates.io-index" 1029 | checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" 1030 | 1031 | [[package]] 1032 | name = "object" 1033 | version = "0.32.2" 1034 | source = "registry+https://github.com/rust-lang/crates.io-index" 1035 | checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" 1036 | dependencies = [ 1037 | "memchr", 1038 | ] 1039 | 1040 | [[package]] 1041 | name = "ogk" 1042 | version = "2.2.1" 1043 | dependencies = [ 1044 | "async-trait", 1045 | "base64", 1046 | "bcrypt", 1047 | "bytes", 1048 | "chrono", 1049 | "clap", 1050 | "console", 1051 | "csv", 1052 | "dirs", 1053 | "dotenv", 1054 | "futures", 1055 | "git2", 1056 | "indicatif", 1057 | "regex", 1058 | "reqwest", 1059 | "rust-crypto", 1060 | "serde", 1061 | "serde_json", 1062 | "tokio", 1063 | "toml", 1064 | ] 1065 | 1066 | [[package]] 1067 | name = "once_cell" 1068 | version = "1.19.0" 1069 | source = "registry+https://github.com/rust-lang/crates.io-index" 1070 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 1071 | 1072 | [[package]] 1073 | name = "openssl" 1074 | version = "0.10.38" 1075 | source = "registry+https://github.com/rust-lang/crates.io-index" 1076 | checksum = "0c7ae222234c30df141154f159066c5093ff73b63204dcda7121eb082fc56a95" 1077 | dependencies = [ 1078 | "bitflags 1.3.2", 1079 | "cfg-if", 1080 | "foreign-types", 1081 | "libc", 1082 | "once_cell", 1083 | "openssl-sys", 1084 | ] 1085 | 1086 | [[package]] 1087 | name = "openssl-probe" 1088 | version = "0.1.5" 1089 | source = "registry+https://github.com/rust-lang/crates.io-index" 1090 | checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" 1091 | 1092 | [[package]] 1093 | name = "openssl-sys" 1094 | version = "0.9.72" 1095 | source = "registry+https://github.com/rust-lang/crates.io-index" 1096 | checksum = "7e46109c383602735fa0a2e48dd2b7c892b048e1bf69e5c3b1d804b7d9c203cb" 1097 | dependencies = [ 1098 | "autocfg", 1099 | "cc", 1100 | "libc", 1101 | "pkg-config", 1102 | "vcpkg", 1103 | ] 1104 | 1105 | [[package]] 1106 | name = "option-ext" 1107 | version = "0.2.0" 1108 | source = "registry+https://github.com/rust-lang/crates.io-index" 1109 | checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" 1110 | 1111 | [[package]] 1112 | name = "parking_lot" 1113 | version = "0.12.2" 1114 | source = "registry+https://github.com/rust-lang/crates.io-index" 1115 | checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb" 1116 | dependencies = [ 1117 | "lock_api", 1118 | "parking_lot_core", 1119 | ] 1120 | 1121 | [[package]] 1122 | name = "parking_lot_core" 1123 | version = "0.9.10" 1124 | source = "registry+https://github.com/rust-lang/crates.io-index" 1125 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 1126 | dependencies = [ 1127 | "cfg-if", 1128 | "libc", 1129 | "redox_syscall 0.5.1", 1130 | "smallvec", 1131 | "windows-targets 0.52.5", 1132 | ] 1133 | 1134 | [[package]] 1135 | name = "percent-encoding" 1136 | version = "2.3.1" 1137 | source = "registry+https://github.com/rust-lang/crates.io-index" 1138 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 1139 | 1140 | [[package]] 1141 | name = "pin-project" 1142 | version = "1.1.5" 1143 | source = "registry+https://github.com/rust-lang/crates.io-index" 1144 | checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" 1145 | dependencies = [ 1146 | "pin-project-internal", 1147 | ] 1148 | 1149 | [[package]] 1150 | name = "pin-project-internal" 1151 | version = "1.1.5" 1152 | source = "registry+https://github.com/rust-lang/crates.io-index" 1153 | checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" 1154 | dependencies = [ 1155 | "proc-macro2", 1156 | "quote", 1157 | "syn 2.0.60", 1158 | ] 1159 | 1160 | [[package]] 1161 | name = "pin-project-lite" 1162 | version = "0.2.14" 1163 | source = "registry+https://github.com/rust-lang/crates.io-index" 1164 | checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" 1165 | 1166 | [[package]] 1167 | name = "pin-utils" 1168 | version = "0.1.0" 1169 | source = "registry+https://github.com/rust-lang/crates.io-index" 1170 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1171 | 1172 | [[package]] 1173 | name = "pkg-config" 1174 | version = "0.3.24" 1175 | source = "registry+https://github.com/rust-lang/crates.io-index" 1176 | checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe" 1177 | 1178 | [[package]] 1179 | name = "portable-atomic" 1180 | version = "1.6.0" 1181 | source = "registry+https://github.com/rust-lang/crates.io-index" 1182 | checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" 1183 | 1184 | [[package]] 1185 | name = "powerfmt" 1186 | version = "0.2.0" 1187 | source = "registry+https://github.com/rust-lang/crates.io-index" 1188 | checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 1189 | 1190 | [[package]] 1191 | name = "proc-macro2" 1192 | version = "1.0.81" 1193 | source = "registry+https://github.com/rust-lang/crates.io-index" 1194 | checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" 1195 | dependencies = [ 1196 | "unicode-ident", 1197 | ] 1198 | 1199 | [[package]] 1200 | name = "psl-types" 1201 | version = "2.0.11" 1202 | source = "registry+https://github.com/rust-lang/crates.io-index" 1203 | checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" 1204 | 1205 | [[package]] 1206 | name = "publicsuffix" 1207 | version = "2.2.3" 1208 | source = "registry+https://github.com/rust-lang/crates.io-index" 1209 | checksum = "96a8c1bda5ae1af7f99a2962e49df150414a43d62404644d98dd5c3a93d07457" 1210 | dependencies = [ 1211 | "idna 0.3.0", 1212 | "psl-types", 1213 | ] 1214 | 1215 | [[package]] 1216 | name = "quote" 1217 | version = "1.0.36" 1218 | source = "registry+https://github.com/rust-lang/crates.io-index" 1219 | checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 1220 | dependencies = [ 1221 | "proc-macro2", 1222 | ] 1223 | 1224 | [[package]] 1225 | name = "rand" 1226 | version = "0.3.23" 1227 | source = "registry+https://github.com/rust-lang/crates.io-index" 1228 | checksum = "64ac302d8f83c0c1974bf758f6b041c6c8ada916fbb44a609158ca8b064cc76c" 1229 | dependencies = [ 1230 | "libc", 1231 | "rand 0.4.6", 1232 | ] 1233 | 1234 | [[package]] 1235 | name = "rand" 1236 | version = "0.4.6" 1237 | source = "registry+https://github.com/rust-lang/crates.io-index" 1238 | checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" 1239 | dependencies = [ 1240 | "fuchsia-cprng", 1241 | "libc", 1242 | "rand_core 0.3.1", 1243 | "rdrand", 1244 | "winapi", 1245 | ] 1246 | 1247 | [[package]] 1248 | name = "rand_core" 1249 | version = "0.3.1" 1250 | source = "registry+https://github.com/rust-lang/crates.io-index" 1251 | checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" 1252 | dependencies = [ 1253 | "rand_core 0.4.2", 1254 | ] 1255 | 1256 | [[package]] 1257 | name = "rand_core" 1258 | version = "0.4.2" 1259 | source = "registry+https://github.com/rust-lang/crates.io-index" 1260 | checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" 1261 | 1262 | [[package]] 1263 | name = "rdrand" 1264 | version = "0.4.0" 1265 | source = "registry+https://github.com/rust-lang/crates.io-index" 1266 | checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" 1267 | dependencies = [ 1268 | "rand_core 0.3.1", 1269 | ] 1270 | 1271 | [[package]] 1272 | name = "redox_syscall" 1273 | version = "0.2.10" 1274 | source = "registry+https://github.com/rust-lang/crates.io-index" 1275 | checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" 1276 | dependencies = [ 1277 | "bitflags 1.3.2", 1278 | ] 1279 | 1280 | [[package]] 1281 | name = "redox_syscall" 1282 | version = "0.5.1" 1283 | source = "registry+https://github.com/rust-lang/crates.io-index" 1284 | checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" 1285 | dependencies = [ 1286 | "bitflags 2.5.0", 1287 | ] 1288 | 1289 | [[package]] 1290 | name = "redox_users" 1291 | version = "0.4.0" 1292 | source = "registry+https://github.com/rust-lang/crates.io-index" 1293 | checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" 1294 | dependencies = [ 1295 | "getrandom", 1296 | "redox_syscall 0.2.10", 1297 | ] 1298 | 1299 | [[package]] 1300 | name = "regex" 1301 | version = "1.5.4" 1302 | source = "registry+https://github.com/rust-lang/crates.io-index" 1303 | checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" 1304 | dependencies = [ 1305 | "aho-corasick", 1306 | "memchr", 1307 | "regex-syntax", 1308 | ] 1309 | 1310 | [[package]] 1311 | name = "regex-automata" 1312 | version = "0.1.10" 1313 | source = "registry+https://github.com/rust-lang/crates.io-index" 1314 | checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" 1315 | 1316 | [[package]] 1317 | name = "regex-syntax" 1318 | version = "0.6.25" 1319 | source = "registry+https://github.com/rust-lang/crates.io-index" 1320 | checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" 1321 | 1322 | [[package]] 1323 | name = "remove_dir_all" 1324 | version = "0.5.3" 1325 | source = "registry+https://github.com/rust-lang/crates.io-index" 1326 | checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" 1327 | dependencies = [ 1328 | "winapi", 1329 | ] 1330 | 1331 | [[package]] 1332 | name = "reqwest" 1333 | version = "0.12.4" 1334 | source = "registry+https://github.com/rust-lang/crates.io-index" 1335 | checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10" 1336 | dependencies = [ 1337 | "async-compression", 1338 | "base64", 1339 | "bytes", 1340 | "cookie", 1341 | "cookie_store", 1342 | "encoding_rs", 1343 | "futures-channel", 1344 | "futures-core", 1345 | "futures-util", 1346 | "h2", 1347 | "http", 1348 | "http-body", 1349 | "http-body-util", 1350 | "hyper", 1351 | "hyper-tls", 1352 | "hyper-util", 1353 | "ipnet", 1354 | "js-sys", 1355 | "log", 1356 | "mime", 1357 | "native-tls", 1358 | "once_cell", 1359 | "percent-encoding", 1360 | "pin-project-lite", 1361 | "rustls-pemfile", 1362 | "serde", 1363 | "serde_json", 1364 | "serde_urlencoded", 1365 | "sync_wrapper", 1366 | "system-configuration", 1367 | "tokio", 1368 | "tokio-native-tls", 1369 | "tokio-util", 1370 | "tower-service", 1371 | "url", 1372 | "wasm-bindgen", 1373 | "wasm-bindgen-futures", 1374 | "web-sys", 1375 | "winreg", 1376 | ] 1377 | 1378 | [[package]] 1379 | name = "rust-crypto" 1380 | version = "0.2.36" 1381 | source = "registry+https://github.com/rust-lang/crates.io-index" 1382 | checksum = "f76d05d3993fd5f4af9434e8e436db163a12a9d40e1a58a726f27a01dfd12a2a" 1383 | dependencies = [ 1384 | "gcc", 1385 | "libc", 1386 | "rand 0.3.23", 1387 | "rustc-serialize", 1388 | "time 0.1.43", 1389 | ] 1390 | 1391 | [[package]] 1392 | name = "rustc-demangle" 1393 | version = "0.1.23" 1394 | source = "registry+https://github.com/rust-lang/crates.io-index" 1395 | checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" 1396 | 1397 | [[package]] 1398 | name = "rustc-serialize" 1399 | version = "0.3.25" 1400 | source = "registry+https://github.com/rust-lang/crates.io-index" 1401 | checksum = "fe834bc780604f4674073badbad26d7219cadfb4a2275802db12cbae17498401" 1402 | 1403 | [[package]] 1404 | name = "rustls-pemfile" 1405 | version = "2.1.2" 1406 | source = "registry+https://github.com/rust-lang/crates.io-index" 1407 | checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" 1408 | dependencies = [ 1409 | "base64", 1410 | "rustls-pki-types", 1411 | ] 1412 | 1413 | [[package]] 1414 | name = "rustls-pki-types" 1415 | version = "1.5.0" 1416 | source = "registry+https://github.com/rust-lang/crates.io-index" 1417 | checksum = "beb461507cee2c2ff151784c52762cf4d9ff6a61f3e80968600ed24fa837fa54" 1418 | 1419 | [[package]] 1420 | name = "ryu" 1421 | version = "1.0.9" 1422 | source = "registry+https://github.com/rust-lang/crates.io-index" 1423 | checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" 1424 | 1425 | [[package]] 1426 | name = "schannel" 1427 | version = "0.1.19" 1428 | source = "registry+https://github.com/rust-lang/crates.io-index" 1429 | checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" 1430 | dependencies = [ 1431 | "lazy_static", 1432 | "winapi", 1433 | ] 1434 | 1435 | [[package]] 1436 | name = "scopeguard" 1437 | version = "1.1.0" 1438 | source = "registry+https://github.com/rust-lang/crates.io-index" 1439 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 1440 | 1441 | [[package]] 1442 | name = "security-framework" 1443 | version = "2.6.1" 1444 | source = "registry+https://github.com/rust-lang/crates.io-index" 1445 | checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc" 1446 | dependencies = [ 1447 | "bitflags 1.3.2", 1448 | "core-foundation", 1449 | "core-foundation-sys", 1450 | "libc", 1451 | "security-framework-sys", 1452 | ] 1453 | 1454 | [[package]] 1455 | name = "security-framework-sys" 1456 | version = "2.6.1" 1457 | source = "registry+https://github.com/rust-lang/crates.io-index" 1458 | checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" 1459 | dependencies = [ 1460 | "core-foundation-sys", 1461 | "libc", 1462 | ] 1463 | 1464 | [[package]] 1465 | name = "serde" 1466 | version = "1.0.199" 1467 | source = "registry+https://github.com/rust-lang/crates.io-index" 1468 | checksum = "0c9f6e76df036c77cd94996771fb40db98187f096dd0b9af39c6c6e452ba966a" 1469 | dependencies = [ 1470 | "serde_derive", 1471 | ] 1472 | 1473 | [[package]] 1474 | name = "serde_derive" 1475 | version = "1.0.199" 1476 | source = "registry+https://github.com/rust-lang/crates.io-index" 1477 | checksum = "11bd257a6541e141e42ca6d24ae26f7714887b47e89aa739099104c7e4d3b7fc" 1478 | dependencies = [ 1479 | "proc-macro2", 1480 | "quote", 1481 | "syn 2.0.60", 1482 | ] 1483 | 1484 | [[package]] 1485 | name = "serde_json" 1486 | version = "1.0.116" 1487 | source = "registry+https://github.com/rust-lang/crates.io-index" 1488 | checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" 1489 | dependencies = [ 1490 | "itoa 1.0.1", 1491 | "ryu", 1492 | "serde", 1493 | ] 1494 | 1495 | [[package]] 1496 | name = "serde_spanned" 1497 | version = "0.6.5" 1498 | source = "registry+https://github.com/rust-lang/crates.io-index" 1499 | checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" 1500 | dependencies = [ 1501 | "serde", 1502 | ] 1503 | 1504 | [[package]] 1505 | name = "serde_urlencoded" 1506 | version = "0.7.1" 1507 | source = "registry+https://github.com/rust-lang/crates.io-index" 1508 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 1509 | dependencies = [ 1510 | "form_urlencoded", 1511 | "itoa 1.0.1", 1512 | "ryu", 1513 | "serde", 1514 | ] 1515 | 1516 | [[package]] 1517 | name = "signal-hook-registry" 1518 | version = "1.4.0" 1519 | source = "registry+https://github.com/rust-lang/crates.io-index" 1520 | checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" 1521 | dependencies = [ 1522 | "libc", 1523 | ] 1524 | 1525 | [[package]] 1526 | name = "slab" 1527 | version = "0.4.5" 1528 | source = "registry+https://github.com/rust-lang/crates.io-index" 1529 | checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" 1530 | 1531 | [[package]] 1532 | name = "smallvec" 1533 | version = "1.13.2" 1534 | source = "registry+https://github.com/rust-lang/crates.io-index" 1535 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 1536 | 1537 | [[package]] 1538 | name = "socket2" 1539 | version = "0.5.7" 1540 | source = "registry+https://github.com/rust-lang/crates.io-index" 1541 | checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" 1542 | dependencies = [ 1543 | "libc", 1544 | "windows-sys 0.52.0", 1545 | ] 1546 | 1547 | [[package]] 1548 | name = "strsim" 1549 | version = "0.11.1" 1550 | source = "registry+https://github.com/rust-lang/crates.io-index" 1551 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 1552 | 1553 | [[package]] 1554 | name = "subtle" 1555 | version = "2.5.0" 1556 | source = "registry+https://github.com/rust-lang/crates.io-index" 1557 | checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" 1558 | 1559 | [[package]] 1560 | name = "syn" 1561 | version = "1.0.86" 1562 | source = "registry+https://github.com/rust-lang/crates.io-index" 1563 | checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" 1564 | dependencies = [ 1565 | "proc-macro2", 1566 | "quote", 1567 | "unicode-xid", 1568 | ] 1569 | 1570 | [[package]] 1571 | name = "syn" 1572 | version = "2.0.60" 1573 | source = "registry+https://github.com/rust-lang/crates.io-index" 1574 | checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" 1575 | dependencies = [ 1576 | "proc-macro2", 1577 | "quote", 1578 | "unicode-ident", 1579 | ] 1580 | 1581 | [[package]] 1582 | name = "sync_wrapper" 1583 | version = "0.1.2" 1584 | source = "registry+https://github.com/rust-lang/crates.io-index" 1585 | checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" 1586 | 1587 | [[package]] 1588 | name = "system-configuration" 1589 | version = "0.5.1" 1590 | source = "registry+https://github.com/rust-lang/crates.io-index" 1591 | checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" 1592 | dependencies = [ 1593 | "bitflags 1.3.2", 1594 | "core-foundation", 1595 | "system-configuration-sys", 1596 | ] 1597 | 1598 | [[package]] 1599 | name = "system-configuration-sys" 1600 | version = "0.5.0" 1601 | source = "registry+https://github.com/rust-lang/crates.io-index" 1602 | checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" 1603 | dependencies = [ 1604 | "core-foundation-sys", 1605 | "libc", 1606 | ] 1607 | 1608 | [[package]] 1609 | name = "tempfile" 1610 | version = "3.3.0" 1611 | source = "registry+https://github.com/rust-lang/crates.io-index" 1612 | checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" 1613 | dependencies = [ 1614 | "cfg-if", 1615 | "fastrand", 1616 | "libc", 1617 | "redox_syscall 0.2.10", 1618 | "remove_dir_all", 1619 | "winapi", 1620 | ] 1621 | 1622 | [[package]] 1623 | name = "terminal_size" 1624 | version = "0.1.17" 1625 | source = "registry+https://github.com/rust-lang/crates.io-index" 1626 | checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" 1627 | dependencies = [ 1628 | "libc", 1629 | "winapi", 1630 | ] 1631 | 1632 | [[package]] 1633 | name = "time" 1634 | version = "0.1.43" 1635 | source = "registry+https://github.com/rust-lang/crates.io-index" 1636 | checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" 1637 | dependencies = [ 1638 | "libc", 1639 | "winapi", 1640 | ] 1641 | 1642 | [[package]] 1643 | name = "time" 1644 | version = "0.3.36" 1645 | source = "registry+https://github.com/rust-lang/crates.io-index" 1646 | checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" 1647 | dependencies = [ 1648 | "deranged", 1649 | "itoa 1.0.1", 1650 | "num-conv", 1651 | "powerfmt", 1652 | "serde", 1653 | "time-core", 1654 | "time-macros", 1655 | ] 1656 | 1657 | [[package]] 1658 | name = "time-core" 1659 | version = "0.1.2" 1660 | source = "registry+https://github.com/rust-lang/crates.io-index" 1661 | checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" 1662 | 1663 | [[package]] 1664 | name = "time-macros" 1665 | version = "0.2.18" 1666 | source = "registry+https://github.com/rust-lang/crates.io-index" 1667 | checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" 1668 | dependencies = [ 1669 | "num-conv", 1670 | "time-core", 1671 | ] 1672 | 1673 | [[package]] 1674 | name = "tinyvec" 1675 | version = "1.5.1" 1676 | source = "registry+https://github.com/rust-lang/crates.io-index" 1677 | checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2" 1678 | dependencies = [ 1679 | "tinyvec_macros", 1680 | ] 1681 | 1682 | [[package]] 1683 | name = "tinyvec_macros" 1684 | version = "0.1.0" 1685 | source = "registry+https://github.com/rust-lang/crates.io-index" 1686 | checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" 1687 | 1688 | [[package]] 1689 | name = "tokio" 1690 | version = "1.37.0" 1691 | source = "registry+https://github.com/rust-lang/crates.io-index" 1692 | checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" 1693 | dependencies = [ 1694 | "backtrace", 1695 | "bytes", 1696 | "libc", 1697 | "mio", 1698 | "num_cpus", 1699 | "parking_lot", 1700 | "pin-project-lite", 1701 | "signal-hook-registry", 1702 | "socket2", 1703 | "tokio-macros", 1704 | "windows-sys 0.48.0", 1705 | ] 1706 | 1707 | [[package]] 1708 | name = "tokio-macros" 1709 | version = "2.2.0" 1710 | source = "registry+https://github.com/rust-lang/crates.io-index" 1711 | checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" 1712 | dependencies = [ 1713 | "proc-macro2", 1714 | "quote", 1715 | "syn 2.0.60", 1716 | ] 1717 | 1718 | [[package]] 1719 | name = "tokio-native-tls" 1720 | version = "0.3.0" 1721 | source = "registry+https://github.com/rust-lang/crates.io-index" 1722 | checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" 1723 | dependencies = [ 1724 | "native-tls", 1725 | "tokio", 1726 | ] 1727 | 1728 | [[package]] 1729 | name = "tokio-util" 1730 | version = "0.7.10" 1731 | source = "registry+https://github.com/rust-lang/crates.io-index" 1732 | checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" 1733 | dependencies = [ 1734 | "bytes", 1735 | "futures-core", 1736 | "futures-sink", 1737 | "pin-project-lite", 1738 | "tokio", 1739 | "tracing", 1740 | ] 1741 | 1742 | [[package]] 1743 | name = "toml" 1744 | version = "0.8.12" 1745 | source = "registry+https://github.com/rust-lang/crates.io-index" 1746 | checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3" 1747 | dependencies = [ 1748 | "serde", 1749 | "serde_spanned", 1750 | "toml_datetime", 1751 | "toml_edit", 1752 | ] 1753 | 1754 | [[package]] 1755 | name = "toml_datetime" 1756 | version = "0.6.5" 1757 | source = "registry+https://github.com/rust-lang/crates.io-index" 1758 | checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" 1759 | dependencies = [ 1760 | "serde", 1761 | ] 1762 | 1763 | [[package]] 1764 | name = "toml_edit" 1765 | version = "0.22.12" 1766 | source = "registry+https://github.com/rust-lang/crates.io-index" 1767 | checksum = "d3328d4f68a705b2a4498da1d580585d39a6510f98318a2cec3018a7ec61ddef" 1768 | dependencies = [ 1769 | "indexmap", 1770 | "serde", 1771 | "serde_spanned", 1772 | "toml_datetime", 1773 | "winnow", 1774 | ] 1775 | 1776 | [[package]] 1777 | name = "tower" 1778 | version = "0.4.13" 1779 | source = "registry+https://github.com/rust-lang/crates.io-index" 1780 | checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" 1781 | dependencies = [ 1782 | "futures-core", 1783 | "futures-util", 1784 | "pin-project", 1785 | "pin-project-lite", 1786 | "tokio", 1787 | "tower-layer", 1788 | "tower-service", 1789 | "tracing", 1790 | ] 1791 | 1792 | [[package]] 1793 | name = "tower-layer" 1794 | version = "0.3.2" 1795 | source = "registry+https://github.com/rust-lang/crates.io-index" 1796 | checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" 1797 | 1798 | [[package]] 1799 | name = "tower-service" 1800 | version = "0.3.1" 1801 | source = "registry+https://github.com/rust-lang/crates.io-index" 1802 | checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" 1803 | 1804 | [[package]] 1805 | name = "tracing" 1806 | version = "0.1.40" 1807 | source = "registry+https://github.com/rust-lang/crates.io-index" 1808 | checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" 1809 | dependencies = [ 1810 | "log", 1811 | "pin-project-lite", 1812 | "tracing-core", 1813 | ] 1814 | 1815 | [[package]] 1816 | name = "tracing-core" 1817 | version = "0.1.32" 1818 | source = "registry+https://github.com/rust-lang/crates.io-index" 1819 | checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" 1820 | dependencies = [ 1821 | "once_cell", 1822 | ] 1823 | 1824 | [[package]] 1825 | name = "try-lock" 1826 | version = "0.2.3" 1827 | source = "registry+https://github.com/rust-lang/crates.io-index" 1828 | checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" 1829 | 1830 | [[package]] 1831 | name = "typenum" 1832 | version = "1.15.0" 1833 | source = "registry+https://github.com/rust-lang/crates.io-index" 1834 | checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" 1835 | 1836 | [[package]] 1837 | name = "unicode-bidi" 1838 | version = "0.3.15" 1839 | source = "registry+https://github.com/rust-lang/crates.io-index" 1840 | checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" 1841 | 1842 | [[package]] 1843 | name = "unicode-ident" 1844 | version = "1.0.12" 1845 | source = "registry+https://github.com/rust-lang/crates.io-index" 1846 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 1847 | 1848 | [[package]] 1849 | name = "unicode-normalization" 1850 | version = "0.1.23" 1851 | source = "registry+https://github.com/rust-lang/crates.io-index" 1852 | checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" 1853 | dependencies = [ 1854 | "tinyvec", 1855 | ] 1856 | 1857 | [[package]] 1858 | name = "unicode-width" 1859 | version = "0.1.9" 1860 | source = "registry+https://github.com/rust-lang/crates.io-index" 1861 | checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" 1862 | 1863 | [[package]] 1864 | name = "unicode-xid" 1865 | version = "0.2.2" 1866 | source = "registry+https://github.com/rust-lang/crates.io-index" 1867 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 1868 | 1869 | [[package]] 1870 | name = "url" 1871 | version = "2.5.0" 1872 | source = "registry+https://github.com/rust-lang/crates.io-index" 1873 | checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" 1874 | dependencies = [ 1875 | "form_urlencoded", 1876 | "idna 0.5.0", 1877 | "percent-encoding", 1878 | ] 1879 | 1880 | [[package]] 1881 | name = "utf8parse" 1882 | version = "0.2.1" 1883 | source = "registry+https://github.com/rust-lang/crates.io-index" 1884 | checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" 1885 | 1886 | [[package]] 1887 | name = "vcpkg" 1888 | version = "0.2.15" 1889 | source = "registry+https://github.com/rust-lang/crates.io-index" 1890 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 1891 | 1892 | [[package]] 1893 | name = "version_check" 1894 | version = "0.9.4" 1895 | source = "registry+https://github.com/rust-lang/crates.io-index" 1896 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 1897 | 1898 | [[package]] 1899 | name = "want" 1900 | version = "0.3.0" 1901 | source = "registry+https://github.com/rust-lang/crates.io-index" 1902 | checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" 1903 | dependencies = [ 1904 | "log", 1905 | "try-lock", 1906 | ] 1907 | 1908 | [[package]] 1909 | name = "wasi" 1910 | version = "0.10.2+wasi-snapshot-preview1" 1911 | source = "registry+https://github.com/rust-lang/crates.io-index" 1912 | checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" 1913 | 1914 | [[package]] 1915 | name = "wasi" 1916 | version = "0.11.0+wasi-snapshot-preview1" 1917 | source = "registry+https://github.com/rust-lang/crates.io-index" 1918 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1919 | 1920 | [[package]] 1921 | name = "wasm-bindgen" 1922 | version = "0.2.79" 1923 | source = "registry+https://github.com/rust-lang/crates.io-index" 1924 | checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06" 1925 | dependencies = [ 1926 | "cfg-if", 1927 | "wasm-bindgen-macro", 1928 | ] 1929 | 1930 | [[package]] 1931 | name = "wasm-bindgen-backend" 1932 | version = "0.2.79" 1933 | source = "registry+https://github.com/rust-lang/crates.io-index" 1934 | checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca" 1935 | dependencies = [ 1936 | "bumpalo", 1937 | "lazy_static", 1938 | "log", 1939 | "proc-macro2", 1940 | "quote", 1941 | "syn 1.0.86", 1942 | "wasm-bindgen-shared", 1943 | ] 1944 | 1945 | [[package]] 1946 | name = "wasm-bindgen-futures" 1947 | version = "0.4.29" 1948 | source = "registry+https://github.com/rust-lang/crates.io-index" 1949 | checksum = "2eb6ec270a31b1d3c7e266b999739109abce8b6c87e4b31fcfcd788b65267395" 1950 | dependencies = [ 1951 | "cfg-if", 1952 | "js-sys", 1953 | "wasm-bindgen", 1954 | "web-sys", 1955 | ] 1956 | 1957 | [[package]] 1958 | name = "wasm-bindgen-macro" 1959 | version = "0.2.79" 1960 | source = "registry+https://github.com/rust-lang/crates.io-index" 1961 | checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01" 1962 | dependencies = [ 1963 | "quote", 1964 | "wasm-bindgen-macro-support", 1965 | ] 1966 | 1967 | [[package]] 1968 | name = "wasm-bindgen-macro-support" 1969 | version = "0.2.79" 1970 | source = "registry+https://github.com/rust-lang/crates.io-index" 1971 | checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc" 1972 | dependencies = [ 1973 | "proc-macro2", 1974 | "quote", 1975 | "syn 1.0.86", 1976 | "wasm-bindgen-backend", 1977 | "wasm-bindgen-shared", 1978 | ] 1979 | 1980 | [[package]] 1981 | name = "wasm-bindgen-shared" 1982 | version = "0.2.79" 1983 | source = "registry+https://github.com/rust-lang/crates.io-index" 1984 | checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2" 1985 | 1986 | [[package]] 1987 | name = "web-sys" 1988 | version = "0.3.56" 1989 | source = "registry+https://github.com/rust-lang/crates.io-index" 1990 | checksum = "c060b319f29dd25724f09a2ba1418f142f539b2be99fbf4d2d5a8f7330afb8eb" 1991 | dependencies = [ 1992 | "js-sys", 1993 | "wasm-bindgen", 1994 | ] 1995 | 1996 | [[package]] 1997 | name = "winapi" 1998 | version = "0.3.9" 1999 | source = "registry+https://github.com/rust-lang/crates.io-index" 2000 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 2001 | dependencies = [ 2002 | "winapi-i686-pc-windows-gnu", 2003 | "winapi-x86_64-pc-windows-gnu", 2004 | ] 2005 | 2006 | [[package]] 2007 | name = "winapi-i686-pc-windows-gnu" 2008 | version = "0.4.0" 2009 | source = "registry+https://github.com/rust-lang/crates.io-index" 2010 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 2011 | 2012 | [[package]] 2013 | name = "winapi-x86_64-pc-windows-gnu" 2014 | version = "0.4.0" 2015 | source = "registry+https://github.com/rust-lang/crates.io-index" 2016 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 2017 | 2018 | [[package]] 2019 | name = "windows-sys" 2020 | version = "0.48.0" 2021 | source = "registry+https://github.com/rust-lang/crates.io-index" 2022 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 2023 | dependencies = [ 2024 | "windows-targets 0.48.5", 2025 | ] 2026 | 2027 | [[package]] 2028 | name = "windows-sys" 2029 | version = "0.52.0" 2030 | source = "registry+https://github.com/rust-lang/crates.io-index" 2031 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 2032 | dependencies = [ 2033 | "windows-targets 0.52.5", 2034 | ] 2035 | 2036 | [[package]] 2037 | name = "windows-targets" 2038 | version = "0.48.5" 2039 | source = "registry+https://github.com/rust-lang/crates.io-index" 2040 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 2041 | dependencies = [ 2042 | "windows_aarch64_gnullvm 0.48.5", 2043 | "windows_aarch64_msvc 0.48.5", 2044 | "windows_i686_gnu 0.48.5", 2045 | "windows_i686_msvc 0.48.5", 2046 | "windows_x86_64_gnu 0.48.5", 2047 | "windows_x86_64_gnullvm 0.48.5", 2048 | "windows_x86_64_msvc 0.48.5", 2049 | ] 2050 | 2051 | [[package]] 2052 | name = "windows-targets" 2053 | version = "0.52.5" 2054 | source = "registry+https://github.com/rust-lang/crates.io-index" 2055 | checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" 2056 | dependencies = [ 2057 | "windows_aarch64_gnullvm 0.52.5", 2058 | "windows_aarch64_msvc 0.52.5", 2059 | "windows_i686_gnu 0.52.5", 2060 | "windows_i686_gnullvm", 2061 | "windows_i686_msvc 0.52.5", 2062 | "windows_x86_64_gnu 0.52.5", 2063 | "windows_x86_64_gnullvm 0.52.5", 2064 | "windows_x86_64_msvc 0.52.5", 2065 | ] 2066 | 2067 | [[package]] 2068 | name = "windows_aarch64_gnullvm" 2069 | version = "0.48.5" 2070 | source = "registry+https://github.com/rust-lang/crates.io-index" 2071 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 2072 | 2073 | [[package]] 2074 | name = "windows_aarch64_gnullvm" 2075 | version = "0.52.5" 2076 | source = "registry+https://github.com/rust-lang/crates.io-index" 2077 | checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" 2078 | 2079 | [[package]] 2080 | name = "windows_aarch64_msvc" 2081 | version = "0.48.5" 2082 | source = "registry+https://github.com/rust-lang/crates.io-index" 2083 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 2084 | 2085 | [[package]] 2086 | name = "windows_aarch64_msvc" 2087 | version = "0.52.5" 2088 | source = "registry+https://github.com/rust-lang/crates.io-index" 2089 | checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" 2090 | 2091 | [[package]] 2092 | name = "windows_i686_gnu" 2093 | version = "0.48.5" 2094 | source = "registry+https://github.com/rust-lang/crates.io-index" 2095 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 2096 | 2097 | [[package]] 2098 | name = "windows_i686_gnu" 2099 | version = "0.52.5" 2100 | source = "registry+https://github.com/rust-lang/crates.io-index" 2101 | checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" 2102 | 2103 | [[package]] 2104 | name = "windows_i686_gnullvm" 2105 | version = "0.52.5" 2106 | source = "registry+https://github.com/rust-lang/crates.io-index" 2107 | checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" 2108 | 2109 | [[package]] 2110 | name = "windows_i686_msvc" 2111 | version = "0.48.5" 2112 | source = "registry+https://github.com/rust-lang/crates.io-index" 2113 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 2114 | 2115 | [[package]] 2116 | name = "windows_i686_msvc" 2117 | version = "0.52.5" 2118 | source = "registry+https://github.com/rust-lang/crates.io-index" 2119 | checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" 2120 | 2121 | [[package]] 2122 | name = "windows_x86_64_gnu" 2123 | version = "0.48.5" 2124 | source = "registry+https://github.com/rust-lang/crates.io-index" 2125 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 2126 | 2127 | [[package]] 2128 | name = "windows_x86_64_gnu" 2129 | version = "0.52.5" 2130 | source = "registry+https://github.com/rust-lang/crates.io-index" 2131 | checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" 2132 | 2133 | [[package]] 2134 | name = "windows_x86_64_gnullvm" 2135 | version = "0.48.5" 2136 | source = "registry+https://github.com/rust-lang/crates.io-index" 2137 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 2138 | 2139 | [[package]] 2140 | name = "windows_x86_64_gnullvm" 2141 | version = "0.52.5" 2142 | source = "registry+https://github.com/rust-lang/crates.io-index" 2143 | checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" 2144 | 2145 | [[package]] 2146 | name = "windows_x86_64_msvc" 2147 | version = "0.48.5" 2148 | source = "registry+https://github.com/rust-lang/crates.io-index" 2149 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 2150 | 2151 | [[package]] 2152 | name = "windows_x86_64_msvc" 2153 | version = "0.52.5" 2154 | source = "registry+https://github.com/rust-lang/crates.io-index" 2155 | checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" 2156 | 2157 | [[package]] 2158 | name = "winnow" 2159 | version = "0.6.7" 2160 | source = "registry+https://github.com/rust-lang/crates.io-index" 2161 | checksum = "14b9415ee827af173ebb3f15f9083df5a122eb93572ec28741fb153356ea2578" 2162 | dependencies = [ 2163 | "memchr", 2164 | ] 2165 | 2166 | [[package]] 2167 | name = "winreg" 2168 | version = "0.52.0" 2169 | source = "registry+https://github.com/rust-lang/crates.io-index" 2170 | checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" 2171 | dependencies = [ 2172 | "cfg-if", 2173 | "windows-sys 0.48.0", 2174 | ] 2175 | 2176 | [[package]] 2177 | name = "zeroize" 2178 | version = "1.7.0" 2179 | source = "registry+https://github.com/rust-lang/crates.io-index" 2180 | checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" 2181 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | description = "cli tool for open.go.kr" 3 | name = "ogk" 4 | version = "2.2.1" 5 | authors = ["hoony "] 6 | edition = "2021" 7 | license-file = "LICENSE" 8 | homepage = "https://githb.com/opengirok/ogk" 9 | documentation = "https://githb.com/opengirok/ogk" 10 | repository = "https://githb.com/opengirok/ogk" 11 | readme = "README.md" 12 | 13 | [dependencies] 14 | async-trait = "0.1.52" 15 | base64 = "0.22.1" 16 | bcrypt = "0.15.1" 17 | bytes = "1.0.1" 18 | chrono = "0.4" 19 | clap = { version = "4.5.4", features = ["derive"] } 20 | csv = "1.1.6" 21 | dirs = "5.0.1" 22 | dotenv = "0.15.0" 23 | futures = "0.3" 24 | git2 = "0.18.3" 25 | reqwest = { version = "0.12.4", features = [ 26 | "blocking", 27 | "cookies", 28 | "gzip", 29 | "json", 30 | ] } 31 | rust-crypto = "0.2.36" 32 | serde = { version = "1.0.114", features = ['derive'] } 33 | serde_json = "1.0" 34 | toml = "0.8.12" 35 | tokio = { version = "1.2.0", features = ["full"] } 36 | regex = "1.5" 37 | indicatif = "0.17.8" 38 | console = "0.15" 39 | 40 | [[bin]] 41 | name = "ogk" 42 | path = "src/main.rs" 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2024 hoony 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /docs/readme.md: -------------------------------------------------------------------------------- 1 | # OGK 관련 문서 -------------------------------------------------------------------------------- /docs/supabase.md: -------------------------------------------------------------------------------- 1 | # Supabase 설정하기 2 | 3 | 1. [Supabase](https://supabase.com/)에 가입합니다. 4 | 2. `Organization`과 `Project`를 생성합니다. 5 | 3. 데이터를 저장할 `Table`을 생성합니다. 6 | 1. 좌측 메뉴 `SQL Editor`에 들어갑니다. 7 | 2. `+ New query` 버튼을 클릭합니다. 8 | 3. 아래 쿼리를 복사하여 붙여넣고 실행합니다. 9 | ```sql 10 | CREATE TABLE bills ( 11 | registration_proc_number varchar(11) PRIMARY KEY, 12 | registration_number varchar(11), 13 | group_id varchar(11), 14 | request_date varchar(11), 15 | notice_date varchar(11), 16 | open_status varchar(11), 17 | open_status_code varchar(11), 18 | open_date varchar(11), 19 | open_date_reason varchar(11), 20 | request_subject varchar(11), 21 | request_description varchar(11), 22 | result_description varchar(11), 23 | open_type varchar(11), 24 | proc_person_class varchar(11), 25 | proc_person_name varchar(11), 26 | proc_org_name varchar(11), 27 | proc_org_dept_name varchar(11), 28 | proc_org_dept_code varchar(11), 29 | proc_org_dept_phone varchar(11), 30 | transfered_org_name varchar(11), 31 | sanction_drafter_name varchar(11), 32 | sanction_drafter_class varchar(11), 33 | sanction_dcaner_name varchar(11), 34 | sanction_dcrber_class varchar(11), 35 | sanction_dcrber_name varchar(11), 36 | sanction_dcaner_class varchar(11), 37 | sanction_checker_name varchar(11), 38 | sanction_checker_class varchar(11), 39 | open_file_method varchar(11), 40 | dept_sn varchar(11), 41 | user_id varchar(11) 42 | ); 43 | ``` 44 | 45 | 4. (⚠️ 중요) 생성한 테이블에 대하여 비공개 설정을 합니다. 46 | 1. 좌측 메뉴 `Table Editor`에 들어갑니다. 47 | 2. 방금 생성한 테이블 `bills`를 클릭합니다. 48 | 3. 테이블 이름 옆에 클릭하고 나서 생긴 화살표를 클릭하여 `Edit Table`을 클릭합니다. 49 | 4. `Enable Row Level Security` 체크박스에 체크표시를 한 뒤 저장합니다. 50 | 5. Supabase `Host` 와 `API Key`를 복사하여 설정에 적용합니다. 51 | 1. 좌측 메뉴 `Settings`에 들어갑니다. 52 | 2. 하위 메뉴 `Project settings` - `API`에 들어갑니다. 53 | 3. `Configuration` - `URL`이 `Host`이고, `Project API Keys` - `service role, secret`이 `API Key` 입니다. 54 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # ogk - cli tool for [open.go.kr](https://open.go.kr) 2 | [정보공개포털](https://open.go.kr) 계정이 있고 플랫폼을 자주 사용하는 사용자라면 공식웹사이트보다 편리하게 데이터 및 파일을 관리할 수 있도록 돕기 위한 프로젝트입니다. 3 | 4 | 5 | ### 후원하기 6 | - [정보공개센터 후원하기](https://www.opengirok.or.kr/) 7 | - [ogk 기여자](https://hoony.land)에게 [커피 한 잔 사주기](https://www.buymeacoffee.com/pretty00butt) 8 | 9 | 10 | ### 설치하기 11 | : 현재 `ogk`는 [Cargo](https://doc.rust-lang.org/cargo/getting-started/installation.html)를 통해서 설치가 가능합니다. 12 | 13 | ##### `cargo` 14 | 15 | ```bash 16 | cargo install ogk 17 | ``` 18 | 19 | ### 설정하기 20 | 21 | ```bash 22 | # 1. 계정 설정 23 | # 정보공개플랫폼 계정 설정(open.go.kr에 등록된 계정이어야 합니다.) 24 | # 처음 로그인을 시도하는 정보는 이후 값으로 사용됩니다. 25 | # 한 번 로그인을 시도한 계정은 이후 다른 명령어를 시도할 때 만 입력하면 됩니다. 26 | ogk auth login --org --username --password 27 | 28 | 29 | # 2. 파일 관리 설정 30 | # 내컴퓨터에 저장할 파일 위치를 지정합니다. 31 | # 기본값: ~/.ogk/.data 32 | ogk auth files --org --local-repository /Path/to/local/repository 33 | 34 | # 3.에서 생성한 원격저장소 주소를 지정합니다. 35 | # 예: ogk auth files --org opengirok --remote-repository hoonyland/data 36 | ogk auth files --org --remote-repository 37 | 38 | 39 | # 4.에서 생성한 원격저장소 주소를 지정합니다. 40 | # 예: ogk auth files --org opengirok --remote-repository hoonyland/data 41 | ogk auth integration --org --slack-webhook-url 42 | 43 | # 5. supabase (데이터베이스) 설정 44 | # [Supabase 설정하기](docs/supabase.md) 문서를 참고하여 Supabase 설정을 먼저 완료하시길 바랍니다. 45 | # 위 설정 후 Supabase에서 발급받은 host, api key를 등록합니다. 46 | ogk config sync --supabase-host https://****.supabase.co 47 | ogk config sync --supabase-api-key **** 48 | ``` 49 | 50 | ##### 파일 관리를 위한 요구사항 51 | 52 | 1. `git` 설치 및 계정 설정 53 | 2. github ssh 인증 설정 - [공식문서](https://docs.github.com/en/authentication/connecting-to-github-with-ssh), [참조 블로그](https://devocean.sk.com/blog/techBoardDetail.do?ID=163311) 54 | 3. [원격 저장소 생성](https://github.com/new) 55 | 56 | 57 | ### 사용하기 58 | 59 | ##### 1. 조회하기 60 | 61 | - 날짜 & 페이지 단위 조회 62 | ```bash 63 | ogk fetch bills --from 2021-01-01 --to 2020-12-31 --page 1 64 | ``` 65 | 66 | ##### 2. 파일 다운로드 67 | : [설정하기](#설정하기)에서 파일관리를 위한 설정이 선행되어야 합니다. 68 | 69 | ```bash 70 | ogk download --from 2021-01-01 --to 2021-12-31 71 | ``` 72 | 73 | ##### 3. 데이터 조회 및 저장하기 74 | : [설정하기](#설정하기)에서 supabase 등록 및 설정이 선행되어야 합니다. 75 | 76 | ```bash 77 | 78 | # 1. 기본 조회 및 저장 79 | ogk sync --from 2021-01-01 --to 2021-12-31 80 | # 1. 이름으로 저장된 계정 조회 및 저장 81 | ogk sync --org opengirok --from 2021-01-01 --to 2021-12-31 82 | 83 | # 2. 현재 설정된 Supabase 데이터베이스에서 통지완료되지 않은 건들만 새로 업데이트 84 | # `--from`과 `--to` 옵션을 지정하지 않습니다. 85 | ogk sync 86 | ``` 87 | 88 | ### TroubleShooting 89 | 90 | 1. ubuntu 환경에서는 `pkg-config`, `libssl-dev` 설치가 필요합니다. 91 | ``` 92 | sudo apt-get install pkg-config libssl-dev 93 | ``` 94 | 95 | ### 외부 라이브러리 96 | - [clap](https://docs.rs/clap/3.0.0-beta.2/clap): rust cli builder 97 | - [reqwest](https://docs.rs/reqwest/0.10.10/reqwest): http client 98 | - [supabase](https://supabase.io): database as a service 99 | 100 | ### 공개 파일 목록 101 | 이 라이브러리를 사용하는 단체 혹은 개인 중 정보공개청구를 통해 받은 파일 목록을 공개한 단체 혹은 개인과 파일 목록 102 | 103 | - [정보공개센터](https://opengirok-openfiles.vercel.app/) 104 | 105 | ### 기여자 106 | 107 | [hoony](hoony.land) 108 | 109 | 110 | 111 | 112 | 113 | ### License 114 | 115 | [MIT License](LICENSE) 116 | -------------------------------------------------------------------------------- /src/client.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_snake_case)] 2 | 3 | use crate::files::{Downloadable, FileManager}; 4 | use crate::utils::auth::AuthConfig; 5 | 6 | use bytes::Bytes; 7 | use regex::Regex; 8 | use reqwest::{self, header, Error}; 9 | use std::str; 10 | 11 | const LIST_HOST: &str = "https://www.open.go.kr/rqestMlrd/rqestDtls/reqstDocSrchList.ajax"; 12 | const LOGIN_HOST: &str = "https://www.open.go.kr/com/login/memberLogin.ajax"; 13 | const DETAIL_HOST_FOR_NOT_OPENED: &str = 14 | "https://www.open.go.kr/rqestMlrd/rqestDtls/reqstDocDetail.do"; 15 | const DETAIL_HOST_FOR_OPENED: &str = 16 | "https://www.open.go.kr/rqestMlrd/rqestDtls/reqstDocDecsnNotie.do"; 17 | const DOWNLOAD_HOST: &str = "https://www.open.go.kr/util/FileDownload.do"; 18 | 19 | #[derive(serde::Deserialize, Debug)] 20 | struct CsrfTokenResponse { 21 | csrfToken: String, 22 | } 23 | 24 | #[derive(serde::Deserialize, Debug)] 25 | pub struct AuthResponseModelAndViewModelResultRtnV0 { 26 | pub accesType: String, 27 | pub addr1: String, 28 | pub addr2: String, 29 | pub age: i32, 30 | pub agent: String, 31 | pub agentInfo: String, 32 | pub apoloId: String, 33 | pub birth: String, 34 | pub birthDe: String, 35 | pub bizrNo: String, 36 | pub bizrNo1: String, 37 | pub bizrNo2: String, 38 | pub bizrNo3: String, 39 | pub changePwdYn: String, 40 | pub crt: String, 41 | } 42 | 43 | #[derive(serde::Deserialize, Debug)] 44 | pub struct AuthResponseModelAndViewModelResult { 45 | pub error_code: String, 46 | pub error_msg: String, 47 | pub mberSeCd: String, 48 | pub sysdate: String, 49 | pub today: String, 50 | } 51 | 52 | #[derive(serde::Deserialize, Debug)] 53 | pub struct AuthResponseModelAndViewModel { 54 | pub result: AuthResponseModelAndViewModelResult, 55 | } 56 | 57 | #[derive(serde::Deserialize, Debug)] 58 | pub struct AuthResponseModelAndView { 59 | pub empty: bool, 60 | pub model: AuthResponseModelAndViewModel, 61 | } 62 | 63 | #[derive(serde::Deserialize, Debug)] 64 | pub struct AuthResponse { 65 | pub modelAndView: AuthResponseModelAndView, 66 | } 67 | 68 | #[derive(serde::Deserialize, serde::Serialize, Debug)] 69 | pub struct ListVo { 70 | pub totalPage: i32, // 아이템 전체 개수 71 | } 72 | 73 | #[derive(Clone, serde::Deserialize, serde::Serialize, Debug)] 74 | pub struct DtlVo { 75 | pub deptSn: String, // 파일 여부 76 | 77 | pub clsdrResnCn: String, // *비공개내용 78 | pub clsdrResnNm: String, // *비공개제목 79 | 80 | pub chckerClsfNm: String, // 결재정보 - 검토자 이름 81 | pub chckerFnm: String, // 결재정보 - 검토자 이름 82 | pub dcrberFnm: String, // 결재정보 - 전결자 이름 83 | pub dcrberClsfNm: String, // 결재정보 - 전결자 직급/직위 84 | pub dcanerFnm: String, // 결재정보 - 대결자 이름 85 | pub dcanerClsfNm: String, // 결재정보 - 대결자 직급/직위 86 | pub drafterFnm: String, // 결재정보 - 기안자 이름 87 | pub drafterClsfNm: String, // 결재정보 - 기안자 직급/직위 88 | pub sanctnDocNo: String, // 결재정보 - 문서 번호 89 | 90 | // pub sanctnerClsfNm: String, // 결재정보 - 기안자 직위/직급 91 | // pub sanctnerFnm: String, // 결재권자 이름 92 | // pub sanctnerDt: String, // 결재일자 이름 93 | // pub sanctnerRequstDt: String, // 결재 요청 일자 94 | pub decsnCn: String, // 공개내용/이송사유 ex) 95 | pub trnsfInsttNmCn: String, // 이송 기관 96 | 97 | // pub feeRdcxptResnCd: String, // 수수료 감면 코드? 98 | // pub feeRdcxptResnNm: String, // 수수료 감면 사유? 99 | // pub feeRdcxptYn: String, // 수수료 감면 여부? 100 | // pub feeSumAmt: String, // 수수료 101 | pub opetrId: String, // *처리기관 내 ID 102 | pub opetrFnm: String, // *처리기관 처리자 이름 103 | pub opetrDeptCd: String, // *처리기관 처리과 코드 104 | pub opetrDeptNm: String, // *처리기관 처리과 이름 105 | pub opetrClsfCd: String, // *처리기관 처리자 직위/직급 코드 106 | pub opetrClsfNm: String, // *처리기관 처리자 직위/직급 107 | pub opetrCbleTelno: String, // *처리기관 처리 108 | 109 | pub othinstSmtmProcessYn: String, 110 | pub othbcDtApnResnNm: String, // *공개일시 지정 사유 ex) 수수료납부 완료후 바로 공개 111 | pub othbcOprtnDt: String, // *공개 일시 112 | // pub othbcInfoCnfirmDt: String, // *공개자료 열람 일시 113 | pub othbcSeNm: String, // 공개여부 ex) 공개 114 | pub othbcStleSeNm: String, // 공개방법 - 교부형태 ex) 전자파일 115 | pub othbcPrearngeDt: String, // *공개 일시 116 | 117 | pub recptMthSeNm: String, // 공개방법 - 교부방법 ex) 정보통신망 118 | pub recptnServerId: String, // 119 | 120 | // pub nticeCnfirmDt: String, // *결정통지 열람일시 121 | pub nticeDt: String, // *공개일시 122 | 123 | pub insttAddr: String, // 처리기관 주소 124 | pub insttRqestProcStCd: String, // 처리상태 코드 ex) 143 125 | pub insttRqestProcStNm: String, // 처리상태명 ex) 공개완료 126 | 127 | pub mberId: String, // 사용자이름 // ex) opengirok 128 | 129 | // pub procCd: String, // [empty] 130 | pub prcsInsttCd: String, // *처리기관 코드 ex) 6110000 131 | pub prcsInsttNm: String, // *처리기관 이름 short ver ex) 서울특별시 132 | pub prcsFullInsttNm: String, // *처리기관 이름 long ver 133 | pub procCn: String, // 통지 결과 상태 134 | pub procDt: String, // 통지 일자 135 | pub procRegstrNo: String, // *세부 페이지 요청에 필요한 번호 136 | pub procDeptCbleTelno: String, // *처리기관 전화번호 137 | pub procUserEmailAdres: String, // 처리자 전자우편 138 | 139 | pub rceptDt: String, // 접수일자 ex) 2020.09.12 140 | pub rqestCn: String, // 청구내용 ex) 141 | pub rqestDt: String, // 청구내용 ex) 142 | pub rqestFullInsttNm: String, // ex) 요청기관 이름 full ver. - 고용노동부 최저임금위원회 143 | pub rqestInsttCd: String, // 요청기관 코드 ex) 1492865 144 | pub rqestInsttNm: String, // ex) 요청기관 이름 short ver. - 최저임금위원회 145 | 146 | pub rqestProcRegstrNo: String, // 처리번호 * 세부 페이지 요청에 필요한 번호 147 | pub rqestRceptNo: String, // 접수번호 * 세부 페이지 요청에 필요한 번호 148 | pub rqestSj: String, // 요청 제목 ex) 최저임금 위원회 회의록 및 속기록 (JE) 149 | } 150 | 151 | #[derive(Clone, serde::Deserialize, serde::Serialize, Debug)] 152 | pub struct DntcFile { 153 | pub atchmnflByteCo: String, // '100081', 154 | pub atchmnflPrsrvNm: String, // '202007171546284220000.zip', 155 | pub csdCnvrStCd: String, // '020', 156 | pub fileAbsltCoursNm: String, // '/pidfiles/uploads/pb/dlsrinfo/', 157 | pub fileSn: String, // '1', 158 | pub fileUploadNo: String, // 'VVdXZnJWYWI5Mm5GTzlsN1dWdno0QT09', 159 | pub frstRegisterId: String, // 'MIG', 160 | pub uploadFileOrginlNm: String, // ex) '서범수 의원 요구자료 일체.zip', 161 | } 162 | 163 | #[derive(serde::Deserialize, serde::Serialize, Debug)] 164 | pub struct BillWithFiles { 165 | pub atchFileList: Option>, 166 | pub dntcFileList: Option>, 167 | pub dtlVo: DtlVo, 168 | } 169 | 170 | #[derive(serde::Deserialize, serde::Serialize, Debug)] 171 | pub struct RedirectedBillWithFiles { 172 | pub redirectUrl: String, 173 | } 174 | 175 | #[derive(Debug)] 176 | pub enum BillReturnType { 177 | BillWithFiles(BillWithFiles), 178 | None, 179 | } 180 | 181 | impl Downloadable for BillWithFiles { 182 | fn get_filename(&self, prcs_full_instt_nm: &str, orig_file_name: &str) -> String { 183 | FileManager::make_filename( 184 | &self.dtlVo.rqestProcRegstrNo.trim(), 185 | prcs_full_instt_nm, 186 | orig_file_name.trim(), 187 | ) 188 | } 189 | 190 | fn get_dirname(&self) -> String { 191 | FileManager::make_dirname(&self.dtlVo.rceptDt.trim(), &self.dtlVo.rqestSj.trim()) 192 | } 193 | } 194 | 195 | #[derive(serde::Deserialize, serde::Serialize, Debug)] 196 | pub struct Bills { 197 | pub list: Vec, 198 | pub vo: ListVo, 199 | } 200 | 201 | #[derive(Debug)] 202 | pub struct Client { 203 | pub username: String, 204 | 205 | client: reqwest::Client, 206 | scui: String, 207 | csrf_token: String, 208 | } 209 | 210 | impl Client { 211 | pub async fn new() -> Result { 212 | let mut headers = header::HeaderMap::new(); 213 | headers.insert( 214 | "Accept", 215 | "application/json, text/javascript, */*; q=0.01" 216 | .parse() 217 | .unwrap(), 218 | ); 219 | headers.insert( 220 | "Content-Type", 221 | "application/x-www-form-urlencoded; charset=UTF-8" 222 | .parse() 223 | .unwrap(), 224 | ); 225 | headers.insert("Host", "www.open.go.kr".parse().unwrap()); 226 | headers.insert("Origin", "https://www.open.go.kr".parse().unwrap()); 227 | headers.insert("Connection", "keep-alive".parse().unwrap()); 228 | headers.insert("Sec-Fetch-Site", "same-origin".parse().unwrap()); 229 | headers.insert("X-Requested-With", "XMLHttpRequest".parse().unwrap()); 230 | headers.insert( 231 | "User-Agent", 232 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:80.0) Gecko/20100101 Firefox/80.0" 233 | .parse() 234 | .unwrap(), 235 | ); 236 | 237 | let client = reqwest::ClientBuilder::new() 238 | .default_headers(headers) 239 | .cookie_store(true) 240 | .build() 241 | .unwrap(); 242 | 243 | let response = client 244 | .get("https://www.open.go.kr/com/login/memberLogin.do") 245 | .send() 246 | .await?; 247 | let text_response = response.text().await?; 248 | 249 | let regex = Regex::new(r"var result(\s+)=(\s+)(.+);").unwrap(); 250 | let mut stringified_json_result = String::from(""); 251 | for cap in regex.captures_iter(&text_response) { 252 | stringified_json_result = String::from(&cap[3]); 253 | } 254 | 255 | let csrf_token_response: CsrfTokenResponse = 256 | serde_json::from_str(&stringified_json_result).unwrap(); 257 | 258 | let scui = ""; 259 | let username: &str = ""; 260 | 261 | Ok(Client { 262 | username: username.to_owned(), 263 | client, 264 | csrf_token: csrf_token_response.csrfToken, 265 | scui: scui.to_owned(), 266 | }) 267 | } 268 | 269 | pub async fn auth( 270 | &mut self, 271 | username: &str, 272 | password: &str, 273 | ) -> Result<(), Box> { 274 | self.username = username.to_owned(); 275 | 276 | let auth: [(&str, &str); 5] = [ 277 | ("mberId", username), 278 | ("pwd", password), 279 | ("agent", "PC"), 280 | ("_csrf", &self.csrf_token), 281 | ("csrf", &self.csrf_token), 282 | ]; 283 | 284 | let response = self.client.post(LOGIN_HOST).form(&auth).send().await?; 285 | match response.json::().await { 286 | Ok(response_json) => { 287 | if response_json.modelAndView.model.result.error_msg == "로그인 완료" { 288 | let response_scui = self 289 | .client 290 | .post("https://www.open.go.kr/com/main/mainView.do") 291 | .send() 292 | .await?; 293 | let response_scui_text = response_scui.text().await?; 294 | 295 | let regex = Regex::new(r"const scui = '(.+)';").unwrap(); 296 | for cap in regex.captures_iter(&response_scui_text) { 297 | self.scui = cap[0].to_owned(); 298 | } 299 | 300 | return Ok(()); 301 | } 302 | 303 | if response_json.modelAndView.model.result.error_msg 304 | == "비밀번호를 마지막으로 변경한지 180일이 지났습니다." 305 | { 306 | let set_password: [(&str, &str); 2] = [("hash", "true"), ("scui", &self.scui)]; 307 | 308 | self.client 309 | .post("https://www.open.go.kr/com/main/mainView.do") 310 | .form(&set_password) 311 | .send() 312 | .await?; 313 | 314 | return Ok(()); 315 | } 316 | 317 | println!("{}", response_json.modelAndView.model.result.error_msg); 318 | panic!("사용자이름과 비밀번호를 확인해주세요."); 319 | } 320 | Err(e) => { 321 | println!("{}", e.to_string()); 322 | panic!("사용자이름과 비밀번호를 확인해주세요."); 323 | } 324 | } 325 | } 326 | 327 | pub async fn auth_from_storage( 328 | &mut self, 329 | org: Option<&str>, 330 | ) -> Result<(), Box> { 331 | let config = AuthConfig::load_or_new()?; 332 | 333 | match org { 334 | Some(org) => { 335 | let account = config.find_org(org).unwrap(); 336 | self.auth( 337 | &account.borrow().username, 338 | &account.borrow().get_decoded_password(), 339 | ) 340 | .await?; 341 | Ok(()) 342 | } 343 | None => { 344 | let account = config.find_org("default").unwrap(); 345 | self.auth( 346 | &account.borrow().username, 347 | &account.borrow().get_decoded_password(), 348 | ) 349 | .await?; 350 | 351 | Ok(()) 352 | } 353 | } 354 | } 355 | 356 | pub async fn download_file(&self, file: &DntcFile) -> Result { 357 | let params = &[ 358 | ("fileUploadNo", &file.fileUploadNo), 359 | ("fileSn", &file.fileSn), 360 | ]; 361 | 362 | self.client 363 | .post(DOWNLOAD_HOST) 364 | .form(params) 365 | .send() 366 | .await? 367 | .bytes() 368 | .await 369 | } 370 | 371 | pub async fn fetch_a_bill( 372 | &self, 373 | registration_proc_number: &str, 374 | open_status_code: &str, 375 | dept_sn: &str, 376 | ) -> Result> { 377 | let host = match open_status_code { 378 | "141" | "143" | "1411" | "1413" | "1415" | "1421" | "163" | "165" | "1861" => { 379 | DETAIL_HOST_FOR_OPENED 380 | } 381 | _ => DETAIL_HOST_FOR_NOT_OPENED, 382 | }; 383 | 384 | let params: [(&str, &str); 8] = [ 385 | ("rqestRceptNo", ""), 386 | ("rqestProcRegstrNo", registration_proc_number), 387 | ("procRegstrNo", registration_proc_number), 388 | ("insttRqestProcStCd", open_status_code), 389 | ("deptSn", &dept_sn), 390 | ("hash", "true"), 391 | ("multiDeptProcYn", "N"), 392 | ("scui", &self.scui), 393 | ]; 394 | 395 | let response = self.post(host, ¶ms).await?; 396 | let text_response = response.text().await?; 397 | 398 | let regex = Regex::new(r"var result(\s+)=(\s+)(.+);").unwrap(); 399 | let mut stringified_json_result = String::from(""); 400 | for cap in regex.captures_iter(&text_response) { 401 | stringified_json_result = String::from(&cap[3]); 402 | } 403 | 404 | if stringified_json_result != "" { 405 | match serde_json::from_str(&stringified_json_result) { 406 | Ok(result) => { 407 | return Ok(BillReturnType::BillWithFiles(result)); 408 | } 409 | 410 | /* 411 | * 특정한 조건에 따라 요청 host가 틀렸을 수 있으니 412 | * 다른 host에서 한번 더 요청해본다. 413 | */ 414 | Err(_) => { 415 | let host = match host { 416 | DETAIL_HOST_FOR_NOT_OPENED => DETAIL_HOST_FOR_OPENED, 417 | _ => DETAIL_HOST_FOR_NOT_OPENED, 418 | }; 419 | 420 | let params: [(&str, &str); 8] = [ 421 | ("rqestRceptNo", ""), 422 | ("rqestProcRegstrNo", registration_proc_number), 423 | ("procRegstrNo", registration_proc_number), 424 | ("insttRqestProcStCd", open_status_code), 425 | ("deptSn", &dept_sn), 426 | ("hash", "true"), 427 | ("multiDeptProcYn", "N"), 428 | ("scui", &self.scui), 429 | ]; 430 | 431 | let response = self.post(host, ¶ms).await?; 432 | let text_response = response.text().await?; 433 | 434 | let regex = Regex::new(r"var result(\s+)=(\s+)(.+);").unwrap(); 435 | let mut stringified_json_result = String::from(""); 436 | for cap in regex.captures_iter(&text_response) { 437 | stringified_json_result = String::from(&cap[3]); 438 | } 439 | 440 | if stringified_json_result != "" { 441 | match serde_json::from_str(&stringified_json_result) { 442 | Ok(result) => { 443 | return Ok(BillReturnType::BillWithFiles(result)); 444 | } 445 | Err(_) => { 446 | return Ok(BillReturnType::None); 447 | } 448 | }; 449 | } else { 450 | return Ok(BillReturnType::None); 451 | } 452 | } 453 | }; 454 | } else { 455 | return Ok(BillReturnType::None); 456 | } 457 | } 458 | 459 | pub async fn fetch_bills( 460 | &self, 461 | page: &i32, 462 | from_date: &str, 463 | to_date: &str, 464 | page_count: &i32, 465 | ) -> Result { 466 | let params: [(&str, &str); 11] = [ 467 | ("stRceptDt", from_date), 468 | ("edRceptDt", to_date), 469 | ("viewPage", &page.to_string()), 470 | ("totalPage", "0"), 471 | ("selRowPage", &page_count.to_string()), 472 | ("rowPage", &page_count.to_string()), 473 | ("sort", "rqestDtList"), 474 | ("searchYn", "Y"), 475 | ("moveStatus", "L"), 476 | ("chkDate", "nonClass"), 477 | ("scui", &self.scui), 478 | ]; 479 | 480 | let response = self.client.post(LIST_HOST).form(¶ms).send().await?; 481 | response.json::().await 482 | } 483 | 484 | pub async fn post(&self, url: &str, form: &[(&str, &str)]) -> Result { 485 | self.client.post(url).form(form).send().await 486 | } 487 | } 488 | -------------------------------------------------------------------------------- /src/commands/auth.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | 3 | use crate::client; 4 | use crate::utils::auth::{AuthConfig, AuthUser}; 5 | // use crate::utils::slack; 6 | use clap::Subcommand; 7 | 8 | #[derive(Subcommand)] 9 | #[clap(about = "(required) Authenticate for open.go.kr", author, long_about = None, version)] 10 | pub enum Commands { 11 | #[clap(about = "Login on open.go.kr with a valid account")] 12 | Login { 13 | #[clap(long = "org")] 14 | org: String, 15 | 16 | #[clap(short = 'u', long = "username")] 17 | username: Option, 18 | 19 | #[clap(short = 'p', long = "password")] 20 | password: Option, 21 | }, 22 | #[clap(about = "list of accounts stored before")] 23 | List {}, 24 | #[clap(about = "Configuration to manage files")] 25 | Files { 26 | #[clap(long = "org")] 27 | org: String, 28 | 29 | #[clap(short = 'r', long = "remote")] 30 | remote_repository: Option, 31 | 32 | #[clap(short = 'l', long = "local")] 33 | local_repository: Option, 34 | }, 35 | #[clap(about = "Configuration for integrations")] 36 | Integration { 37 | #[clap(long = "org")] 38 | org: String, 39 | #[clap(long = "slack-webhook-url")] 40 | slack_webhook_url: Option, 41 | }, 42 | } 43 | 44 | async fn list() -> Result<(), Box> { 45 | let auth_config = AuthConfig::load().unwrap(); 46 | println!("{:?}", auth_config); 47 | 48 | Ok(()) 49 | } 50 | 51 | async fn login_with_username( 52 | org: &str, 53 | username: &str, 54 | password: &str, 55 | ) -> Result<(), Box> { 56 | let mut client = client::Client::new().await?; 57 | let _ = client.auth(username, password).await?; 58 | 59 | let config = AuthConfig::load_or_new().unwrap(); 60 | let _ = config.add_account(org, username, password); 61 | Ok(()) 62 | } 63 | 64 | async fn login_with_auth_user( 65 | auth_config: &RefCell, 66 | ) -> Result<(), Box> { 67 | let mut client = client::Client::new().await?; 68 | let _ = client 69 | .auth( 70 | &auth_config.borrow().username, 71 | &auth_config.borrow().get_decoded_password(), 72 | ) 73 | .await?; 74 | 75 | Ok(()) 76 | } 77 | 78 | async fn set_remote_repository_path( 79 | org: &str, 80 | remote_repository: &str, 81 | ) -> Result<(), Box> { 82 | let config = AuthConfig::load_or_new().unwrap(); 83 | let _ = config.set_remote_repository_path(org, remote_repository); 84 | Ok(()) 85 | } 86 | 87 | async fn set_local_repository_path( 88 | org: &str, 89 | local_repository: &str, 90 | ) -> Result<(), Box> { 91 | let config = AuthConfig::load_or_new().unwrap(); 92 | let _ = config.set_local_repository_path(org, local_repository); 93 | Ok(()) 94 | } 95 | 96 | pub async fn run(args: &Commands) -> Result<(), Box> { 97 | match args { 98 | Commands::Login { 99 | org, 100 | username, 101 | password, 102 | } => { 103 | let auth_config = AuthConfig::load_or_new().unwrap(); 104 | match auth_config.find_org(org) { 105 | Some(auth_user) => { 106 | if username.is_none() || password.is_none() { 107 | let _result = login_with_auth_user(auth_user.to_owned()).await; 108 | return Ok(()); 109 | } else { 110 | let _username = username.as_ref().expect("username is required"); 111 | let _password = password.as_ref().expect("password is required"); 112 | let _result = login_with_username(&org, &_username, &_password).await; 113 | return Ok(()); 114 | } 115 | } 116 | None => { 117 | if username.is_none() || password.is_none() { 118 | println!( 119 | "이전에 저장된 로그인 정보가 없는 조직명입니다. 로그인 정보를 입력해주세요." 120 | ); 121 | return Ok(()); 122 | } 123 | let _username = username.as_ref().expect("username is required"); 124 | let _password = password.as_ref().expect("password is required"); 125 | let _result = login_with_username(&org, &_username, &_password).await; 126 | } 127 | } 128 | } 129 | Commands::List {} => { 130 | let _result = list().await; 131 | } 132 | Commands::Files { 133 | org, 134 | remote_repository, 135 | local_repository, 136 | } => { 137 | match remote_repository { 138 | Some(remote_repository) => { 139 | let _result = set_remote_repository_path(org, remote_repository).await; 140 | } 141 | None => { 142 | // println!("remote_repository is required"); 143 | } 144 | } 145 | 146 | match local_repository { 147 | Some(local_repository) => { 148 | let _result = set_local_repository_path(org, local_repository).await; 149 | } 150 | None => { 151 | // println!("remote_repository is required"); 152 | } 153 | } 154 | } 155 | Commands::Integration { 156 | org, 157 | slack_webhook_url, 158 | } => { 159 | let auth_config = AuthConfig::load_or_new().unwrap(); 160 | let url = slack_webhook_url.as_ref().unwrap(); 161 | auth_config.set_slack_webhook_url(org, url); 162 | } 163 | } 164 | 165 | Ok(()) 166 | } 167 | -------------------------------------------------------------------------------- /src/commands/config.rs: -------------------------------------------------------------------------------- 1 | use crate::utils::config::Config; 2 | use clap::Subcommand; 3 | use std::error::Error; 4 | 5 | #[derive(Subcommand)] 6 | #[clap(about = "Configurations", author, long_about = None, version)] 7 | pub enum Commands { 8 | #[clap(about = "Display all config list")] 9 | List, 10 | #[clap(about = "Configuration to manage files")] 11 | Files { 12 | #[clap(long = "local-repository", required = false)] 13 | local_repository: Option, 14 | #[clap(long = "remote-repository", required = false)] 15 | remote_repository: Option, 16 | }, 17 | #[clap(about = "Configuration to sync data")] 18 | Sync { 19 | #[clap(long = "supabase-host", required = false)] 20 | supabase_host: Option, 21 | #[clap(long = "supabase-api-key", required = false)] 22 | supabase_api_key: Option, 23 | }, 24 | #[clap(about = "Configuration for integration")] 25 | Integration { 26 | #[clap(long = "slack-webhook-url", required = false)] 27 | slack_webhook_url: Option, 28 | }, 29 | } 30 | 31 | pub async fn run(args: &Commands) -> Result<(), Box> { 32 | match args { 33 | Commands::List => { 34 | let config = Config::load_or_new()?; 35 | println!("{}", config); 36 | } 37 | Commands::Sync { 38 | supabase_api_key, 39 | supabase_host, 40 | } => { 41 | let mut config = Config::load_or_new()?; 42 | 43 | match supabase_api_key { 44 | Some(rr) => { 45 | config.supabase_api_key = Some(rr.to_string()); 46 | } 47 | None => {} 48 | } 49 | 50 | match supabase_host { 51 | Some(lr) => { 52 | config.supabase_host = Some(lr.to_string()); 53 | } 54 | None => {} 55 | } 56 | 57 | config.save()?; 58 | } 59 | Commands::Files { 60 | local_repository, 61 | remote_repository, 62 | } => { 63 | let mut config = Config::load_or_new()?; 64 | 65 | match remote_repository { 66 | Some(rr) => { 67 | config.remote_file_repository = Some(rr.to_string()); 68 | } 69 | None => {} 70 | } 71 | 72 | match local_repository { 73 | Some(lr) => { 74 | config.local_file_repository = Some(lr.to_string()); 75 | } 76 | None => {} 77 | } 78 | 79 | config.save()?; 80 | } 81 | Commands::Integration { slack_webhook_url } => { 82 | let mut config = Config::load_or_new()?; 83 | 84 | match slack_webhook_url { 85 | Some(rr) => { 86 | config.slack_webhook_url = Some(rr.to_string()); 87 | } 88 | None => {} 89 | } 90 | 91 | config.save()?; 92 | } 93 | } 94 | 95 | Ok(()) 96 | } 97 | -------------------------------------------------------------------------------- /src/commands/download.rs: -------------------------------------------------------------------------------- 1 | use crate::client::{self, BillReturnType, DntcFile}; 2 | use crate::files::FileManager; 3 | use crate::utils::auth::AuthConfig; 4 | use crate::utils::{date, log, progress}; 5 | use chrono::prelude::*; 6 | use clap::Args; 7 | use indicatif::{HumanDuration, ProgressBar}; 8 | use std::error::Error; 9 | use std::time::Instant; 10 | 11 | #[derive(Args, Debug)] 12 | pub struct Commands { 13 | #[clap(long = "from", required = false)] 14 | from: Option, 15 | #[clap(long = "to", required = false)] 16 | to: Option, 17 | #[clap(long = "with-slack-notification", required = false)] 18 | with_slack: Option, 19 | #[clap(long = "org", required = false)] 20 | org: Option, 21 | } 22 | 23 | pub async fn run(args: &Commands) -> Result<(), Box> { 24 | let auth_config = AuthConfig::load_or_new().unwrap(); 25 | let org = args.org.clone().unwrap_or(String::from("default")); 26 | let auth_user = &auth_config.find_org(&org).unwrap().borrow(); 27 | let from_date = match &args.from { 28 | Some(date) => date.to_owned(), 29 | None => date::KstDateTime::from(Utc::now()).format(Some("%Y-%m-%d")), 30 | }; 31 | let to_date = match &args.to { 32 | Some(td) => td.to_owned(), 33 | None => date::KstDateTime::from(Utc::now()).format(Some("%Y-%m-%d")), 34 | }; 35 | 36 | let mut print_type = log::PrintType::DEFAULT; 37 | let _with_slack = &args.with_slack.unwrap_or_default(); 38 | if *_with_slack == true { 39 | print_type = log::PrintType::SLACK; 40 | } 41 | 42 | let started = Instant::now(); 43 | let mut client = client::Client::new().await?; 44 | client.auth_from_storage(args.org.as_deref()).await?; 45 | 46 | let init_page = 1 as i32; 47 | let init_count = 1 as i32; 48 | 49 | log::print( 50 | &format!( 51 | "[{}] DOWNLOAD [1/5] {}{} ~ {} 기간 동안의 청구 내역을 조회합니다.", 52 | client.username, 53 | progress::LOOKING_GLASS, 54 | &from_date, 55 | &to_date 56 | ), 57 | &log::PrintType::DEFAULT, 58 | ) 59 | .await; 60 | 61 | let response = client 62 | .fetch_bills(&init_page, &from_date, &to_date, &init_count) 63 | .await?; 64 | 65 | let fm = FileManager::new(auth_user).await.unwrap(); 66 | let total_count = &response.vo.totalPage; 67 | 68 | log::print( 69 | &format!( 70 | "[{}] DOWNLOAD [2/5] {}다운로드 받기 전 원격 저장소 최신 정보를 확인합니다.", 71 | client.username, 72 | progress::HAND_WITH_EYE, 73 | ), 74 | &print_type, 75 | ) 76 | .await; 77 | 78 | fm.sync_with_remote().await; 79 | 80 | log::print( 81 | &format!( 82 | "[{}] DOWNLOAD [3/5] {}청구 내역 {}건 중 공개된 파일을 찾아 다운로드 합니다.", 83 | client.username, 84 | progress::DISK, 85 | total_count 86 | ), 87 | &print_type, 88 | ) 89 | .await; 90 | 91 | let pb = ProgressBar::new(*total_count as u64); 92 | let mut downloaded_files: Vec = vec![]; 93 | 94 | match client 95 | .fetch_bills(&init_page, &from_date, &to_date, total_count) 96 | .await 97 | { 98 | Ok(response) => { 99 | for bill in &response.list { 100 | pb.inc(1); 101 | 102 | let _response_bill = client 103 | .fetch_a_bill( 104 | &bill.rqestProcRegstrNo, 105 | &bill.insttRqestProcStCd, 106 | &bill.deptSn, 107 | ) 108 | .await?; 109 | match _response_bill { 110 | BillReturnType::BillWithFiles(response) => { 111 | let mut _result = fm 112 | .download(auth_user, &client, &response, bill) 113 | .await 114 | .unwrap() 115 | .unwrap_or_default(); 116 | downloaded_files.append(&mut _result); 117 | } 118 | _ => {} 119 | }; 120 | } 121 | 122 | pb.finish_and_clear(); 123 | 124 | log::print( 125 | &format!( 126 | "[{}] DOWNLOAD [4/5] {}다운로드한 총 {}개의 파일을 원격 저장소에 저장합니다.", 127 | client.username, 128 | progress::WRITE, 129 | downloaded_files.len() 130 | ), 131 | &print_type, 132 | ) 133 | .await; 134 | 135 | if downloaded_files.len() > 0 { 136 | let _result2 = fm.upload().await; 137 | } 138 | } 139 | Err(e) => { 140 | panic!("{}", e); 141 | } 142 | }; 143 | 144 | let downloaded_file_names = downloaded_files 145 | .iter() 146 | .map(|d| format!("- {}", d.uploadFileOrginlNm)) 147 | .collect::>() 148 | .join("\n"); 149 | 150 | log::print( 151 | &format!( 152 | "[{}] DOWNLOAD [5/5] {} 다운로드 및 원격 저장소 업로드 완료! - {}\n{}", 153 | client.username, 154 | progress::SPARKLE, 155 | HumanDuration(started.elapsed()), 156 | &downloaded_file_names 157 | ), 158 | &print_type, 159 | ) 160 | .await; 161 | 162 | Ok(()) 163 | } 164 | -------------------------------------------------------------------------------- /src/commands/fetch.rs: -------------------------------------------------------------------------------- 1 | use crate::client; 2 | use crate::utils::date; 3 | use chrono::prelude::*; 4 | use clap::Subcommand; 5 | use std::error::Error; 6 | 7 | #[derive(Subcommand)] 8 | #[clap(about = "Fetch query to open.go.kr", author, long_about = None, version)] 9 | pub enum Commands { 10 | #[clap(about = "Fetch bills with date range")] 11 | Bills { 12 | #[clap(long = "from", required = false)] 13 | from: Option, 14 | #[clap(long = "page", required = false)] 15 | page: Option, 16 | #[clap(long = "to", required = false)] 17 | to: Option, 18 | #[clap(long = "page-size", required = false)] 19 | page_size: Option, 20 | #[clap(long = "org", required = false)] 21 | org: Option, 22 | }, 23 | } 24 | 25 | async fn fetch_bills( 26 | client: &client::Client, 27 | page: &i32, 28 | from_date: &str, 29 | to_date: &str, 30 | page_size: &i32, 31 | ) -> Result<(), Box> { 32 | let response = client 33 | .fetch_bills(page, from_date, to_date, page_size) 34 | .await?; 35 | 36 | let pretty_response = serde_json::to_string_pretty(&response)?; 37 | println!("{}", pretty_response); 38 | 39 | Ok(()) 40 | } 41 | 42 | pub async fn run(args: &Commands) -> Result<(), Box> { 43 | match args { 44 | Commands::Bills { 45 | from, 46 | page, 47 | page_size, 48 | to, 49 | org, 50 | } => { 51 | let from_date = match from { 52 | Some(date) => date.to_owned(), 53 | None => date::KstDateTime::from(Utc::now()).format(Some("%Y-%m-%d")), 54 | }; 55 | let to_date = match to { 56 | Some(td) => td.to_owned(), 57 | None => date::KstDateTime::from(Utc::now()).format(Some("%Y-%m-%d")), 58 | }; 59 | 60 | let _page = match page { 61 | Some(p) => p.to_owned(), 62 | None => 1 as i32, 63 | }; 64 | 65 | let _page_size = match page_size { 66 | Some(ps) => ps.to_owned(), 67 | None => 10 as i32, 68 | }; 69 | 70 | let mut client = client::Client::new().await?; 71 | client.auth_from_storage(org.as_deref()).await?; 72 | 73 | let _result = fetch_bills(&client, &_page, &from_date, &to_date, &_page_size).await; 74 | } 75 | } 76 | 77 | Ok(()) 78 | } 79 | -------------------------------------------------------------------------------- /src/commands/mod.rs: -------------------------------------------------------------------------------- 1 | use clap::Subcommand; 2 | use std::error::Error; 3 | 4 | pub mod auth; 5 | pub mod config; 6 | pub mod download; 7 | pub mod fetch; 8 | pub mod sync; 9 | 10 | #[derive(Subcommand)] 11 | pub enum Commands { 12 | #[clap(subcommand)] 13 | Auth(auth::Commands), 14 | #[clap(subcommand)] 15 | Config(config::Commands), 16 | #[clap(about = "(config required) Download files which are open on open.go.kr", author, long_about = None, version)] 17 | Download(download::Commands), 18 | #[clap(subcommand)] 19 | Fetch(fetch::Commands), 20 | #[clap(about = "Syncronize data on open.go.kr with Supabase database", author, long_about = None, version)] 21 | Sync(sync::Commands), 22 | } 23 | 24 | pub async fn run(args: &Commands) -> Result<(), Box> { 25 | match args { 26 | Commands::Auth(subcommands) => { 27 | let _result = auth::run(subcommands).await; 28 | } 29 | Commands::Config(subcommands) => { 30 | let _result = config::run(subcommands).await; 31 | } 32 | Commands::Download(subcommands) => { 33 | let _result = download::run(subcommands).await; 34 | } 35 | Commands::Fetch(subcommands) => { 36 | let _result = fetch::run(subcommands).await; 37 | } 38 | Commands::Sync(args) => { 39 | let _result = sync::run(args).await; 40 | } 41 | } 42 | 43 | Ok(()) 44 | } 45 | -------------------------------------------------------------------------------- /src/commands/sync.rs: -------------------------------------------------------------------------------- 1 | use crate::client::{self, BillReturnType}; 2 | use crate::database::supabase::Supabase; 3 | use crate::database::{create_bills, find_bills}; 4 | use crate::utils::date; 5 | use crate::utils::log; 6 | use crate::utils::progress; 7 | use chrono::{Duration, Utc}; 8 | use clap::Args; 9 | use futures::future::join_all; 10 | use indicatif::{HumanDuration, ProgressBar}; 11 | use std::error::Error; 12 | use std::time::Instant; 13 | 14 | #[derive(Args, Debug)] 15 | pub struct Commands { 16 | #[clap(long = "from", required = false)] 17 | from: Option, 18 | #[clap(long = "to", required = false)] 19 | to: Option, 20 | #[clap(long = "with-slack-notification", required = false)] 21 | with_slack: Option, 22 | #[clap(long = "org", required = false)] 23 | org: Option, 24 | } 25 | 26 | pub async fn run(args: &Commands) -> Result<(), Box> { 27 | let mut print_type = log::PrintType::DEFAULT; 28 | 29 | let _with_slack = &args.with_slack.unwrap_or_default(); 30 | if *_with_slack == true { 31 | print_type = log::PrintType::SLACK; 32 | } 33 | 34 | let mut client = client::Client::new().await?; 35 | client.auth_from_storage(args.org.as_deref()).await?; 36 | 37 | let started = Instant::now(); 38 | let init_page = 1 as i32; 39 | let init_count = 1 as i32; 40 | 41 | let mut date_from: String = args.from.as_ref().unwrap_or(&"".to_string()).to_string(); 42 | let mut date_to: String = args.to.as_ref().unwrap_or(&"".to_string()).to_string(); 43 | 44 | // TODO: 45 | if date_from == "" && date_to == "" { 46 | log::print( 47 | &format!( 48 | "[{}] SYNC [1/3] {}SUPABASE 데이터베이스에 저장된 청구건들 중 아직 통지완료되지 않은 건들을 조회합니다.", 49 | client.username, 50 | progress::LOOKING_GLASS, 51 | ), 52 | &log::PrintType::DEFAULT, 53 | ) 54 | .await; 55 | 56 | let supabase_client = Supabase::new(); 57 | let mut bills: Vec = vec![]; 58 | let bill_rows = find_bills( 59 | &supabase_client, 60 | "open_status_code=not.in.(\"141\",\"143\",\"151\",\"1411\",\"1415\"))", 61 | ) 62 | .await?; 63 | let pb = ProgressBar::new(bill_rows.len() as u64); 64 | 65 | log::print( 66 | &format!( 67 | "[{}] SYNC [2/3] {}총 {}건의 각 청구건의 최신 통지 상태를 조회합니다.", 68 | client.username, 69 | progress::TRUCK, 70 | &bill_rows.len(), 71 | ), 72 | &log::PrintType::DEFAULT, 73 | ) 74 | .await; 75 | 76 | for bill in bill_rows { 77 | pb.inc(1); 78 | let bill_response = client 79 | .fetch_a_bill( 80 | &bill.registration_proc_number, 81 | &bill.open_status_code.unwrap_or("".to_string()), 82 | &bill.dept_sn.unwrap_or("1".to_string()), 83 | ) 84 | .await; 85 | 86 | match bill_response { 87 | Ok(b) => { 88 | match b { 89 | BillReturnType::BillWithFiles(res) => { 90 | bills.push(res.dtlVo); 91 | } 92 | _ => {} 93 | }; 94 | } 95 | Err(_) => {} 96 | } 97 | } 98 | 99 | pb.finish_and_clear(); 100 | log::print( 101 | &format!( 102 | "[{}] SYNC [3/3] {}조회한 내역을 데이터베이스에 저장합니다.", 103 | client.username, 104 | progress::DISK, 105 | ), 106 | &print_type, 107 | ) 108 | .await; 109 | 110 | let _result = create_bills(&supabase_client, &bills).await; 111 | 112 | return Ok(()); 113 | } 114 | 115 | if date_from == "" { 116 | date_from = 117 | date::KstDateTime::from(Utc::now() - Duration::days(1)).format(Some("%Y-%m-%d")); 118 | }; 119 | 120 | if date_to == "" { 121 | date_to = date::KstDateTime::from(Utc::now()).format(Some("%Y-%m-%d")); 122 | }; 123 | 124 | log::print( 125 | &format!( 126 | "[{}] SYNC [1/3] {}{}~{} 청구 내역을 확인합니다.", 127 | client.username, 128 | progress::LOOKING_GLASS, 129 | date_from, 130 | date_to, 131 | ), 132 | &print_type, 133 | ) 134 | .await; 135 | 136 | let response = match client 137 | .fetch_bills(&init_page, &date_from, &date_to, &init_count) 138 | .await 139 | { 140 | Ok(_response) => _response, 141 | Err(e) => { 142 | eprintln!("{}", e); 143 | panic!(); 144 | } 145 | }; 146 | 147 | let total_count = &response.vo.totalPage; 148 | let pb = ProgressBar::new(*total_count as u64); 149 | 150 | log::print( 151 | &format!( 152 | "[{}] SYNC [2/3] {}청구 내역 {}건을 조회합니다.", 153 | client.username, 154 | progress::TRUCK, 155 | total_count 156 | ), 157 | &print_type, 158 | ) 159 | .await; 160 | 161 | match client 162 | .fetch_bills(&init_page, &date_from, &date_to, total_count) 163 | .await 164 | { 165 | Ok(response) => { 166 | let mut bills: Vec = vec![]; 167 | let supabase_client = Supabase::new(); 168 | 169 | let mut i = 0; 170 | let once_loop_len = 30; 171 | let mut is_last_index = false; 172 | 173 | while i < response.list.len() { 174 | let mut end_index = i + once_loop_len; 175 | if response.list.len() < i + once_loop_len { 176 | end_index = response.list.len(); 177 | is_last_index = true; 178 | } 179 | 180 | pb.inc((end_index - i) as u64); 181 | 182 | let fetch_bills_awaits = response.list[i..end_index].iter().map(|bill| { 183 | client.fetch_a_bill( 184 | &bill.rqestProcRegstrNo, 185 | &bill.insttRqestProcStCd, 186 | &bill.deptSn, 187 | ) 188 | }); 189 | 190 | let results = join_all(fetch_bills_awaits).await; 191 | for bill in results { 192 | match bill { 193 | Ok(b) => { 194 | match b { 195 | BillReturnType::BillWithFiles(res) => { 196 | bills.push(res.dtlVo); 197 | } 198 | _ => { 199 | eprintln!("error2가 발생했습니다.") 200 | } 201 | }; 202 | } 203 | Err(e) => { 204 | eprintln!("{}", e); 205 | panic!(); 206 | } 207 | } 208 | } 209 | 210 | if is_last_index == true { 211 | i = end_index; 212 | } else { 213 | i += once_loop_len; 214 | } 215 | } 216 | 217 | pb.finish_and_clear(); 218 | 219 | log::print( 220 | &format!( 221 | "[{}] SYNC [3/3] {}조회한 내역을 데이터베이스에 저장합니다.", 222 | client.username, 223 | progress::DISK, 224 | ), 225 | &print_type, 226 | ) 227 | .await; 228 | 229 | let _result = create_bills(&supabase_client, &bills).await; 230 | 231 | log::print( 232 | &format!( 233 | "[{}] SYNC {} 총 {}건 동기화 완료! - {}", 234 | client.username, 235 | progress::SPARKLE, 236 | total_count, 237 | HumanDuration(started.elapsed()) 238 | ), 239 | &print_type, 240 | ) 241 | .await; 242 | } 243 | Err(e) => { 244 | eprintln!("{}", e); 245 | } 246 | }; 247 | 248 | Ok(()) 249 | } 250 | -------------------------------------------------------------------------------- /src/database/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::client; 2 | use async_trait::async_trait; 3 | use serde::Serialize; 4 | use std::fmt::Debug; 5 | use std::marker::Send; 6 | 7 | pub mod models; 8 | pub mod supabase; 9 | 10 | #[async_trait] 11 | pub trait DatabaseClient { 12 | async fn get( 13 | &self, 14 | table_name: &str, 15 | query: Option<&str>, 16 | ) -> Result; 17 | 18 | async fn post( 19 | &self, 20 | table_name: &str, 21 | items: Vec, 22 | ) -> Result; 23 | } 24 | 25 | // // TODO: 26 | // pub enum Database { 27 | // Supabase(supabase::Supabase), 28 | // } 29 | 30 | pub async fn create_bills( 31 | database_client: &C, 32 | bills_from_api: &Vec, 33 | ) -> Result, reqwest::Error> { 34 | let bills = bills_from_api 35 | .iter() 36 | .map(|b| models::BillRow::new(b)) 37 | .collect(); 38 | 39 | let response = database_client.post("bills", bills).await; 40 | match response { 41 | Ok(result) => { 42 | let status_code = result.status().as_u16(); 43 | if status_code != 200 && status_code != 201 { 44 | panic!( 45 | "Supabase 업로드에 실패하였습니다\n상태코드: {}", 46 | result.status() 47 | ) 48 | } 49 | 50 | result.json::>().await 51 | } 52 | Err(e) => { 53 | eprintln!("{}", e); 54 | Err(e) 55 | } 56 | } 57 | } 58 | 59 | pub async fn find_bills( 60 | database_client: &C, 61 | query: &str, 62 | ) -> Result, reqwest::Error> { 63 | let response = database_client.get("bills", Some(query)).await; 64 | match response { 65 | Ok(result) => result.json::>().await, 66 | Err(e) => { 67 | eprintln!("{}", e); 68 | Err(e) 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/database/models.rs: -------------------------------------------------------------------------------- 1 | use crypto::digest::Digest; 2 | use crypto::sha1::Sha1; 3 | 4 | use crate::client::DtlVo; 5 | 6 | #[derive(Debug, serde::Deserialize, serde::Serialize)] 7 | pub struct BillRow { 8 | pub registration_number: String, 9 | pub registration_proc_number: String, 10 | pub group_id: Option, 11 | 12 | pub proc_org_dept_code: Option, 13 | pub proc_org_dept_phone: Option, 14 | pub proc_org_dept_name: Option, 15 | pub proc_org_name: Option, 16 | pub proc_org_full_name: Option, 17 | pub proc_person_class: Option, 18 | pub proc_person_name: Option, 19 | 20 | pub dept_sn: Option, 21 | pub open_type: Option, 22 | pub open_status: Option, 23 | pub open_status_code: Option, 24 | pub open_date: Option, 25 | pub open_date_reason: Option, 26 | pub open_file_method: Option, 27 | pub notice_date: Option, 28 | 29 | pub request_date: String, 30 | pub request_subject: String, 31 | pub request_description: String, 32 | pub result_description: Option, 33 | pub transfered_org_name: Option, 34 | 35 | pub sanction_checker_name: Option, 36 | pub sanction_checker_class: Option, 37 | pub sanction_drafter_name: Option, 38 | pub sanction_drafter_class: Option, 39 | pub sanction_dcaner_name: Option, 40 | pub sanction_dcaner_class: Option, 41 | pub sanction_dcrber_name: Option, 42 | pub sanction_dcrber_class: Option, 43 | 44 | pub user_id: String, 45 | } 46 | 47 | impl BillRow { 48 | pub fn new(bill: &DtlVo) -> Self { 49 | let group_id = BillRow::create_group_id(&bill.rqestSj, &bill.rqestCn); 50 | 51 | let mut result_description: Option = None; 52 | if !bill.decsnCn.is_empty() { 53 | result_description = Some(bill.decsnCn.clone()); 54 | } else if !bill.clsdrResnCn.is_empty() { 55 | result_description = Some(bill.clsdrResnCn.clone()); 56 | } 57 | 58 | let open_date: String = if bill.othbcOprtnDt.is_empty() { 59 | if bill.othbcPrearngeDt.is_empty() { 60 | format!("") 61 | } else { 62 | bill.othbcPrearngeDt.clone() 63 | } 64 | } else { 65 | bill.othbcOprtnDt.clone() 66 | }; 67 | 68 | BillRow { 69 | group_id: Some(group_id), 70 | notice_date: Some(bill.nticeDt.clone()), 71 | dept_sn: Some(bill.deptSn.clone()), 72 | open_date: Some(open_date.replace(".", "-")), 73 | open_date_reason: Some(bill.othbcDtApnResnNm.clone()), 74 | open_status: Some(bill.insttRqestProcStNm.clone()), 75 | open_status_code: Some(bill.insttRqestProcStCd.clone()), 76 | open_type: Some(bill.othbcSeNm.clone()), 77 | proc_org_dept_code: Some(bill.opetrDeptCd.clone()), 78 | proc_org_dept_name: Some(bill.opetrDeptNm.clone()), 79 | proc_org_dept_phone: Some(bill.opetrCbleTelno.clone()), 80 | proc_org_name: Some(bill.prcsInsttNm.clone()), 81 | proc_org_full_name: Some(bill.prcsFullInsttNm.clone()), 82 | proc_person_class: Some(bill.opetrClsfNm.clone()), 83 | proc_person_name: Some(bill.opetrFnm.clone()), 84 | registration_number: bill.rqestRceptNo.clone(), 85 | registration_proc_number: bill.rqestProcRegstrNo.clone(), 86 | request_date: bill.rqestDt.replace(".", "-").clone(), 87 | request_description: bill.rqestCn.clone(), 88 | request_subject: bill.rqestSj.clone(), 89 | 90 | result_description: result_description, 91 | open_file_method: Some(bill.othbcStleSeNm.clone()), 92 | 93 | sanction_checker_class: Some(bill.chckerClsfNm.clone()), 94 | sanction_checker_name: Some(bill.chckerFnm.clone()), 95 | sanction_drafter_name: Some(bill.drafterFnm.clone()), 96 | sanction_drafter_class: Some(bill.drafterClsfNm.clone()), 97 | sanction_dcaner_name: Some(bill.dcanerFnm.clone()), 98 | sanction_dcaner_class: Some(bill.dcanerClsfNm.clone()), 99 | sanction_dcrber_name: Some(bill.dcrberFnm.clone()), 100 | sanction_dcrber_class: Some(bill.dcrberClsfNm.clone()), 101 | 102 | transfered_org_name: Some(bill.trnsfInsttNmCn.clone()), 103 | 104 | user_id: bill.mberId.to_owned(), 105 | } 106 | } 107 | 108 | pub fn create_group_id(rqest_sj: &str, rqest_cn: &str) -> String { 109 | let mut hasher = Sha1::new(); 110 | hasher.input_str(format!("{}_{}", rqest_sj, rqest_cn).as_str()); 111 | return hasher.result_str(); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/database/supabase.rs: -------------------------------------------------------------------------------- 1 | use crate::{database::DatabaseClient, utils::config}; 2 | 3 | use std::fmt::Debug; 4 | use std::marker::Send; 5 | 6 | use async_trait::async_trait; 7 | use reqwest::{self, header}; 8 | use serde::Serialize; 9 | 10 | #[derive(Debug)] 11 | pub struct Supabase { 12 | client: reqwest::Client, 13 | host: String, 14 | } 15 | 16 | impl Supabase { 17 | pub fn new() -> Self { 18 | let _config = config::Config::load_or_new().unwrap(); 19 | let supabase_host = _config 20 | .supabase_host 21 | .expect("supabase host 를 먼저 설정해주세요."); 22 | let supabase_api_key = _config 23 | .supabase_api_key 24 | .expect("supabase api key 를 먼저 설정해주세요."); 25 | 26 | let mut headers = header::HeaderMap::new(); 27 | 28 | headers.insert( 29 | "Authorization", 30 | format!("{} {}", "Bearer", supabase_api_key) 31 | .parse() 32 | .unwrap(), 33 | ); 34 | 35 | headers.insert("apiKey", supabase_api_key.parse().unwrap()); 36 | 37 | let client = reqwest::ClientBuilder::new() 38 | .default_headers(headers) 39 | .cookie_store(true) 40 | .build() 41 | .unwrap(); 42 | 43 | Supabase { 44 | client, 45 | host: supabase_host.to_owned(), 46 | } 47 | } 48 | } 49 | 50 | #[async_trait] 51 | impl DatabaseClient for Supabase { 52 | async fn get( 53 | &self, 54 | table_name: &str, 55 | query_string: Option<&str>, 56 | ) -> Result { 57 | let builder = self.client.get(format!( 58 | "{}/rest/v1/{}?{}", 59 | &self.host, 60 | table_name, 61 | query_string.unwrap_or_default() 62 | )); 63 | 64 | builder.send().await 65 | } 66 | 67 | async fn post( 68 | &self, 69 | table_name: &str, 70 | items: Vec, 71 | ) -> Result { 72 | let builder = self 73 | .client 74 | .post(format!("{}/rest/v1/{}", &self.host, table_name)) 75 | .header("Content-Type", "application/json") 76 | .header("Prefer", "resolution=merge-duplicates") 77 | .json(&items); 78 | 79 | builder.send().await 80 | } 81 | } 82 | 83 | #[cfg(test)] 84 | mod tests { 85 | use super::*; 86 | use crate::client::DtlVo; 87 | use crate::database::models::BillRow; 88 | 89 | #[tokio::test] 90 | async fn test_get() { 91 | let supabase = Supabase::new(); 92 | let query = "open_status_code=in.%28\"121\",\"131\"%29"; 93 | let response = supabase.get("bills", Some(query)).await; 94 | match response { 95 | Ok(r) => { 96 | let bills = r.json::>().await.unwrap(); 97 | println!("{:?}", bills.len()); 98 | assert_eq!(bills.len(), 7); 99 | } 100 | Err(e) => { 101 | eprintln!("{}", e); 102 | } 103 | }; 104 | } 105 | 106 | #[ignore] 107 | async fn test_post() { 108 | let supabase = Supabase::new(); 109 | let bill = BillRow::new(&DtlVo { 110 | clsdrResnCn: format!("test"), 111 | clsdrResnNm: format!("test"), 112 | chckerFnm: format!("test"), 113 | chckerClsfNm: format!("test"), 114 | dcrberFnm: format!("test"), 115 | dcrberClsfNm: format!("test"), 116 | dcanerFnm: format!("test"), 117 | dcanerClsfNm: format!("test"), 118 | drafterFnm: format!("test"), 119 | drafterClsfNm: format!("test"), 120 | othinstSmtmProcessYn: format!("N"), 121 | sanctnDocNo: format!("test"), 122 | // pub sanctnerClsfNm: String, // 결재정보 - 기안자 직위/직급 123 | // pub sanctnerFnm: String, // 결재권자 이름 124 | // pub sanctnerDt: String, // 결재일자 이름 125 | // pub sanctnerRequstDt: String, // 결재 요청 일자 126 | deptSn: format!("2"), 127 | decsnCn: format!("test"), 128 | trnsfInsttNmCn: format!("test"), 129 | opetrId: format!("test"), 130 | opetrFnm: format!("test"), 131 | opetrDeptCd: format!("test"), 132 | opetrDeptNm: format!("test"), 133 | opetrClsfCd: format!("test"), 134 | opetrClsfNm: format!("test"), 135 | opetrCbleTelno: format!("test"), 136 | othbcDtApnResnNm: format!("test"), 137 | othbcOprtnDt: format!("test"), 138 | // pub othbcInfoCnfirmDt: String, // *공개자료 열람 일시 139 | othbcPrearngeDt: format!("test"), 140 | othbcSeNm: format!("test"), 141 | othbcStleSeNm: format!("test"), 142 | recptMthSeNm: format!("test"), 143 | recptnServerId: format!("test"), 144 | // nticeCnfirmDt: String, // *결정통지 열람일시 145 | nticeDt: format!("test"), 146 | insttAddr: format!("test"), 147 | insttRqestProcStCd: format!("test"), 148 | insttRqestProcStNm: format!("test"), 149 | mberId: format!("test"), 150 | // procCd: String, // [empty] 151 | prcsInsttCd: format!("test"), 152 | prcsInsttNm: format!("test"), 153 | prcsFullInsttNm: format!("test"), 154 | // prcsFullInsttNm: String, // [empty] 155 | procCn: format!("test"), 156 | procDt: format!("test"), 157 | procRegstrNo: format!("test"), 158 | procDeptCbleTelno: format!("test"), 159 | procUserEmailAdres: format!("test"), 160 | rceptDt: format!("test"), 161 | rqestCn: format!("test"), 162 | rqestDt: format!("test"), 163 | rqestFullInsttNm: format!("test"), 164 | rqestInsttCd: format!("test"), 165 | rqestInsttNm: format!("test"), 166 | 167 | rqestProcRegstrNo: format!("test"), 168 | rqestRceptNo: format!("test"), 169 | rqestSj: format!("test"), 170 | }); 171 | 172 | let bills = vec![bill]; 173 | let response = supabase.post("bills", bills).await; 174 | 175 | match response { 176 | Ok(r) => { 177 | let bills = r.json::>().await.unwrap(); 178 | assert_eq!(bills.len(), 1); 179 | } 180 | Err(e) => { 181 | eprintln!("{}", e); 182 | } 183 | }; 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /src/files.rs: -------------------------------------------------------------------------------- 1 | use crate::client::{BillWithFiles, Client, DntcFile, DtlVo}; 2 | use crate::utils::auth::AuthUser; 3 | use crate::utils::{config, date}; 4 | use async_trait::async_trait; 5 | use bytes::Bytes; 6 | use chrono::prelude::Utc; 7 | use console::Emoji; 8 | use dirs::home_dir; 9 | use git2::{ 10 | self, Commit, Cred, IndexAddOption, ObjectType, Oid, RemoteCallbacks, Repository, Signature, 11 | }; 12 | use regex::Regex; 13 | use std::error::Error; 14 | use std::fs::{create_dir, remove_dir_all, File}; 15 | use std::io; 16 | use std::path::Path; 17 | 18 | static DOCUMENT: Emoji<'_, '_> = Emoji("📑 ", ""); 19 | 20 | pub struct FileManager<'a> { 21 | _auth_user: &'a AuthUser, 22 | _remote_url: String, 23 | _local_path: String, 24 | _local_repo: Option, 25 | _git_signature: Signature<'a>, 26 | } 27 | 28 | impl<'a> FileManager<'a> { 29 | pub async fn new(auth_user: &'a AuthUser) -> Result, Box> { 30 | let global_config = git2::Config::open_default().unwrap(); 31 | 32 | let _local_path = auth_user 33 | .local_repository 34 | .as_ref() 35 | .unwrap() 36 | .clone() 37 | .to_string(); 38 | let _remote_url = auth_user 39 | .remote_repository 40 | .as_ref() 41 | .unwrap() 42 | .clone() 43 | .to_string(); 44 | 45 | let mut fm = FileManager { 46 | _auth_user: auth_user, 47 | _local_path, 48 | _remote_url, 49 | _local_repo: None, 50 | _git_signature: Signature::now( 51 | &global_config.get_string("user.name").unwrap(), 52 | &global_config.get_string("user.email").unwrap(), 53 | ) 54 | .unwrap(), 55 | }; 56 | 57 | if !Path::new(&fm._local_path).exists() { 58 | fm.clone_remote_repo(); 59 | } 60 | 61 | Ok(fm) 62 | } 63 | 64 | pub fn clone_remote_repo(&mut self) -> &Option { 65 | let _ = remove_dir_all(&self._local_path); 66 | 67 | let mut callbacks = RemoteCallbacks::new(); 68 | callbacks.credentials(|_url, username_from_url, _allowed_types| { 69 | Cred::ssh_key( 70 | username_from_url.unwrap(), 71 | None, 72 | // TODO: ssh key 관리 고려 73 | std::path::Path::new(&format!( 74 | "{}/.ssh/id_ed25519", 75 | home_dir().unwrap().to_str().unwrap() 76 | )), 77 | None, 78 | ) 79 | }); 80 | 81 | let mut fo = git2::FetchOptions::new(); 82 | fo.remote_callbacks(callbacks); 83 | 84 | let mut builder = git2::build::RepoBuilder::new(); 85 | builder.fetch_options(fo); 86 | 87 | match builder.clone(&self._remote_url, Path::new(&self._local_path)) { 88 | Ok(repo) => { 89 | self._local_repo = Some(repo); 90 | } 91 | Err(error) => { 92 | println!("{}", &self._remote_url); 93 | println!("{}", &self._local_path); 94 | panic!("{}", error); 95 | } 96 | } 97 | 98 | &self._local_repo 99 | } 100 | 101 | pub async fn download( 102 | &self, 103 | auth_user: &AuthUser, 104 | client: &Client, 105 | bill: &BillWithFiles, 106 | bill_from_list: &DtlVo, 107 | ) -> Result>, Box> { 108 | let config = config::Config::load_or_new()?; 109 | match config.remote_file_repository { 110 | Some(_) => { 111 | let mut downloaded_files: Vec = vec![]; 112 | let fm = FileManager::new(auth_user).await.unwrap(); 113 | 114 | if let Some(ref file_list) = bill.atchFileList { 115 | for file in &*file_list { 116 | if fm.has_downloaded(bill, bill_from_list, &file.uploadFileOrginlNm) 117 | == false 118 | { 119 | let downloaded = client.download_file(file).await?; 120 | let _ = fm.save( 121 | &downloaded, 122 | bill, 123 | bill_from_list, 124 | &file.uploadFileOrginlNm, 125 | ); 126 | downloaded_files.push(file.clone()); 127 | } 128 | } 129 | 130 | Ok(Some(downloaded_files)) 131 | } else { 132 | Ok(Some(downloaded_files)) 133 | } 134 | } 135 | None => { 136 | eprintln!("청구파일을 다운로드 하려면 원격저장소 주소를 먼저 설정해주세요."); 137 | Ok(None) 138 | } 139 | } 140 | } 141 | 142 | // {접수일자}_{청구_제묵} 143 | pub fn make_dirname(request_date: &str, request_subject: &str) -> String { 144 | let re_illegal_symbols = Regex::new("[,<>\"\n \t()\'?~\u{1c}]").unwrap(); 145 | let re_retouch = Regex::new("_+").unwrap(); 146 | format!( 147 | "{}_{}", 148 | request_date.replace(".", "-"), 149 | re_retouch 150 | .replace_all( 151 | &re_illegal_symbols.replace_all(request_subject.trim(), "_"), 152 | "_", 153 | ) 154 | .to_string() 155 | ) 156 | } 157 | 158 | // {접수번호}_{처리기관이름}_{업로드_파일명} 159 | pub fn make_filename( 160 | registration_number: &str, 161 | rqest_full_instt_name: &str, 162 | file_name: &str, 163 | ) -> String { 164 | let re_illegal_symbols = Regex::new("[<>,\"\n \t()\'?~\u{1c}]").unwrap(); 165 | let re_retouch = Regex::new("_+").unwrap(); 166 | 167 | format!( 168 | "{}_{}_{}", 169 | registration_number, 170 | rqest_full_instt_name.replace(" ", "_"), 171 | re_retouch 172 | .replace_all(&re_illegal_symbols.replace_all(file_name.trim(), "_"), "_",) 173 | .to_string() 174 | ) 175 | } 176 | 177 | pub fn save( 178 | &self, 179 | downloaded_file: &Bytes, 180 | downloadable_bill: &BillWithFiles, 181 | bill_from_list: &DtlVo, 182 | orig_file_name: &str, 183 | ) -> Result> { 184 | let dir_path = format!("{}/{}", &self._local_path, downloadable_bill.get_dirname(),); 185 | let file_path = format!( 186 | "{}/{}", 187 | &dir_path, 188 | downloadable_bill.get_filename(&bill_from_list.prcsFullInsttNm, orig_file_name) 189 | ); 190 | 191 | create_dir(Path::new(&dir_path)).unwrap_or_default(); 192 | let mut local_file = File::create(&file_path)?; 193 | io::copy(&mut downloaded_file.as_ref(), &mut local_file)?; 194 | Ok(local_file) 195 | } 196 | 197 | fn has_downloaded( 198 | &self, 199 | downloadable_bill: &T, 200 | bill_from_list: &DtlVo, 201 | orig_file_name: &str, 202 | ) -> bool { 203 | let dir_path = format!("{}/{}", &self._local_path, downloadable_bill.get_dirname(),); 204 | let file_path = format!( 205 | "{}/{}", 206 | &dir_path, 207 | downloadable_bill.get_filename(&bill_from_list.prcsFullInsttNm, orig_file_name) 208 | ); 209 | 210 | return Path::new(&file_path).exists(); 211 | } 212 | 213 | fn remote_callbaks(&self) -> RemoteCallbacks<'a> { 214 | let mut callbacks = RemoteCallbacks::new(); 215 | callbacks.credentials(|_url, username_from_url, _allowed_types| { 216 | Cred::ssh_key( 217 | username_from_url.unwrap(), 218 | None, 219 | std::path::Path::new(&format!( 220 | "{}/.ssh/id_ed25519", 221 | home_dir().unwrap().to_str().unwrap() 222 | )), 223 | None, 224 | ) 225 | }); 226 | return callbacks; 227 | } 228 | 229 | pub async fn sync_with_remote(&self) -> Result<(), Box> { 230 | let repo = match Repository::open(&self._local_path) { 231 | Ok(repo) => repo, 232 | Err(e) => panic!("파일 저장소를 불러오는데 실패하였습니다.: {}", e), 233 | }; 234 | 235 | let callbacks = self.remote_callbaks(); 236 | let mut po = git2::FetchOptions::new(); 237 | let mut po = po.remote_callbacks(callbacks); 238 | 239 | repo.find_remote("origin")? 240 | .fetch(&["main"], Some(&mut po), None); 241 | let fetch_head = repo.find_reference("FETCH_HEAD")?; 242 | let fetch_commit = repo.reference_to_annotated_commit(&fetch_head)?; 243 | let analysis = repo.merge_analysis(&[&fetch_commit])?; 244 | if analysis.0.is_up_to_date() { 245 | Ok(()) 246 | } else { 247 | let refname = format!("refs/heads/main"); 248 | let mut reference = repo.find_reference(&refname)?; 249 | reference.set_target(fetch_commit.id(), "Fast-Forward")?; 250 | repo.set_head(&refname)?; 251 | repo.checkout_head(Some(git2::build::CheckoutBuilder::default().force())); 252 | 253 | Ok(()) 254 | } 255 | } 256 | 257 | pub async fn upload(&self) -> Result { 258 | let callbacks = self.remote_callbaks(); 259 | fn find_last_commit(repo: &Repository) -> Result { 260 | let obj = repo.head()?.resolve()?.peel(ObjectType::Commit)?; 261 | obj.into_commit() 262 | .map_err(|_| git2::Error::from_str("Couldn't find commit")) 263 | } 264 | 265 | let repo = match Repository::open(&self._local_path) { 266 | Ok(repo) => repo, 267 | Err(e) => panic!("failed to open: {}", e), 268 | }; 269 | 270 | let mut index = repo.index().unwrap(); 271 | let _ = index.add_all(["*"].iter(), IndexAddOption::DEFAULT, None); 272 | let _ = index.write(); 273 | let oid = index.write_tree().unwrap(); 274 | let parent_commit = find_last_commit(&repo).unwrap(); 275 | let tree = repo.find_tree(oid).unwrap(); 276 | 277 | repo.commit( 278 | Some("HEAD"), 279 | &self._git_signature, 280 | &self._git_signature, 281 | &format!( 282 | "{} - {}", 283 | DOCUMENT, 284 | date::KstDateTime::from(Utc::now()).format(Some("%F %T")) 285 | ), 286 | &tree, 287 | &[&parent_commit], 288 | ) 289 | .unwrap(); 290 | 291 | let mut remote = match repo.find_remote("origin") { 292 | Ok(r) => r, 293 | Err(_) => repo.remote("origin", &self._remote_url)?, 294 | }; 295 | 296 | let mut po = git2::PushOptions::new(); 297 | let mut po = po.remote_callbacks(callbacks); 298 | 299 | match remote.push( 300 | &["refs/heads/main", "refs/remotes/origin/main"], 301 | Some(&mut po), 302 | ) { 303 | Ok(_) => Ok(oid), 304 | Err(e) => { 305 | eprintln!("{}", e); 306 | Ok(oid) 307 | } 308 | } 309 | } 310 | } 311 | 312 | #[async_trait] 313 | pub trait Downloadable { 314 | fn get_filename(&self, prcs_full_instt_nm: &str, orig_file_name: &str) -> String; 315 | fn get_dirname(&self) -> String; 316 | } 317 | 318 | #[cfg(test)] 319 | mod tests { 320 | use crate::{files::FileManager, utils::auth::AuthConfig}; 321 | 322 | #[tokio::test] 323 | async fn test_sync_with_remote() { 324 | let auth_config = AuthConfig::load_or_new().unwrap(); 325 | let auth_user = &auth_config.find_org("default").unwrap().borrow(); 326 | let fm = FileManager::new(auth_user).await.unwrap(); 327 | fm.sync_with_remote().await; 328 | } 329 | } 330 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_must_use)] 2 | 3 | mod client; 4 | mod commands; 5 | mod database; 6 | mod files; 7 | mod utils; 8 | 9 | use clap::Parser; 10 | use dotenv::dotenv; 11 | use std::error::Error; 12 | 13 | #[derive(Parser)] 14 | #[clap(name = "ogk")] 15 | #[clap(about = "cli for open.go.kr", long_about = None)] 16 | #[clap(author = "hoonyland.newsletter@gmail.com", long_about = None)] 17 | struct Cli { 18 | #[clap(subcommand)] 19 | command: commands::Commands, 20 | } 21 | 22 | #[tokio::main] 23 | async fn main() -> Result<(), Box> { 24 | dotenv().ok(); 25 | 26 | let args = Cli::parse(); 27 | let _result = commands::run(&args.command).await; 28 | Ok(()) 29 | } 30 | -------------------------------------------------------------------------------- /src/utils/auth.rs: -------------------------------------------------------------------------------- 1 | use base64::{engine::general_purpose, Engine as _}; 2 | use dirs::home_dir; 3 | use serde::{Deserialize, Serialize}; 4 | use std::cell::RefCell; 5 | use std::collections::HashMap; 6 | use std::error::Error; 7 | use std::fs::{create_dir_all, read_to_string, File}; 8 | use std::io::prelude::*; 9 | use std::path::Path; 10 | use std::str; 11 | 12 | #[derive(Debug, Deserialize, Serialize)] 13 | pub struct AuthConfig { 14 | pub accounts: HashMap>, 15 | } 16 | 17 | #[derive(Debug, Deserialize, Serialize)] 18 | pub struct AuthUser { 19 | pub org: String, 20 | pub username: String, 21 | pub password: String, 22 | pub local_repository: Option, 23 | pub remote_repository: Option, 24 | pub slack_webhook_url: Option, 25 | } 26 | 27 | impl AuthConfig { 28 | pub fn new() -> Self { 29 | AuthConfig { 30 | accounts: HashMap::new(), 31 | } 32 | } 33 | 34 | pub fn root_path() -> String { 35 | format!("{}/{}", home_dir().unwrap().to_str().unwrap(), ".ogk") 36 | } 37 | 38 | pub fn credential_file_path() -> String { 39 | format!("{}/{}", AuthConfig::root_path(), "credentials") 40 | } 41 | 42 | pub fn load() -> Result> { 43 | let file_path = AuthConfig::credential_file_path(); 44 | let credential_file = read_to_string(file_path)?; 45 | let credential = toml::from_str(&credential_file)?; 46 | Ok(credential) 47 | } 48 | 49 | pub fn load_or_new() -> Result> { 50 | let file_path = AuthConfig::credential_file_path(); 51 | 52 | match read_to_string(file_path) { 53 | Ok(credential_file) => { 54 | let credential = toml::from_str(&credential_file)?; 55 | return Ok(credential); 56 | } 57 | Err(_) => { 58 | return Ok(AuthConfig::new()); 59 | } 60 | } 61 | } 62 | 63 | pub fn save(&self) -> Result<(), Box> { 64 | let toml = toml::to_string(self)?; 65 | let realopen_path = AuthConfig::root_path(); 66 | let file_path = AuthConfig::credential_file_path(); 67 | create_dir_all(Path::new(&realopen_path))?; 68 | let mut local_file = File::create(Path::new(&file_path))?; 69 | local_file.write_all(toml.as_bytes())?; 70 | Ok(()) 71 | } 72 | 73 | pub fn add_account( 74 | &self, 75 | org: &str, 76 | username: &str, 77 | password: &str, 78 | ) -> Result> { 79 | let mut auth_config = AuthConfig::load_or_new().unwrap(); 80 | 81 | if auth_config.accounts.len() == 0 { 82 | auth_config.accounts.insert( 83 | String::from("default"), 84 | RefCell::new(AuthUser::new(org, username, password)), 85 | ); 86 | 87 | auth_config.save(); 88 | } 89 | 90 | auth_config.accounts.insert( 91 | org.to_owned(), 92 | RefCell::new(AuthUser::new(org, username, password)), 93 | ); 94 | 95 | auth_config.save(); 96 | 97 | Ok(auth_config) 98 | } 99 | 100 | pub fn find_org(&self, org: &str) -> Option<&RefCell> { 101 | self.accounts.get(org) 102 | } 103 | 104 | pub fn set_remote_repository_path( 105 | &self, 106 | org: &str, 107 | remote_repository: &str, 108 | ) -> Result> { 109 | let auth_config = AuthConfig::load_or_new().unwrap(); 110 | if let Some(value_refcell) = auth_config.accounts.get(org) { 111 | let mut option = value_refcell.borrow_mut(); 112 | option.remote_repository = Some(remote_repository.to_string()); 113 | } 114 | 115 | if let Some(value_refcell) = auth_config.accounts.get("default") { 116 | let mut option = value_refcell.borrow_mut(); 117 | if option.org == org { 118 | option.remote_repository = Some(remote_repository.to_string()); 119 | } 120 | } 121 | 122 | auth_config.save(); 123 | 124 | Ok(auth_config) 125 | } 126 | 127 | pub fn set_local_repository_path( 128 | &self, 129 | org: &str, 130 | local_repository: &str, 131 | ) -> Result> { 132 | let auth_config = AuthConfig::load_or_new().unwrap(); 133 | if let Some(value_refcell) = auth_config.accounts.get(org) { 134 | let mut option = value_refcell.borrow_mut(); 135 | option.local_repository = Some(local_repository.to_string()); 136 | } 137 | 138 | if let Some(value_refcell) = auth_config.accounts.get("default") { 139 | let mut option = value_refcell.borrow_mut(); 140 | if option.org == org { 141 | option.local_repository = Some(local_repository.to_string()); 142 | } 143 | } 144 | 145 | auth_config.save(); 146 | 147 | Ok(auth_config) 148 | } 149 | 150 | pub fn set_slack_webhook_url( 151 | &self, 152 | org: &str, 153 | url: &str, 154 | ) -> Result> { 155 | let auth_config = AuthConfig::load_or_new().unwrap(); 156 | if let Some(value_refcell) = auth_config.accounts.get(org) { 157 | let mut option = value_refcell.borrow_mut(); 158 | option.slack_webhook_url = Some(url.to_string()); 159 | } 160 | 161 | if let Some(value_refcell) = auth_config.accounts.get("default") { 162 | let mut option = value_refcell.borrow_mut(); 163 | if option.org == org { 164 | option.slack_webhook_url = Some(url.to_string()); 165 | } 166 | } 167 | 168 | auth_config.save(); 169 | 170 | Ok(auth_config) 171 | } 172 | } 173 | 174 | impl AuthUser { 175 | fn encode_password(password: &str) -> String { 176 | general_purpose::STANDARD.encode(password.as_bytes()) 177 | } 178 | 179 | pub fn new(org: &str, username: &str, password: &str) -> Self { 180 | AuthUser { 181 | org: org.to_owned(), 182 | username: username.to_owned(), 183 | password: AuthUser::encode_password(password), 184 | remote_repository: None, 185 | local_repository: None, 186 | slack_webhook_url: None, 187 | } 188 | } 189 | 190 | pub fn get_decoded_password(&self) -> String { 191 | let decoded_password = general_purpose::STANDARD 192 | .decode(&self.password.as_bytes()) 193 | .unwrap(); 194 | 195 | str::from_utf8(&decoded_password).unwrap().to_owned() 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /src/utils/config.rs: -------------------------------------------------------------------------------- 1 | use dirs::home_dir; 2 | use serde::{Deserialize, Serialize}; 3 | use std::error::Error; 4 | use std::fmt; 5 | use std::fs::{create_dir_all, read_to_string, File}; 6 | use std::io::prelude::*; 7 | use std::path::Path; 8 | 9 | #[derive(Debug, Deserialize, Serialize)] 10 | pub struct Config { 11 | // files 12 | pub local_file_repository: Option, 13 | pub remote_file_repository: Option, 14 | 15 | // sync 16 | pub supabase_host: Option, 17 | pub supabase_api_key: Option, 18 | 19 | // integration 20 | pub slack_webhook_url: Option, 21 | } 22 | 23 | impl fmt::Display for Config { 24 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 25 | let _local_file_repository = match &self.local_file_repository { 26 | Some(rr) => format!("{}", rr), 27 | None => Config::default_local_repository(), 28 | }; 29 | 30 | let _remote_file_repository = match &self.remote_file_repository { 31 | Some(rr) => format!("{}", rr), 32 | None => format!("⚠️ NOT CONFIGURED ⚠️"), 33 | }; 34 | 35 | let _supabase_host = match &self.supabase_host { 36 | Some(rr) => format!("{}", rr), 37 | None => format!("⚠️ NOT CONFIGURED ⚠️"), 38 | }; 39 | 40 | let _supabase_api_key = match &self.supabase_api_key { 41 | Some(rr) => format!("{}", rr), 42 | None => format!("⚠️ NOT CONFIGURED ⚠️"), 43 | }; 44 | 45 | let _slack_webhook_url = match &self.slack_webhook_url { 46 | Some(rr) => format!("{}", rr), 47 | None => format!("⚠️ NOT CONFIGURED ⚠️"), 48 | }; 49 | 50 | write!( 51 | f, 52 | "🗄 FILES:\nlocal file repository: {}\nremote file repository(github): {}\n\n💾 DATABASE(supabase)\nhost: {}\napi_key: {}\n\n🔌 INTEGRATION\nSLACK WEBHOOK URL: {}", 53 | _local_file_repository, _remote_file_repository, _supabase_host, _supabase_api_key, _slack_webhook_url 54 | ) 55 | } 56 | } 57 | 58 | impl Config { 59 | pub fn new() -> Self { 60 | Config { 61 | local_file_repository: Some(Config::default_local_repository()), 62 | remote_file_repository: None, 63 | 64 | supabase_host: None, 65 | supabase_api_key: None, 66 | 67 | slack_webhook_url: None, 68 | } 69 | } 70 | 71 | pub fn _load() -> Result> { 72 | let file_path = Config::file_path(); 73 | let config_file = read_to_string(file_path)?; 74 | let config = toml::from_str(&config_file)?; 75 | Ok(config) 76 | } 77 | 78 | pub fn load_or_new() -> Result> { 79 | let file_path = Config::file_path(); 80 | match read_to_string(file_path) { 81 | Ok(config_file) => { 82 | let config = toml::from_str(&config_file)?; 83 | return Ok(config); 84 | } 85 | Err(_) => { 86 | return Ok(Config::new()); 87 | } 88 | } 89 | } 90 | 91 | pub fn save(&self) -> Result<(), Box> { 92 | let toml = toml::to_string(self)?; 93 | let realopen_path = Config::root_path(); 94 | let file_path = Config::file_path(); 95 | create_dir_all(Path::new(&realopen_path))?; 96 | let mut local_file = File::create(Path::new(&file_path))?; 97 | local_file.write_all(toml.as_bytes())?; 98 | Ok(()) 99 | } 100 | 101 | pub fn file_path() -> String { 102 | format!("{}/{}", Config::root_path(), "config") 103 | } 104 | 105 | pub fn default_local_repository() -> String { 106 | format!("{}/{}", Config::root_path(), ".data") 107 | } 108 | 109 | pub fn root_path() -> String { 110 | format!("{}/{}", home_dir().unwrap().to_str().unwrap(), ".ogk") 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/utils/date.rs: -------------------------------------------------------------------------------- 1 | use chrono::prelude::*; 2 | use std::convert::From; 3 | 4 | pub struct KstDateTime { 5 | pub datetime: DateTime, 6 | } 7 | 8 | impl KstDateTime { 9 | pub fn format(&self, format: Option<&str>) -> String { 10 | let _format = match format { 11 | Some(f) => f, 12 | None => "%Y-%m-%d", 13 | }; 14 | self.datetime.format(_format).to_string() 15 | } 16 | } 17 | 18 | impl From> for KstDateTime { 19 | fn from(datetime: DateTime) -> KstDateTime { 20 | KstDateTime { 21 | datetime: datetime.with_timezone(&FixedOffset::east(9 * 3600)), // KST +09:00 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/utils/log.rs: -------------------------------------------------------------------------------- 1 | use crate::utils::config; 2 | use crate::utils::slack::send_webhook_message; 3 | 4 | #[derive(Debug)] 5 | pub enum PrintType { 6 | SLACK, 7 | DEFAULT, 8 | } 9 | 10 | pub async fn print( 11 | message: &str, 12 | print_type: &PrintType, 13 | ) -> Result<(), Box> { 14 | match print_type { 15 | PrintType::SLACK => { 16 | println!("{}", message); 17 | 18 | let _config = config::Config::load_or_new()?; 19 | 20 | match _config.slack_webhook_url { 21 | Some(url) => { 22 | let _result = send_webhook_message(&url, message).await; 23 | } 24 | _ => {} 25 | } 26 | } 27 | _ => { 28 | println!("{}", message); 29 | } 30 | }; 31 | 32 | Ok(()) 33 | } 34 | -------------------------------------------------------------------------------- /src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod auth; 2 | pub mod config; 3 | pub mod date; 4 | pub mod log; 5 | pub mod progress; 6 | pub mod slack; 7 | -------------------------------------------------------------------------------- /src/utils/progress.rs: -------------------------------------------------------------------------------- 1 | use console::Emoji; 2 | 3 | pub static LOOKING_GLASS: Emoji<'_, '_> = Emoji("🔍 ", ""); 4 | pub static TRUCK: Emoji<'_, '_> = Emoji("🚚 ", ""); 5 | pub static DISK: Emoji<'_, '_> = Emoji("💾 ", ""); 6 | pub static SPARKLE: Emoji<'_, '_> = Emoji("✨ ", ":-)"); 7 | pub static WRITE: Emoji<'_, '_> = Emoji("📝 ", ""); 8 | pub static HAND_WITH_EYE: Emoji<'_, '_> = Emoji("🪬 ", ""); 9 | -------------------------------------------------------------------------------- /src/utils/slack.rs: -------------------------------------------------------------------------------- 1 | use reqwest::{self, Error, Response}; 2 | use std::str; 3 | 4 | pub async fn send_webhook_message(host: &str, message: &str) -> Result { 5 | let mut headers = reqwest::header::HeaderMap::new(); 6 | headers.insert("Content-Type", "application/json".parse().unwrap()); 7 | 8 | let client = reqwest::ClientBuilder::new() 9 | .default_headers(headers) 10 | .build() 11 | .unwrap(); 12 | 13 | let mut map = std::collections::HashMap::new(); 14 | map.insert("text", message); 15 | 16 | client.post(host).json(&map).send().await 17 | } 18 | 19 | #[cfg(test)] 20 | mod tests { 21 | use super::send_webhook_message; 22 | 23 | #[tokio::test] 24 | async fn test_send_webhook_message() { 25 | let host: &str = ""; 26 | let _result = send_webhook_message(host, "test").await; 27 | } 28 | } 29 | --------------------------------------------------------------------------------