From 52a82210ea7b9aa1c96d7813e1396bfe9b1d9d10 Mon Sep 17 00:00:00 2001 From: Nikolas Goerlitz Date: Sun, 30 Mar 2025 14:18:43 +0200 Subject: [PATCH 01/19] Initial commit for the rewrite in rust. --- .dockerignore | 4 - .env.example | 3 - .github/workflows/dev.prettier.yml | 39 +- .github/workflows/prod.docker.yml | 6 +- .gitignore | 22 +- .prettierignore | 13 - .prettierrc | 10 - Cargo.lock | 2735 ++++++++++++++++++++++++ Cargo.toml | 15 + Dockerfile | 16 +- config.ts | 11 - controller/DatafeedController.ts | 105 - controller/DatafeedVatgerController.ts | 103 - docker-compose.yml | 12 +- index.ts | 16 - models/DatafeedModel.ts | 135 -- models/VatsimStatusModel.ts | 11 - package-lock.json | 1228 ----------- package.json | 24 - routes.ts | 34 - service/DatafeedService.ts | 124 -- src/api/handlers.rs | 206 ++ src/api/mod.rs | 33 + src/api/state.rs | 31 + src/api/types.rs | 32 + src/datafeed/datafeed.rs | 84 + src/datafeed/datafeed_status.rs | 17 + src/datafeed/mod.rs | 16 + src/datafeed/status.rs | 53 + src/datafeed/types.rs | 101 + src/main.rs | 25 + tsconfig.json | 109 - 32 files changed, 3404 insertions(+), 1969 deletions(-) delete mode 100644 .dockerignore delete mode 100644 .env.example delete mode 100644 .prettierignore delete mode 100644 .prettierrc create mode 100644 Cargo.lock create mode 100644 Cargo.toml delete mode 100644 config.ts delete mode 100644 controller/DatafeedController.ts delete mode 100644 controller/DatafeedVatgerController.ts delete mode 100644 index.ts delete mode 100644 models/DatafeedModel.ts delete mode 100644 models/VatsimStatusModel.ts delete mode 100644 package-lock.json delete mode 100644 package.json delete mode 100644 routes.ts delete mode 100644 service/DatafeedService.ts create mode 100644 src/api/handlers.rs create mode 100644 src/api/mod.rs create mode 100644 src/api/state.rs create mode 100644 src/api/types.rs create mode 100644 src/datafeed/datafeed.rs create mode 100644 src/datafeed/datafeed_status.rs create mode 100644 src/datafeed/mod.rs create mode 100644 src/datafeed/status.rs create mode 100644 src/datafeed/types.rs create mode 100644 src/main.rs delete mode 100644 tsconfig.json diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index 6a87283..0000000 --- a/.dockerignore +++ /dev/null @@ -1,4 +0,0 @@ -node_modules -dist -LICENSE -.gitignore \ No newline at end of file diff --git a/.env.example b/.env.example deleted file mode 100644 index 26ca4e6..0000000 --- a/.env.example +++ /dev/null @@ -1,3 +0,0 @@ -APP_HOST=0.0.0.0 -APP_PORT=8000 -DATAFEED_REFRESH_INTERVAL_S=15 \ No newline at end of file diff --git a/.github/workflows/dev.prettier.yml b/.github/workflows/dev.prettier.yml index 0f3793a..6641f27 100644 --- a/.github/workflows/dev.prettier.yml +++ b/.github/workflows/dev.prettier.yml @@ -1,26 +1,33 @@ -name: Code-Style Check +name: Build dev image -# Run only if a *.ts file has changed (i.e. anything relevant) on: push: - branches: [ "dev" ] - paths: - - "**.ts" - pull_request: - branches: [ "dev" ] - paths: - - "**.ts" + branches-ignore: + - main + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} jobs: - code-style-check: + docker-image-build: runs-on: ubuntu-latest + permissions: + contents: read + packages: write steps: - - name: Checkout - uses: actions/checkout@v3 + - name: Checkout repository + uses: actions/checkout@v4 - - name: Prettier Check - uses: creyD/prettier_action@v4.3 + - name: Docker Login + uses: docker/login-action@v3 with: - dry: true - prettier_options: "--check **/*.ts" \ No newline at end of file + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push Docker image + run: | + docker build --pull -t ${{ env.REGISTRY }}/${{ github.repository }}:dev . + docker push ${{ env.REGISTRY }}/${{ github.repository }}:dev \ No newline at end of file diff --git a/.github/workflows/prod.docker.yml b/.github/workflows/prod.docker.yml index 882ae7a..913233c 100644 --- a/.github/workflows/prod.docker.yml +++ b/.github/workflows/prod.docker.yml @@ -1,4 +1,4 @@ -name: Docker CI/CD +name: Build latest image on: push: @@ -17,10 +17,10 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Docker Login - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} diff --git a/.gitignore b/.gitignore index 5cf1ade..e26ed6d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,18 @@ -dist -node_modules -.idea -.vscode +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ -.env \ No newline at end of file +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +# RustRover +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +.idea/ +.vscode/ \ No newline at end of file diff --git a/.prettierignore b/.prettierignore deleted file mode 100644 index 99b4f5d..0000000 --- a/.prettierignore +++ /dev/null @@ -1,13 +0,0 @@ -# Folders -.github -dist -node_modules - -# Files -*.md -*.json -.gitignore -.prettier* -Dockerfile -docker-compose.yml -LICENSE \ No newline at end of file diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index bed5b38..0000000 --- a/.prettierrc +++ /dev/null @@ -1,10 +0,0 @@ -{ - "useTabs": false, - "tabWidth": 4, - "semi": true, - "singleQuote": true, - "bracketSpacing": true, - "arrowParens": "avoid", - "parser": "typescript", - "endOfLine": "auto" -} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..01c4650 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,2735 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "actix-codec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" +dependencies = [ + "bitflags", + "bytes", + "futures-core", + "futures-sink", + "memchr", + "pin-project-lite", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "actix-http" +version = "3.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa882656b67966045e4152c634051e70346939fced7117d5f0b52146a7c74c9" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-utils", + "base64", + "bitflags", + "brotli", + "bytes", + "bytestring", + "derive_more", + "encoding_rs", + "flate2", + "foldhash", + "futures-core", + "h2 0.3.26", + "http 0.2.12", + "httparse", + "httpdate", + "itoa", + "language-tags", + "local-channel", + "mime", + "percent-encoding", + "pin-project-lite", + "rand", + "sha1", + "smallvec", + "tokio", + "tokio-util", + "tracing", + "zstd", +] + +[[package]] +name = "actix-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "actix-router" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13d324164c51f63867b57e73ba5936ea151b8a41a1d23d1031eeb9f70d0236f8" +dependencies = [ + "bytestring", + "cfg-if", + "http 0.2.12", + "regex", + "regex-lite", + "serde", + "tracing", +] + +[[package]] +name = "actix-rt" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eda4e2a6e042aa4e55ac438a2ae052d3b5da0ecf83d7411e1a368946925208" +dependencies = [ + "futures-core", + "tokio", +] + +[[package]] +name = "actix-server" +version = "2.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6398974fd4284f4768af07965701efbbb5fdc0616bff20cade1bb14b77675e24" +dependencies = [ + "actix-rt", + "actix-service", + "actix-utils", + "futures-core", + "futures-util", + "mio", + "socket2", + "tokio", + "tracing", +] + +[[package]] +name = "actix-service" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e46f36bf0e5af44bdc4bdb36fbbd421aa98c79a9bce724e1edeb3894e10dc7f" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "actix-utils" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" +dependencies = [ + "local-waker", + "pin-project-lite", +] + +[[package]] +name = "actix-web" +version = "4.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2e3b15b3dc6c6ed996e4032389e9849d4ab002b1e92fbfe85b5f307d1479b4d" +dependencies = [ + "actix-codec", + "actix-http", + "actix-macros", + "actix-router", + "actix-rt", + "actix-server", + "actix-service", + "actix-utils", + "actix-web-codegen", + "bytes", + "bytestring", + "cfg-if", + "cookie", + "derive_more", + "encoding_rs", + "foldhash", + "futures-core", + "futures-util", + "impl-more", + "itoa", + "language-tags", + "log", + "mime", + "once_cell", + "pin-project-lite", + "regex", + "regex-lite", + "serde", + "serde_json", + "serde_urlencoded", + "smallvec", + "socket2", + "time", + "tracing", + "url", +] + +[[package]] +name = "actix-web-codegen" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f591380e2e68490b5dfaf1dd1aa0ebe78d84ba7067078512b4ea6e4492d622b8" +dependencies = [ + "actix-router", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" + +[[package]] +name = "anstyle-parse" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +dependencies = [ + "anstyle", + "once_cell", + "windows-sys 0.59.0", +] + +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "brotli" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc97b8f16f944bba54f0433f07e30be199b6dc2bd25937444bbad560bcea29bd" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "4.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74fa05ad7d803d413eb8380983b092cbbaf9a85f151b871360e7b00cd7060b37" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bumpalo" +version = "3.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "bytestring" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e465647ae23b2823b0753f50decb2d5a86d2bb2cac04788fafd1f80e45378e5f" +dependencies = [ + "bytes", +] + +[[package]] +name = "cc" +version = "1.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fcb57c740ae1daf453ae85f16e37396f672b039e00d9d866e07ddb24e328e3a" +dependencies = [ + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "colorchoice" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + +[[package]] +name = "cookie" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "deranged" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28cfac68e08048ae1883171632c2aef3ebc555621ae56fbccce1cbf22dd7f058" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "derive_more" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "unicode-xid", +] + +[[package]] +name = "df-cache" +version = "0.1.0" +dependencies = [ + "actix-web", + "chrono", + "env_logger", + "geo", + "log", + "rand", + "reqwest", + "serde", + "tokio", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "earcutr" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79127ed59a85d7687c409e9978547cffb7dc79675355ed22da6b66fd5f6ead01" +dependencies = [ + "itertools", + "num-traits", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "env_filter" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3716d7a920fb4fac5d84e9d4bce8ceb321e9414b4409da61b07b75c1e3d0697" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "flate2" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "float_next_after" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bf7cc16383c4b8d58b9905a8509f02926ce3058053c056376248d958c9df1e8" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "geo" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4416397671d8997e9a3e7ad99714f4f00a22e9eaa9b966a5985d2194fc9e02e1" +dependencies = [ + "earcutr", + "float_next_after", + "geo-types", + "geographiclib-rs", + "i_overlay", + "log", + "num-traits", + "robust", + "rstar", + "spade", +] + +[[package]] +name = "geo-types" +version = "0.7.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62ddb1950450d67efee2bbc5e429c68d052a822de3aad010d28b351fbb705224" +dependencies = [ + "approx", + "num-traits", + "rayon", + "rstar", + "serde", +] + +[[package]] +name = "geographiclib-rs" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e5ed84f8089c70234b0a8e0aedb6dc733671612ddc0d37c6066052f9781960" +dependencies = [ + "libm", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "h2" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "h2" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5017294ff4bb30944501348f6f8e42e6ad28f42c8bbef7a74029aff064a4e3c2" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.3.1", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32", + "stable_deref_trait", +] + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http 1.3.1", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http 1.3.1", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2 0.4.8", + "http 1.3.1", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" +dependencies = [ + "futures-util", + "http 1.3.1", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.3.1", + "http-body", + "hyper", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "i_float" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85df3a416829bb955fdc2416c7b73680c8dcea8d731f2c7aa23e1042fe1b8343" +dependencies = [ + "serde", +] + +[[package]] +name = "i_key_sort" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "347c253b4748a1a28baf94c9ce133b6b166f08573157e05afe718812bc599fcd" + +[[package]] +name = "i_overlay" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0542dfef184afdd42174a03dcc0625b6147fb73e1b974b1a08a2a42ac35cee49" +dependencies = [ + "i_float", + "i_key_sort", + "i_shape", + "i_tree", + "rayon", +] + +[[package]] +name = "i_shape" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a38f5a42678726718ff924f6d4a0e79b129776aeed298f71de4ceedbd091bce" +dependencies = [ + "i_float", + "serde", +] + +[[package]] +name = "i_tree" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "155181bc97d770181cf9477da51218a19ee92a8e5be642e796661aee2b601139" + +[[package]] +name = "iana-time-zone" +version = "0.1.62" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2fd658b06e56721792c5df4475705b6cda790e9298d19d2f8af083457bcd127" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "impl-more" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2" + +[[package]] +name = "indexmap" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "jiff" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c102670231191d07d37a35af3eb77f1f0dbf7a71be51a962dcd57ea607be7260" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde", +] + +[[package]] +name = "jiff-static" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cdde31a9d349f1b1f51a0b3714a5940ac022976f4b49485fc04be052b183b4c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "jobserver" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "language-tags" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" + +[[package]] +name = "libc" +version = "0.2.171" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" + +[[package]] +name = "libm" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" + +[[package]] +name = "linux-raw-sys" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413" + +[[package]] +name = "litemap" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" + +[[package]] +name = "local-channel" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6cbc85e69b8df4b8bb8b89ec634e7189099cea8927a276b7384ce5488e53ec8" +dependencies = [ + "futures-core", + "futures-sink", + "local-waker", +] + +[[package]] +name = "local-waker" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "log", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.52.0", +] + +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "openssl" +version = "0.10.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e14130c6a98cd258fdcb0fb6d744152343ff729cbfcb28c656a9d12b999fbcd" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-sys" +version = "0.9.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bb61ea9811cc39e3c2069f40b8b8e2e70d8569b361f879786cc7ed48b777cdd" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "portable-atomic" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + +[[package]] +name = "rand" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +dependencies = [ + "rand_chacha", + "rand_core", + "zerocopy", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.2", +] + +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_syscall" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-lite" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "reqwest" +version = "0.12.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2 0.4.8", + "http 1.3.1", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-native-tls", + "tower", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-registry", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.15", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "robust" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf4a6aa5f6d6888f39e980649f3ad6b666acdce1d78e95b8a2cb076e687ae30" + +[[package]] +name = "rstar" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "421400d13ccfd26dfa5858199c30a5d76f9c54e0dba7575273025b43c5175dbb" +dependencies = [ + "heapless", + "num-traits", + "smallvec", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustix" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e56a18552996ac8d29ecc3b190b4fdbb2d91ca4ec396de7bbffaf43f3d637e96" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustls" +version = "0.23.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "822ee9188ac4ec04a2f0531e55d035fb2de73f18b41a63c70c2712503b6fb13c" +dependencies = [ + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" + +[[package]] +name = "rustls-webpki" +version = "0.103.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "schannel" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" + +[[package]] +name = "socket2" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "spade" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ece03ff43cd2a9b57ebf776ea5e78bd30b3b4185a619f041079f4109f385034" +dependencies = [ + "hashbrown", + "num-traits", + "robust", + "smallvec", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tempfile" +version = "3.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" +dependencies = [ + "fastrand", + "getrandom 0.3.2", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "time" +version = "0.3.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" + +[[package]] +name = "time-macros" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.44.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f382da615b842244d4b8738c82ed1275e6c5dd90c459a30941cd07080b06c91a" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b9590b93e6fcc1739458317cccd391ad3955e2bde8913edf6f95f9e65a8f034" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-link" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" + +[[package]] +name = "windows-registry" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets 0.53.0", +] + +[[package]] +name = "windows-result" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] + +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.15+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..04fab26 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "df-cache" +version = "0.1.0" +edition = "2024" + +[dependencies] +tokio = {version = "1.44.1", features = ["macros", "rt-multi-thread", "time"]} +actix-web = { version = "4.10.2" } +reqwest = {version = "0.12.15", features = ["json"]} +serde = {version = "1.0.219", features = ["derive"]} +chrono = {version = "0.4.40", features = ["serde"]} +rand = "0.9.0" +env_logger = "0.11.7" +log = "0.4.27" +geo = { version = "0.30.0" } diff --git a/Dockerfile b/Dockerfile index 1e2251c..002504d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,13 +1,15 @@ -FROM node:alpine +FROM rust:1.85-bookworm AS builder -WORKDIR /opt/datafeed_cache +WORKDIR /tmp/dfcache +COPY . . -ARG NODE_ENV=production +RUN cargo build --release -COPY package*.json ./ +FROM debian:bookworm-20210816-slim +RUN apt update && apt install -y openssl ca-certificates && rm -rf /var/lib/apt/lists/* +COPY --from=builder /tmp/dfcache/target/release/df-cache /usr/local/bin/df-cache -RUN npm install --quiet --unsafe-perm --no-progress --no-audit +EXPOSE 8000 -COPY . . +CMD ["df-cache"] -CMD npm run start:prod \ No newline at end of file diff --git a/config.ts b/config.ts deleted file mode 100644 index f5c93ab..0000000 --- a/config.ts +++ /dev/null @@ -1,11 +0,0 @@ -import * as dotenv from 'dotenv'; - -dotenv.config(); - -export const Config = { - APP_HOST: process.env.APP_HOST ?? '0.0.0.0', - APP_PORT: Number(process.env.APP_PORT), - DATAFEED_REFRESH_INTERVAL_S: Number( - process.env.DATAFEED_REFRESH_INTERVAL_S ?? '15' - ), -}; diff --git a/controller/DatafeedController.ts b/controller/DatafeedController.ts deleted file mode 100644 index 1acb65c..0000000 --- a/controller/DatafeedController.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { Request, Response } from 'express'; -import DatafeedService from '../service/DatafeedService'; - -async function getDatafeed(request: Request, response: Response) { - const datafeed = await DatafeedService.getCachedDatafeed(); - const failed = DatafeedService.getUpdateFailed(); - - response.send({ - data: datafeed, - failed: failed, - }); -} - -async function getDatafeedGeneral(request: Request, response: Response) { - const datafeed = await DatafeedService.getCachedDatafeed(); - const failed = DatafeedService.getUpdateFailed(); - - response.send({ - data: datafeed?.general, - controller_length: datafeed?.controllers.length, - pilots_length: datafeed?.pilots.length, - atis_length: datafeed?.atis.length, - failed: failed, - }); -} - -async function getDatafeedControllers(request: Request, response: Response) { - const datafeed = await DatafeedService.getCachedDatafeed(); - const failed = DatafeedService.getUpdateFailed(); - - response.send({ - data: datafeed?.controllers, - length: datafeed?.controllers.length, - failed: failed, - }); -} - -async function getDatafeedPilots(request: Request, response: Response) { - const datafeed = await DatafeedService.getCachedDatafeed(); - const failed = DatafeedService.getUpdateFailed(); - - response.send({ - data: datafeed?.pilots, - length: datafeed?.pilots.length, - failed: failed, - }); -} - -async function getDatafeedAtis(request: Request, response: Response) { - const datafeed = await DatafeedService.getCachedDatafeed(); - const failed = DatafeedService.getUpdateFailed(); - - response.send({ - data: datafeed?.atis, - length: datafeed?.atis.length, - failed: failed, - }); -} - -async function getDatafeedServers(request: Request, response: Response) { - const datafeed = await DatafeedService.getCachedDatafeed(); - const failed = DatafeedService.getUpdateFailed(); - - response.send({ - data: datafeed?.servers, - length: datafeed?.servers.length, - failed: failed, - }); -} - -async function getDatafeedPilotRatings(request: Request, response: Response) { - const datafeed = await DatafeedService.getCachedDatafeed(); - const failed = DatafeedService.getUpdateFailed(); - - response.send({ - data: datafeed?.pilot_ratings, - length: datafeed?.pilot_ratings.length, - failed: failed, - }); -} - -async function getDatafeedMilitaryRatings( - request: Request, - response: Response -) { - const datafeed = await DatafeedService.getCachedDatafeed(); - const failed = DatafeedService.getUpdateFailed(); - - response.send({ - data: datafeed?.military_ratings, - length: datafeed?.military_ratings.length, - failed: failed, - }); -} - -export default { - getDatafeed, - getDatafeedGeneral, - getDatafeedControllers, - getDatafeedPilots, - getDatafeedAtis, - getDatafeedServers, - getDatafeedPilotRatings, - getDatafeedMilitaryRatings, -}; diff --git a/controller/DatafeedVatgerController.ts b/controller/DatafeedVatgerController.ts deleted file mode 100644 index 90aea29..0000000 --- a/controller/DatafeedVatgerController.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { Request, Response } from 'express'; -import pointInPolygon from 'point-in-polygon'; -import { - DatafeedModel, - DatafeedModelAtis, - DatafeedModelController, - DatafeedModelPilot, -} from '../models/DatafeedModel'; -import DatafeedService from '../service/DatafeedService'; - -// Polygon representing Germany (low poly) -const germanyPolygon: Array> = [ - [47.610078, 7.476857], - [48.97135, 8.189489], - [49.466566, 6.380295], - [51.028373, 5.932113], - [54.84348, 7.145719], - [54.642252, 14.133224], - [50.843505, 14.750028], - [50.249433, 12.140394], - [48.675772, 13.854829], - [47.475928, 12.926649], -]; - -async function getVatgerControllers(request: Request, response: Response) { - const datafeed: DatafeedModel | null = - await DatafeedService.getCachedDatafeed(); - const failed = DatafeedService.getUpdateFailed(); - if (datafeed == null) { - response.status(500).send({}); - return; - } - - const atc: DatafeedModelController[] = datafeed.controllers.filter( - (c: DatafeedModelController) => { - return ( - (c.callsign.startsWith('ED') || c.callsign.startsWith('ET')) && - c.frequency != '199.998' - ); - } - ); - - response.send({ - data: atc, - length: atc.length, - failed: failed, - }); -} - -async function getVatgerPilots(request: Request, response: Response) { - const datafeed: DatafeedModel | null = - await DatafeedService.getCachedDatafeed(); - const failed = DatafeedService.getUpdateFailed(); - if (datafeed == null) { - response.status(500).send({}); - return; - } - - const pilots: DatafeedModelPilot[] = datafeed.pilots.filter( - (p: DatafeedModelPilot) => { - // TODO: Should this take too long in the future, we can bounds check lat & lon to a square over Germany. - // Only if the plane is in this rect, we check if it's actually in the poly. - return pointInPolygon([p.latitude, p.longitude], germanyPolygon); - } - ); - - response.send({ - data: pilots, - length: pilots.length, - failed: failed, - }); -} - -async function getVatgerAtis(request: Request, response: Response) { - const datafeed: DatafeedModel | null = - await DatafeedService.getCachedDatafeed(); - const failed = DatafeedService.getUpdateFailed(); - if (datafeed == null) { - response.status(500).send({}); - return; - } - - const atis: DatafeedModelAtis[] = datafeed.atis.filter( - (a: DatafeedModelAtis) => { - return ( - (a.callsign.startsWith('ED') || a.callsign.startsWith('ET')) && - a.frequency != '199.998' - ); - } - ); - - response.send({ - data: atis, - length: atis.length, - failed: failed, - }); -} - -export default { - getVatgerControllers, - getVatgerPilots, - getVatgerAtis, -}; diff --git a/docker-compose.yml b/docker-compose.yml index 6313bfe..4b37727 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,15 +1,11 @@ -version: '3.8' +version: "1" services: - dfcache: + df-cache: build: context: . dockerfile: Dockerfile - env_file: - - .env ports: - - '8000:8000/tcp' + - "8000:8000/tcp" environment: - - APP_HOST - - APP_PORT - - DATAFEED_REFRESH_INTERVAL_S \ No newline at end of file + - RUST_LOG=info \ No newline at end of file diff --git a/index.ts b/index.ts deleted file mode 100644 index 5c97a67..0000000 --- a/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -import express, { Express } from 'express'; -import { GlobalRouter } from './routes'; -import DatafeedService from './service/DatafeedService'; -import { Config } from './config'; - -const application: Express = express(); - -setInterval(async () => { - await DatafeedService.getDatafeed(); -}, 1000 * Config.DATAFEED_REFRESH_INTERVAL_S); - -application.listen(Config.APP_PORT, Config.APP_HOST, () => { - console.log(`Listening on ${Config.APP_HOST}:${Config.APP_PORT}`); -}); - -application.use('/', GlobalRouter); diff --git a/models/DatafeedModel.ts b/models/DatafeedModel.ts deleted file mode 100644 index 48b1a5a..0000000 --- a/models/DatafeedModel.ts +++ /dev/null @@ -1,135 +0,0 @@ -export type DatafeedModel = { - general: DatafeedModelGeneral; - pilots: Array; - controllers: Array; - atis: Array; - servers: Array; - prefiles: Array; - facilities: Array; - ratings: Array; - pilot_ratings: Array; - military_ratings: Array; -}; - -export type DatafeedModelGeneral = { - version: number; - reload: number; - update: string; - update_timestamp: string; - connected_clients: number; - unique_users: number; -}; - -export type DatafeedModelPilot = { - cid: number; - name: string; - callsign: string; - server: string; - pilot_rating: number; - military_rating: number; - latitude: number; - longitude: number; - altitude: number; - groundspeed: number; - transponder: string; - heading: number; - qnh_i_hg: number; - qnh_mb: number; - - flight_plan: DatafeedModelPilotFlightPlan; - - logon_time: string; - last_updated: string; -}; - -export type DatafeedModelPrefile = { - cid: number; - name: string; - callsign: string; - - flight_plan: DatafeedModelPilotFlightPlan; - - last_updated: string; -}; - -type DatafeedModelPilotFlightPlan = { - flight_rules: string; - aircraft: string; - aircraft_faa: string; - aircraft_short: string; - departure: string; - arrival: string; - alternate: string; - cruise_tas: string; - altitude: string; - deptime: string; - enroute_time: string; - fuel_time: string; - remarks: string; - route: string; - revision_id: number; - assigned_transponder: string; -}; - -export type DatafeedModelController = { - cid: number; - name: string; - callsign: string; - frequency: string; - facility: number; - rating: number; - server: string; - visual_range: number; - text_atis?: Array; - last_updated: string; - logon_time: string; -}; - -export type DatafeedModelAtis = { - cid: number; - name: string; - callsign: string; - frequency: string; - facility: number; - rating: number; - server: string; - visual_range: number; - atis_code: string; - text_atis: Array; - last_updated: string; - logon_time: string; -}; - -export type DatafeedModelServer = { - ident: string; - hostname_or_ip: string; - location: string; - name: string; - clients_connection_allowed: number; - client_connections_allowed: boolean; - is_sweatbox: boolean; -}; - -export type DatafeedModelFacility = { - id: number; - short: string; - long: string; -}; - -export type DatafeedModelRating = { - id: number; - short: string; - long: string; -}; - -export type DatafeedModelPilotRating = { - id: number; - short_name: string; - long_name: string; -}; - -export type DatafeedModelMilitaryRating = { - id: number; - short_name: string; - long_name: string; -}; diff --git a/models/VatsimStatusModel.ts b/models/VatsimStatusModel.ts deleted file mode 100644 index e0a2008..0000000 --- a/models/VatsimStatusModel.ts +++ /dev/null @@ -1,11 +0,0 @@ -export type VatsimStatusModel = { - data: { - v3: Array; - transceivers: Array; - servers: Array; - servers_sweatbox: Array; - servers_all: Array; - }; - user: Array; - metar: Array; -}; diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index d678d3d..0000000 --- a/package-lock.json +++ /dev/null @@ -1,1228 +0,0 @@ -{ - "name": "datafeed-cache", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "datafeed-cache", - "version": "1.0.0", - "license": "ISC", - "dependencies": { - "@types/express": "^4.17.17", - "@types/point-in-polygon": "^1.1.1", - "axios": "^1.4.0", - "dotenv": "^16.0.3", - "express": "^4.18.2", - "point-in-polygon": "^1.1.0", - "rimraf": "^5.0.0", - "typescript": "^5.0.4" - } - }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "optional": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/@types/body-parser": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", - "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", - "dependencies": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "node_modules/@types/connect": { - "version": "3.4.35", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", - "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/express": { - "version": "4.17.17", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.17.tgz", - "integrity": "sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==", - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.33", - "@types/qs": "*", - "@types/serve-static": "*" - } - }, - "node_modules/@types/express-serve-static-core": { - "version": "4.17.34", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.34.tgz", - "integrity": "sha512-fvr49XlCGoUj2Pp730AItckfjat4WNb0lb3kfrLWffd+RLeoGAMsq7UOy04PAPtoL01uKwcp6u8nhzpgpDYr3w==", - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" - } - }, - "node_modules/@types/mime": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", - "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" - }, - "node_modules/@types/node": { - "version": "20.0.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.0.0.tgz", - "integrity": "sha512-cD2uPTDnQQCVpmRefonO98/PPijuOnnEy5oytWJFPY1N9aJCz2wJ5kSGWO+zJoed2cY2JxQh6yBuUq4vIn61hw==" - }, - "node_modules/@types/point-in-polygon": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/point-in-polygon/-/point-in-polygon-1.1.1.tgz", - "integrity": "sha512-DDl4uxqbpRsBYg+youv7AbWsED4eycsPiSD+AaB+NAkFjHKHTuij55K29R8rFgqeoeazi/5syNW5J2uNnpS+Ow==" - }, - "node_modules/@types/qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" - }, - "node_modules/@types/range-parser": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", - "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" - }, - "node_modules/@types/send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.1.tgz", - "integrity": "sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==", - "dependencies": { - "@types/mime": "^1", - "@types/node": "*" - } - }, - "node_modules/@types/serve-static": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.1.tgz", - "integrity": "sha512-NUo5XNiAdULrJENtJXZZ3fHtfMolzZwczzBbnAeBbqBwG+LaG6YaJtuwzwGSQZ2wsCrxjEhNNjAkKigy3n8teQ==", - "dependencies": { - "@types/mime": "*", - "@types/node": "*" - } - }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "node_modules/axios": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz", - "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==", - "dependencies": { - "follow-redirects": "^1.15.0", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "node_modules/body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.1", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/dotenv": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", - "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==", - "engines": { - "node": ">=12" - } - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" - }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" - }, - "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.1", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.5.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.2.0", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.7", - "qs": "6.11.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/foreground-child": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", - "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", - "dependencies": { - "cross-spawn": "^7.0.0", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "node_modules/get-intrinsic": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", - "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", - "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/glob": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.2.2.tgz", - "integrity": "sha512-Xsa0BcxIC6th9UwNjZkhrMtNo/MnyRL8jGCP+uEwhA5oFOCY1f2s1/oNKY47xQ0Bg5nkjsfAEIej1VeH62bDDQ==", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^2.0.3", - "minimatch": "^9.0.0", - "minipass": "^5.0.0", - "path-scurry": "^1.7.0" - }, - "bin": { - "glob": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" - }, - "node_modules/jackspeak": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.2.0.tgz", - "integrity": "sha512-r5XBrqIJfwRIjRt/Xr5fv9Wh09qyhHfKnYddDlpM+ibRR20qrYActpCAgU6U+d53EOEjzkvxPMVHSlgR7leXrQ==", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/lru-cache": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-9.1.1.tgz", - "integrity": "sha512-65/Jky17UwSb0BuB9V+MyDpsOtXKmYwzhyl+cOa9XUiI4uV2Ouy/2voFP3+al0BjZbJgMBD8FojMpAf+Z+qn4A==", - "engines": { - "node": "14 || >=16.14" - } - }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/minimatch": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.0.tgz", - "integrity": "sha512-0jJj8AvgKqWN05mrwuqi8QYKx1WmYSUoKSxu5Qhs9prezTz10sxAHGNZe9J9cqIJzta8DWsleh2KaVaLl6Ru2w==", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/object-inspect": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-scurry": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.7.0.tgz", - "integrity": "sha512-UkZUeDjczjYRE495+9thsgcVgsaCPkaw80slmfVFgllxY+IO8ubTsOpFVjDPROBqJdHfVPUFRHPBV/WciOVfWg==", - "dependencies": { - "lru-cache": "^9.0.0", - "minipass": "^5.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" - }, - "node_modules/point-in-polygon": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/point-in-polygon/-/point-in-polygon-1.1.0.tgz", - "integrity": "sha512-3ojrFwjnnw8Q9242TzgXuTD+eKiutbzyslcq1ydfu82Db2y+Ogbmyrkpv0Hgj31qwT3lbS9+QAAO/pIQM35XRw==" - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" - }, - "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/rimraf": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.0.tgz", - "integrity": "sha512-Jf9llaP+RvaEVS5nPShYFhtXIrb3LRKP281ib3So0KkeZKo2wIKyq0Re7TOSwanasA423PSr6CCIL4bP6T040g==", - "dependencies": { - "glob": "^10.0.0" - }, - "bin": { - "rimraf": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", - "dependencies": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.18.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "engines": { - "node": ">=8" - } - }, - "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/signal-exit": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.0.1.tgz", - "integrity": "sha512-uUWsN4aOxJAS8KOuf3QMyFtgm1pkb6I+KRZbRF/ghdf5T7sM+B1lLLzPDxswUjkmHyxQAVzEgG35E3NzDM9GVw==", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "node_modules/string-width-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", - "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/typescript": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz", - "integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=12.20" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "node_modules/wrap-ansi-cjs/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - } - } -} diff --git a/package.json b/package.json deleted file mode 100644 index 1d34d0d..0000000 --- a/package.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "datafeed-cache", - "version": "1.0.0", - "description": "", - "main": "index.js", - "scripts": { - "start:dev": "tsc && node ./dist/index.js", - "build": "rimraf ./dist && tsc", - "start:prod": "npm run build && node dist/index.js" - }, - "keywords": [], - "author": "", - "license": "ISC", - "dependencies": { - "@types/express": "^4.17.17", - "@types/point-in-polygon": "^1.1.1", - "axios": "^1.4.0", - "dotenv": "^16.0.3", - "express": "^4.18.2", - "point-in-polygon": "^1.1.0", - "rimraf": "^5.0.0", - "typescript": "^5.0.4" - } -} diff --git a/routes.ts b/routes.ts deleted file mode 100644 index 59c5b12..0000000 --- a/routes.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Router } from 'express'; -import DatafeedController from './controller/DatafeedController'; -import DatafeedVatgerController from './controller/DatafeedVatgerController'; - -export const GlobalRouter = Router(); - -GlobalRouter.get('/datafeed', DatafeedController.getDatafeed); -GlobalRouter.get('/datafeed/general', DatafeedController.getDatafeedGeneral); -GlobalRouter.get( - '/datafeed/controllers', - DatafeedController.getDatafeedControllers -); -GlobalRouter.get('/datafeed/pilots', DatafeedController.getDatafeedPilots); -GlobalRouter.get('/datafeed/atis', DatafeedController.getDatafeedAtis); -GlobalRouter.get('/datafeed/servers', DatafeedController.getDatafeedServers); -GlobalRouter.get( - '/datafeed/pilot_ratings', - DatafeedController.getDatafeedPilotRatings -); -GlobalRouter.get( - '/datafeed/military_ratings', - DatafeedController.getDatafeedMilitaryRatings -); - -// VATGER Specific Routes -GlobalRouter.get( - '/datafeed/controllers/ger', - DatafeedVatgerController.getVatgerControllers -); -GlobalRouter.get( - '/datafeed/pilots/ger', - DatafeedVatgerController.getVatgerPilots -); -GlobalRouter.get('/datafeed/atis/ger', DatafeedVatgerController.getVatgerAtis); diff --git a/service/DatafeedService.ts b/service/DatafeedService.ts deleted file mode 100644 index e7170df..0000000 --- a/service/DatafeedService.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { DatafeedModel } from '../models/DatafeedModel'; -import axios, { AxiosResponse } from 'axios'; -import { VatsimStatusModel } from '../models/VatsimStatusModel'; - -type DatafeedStatus = { - url: string | null; - last_queried_url: Date | null; - data: DatafeedModel | null; - last_update_failed: boolean; -}; - -let datafeedStatus: DatafeedStatus = { - url: null, - last_queried_url: null, - data: null, - last_update_failed: false, -}; - -let same_timestamp_count = 0; - -async function getDatafeedURL(): Promise { - // Check if we need to query URL again - if (datafeedStatus.url != null) { - return true; - } - - console.log('Querying Status'); - - try { - const res: AxiosResponse = await axios.get( - 'https://status.vatsim.net/status.json' - ); - const status: VatsimStatusModel = res.data as VatsimStatusModel; - - datafeedStatus.last_queried_url = new Date(); - datafeedStatus.url = - status.data.v3[Math.floor(Math.random() * status.data.v3.length)]; - return true; - } catch (e) { - console.error('Failed to retrieve Datafeed URL!'); - return false; - } -} - -async function getDatafeed(): Promise { - if (!(await getDatafeedURL()) || datafeedStatus.url == null) { - return null; - } - - console.log(new Date().toISOString(), 'Updating Datafeed'); - - let res: AxiosResponse | undefined = undefined; - try { - res = await axios.get(datafeedStatus.url); - - // Check for valid JSON - JSON.parse(JSON.stringify((res?.data).general)); - } catch (e: any) { - console.error('Failed to update Datafeed: ', e.message); - datafeedStatus.last_update_failed = true; - return null; - } - - // Check if response looks good - if (res == null || res.data == null) { - return null; - } - - const df = res.data as DatafeedModel; - - if (datafeedStatus.data?.general.update_timestamp == df.general.update_timestamp) { - same_timestamp_count++; - } else { - same_timestamp_count = 0; - } - - // Check if both update timestamps are equal -> No update - // The chance that zero pilots are online is minuscule, such that we can conclude a failed datafeed update if there are no pilots - // Also, if we queried multiple times and the same timestamp was found 6 times, then we can be pretty sure that the datafeed failed - if ( - (datafeedStatus.data != null && - (Math.abs(datafeedStatus.data.pilots.length - df.pilots.length) > 500) || df.pilots.length == 0 || same_timestamp_count > 5) - ) { - console.error( - '\t Update failed! Previous Pilot Count: ', - datafeedStatus.data?.pilots.length, - ' | Current Pilot Count: ', - df.pilots.length, - ' | Same Timestamp count: ', - same_timestamp_count - ); - datafeedStatus.last_update_failed = true; - return datafeedStatus.data; - } - - // Set values if all looks fine - datafeedStatus.data = df; - datafeedStatus.last_update_failed = false; - - console.log( - `\t Done: ${datafeedStatus.data.controllers.length} Controllers, ${datafeedStatus.data.pilots.length} Pilots, Timestamp: ${datafeedStatus.data.general.update_timestamp} (Same Timestamp: ${same_timestamp_count})` - ); - - return datafeedStatus.data; -} - -async function getCachedDatafeed(): Promise { - // Check if cached value is empty - if (datafeedStatus.data != null) { - return datafeedStatus.data; - } - - return await getDatafeed(); -} - -function getUpdateFailed(): boolean { - return datafeedStatus.last_update_failed; -} - -export default { - getDatafeed, - getCachedDatafeed, - getUpdateFailed, -}; diff --git a/src/api/handlers.rs b/src/api/handlers.rs new file mode 100644 index 0000000..b5fa054 --- /dev/null +++ b/src/api/handlers.rs @@ -0,0 +1,206 @@ +use crate::api::state::ApiStateData; +use crate::api::types::{ + DatafeedGeneralResponse, DatafeedGerListResponse, DatafeedListResponse, DatafeedResponse, +}; +use crate::datafeed::types::{ + DatafeedAtis, DatafeedController, DatafeedMilitaryRating, DatafeedPilot, DatafeedPilotRating, + DatafeedServer, +}; +use actix_web::HttpResponse; +use geo::{Contains, Coord}; + +#[actix_web::get("")] +async fn get_datafeed(data: ApiStateData) -> HttpResponse { + let read_lock = data.shared_state.read().await; + let status = &*read_lock; + + HttpResponse::Ok().json(DatafeedResponse { + data: &status.data, + failed: status.failed, + }) +} + +#[actix_web::get("/general")] +async fn get_general_datafeed(data: ApiStateData) -> HttpResponse { + let read_lock = data.shared_state.read().await; + let status = &*read_lock; + + let datafeed = status.data.as_ref(); + + HttpResponse::Ok().json(DatafeedGeneralResponse { + data: datafeed.map_or(None, |df| Some(&df.general)), + controller_length: datafeed.map_or(0, |df| df.controllers.len()), + pilots_length: datafeed.map_or(0, |df| df.pilots.len()), + atis_length: datafeed.map_or(0, |df| df.atis.len()), + failed: status.failed, + }) +} + +#[actix_web::get("/controllers")] +async fn get_controllers_datafeed(data: ApiStateData) -> HttpResponse { + let read_lock = data.shared_state.read().await; + let status = &*read_lock; + + let controllers: &[DatafeedController] = status + .data + .as_ref() + .map_or(&[], |df| df.controllers.as_slice()); + + HttpResponse::Ok().json(DatafeedListResponse { + data: controllers, + length: controllers.len(), + failed: status.failed, + }) +} + +#[actix_web::get("/pilots")] +async fn get_pilots_datafeed(data: ApiStateData) -> HttpResponse { + let read_lock = data.shared_state.read().await; + let status = &*read_lock; + + let pilots: &[DatafeedPilot] = status.data.as_ref().map_or(&[], |df| df.pilots.as_slice()); + + HttpResponse::Ok().json(DatafeedListResponse { + data: pilots, + length: pilots.len(), + failed: status.failed, + }) +} + +#[actix_web::get("/atis")] +async fn get_atis_datafeed(data: ApiStateData) -> HttpResponse { + let read_lock = data.shared_state.read().await; + let status = &*read_lock; + + let atis_slice: &[DatafeedAtis] = status.data.as_ref().map_or(&[], |df| df.atis.as_slice()); + + HttpResponse::Ok().json(DatafeedListResponse { + data: atis_slice, + length: atis_slice.len(), + failed: status.failed, + }) +} + +#[actix_web::get("/server")] +async fn get_servers_datafeed(data: ApiStateData) -> HttpResponse { + let read_lock = data.shared_state.read().await; + let status = &*read_lock; + + let servers: &[DatafeedServer] = status.data.as_ref().map_or(&[], |df| df.servers.as_slice()); + + HttpResponse::Ok().json(DatafeedListResponse { + data: servers, + length: servers.len(), + failed: status.failed, + }) +} + +#[actix_web::get("/pilot_ratings")] +async fn get_pilot_ratings_datafeed(data: ApiStateData) -> HttpResponse { + let read_lock = data.shared_state.read().await; + let status = &*read_lock; + + let pilot_ratings: &[DatafeedPilotRating] = status + .data + .as_ref() + .map_or(&[], |df| df.pilot_ratings.as_slice()); + + HttpResponse::Ok().json(DatafeedListResponse { + data: pilot_ratings, + length: pilot_ratings.len(), + failed: status.failed, + }) +} + +#[actix_web::get("/military_ratings")] +async fn get_mil_pilot_ratings_datafeed(data: ApiStateData) -> HttpResponse { + let read_lock = data.shared_state.read().await; + let status = &*read_lock; + + let military_ratings: &[DatafeedMilitaryRating] = status + .data + .as_ref() + .map_or(&[], |df| df.military_ratings.as_slice()); + + HttpResponse::Ok().json(DatafeedListResponse { + data: military_ratings, + length: military_ratings.len(), + failed: status.failed, + }) +} + +/// +/// Below contains the handlers for the endpoints specific to VATSIM-Germany. +/// +/// /datafeed/ger/[...] +/// + +#[actix_web::get("/controllers/ger")] +async fn get_ger_controllers_datafeed(data: ApiStateData) -> HttpResponse { + let read_lock = data.shared_state.read().await; + let status = &*read_lock; + + let controllers = status.data.as_ref().map_or(Vec::new(), |df| { + df.controllers + .iter() + .filter(|controller| { + (controller.callsign.starts_with("ED") || controller.callsign.starts_with("ET")) + && controller.frequency != "199.998" + }) + .cloned() + .collect() + }); + + HttpResponse::Ok().json(DatafeedGerListResponse { + data: &controllers, + length: controllers.len(), + failed: status.failed, + }) +} + +#[actix_web::get("/pilots/ger")] +async fn get_ger_pilots_datafeed(data: ApiStateData) -> HttpResponse { + let read_lock = data.shared_state.read().await; + let status = &*read_lock; + + let pilots = status.data.as_ref().map_or(Vec::new(), |df| { + df.pilots + .iter() + .filter(|pilot| { + let coord: Coord = + Coord::from((pilot.latitude.into(), pilot.longitude.into())); + data.ger_poly.contains(&coord) + }) + .cloned() + .collect() + }); + + HttpResponse::Ok().json(DatafeedGerListResponse { + data: &pilots, + length: pilots.len(), + failed: status.failed, + }) +} + +#[actix_web::get("/pilots/ger")] +async fn get_ger_atis_datafeed(data: ApiStateData) -> HttpResponse { + let read_lock = data.shared_state.read().await; + let status = &*read_lock; + + let atis = status.data.as_ref().map_or(Vec::new(), |df| { + df.atis + .iter() + .filter(|atis| { + (atis.callsign.starts_with("ED") || atis.callsign.starts_with("ET")) + && atis.frequency != "199.998" + }) + .cloned() + .collect() + }); + + HttpResponse::Ok().json(DatafeedGerListResponse { + data: &atis, + length: atis.len(), + failed: status.failed, + }) +} diff --git a/src/api/mod.rs b/src/api/mod.rs new file mode 100644 index 0000000..84e9217 --- /dev/null +++ b/src/api/mod.rs @@ -0,0 +1,33 @@ +use crate::api::state::ApiState; +use crate::datafeed::DatafeedSharedState; +use actix_web::{App, HttpServer, web}; + +mod handlers; +mod state; +pub(crate) mod types; + +pub(crate) async fn init_api(shared_datafeed: DatafeedSharedState) -> std::io::Result<()> { + HttpServer::new(move || { + let shared_datafeed = shared_datafeed.clone(); + + App::new() + .app_data(web::Data::new(ApiState::new(shared_datafeed))) + .service( + web::scope("/datafeed") + .service(handlers::get_datafeed) + .service(handlers::get_general_datafeed) + .service(handlers::get_controllers_datafeed) + .service(handlers::get_pilots_datafeed) + .service(handlers::get_atis_datafeed) + .service(handlers::get_servers_datafeed) + .service(handlers::get_pilot_ratings_datafeed) + .service(handlers::get_mil_pilot_ratings_datafeed) + .service(handlers::get_ger_controllers_datafeed) + .service(handlers::get_ger_pilots_datafeed) + .service(handlers::get_ger_atis_datafeed), + ) + }) + .bind("0.0.0.0:8000")? + .run() + .await +} diff --git a/src/api/state.rs b/src/api/state.rs new file mode 100644 index 0000000..1ef1f26 --- /dev/null +++ b/src/api/state.rs @@ -0,0 +1,31 @@ +use crate::datafeed::DatafeedSharedState; +use geo::{LineString, Polygon}; + +const GERMANY_POLY: &[(f64, f64)] = &[ + (47.610078, 7.476857), + (48.97135, 8.189489), + (49.466566, 6.380295), + (51.028373, 5.932113), + (54.84348, 7.145719), + (54.642252, 14.133224), + (50.843505, 14.750028), + (50.249433, 12.140394), + (48.675772, 13.854829), + (47.475928, 12.926649), +]; + +pub(crate) type ApiStateData = actix_web::web::Data; + +pub(crate) struct ApiState { + pub(crate) shared_state: DatafeedSharedState, + pub(crate) ger_poly: Polygon, +} + +impl ApiState { + pub(crate) fn new(shared_state: DatafeedSharedState) -> Self { + Self { + shared_state, + ger_poly: Polygon::new(LineString::from(GERMANY_POLY.to_vec()), vec![]), + } + } +} diff --git a/src/api/types.rs b/src/api/types.rs new file mode 100644 index 0000000..89ce5b2 --- /dev/null +++ b/src/api/types.rs @@ -0,0 +1,32 @@ +use crate::datafeed::datafeed::Datafeed; +use crate::datafeed::types::DatafeedGeneral; +use serde::Serialize; + +#[derive(Serialize)] +pub(crate) struct DatafeedResponse<'a> { + pub(crate) data: &'a Option, + pub(crate) failed: bool, +} + +#[derive(Serialize)] +pub(crate) struct DatafeedGeneralResponse<'a> { + pub(crate) data: Option<&'a DatafeedGeneral>, + pub(crate) controller_length: usize, + pub(crate) pilots_length: usize, + pub(crate) atis_length: usize, + pub(crate) failed: bool, +} + +#[derive(Serialize)] +pub(crate) struct DatafeedListResponse<'a, T> { + pub(crate) data: &'a [T], + pub(crate) length: usize, + pub(crate) failed: bool, +} + +#[derive(Serialize)] +pub(crate) struct DatafeedGerListResponse<'a, T> { + pub(crate) data: &'a Vec, + pub(crate) length: usize, + pub(crate) failed: bool, +} diff --git a/src/datafeed/datafeed.rs b/src/datafeed/datafeed.rs new file mode 100644 index 0000000..a61b5f4 --- /dev/null +++ b/src/datafeed/datafeed.rs @@ -0,0 +1,84 @@ +use crate::datafeed::DatafeedSharedState; +use crate::datafeed::types::{ + DatafeedAtis, DatafeedController, DatafeedFacility, DatafeedGeneral, DatafeedMilitaryRating, + DatafeedPilot, DatafeedPilotRating, DatafeedPrefile, DatafeedRating, DatafeedServer, +}; +use log::{error, info}; +use serde::{Deserialize, Serialize}; +use std::error::Error; + +const UPDATE_DATAFEED_INTERVAL_SECS: u64 = 15; + +#[derive(Serialize, Deserialize, Debug)] +pub(crate) struct Datafeed { + pub general: DatafeedGeneral, + pub pilots: Vec, + pub controllers: Vec, + pub atis: Vec, + pub servers: Vec, + pub prefiles: Vec, + pub facilities: Vec, + pub ratings: Vec, + pub pilot_ratings: Vec, + pub military_ratings: Vec, +} + +impl Datafeed { + async fn download_from_url(url: &str) -> Result { + reqwest::get(url).await?.json().await + } + + fn is_failed(&self, same_timestamp_count: &mut u32, previous: &Option) -> bool { + match previous { + Some(previous) => { + if self.general.update_timestamp == previous.general.update_timestamp { + *same_timestamp_count += 1; + info!("Same timestamp count: {}", same_timestamp_count); + } else { + *same_timestamp_count = 0; + } + + if self.pilots.len() == 0 + || self.pilots.len().abs_diff(previous.pilots.len()) > 500 + || *same_timestamp_count > 5 + { + return true; + } + + false + } + None => false, + } + } +} + +pub(crate) async fn update_datafeed_loop(datafeed_url: String, shared_state: DatafeedSharedState) { + let mut same_timestamp_count: u32 = 0; + let mut interval = tokio::time::interval(tokio::time::Duration::from_secs( + UPDATE_DATAFEED_INTERVAL_SECS, + )); + + loop { + interval.tick().await; + + match Datafeed::download_from_url(datafeed_url.as_str()).await { + Ok(datafeed) => { + let mut write_guard = shared_state.write().await; + if datafeed.is_failed(&mut same_timestamp_count, &write_guard.data) { + error!("Failed to update datafeed"); + write_guard.failed = true; + continue; + } + + info!("{:?}", datafeed.general); + write_guard.data = Some(datafeed); + write_guard.failed = false; + } + Err(err) => { + error!("Failed to download or parse response: {:?}", err.source()); + let mut write_guard = shared_state.write().await; + write_guard.failed = true; + } + } + } +} diff --git a/src/datafeed/datafeed_status.rs b/src/datafeed/datafeed_status.rs new file mode 100644 index 0000000..8658a16 --- /dev/null +++ b/src/datafeed/datafeed_status.rs @@ -0,0 +1,17 @@ +use crate::datafeed::datafeed::Datafeed; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug)] +pub(crate) struct DatafeedStatus { + pub data: Option, + pub failed: bool, +} + +impl DatafeedStatus { + pub(crate) fn new() -> Self { + Self { + data: None, + failed: true, + } + } +} diff --git a/src/datafeed/mod.rs b/src/datafeed/mod.rs new file mode 100644 index 0000000..702221f --- /dev/null +++ b/src/datafeed/mod.rs @@ -0,0 +1,16 @@ +use crate::datafeed::datafeed_status::DatafeedStatus; +use std::sync::Arc; +use tokio::sync::RwLock; + +pub(crate) mod datafeed; +pub(crate) mod datafeed_status; +pub(crate) mod status; +pub(crate) mod types; + +pub(crate) type DatafeedSharedState = Arc>; + +impl From for DatafeedSharedState { + fn from(value: DatafeedStatus) -> Self { + Arc::new(RwLock::new(value)) + } +} diff --git a/src/datafeed/status.rs b/src/datafeed/status.rs new file mode 100644 index 0000000..a58181e --- /dev/null +++ b/src/datafeed/status.rs @@ -0,0 +1,53 @@ +use rand::Rng; +use serde::Deserialize; + +#[derive(Debug, Deserialize)] +struct StatusData { + v3: Vec, +} + +#[derive(Debug, Deserialize)] +pub(crate) struct VatsimStatus { + data: StatusData, +} + +const VATSIM_STATUS_URL: &'static str = "https://status.vatsim.net/status.json"; +const VATSIM_DEFAULT_DATAFEED_URL: &'static str = "https://data.vatsim.net/v3/vatsim-data.json"; + +impl VatsimStatus { + /// Retrieves a datafeed url to use from VATSIM's status page. + /// + /// # Returns + /// The selected datafeed url, or the default url in case there was an issue fetching the + /// newest status.json file. + pub(crate) async fn get_datafeed_url() -> String { + let response = reqwest::get(VATSIM_STATUS_URL).await; + let status: VatsimStatus = match response { + Ok(res) => res.json::().await.unwrap_or_default(), + Err(_) => Self::default(), + }; + + Self::select_random_url(&status.data.v3) + } + + /// Selects a random string from a vector of strings. This is to be done as per VATSIM's + /// documentation in order to balance the load on the datafeed if this should become an issue + /// in the future + /// + /// # Returns + /// An owned version (cloned) of the selected String within the vector. + fn select_random_url(urls: &Vec) -> String { + let idx = rand::rng().random_range(0..urls.len()); + urls[idx].clone() + } +} + +impl Default for VatsimStatus { + fn default() -> Self { + Self { + data: StatusData { + v3: vec![VATSIM_DEFAULT_DATAFEED_URL.into()], + }, + } + } +} diff --git a/src/datafeed/types.rs b/src/datafeed/types.rs new file mode 100644 index 0000000..7ac7a76 --- /dev/null +++ b/src/datafeed/types.rs @@ -0,0 +1,101 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug)] +pub struct DatafeedGeneral { + pub version: u32, + pub reload: u32, + pub update_timestamp: DateTime, + pub connected_clients: u32, + pub unique_users: u32, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct DatafeedPilotFlightPlan { + pub flight_rules: String, + pub aircraft_short: String, + pub departure: String, + pub arrival: String, + pub alternate: String, + pub cruise_tas: String, + pub altitude: String, + pub deptime: String, + pub enroute_time: String, + pub fuel_time: String, + pub remarks: String, + pub route: String, + pub assigned_transponder: String, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct DatafeedPilot { + pub cid: u32, + pub name: String, + pub callsign: String, + pub server: String, + pub pilot_rating: i32, + pub military_rating: i32, + pub latitude: f32, + pub longitude: f32, + pub altitude: i32, + pub groundspeed: i32, + pub transponder: String, + pub heading: u16, + pub qnh_mb: i32, + pub flight_plan: Option, + pub logon_time: DateTime, + pub last_updated: DateTime, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct DatafeedController { + pub cid: u32, + pub name: String, + pub callsign: String, + pub frequency: String, + pub facility: i32, + pub rating: i32, + pub server: String, + pub visual_range: u32, + pub text_atis: Option>, + pub last_updated: DateTime, + pub logon_time: DateTime, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct DatafeedServer { + pub ident: String, + pub hostname_or_ip: String, + pub location: String, + pub name: String, + pub clients_connection_allowed: i32, + pub client_connections_allowed: bool, + pub is_sweatbox: bool, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct DatafeedFacility { + pub id: i32, + pub short: String, + pub long: String, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct DatafeedPilotRating { + pub id: i32, + pub short_name: String, + pub long_name: String, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct DatafeedPrefile { + pub cid: u32, + pub name: String, + pub callsign: String, + pub flight_plan: DatafeedPilotFlightPlan, + pub last_updated: DateTime, +} + +pub type DatafeedAtis = DatafeedController; +pub type DatafeedRating = DatafeedFacility; +pub type DatafeedMilitaryRating = DatafeedPilotRating; diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..6dfd9a4 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,25 @@ +use crate::api::init_api; +use crate::datafeed::DatafeedSharedState; +use crate::datafeed::datafeed_status::DatafeedStatus; +use datafeed::status::VatsimStatus; +use env_logger; + +mod api; +mod datafeed; + +#[tokio::main] +async fn main() { + env_logger::init(); + + let shared_datafeed: DatafeedSharedState = DatafeedStatus::new().into(); + let datafeed_url = VatsimStatus::get_datafeed_url().await; + + tokio::task::spawn({ + let shared_datafeed = shared_datafeed.clone(); + async move { + datafeed::datafeed::update_datafeed_loop(datafeed_url, shared_datafeed).await; + } + }); + + let _ = init_api(shared_datafeed).await; +} diff --git a/tsconfig.json b/tsconfig.json deleted file mode 100644 index 6bc9e20..0000000 --- a/tsconfig.json +++ /dev/null @@ -1,109 +0,0 @@ -{ - "compilerOptions": { - /* Visit https://aka.ms/tsconfig to read more about this file */ - - /* Projects */ - // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ - // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ - // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ - // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ - // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ - // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ - - /* Language and Environment */ - "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ - // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ - // "jsx": "preserve", /* Specify what JSX code is generated. */ - // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ - // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ - // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ - // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ - // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ - // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ - // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ - // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ - // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ - - /* Modules */ - "module": "commonjs", /* Specify what module code is generated. */ - // "rootDir": "./", /* Specify the root folder within your source files. */ - // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ - // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ - // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ - // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ - // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ - // "types": [], /* Specify type package names to be included without being referenced in a source file. */ - // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ - // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ - // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ - // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ - // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ - // "resolveJsonModule": true, /* Enable importing .json files. */ - // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ - // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ - - /* JavaScript Support */ - // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ - // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ - // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ - - /* Emit */ - // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ - // "declarationMap": true, /* Create sourcemaps for d.ts files. */ - // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ - // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ - // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ - // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ - "outDir": "./dist", /* Specify an output folder for all emitted files. */ - // "removeComments": true, /* Disable emitting comments. */ - // "noEmit": true, /* Disable emitting files from a compilation. */ - // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ - // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ - // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ - // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ - // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ - // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ - // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ - // "newLine": "crlf", /* Set the newline character for emitting files. */ - // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ - // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ - // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ - // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ - // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ - // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ - - /* Interop Constraints */ - // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ - // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ - // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ - "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ - // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ - "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ - - /* Type Checking */ - "strict": true, /* Enable all strict type-checking options. */ - // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ - // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ - // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ - // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ - // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ - // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ - // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ - // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ - // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ - // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ - // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ - // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ - // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ - // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ - // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ - // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ - // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ - // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ - - /* Completeness */ - // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ - "skipLibCheck": true /* Skip type checking all .d.ts files. */ - } -} From 6dee3b459dc54ba572151002d622c385b254b1f3 Mon Sep 17 00:00:00 2001 From: Nikolas Goerlitz Date: Sun, 30 Mar 2025 14:32:21 +0200 Subject: [PATCH 02/19] Update README.md - Started transition to Rust; not quite complete yet. --- README.md | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index a2d7b2b..d38ed2e 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ [![Docker CI/CD](https://github.com/vatger/datafeed-cache/actions/workflows/prod.docker.yml/badge.svg)](https://github.com/vatger/datafeed-cache/actions/workflows/prod.docker.yml) -[![Code-Style Check](https://github.com/vatger/datafeed-cache/actions/workflows/dev.prettier.yml/badge.svg?branch=dev)](https://github.com/vatger/datafeed-cache/actions/workflows/dev.prettier.yml) +[![Code-Style Check](https://github.com/vatger/datafeed-cache/actions/workflows/dev.prettier.yml/badge.svg)](https://github.com/vatger/datafeed-cache/actions/workflows/dev.prettier.yml) # Datafeed Cache @@ -19,21 +19,15 @@ If you wish to contribute and/or make changes, please check out our contribution | Nikolas G. - 1373921 | * | `git[at]vatger.de` | ## Prerequisites -- **Node.js** (https://nodejs.org/en) -- **Typescript** (Optional) - - Typescript is included in the project's dependencies, can however be installed globally using `npm install -g typescript`. - This may however not be necessary in your situation and is more of a recommendation rather than a requirement. - -## Running the Application - -### Using Node.js - -Assuming node.js is installed, running the application (locally) should be as simple as executing the following two commands. - -1. Run `npm install` -2. Run `npm run start:dev` +- **Rust** (tested with version 1.85.1) +- **Cargo** ### Using Docker -Using the included `docker-compose.yml` you should simply be able to build and run the application. -The included compose file exposes port `8000` on the host by default, however can be configured at will to fit any deployment scheme. +Using Docker is possibly the easiest way to deploy the application. Using the included `docker-compose.yml`, you should +simply be able to build and run the application by running `docker compose up`. +The included compose file exposes port `8000` on the host by default, however can be configured at will to fit any +deployment scheme. + +## Running the Application +`cargo run` From ef74305379734e5b44746464b2d1b520c9ef713e Mon Sep 17 00:00:00 2001 From: Nikolas Goerlitz Date: Sun, 30 Mar 2025 15:17:10 +0200 Subject: [PATCH 03/19] Fix shared state in actix handler and update fail checks - Add proper shared state in the actix handler, since the factory method for app_data is called on every worker! - Fixes #5 by failing if the time of last update is >2 minutes ago --- src/api/mod.rs | 13 +++++++++---- src/datafeed/datafeed.rs | 4 ++++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/api/mod.rs b/src/api/mod.rs index 84e9217..419b601 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,17 +1,22 @@ use crate::api::state::ApiState; use crate::datafeed::DatafeedSharedState; -use actix_web::{App, HttpServer, web}; +use actix_web::middleware::TrailingSlash::Trim; +use actix_web::{App, HttpServer, middleware, web}; mod handlers; mod state; pub(crate) mod types; pub(crate) async fn init_api(shared_datafeed: DatafeedSharedState) -> std::io::Result<()> { - HttpServer::new(move || { - let shared_datafeed = shared_datafeed.clone(); + let api_state = web::Data::new(ApiState::new(shared_datafeed)); + HttpServer::new(move || { App::new() - .app_data(web::Data::new(ApiState::new(shared_datafeed))) + .app_data(api_state.clone()) + .wrap(middleware::NormalizePath::new(Trim)) + .wrap(middleware::Logger::new( + "%a \"%r\" %s \"%{User-Agent}i\" %Ts", + )) .service( web::scope("/datafeed") .service(handlers::get_datafeed) diff --git a/src/datafeed/datafeed.rs b/src/datafeed/datafeed.rs index a61b5f4..ece31a7 100644 --- a/src/datafeed/datafeed.rs +++ b/src/datafeed/datafeed.rs @@ -3,9 +3,11 @@ use crate::datafeed::types::{ DatafeedAtis, DatafeedController, DatafeedFacility, DatafeedGeneral, DatafeedMilitaryRating, DatafeedPilot, DatafeedPilotRating, DatafeedPrefile, DatafeedRating, DatafeedServer, }; +use chrono::{TimeDelta, Utc}; use log::{error, info}; use serde::{Deserialize, Serialize}; use std::error::Error; +use std::ops::Sub; const UPDATE_DATAFEED_INTERVAL_SECS: u64 = 15; @@ -38,9 +40,11 @@ impl Datafeed { *same_timestamp_count = 0; } + let now = Utc::now().sub(TimeDelta::minutes(2)); if self.pilots.len() == 0 || self.pilots.len().abs_diff(previous.pilots.len()) > 500 || *same_timestamp_count > 5 + || (*same_timestamp_count > 0 && previous.general.update_timestamp < now) { return true; } From fcdb7d18a967a47187a3b21d030551aa3cfc3c6f Mon Sep 17 00:00:00 2001 From: Nikolas Goerlitz Date: Sun, 30 Mar 2025 15:33:09 +0200 Subject: [PATCH 04/19] Rename datafeed to vatsim - This makes more sense, since - f.e. - the status file is also queried from within this module. --- docker-compose.yml | 5 +++-- src/api/handlers.rs | 4 ++-- src/api/mod.rs | 4 ++-- src/api/state.rs | 2 +- src/api/types.rs | 4 ++-- src/main.rs | 10 +++++----- src/{datafeed => vatsim}/datafeed.rs | 6 +++--- src/{datafeed => vatsim}/datafeed_status.rs | 2 +- src/{datafeed => vatsim}/mod.rs | 2 +- src/{datafeed => vatsim}/status.rs | 6 +++--- src/{datafeed => vatsim}/types.rs | 0 11 files changed, 23 insertions(+), 22 deletions(-) rename src/{datafeed => vatsim}/datafeed.rs (95%) rename src/{datafeed => vatsim}/datafeed_status.rs (88%) rename src/{datafeed => vatsim}/mod.rs (86%) rename src/{datafeed => vatsim}/status.rs (84%) rename src/{datafeed => vatsim}/types.rs (100%) diff --git a/docker-compose.yml b/docker-compose.yml index 4b37727..0788a20 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,4 @@ -version: "1" +version: "3" services: df-cache: @@ -6,6 +6,7 @@ services: context: . dockerfile: Dockerfile ports: - - "8000:8000/tcp" + - "8007:8000/tcp" + restart: unless-stopped environment: - RUST_LOG=info \ No newline at end of file diff --git a/src/api/handlers.rs b/src/api/handlers.rs index b5fa054..7468295 100644 --- a/src/api/handlers.rs +++ b/src/api/handlers.rs @@ -2,7 +2,7 @@ use crate::api::state::ApiStateData; use crate::api::types::{ DatafeedGeneralResponse, DatafeedGerListResponse, DatafeedListResponse, DatafeedResponse, }; -use crate::datafeed::types::{ +use crate::vatsim::types::{ DatafeedAtis, DatafeedController, DatafeedMilitaryRating, DatafeedPilot, DatafeedPilotRating, DatafeedServer, }; @@ -132,7 +132,7 @@ async fn get_mil_pilot_ratings_datafeed(data: ApiStateData) -> HttpResponse { /// /// Below contains the handlers for the endpoints specific to VATSIM-Germany. /// -/// /datafeed/ger/[...] +/// /vatsim/ger/[...] /// #[actix_web::get("/controllers/ger")] diff --git a/src/api/mod.rs b/src/api/mod.rs index 419b601..52792ae 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,5 +1,5 @@ use crate::api::state::ApiState; -use crate::datafeed::DatafeedSharedState; +use crate::vatsim::DatafeedSharedState; use actix_web::middleware::TrailingSlash::Trim; use actix_web::{App, HttpServer, middleware, web}; @@ -18,7 +18,7 @@ pub(crate) async fn init_api(shared_datafeed: DatafeedSharedState) -> std::io::R "%a \"%r\" %s \"%{User-Agent}i\" %Ts", )) .service( - web::scope("/datafeed") + web::scope("/vatsim") .service(handlers::get_datafeed) .service(handlers::get_general_datafeed) .service(handlers::get_controllers_datafeed) diff --git a/src/api/state.rs b/src/api/state.rs index 1ef1f26..ed95b04 100644 --- a/src/api/state.rs +++ b/src/api/state.rs @@ -1,4 +1,4 @@ -use crate::datafeed::DatafeedSharedState; +use crate::vatsim::DatafeedSharedState; use geo::{LineString, Polygon}; const GERMANY_POLY: &[(f64, f64)] = &[ diff --git a/src/api/types.rs b/src/api/types.rs index 89ce5b2..75e3b5d 100644 --- a/src/api/types.rs +++ b/src/api/types.rs @@ -1,5 +1,5 @@ -use crate::datafeed::datafeed::Datafeed; -use crate::datafeed::types::DatafeedGeneral; +use crate::vatsim::datafeed::Datafeed; +use crate::vatsim::types::DatafeedGeneral; use serde::Serialize; #[derive(Serialize)] diff --git a/src/main.rs b/src/main.rs index 6dfd9a4..88bd96b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,11 @@ use crate::api::init_api; -use crate::datafeed::DatafeedSharedState; -use crate::datafeed::datafeed_status::DatafeedStatus; -use datafeed::status::VatsimStatus; +use crate::vatsim::DatafeedSharedState; +use crate::vatsim::datafeed_status::DatafeedStatus; use env_logger; +use vatsim::status::VatsimStatus; mod api; -mod datafeed; +mod vatsim; #[tokio::main] async fn main() { @@ -17,7 +17,7 @@ async fn main() { tokio::task::spawn({ let shared_datafeed = shared_datafeed.clone(); async move { - datafeed::datafeed::update_datafeed_loop(datafeed_url, shared_datafeed).await; + vatsim::datafeed::update_datafeed_loop(datafeed_url, shared_datafeed).await; } }); diff --git a/src/datafeed/datafeed.rs b/src/vatsim/datafeed.rs similarity index 95% rename from src/datafeed/datafeed.rs rename to src/vatsim/datafeed.rs index ece31a7..293bf84 100644 --- a/src/datafeed/datafeed.rs +++ b/src/vatsim/datafeed.rs @@ -1,5 +1,5 @@ -use crate::datafeed::DatafeedSharedState; -use crate::datafeed::types::{ +use crate::vatsim::DatafeedSharedState; +use crate::vatsim::types::{ DatafeedAtis, DatafeedController, DatafeedFacility, DatafeedGeneral, DatafeedMilitaryRating, DatafeedPilot, DatafeedPilotRating, DatafeedPrefile, DatafeedRating, DatafeedServer, }; @@ -69,7 +69,7 @@ pub(crate) async fn update_datafeed_loop(datafeed_url: String, shared_state: Dat Ok(datafeed) => { let mut write_guard = shared_state.write().await; if datafeed.is_failed(&mut same_timestamp_count, &write_guard.data) { - error!("Failed to update datafeed"); + error!("Failed to update vatsim"); write_guard.failed = true; continue; } diff --git a/src/datafeed/datafeed_status.rs b/src/vatsim/datafeed_status.rs similarity index 88% rename from src/datafeed/datafeed_status.rs rename to src/vatsim/datafeed_status.rs index 8658a16..f0032ed 100644 --- a/src/datafeed/datafeed_status.rs +++ b/src/vatsim/datafeed_status.rs @@ -1,4 +1,4 @@ -use crate::datafeed::datafeed::Datafeed; +use crate::vatsim::datafeed::Datafeed; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Debug)] diff --git a/src/datafeed/mod.rs b/src/vatsim/mod.rs similarity index 86% rename from src/datafeed/mod.rs rename to src/vatsim/mod.rs index 702221f..0398d90 100644 --- a/src/datafeed/mod.rs +++ b/src/vatsim/mod.rs @@ -1,4 +1,4 @@ -use crate::datafeed::datafeed_status::DatafeedStatus; +use crate::vatsim::datafeed_status::DatafeedStatus; use std::sync::Arc; use tokio::sync::RwLock; diff --git a/src/datafeed/status.rs b/src/vatsim/status.rs similarity index 84% rename from src/datafeed/status.rs rename to src/vatsim/status.rs index a58181e..70396a7 100644 --- a/src/datafeed/status.rs +++ b/src/vatsim/status.rs @@ -15,10 +15,10 @@ const VATSIM_STATUS_URL: &'static str = "https://status.vatsim.net/status.json"; const VATSIM_DEFAULT_DATAFEED_URL: &'static str = "https://data.vatsim.net/v3/vatsim-data.json"; impl VatsimStatus { - /// Retrieves a datafeed url to use from VATSIM's status page. + /// Retrieves a vatsim url to use from VATSIM's status page. /// /// # Returns - /// The selected datafeed url, or the default url in case there was an issue fetching the + /// The selected vatsim url, or the default url in case there was an issue fetching the /// newest status.json file. pub(crate) async fn get_datafeed_url() -> String { let response = reqwest::get(VATSIM_STATUS_URL).await; @@ -31,7 +31,7 @@ impl VatsimStatus { } /// Selects a random string from a vector of strings. This is to be done as per VATSIM's - /// documentation in order to balance the load on the datafeed if this should become an issue + /// documentation in order to balance the load on the vatsim if this should become an issue /// in the future /// /// # Returns diff --git a/src/datafeed/types.rs b/src/vatsim/types.rs similarity index 100% rename from src/datafeed/types.rs rename to src/vatsim/types.rs From 233d4c126ed7bf764ea8acbfe34dcd2914f60865 Mon Sep 17 00:00:00 2001 From: Nikolas Goerlitz Date: Sun, 30 Mar 2025 15:35:12 +0200 Subject: [PATCH 05/19] Fix incorrect actix scope --- src/api/mod.rs | 2 +- src/main.rs | 2 +- src/vatsim/datafeed.rs | 15 +++++++++++++++ src/vatsim/datafeed_status.rs | 17 ----------------- src/vatsim/mod.rs | 3 +-- 5 files changed, 18 insertions(+), 21 deletions(-) delete mode 100644 src/vatsim/datafeed_status.rs diff --git a/src/api/mod.rs b/src/api/mod.rs index 52792ae..d76e005 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -18,7 +18,7 @@ pub(crate) async fn init_api(shared_datafeed: DatafeedSharedState) -> std::io::R "%a \"%r\" %s \"%{User-Agent}i\" %Ts", )) .service( - web::scope("/vatsim") + web::scope("/datafeed") .service(handlers::get_datafeed) .service(handlers::get_general_datafeed) .service(handlers::get_controllers_datafeed) diff --git a/src/main.rs b/src/main.rs index 88bd96b..3f84586 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,6 @@ use crate::api::init_api; use crate::vatsim::DatafeedSharedState; -use crate::vatsim::datafeed_status::DatafeedStatus; +use crate::vatsim::datafeed::DatafeedStatus; use env_logger; use vatsim::status::VatsimStatus; diff --git a/src/vatsim/datafeed.rs b/src/vatsim/datafeed.rs index 293bf84..ca59334 100644 --- a/src/vatsim/datafeed.rs +++ b/src/vatsim/datafeed.rs @@ -11,6 +11,21 @@ use std::ops::Sub; const UPDATE_DATAFEED_INTERVAL_SECS: u64 = 15; +#[derive(Serialize, Deserialize, Debug)] +pub(crate) struct DatafeedStatus { + pub data: Option, + pub failed: bool, +} + +impl DatafeedStatus { + pub(crate) fn new() -> Self { + Self { + data: None, + failed: true, + } + } +} + #[derive(Serialize, Deserialize, Debug)] pub(crate) struct Datafeed { pub general: DatafeedGeneral, diff --git a/src/vatsim/datafeed_status.rs b/src/vatsim/datafeed_status.rs deleted file mode 100644 index f0032ed..0000000 --- a/src/vatsim/datafeed_status.rs +++ /dev/null @@ -1,17 +0,0 @@ -use crate::vatsim::datafeed::Datafeed; -use serde::{Deserialize, Serialize}; - -#[derive(Serialize, Deserialize, Debug)] -pub(crate) struct DatafeedStatus { - pub data: Option, - pub failed: bool, -} - -impl DatafeedStatus { - pub(crate) fn new() -> Self { - Self { - data: None, - failed: true, - } - } -} diff --git a/src/vatsim/mod.rs b/src/vatsim/mod.rs index 0398d90..285ad2d 100644 --- a/src/vatsim/mod.rs +++ b/src/vatsim/mod.rs @@ -1,9 +1,8 @@ -use crate::vatsim::datafeed_status::DatafeedStatus; +use crate::vatsim::datafeed::DatafeedStatus; use std::sync::Arc; use tokio::sync::RwLock; pub(crate) mod datafeed; -pub(crate) mod datafeed_status; pub(crate) mod status; pub(crate) mod types; From 9f592aeb7d237f4521480800d65dec8c74e47000 Mon Sep 17 00:00:00 2001 From: Nikolas Goerlitz Date: Sun, 30 Mar 2025 16:32:51 +0200 Subject: [PATCH 06/19] add some convenience features for debugging --- .github/workflows/dev.prettier.yml | 6 +++- Dockerfile | 3 ++ README.md | 48 +++++++++++++++++++++++------- src/api/handlers.rs | 22 ++++++++++++++ src/api/mod.rs | 1 + src/main.rs | 2 ++ src/vatsim/status.rs | 11 +++++-- 7 files changed, 80 insertions(+), 13 deletions(-) diff --git a/.github/workflows/dev.prettier.yml b/.github/workflows/dev.prettier.yml index 6641f27..f609f26 100644 --- a/.github/workflows/dev.prettier.yml +++ b/.github/workflows/dev.prettier.yml @@ -17,6 +17,10 @@ jobs: packages: write steps: + - name: Extract commit SHA + run: + echo "SHORT_SHA=`echo ${GITHUB_SHA} | cut -c1-8`" >> $GITHUB_ENV + - name: Checkout repository uses: actions/checkout@v4 @@ -29,5 +33,5 @@ jobs: - name: Build and push Docker image run: | - docker build --pull -t ${{ env.REGISTRY }}/${{ github.repository }}:dev . + docker build --pull --build-arg COM_SHA=${{ env.SHORT_SHA }} -t ${{ env.REGISTRY }}/${{ github.repository }}:dev . docker push ${{ env.REGISTRY }}/${{ github.repository }}:dev \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 002504d..502b586 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,6 +9,9 @@ FROM debian:bookworm-20210816-slim RUN apt update && apt install -y openssl ca-certificates && rm -rf /var/lib/apt/lists/* COPY --from=builder /tmp/dfcache/target/release/df-cache /usr/local/bin/df-cache +ARG COM_SHA="0000000" +ENV COMMIT_SHA=${COM_SHA} + EXPOSE 8000 CMD ["df-cache"] diff --git a/README.md b/README.md index d38ed2e..518c88f 100644 --- a/README.md +++ b/README.md @@ -3,12 +3,18 @@ # Datafeed Cache -The Datafeed Cache project is a wrapper around [VATSIM's Datafeed service](https://data.vatsim.net/v3/vatsim-data.json) which provides basic caching functionality -as well as failure detection. Every response includes an extra flag `failed` which indicates whether the last update -made by the server was successful, or whether the datafeed failed to update. If `failed` is true, then the previously cached datafeed is returned -as the response in `data`. +The Datafeed Cache project is a wrapper around [VATSIM's Datafeed service](https://data.vatsim.net/v3/vatsim-data.json) +which provides basic caching functionality +as well as failure detection. Every response includes an extra flag `failed` which indicates whether the last update +made by the server was successful, or whether the datafeed failed to update. If `failed` is true, then the previously +cached datafeed is returned +as the response in `data`. The API Documentation can be found in the [wiki](https://github.com/vatger/datafeed-cache/wiki). +> [!IMPORTANT] +> The wiki still contains the older typescript type syntax. Semantically, the result types are identical though. +> To view the concrete implementation of the types used, visit [the datafeed type definitions](./src/vatsim/types.rs). + If you wish to contribute and/or make changes, please check out our contribution guide [here](CONTRIBUTING.md). @@ -19,15 +25,37 @@ If you wish to contribute and/or make changes, please check out our contribution | Nikolas G. - 1373921 | * | `git[at]vatger.de` | ## Prerequisites + - **Rust** (tested with version 1.85.1) - **Cargo** +- **OpenSSL** (required by *reqwest*) + +## Running the Application + +Firstly clone the repository by running: + +```shell +$ git clone https://github.com/vatger/datafeed-cache.git +$ cd datafeed-cache +``` + +You can build the application using `cargo build` or run it directly using `cargo run`, which will include the +compilation steps. + +You can also specify the type of release you would like to build (e.g. release) by specifying `cargo build --release`. ### Using Docker -Using Docker is possibly the easiest way to deploy the application. Using the included `docker-compose.yml`, you should -simply be able to build and run the application by running `docker compose up`. -The included compose file exposes port `8000` on the host by default, however can be configured at will to fit any -deployment scheme. +If you prefer to use Docker for production / development, you can use the provided `docker-compose.yml` file, or create +your own. +This compose-file will build the image and deploy it locally exposing port `8007` on the host machine. -## Running the Application -`cargo run` +```shell +$ docker compose up +``` + +To stop the deployed stack, run + +```shell +$ docker compose down +``` \ No newline at end of file diff --git a/src/api/handlers.rs b/src/api/handlers.rs index 7468295..69fa6ad 100644 --- a/src/api/handlers.rs +++ b/src/api/handlers.rs @@ -7,7 +7,9 @@ use crate::vatsim::types::{ DatafeedServer, }; use actix_web::HttpResponse; +use chrono::Utc; use geo::{Contains, Coord}; +use serde::Serialize; #[actix_web::get("")] async fn get_datafeed(data: ApiStateData) -> HttpResponse { @@ -204,3 +206,23 @@ async fn get_ger_atis_datafeed(data: ApiStateData) -> HttpResponse { failed: status.failed, }) } + +/// +/// Health-Check endpoint +/// +/// /health-check +/// + +#[actix_web::get("/health-check")] +async fn get_health_check() -> HttpResponse { + #[derive(Serialize)] + struct Response { + build_hash: String, + timestamp: i64, + } + + HttpResponse::Ok().json(Response { + build_hash: std::env::var("COMMIT_SHA").unwrap_or_else(|_| "Err reading env".into()), + timestamp: Utc::now().timestamp(), + }) +} diff --git a/src/api/mod.rs b/src/api/mod.rs index d76e005..00187f7 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -31,6 +31,7 @@ pub(crate) async fn init_api(shared_datafeed: DatafeedSharedState) -> std::io::R .service(handlers::get_ger_pilots_datafeed) .service(handlers::get_ger_atis_datafeed), ) + .service(handlers::get_health_check) }) .bind("0.0.0.0:8000")? .run() diff --git a/src/main.rs b/src/main.rs index 3f84586..93a55ab 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ use crate::api::init_api; use crate::vatsim::DatafeedSharedState; use crate::vatsim::datafeed::DatafeedStatus; use env_logger; +use log::info; use vatsim::status::VatsimStatus; mod api; @@ -13,6 +14,7 @@ async fn main() { let shared_datafeed: DatafeedSharedState = DatafeedStatus::new().into(); let datafeed_url = VatsimStatus::get_datafeed_url().await; + info!("Selected Datafeed-URL: {}", datafeed_url); tokio::task::spawn({ let shared_datafeed = shared_datafeed.clone(); diff --git a/src/vatsim/status.rs b/src/vatsim/status.rs index 70396a7..9080e50 100644 --- a/src/vatsim/status.rs +++ b/src/vatsim/status.rs @@ -1,3 +1,4 @@ +use log::warn; use rand::Rng; use serde::Deserialize; @@ -23,8 +24,14 @@ impl VatsimStatus { pub(crate) async fn get_datafeed_url() -> String { let response = reqwest::get(VATSIM_STATUS_URL).await; let status: VatsimStatus = match response { - Ok(res) => res.json::().await.unwrap_or_default(), - Err(_) => Self::default(), + Ok(res) => res.json::().await.unwrap_or_else(|err| { + warn!("Falling back to default VatsimStatus. Error: {err}"); + Self::default() + }), + Err(err) => { + warn!("Falling back to default VatsimStatus. Error: {err}"); + Self::default() + } }; Self::select_random_url(&status.data.v3) From ae4cfe7aa17f02e4102a146162f43a5fa3a66367 Mon Sep 17 00:00:00 2001 From: Nikolas Goerlitz Date: Sun, 30 Mar 2025 16:42:59 +0200 Subject: [PATCH 07/19] Bump to version 1.0.0, add healthcheck --- Cargo.lock | 2 +- Cargo.toml | 10 +++++----- Dockerfile | 3 ++- docker-compose.yml | 5 +++++ src/api/handlers.rs | 2 +- src/main.rs | 5 +++++ 6 files changed, 19 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 01c4650..359db44 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -550,7 +550,7 @@ dependencies = [ [[package]] name = "df-cache" -version = "0.1.0" +version = "1.0.0" dependencies = [ "actix-web", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 04fab26..659322a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,14 +1,14 @@ [package] name = "df-cache" -version = "0.1.0" +version = "1.0.0" edition = "2024" [dependencies] -tokio = {version = "1.44.1", features = ["macros", "rt-multi-thread", "time"]} +tokio = { version = "1.44.1", features = ["macros", "rt-multi-thread", "time"] } actix-web = { version = "4.10.2" } -reqwest = {version = "0.12.15", features = ["json"]} -serde = {version = "1.0.219", features = ["derive"]} -chrono = {version = "0.4.40", features = ["serde"]} +reqwest = { version = "0.12.15", features = ["json"] } +serde = { version = "1.0.219", features = ["derive"] } +chrono = { version = "0.4.40", features = ["serde"] } rand = "0.9.0" env_logger = "0.11.7" log = "0.4.27" diff --git a/Dockerfile b/Dockerfile index 502b586..10c9536 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,11 +6,12 @@ COPY . . RUN cargo build --release FROM debian:bookworm-20210816-slim -RUN apt update && apt install -y openssl ca-certificates && rm -rf /var/lib/apt/lists/* +RUN apt update && apt install -y openssl ca-certificates curl && rm -rf /var/lib/apt/lists/* COPY --from=builder /tmp/dfcache/target/release/df-cache /usr/local/bin/df-cache ARG COM_SHA="0000000" ENV COMMIT_SHA=${COM_SHA} +ENV RUST_LOG=info EXPOSE 8000 diff --git a/docker-compose.yml b/docker-compose.yml index 0788a20..a0a32e8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,5 +8,10 @@ services: ports: - "8007:8000/tcp" restart: unless-stopped + healthcheck: + test: [ "curl", "-f", "http://localhost:8000/health-check" ] + interval: 5m + timeout: 10s + retries: 3 environment: - RUST_LOG=info \ No newline at end of file diff --git a/src/api/handlers.rs b/src/api/handlers.rs index 69fa6ad..824e261 100644 --- a/src/api/handlers.rs +++ b/src/api/handlers.rs @@ -222,7 +222,7 @@ async fn get_health_check() -> HttpResponse { } HttpResponse::Ok().json(Response { - build_hash: std::env::var("COMMIT_SHA").unwrap_or_else(|_| "Err reading env".into()), + build_hash: std::env::var("COMMIT_SHA").unwrap_or("-------".into()), timestamp: Utc::now().timestamp(), }) } diff --git a/src/main.rs b/src/main.rs index 93a55ab..e895b44 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,6 +12,11 @@ mod vatsim; async fn main() { env_logger::init(); + info!( + "Starting Version: {}", + std::env::var("COMMIT_SHA").unwrap_or("-------".into()) + ); + let shared_datafeed: DatafeedSharedState = DatafeedStatus::new().into(); let datafeed_url = VatsimStatus::get_datafeed_url().await; info!("Selected Datafeed-URL: {}", datafeed_url); From 6a3a8379b44d37e93aec77678263f3a756b694dc Mon Sep 17 00:00:00 2001 From: Nikolas Goerlitz Date: Sun, 30 Mar 2025 16:43:43 +0200 Subject: [PATCH 08/19] Bump to 1.2.0 to avoid confusion with previous packages --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 359db44..9cd56ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -550,7 +550,7 @@ dependencies = [ [[package]] name = "df-cache" -version = "1.0.0" +version = "1.2.0" dependencies = [ "actix-web", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 659322a..a650328 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "df-cache" -version = "1.0.0" +version = "1.2.0" edition = "2024" [dependencies] From 2db5d0f79b8adfd3d1d9e48f739e77bf77c4679d Mon Sep 17 00:00:00 2001 From: Nikolas Goerlitz Date: Sun, 30 Mar 2025 16:56:45 +0200 Subject: [PATCH 09/19] Update docker-compose.yml to fix health check command --- docker-compose.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index a0a32e8..7415411 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,3 @@ -version: "3" - services: df-cache: build: @@ -9,9 +7,11 @@ services: - "8007:8000/tcp" restart: unless-stopped healthcheck: - test: [ "curl", "-f", "http://localhost:8000/health-check" ] + test: [ "CMD", "curl", "-f", "http://localhost:8000/health-check" ] interval: 5m - timeout: 10s + timeout: 20s retries: 3 + start_period: 5s + start_interval: 10s environment: - RUST_LOG=info \ No newline at end of file From 68d29800e39c81e48fc58f35b17b5ed3b831f9b9 Mon Sep 17 00:00:00 2001 From: Nikolas Goerlitz Date: Sun, 30 Mar 2025 20:40:16 +0200 Subject: [PATCH 10/19] Update cargo to version 1.2.1 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9cd56ad..b89a8a2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -550,7 +550,7 @@ dependencies = [ [[package]] name = "df-cache" -version = "1.2.0" +version = "1.2.1" dependencies = [ "actix-web", "chrono", diff --git a/Cargo.toml b/Cargo.toml index a650328..4c36737 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "df-cache" -version = "1.2.0" +version = "1.2.1" edition = "2024" [dependencies] From 2f69bdacadf7ffae01697ec1e74d62fa09676fac Mon Sep 17 00:00:00 2001 From: Nikolas Goerlitz Date: Sun, 30 Mar 2025 20:40:51 +0200 Subject: [PATCH 11/19] Update prod.docker.yml --- .github/workflows/prod.docker.yml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/prod.docker.yml b/.github/workflows/prod.docker.yml index 913233c..3c39cc5 100644 --- a/.github/workflows/prod.docker.yml +++ b/.github/workflows/prod.docker.yml @@ -1,8 +1,9 @@ -name: Build latest image +name: Build prod image on: push: - branches: [ "main" ] + branches-ignore: + - main env: REGISTRY: ghcr.io @@ -16,6 +17,10 @@ jobs: packages: write steps: + - name: Extract commit SHA + run: + echo "SHORT_SHA=`echo ${GITHUB_SHA} | cut -c1-8`" >> $GITHUB_ENV + - name: Checkout repository uses: actions/checkout@v4 @@ -28,5 +33,5 @@ jobs: - name: Build and push Docker image run: | - docker build --pull -t ${{ env.REGISTRY }}/${{ github.repository }}:latest . + docker build --pull --build-arg COM_SHA=${{ env.SHORT_SHA }} -t ${{ env.REGISTRY }}/${{ github.repository }}:latest . docker push ${{ env.REGISTRY }}/${{ github.repository }}:latest \ No newline at end of file From d9b8ec1aa537cba0f16695c1a641df2737ec9771 Mon Sep 17 00:00:00 2001 From: Nikolas Goerlitz Date: Sun, 30 Mar 2025 20:41:56 +0200 Subject: [PATCH 12/19] fix prod workflow only running on main branch --- .github/workflows/prod.docker.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/prod.docker.yml b/.github/workflows/prod.docker.yml index 3c39cc5..8e0824a 100644 --- a/.github/workflows/prod.docker.yml +++ b/.github/workflows/prod.docker.yml @@ -2,8 +2,7 @@ name: Build prod image on: push: - branches-ignore: - - main + - main env: REGISTRY: ghcr.io From 6b9dbd1e534df07e7674aeb8d14a099b3ec17629 Mon Sep 17 00:00:00 2001 From: Nikolas Goerlitz Date: Sun, 30 Mar 2025 20:43:45 +0200 Subject: [PATCH 13/19] dev workflow creates images based on commit sha --- .github/workflows/dev.prettier.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/dev.prettier.yml b/.github/workflows/dev.prettier.yml index f609f26..3c255a9 100644 --- a/.github/workflows/dev.prettier.yml +++ b/.github/workflows/dev.prettier.yml @@ -1,7 +1,7 @@ name: Build dev image on: - push: + pull-request: branches-ignore: - main @@ -33,5 +33,5 @@ jobs: - name: Build and push Docker image run: | - docker build --pull --build-arg COM_SHA=${{ env.SHORT_SHA }} -t ${{ env.REGISTRY }}/${{ github.repository }}:dev . - docker push ${{ env.REGISTRY }}/${{ github.repository }}:dev \ No newline at end of file + docker build --pull --build-arg COM_SHA=${{ env.SHORT_SHA }} -t ${{ env.REGISTRY }}/${{ github.repository }}:${{ env.SHORT_SHA }} . + docker push ${{ env.REGISTRY }}/${{ github.repository }}:${{ env.SHORT_SHA }} \ No newline at end of file From 7d11a6077f88f9f083d8a91b4219021d5feb58d1 Mon Sep 17 00:00:00 2001 From: Nikolas Goerlitz Date: Sun, 30 Mar 2025 20:45:27 +0200 Subject: [PATCH 14/19] fix typo --- .github/workflows/dev.prettier.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dev.prettier.yml b/.github/workflows/dev.prettier.yml index 3c255a9..ee49b69 100644 --- a/.github/workflows/dev.prettier.yml +++ b/.github/workflows/dev.prettier.yml @@ -1,7 +1,7 @@ name: Build dev image on: - pull-request: + pull_request: branches-ignore: - main From fdf2e50a2ec915e6381da7c91f5afa0586ab09aa Mon Sep 17 00:00:00 2001 From: Nikolas Goerlitz Date: Sun, 30 Mar 2025 20:51:12 +0200 Subject: [PATCH 15/19] fix typo --- .github/workflows/dev.prettier.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dev.prettier.yml b/.github/workflows/dev.prettier.yml index ee49b69..4b8472b 100644 --- a/.github/workflows/dev.prettier.yml +++ b/.github/workflows/dev.prettier.yml @@ -2,7 +2,7 @@ name: Build dev image on: pull_request: - branches-ignore: + branches: - main env: From f124a92414f0f65718378f270ec64ed7c02e0098 Mon Sep 17 00:00:00 2001 From: Nikolas Goerlitz Date: Sun, 30 Mar 2025 21:06:38 +0200 Subject: [PATCH 16/19] Fix missing struct members --- src/api/handlers.rs | 2 +- src/vatsim/types.rs | 28 ++++++++++++++++++++++++---- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/api/handlers.rs b/src/api/handlers.rs index 824e261..68a27fd 100644 --- a/src/api/handlers.rs +++ b/src/api/handlers.rs @@ -83,7 +83,7 @@ async fn get_atis_datafeed(data: ApiStateData) -> HttpResponse { }) } -#[actix_web::get("/server")] +#[actix_web::get("/servers")] async fn get_servers_datafeed(data: ApiStateData) -> HttpResponse { let read_lock = data.shared_state.read().await; let status = &*read_lock; diff --git a/src/vatsim/types.rs b/src/vatsim/types.rs index 7ac7a76..785ce1f 100644 --- a/src/vatsim/types.rs +++ b/src/vatsim/types.rs @@ -5,14 +5,17 @@ use serde::{Deserialize, Serialize}; pub struct DatafeedGeneral { pub version: u32, pub reload: u32, + pub update: String, pub update_timestamp: DateTime, pub connected_clients: u32, pub unique_users: u32, } #[derive(Serialize, Deserialize, Debug, Clone)] -pub struct DatafeedPilotFlightPlan { +pub struct DatafeedFlightPlan { pub flight_rules: String, + pub aircraft: String, + pub aircraft_faa: String, pub aircraft_short: String, pub departure: String, pub arrival: String, @@ -24,6 +27,7 @@ pub struct DatafeedPilotFlightPlan { pub fuel_time: String, pub remarks: String, pub route: String, + pub revision_id: u32, pub assigned_transponder: String, } @@ -41,8 +45,9 @@ pub struct DatafeedPilot { pub groundspeed: i32, pub transponder: String, pub heading: u16, + pub qnh_i_hg: i32, pub qnh_mb: i32, - pub flight_plan: Option, + pub flight_plan: Option, pub logon_time: DateTime, pub last_updated: DateTime, } @@ -62,6 +67,22 @@ pub struct DatafeedController { pub logon_time: DateTime, } +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct DatafeedAtis { + pub cid: u32, + pub name: String, + pub callsign: String, + pub frequency: String, + pub facility: i32, + pub rating: i32, + pub server: String, + pub visual_range: u32, + pub atis_code: String, + pub text_atis: Option>, + pub last_updated: DateTime, + pub logon_time: DateTime, +} + #[derive(Serialize, Deserialize, Debug, Clone)] pub struct DatafeedServer { pub ident: String, @@ -92,10 +113,9 @@ pub struct DatafeedPrefile { pub cid: u32, pub name: String, pub callsign: String, - pub flight_plan: DatafeedPilotFlightPlan, + pub flight_plan: DatafeedFlightPlan, pub last_updated: DateTime, } -pub type DatafeedAtis = DatafeedController; pub type DatafeedRating = DatafeedFacility; pub type DatafeedMilitaryRating = DatafeedPilotRating; From fe3deb3fbe28753569fcfdf61e9a85a5a554051e Mon Sep 17 00:00:00 2001 From: Nikolas Goerlitz Date: Sun, 30 Mar 2025 21:09:32 +0200 Subject: [PATCH 17/19] update workflows --- .github/workflows/dev.prettier.yml | 3 +-- .github/workflows/prod.docker.yml | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/dev.prettier.yml b/.github/workflows/dev.prettier.yml index 4b8472b..8d00193 100644 --- a/.github/workflows/dev.prettier.yml +++ b/.github/workflows/dev.prettier.yml @@ -2,8 +2,7 @@ name: Build dev image on: pull_request: - branches: - - main + branches: [ "main" ] env: REGISTRY: ghcr.io diff --git a/.github/workflows/prod.docker.yml b/.github/workflows/prod.docker.yml index 8e0824a..3ce105a 100644 --- a/.github/workflows/prod.docker.yml +++ b/.github/workflows/prod.docker.yml @@ -2,7 +2,7 @@ name: Build prod image on: push: - - main + branches: [ "main" ] env: REGISTRY: ghcr.io From 2a081aff3f510f1d4dfb1342272bd2541d33c2e7 Mon Sep 17 00:00:00 2001 From: Nikolas Goerlitz Date: Sun, 30 Mar 2025 21:15:05 +0200 Subject: [PATCH 18/19] fix qnh_i_hg type --- src/vatsim/types.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vatsim/types.rs b/src/vatsim/types.rs index 785ce1f..e77bde4 100644 --- a/src/vatsim/types.rs +++ b/src/vatsim/types.rs @@ -45,7 +45,7 @@ pub struct DatafeedPilot { pub groundspeed: i32, pub transponder: String, pub heading: u16, - pub qnh_i_hg: i32, + pub qnh_i_hg: f32, pub qnh_mb: i32, pub flight_plan: Option, pub logon_time: DateTime, From a5065186285121d85e67d8702c565d392de2e6b9 Mon Sep 17 00:00:00 2001 From: Nikolas Goerlitz Date: Sun, 30 Mar 2025 21:23:56 +0200 Subject: [PATCH 19/19] fix atis_code being non-optional --- src/vatsim/types.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vatsim/types.rs b/src/vatsim/types.rs index e77bde4..9d8a505 100644 --- a/src/vatsim/types.rs +++ b/src/vatsim/types.rs @@ -77,7 +77,7 @@ pub struct DatafeedAtis { pub rating: i32, pub server: String, pub visual_range: u32, - pub atis_code: String, + pub atis_code: Option, pub text_atis: Option>, pub last_updated: DateTime, pub logon_time: DateTime,