feat: implement Rust SDK for Remnawave API with comprehensive client controllers and types

This commit is contained in:
vffuunnyy 2025-08-01 20:33:57 +07:00
parent 044f87ff36
commit d75d718c0f
No known key found for this signature in database
GPG key ID: 5E656A84B06B0AF1
43 changed files with 3382 additions and 13078 deletions

456
Cargo.lock generated
View file

@ -26,12 +26,6 @@ dependencies = [
"memchr",
]
[[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"
@ -53,6 +47,38 @@ version = "1.0.98"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
[[package]]
name = "assert-json-diff"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12"
dependencies = [
"serde",
"serde_json",
]
[[package]]
name = "async-stream"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476"
dependencies = [
"async-stream-impl",
"futures-core",
"pin-project-lite",
]
[[package]]
name = "async-stream-impl"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "atomic-waker"
version = "1.1.2"
@ -134,6 +160,15 @@ dependencies = [
"windows-link",
]
[[package]]
name = "colored"
version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e"
dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "core-foundation"
version = "0.9.4"
@ -162,10 +197,10 @@ dependencies = [
]
[[package]]
name = "dyn-clone"
version = "1.0.19"
name = "dotenv"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005"
checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
[[package]]
name = "encoding_rs"
@ -204,12 +239,6 @@ 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"
@ -376,17 +405,6 @@ name = "hashbrown"
version = "0.15.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5"
dependencies = [
"allocator-api2",
"equivalent",
"foldhash",
]
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "http"
@ -428,6 +446,12 @@ 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"
@ -441,6 +465,7 @@ dependencies = [
"http",
"http-body",
"httparse",
"httpdate",
"itoa",
"pin-project-lite",
"smallvec",
@ -498,7 +523,7 @@ dependencies = [
"libc",
"percent-encoding",
"pin-project-lite",
"socket2",
"socket2 0.5.10",
"system-configuration",
"tokio",
"tower-service",
@ -645,7 +670,6 @@ checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661"
dependencies = [
"equivalent",
"hashbrown",
"serde",
]
[[package]]
@ -757,6 +781,30 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "mockito"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7760e0e418d9b7e5777c0374009ca4c93861b9066f18cb334a20ce50ab63aa48"
dependencies = [
"assert-json-diff",
"bytes",
"colored",
"futures-util",
"http",
"http-body",
"http-body-util",
"hyper",
"hyper-util",
"log",
"rand",
"regex",
"serde_json",
"serde_urlencoded",
"similar",
"tokio",
]
[[package]]
name = "native-tls"
version = "0.2.14"
@ -798,17 +846,6 @@ version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "openapiv3"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c8d427828b22ae1fff2833a03d8486c2c881367f1c336349f307f321e7f4d05"
dependencies = [
"indexmap",
"serde",
"serde_json",
]
[[package]]
name = "openssl"
version = "0.10.73"
@ -910,13 +947,12 @@ dependencies = [
]
[[package]]
name = "prettyplease"
version = "0.2.35"
name = "ppv-lite86"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "061c1221631e079b26479d25bbf2275bfe5917ae8419cd7e34f13bfc2aa7539a"
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
dependencies = [
"proc-macro2",
"syn",
"zerocopy",
]
[[package]]
@ -928,68 +964,6 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "progenitor"
version = "0.11.0"
source = "git+https://github.com/oxidecomputer/progenitor#dc722c035fabdca010565853ed7c0114b8f9fca9"
dependencies = [
"progenitor-client",
"progenitor-impl",
"progenitor-macro",
]
[[package]]
name = "progenitor-client"
version = "0.11.0"
source = "git+https://github.com/oxidecomputer/progenitor#dc722c035fabdca010565853ed7c0114b8f9fca9"
dependencies = [
"bytes",
"futures-core",
"percent-encoding",
"reqwest",
"serde",
"serde_json",
"serde_urlencoded",
]
[[package]]
name = "progenitor-impl"
version = "0.11.0"
source = "git+https://github.com/oxidecomputer/progenitor#dc722c035fabdca010565853ed7c0114b8f9fca9"
dependencies = [
"heck",
"http",
"indexmap",
"openapiv3",
"proc-macro2",
"quote",
"regex",
"schemars",
"serde",
"serde_json",
"syn",
"thiserror",
"typify",
"unicode-ident",
]
[[package]]
name = "progenitor-macro"
version = "0.11.0"
source = "git+https://github.com/oxidecomputer/progenitor#dc722c035fabdca010565853ed7c0114b8f9fca9"
dependencies = [
"openapiv3",
"proc-macro2",
"progenitor-impl",
"quote",
"schemars",
"serde",
"serde_json",
"serde_tokenstream",
"serde_yaml",
"syn",
]
[[package]]
name = "quote"
version = "1.0.40"
@ -1006,10 +980,39 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]]
name = "redox_syscall"
version = "0.5.13"
name = "rand"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6"
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
dependencies = [
"rand_chacha",
"rand_core",
]
[[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.3",
]
[[package]]
name = "redox_syscall"
version = "0.5.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77"
dependencies = [
"bitflags",
]
@ -1043,33 +1046,21 @@ version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "regress"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "145bb27393fe455dd64d6cbc8d059adfa392590a45eadf079c01b11857e7b010"
dependencies = [
"hashbrown",
"memchr",
]
[[package]]
name = "remnawave"
version = "1.6.15"
version = "2.0.0"
dependencies = [
"anyhow",
"chrono",
"dotenv",
"futures",
"openapiv3",
"prettyplease",
"progenitor",
"progenitor-client",
"regress",
"mockito",
"reqwest",
"serde",
"serde_json",
"syn",
"serde_plain",
"tokio",
"tokio-test",
"uuid",
]
@ -1203,32 +1194,6 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "schemars"
version = "0.8.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615"
dependencies = [
"chrono",
"dyn-clone",
"schemars_derive",
"serde",
"serde_json",
"uuid",
]
[[package]]
name = "schemars_derive"
version = "0.8.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d"
dependencies = [
"proc-macro2",
"quote",
"serde_derive_internals",
"syn",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
@ -1258,15 +1223,6 @@ dependencies = [
"libc",
]
[[package]]
name = "semver"
version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0"
dependencies = [
"serde",
]
[[package]]
name = "serde"
version = "1.0.219"
@ -1287,22 +1243,11 @@ dependencies = [
"syn",
]
[[package]]
name = "serde_derive_internals"
version = "0.29.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.140"
version = "1.0.142"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7"
dependencies = [
"itoa",
"memchr",
@ -1311,15 +1256,12 @@ dependencies = [
]
[[package]]
name = "serde_tokenstream"
version = "0.2.2"
name = "serde_plain"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64060d864397305347a78851c51588fd283767e7e7589829e8121d65512340f1"
checksum = "9ce1fc6db65a611022b23a0dec6975d63fb80a302cb3388835ff02c097258d50"
dependencies = [
"proc-macro2",
"quote",
"serde",
"syn",
]
[[package]]
@ -1334,19 +1276,6 @@ dependencies = [
"serde",
]
[[package]]
name = "serde_yaml"
version = "0.9.34+deprecated"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
dependencies = [
"indexmap",
"itoa",
"ryu",
"serde",
"unsafe-libyaml",
]
[[package]]
name = "shlex"
version = "1.3.0"
@ -1362,6 +1291,12 @@ dependencies = [
"libc",
]
[[package]]
name = "similar"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa"
[[package]]
name = "slab"
version = "0.4.10"
@ -1384,6 +1319,16 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "socket2"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807"
dependencies = [
"libc",
"windows-sys 0.59.0",
]
[[package]]
name = "stable_deref_trait"
version = "1.2.0"
@ -1461,26 +1406,6 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "thiserror"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tinystr"
version = "0.8.1"
@ -1493,9 +1418,9 @@ dependencies = [
[[package]]
name = "tokio"
version = "1.46.1"
version = "1.47.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17"
checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038"
dependencies = [
"backtrace",
"bytes",
@ -1506,9 +1431,9 @@ dependencies = [
"pin-project-lite",
"signal-hook-registry",
"slab",
"socket2",
"socket2 0.6.0",
"tokio-macros",
"windows-sys 0.52.0",
"windows-sys 0.59.0",
]
[[package]]
@ -1542,6 +1467,30 @@ dependencies = [
"tokio",
]
[[package]]
name = "tokio-stream"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047"
dependencies = [
"futures-core",
"pin-project-lite",
"tokio",
]
[[package]]
name = "tokio-test"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2468baabc3311435b55dd935f702f42cd1b8abb7e754fb7dfb16bd36aa88f9f7"
dependencies = [
"async-stream",
"bytes",
"futures-core",
"tokio",
"tokio-stream",
]
[[package]]
name = "tokio-util"
version = "0.7.15"
@ -1625,65 +1574,12 @@ version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
[[package]]
name = "typify"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c6c647a34e851cf0260ccc14687f17cdcb8302ff1a8a687a24b97ca0f82406f"
dependencies = [
"typify-impl",
"typify-macro",
]
[[package]]
name = "typify-impl"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "741b7f1e2e1338c0bee5ad5a7d3a9bbd4e24c33765c08b7691810e68d879365d"
dependencies = [
"heck",
"log",
"proc-macro2",
"quote",
"regress",
"schemars",
"semver",
"serde",
"serde_json",
"syn",
"thiserror",
"unicode-ident",
]
[[package]]
name = "typify-macro"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7560adf816a1e8dad7c63d8845ef6e31e673e39eab310d225636779230cbedeb"
dependencies = [
"proc-macro2",
"quote",
"schemars",
"semver",
"serde",
"serde_json",
"serde_tokenstream",
"syn",
"typify-impl",
]
[[package]]
name = "unicode-ident"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "unsafe-libyaml"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
[[package]]
name = "untrusted"
version = "0.9.0"
@ -2107,6 +2003,26 @@ dependencies = [
"synstructure",
]
[[package]]
name = "zerocopy"
version = "0.8.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "zerofrom"
version = "0.1.6"

View file

@ -1,31 +1,35 @@
[package]
name = "remnawave"
version = "1.6.15"
edition = "2024"
version = "2.0.0"
authors = ["vffuunnyy <vffuunnyy@gmail.com>"]
description = "Rust SDK for Remnawave API - A comprehensive client library for interacting with Remnawave services"
license = "MIT"
repository = "https://github.com/remnawave/rust-sdk"
homepage = "https://github.com/remnawave/rust-sdk"
documentation = "https://docs.rs/remnawave"
keywords = ["api", "sdk", "client", "remnawave", "http", "rust"]
categories = ["api-bindings", "web-programming::http-client"]
readme = "README.md"
edition = "2021"
exclude = ["tests/*"]
[lib]
name = "remnawave"
name = "remnawave_api"
path = "src/lib.rs"
[[bin]]
name = "remnawave-test"
path = "src/bin/test.rs"
[dependencies]
chrono = { version = "0.4.41", features = ["serde"] }
uuid = { version = "1.17.0", features = ["serde", "v4"] }
reqwest = { version = "0.12.22", features = ["json", "stream"] }
serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.140"
serde_json = "1.0.142"
serde_plain = "1.0"
futures = "0.3.31"
progenitor-client = { git = "https://github.com/oxidecomputer/progenitor" }
anyhow = "1.0.98"
regress = "0.10"
tokio = { version = "1.0", features = ["full"] }
tokio = { version = "1.47.1", features = ["full"] }
[build-dependencies]
progenitor = { git = "https://github.com/oxidecomputer/progenitor" }
serde_json = "1.0.140"
syn = "2.0.104"
prettyplease = "0.2.35"
openapiv3 = "2.2.0"
[dev-dependencies]
tokio-test = "0.4.4"
mockito = "1.4.0"
dotenv = "0.15.0"

61
README.md Normal file
View file

@ -0,0 +1,61 @@
# Remnawave API Client
A Rust client library for the Remnawave API.
## Installation
Add this to your `Cargo.toml`:
```toml
[dependencies]
remnawave = "1.6.15"
```
## Usage
```rust
use remnawave::{RemnawaveApiClient, Result};
#[tokio::main]
async fn main() -> Result<()> {
let client = RemnawaveApiClient::new(
"https://api.example.com".to_string(),
"your_token_here".to_string()
)?;
let status = client.auth.get_status().await?;
println!("Auth status: {:?}", status);
Ok(())
}
```
## Features
This library provides access to all Remnawave API endpoints through organized controllers:
- **Auth Controller** - Authentication and status
- **Users Controller** - User management operations
- **Subscription Controller** - Subscription information
- **API Tokens Controller** - API token management
- **Nodes Controller** - Node management
- **Hosts Controller** - Host management
- **Inbounds Controller** - Inbound configuration
- **System Controller** - System statistics and health
## Examples
See the `examples/` directory for more usage examples.
## Error Handling
The library provides detailed error information through the `ApiError` type, including:
- HTTP status codes
- Response body
- Request details
- Parsed error messages from the API
## License
This project is licensed under the MIT License.

12112
api.json

File diff suppressed because it is too large Load diff

555
build.rs
View file

@ -1,555 +0,0 @@
fn main() {
let spec_path = "api.json";
println!("cargo:rerun-if-changed={}", spec_path);
let file = std::fs::File::open(spec_path).unwrap();
let mut spec: openapiv3::OpenAPI = serde_json::from_reader(file).unwrap();
// Fix progenitor issue with multiple response types
// Remove "default" responses when specific status codes are present
fix_response_conflicts(&mut spec);
let mut binding = progenitor::GenerationSettings::default();
let settings = binding
.with_interface(progenitor::InterfaceStyle::Builder);
let mut generator = progenitor::Generator::new(&settings);
let tokens = generator.generate_tokens(&spec).unwrap();
let ast = syn::parse2(tokens).unwrap();
let src = prettyplease::unparse(&ast);
// Генерируем код контроллеров
let (controller_structs, client_methods) = generate_controllers(&spec);
let out_file = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap())
.join("remnawave_api.rs");
let controllers_file = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap())
.join("controllers.rs");
// Write only the original generated API
std::fs::write(out_file, src).unwrap();
// Write our custom controllers separately
std::fs::write(controllers_file, format!("{}\n{}", controller_structs, client_methods)).unwrap();
}
fn fix_response_conflicts(spec: &mut openapiv3::OpenAPI) {
for path_item in spec.paths.paths.values_mut() {
if let openapiv3::ReferenceOr::Item(path_item) = path_item {
// Check all operations in the path
let operations = [
&mut path_item.get,
&mut path_item.post,
&mut path_item.put,
&mut path_item.delete,
&mut path_item.options,
&mut path_item.head,
&mut path_item.patch,
&mut path_item.trace,
];
for operation in operations.into_iter().filter_map(|op| op.as_mut()) {
fix_operation_responses(&mut operation.responses);
}
}
}
}
fn fix_operation_responses(responses: &mut openapiv3::Responses) {
// If there are both specific status codes and a default response,
// remove the default response to avoid progenitor conflicts
let has_specific_responses = responses.responses.keys().any(|key| match key {
openapiv3::StatusCode::Code(_) => true,
openapiv3::StatusCode::Range(_) => true,
});
if has_specific_responses && responses.default.is_some() {
// Remove the default response to resolve the conflict
responses.default = None;
}
}
use std::collections::HashMap;
fn generate_controllers(spec: &openapiv3::OpenAPI) -> (String, String) {
let mut controllers: HashMap<String, Vec<(String, String, String, String)>> = HashMap::new();
// Анализируем все пути и группируем по контроллерам на основе operation_id
for (_path, path_item) in &spec.paths.paths {
if let openapiv3::ReferenceOr::Item(path_item) = path_item {
let operations = [
("GET", &path_item.get),
("POST", &path_item.post),
("PUT", &path_item.put),
("DELETE", &path_item.delete),
("PATCH", &path_item.patch),
("HEAD", &path_item.head),
("OPTIONS", &path_item.options),
("TRACE", &path_item.trace),
];
for (method, operation) in operations {
if let Some(operation) = operation {
if let Some(operation_id) = &operation.operation_id {
// Группируем по контроллерам на основе operation_id
if let Some((controller_name, method_name)) = extract_controller_from_operation_id(operation_id) {
let summary = operation.summary.clone().unwrap_or_default();
controllers
.entry(controller_name)
.or_insert_with(Vec::new)
.push((method_name, operation_id.clone(), summary, method.to_string()));
}
}
}
}
}
}
// Удаляем дублирующиеся методы
for methods in controllers.values_mut() {
methods.sort_by(|a, b| a.0.cmp(&b.0));
methods.dedup_by(|a, b| a.0 == b.0);
}
// Генерируем код контроллеров
let controller_structs = generate_controller_code(&controllers);
let client_methods = generate_client_methods(&controllers);
(controller_structs, client_methods)
}
fn extract_controller_from_operation_id(operation_id: &str) -> Option<(String, String)> {
// operation_id имеет вид: "AuthController_login" или "UsersController_createUser"
// Разбиваем на части по символу "_"
let parts: Vec<&str> = operation_id.split('_').collect();
if parts.len() < 2 {
return None;
}
// Ищем "Controller" в первой части
let controller_part = parts[0];
if !controller_part.ends_with("Controller") {
return None;
}
// Извлекаем имя контроллера (убираем "Controller")
let controller_base = &controller_part[0..controller_part.len() - 10]; // убираем "Controller"
let controller_name = match controller_base {
"Auth" => "auth".to_string(),
"Users" => "users".to_string(),
"UsersBulkActions" => "users".to_string(),
"Nodes" => "nodes".to_string(),
"NodesUserUsageHistory" => "nodes".to_string(),
"NodesUsageHistory" => "nodes".to_string(),
"Hosts" => "hosts".to_string(),
"HostsBulkActions" => "hosts".to_string(),
"Inbounds" => "inbounds".to_string(),
"InboundsBulkActions" => "inbounds".to_string(),
"ApiTokens" => "api_tokens".to_string(),
"Keygen" => "keygen".to_string(),
"HwidUserDevices" => "hwid".to_string(),
"SubscriptionSettings" => "subscription_settings".to_string(),
"SubscriptionTemplate" => "subscription_templates".to_string(),
"SubscriptionTemplates" => "subscription_templates".to_string(),
"Subscription" => "subscriptions".to_string(),
"Subscriptions" => "subscriptions".to_string(),
"XrayConfig" => "xray_config".to_string(),
"System" => "system".to_string(),
other => other.to_lowercase(),
};
// Извлекаем имя метода (все части после первой)
let method_parts = &parts[1..];
let method_name = create_method_name_from_operation(method_parts, &controller_name, controller_base);
Some((controller_name, method_name))
}
fn create_method_name_from_operation(parts: &[&str], controller_name: &str, _controller_base: &str) -> String {
if parts.is_empty() {
return "unknown".to_string();
}
let method_str = parts.join("_");
let method_lower = method_str.to_lowercase();
// Маппинг специальных случаев для разных контроллеров
match controller_name {
"subscriptions" => {
match method_lower.as_str() {
"getsubscriptioninfobyshortuuid" => "get_info".to_string(),
"getsubscription" => "get".to_string(),
"getsubscriptionbyclienttype" => "get_by_client_type".to_string(),
"getsubscriptionwithtype" => "get_with_type".to_string(),
"getallsubscriptions" => "get_all".to_string(),
"getsubscriptionbyusername" => "get_by_username".to_string(),
_ => snake_case(&method_str),
}
}
"users" => {
match method_lower.as_str() {
"bulkdeleteusers" => "bulk_delete".to_string(),
"bulkdeleteusersbystatus" => "bulk_delete_by_status".to_string(),
"bulkrevokeusersubscription" => "bulk_revoke_subscription".to_string(),
"bulkresetusertraffic" => "bulk_reset_traffic".to_string(),
"bulkupdateusers" => "bulk_update".to_string(),
"bulkupdateusersinbounds" => "bulk_update_inbounds".to_string(),
"bulkupdateallusers" => "bulk_update_all".to_string(),
"bulkallresetusertraffic" => "bulk_reset_all_traffic".to_string(),
"getallusers" => "get_all".to_string(),
"createuser" => "create".to_string(),
"updateuser" => "update".to_string(),
"getuserbyuuid" => "get_by_uuid".to_string(),
"deleteuser" => "delete".to_string(),
"getalltags" => "get_tags".to_string(),
"getuserbyshortuuid" => "get_by_short_uuid".to_string(),
"getuserbysubscriptionuuid" => "get_by_subscription_uuid".to_string(),
"getuserbyusername" => "get_by_username".to_string(),
"getuserbytelegramid" => "get_by_telegram_id".to_string(),
"getusersbyemail" => "get_by_email".to_string(),
"getusersbytag" => "get_by_tag".to_string(),
"revokeusersubscription" => "revoke_subscription".to_string(),
"disableuser" => "disable".to_string(),
"enableuser" => "enable".to_string(),
"resetusertraffic" => "reset_traffic".to_string(),
"activateallinbounds" => "activate_all_inbounds".to_string(),
_ => snake_case(&method_str),
}
}
"subscription_templates" => {
match method_lower.as_str() {
"getallsubscriptiontemplates" => "get_all".to_string(),
"createsubscriptiontemplate" => "create".to_string(),
"gettemplate" => "get".to_string(),
"updatetemplate" => "update".to_string(),
"deletesubscriptiontemplate" => "delete".to_string(),
_ => snake_case(&method_str),
}
}
"auth" => {
match method_lower.as_str() {
"login" => "login".to_string(),
"register" => "register".to_string(),
"getstatus" => "get_status".to_string(),
"telegramcallback" => "telegram_callback".to_string(),
_ => snake_case(&method_str),
}
}
_ => snake_case(&method_str),
}
}
fn controller_name_to_struct_name(controller_name: &str) -> String {
// Преобразуем "auth" в "AuthController"
format!("{}Controller", to_pascal_case(controller_name))
}
fn to_pascal_case(s: &str) -> String {
s.split('_')
.map(|part| {
let mut chars = part.chars();
match chars.next() {
None => String::new(),
Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
}
})
.collect()
}
fn controller_name_to_method_name(controller_name: &str) -> String {
// Преобразуем "AuthController" в "auth"
if controller_name.ends_with("Controller") {
let base = &controller_name[..controller_name.len() - 10]; // убираем "Controller"
snake_case(&base)
} else {
snake_case(controller_name)
}
}
fn snake_case(s: &str) -> String {
let mut result = String::new();
let mut chars = s.chars().peekable();
while let Some(c) = chars.next() {
if c.is_uppercase() {
if !result.is_empty() {
result.push('_');
}
result.push(c.to_lowercase().next().unwrap());
} else {
result.push(c);
}
}
result
}
fn generate_controller_code(controllers: &HashMap<String, Vec<(String, String, String, String)>>) -> String {
let mut code = String::new();
// Генерируем структуры контроллеров
for (controller_name, methods) in controllers {
let struct_name = controller_name_to_struct_name(controller_name);
code.push_str(&format!(
"/// {} methods wrapper\npub struct {}<'a> {{\n client: &'a Client,\n}}\n\n",
controller_name, struct_name
));
code.push_str(&format!("impl<'a> {}<'a> {{\n", struct_name));
for (method_name, operation_id, summary, _http_method) in methods {
let clean_method_name = method_name.to_lowercase();
code.push_str(&format!(
" /// {}\n pub fn {}(&self) -> builder::{} {{\n self.client.{}()\n }}\n\n",
summary,
clean_method_name,
operation_id_to_builder_name(operation_id),
to_snake_case_operation_id(operation_id)
));
}
code.push_str("}\n\n");
}
code
}
fn generate_controller_code_with_params(controllers: &HashMap<String, Vec<(String, String, String, String, Vec<(String, String)>, Option<Vec<(String, String)>>)>>) -> String {
let mut code = String::new();
// Генерируем структуры контроллеров
for (controller_name, methods) in controllers {
let struct_name = controller_name_to_struct_name(controller_name);
code.push_str(&format!(
"/// {} methods wrapper\npub struct {}<'a> {{\n client: &'a Client,\n}}\n\n",
controller_name, struct_name
));
code.push_str(&format!("impl<'a> {}<'a> {{\n", struct_name));
for (method_name, operation_id, summary, _http_method, path_params, body_params) in methods {
let clean_method_name = method_name.to_lowercase();
// Generate function signature with actual parameters
let mut fn_params = Vec::new();
fn_params.push("&self".to_string());
// Add path parameters
for (param_name, param_type) in path_params {
fn_params.push(format!("{}: {}", param_name, param_type));
}
// Add body parameters as individual arguments
if let Some(body_fields) = body_params {
for (field_name, field_type) in body_fields {
fn_params.push(format!("{}: {}", field_name, field_type));
}
}
let fn_signature = fn_params.join(", ");
let return_type = format!("impl std::future::Future<Output = Result<ResponseValue<ByteStream>, Error<types::{}Response>>> + '_",
operation_id_to_builder_name(operation_id));
code.push_str(&format!(
" /// {}\n pub fn {}({}) -> {} {{\n",
summary, clean_method_name, fn_signature, return_type
));
// Generate function body that constructs the request
if body_params.is_some() && !body_params.as_ref().unwrap().is_empty() {
// POST/PUT/PATCH with body
let body_fields = body_params.as_ref().unwrap();
code.push_str(" async move {\n");
code.push_str(&format!(" let mut builder = self.client.{}();\n", to_snake_case_operation_id(operation_id)));
if body_fields.len() == 2 && body_fields.iter().any(|(name, _)| name == "username") && body_fields.iter().any(|(name, _)| name == "password") {
// Special case for login-like endpoints
code.push_str(&format!(
" builder = builder.body_map(|b| b.username(username).password(password));\n"
));
} else {
// Generic body construction
let field_calls: Vec<String> = body_fields.iter()
.map(|(field_name, _)| format!(".{}({})", field_name, field_name))
.collect();
code.push_str(&format!(
" builder = builder.body_map(|b| b{});\n",
field_calls.join("")
));
}
code.push_str(" builder.send().await\n");
code.push_str(" }\n");
} else {
// GET/DELETE without body
code.push_str(" async move {\n");
code.push_str(&format!(" self.client.{}().send().await\n", to_snake_case_operation_id(operation_id)));
code.push_str(" }\n");
}
code.push_str(" }\n\n");
}
code.push_str("}\n\n");
}
code
}
fn generate_client_methods(controllers: &HashMap<String, Vec<(String, String, String, String)>>) -> String {
let mut code = String::new();
code.push_str("impl RemnawaveClient {\n");
for (controller_name, _methods) in controllers {
let struct_name = controller_name_to_struct_name(controller_name);
let method_name = controller_name_to_method_name(controller_name);
code.push_str(&format!(
" /// {} methods\n pub fn {}(&self) -> {} {{\n {} {{ client: &self.client }}\n }}\n\n",
controller_name, method_name, struct_name, struct_name
));
}
code.push_str("}\n");
code
}
fn generate_client_methods_simple(controllers: &HashMap<String, Vec<(String, String, String, String, Vec<(String, String)>, Option<Vec<(String, String)>>)>>) -> String {
let mut code = String::new();
code.push_str("impl RemnawaveClient {\n");
for (controller_name, _methods) in controllers {
let struct_name = controller_name_to_struct_name(controller_name);
let method_name = controller_name_to_method_name(controller_name);
code.push_str(&format!(
" /// {} methods\n pub fn {}(&self) -> {} {{\n {} {{ client: &self.client }}\n }}\n\n",
controller_name, method_name, struct_name, struct_name
));
}
code.push_str("}\n");
code
}
// Add OpenAPI parameter analysis functions
fn analyze_operation_parameters(spec: &openapiv3::OpenAPI, operation: &openapiv3::Operation) -> (Vec<(String, String)>, Option<Vec<(String, String)>>) {
let mut path_params = Vec::new();
let mut query_params = Vec::new();
let mut body_params = None;
// Analyze parameters
for param_ref in &operation.parameters {
if let openapiv3::ReferenceOr::Item(param) = param_ref {
let _param_name = match param {
openapiv3::Parameter::Path { parameter_data, .. } => {
path_params.push((parameter_data.name.clone(), "String".to_string()));
continue;
}
openapiv3::Parameter::Query { parameter_data, .. } => {
query_params.push((parameter_data.name.clone(), "String".to_string()));
continue;
}
openapiv3::Parameter::Header { parameter_data: _, .. } => {
continue; // Skip headers for now
}
openapiv3::Parameter::Cookie { parameter_data: _, .. } => {
continue; // Skip cookies for now
}
};
}
}
// Analyze request body
if let Some(request_body_ref) = &operation.request_body {
if let openapiv3::ReferenceOr::Item(request_body) = request_body_ref {
for (content_type, media_type) in &request_body.content {
if content_type == "application/json" {
if let Some(schema_ref) = &media_type.schema {
body_params = Some(analyze_schema_for_parameters(spec, schema_ref));
}
}
}
}
}
let mut all_params = path_params;
all_params.extend(query_params);
(all_params, body_params)
}
fn analyze_schema_for_parameters(spec: &openapiv3::OpenAPI, schema_ref: &openapiv3::ReferenceOr<openapiv3::Schema>) -> Vec<(String, String)> {
match schema_ref {
openapiv3::ReferenceOr::Reference { reference } => {
// Extract schema name from reference like "#/components/schemas/LoginRequestDto"
if let Some(schema_name) = reference.strip_prefix("#/components/schemas/") {
if let Some(schema) = spec.components.as_ref()
.and_then(|c| c.schemas.get(schema_name)) {
if let openapiv3::ReferenceOr::Item(schema) = schema {
return extract_parameters_from_schema(schema);
}
}
}
vec![("body".to_string(), "String".to_string())]
}
openapiv3::ReferenceOr::Item(schema) => {
extract_parameters_from_schema(schema)
}
}
}
fn extract_parameters_from_schema(schema: &openapiv3::Schema) -> Vec<(String, String)> {
match &schema.schema_kind {
openapiv3::SchemaKind::Type(openapiv3::Type::Object(obj)) => {
let mut params = Vec::new();
for (prop_name, _prop_schema) in &obj.properties {
// Simplified type detection - could be enhanced
params.push((prop_name.clone(), "String".to_string()));
}
params
}
_ => vec![("body".to_string(), "String".to_string())],
}
}
fn operation_id_to_builder_name(operation_id: &str) -> String {
// Преобразуем "AuthController_login" в "AuthControllerLogin"
operation_id
.split('_')
.map(|part| {
let mut chars = part.chars();
match chars.next() {
None => String::new(),
Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
}
})
.collect()
}
fn to_snake_case_operation_id(operation_id: &str) -> String {
// Преобразуем "AuthController_login" в "auth_controller_login"
let mut result = String::new();
let mut chars = operation_id.chars().peekable();
while let Some(c) = chars.next() {
if c.is_uppercase() {
if !result.is_empty() {
result.push('_');
}
result.push(c.to_ascii_lowercase());
} else {
result.push(c);
}
}
result
}

2
rustfmt.toml Normal file
View file

@ -0,0 +1,2 @@
max_width = 180
use_small_heuristics = "Off"

31
src/api/client.rs Normal file
View file

@ -0,0 +1,31 @@
use reqwest::Client as HttpClient;
pub struct ApiClient {
base_url: String,
pub token: Option<String>,
http_client: HttpClient,
}
impl ApiClient {
pub fn new(base_url: String, token: Option<String>) -> Self {
let base_url = base_url.trim_end_matches('/').to_string();
Self {
base_url,
token,
http_client: HttpClient::new(),
}
}
pub fn base_url(&self) -> &str {
&self.base_url
}
pub fn http_client(&self) -> &HttpClient {
&self.http_client
}
pub fn set_token(&mut self, token: Option<String>) {
self.token = token;
}
}

View file

@ -0,0 +1,11 @@
use crate::api::controllers::macros::*;
use crate::api::types::*;
api_controller!(AuthController);
api_post!(AuthController, login, "/api/auth/login", LoginRequestDto, LoginResponseDto);
api_post!(AuthController, register, "/api/auth/register", RegisterRequestDto, RegisterResponseDto);
api_get!(AuthController, get_status, "/api/auth/status", GetStatusResponseDto);
api_post!(AuthController, telegram_callback, "/api/auth/oauth2/tg/callback", TelegramCallbackRequestDto, TelegramCallbackResponseDto);
api_post!(AuthController, oauth2_authorize, "/api/auth/oauth2/authorize", OAuth2AuthorizeRequestDto, OAuth2AuthorizeResponseDto);
api_post!(AuthController, oauth2_callback, "/api/auth/oauth2/callback", OAuth2CallbackRequestDto, OAuth2CallbackResponseDto);

View file

@ -0,0 +1,26 @@
use crate::api::controllers::macros::*;
use crate::api::types::billing::*;
use uuid::Uuid;
api_controller!(InfraBillingController);
api_get!(InfraBillingController, get_infra_providers, "/api/infra-billing/providers", GetInfraProvidersResponseDto);
api_post!(InfraBillingController, create_infra_provider, "/api/infra-billing/providers", CreateInfraProviderRequestDto, CreateInfraProviderResponseDto);
api_patch!(InfraBillingController, update_infra_provider, "/api/infra-billing/providers", UpdateInfraProviderRequestDto, UpdateInfraProviderResponseDto);
api_get_with_path!(InfraBillingController, get_infra_provider_by_uuid, "/api/infra-billing/providers/{}", GetInfraProviderByUuidResponseDto, uuid: Uuid);
api_delete!(InfraBillingController, delete_infra_provider_by_uuid, "/api/infra-billing/providers/{}", DeleteInfraProviderByUuidResponseDto, uuid: Uuid);
api_post!(
InfraBillingController,
create_infra_billing_history_record,
"/api/infra-billing/history",
CreateInfraBillingHistoryRecordRequestDto,
CreateInfraBillingHistoryRecordResponseDto
);
api_get!(InfraBillingController, get_infra_billing_history_records, "/api/infra-billing/history", GetInfraBillingHistoryRecordsResponseDto);
api_delete!(InfraBillingController, delete_infra_billing_history_record_by_uuid, "/api/infra-billing/history/{}", DeleteInfraBillingHistoryRecordByUuidResponseDto, uuid: Uuid);
api_get!(InfraBillingController, get_billing_nodes, "/api/infra-billing/nodes", GetInfraBillingNodesResponseDto);
api_patch!(InfraBillingController, update_infra_billing_node, "/api/infra-billing/nodes", UpdateInfraBillingNodeRequestDto, UpdateInfraBillingNodeResponseDto);
api_post!(InfraBillingController, create_infra_billing_node, "/api/infra-billing/nodes", CreateInfraBillingNodeRequestDto, CreateInfraBillingNodeResponseDto);
api_delete!(InfraBillingController, delete_infra_billing_node_by_uuid, "/api/infra-billing/nodes/{}", DeleteInfraBillingNodeByUuidResponseDto, uuid: Uuid);

View file

@ -0,0 +1,13 @@
use crate::api::controllers::macros::*;
use crate::api::types::config_profiles::*;
use uuid::Uuid;
api_controller!(ConfigProfilesController);
api_get!(ConfigProfilesController, get_config_profiles, "/api/config-profiles", GetConfigProfilesResponseDto);
api_post!(ConfigProfilesController, create_config_profile, "/api/config-profiles", CreateConfigProfileRequestDto, CreateConfigProfileResponseDto);
api_patch!(ConfigProfilesController, update_config_profile, "/api/config-profiles", UpdateConfigProfileRequestDto, UpdateConfigProfileResponseDto);
api_get!(ConfigProfilesController, get_all_inbounds, "/api/config-profiles/inbounds", GetAllInboundsResponseDto);
api_get_with_path!(ConfigProfilesController, get_inbounds_by_profile_uuid, "/api/config-profiles/{}", GetInboundsByProfileUuidResponseDto, uuid: Uuid);
api_get_with_path!(ConfigProfilesController, get_config_profile_by_uuid, "/api/config-profiles/{}", GetConfigProfileByUuidResponseDto, uuid: Uuid);
api_delete!(ConfigProfilesController, delete_config_profile_by_uuid, "/api/config-profiles/{}", DeleteConfigProfileResponseDto, uuid: Uuid);

View file

@ -0,0 +1,19 @@
use crate::api::controllers::macros::*;
use crate::api::types::hosts::*;
use uuid::Uuid;
api_controller!(HostsController);
api_post!(HostsController, create_host, "/api/hosts", CreateHostRequestDto, CreateHostResponseDto);
api_patch!(HostsController, update_host, "/api/hosts", UpdateHostRequestDto, UpdateHostResponseDto);
api_get!(HostsController, get_all_hosts, "/api/hosts", GetAllHostsResponseDto);
api_get_with_path!(HostsController, get_one_host, "/api/hosts/{}", GetOneHostResponseDto, uuid: Uuid);
api_delete!(HostsController, delete_host, "/api/hosts/{}", DeleteHostResponseDto, uuid: Uuid);
api_post!(HostsController, reorder_hosts, "/api/hosts/actions/reorder", ReorderHostRequestDto, ReorderHostResponseDto);
api_post!(HostsController, bulk_delete_hosts, "/api/hosts/bulk/delete", BulkDeleteHostsRequestDto, BulkDeleteHostsResponseDto);
api_post!(HostsController, bulk_disable_hosts, "/api/hosts/bulk/disable", BulkDisableHostsRequestDto, BulkDisableHostsResponseDto);
api_post!(HostsController, bulk_enable_hosts, "/api/hosts/bulk/enable", BulkEnableHostsRequestDto, BulkEnableHostsResponseDto);
api_post!(HostsController, bulk_set_inbound_to_hosts, "/api/hosts/bulk/set-inbound", SetInboundToManyHostsRequestDto, SetInboundToManyHostsResponseDto);
api_post!(HostsController, bulk_set_port_to_hosts, "/api/hosts/bulk/set-port", SetPortToManyHostsRequestDto, SetPortToManyHostsResponseDto);

View file

@ -0,0 +1,10 @@
use uuid::Uuid;
use crate::api::controllers::macros::*;
use crate::api::types::hwid::*;
api_controller!(HwidUserDevicesController);
api_post!(HwidUserDevicesController, create_user_hwid_device, "/api/hwid/devices", CreateUserHwidDeviceRequestDto, CreateUserHwidDeviceResponseDto);
api_post!(HwidUserDevicesController, delete_user_hwid_device, "/api/hwid/devices/delete", DeleteUserHwidDeviceRequestDto, DeleteUserHwidDeviceResponseDto);
api_get_with_path!(HwidUserDevicesController, get_user_hwid_devices, "/api/hwid/devices/{}", GetUserHwidDevicesResponseDto, user_uuid: Uuid);

View file

@ -0,0 +1,14 @@
use crate::api::controllers::macros::*;
use crate::api::types::internal_squads::*;
use uuid::Uuid;
api_controller!(InternalSquadsController);
api_get!(InternalSquadsController, get_internal_squads, "/api/internal-squads", GetInternalSquadsResponseDto);
api_get_with_path!(InternalSquadsController, get_internal_squad_by_uuid, "/api/internal-squads/{}", GetInternalSquadByUuidResponseDto,uuid: Uuid);
api_post!(InternalSquadsController, create_internal_squad, "/api/internal-squads", CreateInternalSquadRequestDto, CreateInternalSquadResponseDto);
api_patch!(InternalSquadsController, update_internal_squad, "/api/internal-squads", UpdateInternalSquadRequestDto, UpdateInternalSquadResponseDto);
api_delete!(InternalSquadsController, delete_internal_squad, "/api/internal-squads/{}", DeleteInternalSquadResponseDto, uuid: Uuid);
api_post_with_path_no_body!(InternalSquadsController, bulk_add_users_to_internal_squad, "/api/internal-squads/{}/bulk-actions/add-users", AddUsersToInternalSquadResponseDto, uuid: Uuid);
api_delete!(InternalSquadsController, bulk_remove_users_from_internal_squad, "/api/internal-squads/{}/bulk-actions/remove-users", RemoveUsersFromInternalSquadResponseDto, uuid: Uuid);

View file

@ -0,0 +1,6 @@
use crate::api::controllers::macros::*;
use crate::api::types::keygen::GetPubKeyResponseDto;
api_controller!(KeygenController);
api_get!(KeygenController, generate_key, "/api/keygen", GetPubKeyResponseDto);

View file

@ -0,0 +1,4 @@
pub use crate::{
api_controller, api_delete, api_get, api_get_with_path, api_get_with_path_and_query, api_get_with_query, api_patch, api_patch_with_path, api_post, api_post_no_body,
api_post_with_path, api_post_with_path_no_body, api_request_common,
};

View file

@ -0,0 +1,27 @@
pub mod macros;
pub mod auth;
pub mod billing;
pub mod config_profiles;
pub mod hosts;
pub mod hwid;
pub mod internal_squads;
pub mod keygen;
pub mod nodes;
pub mod subscriptions;
pub mod system;
pub mod tokens;
pub mod users;
pub use auth::AuthController;
pub use billing::InfraBillingController;
pub use config_profiles::ConfigProfilesController;
pub use hosts::HostsController;
pub use hwid::HwidUserDevicesController;
pub use internal_squads::InternalSquadsController;
pub use keygen::KeygenController;
pub use nodes::{NodesController, NodesUsageController};
pub use subscriptions::{SubscriptionSettingsController, SubscriptionTemplateController, SubscriptionsController};
pub use system::SystemController;
pub use tokens::ApiTokensController;
pub use users::UsersController;

View file

@ -0,0 +1,23 @@
use crate::api::controllers::macros::*;
use crate::api::types::nodes::*;
use uuid::Uuid;
api_controller!(NodesController);
api_post!(NodesController, create_node, "/api/nodes", CreateNodeRequestDto, CreateNodeResponseDto);
api_get!(NodesController, get_all_nodes, "/api/nodes", GetAllNodesResponseDto);
api_patch!(NodesController, update_node, "/api/nodes", UpdateNodeRequestDto, UpdateNodeResponseDto);
api_get_with_path!(NodesController, get_one_node, "/api/nodes/{}", GetOneNodeResponseDto, uuid: Uuid);
api_delete!(NodesController, delete_node, "/api/nodes/{}", DeleteNodeResponseDto, uuid: Uuid);
api_post_with_path_no_body!(NodesController, enable_node, "/api/nodes/{}/actions/enable", EnableNodeResponseDto, uuid: Uuid);
api_post_with_path_no_body!(NodesController, disable_node, "/api/nodes/{}/actions/disable", DisableNodeResponseDto, uuid: Uuid);
api_post_with_path_no_body!(NodesController, restart_node, "/api/nodes/{}/actions/restart", RestartNodeResponseDto, uuid: Uuid);
api_post_no_body!(NodesController, restart_all_nodes, "/api/nodes/actions/restart-all", RestartAllNodesResponseDto);
api_post!(NodesController, reorder_nodes, "/api/nodes/actions/reorder", ReorderNodeRequestDto, ReorderNodeResponseDto);
api_controller!(NodesUsageController);
api_get_with_query!(NodesUsageController, get_nodes_usage_by_range, "/api/nodes/usage/range", GetNodesUsageByRangeResponseDto, start: Option<String>, end: Option<String>);
api_get_with_path_and_query!(NodesUsageController, get_node_user_usage, "/api/nodes/usage/{}/users/range", GetNodeUserUsageByRangeResponseDto, path_params: [uuid: String], query_params: [start: Option<String>, end: Option<String>]);
api_get!(NodesUsageController, get_nodes_realtime_usage, "/api/nodes/usage/realtime", GetNodesRealtimeUsageResponseDto);

View file

@ -0,0 +1,33 @@
use crate::api::controllers::macros::*;
use crate::api::types::subscriptions::*;
api_controller!(SubscriptionsController);
api_get_with_path!(SubscriptionsController, get_subscription_info_by_short_uuid, "/api/sub/{}/info", GetSubscriptionInfoResponseDto, short_uuid: String);
api_get_with_path!(SubscriptionsController, get_raw_subscription_by_short_uuid, "/api/sub/{}/raw", GetRawSubscriptionByShortUuidResponseDto, short_uuid: String);
api_get_with_path!(SubscriptionsController, get_subscription, "/api/sub/{}", String, short_uuid: String);
api_get_with_path!(SubscriptionsController, get_subscription_by_client_type, "/api/sub/{}/{}", String, short_uuid: String, client_type: SubscriptionClientType);
impl SubscriptionsController {
#[doc = "GET /api/sub/outline/{}/{}/{} - SubscriptionsController"]
pub async fn get_subscription_with_type(&self, short_uuid: String, encoded_tag: String, subscription_type: Option<String>) -> Result<String, crate::ApiError> {
let subscription_type = subscription_type.unwrap_or_else(|| "ss".to_string());
let url = format!("{}{}", self.client.base_url(), format!("/api/sub/outline/{}/{}/{}", short_uuid, subscription_type, encoded_tag));
let response = api_request_common!(self, get, url, None::<()>)?;
self.handle_response(response, url).await
}
}
api_get_with_query!(SubscriptionsController, get_all_subscriptions, "/api/subscriptions", GetAllSubscriptionsResponseDto, size: Option<usize>, start: Option<usize>);
api_get_with_path!(SubscriptionsController, get_subscription_by_username, "/api/subscriptions/by-username/{}", GetSubscriptionByUsernameResponseDto, username: String);
api_controller!(SubscriptionTemplateController);
api_get_with_path!(SubscriptionTemplateController, get_template, "/api/subscription-templates/{}", GetTemplateResponseDto, template_type: SubscriptionTemplateType);
api_patch!(SubscriptionTemplateController, update_template, "/api/subscription-templates", UpdateTemplateRequestDto, UpdateTemplateResponseDto);
api_controller!(SubscriptionSettingsController);
api_get!(SubscriptionSettingsController, get_settings, "/api/subscription-settings", GetSubscriptionSettingsResponseDto);
api_patch!(SubscriptionSettingsController, update_settings, "/api/subscription-settings", UpdateSubscriptionSettingsRequestDto, UpdateSubscriptionSettingsResponseDto);

View file

@ -0,0 +1,10 @@
use crate::api::controllers::macros::*;
use crate::api::types::system::*;
api_controller!(SystemController);
api_get!(SystemController, get_stats, "/api/system/stats", GetStatsResponseDto);
api_get!(SystemController, get_bandwidth_stats, "/api/system/stats/bandwidth", GetBandwidthStatsResponseDto);
api_get!(SystemController, get_nodes_statistics, "/api/system/stats/nodes", GetNodesStatisticsResponseDto);
api_get!(SystemController, get_remnawave_health, "/api/system/health", GetRemnawaveHealthResponseDto);
api_get!(SystemController, get_nodes_metrics, "/api/system/nodes/metrics", GetNodesMetricsResponseDto);

View file

@ -0,0 +1,9 @@
use crate::api::controllers::macros::*;
use crate::api::types::tokens::*;
use uuid::Uuid;
api_controller!(ApiTokensController);
api_post!(ApiTokensController, create, "/api/tokens", CreateApiTokenRequestDto, CreateApiTokenResponseDto);
api_get!(ApiTokensController, find_all, "/api/tokens", FindAllApiTokensResponseDto);
api_delete!(ApiTokensController, delete, "/api/tokens/{}", DeleteApiTokenResponseDto, uuid: Uuid);

View file

@ -0,0 +1,33 @@
use crate::api::controllers::macros::*;
use crate::api::types::*;
use uuid::Uuid;
api_controller!(UsersController);
api_post!(UsersController, create_user, "/api/users", CreateUserRequestDto, CreateUserResponseDto);
api_patch!(UsersController, update_user, "/api/users", UpdateUserRequestDto, UpdateUserResponseDto);
api_get_with_query!(UsersController, get_all_users, "/api/users", GetAllUsersResponseDto, size: Option<u32>, start: Option<u32>);
api_delete!(UsersController, delete_user, "/api/users/{uuid}", DeleteUserResponseDto, uuid: Uuid);
api_get_with_path!(UsersController, get_user_by_uuid, "/api/users/{}", GetUserByUuidResponseDto, uuid: Uuid);
api_get!(UsersController, get_all_tags, "/api/users/tags", GetAllTagsResponseDto);
api_get_with_path!(UsersController, get_user_accessible_nodes, "/api/users/{}/accessible-nodes", GetUserAccessibleNodesResponseDto, uuid: Uuid);
api_get_with_path!(UsersController, get_user_by_short_uuid, "/api/users/by-short-uuid/{}", GetUserByShortUuidResponseDto, short_uuid: String);
api_get_with_path!(UsersController, get_user_by_username, "/api/users/by-username/{}", GetUserByUsernameResponseDto, username: String);
api_get_with_path!(UsersController, get_user_by_telegram_id, "/api/users/by-telegram-id/{}", GetUserByTelegramIdResponseDto, telegram_id: String);
api_get_with_path!(UsersController, get_users_by_email, "/api/users/by-email/{}", GetUserByEmailResponseDto, email: String);
api_get_with_path!(UsersController, get_users_by_tag, "/api/users/by-tag/{}", GetUserByTagResponseDto, tag: String);
api_post_with_path!(UsersController, revoke_user_subscription, "/api/users/{}/actions/revoke", RevokeUserSubscriptionBodyDto, RevokeUserSubscriptionResponseDto, uuid: Uuid);
api_post_with_path_no_body!(UsersController, disable_user, "/api/users/{}/actions/disable", DisableUserResponseDto, uuid: Uuid);
api_post_with_path_no_body!(UsersController, enable_user, "/api/users/{}/actions/enable", EnableUserResponseDto, uuid: Uuid);
api_post_with_path_no_body!(UsersController, reset_user_traffic, "/api/users/{}/actions/reset-traffic", ResetUserTrafficResponseDto, uuid: Uuid);
api_post!(UsersController, bulk_delete_users_by_status, "/api/users/bulk/delete-by-status", BulkDeleteUsersByStatusRequestDto, BulkDeleteUsersByStatusResponseDto);
api_post!(UsersController, bulk_delete_users, "/api/users/bulk/delete", BulkDeleteUsersRequestDto, BulkDeleteUsersResponseDto);
api_post!(UsersController, bulk_revoke_users_subscription, "/api/users/bulk/revoke-subscription", BulkRevokeUsersSubscriptionRequestDto, BulkRevokeUsersSubscriptionResponseDto);
api_post!(UsersController, bulk_reset_user_traffic, "/api/users/bulk/reset-traffic", BulkResetTrafficUsersRequestDto, BulkResetTrafficUsersResponseDto);
api_post!(UsersController, bulk_update_users, "/api/users/bulk/update", BulkUpdateUsersRequestDto, BulkUpdateUsersResponseDto);
api_post!(UsersController, bulk_update_users_internal_squads, "/api/users/bulk/update-squads", BulkUpdateUsersSquadsRequestDto, BulkUpdateUsersSquadsResponseDto);
api_post!(UsersController, bulk_update_all_users, "/api/users/bulk/all/update", BulkAllUpdateUsersRequestDto, BulkAllUpdateUsersResponseDto);
api_post_no_body!(UsersController, bulk_all_reset_user_traffic, "/api/users/bulk/all/reset-traffic", BulkAllResetTrafficUsersResponseDto);
api_get_with_path!(UsersController, get_user_usage_by_range, "/api/users/stats/usage/{}/range", GetUserUsageByRangeResponseDto, uuid: Uuid);

302
src/api/macros.rs Normal file
View file

@ -0,0 +1,302 @@
#[macro_export]
macro_rules! api_request_common {
($self:expr, $method:ident, $url:expr, $body:expr) => {{
let mut request = $self.client.http_client().$method(&$url);
if let Some(token) = &$self.client.token {
request = request.header("Authorization", format!("Bearer {}", token));
}
let request = if let Some(body) = $body {
request.json(&body)
} else {
request
};
request.send().await.map_err(|e| crate::ApiError {
status_code: 0,
url: $url.clone(),
request_body: None,
response_body: e.to_string(),
response_headers: std::collections::HashMap::new(),
timestamp: None,
path: None,
message: Some(e.to_string()),
error_code: None,
error: None,
})
}};
}
#[macro_export]
macro_rules! api_get {
($controller:ident, $method_name:ident, $path:expr, $response_type:ty) => {
impl $controller {
#[doc = concat!("GET ", $path, " - ", stringify!($controller))]
pub async fn $method_name(&self) -> Result<$response_type, crate::ApiError> {
let url = format!("{}{}", self.client.base_url(), $path);
let response = api_request_common!(self, get, url, None::<()>)?;
self.handle_response(response, url).await
}
}
};
}
/// Macro for generating POST endpoints with body
#[macro_export]
macro_rules! api_post {
($controller:ident, $method_name:ident, $path:expr, $request_type:ty, $response_type:ty) => {
impl $controller {
#[doc = concat!("POST ", $path, " - ", stringify!($controller))]
pub async fn $method_name(&self, request: $request_type) -> Result<$response_type, crate::ApiError> {
let url = format!("{}{}", self.client.base_url(), $path);
let response = api_request_common!(self, post, url, Some(request))?;
self.handle_response(response, url).await
}
}
};
}
/// Macro for generating POST endpoints with no body
#[macro_export]
macro_rules! api_post_no_body {
($controller:ident, $method_name:ident, $path:expr, $response_type:ty) => {
impl $controller {
#[doc = concat!("POST ", $path, " - ", stringify!($controller))]
pub async fn $method_name(&self) -> Result<$response_type, crate::ApiError> {
let url = format!("{}{}", self.client.base_url(), $path);
let response = api_request_common!(self, post, url, None::<()>)?;
self.handle_response(response, url).await
}
}
};
}
/// Macro for generating GET endpoints with path parameters
#[macro_export]
macro_rules! api_get_with_path {
($controller:ident, $method_name:ident, $path:expr, $response_type:ty, $($param:ident: $param_type:ty),*) => {
impl $controller {
#[doc = concat!("GET ", $path, " - ", stringify!($controller))]
pub async fn $method_name(&self, $($param: $param_type),*) -> Result<$response_type, crate::ApiError> {
let url = format!("{}{}", self.client.base_url(), format!($path, $($param),*));
let response = api_request_common!(self, get, url, None::<()>)?;
self.handle_response(response, url).await
}
}
};
}
/// Macro for generating GET endpoints with query parameters
#[macro_export]
macro_rules! api_get_with_query {
($controller:ident, $method_name:ident, $path:expr, $response_type:ty, $($param:ident: $param_type:ty),*) => {
impl $controller {
#[doc = concat!("GET ", $path, " - ", stringify!($controller))]
pub async fn $method_name(&self, $($param: $param_type),*) -> Result<$response_type, crate::ApiError> {
let mut url = format!("{}{}", self.client.base_url(), $path);
let mut query_params = Vec::new();
$(
if let Some(value) = $param.as_ref() {
query_params.push(format!("{}={}", stringify!($param), value));
}
)*
if !query_params.is_empty() {
url = format!("{}?{}", url, query_params.join("&"));
}
let response = api_request_common!(self, get, url, None::<()>)?;
self.handle_response(response, url).await
}
}
};
}
/// Macro for generating GET endpoints with both path and query parameters
#[macro_export]
macro_rules! api_get_with_path_and_query {
($controller:ident, $method_name:ident, $path:expr, $response_type:ty, path_params: [$($path_param:ident: $path_param_type:ty),*], query_params: [$($query_param:ident: $query_param_type:ty),*]) => {
impl $controller {
#[doc = concat!("GET ", $path, " - ", stringify!($controller))]
pub async fn $method_name(&self, $($path_param: $path_param_type,)* $($query_param: $query_param_type),*) -> Result<$response_type, crate::ApiError> {
let mut url = format!("{}{}", self.client.base_url(), format!($path, $($path_param),*));
let mut query_params = Vec::new();
$(
if let Some(value) = $query_param.as_ref() {
query_params.push(format!("{}={}", stringify!($query_param), value));
}
)*
if !query_params.is_empty() {
url = format!("{}?{}", url, query_params.join("&"));
}
let response = api_request_common!(self, get, url, None::<()>)?;
self.handle_response(response, url).await
}
}
};
}
/// Macro for generating PATCH endpoints with request body
#[macro_export]
macro_rules! api_patch {
($controller:ident, $method_name:ident, $path:expr, $request_type:ty, $response_type:ty) => {
impl $controller {
#[doc = concat!("PATCH ", $path, " - ", stringify!($controller))]
pub async fn $method_name(&self, request: $request_type) -> Result<$response_type, crate::ApiError> {
let url = format!("{}{}", self.client.base_url(), $path);
let response = api_request_common!(self, patch, url, Some(request))?;
self.handle_response(response, url).await
}
}
};
}
/// Macro for generating DELETE endpoints with path parameters
#[macro_export]
macro_rules! api_delete {
($controller:ident, $method_name:ident, $path:expr, $response_type:ty, $param:ident: $param_type:ty) => {
impl $controller {
#[doc = concat!("DELETE ", $path, " - ", stringify!($controller))]
pub async fn $method_name(&self, $param: $param_type) -> Result<$response_type, crate::ApiError> {
let url = format!("{}{}", self.client.base_url(), $path.replace(&format!("{{{}}}", stringify!($param)), &$param.to_string()));
let response = api_request_common!(self, delete, url, None::<()>)?;
self.handle_response(response, url).await
}
}
};
}
#[macro_export]
macro_rules! api_post_with_path {
($controller:ident, $method_name:ident, $path:expr, $request_type:ty, $response_type:ty, $($param:ident: $param_type:ty),*) => {
impl $controller {
#[doc = concat!("POST ", $path, " - ", stringify!($controller))]
pub async fn $method_name(&self, $($param: $param_type,)* request: $request_type) -> Result<$response_type, crate::ApiError> {
let url = format!("{}{}", self.client.base_url(), format!($path, $($param),*));
let response = api_request_common!(self, post, url, Some(request))?;
self.handle_response(response, url).await
}
}
};
}
/// Macro for generating POST endpoints with path parameters and no request body
#[macro_export]
macro_rules! api_post_with_path_no_body {
($controller:ident, $method_name:ident, $path:expr, $response_type:ty, $($param:ident: $param_type:ty),*) => {
impl $controller {
#[doc = concat!("POST ", $path, " - ", stringify!($controller))]
pub async fn $method_name(&self, $($param: $param_type),*) -> Result<$response_type, crate::ApiError> {
let url = format!("{}{}", self.client.base_url(), format!($path, $($param),*));
let response = api_request_common!(self, post, url, None::<()>)?;
self.handle_response(response, url).await
}
}
};
}
/// Macro for generating PATCH endpoints with path parameters
#[macro_export]
macro_rules! api_patch_with_path {
($controller:ident, $method_name:ident, $path:expr, $request_type:ty, $response_type:ty, $($param:ident: $param_type:ty),*) => {
impl $controller {
#[doc = concat!("PATCH ", $path, " - ", stringify!($controller))]
pub async fn $method_name(&self, $($param: $param_type,)* request: $request_type) -> Result<$response_type, crate::ApiError> {
let url = format!("{}{}", self.client.base_url(), format!($path, $($param),*));
let response = api_request_common!(self, patch, url, Some(request))?;
self.handle_response(response, url).await
}
}
};
}
/// Macro for generating controller structs
#[macro_export]
macro_rules! api_controller {
($controller:ident) => {
pub struct $controller {
client: std::sync::Arc<crate::api::client::ApiClient>,
}
impl $controller {
pub fn new(client: std::sync::Arc<crate::api::client::ApiClient>) -> Self {
Self {
client,
}
}
async fn handle_response<T>(&self, response: reqwest::Response, url: String) -> Result<T, crate::ApiError>
where
T: serde::de::DeserializeOwned,
{
let status = response.status();
let response_headers: std::collections::HashMap<String, String> =
response.headers().iter().filter_map(|(name, value)| value.to_str().ok().map(|v| (name.to_string(), v.to_string()))).collect();
if status.is_success() {
let response_text = response.text().await.map_err(|e| crate::ApiError {
status_code: status.as_u16(),
url: url.clone(),
request_body: None,
response_body: e.to_string(),
response_headers: response_headers.clone(),
timestamp: None,
path: None,
message: Some("Failed to read response body".to_string()),
error_code: None,
error: None,
})?;
serde_json::from_str(&response_text).map_err(|e| crate::ApiError {
status_code: status.as_u16(),
url: url.clone(),
request_body: None,
response_body: response_text,
response_headers,
timestamp: None,
path: None,
message: Some(format!("Failed to deserialize response: {}", e)),
error_code: None,
error: None,
})
} else {
let response_body = response.text().await.unwrap_or_default();
// Try to parse error details from response
let (timestamp, path, message, error_code, error) = if !response_body.is_empty() {
if let Ok(json_value) = serde_json::from_str::<serde_json::Value>(&response_body) {
let timestamp = json_value.get("timestamp").and_then(|v| v.as_str()).map(|s| s.to_string());
let path = json_value.get("path").and_then(|v| v.as_str()).map(|s| s.to_string());
let message = json_value.get("message").and_then(|v| v.as_str()).map(|s| s.to_string());
let error_code = json_value.get("errorCode").and_then(|v| v.as_str()).map(|s| s.to_string());
let error = json_value.get("error").and_then(|v| v.as_str()).map(|s| s.to_string());
(timestamp, path, message, error_code, error)
} else {
(None, None, None, None, None)
}
} else {
(None, None, None, None, None)
};
Err(crate::ApiError {
status_code: status.as_u16(),
url,
request_body: None,
response_body,
response_headers,
timestamp,
path,
message,
error_code,
error,
})
}
}
}
};
}

42
src/api/mod.rs Normal file
View file

@ -0,0 +1,42 @@
pub mod client;
pub mod controllers;
pub mod macros;
pub mod remnawave_client;
pub mod types;
pub use client::ApiClient;
pub use remnawave_client::RemnawaveApiClient;
pub use types::{
AddUsersToInternalSquadResponseDto, ApiError as ApiErrorType, BulkAllResetTrafficUsersResponseDto, BulkAllUpdateUsersRequestDto, BulkAllUpdateUsersResponseDto,
BulkDeleteHostsRequestDto, BulkDeleteHostsResponseDto, BulkDeleteUsersByStatusRequestDto, BulkDeleteUsersByStatusResponseDto, BulkDeleteUsersRequestDto,
BulkDeleteUsersResponseDto, BulkDisableHostsRequestDto, BulkDisableHostsResponseDto, BulkEnableHostsRequestDto, BulkEnableHostsResponseDto, BulkResetTrafficUsersRequestDto,
BulkResetTrafficUsersResponseDto, BulkRevokeUsersSubscriptionRequestDto, BulkRevokeUsersSubscriptionResponseDto, BulkUpdateUsersRequestDto, BulkUpdateUsersResponseDto,
BulkUpdateUsersSquadsRequestDto, BulkUpdateUsersSquadsResponseDto, CreateApiTokenRequestDto, CreateApiTokenResponseDto, CreateConfigProfileRequestDto,
CreateConfigProfileResponseDto, CreateHostRequestDto, CreateHostResponseDto, CreateInfraBillingHistoryRecordRequestDto, CreateInfraBillingHistoryRecordResponseDto,
CreateInfraBillingNodeRequestDto, CreateInfraBillingNodeResponseDto, CreateInfraProviderRequestDto, CreateInfraProviderResponseDto, CreateInternalSquadRequestDto,
CreateInternalSquadResponseDto, CreateNodeRequestDto, CreateNodeResponseDto, CreateUserHwidDeviceRequestDto, CreateUserHwidDeviceResponseDto, CreateUserRequestDto,
CreateUserResponseDto, DeleteApiTokenResponseDto, DeleteConfigProfileResponseDto, DeleteHostResponseDto, DeleteInfraBillingHistoryRecordByUuidResponseDto,
DeleteInfraBillingNodeByUuidResponseDto, DeleteInfraProviderByUuidResponseDto, DeleteInternalSquadResponseDto, DeleteNodeResponseDto, DeleteUserHwidDeviceRequestDto,
DeleteUserHwidDeviceResponseDto, DeleteUserResponseDto, DisableNodeResponseDto, DisableUserResponseDto, EnableNodeResponseDto, EnableUserResponseDto,
FindAllApiTokensResponseDto, GetAllHostsResponseDto, GetAllInboundsResponseDto, GetAllNodesResponseDto, GetAllSubscriptionsResponseDto, GetAllTagsResponseDto,
GetAllUsersResponseDto, GetBandwidthStatsResponseDto, GetConfigProfileByUuidResponseDto, GetConfigProfilesResponseDto, GetInboundsByProfileUuidResponseDto,
GetInfraBillingHistoryRecordsResponseDto, GetInfraBillingNodesResponseDto, GetInfraProviderByUuidResponseDto, GetInfraProvidersResponseDto, GetInternalSquadByUuidResponseDto,
GetInternalSquadsResponseDto, GetNodeUserUsageByRangeResponseDto, GetNodesMetricsResponseDto, GetNodesRealtimeUsageResponseDto, GetNodesStatisticsResponseDto,
GetNodesUsageByRangeResponseDto, GetOneHostResponseDto, GetOneNodeResponseDto, GetPubKeyResponseDto, GetRawSubscriptionByShortUuidResponseDto, GetRemnawaveHealthResponseDto,
GetStatsResponseDto, GetStatusResponseDto, GetSubscriptionByUsernameResponseDto, GetSubscriptionInfoResponseDto, GetSubscriptionSettingsResponseDto, GetTemplateResponseDto,
GetUserAccessibleNodesResponseDto, GetUserByEmailResponseDto, GetUserByShortUuidResponseDto, GetUserByTagResponseDto, GetUserByTelegramIdResponseDto,
GetUserByUsernameResponseDto, GetUserByUuidResponseDto, GetUserHwidDevicesResponseDto, GetUserUsageByRangeResponseDto, LoginRequestDto, LoginResponseDto,
OAuth2AuthorizeRequestDto, OAuth2AuthorizeResponseDto, OAuth2CallbackRequestDto, OAuth2CallbackResponseDto, RegisterRequestDto, RegisterResponseDto,
RemoveUsersFromInternalSquadResponseDto, ReorderHostRequestDto, ReorderHostResponseDto, ReorderNodeRequestDto, ReorderNodeResponseDto, ResetUserTrafficResponseDto,
RestartAllNodesResponseDto, RestartNodeResponseDto, RevokeUserSubscriptionBodyDto, RevokeUserSubscriptionResponseDto, SetPortToManyHostsRequestDto,
SetPortToManyHostsResponseDto, TelegramCallbackRequestDto, TelegramCallbackResponseDto, UpdateConfigProfileRequestDto, UpdateConfigProfileResponseDto, UpdateHostRequestDto,
UpdateHostResponseDto, UpdateInfraBillingNodeRequestDto, UpdateInfraBillingNodeResponseDto, UpdateInfraProviderRequestDto, UpdateInfraProviderResponseDto,
UpdateInternalSquadRequestDto, UpdateInternalSquadResponseDto, UpdateNodeRequestDto, UpdateNodeResponseDto, UpdateSubscriptionSettingsRequestDto,
UpdateSubscriptionSettingsResponseDto, UpdateTemplateRequestDto, UpdateTemplateResponseDto, UpdateUserRequestDto, UpdateUserResponseDto,
};
pub use controllers::{
ApiTokensController, AuthController, ConfigProfilesController, HostsController, HwidUserDevicesController, InfraBillingController, InternalSquadsController, KeygenController,
NodesController, NodesUsageController, SubscriptionSettingsController, SubscriptionTemplateController, SubscriptionsController, SystemController, UsersController,
};

View file

@ -0,0 +1,76 @@
use crate::api::{
ApiClient, ApiTokensController, AuthController, ConfigProfilesController, HostsController, HwidUserDevicesController, InfraBillingController, InternalSquadsController,
KeygenController, NodesController, NodesUsageController, SubscriptionSettingsController, SubscriptionTemplateController, SubscriptionsController, SystemController,
UsersController,
};
use anyhow::Result;
use std::sync::Arc;
pub struct RemnawaveApiClient {
client: Arc<ApiClient>,
pub auth: AuthController,
pub users: UsersController,
pub subscriptions: SubscriptionsController,
pub subscription_templates: SubscriptionTemplateController,
pub subscription_settings: SubscriptionSettingsController,
pub nodes: NodesController,
pub nodes_usage: NodesUsageController,
pub hosts: HostsController,
pub system: SystemController,
pub tokens: ApiTokensController,
pub config_profiles: ConfigProfilesController,
pub internal_squads: InternalSquadsController,
pub hwid: HwidUserDevicesController,
pub billing: InfraBillingController,
pub keygen: KeygenController,
}
impl RemnawaveApiClient {
pub fn new(base_url: String, token: Option<String>) -> Result<Self> {
let client = Arc::new(ApiClient::new(base_url, token));
Ok(Self {
auth: AuthController::new(client.clone()),
users: UsersController::new(client.clone()),
subscriptions: SubscriptionsController::new(client.clone()),
subscription_templates: SubscriptionTemplateController::new(client.clone()),
subscription_settings: SubscriptionSettingsController::new(client.clone()),
nodes: NodesController::new(client.clone()),
nodes_usage: NodesUsageController::new(client.clone()),
hosts: HostsController::new(client.clone()),
system: SystemController::new(client.clone()),
tokens: ApiTokensController::new(client.clone()),
config_profiles: ConfigProfilesController::new(client.clone()),
internal_squads: InternalSquadsController::new(client.clone()),
hwid: HwidUserDevicesController::new(client.clone()),
billing: InfraBillingController::new(client.clone()),
keygen: KeygenController::new(client.clone()),
client,
})
}
pub fn set_token(&mut self, token: Option<String>) {
let new_client = Arc::new(ApiClient::new(self.client.base_url().to_string(), token));
self.client = new_client.clone();
self.auth = AuthController::new(new_client.clone());
self.users = UsersController::new(new_client.clone());
self.subscriptions = SubscriptionsController::new(new_client.clone());
self.subscription_templates = SubscriptionTemplateController::new(new_client.clone());
self.subscription_settings = SubscriptionSettingsController::new(new_client.clone());
self.nodes = NodesController::new(new_client.clone());
self.nodes_usage = NodesUsageController::new(new_client.clone());
self.hosts = HostsController::new(new_client.clone());
self.system = SystemController::new(new_client.clone());
self.tokens = ApiTokensController::new(new_client.clone());
self.config_profiles = ConfigProfilesController::new(new_client.clone());
self.internal_squads = InternalSquadsController::new(new_client.clone());
self.hwid = HwidUserDevicesController::new(new_client.clone());
self.billing = InfraBillingController::new(new_client.clone());
self.keygen = KeygenController::new(new_client.clone());
}
pub fn base_url(&self) -> &str {
self.client.base_url()
}
}

157
src/api/types/auth.rs Normal file
View file

@ -0,0 +1,157 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct LoginRequestDto {
pub username: String,
pub password: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct LoginResponseData {
pub access_token: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct LoginResponseDto {
pub response: LoginResponseData,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct RegisterRequestDto {
pub username: String,
#[serde(with = "password_validation")]
pub password: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct RegisterResponseData {
pub access_token: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct RegisterResponseDto {
pub response: RegisterResponseData,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct TgAuth {
pub bot_id: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct OAuth2Providers {
pub providers: std::collections::HashMap<String, bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct GetStatusResponseData {
pub is_login_allowed: bool,
pub is_register_allowed: bool,
pub tg_auth: Option<TgAuth>,
pub oauth2: OAuth2Providers,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct GetStatusResponseDto {
pub response: GetStatusResponseData,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct TelegramCallbackRequestDto {
pub id: i64,
pub first_name: String,
pub last_name: Option<String>,
pub username: Option<String>,
pub photo_url: Option<String>,
pub auth_date: usize,
pub hash: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct TelegramCallbackResponseData {
pub access_token: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct TelegramCallbackResponseDto {
pub response: TelegramCallbackResponseData,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum OAuth2Provider {
Github,
Pocketid,
Yandex,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct OAuth2AuthorizeRequestDto {
pub provider: OAuth2Provider,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct OAuth2AuthorizeResponseData {
pub authorization_url: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct OAuth2AuthorizeResponseDto {
pub response: OAuth2AuthorizeResponseData,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct OAuth2CallbackRequestDto {
pub provider: OAuth2Provider,
pub code: String,
pub state: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct OAuth2CallbackResponseData {
pub access_token: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct OAuth2CallbackResponseDto {
pub response: OAuth2CallbackResponseData,
}
mod password_validation {
use serde::{Deserialize, Deserializer, Serialize, Serializer};
pub fn serialize<S>(password: &String, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
password.serialize(serializer)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<String, D::Error>
where
D: Deserializer<'de>,
{
let password = String::deserialize(deserializer)?;
if password.len() < 24 {
return Err(serde::de::Error::custom("Password must be at least 24 characters long"));
}
let has_upper = password.chars().any(|c| c.is_ascii_uppercase());
let has_lower = password.chars().any(|c| c.is_ascii_lowercase());
let has_digit = password.chars().any(|c| c.is_ascii_digit());
if !has_upper || !has_lower || !has_digit {
return Err(serde::de::Error::custom("Password must contain at least one uppercase letter, one lowercase letter, and one digit"));
}
Ok(password)
}
}

229
src/api/types/billing.rs Normal file
View file

@ -0,0 +1,229 @@
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct GetInfraProvidersResponseDto {
pub response: GetInfraProvidersData,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct GetInfraProvidersData {
pub total: usize,
pub providers: Vec<InfraProviderDto>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct GetInfraProviderByUuidResponseDto {
pub response: InfraProviderDto,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct DeleteInfraProviderByUuidResponseDto {
pub response: DeleteInfraProviderData,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct DeleteInfraProviderData {
pub is_deleted: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct CreateInfraProviderRequestDto {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub favicon_link: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub login_url: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct CreateInfraProviderResponseDto {
pub response: InfraProviderDto,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct UpdateInfraProviderRequestDto {
pub uuid: Uuid,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub favicon_link: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub login_url: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct UpdateInfraProviderResponseDto {
pub response: InfraProviderDto,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct InfraProviderDto {
pub uuid: Uuid,
pub name: String,
pub favicon_link: Option<String>,
pub login_url: Option<String>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub billing_history: BillingHistorySummary,
pub billing_nodes: Vec<BillingNodeSummary>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct BillingHistorySummary {
pub total_amount: usize,
pub total_bills: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct BillingNodeSummary {
pub node_uuid: String,
pub name: String,
pub country_code: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct CreateInfraBillingHistoryRecordRequestDto {
pub provider_uuid: String,
pub amount: usize,
pub billed_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct CreateInfraBillingHistoryRecordResponseDto {
pub response: BillingHistoryResponseData,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct GetInfraBillingHistoryRecordsResponseDto {
pub response: BillingHistoryResponseData,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct DeleteInfraBillingHistoryRecordByUuidResponseDto {
pub response: BillingHistoryResponseData,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct BillingHistoryResponseData {
pub records: Vec<BillingHistoryRecordDto>,
pub total: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct BillingHistoryRecordDto {
pub uuid: Uuid,
pub provider_uuid: String,
pub amount: usize,
pub billed_at: DateTime<Utc>,
pub provider: BillingProviderInfo,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct BillingProviderInfo {
pub uuid: Uuid,
pub name: String,
pub favicon_link: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct GetInfraBillingNodesResponseDto {
pub response: BillingNodesResponseData,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct CreateInfraBillingNodeRequestDto {
pub provider_uuid: String,
pub node_uuid: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub next_billing_at: Option<DateTime<Utc>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct CreateInfraBillingNodeResponseDto {
pub response: BillingNodesResponseData,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct UpdateInfraBillingNodeRequestDto {
pub uuid: Uuid,
pub next_billing_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct UpdateInfraBillingNodeResponseDto {
pub response: BillingNodesResponseData,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct DeleteInfraBillingNodeByUuidResponseDto {
pub response: BillingNodesResponseData,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct BillingNodesResponseData {
pub total_billing_nodes: usize,
pub billing_nodes: Vec<BillingNodeDto>,
pub available_billing_nodes: Vec<AvailableBillingNodeDto>,
pub total_available_billing_nodes: usize,
pub stats: BillingNodesStats,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct BillingNodeDto {
pub uuid: Uuid,
pub node_uuid: String,
pub provider_uuid: String,
pub provider: BillingNodeProviderInfo,
pub node: BillingNodeInfo,
pub next_billing_at: DateTime<Utc>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct BillingNodeProviderInfo {
pub uuid: Uuid,
pub name: String,
pub login_url: Option<String>,
pub favicon_link: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct BillingNodeInfo {
pub uuid: Uuid,
pub name: String,
pub country_code: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct AvailableBillingNodeDto {
pub uuid: Uuid,
pub name: String,
pub country_code: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct BillingNodesStats {
pub upcoming_nodes_count: usize,
pub current_month_payments: usize,
pub total_spent: usize,
}

71
src/api/types/common.rs Normal file
View file

@ -0,0 +1,71 @@
use serde::{Deserialize, Serialize};
use std::fmt;
use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct ApiError {
pub timestamp: Option<String>,
pub path: Option<String>,
pub message: String,
pub error_code: Option<String>,
pub error: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct InboundDto {
pub uuid: Uuid,
pub profile_uuid: Uuid,
pub tag: String,
#[serde(rename = "type")]
pub inbound_type: String,
pub network: Option<String>,
pub security: Option<String>,
pub port: Option<u16>,
pub raw_inbound: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum TrafficLimitStrategy {
NoReset,
Day,
Week,
Month,
}
impl Default for TrafficLimitStrategy {
fn default() -> Self {
TrafficLimitStrategy::NoReset
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "UPPERCASE")]
pub enum UserStatus {
Active,
Disabled,
Limited,
Expired,
}
impl Default for UserStatus {
fn default() -> Self {
UserStatus::Active
}
}
impl fmt::Display for TrafficLimitStrategy {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = serde_plain::to_string(self).unwrap();
write!(f, "{}", s)
}
}
impl fmt::Display for UserStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = serde_plain::to_string(self).unwrap();
write!(f, "{}", s)
}
}

View file

@ -0,0 +1,108 @@
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct GetConfigProfilesResponseDto {
pub response: GetConfigProfilesResponseData,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct GetConfigProfilesResponseData {
pub total: usize,
pub config_profiles: Vec<ConfigProfile>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct GetAllInboundsResponseDto {
pub response: GetAllInboundsResponseData,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct GetAllInboundsResponseData {
pub total: usize,
pub inbounds: Vec<Inbound>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct GetInboundsByProfileUuidResponseDto {
pub response: GetInboundsByProfileUuidResponseData,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct GetInboundsByProfileUuidResponseData {
pub total: usize,
pub inbounds: Vec<Inbound>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct GetConfigProfileByUuidResponseDto {
pub response: ConfigProfile,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct DeleteConfigProfileResponseDto {
pub response: DeleteConfigProfileResponseData,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct DeleteConfigProfileResponseData {
pub is_deleted: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct CreateConfigProfileRequestDto {
pub name: String,
pub config: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct CreateConfigProfileResponseDto {
pub response: ConfigProfile,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct UpdateConfigProfileRequestDto {
pub uuid: Uuid,
pub config: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct UpdateConfigProfileResponseDto {
pub response: ConfigProfile,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct ConfigProfile {
pub uuid: Uuid,
pub name: String,
pub config: serde_json::Value,
pub inbounds: Vec<Inbound>,
pub nodes: Vec<Node>,
pub created_at: String,
pub updated_at: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct Inbound {
pub uuid: Uuid,
pub profile_uuid: Uuid,
pub tag: String,
#[serde(rename = "type")]
pub inbound_type: String,
pub network: Option<String>,
pub security: Option<String>,
pub port: Option<u16>,
pub raw_inbound: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct Node {
pub uuid: Uuid,
pub name: String,
pub country_code: String,
}

247
src/api/types/hosts.rs Normal file
View file

@ -0,0 +1,247 @@
use serde::{Deserialize, Serialize};
use std::fmt;
use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
#[allow(non_camel_case_types)]
pub enum AlpnType {
H3,
H2,
#[serde(rename = "http/1.1")]
HTTP_1_1,
#[serde(rename = "h2,http/1.1")]
H_COMBINED,
#[serde(rename = "h3,h2,http/1.1")]
H3_H2_H1_COMBINED,
#[serde(rename = "h3,h2")]
H3_H2_COMBINED,
}
impl fmt::Display for AlpnType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = serde_plain::to_string(self).unwrap();
write!(f, "{}", s)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum FingerprintType {
CHROME,
FIREFOX,
SAFARI,
IOS,
ANDROID,
EDGE,
QQ,
RANDOM,
RANDOMIZED,
}
impl fmt::Display for FingerprintType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = serde_plain::to_string(self).unwrap();
write!(f, "{}", s)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "UPPERCASE")]
pub enum SecurityLayerType {
DEFAULT,
TLS,
NONE,
}
impl Default for SecurityLayerType {
fn default() -> Self {
SecurityLayerType::DEFAULT
}
}
impl fmt::Display for SecurityLayerType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = serde_plain::to_string(self).unwrap();
write!(f, "{}", s)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct HostInboundRequest {
pub config_profile_uuid: Uuid,
pub config_profile_inbound_uuid: Uuid,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct HostInboundDto {
pub config_profile_uuid: Option<Uuid>,
pub config_profile_inbound_uuid: Option<Uuid>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct HostOrderItem {
pub view_position: i32,
pub uuid: Uuid,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct DeleteHostData {
pub is_deleted: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct ReorderHostData {
pub is_updated: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct HostDto {
pub uuid: Uuid,
pub view_position: i32,
pub remark: String,
pub address: String,
pub port: u16,
pub path: Option<String>,
pub sni: Option<String>,
pub host: Option<String>,
pub alpn: Option<String>,
pub fingerprint: Option<String>,
pub is_disabled: bool,
pub security_layer: SecurityLayerType,
pub x_http_extra_params: Option<serde_json::Value>,
pub inbound: HostInboundDto,
pub server_description: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct CreateHostRequestDto {
pub inbound: HostInboundRequest,
pub remark: String,
pub address: String,
pub port: u16,
pub path: Option<String>,
pub sni: Option<String>,
pub host: Option<String>,
pub alpn: Option<AlpnType>,
pub fingerprint: Option<FingerprintType>,
pub is_disabled: bool,
pub security_layer: SecurityLayerType,
pub x_http_extra_params: Option<serde_json::Value>,
pub server_description: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct UpdateHostRequestDto {
pub uuid: Uuid,
pub inbound: Option<HostInboundRequest>,
pub remark: Option<String>,
pub address: Option<String>,
pub port: Option<u16>,
pub path: Option<String>,
pub sni: Option<String>,
pub host: Option<String>,
pub alpn: Option<AlpnType>,
pub fingerprint: Option<FingerprintType>,
pub is_disabled: Option<bool>,
pub security_layer: Option<SecurityLayerType>,
pub x_http_extra_params: Option<serde_json::Value>,
pub server_description: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ReorderHostRequestDto {
pub hosts: Vec<HostOrderItem>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct BulkDeleteHostsRequestDto {
pub uuids: Vec<Uuid>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct BulkDisableHostsRequestDto {
pub uuids: Vec<Uuid>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct BulkEnableHostsRequestDto {
pub uuids: Vec<Uuid>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct SetInboundToManyHostsRequestDto {
pub uuids: Vec<Uuid>,
pub config_profile_uuid: Uuid,
pub config_profile_inbound_uuid: Uuid,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct SetPortToManyHostsRequestDto {
pub uuids: Vec<Uuid>,
pub port: u16,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct CreateHostResponseDto {
pub response: HostDto,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct UpdateHostResponseDto {
pub response: HostDto,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct GetAllHostsResponseDto {
pub response: Vec<HostDto>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct GetOneHostResponseDto {
pub response: HostDto,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct DeleteHostResponseDto {
pub response: DeleteHostData,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ReorderHostResponseDto {
pub response: ReorderHostData,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct BulkDeleteHostsResponseDto {
pub response: Vec<HostDto>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct BulkDisableHostsResponseDto {
pub response: Vec<HostDto>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct BulkEnableHostsResponseDto {
pub response: Vec<HostDto>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct SetInboundToManyHostsResponseDto {
pub response: Vec<HostDto>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct SetPortToManyHostsResponseDto {
pub response: Vec<HostDto>,
}

72
src/api/types/hwid.rs Normal file
View file

@ -0,0 +1,72 @@
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct HwidDeviceDto {
pub hwid: String,
pub user_uuid: Uuid,
pub platform: Option<String>,
pub os_version: Option<String>,
pub device_model: Option<String>,
pub user_agent: Option<String>,
pub created_at: String,
pub updated_at: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct CreateUserHwidDeviceRequestDto {
pub hwid: String,
pub user_uuid: Uuid,
pub platform: Option<String>,
pub os_version: Option<String>,
pub device_model: Option<String>,
pub user_agent: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct CreateUserHwidDeviceData {
pub total: usize,
pub devices: Vec<HwidDeviceDto>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct CreateUserHwidDeviceResponseDto {
pub response: CreateUserHwidDeviceData,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct DeleteUserHwidDeviceRequestDto {
pub user_uuid: Uuid,
pub hwid: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct DeleteUserHwidDeviceData {
pub total: usize,
pub devices: Vec<HwidDeviceDto>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct DeleteUserHwidDeviceResponseDto {
pub response: DeleteUserHwidDeviceData,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct GetUserHwidDevicesData {
pub total: usize,
pub devices: Vec<HwidDeviceDto>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct GetUserHwidDevicesResponseDto {
pub response: GetUserHwidDevicesData,
}

View file

@ -0,0 +1,88 @@
use crate::types::InboundDto;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct GetInternalSquadsResponseDto {
pub response: GetInternalSquadsData,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct GetInternalSquadsData {
pub total: usize,
pub internal_squads: Vec<InternalSquadDto>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct GetInternalSquadByUuidResponseDto {
pub response: InternalSquadDto,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct CreateInternalSquadRequestDto {
pub name: String,
pub inbounds: Vec<Uuid>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct CreateInternalSquadResponseDto {
pub response: InternalSquadDto,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct UpdateInternalSquadRequestDto {
pub uuid: Uuid,
pub inbounds: Vec<Uuid>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct UpdateInternalSquadResponseDto {
pub response: InternalSquadDto,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct DeleteInternalSquadResponseDto {
pub response: DeleteInternalSquadData,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct DeleteInternalSquadData {
pub is_deleted: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct AddUsersToInternalSquadResponseDto {
pub response: BulkActionResponseData,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct RemoveUsersFromInternalSquadResponseDto {
pub response: BulkActionResponseData,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct BulkActionResponseData {
pub event_sent: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct InternalSquadDto {
pub uuid: Uuid,
pub name: String,
pub info: InternalSquadInfo,
pub inbounds: Vec<InboundDto>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct InternalSquadInfo {
pub members_count: usize,
pub inbounds_count: usize,
}

12
src/api/types/keygen.rs Normal file
View file

@ -0,0 +1,12 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct GetPubKeyResponseDto {
pub response: GetPubKeyData,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct GetPubKeyData {
pub pub_key: String,
}

27
src/api/types/mod.rs Normal file
View file

@ -0,0 +1,27 @@
pub mod auth;
pub mod billing;
pub mod common;
pub mod config_profiles;
pub mod hosts;
pub mod hwid;
pub mod internal_squads;
pub mod keygen;
pub mod nodes;
pub mod subscriptions;
pub mod system;
pub mod tokens;
pub mod users;
pub use auth::*;
pub use billing::*;
pub use common::*;
pub use config_profiles::*;
pub use hosts::*;
pub use hwid::*;
pub use internal_squads::*;
pub use keygen::*;
pub use nodes::*;
pub use subscriptions::*;
pub use system::*;
pub use tokens::*;
pub use users::*;

235
src/api/types/nodes.rs Normal file
View file

@ -0,0 +1,235 @@
use crate::types::InboundDto;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct CreateNodeRequestDto {
pub name: String,
pub address: String,
pub port: u16,
pub is_traffic_tracking_active: bool,
pub traffic_limit_bytes: usize,
pub notify_percent: u8,
pub traffic_reset_day: u8,
#[serde(default = "default_country_code")]
pub country_code: String,
pub consumption_multiplier: f32,
pub config_profile: ConfigProfileRequest,
pub provider_uuid: Option<Uuid>,
}
fn default_country_code() -> String {
"XX".to_string()
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct UpdateNodeRequestDto {
pub uuid: Uuid,
pub name: Option<String>,
pub address: Option<String>,
pub port: Option<u16>,
pub is_traffic_tracking_active: Option<bool>,
pub traffic_limit_bytes: Option<usize>,
pub notify_percent: Option<u8>,
pub traffic_reset_day: Option<u8>,
pub country_code: Option<String>,
pub consumption_multiplier: Option<f32>,
pub config_profile: Option<ConfigProfileRequest>,
pub provider_uuid: Option<Uuid>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ReorderNodeRequestDto {
pub nodes: Vec<NodeOrderItem>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct CreateNodeResponseDto {
pub response: NodeDto,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct GetAllNodesResponseDto {
pub response: Vec<NodeDto>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct GetOneNodeResponseDto {
pub response: NodeDto,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct UpdateNodeResponseDto {
pub response: NodeDto,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct DeleteNodeResponseDto {
pub response: DeleteNodeData,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct EnableNodeResponseDto {
pub response: NodeDto,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct DisableNodeResponseDto {
pub response: NodeDto,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct RestartNodeResponseDto {
pub response: RestartNodeData,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct RestartAllNodesResponseDto {
pub response: RestartAllNodesData,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ReorderNodeResponseDto {
pub response: Vec<NodeDto>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct GetNodesUsageByRangeResponseDto {
pub response: Vec<NodesUsageData>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct GetNodeUserUsageByRangeResponseDto {
pub response: Vec<NodeUserUsageData>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct GetNodesRealtimeUsageResponseDto {
pub response: Vec<NodeRealtimeUsage>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct NodeDto {
pub uuid: Uuid,
pub name: String,
pub address: String,
pub port: u16,
pub is_connected: bool,
pub is_disabled: bool,
pub is_connecting: bool,
pub is_node_online: bool,
pub is_xray_running: bool,
pub last_status_change: Option<String>,
pub last_status_message: Option<String>,
pub xray_version: Option<String>,
pub node_version: Option<String>,
pub xray_uptime: String,
pub is_traffic_tracking_active: bool,
pub traffic_reset_day: Option<i32>,
pub traffic_limit_bytes: Option<usize>,
pub traffic_used_bytes: Option<usize>,
pub notify_percent: Option<i32>,
pub users_online: Option<i32>,
pub view_position: u8,
pub country_code: String,
pub consumption_multiplier: f32,
pub cpu_count: Option<i32>,
pub cpu_model: Option<String>,
pub total_ram: Option<String>,
pub created_at: String,
pub updated_at: String,
pub config_profile: NodesConfigProfile,
pub provider_uuid: Option<Uuid>,
pub provider: Option<Provider>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct ConfigProfileRequest {
pub active_config_profile_uuid: Uuid,
pub active_inbounds: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct NodesConfigProfile {
pub active_config_profile_uuid: Option<Uuid>,
pub active_inbounds: Vec<InboundDto>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct Provider {
pub uuid: Uuid,
pub name: String,
pub favicon_link: Option<String>,
pub login_url: Option<String>,
pub created_at: String,
pub updated_at: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct NodeOrderItem {
pub view_position: u8,
pub uuid: Uuid,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct DeleteNodeData {
pub is_deleted: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct RestartNodeData {
pub event_sent: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct RestartAllNodesData {
pub event_sent: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct NodesUsageData {
pub node_uuid: Uuid,
pub node_name: String,
pub total: usize,
pub total_download: usize,
pub total_upload: usize,
pub human_readable_total: String,
pub human_readable_total_download: String,
pub human_readable_total_upload: String,
pub date: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct NodeUserUsageData {
pub user_uuid: Uuid,
pub username: String,
pub node_uuid: Uuid,
pub total: usize,
pub date: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct NodeRealtimeUsage {
pub node_uuid: Uuid,
pub node_name: String,
pub country_code: String,
pub download_bytes: usize,
pub upload_bytes: usize,
pub total_bytes: usize,
pub download_speed_bps: usize,
pub upload_speed_bps: usize,
pub total_speed_bps: usize,
}

View file

@ -0,0 +1,254 @@
use crate::types::{TrafficLimitStrategy, UserStatus};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, fmt};
use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum SubscriptionClientType {
Stash,
SingBox,
#[serde(rename = "singbox-legacy")]
SingBoxLegacy,
Mihomo,
Json,
#[serde(rename = "v2ray-Json")]
V2RayJson,
Clash,
}
impl fmt::Display for SubscriptionClientType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = serde_plain::to_string(self).unwrap();
write!(f, "{}", s)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "UPPERCASE")]
pub enum SubscriptionTemplateType {
Stash,
SingBox,
#[serde(rename = "SINGBOX_LEGACY")]
SingBoxLegacy,
Mihomo,
#[serde(rename = "XRAY_JSON")]
XrayJson,
Clash,
}
impl fmt::Display for SubscriptionTemplateType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = serde_plain::to_string(self).unwrap();
write!(f, "{}", s)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct SubscriptionUser {
pub short_uuid: String,
pub days_left: usize,
pub traffic_used: String,
pub traffic_limit: String,
pub username: String,
pub expires_at: DateTime<Utc>,
pub is_active: bool,
pub user_status: UserStatus,
pub traffic_limit_strategy: TrafficLimitStrategy,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct HappConfig {
pub crypto_link: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct RawHost {
pub address: Option<String>,
pub alpn: Option<String>,
pub fingerprint: Option<String>,
pub host: Option<String>,
pub network: Option<String>,
pub password: Option<String>,
pub path: Option<String>,
pub public_key: Option<String>,
pub port: Option<u16>,
pub protocol: Option<String>,
pub remark: Option<String>,
pub short_id: Option<String>,
pub sni: Option<String>,
pub spider_x: Option<String>,
pub tls: Option<String>,
pub header_type: Option<String>,
pub additional_params: Option<AdditionalParams>,
pub x_http_extra_params: Option<HashMap<String, serde_json::Value>>,
pub server_description: Option<String>,
pub flow: Option<String>,
pub protocol_options: Option<ProtocolOptions>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct AdditionalParams {
pub mode: Option<String>,
pub heartbeat_period: Option<usize>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ProtocolOptions {
pub ss: Option<SsOptions>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct SsOptions {
pub method: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct Subscription {
pub is_found: bool,
pub user: SubscriptionUser,
pub links: Vec<String>,
pub ss_conf_links: HashMap<String, String>,
pub subscription_url: String,
pub happ: Option<HappConfig>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct GetSubscriptionInfoResponseDto {
pub response: Subscription,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct GetRawSubscriptionByShortUuidResponseDto {
pub response: RawSubscriptionResponse,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct RawSubscriptionResponse {
pub user: SubscriptionUser,
pub subscription_url: String,
pub raw_hosts: Vec<RawHost>,
pub headers: HashMap<String, String>,
pub is_hwid_limited: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct GetAllSubscriptionsResponseDto {
pub response: AllSubscriptionsResponse,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct AllSubscriptionsResponse {
pub subscriptions: Vec<BasicSubscription>,
pub total: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct BasicSubscription {
pub is_found: bool,
pub user: SubscriptionUser,
pub links: Vec<String>,
pub ss_conf_links: HashMap<String, String>,
pub subscription_url: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct GetSubscriptionByUsernameResponseDto {
pub response: UsernameSubscriptionResponse,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct UsernameSubscriptionResponse {
pub is_found: bool,
pub user: SubscriptionUser,
pub links: Vec<String>,
pub ss_conf_links: HashMap<String, String>,
pub subscription_url: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct GetTemplateResponseDto {
pub response: TemplateResponse,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct UpdateTemplateRequestDto {
pub template_type: SubscriptionTemplateType,
pub template: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct TemplateResponse {
pub uuid: Uuid,
pub template_type: SubscriptionTemplateType,
pub template_json: Option<serde_json::Value>,
pub encoded_template_yaml: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct UpdateTemplateResponseDto {
pub response: TemplateResponse,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct GetSubscriptionSettingsResponseDto {
pub response: SubscriptionSettings,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct UpdateSubscriptionSettingsRequestDto {
pub uuid: Uuid,
pub profile_title: String,
pub support_link: String,
pub profile_update_interval: usize,
pub is_profile_webpage_url_enabled: bool,
pub serve_json_at_base_subscription: bool,
pub add_username_to_base_subscription: bool,
pub is_show_custom_remarks: bool,
pub happ_announce: Option<String>,
pub happ_routing: Option<String>,
pub expired_users_remarks: Vec<String>,
pub limited_users_remarks: Vec<String>,
pub disabled_users_remarks: Vec<String>,
pub custom_response_headers: Option<std::collections::HashMap<String, String>>,
pub randomize_hosts: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct UpdateSubscriptionSettingsResponseDto {
pub response: SubscriptionSettings,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct SubscriptionSettings {
pub uuid: Uuid,
pub profile_title: String,
pub support_link: String,
pub profile_update_interval: usize,
pub is_profile_webpage_url_enabled: bool,
pub serve_json_at_base_subscription: bool,
pub add_username_to_base_subscription: bool,
pub is_show_custom_remarks: bool,
pub happ_announce: Option<String>,
pub happ_routing: Option<String>,
pub expired_users_remarks: Vec<String>,
pub limited_users_remarks: Vec<String>,
pub disabled_users_remarks: Vec<String>,
pub custom_response_headers: Option<std::collections::HashMap<String, String>>,
pub randomize_hosts: bool,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}

157
src/api/types/system.rs Normal file
View file

@ -0,0 +1,157 @@
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct GetStatsResponseDto {
pub response: SystemStatsData,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct GetBandwidthStatsResponseDto {
pub response: BandwidthStatsData,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct GetNodesStatisticsResponseDto {
pub response: NodesStatisticsData,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct GetRemnawaveHealthResponseDto {
pub response: RemnawaveHealthData,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct GetNodesMetricsResponseDto {
pub response: NodesMetricsData,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct SystemStatsData {
pub cpu: CpuStats,
pub memory: MemoryStats,
pub uptime: usize,
pub timestamp: usize,
pub users: UsersStats,
pub online_stats: OnlineStats,
pub nodes: NodesStats,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct CpuStats {
pub cores: usize,
pub physical_cores: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct MemoryStats {
pub total: usize,
pub free: usize,
pub used: usize,
pub active: usize,
pub available: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct UsersStats {
pub status_counts: std::collections::HashMap<String, usize>,
pub total_users: usize,
pub total_traffic_bytes: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct OnlineStats {
pub last_day: usize,
pub last_week: usize,
pub never_online: usize,
pub online_now: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct NodesStats {
pub total_online: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct BandwidthStatsData {
#[serde(rename = "bandwidthLastTwoDays")]
pub last_two_days: BandwidthPeriod,
#[serde(rename = "bandwidthLastSevenDays")]
pub last_seven_days: BandwidthPeriod,
#[serde(rename = "bandwidthLast30Days")]
pub last_30_days: BandwidthPeriod,
#[serde(rename = "bandwidthCalendarMonth")]
pub calendar_month: BandwidthPeriod,
#[serde(rename = "bandwidthCurrentYear")]
pub current_year: BandwidthPeriod,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct BandwidthPeriod {
pub current: String,
pub previous: String,
pub difference: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct NodesStatisticsData {
pub last_seven_days: Vec<NodeStatisticItem>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct NodeStatisticItem {
pub node_name: String,
pub date: String,
pub total_bytes: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct RemnawaveHealthData {
pub pm2_stats: Vec<Pm2Stat>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Pm2Stat {
pub name: String,
pub memory: String,
pub cpu: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct NodesMetricsData {
pub nodes: Vec<NodeMetricItem>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct NodeMetricItem {
pub node_uuid: Uuid,
pub node_name: String,
pub country_emoji: String,
pub provider_name: String,
pub users_online: usize,
pub inbounds_stats: Vec<InboundStat>,
pub outbounds_stats: Vec<OutboundStat>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct InboundStat {
pub tag: String,
pub upload: String,
pub download: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct OutboundStat {
pub tag: String,
pub upload: String,
pub download: String,
}

55
src/api/types/tokens.rs Normal file
View file

@ -0,0 +1,55 @@
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CreateApiTokenRequestDto {
pub token_name: String,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct CreateApiTokenResponseDto {
pub response: CreateApiTokenResponse,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct CreateApiTokenResponse {
pub token: String,
pub uuid: Uuid,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct DeleteApiTokenResponseDto {
pub response: bool,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct FindAllApiTokensResponseDto {
pub response: FindAllApiTokensResponse,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FindAllApiTokensResponse {
pub api_keys: Vec<ApiTokenInfo>,
pub docs: DocsInfo,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ApiTokenInfo {
pub uuid: Uuid,
pub token: String,
pub token_name: String,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DocsInfo {
pub is_docs_enabled: bool,
pub scalar_path: Option<String>,
pub swagger_path: Option<String>,
}

393
src/api/types/users.rs Normal file
View file

@ -0,0 +1,393 @@
use crate::types::{TrafficLimitStrategy, UserStatus};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct InternalSquad {
pub uuid: Uuid,
pub name: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct LastConnectedNode {
pub connected_at: DateTime<Utc>,
pub node_name: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct Happ {
pub crypto_link: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct ActiveNode {
pub uuid: Uuid,
pub node_name: String,
pub country_code: String,
pub config_profile_uuid: Uuid,
pub config_profile_name: String,
pub active_squads: Vec<ActiveSquad>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct ActiveSquad {
pub squad_name: String,
pub active_inbounds: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct CreateUserRequestDto {
pub username: String,
#[serde(default)]
pub status: UserStatus,
#[serde(skip_serializing_if = "Option::is_none")]
pub short_uuid: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub trojan_password: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub vless_uuid: Option<Uuid>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ss_password: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub traffic_limit_bytes: Option<usize>,
#[serde(default)]
pub traffic_limit_strategy: TrafficLimitStrategy,
pub expire_at: DateTime<Utc>,
#[serde(skip_serializing_if = "Option::is_none")]
pub created_at: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub last_traffic_reset_at: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tag: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub telegram_id: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub email: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub hwid_device_limit: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub active_internal_squads: Option<Vec<String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct CreateUserResponseDto {
pub response: UserData,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct UpdateUserRequestDto {
pub uuid: Uuid,
#[serde(default)]
pub status: UserStatus,
#[serde(skip_serializing_if = "Option::is_none")]
pub traffic_limit_bytes: Option<usize>,
#[serde(default)]
pub traffic_limit_strategy: TrafficLimitStrategy,
pub expire_at: DateTime<Utc>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tag: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub telegram_id: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub email: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub hwid_device_limit: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub active_internal_squads: Option<Vec<String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct UpdateUserResponseDto {
pub response: UserData,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct UserData {
pub uuid: Uuid,
pub short_uuid: String,
pub username: String,
pub status: UserStatus,
pub used_traffic_bytes: i64,
pub lifetime_used_traffic_bytes: i64,
pub traffic_limit_bytes: i64,
#[serde(default)]
pub traffic_limit_strategy: TrafficLimitStrategy,
pub sub_last_user_agent: Option<String>,
pub sub_last_opened_at: Option<DateTime<Utc>>,
pub expire_at: DateTime<Utc>,
pub online_at: Option<DateTime<Utc>>,
pub sub_revoked_at: Option<DateTime<Utc>>,
pub last_traffic_reset_at: Option<DateTime<Utc>>,
pub trojan_password: String,
pub vless_uuid: Uuid,
pub ss_password: String,
pub description: Option<String>,
pub tag: Option<String>,
pub telegram_id: Option<i64>,
pub email: Option<String>,
pub hwid_device_limit: Option<usize>,
pub first_connected_at: Option<DateTime<Utc>>,
#[serde(default)]
pub last_triggered_threshold: usize,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub active_internal_squads: Vec<InternalSquad>,
pub subscription_url: String,
pub last_connected_node: Option<LastConnectedNode>,
pub happ: Happ,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct DeleteUserResponseDto {
pub response: DeleteUserResponse,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct DeleteUserResponse {
pub is_deleted: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct GetAllUsersResponseDto {
pub response: GetAllUsersResponse,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct GetAllUsersResponse {
pub users: Vec<UserData>,
pub total: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct GetAllTagsResponseDto {
pub response: GetAllTagsResponse,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct GetAllTagsResponse {
pub tags: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct GetUserAccessibleNodesResponseDto {
pub response: GetUserAccessibleNodesResponse,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct GetUserAccessibleNodesResponse {
pub user_uuid: Uuid,
pub active_nodes: Vec<ActiveNode>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct GetUserByShortUuidResponseDto {
pub response: UserData,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct GetUserByUuidResponseDto {
pub response: UserData,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct GetUserByUsernameResponseDto {
pub response: UserData,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct GetUserByTelegramIdResponseDto {
pub response: Vec<UserData>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct GetUserByEmailResponseDto {
pub response: Vec<UserData>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct GetUserByTagResponseDto {
pub response: Vec<UserData>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct RevokeUserSubscriptionBodyDto {
#[serde(skip_serializing_if = "Option::is_none")]
pub short_uuid: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct RevokeUserSubscriptionResponseDto {
pub response: UserData,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct DisableUserResponseDto {
pub response: UserData,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct EnableUserResponseDto {
pub response: UserData,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ResetUserTrafficResponseDto {
pub response: UserData,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct BulkDeleteUsersByStatusRequestDto {
pub status: UserStatus,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct BulkDeleteUsersByStatusResponseDto {
pub response: BulkOperationResponse,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct BulkDeleteUsersRequestDto {
pub uuids: Vec<Uuid>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct BulkDeleteUsersResponseDto {
pub response: BulkOperationResponse,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct BulkRevokeUsersSubscriptionRequestDto {
pub uuids: Vec<Uuid>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct BulkRevokeUsersSubscriptionResponseDto {
pub response: BulkOperationResponse,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct BulkResetTrafficUsersRequestDto {
pub uuids: Vec<Uuid>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct BulkResetTrafficUsersResponseDto {
pub response: BulkOperationResponse,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct BulkUpdateUsersRequestDto {
pub uuids: Vec<Uuid>,
pub fields: BulkUpdateFields,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct BulkUpdateUsersResponseDto {
pub response: BulkOperationResponse,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct BulkUpdateUsersSquadsRequestDto {
pub uuids: Vec<Uuid>,
pub active_internal_squads: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct BulkUpdateUsersSquadsResponseDto {
pub response: BulkOperationResponse,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct BulkAllUpdateUsersRequestDto {
#[serde(skip_serializing_if = "Option::is_none")]
pub status: Option<UserStatus>,
#[serde(skip_serializing_if = "Option::is_none")]
pub traffic_limit_bytes: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub traffic_limit_strategy: Option<TrafficLimitStrategy>,
#[serde(skip_serializing_if = "Option::is_none")]
pub expire_at: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub telegram_id: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub email: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tag: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct BulkAllUpdateUsersResponseDto {
pub response: BulkEventResponse,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct BulkAllResetTrafficUsersResponseDto {
pub response: BulkEventResponse,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct BulkOperationResponse {
pub affected_rows: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct BulkEventResponse {
pub event_sent: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct BulkUpdateFields {
#[serde(skip_serializing_if = "Option::is_none")]
pub status: Option<UserStatus>,
#[serde(skip_serializing_if = "Option::is_none")]
pub traffic_limit_bytes: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub traffic_limit_strategy: Option<TrafficLimitStrategy>,
#[serde(skip_serializing_if = "Option::is_none")]
pub expire_at: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub telegram_id: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub email: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tag: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct GetUserUsageByRangeResponseDto {
pub response: Vec<UsageData>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct UsageData {
pub user_uuid: Uuid,
pub node_uuid: Uuid,
pub node_name: String,
pub total: usize,
pub date: String,
}

View file

@ -1,49 +0,0 @@
use remnawave::RemnawaveClient;
use anyhow::Result;
#[tokio::main]
async fn main() -> Result<()> {
// Пример использования клиента
let base_url = std::env::var("REMNAWAVE_URL")
.unwrap_or_else(|_| "https://localhost".to_string());
let token = std::env::var("REMNAWAVE_TOKEN")
.unwrap_or_else(|_| "your-token-here".to_string());
println!("Создание клиента RemnaWave...");
println!("Base URL: {}", base_url);
let client = RemnawaveClient::new(base_url, token)?;
println!("Клиент успешно создан!");
// Демонстрируем текущий API (builder pattern)
println!("\n=== Текущий API (builder pattern) ===");
println!("client.auth().get_status() - возвращает builder");
println!("client.auth().login() - возвращает builder");
// Показываем примеры вызовов через builder
let _auth_controller = client.auth();
let _status_builder = _auth_controller.get_status();
// Демонстрируем желаемый API (простые функции)
println!("\n=== Желаемый API (простые функции) ===");
println!("// client.auth().login(\"username\", \"password\").await - прямой вызов с параметрами");
println!("// client.auth().get_status().await - простой вызов без параметров");
println!("// client.users().create(\"test\", \"test@test.com\").await - создание пользователя");
println!("// client.users().get_all().await - получение всех пользователей");
println!("\nДля полноценной реализации нужно:");
println!("1. Проанализировать каждую операцию в OpenAPI");
println!("2. Извлечь типы параметров из схем");
println!("3. Создать функции-обертки с правильными типами");
println!("4. Автоматически формировать body из параметров");
println!("\nТекущий статус:");
println!("✅ Группировка по контроллерам");
println!("✅ Автоматическая генерация из OpenAPI");
println!("⚠️ Простые параметры (требует анализа типов)");
println!("\nТест завершен успешно!");
Ok(())
}

View file

@ -1,47 +0,0 @@
use remnawave::RemnawaveClient;
use anyhow::Result;
#[tokio::main]
async fn main() -> Result<()> {
// Пример использования клиента
let base_url = std::env::var("REMNAWAVE_URL")
.unwrap_or_else(|_| "https://localhost".to_string());
let token = std::env::var("REMNAWAVE_TOKEN")
.unwrap_or_else(|_| "your-token-here".to_string());
println!("Создание клиента RemnaWave...");
println!("Base URL: {}", base_url);
let client = RemnawaveClient::new(base_url, token)?;
println!("Клиент успешно создан!");
// Проверяем, что контроллеры доступны
println!("Доступные контроллеры:");
println!("- Auth: client.auth() - содержит методы аутентификации");
println!("- Users: client.users() - содержит методы управления пользователями");
println!("- Subscriptions: client.subscriptions() - содержит методы подписок");
println!("- Nodes: client.nodes() - содержит методы управления нодами");
println!("- System: client.system() - содержит системные методы");
// Демонстрируем структуру API
println!("\nПример использования:");
println!("client.auth().get_status() - получить статус аутентификации");
println!("client.users().get_all() - получить всех пользователей");
println!("client.subscriptions().get_info() - получить информацию о подписке");
// Показываем, что контроллеры создаются
let _auth = client.auth().get_status(); // Пример вызова метода (закомментирован, так как требует настоящего сервера)
let _users = client.users();
let _subscriptions = client.subscriptions();
let _nodes = client.nodes();
let _system = client.system();
// Пример вызова метода (закомментирован, так как требует настоящего сервера)
// let status = client.auth().get_status().await?;
// println!("Auth status: {:?}", status);
println!("Тест завершен успешно!");
Ok(())
}

View file

@ -1,33 +1,44 @@
include!(concat!(env!("OUT_DIR"), "/remnawave_api.rs"));
include!(concat!(env!("OUT_DIR"), "/controllers.rs"));
pub mod api;
pub use api::*;
use reqwest::header::{HeaderMap, HeaderValue, AUTHORIZATION};
/// Thin wrapper around the generated client to inject auth & base URL.
#[derive(Clone)]
pub struct RemnawaveClient {
client: Client,
#[derive(Debug)]
pub struct ApiError {
pub status_code: u16,
pub url: String,
pub request_body: Option<String>,
pub response_body: String,
pub response_headers: std::collections::HashMap<String, String>,
pub timestamp: Option<String>,
pub path: Option<String>,
pub message: Option<String>,
pub error_code: Option<String>,
pub error: Option<String>,
}
impl RemnawaveClient {
pub fn new(base_url: impl Into<String>, token: impl Into<String>) -> anyhow::Result<Self> {
let mut headers = HeaderMap::new();
headers.insert(
AUTHORIZATION,
HeaderValue::from_str(&format!("Bearer {}", token.into()))?,
);
let http = reqwest::ClientBuilder::new()
.default_headers(headers)
.build()?;
Ok(Self {
client: Client::new_with_client(&base_url.into(), http),
})
impl std::fmt::Display for ApiError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(msg) = &self.message {
write!(
f,
"API Error [{}]: {} (code: {}) - {} | <{}>",
self.status_code,
msg,
self.error_code.as_deref().unwrap_or("unknown"),
self.error.as_deref().unwrap_or("unknown"),
self.request_body.as_deref().unwrap_or("unknown")
)
} else {
write!(
f,
"HTTP {} from {}: {} (code: {}) - {}",
self.status_code,
self.url,
self.response_body,
self.error_code.as_deref().unwrap_or("unknown"),
self.error.as_deref().unwrap_or("unknown")
)
}
}
}
/// Access the low-level, strongly typed API generated by Progenitor.
pub fn api(&self) -> &Client {
&self.client
}
}
impl std::error::Error for ApiError {}

120
tests/integration_tests.rs Normal file
View file

@ -0,0 +1,120 @@
use dotenv::dotenv;
use remnawave_api::{RemnawaveApiClient, types::SubscriptionTemplateType};
use std::env;
fn setup_client() -> RemnawaveApiClient {
dotenv().ok();
let base_url = env::var("REMNAWAVE_BASE_URL").expect("REMNAWAVE_BASE_URL must be set in .env file");
let token = env::var("REMNAWAVE_TOKEN").expect("REMNAWAVE_TOKEN must be set in .env file");
RemnawaveApiClient::new(base_url, Some(token)).expect("Failed to create API client")
}
#[tokio::test]
async fn test_client_creation() {
let client = setup_client();
assert!(!client.base_url().is_empty());
}
#[tokio::test]
async fn test_subscriptions_get_all() {
let client = setup_client();
let result = client.subscriptions.get_all_subscriptions(Some(10), Some(0)).await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_subscription_settings() {
let client = setup_client();
let result = client.subscription_settings.get_settings().await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_subscription_info() {
let client = setup_client();
let result = client.subscriptions.get_subscription("test-invalid-uuid".to_string()).await;
assert!(result.is_err(), "Expected error for invalid UUID");
if let Err(e) = result {
assert!(e.status_code == 404);
}
}
#[tokio::test]
async fn test_subscription_by_username() {
let client = setup_client();
let result = client.subscriptions.get_subscription_by_username("nonexistent_user_test_12345".to_string()).await;
assert!(result.is_err(), "Expected error for non-existent username");
if let Err(e) = result {
assert!(e.status_code == 404);
}
}
#[tokio::test]
async fn test_raw_subscription_endpoint() {
let client = setup_client();
let result = client.subscriptions.get_raw_subscription_by_short_uuid("invalid-test-uuid".to_string()).await;
assert!(result.is_err(), "Expected error for invalid UUID");
if let Err(e) = result {
assert!(e.status_code == 404);
}
}
#[tokio::test]
async fn test_multiple_template_requests() {
let client = setup_client();
let clash_result = client.subscription_templates.get_template(SubscriptionTemplateType::Clash).await;
let v2ray_result = client.subscription_templates.get_template(SubscriptionTemplateType::XrayJson).await;
let singbox_result = client.subscription_templates.get_template(SubscriptionTemplateType::SingBox).await;
assert!(clash_result.is_ok());
assert!(v2ray_result.is_ok());
assert!(singbox_result.is_ok());
}
#[tokio::test]
async fn test_concurrent_requests() {
let client = setup_client();
let (clash_result, v2ray_result, singbox_result) = tokio::join!(
client.subscription_templates.get_template(SubscriptionTemplateType::Clash),
client.subscription_templates.get_template(SubscriptionTemplateType::XrayJson),
client.subscription_templates.get_template(SubscriptionTemplateType::SingBox)
);
assert!(clash_result.is_ok(), "Expected Ok result for Clash template: {:?}", clash_result);
assert!(v2ray_result.is_ok(), "Expected Ok result for V2Ray template: {:?}", v2ray_result);
assert!(singbox_result.is_ok(), "Expected Ok result for SingBox template: {:?}", singbox_result);
}
#[tokio::test]
async fn test_client_token_update() {
let mut client = setup_client();
let original_token = env::var("REMNAWAVE_TOKEN").unwrap();
client.set_token(Some("new_test_token".to_string()));
client.set_token(Some(original_token));
println!("Token update functionality works");
}
#[tokio::test]
async fn test_client_without_token() {
dotenv().ok();
let base_url = env::var("REMNAWAVE_BASE_URL").expect("REMNAWAVE_BASE_URL must be set in .env file");
let client = RemnawaveApiClient::new(base_url, None).expect("Failed to create API client without token");
assert!(!client.base_url().is_empty());
let result = client.subscriptions.get_all_subscriptions(Some(10), Some(0)).await;
assert!(result.is_err());
}

159
tests/unit_tests.rs Normal file
View file

@ -0,0 +1,159 @@
use mockito::Server;
use remnawave_api::{ApiError, RemnawaveApiClient};
use serde_json::json;
#[tokio::test]
async fn test_api_client_creation() {
let client = RemnawaveApiClient::new("https://api.example.com".to_string(), Some("test_token".to_string())).unwrap();
assert_eq!(client.base_url(), "https://api.example.com");
}
#[tokio::test]
async fn test_api_client_creation_without_token() {
let client = RemnawaveApiClient::new("https://api.example.com".to_string(), None).unwrap();
assert_eq!(client.base_url(), "https://api.example.com");
}
#[tokio::test]
async fn test_token_update() {
let mut client = RemnawaveApiClient::new("https://api.example.com".to_string(), Some("initial_token".to_string())).unwrap();
client.set_token(Some("new_token".to_string()));
client.set_token(None);
assert_eq!(client.base_url(), "https://api.example.com");
}
#[tokio::test]
async fn test_api_error_display() {
let error = ApiError {
status_code: 404,
url: "https://api.example.com/test".to_string(),
request_body: None,
response_body: "Not Found".to_string(),
response_headers: std::collections::HashMap::new(),
timestamp: None,
path: None,
message: Some("Resource not found".to_string()),
error_code: Some("NOT_FOUND".to_string()),
error: None,
};
let error_string = format!("{}", error);
assert!(error_string.contains("404"));
assert!(error_string.contains("Resource not found"));
assert!(error_string.contains("NOT_FOUND"));
}
#[tokio::test]
async fn test_api_error_display_without_message() {
let error = ApiError {
status_code: 500,
url: "https://api.example.com/test".to_string(),
request_body: None,
response_body: "Internal Server Error".to_string(),
response_headers: std::collections::HashMap::new(),
timestamp: None,
path: None,
message: None,
error_code: None,
error: None,
};
let error_string = format!("{}", error);
assert!(error_string.contains("500"));
assert!(error_string.contains("https://api.example.com/test"));
assert!(error_string.contains("Internal Server Error"));
}
#[tokio::test]
async fn test_mock_subscription_endpoint() {
let mut server = Server::new_async().await;
let _mock = server
.mock("GET", "/api/subscriptions")
.with_status(200)
.with_header("content-type", "application/json")
.with_body(
json!({
"subscriptions": [],
"total": 0,
"page": 0,
"size": 10
})
.to_string(),
)
.create_async()
.await;
let client = RemnawaveApiClient::new(server.url(), Some("test_token".to_string())).unwrap();
assert!(client.base_url().starts_with("http://127.0.0.1"));
}
#[tokio::test]
async fn test_mock_template_endpoint() {
let mut server = Server::new_async().await;
let _mock = server
.mock("GET", "/api/subscriptions/template/clash")
.with_status(200)
.with_header("content-type", "application/json")
.with_body(
json!({
"template": "clash_template_content",
"version": "1.0"
})
.to_string(),
)
.create_async()
.await;
let client = RemnawaveApiClient::new(server.url(), Some("test_token".to_string())).unwrap();
assert!(client.base_url().starts_with("http://127.0.0.1"));
}
#[tokio::test]
async fn test_mock_error_response() {
let mut server = Server::new_async().await;
let _mock = server
.mock("GET", "/api/subscriptions/info/invalid")
.with_status(404)
.with_header("content-type", "application/json")
.with_body(
json!({
"error": "Subscription not found",
"code": "NOT_FOUND"
})
.to_string(),
)
.create_async()
.await;
let client = RemnawaveApiClient::new(server.url(), Some("test_token".to_string())).unwrap();
assert!(client.base_url().starts_with("http://127.0.0.1"));
}
#[test]
fn test_controllers_exist() {
let client = RemnawaveApiClient::new("https://api.example.com".to_string(), Some("test_token".to_string())).unwrap();
let _ = &client.auth;
let _ = &client.users;
let _ = &client.subscriptions;
let _ = &client.nodes;
let _ = &client.hosts;
let _ = &client.system;
let _ = &client.tokens;
let _ = &client.config_profiles;
let _ = &client.internal_squads;
let _ = &client.hwid;
let _ = &client.billing;
let _ = &client.keygen;
}