mirror of
https://github.com/JonasunderscoreJones/jonas_jones-api.git
synced 2025-10-23 17:19:18 +02:00
Compare commits
37 commits
Author | SHA1 | Date | |
---|---|---|---|
c25f2032d7 | |||
9ef64b5b82 | |||
c20e276de2 | |||
2c8a9db949 | |||
350c9f722a | |||
cdcf27b0fa | |||
41fa898a67 | |||
d2a89d5067 | |||
3b5378c6f2 | |||
1cdb34503d | |||
4ecbd7aa98 | |||
a63f933dd9 | |||
48da0d7f95 | |||
5e9ed3738c | |||
7bee1f5bae | |||
bdfd5a74a6 | |||
4c62ba2845 | |||
|
852e184b89 | ||
|
a851b18875 | ||
|
c18ab8a8da | ||
|
462ee68763 | ||
|
f201bc7fda | ||
5f2026af0d | |||
5c6a2ac53f | |||
e8d1b934d7 | |||
8e61e81f06 | |||
5eb6080694 | |||
83e1b85811 | |||
bc09ff5f3c | |||
2188f2acd5 | |||
169be592ab | |||
5aefb1a7c7 | |||
ce7aa20a57 | |||
5c7c572641 | |||
3c1d88bd42 | |||
|
69ea09b223 | ||
|
431c6c080a |
23 changed files with 2051 additions and 82 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -5,3 +5,4 @@
|
||||||
/.vscode
|
/.vscode
|
||||||
|
|
||||||
.env
|
.env
|
||||||
|
/resources
|
||||||
|
|
718
Cargo.lock
generated
718
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
10
Cargo.toml
10
Cargo.toml
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "jonas_jones-api"
|
name = "jonas_jones-api"
|
||||||
version = "0.1.0"
|
version = "0.4.1"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
@ -11,9 +11,13 @@ parking_lot = "0.12.1"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
tokio = { version = "1.35", features = ["macros"] }
|
tokio = { version = "1.35", features = ["macros"] }
|
||||||
dotenv = "0.15.0"
|
dotenv = "0.15.0"
|
||||||
lastfm = "0.6.1"
|
lastfm = "0.10.0"
|
||||||
log = "0.4.20"
|
log = "0.4.20"
|
||||||
chrono = "0.4.31"
|
chrono = "0.4.31"
|
||||||
toml = "0.8.8"
|
toml = "0.8.8"
|
||||||
reqwest = { version = "0.11.22", features = ["json"] }
|
reqwest = { version = "0.12.5", features = ["json", "blocking"] }
|
||||||
serde_json = "1.0.108"
|
serde_json = "1.0.108"
|
||||||
|
regex = "1"
|
||||||
|
git2 = "0.19.0"
|
||||||
|
ip2location = "0.5.0"
|
||||||
|
sha2 = "0.10.8"
|
||||||
|
|
49
README.md
49
README.md
|
@ -9,28 +9,59 @@ As of now, the project has no proper production build and unless the proper envi
|
||||||
|
|
||||||
Clone the repository and install the dependencies.
|
Clone the repository and install the dependencies.
|
||||||
```bash
|
```bash
|
||||||
$ git clone git@github.com:J-onasJones/jonas_jones-api.git
|
git clone git@github.com:J-onasJones/jonas_jones-api.git
|
||||||
$ cd jonas_jones-api
|
cd jonas_jones-api
|
||||||
$ cargo build
|
cargo build
|
||||||
```
|
```
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
To run the API, simply run the following command.
|
To run the API, simply run the following command.
|
||||||
```bash
|
```bash
|
||||||
$ cargo run
|
cargo run
|
||||||
```
|
```
|
||||||
|
|
||||||
If you want to run the API in a production environment, you will need to set the following environment variables.
|
If you want to run the API in a production environment, you will need to set the following environment variables.
|
||||||
|
|
||||||
- API_PORT
|
- API_PORT
|
||||||
- API_IP =
|
- API_IP
|
||||||
- LASTFM_API_KEY
|
- LASTFM_API_KEY
|
||||||
- LASTFM_API_SECRET
|
- LASTFM_API_SECRET
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ export API_PORT={port}
|
export API_PORT={port}
|
||||||
$ export API_IP={ip_address}
|
export API_IP={ip_address}
|
||||||
$ export LASTFM_API_KEY={lastfm_api_key}
|
export LASTFM_API_KEY={lastfm_api_key}
|
||||||
$ export LASTFM_API_SECRET={lastfm_api_secret}
|
export LASTFM_API_SECRET={lastfm_api_secret}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Docker Compose
|
||||||
|
|
||||||
|
`docker-compose.yaml` (folder paths need adjusting):
|
||||||
|
```yaml
|
||||||
|
version: '3.8'
|
||||||
|
services:
|
||||||
|
arch-linux:
|
||||||
|
image: archlinux:latest
|
||||||
|
container_name: jonas_jones-api
|
||||||
|
ports:
|
||||||
|
- "3030:3030"
|
||||||
|
volumes:
|
||||||
|
- /home/jonas_jones/jonas_jones-api:/home/jonas_jones/jonas_jones-api
|
||||||
|
- /home/jonas_jones/.config/rclone/:/root/.config/rclone/
|
||||||
|
command: ["sh", "-c", "pacman -Syu --noconfirm --needed pkg-config openssl python3 python-pip rclone cargo && python3 -m venv api-venv && source api-venv/bin/activate && cd /home/jonas_jones/jonas_jones-api && pip install -r requirements.txt && /usr/bin/cargo run"]
|
||||||
|
```
|
||||||
|
|
||||||
|
run container:
|
||||||
|
```sh
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
## Roadmap
|
||||||
|
|
||||||
|
- analytics backend. track request origin through IP from header (store IP hash, region and time)
|
||||||
|
- rewrite all scripts in rust
|
||||||
|
- DB implementation for projects, kcomebacks, minecraft mod versions
|
||||||
|
- session backend, auth token system
|
||||||
|
- implementation for dashboard front-end with analytics/config
|
||||||
|
- complete minecraft mod implementation
|
11
docker-compose.yaml
Normal file
11
docker-compose.yaml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
version: '3.8'
|
||||||
|
services:
|
||||||
|
arch-linux:
|
||||||
|
image: archlinux:latest
|
||||||
|
container_name: jonas_jones-api
|
||||||
|
ports:
|
||||||
|
- "3030:3030"
|
||||||
|
volumes:
|
||||||
|
- /home/jonas_jones/jonas_jones-api:/home/jonas_jones/jonas_jones-api
|
||||||
|
- /home/jonas_jones/.config/rclone/:/root/.config/rclone/
|
||||||
|
command: ["sh", "-c", "pacman -Syu --noconfirm --needed pkg-config openssl python3 python-pip rclone cargo && python3 -m venv api-venv && source api-venv/bin/activate && cd /home/jonas_jones/jonas_jones-api && pip install -r requirements.txt && /usr/bin/cargo run"]
|
9
requirements.txt
Normal file
9
requirements.txt
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
requests
|
||||||
|
datetime
|
||||||
|
python-dotenv
|
||||||
|
spotipy
|
||||||
|
praw
|
||||||
|
spotipy
|
||||||
|
pylast
|
||||||
|
typing
|
||||||
|
markdown
|
|
@ -26,3 +26,7 @@ impl warp::reject::Reject for UnauthorizedError {}
|
||||||
pub struct ForbiddenError;
|
pub struct ForbiddenError;
|
||||||
impl warp::reject::Reject for ForbiddenError {}
|
impl warp::reject::Reject for ForbiddenError {}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct NotImplementedError;
|
||||||
|
impl warp::reject::Reject for NotImplementedError {}
|
||||||
|
|
||||||
|
|
BIN
src/favicon.png
Normal file
BIN
src/favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 39 KiB |
14
src/iplookup.rs
Normal file
14
src/iplookup.rs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
use ip2location::{DB, Record};
|
||||||
|
|
||||||
|
pub fn ip_lookup(ip: &str) -> String {
|
||||||
|
let database_path = "resources/IP2LOCATION-LITE-DB5.IPV6.BIN/IP2LOCATION-LITE-DB5.IPV6.BIN";
|
||||||
|
|
||||||
|
let mut db = DB::from_file(database_path).unwrap();
|
||||||
|
|
||||||
|
let geo_info = db.ip_lookup(ip.parse().unwrap()).unwrap();
|
||||||
|
|
||||||
|
let record = if let Record::LocationDb(rec) = geo_info {
|
||||||
|
Some(rec)
|
||||||
|
} else { None };
|
||||||
|
return record.unwrap().country.unwrap().short_name.to_string();
|
||||||
|
}
|
33
src/main.rs
33
src/main.rs
|
@ -1,19 +1,50 @@
|
||||||
use dotenv::dotenv;
|
use dotenv::dotenv;
|
||||||
|
use tokio::time::{sleep, Duration};
|
||||||
|
use v1::{run_sync_all_command};
|
||||||
|
use std::fs;
|
||||||
|
|
||||||
pub mod v1;
|
pub mod v1;
|
||||||
pub mod logger;
|
pub mod logger;
|
||||||
pub mod tools;
|
pub mod tools;
|
||||||
pub mod server;
|
pub mod server;
|
||||||
pub mod error_responses;
|
pub mod error_responses;
|
||||||
|
pub mod iplookup;
|
||||||
|
pub mod request_logger;
|
||||||
|
|
||||||
pub use logger::Logger;
|
pub use logger::Logger;
|
||||||
pub use tools::parse_ip;
|
pub use tools::parse_ip;
|
||||||
|
|
||||||
|
|
||||||
|
async fn periodic_script_runner() {
|
||||||
|
loop {
|
||||||
|
Logger::info("Running periodic scripts...");
|
||||||
|
// Run all Functions
|
||||||
|
let _ = run_sync_all_command();
|
||||||
|
|
||||||
|
// Sleep for 6 hours
|
||||||
|
sleep(Duration::from_secs(6 * 60 * 60)).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::main(flavor = "current_thread")]
|
#[tokio::main(flavor = "current_thread")]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
// load .env file
|
// Load .env file
|
||||||
dotenv().ok();
|
dotenv().ok();
|
||||||
|
|
||||||
|
// Start the api
|
||||||
|
let server_task = tokio::spawn(async {
|
||||||
server::serve().await;
|
server::serve().await;
|
||||||
|
});
|
||||||
|
|
||||||
|
// periodic script runner
|
||||||
|
let second_task = tokio::spawn(async {
|
||||||
|
// check if the local repository exists, if not, clone it
|
||||||
|
if !fs::metadata("./resources/turbo_octo_potato").is_ok() {
|
||||||
|
v1::run_setup().unwrap();
|
||||||
|
};
|
||||||
|
periodic_script_runner().await;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Wait for both tasks to complete
|
||||||
|
let _ = tokio::try_join!(server_task, second_task);
|
||||||
}
|
}
|
||||||
|
|
71
src/request_logger.rs
Normal file
71
src/request_logger.rs
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
use chrono::Utc;
|
||||||
|
use reqwest::Client;
|
||||||
|
use reqwest::blocking::get;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_json::Value;
|
||||||
|
use sha2::{Digest, Sha256};
|
||||||
|
use std::fs::OpenOptions;
|
||||||
|
use std::io::{BufReader, BufWriter};
|
||||||
|
use warp::http::Method;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
struct RequestLog {
|
||||||
|
timestamp: String,
|
||||||
|
method: String,
|
||||||
|
pathname: String,
|
||||||
|
ip_country_code: String,
|
||||||
|
ip_hash: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_ip_country_code(ip: &str) -> Result<String, Box<dyn std::error::Error>> {
|
||||||
|
let url = format!("http://ip-api.com/json/{}", ip);
|
||||||
|
let response: Value = get(&url)?.json()?;
|
||||||
|
if let Some(country_code) = response["countryCode"].as_str() {
|
||||||
|
Ok(country_code.to_string())
|
||||||
|
} else {
|
||||||
|
Err("Could not fetch country code".into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn hash_ip(ip: &str) -> String {
|
||||||
|
let mut hasher = Sha256::new();
|
||||||
|
hasher.update(ip);
|
||||||
|
format!("{:x}", hasher.finalize())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn log_request(client: &Client, ip: &str, pathname: &str, method: &Method, file_path: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let method_str = method.as_str();
|
||||||
|
let timestamp = Utc::now().to_rfc3339();
|
||||||
|
let ip_country_code = get_ip_country_code(ip).await?;
|
||||||
|
let ip_hash = hash_ip(ip).await;
|
||||||
|
|
||||||
|
let log_entry = RequestLog {
|
||||||
|
timestamp,
|
||||||
|
method: method_str.to_string(),
|
||||||
|
pathname: pathname.to_string(),
|
||||||
|
ip_country_code,
|
||||||
|
ip_hash,
|
||||||
|
};
|
||||||
|
|
||||||
|
let path = Path::new(file_path);
|
||||||
|
let file = OpenOptions::new()
|
||||||
|
.read(true)
|
||||||
|
.write(true)
|
||||||
|
.create(true)
|
||||||
|
.open(path)?;
|
||||||
|
|
||||||
|
let mut logs: Vec<RequestLog> = if path.exists() {
|
||||||
|
let reader = BufReader::new(&file);
|
||||||
|
serde_json::from_reader(reader)?
|
||||||
|
} else {
|
||||||
|
Vec::new()
|
||||||
|
};
|
||||||
|
|
||||||
|
logs.push(log_entry);
|
||||||
|
|
||||||
|
let writer = BufWriter::new(&file);
|
||||||
|
serde_json::to_writer_pretty(writer, &logs)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -1,13 +1,19 @@
|
||||||
use std::convert::Infallible;
|
use std::convert::Infallible;
|
||||||
|
use std::net::SocketAddr;
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
use reqwest::StatusCode;
|
use lastfm::reqwest::StatusCode;
|
||||||
|
use warp::filters::path::FullPath;
|
||||||
|
use warp::http::Method;
|
||||||
use warp::Filter;
|
use warp::Filter;
|
||||||
use warp::reply::Reply;
|
use warp::reply::Reply;
|
||||||
|
|
||||||
use crate::error_responses::{ErrorMessage, InternalServerError, BadRequestError, NotFoundError};
|
use reqwest::Client;
|
||||||
|
|
||||||
|
use crate::error_responses::{ErrorMessage, InternalServerError, BadRequestError, NotFoundError, NotImplementedError};
|
||||||
use crate::v1::get_v1_routes;
|
use crate::v1::get_v1_routes;
|
||||||
use crate::{Logger, parse_ip};
|
use crate::{parse_ip, request_logger, Logger};
|
||||||
|
use crate::iplookup::ip_lookup;
|
||||||
|
|
||||||
|
|
||||||
pub async fn serve() {
|
pub async fn serve() {
|
||||||
|
@ -18,9 +24,40 @@ pub async fn serve() {
|
||||||
|
|
||||||
let socket_addr = parse_ip();
|
let socket_addr = parse_ip();
|
||||||
|
|
||||||
|
let favicon = warp::path("favicon.ico").and(warp::fs::file("./src/favicon.png"));
|
||||||
|
|
||||||
|
// /status => 200 OK
|
||||||
|
let status = warp::path("status")
|
||||||
|
.map(|| warp::reply());
|
||||||
|
|
||||||
|
// Middleware filter to log request details
|
||||||
|
let log_request = warp::any()
|
||||||
|
.and(warp::method())
|
||||||
|
.and(warp::path::full())
|
||||||
|
.and(warp::addr::remote())
|
||||||
|
.and(warp::header::optional::<String>("x-forwarded-for"))
|
||||||
|
.map(|method: Method, path: FullPath, addr: Option<SocketAddr>, fwd_for: Option<String>| {
|
||||||
|
let client_ip = fwd_for.unwrap_or_else(|| addr.map(|a| a.ip().to_string()).unwrap_or_else(|| String::from("unknown")));
|
||||||
|
let path_str = path.as_str().to_string(); // Convert to owned String
|
||||||
|
let method_clone = method.clone();
|
||||||
|
let method_str = method_clone.clone().as_str().to_string();
|
||||||
|
let client_ip_clone = client_ip.clone(); // Clone for use outside the async block
|
||||||
|
let path_str_clone = path_str.clone();
|
||||||
|
|
||||||
|
/*tokio::spawn(async move {
|
||||||
|
let client = Client::new();
|
||||||
|
if let Err(e) = request_logger::log_request(&client, &client_ip, &path_str, &method_clone, "requests.json").await {
|
||||||
|
eprintln!("Failed to log request: {:?}", e);
|
||||||
|
}
|
||||||
|
});*/
|
||||||
|
|
||||||
|
Logger::info(&format!("{} {} from {} ({})", method_str, path_str_clone, ip_lookup(&client_ip_clone), client_ip_clone));
|
||||||
|
});
|
||||||
|
|
||||||
// GET (any) => reply with return from handle_path
|
// GET (any) => reply with return from handle_path
|
||||||
let routes = get_v1_routes()
|
let routes = log_request
|
||||||
.recover(handle_rejection);
|
.clone().untuple_one().and(favicon.or(status.or(get_v1_routes())
|
||||||
|
.recover(handle_rejection)));
|
||||||
|
|
||||||
|
|
||||||
async fn handle_rejection(err: warp::Rejection) -> Result<impl Reply, Infallible> {
|
async fn handle_rejection(err: warp::Rejection) -> Result<impl Reply, Infallible> {
|
||||||
|
@ -30,6 +67,8 @@ pub async fn serve() {
|
||||||
(StatusCode::BAD_REQUEST, "Bad Request")
|
(StatusCode::BAD_REQUEST, "Bad Request")
|
||||||
} else if err.is_not_found() || err.find::<NotFoundError>().is_some() {
|
} else if err.is_not_found() || err.find::<NotFoundError>().is_some() {
|
||||||
(StatusCode::NOT_FOUND, "Not Found")
|
(StatusCode::NOT_FOUND, "Not Found")
|
||||||
|
} else if err.find::<NotImplementedError>().is_some() {
|
||||||
|
(StatusCode::NOT_IMPLEMENTED, "Not Implemented")
|
||||||
} else {
|
} else {
|
||||||
(StatusCode::INTERNAL_SERVER_ERROR, "Unhandled Rejection") // Default case
|
(StatusCode::INTERNAL_SERVER_ERROR, "Unhandled Rejection") // Default case
|
||||||
};
|
};
|
||||||
|
@ -39,7 +78,7 @@ pub async fn serve() {
|
||||||
message: message.into(),
|
message: message.into(),
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(warp::reply::with_status(json, code))
|
Ok(warp::reply::with_status(json, StatusCode::from_u16(code.as_u16()).unwrap()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
8
src/v1/analytics/mod.rs
Normal file
8
src/v1/analytics/mod.rs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
use warp::Filter;
|
||||||
|
|
||||||
|
pub fn get_analytics_routes() -> impl warp::Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
|
||||||
|
warp::path("v1").and(warp::path("analytics"))
|
||||||
|
.and((warp::path("logcdnrequest").map(|| "Please refer to the wiki at https://wiki.jonasjones.dev/Api/"))
|
||||||
|
.or(warp::path("ping").map(|| "pong"))
|
||||||
|
.or(warp::path("version").map(|| warp::reply::json(&[option_env!("CARGO_PKG_VERSION").unwrap_or("unknown")]))))
|
||||||
|
}
|
7
src/v1/auth/mod.rs
Normal file
7
src/v1/auth/mod.rs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
use warp::Filter;
|
||||||
|
|
||||||
|
pub fn get_auth_routes() -> impl warp::Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
|
||||||
|
warp::path("v1").and(warp::path("auth"))
|
||||||
|
.and((warp::path("requestsession").and(warp))
|
||||||
|
.or(warp::path("login")))
|
||||||
|
}
|
|
@ -323,8 +323,6 @@ async fn filter_type_handler(params: HashMap<String, String>) -> Result<impl war
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn filter_gettypes_handler() -> Result<impl warp::Reply, warp::Rejection> {
|
async fn filter_gettypes_handler() -> Result<impl warp::Reply, warp::Rejection> {
|
||||||
println!("Received parameters - parameters: gettypes");
|
|
||||||
|
|
||||||
// fetch the data
|
// fetch the data
|
||||||
let data = fetch_data().await.unwrap();
|
let data = fetch_data().await.unwrap();
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::{Value, json};
|
use serde_json::{Value, json};
|
||||||
use warp::Filter;
|
use warp::Filter;
|
||||||
|
@ -10,7 +8,7 @@ mod upcoming;
|
||||||
|
|
||||||
use filter::get_kcomebacks_filter_routes;
|
use filter::get_kcomebacks_filter_routes;
|
||||||
use upcoming::get_kcomebacks_upcoming_routes;
|
use upcoming::get_kcomebacks_upcoming_routes;
|
||||||
use crate::error_responses::{InternalServerError, BadRequestError, NotFoundError};
|
use crate::error_responses::InternalServerError;
|
||||||
|
|
||||||
pub fn get_kcomebacks_routes() -> impl warp::Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
|
pub fn get_kcomebacks_routes() -> impl warp::Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
|
||||||
warp::path("v1").and(warp::path("kcomebacks"))
|
warp::path("v1").and(warp::path("kcomebacks"))
|
||||||
|
|
|
@ -2,7 +2,7 @@ use std::{collections::HashMap, ops::Add};
|
||||||
|
|
||||||
use warp::Filter;
|
use warp::Filter;
|
||||||
|
|
||||||
use crate::{v1::kcomebacks::filter::filter_daterange_handler, error_responses::BadRequestError};
|
use crate::v1::kcomebacks::filter::filter_daterange_handler;
|
||||||
|
|
||||||
pub fn get_kcomebacks_upcoming_routes() -> impl warp::Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
|
pub fn get_kcomebacks_upcoming_routes() -> impl warp::Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
|
||||||
warp::path("upcoming")
|
warp::path("upcoming")
|
||||||
|
|
|
@ -1,12 +1,17 @@
|
||||||
mod builtin;
|
mod builtin;
|
||||||
mod debug;
|
mod debug;
|
||||||
mod kcomebacks;
|
mod kcomebacks;
|
||||||
mod updates;
|
mod projects;
|
||||||
|
mod run;
|
||||||
|
|
||||||
|
pub use run::setup as run_setup;
|
||||||
|
pub use run::run_sync_all_command;
|
||||||
|
|
||||||
pub use builtin::get_builtin_routes as get_v1_builtin_routes;
|
pub use builtin::get_builtin_routes as get_v1_builtin_routes;
|
||||||
pub use debug::get_debug_routes as get_v1_debug_routes;
|
pub use debug::get_debug_routes as get_v1_debug_routes;
|
||||||
pub use kcomebacks::get_kcomebacks_routes as get_v1_kcomebacks_routes;
|
pub use kcomebacks::get_kcomebacks_routes as get_v1_kcomebacks_routes;
|
||||||
pub use updates::get_updates_routes as get_v1_updates_routes;
|
pub use projects::get_project_routes as get_v1_project_routes;
|
||||||
|
pub use run::get_run_routes as get_v1_updates_routes;
|
||||||
|
|
||||||
use warp::Filter;
|
use warp::Filter;
|
||||||
|
|
||||||
|
@ -14,5 +19,6 @@ pub fn get_v1_routes() -> impl warp::Filter<Extract = impl warp::Reply, Error =
|
||||||
return get_v1_builtin_routes()
|
return get_v1_builtin_routes()
|
||||||
.or(get_v1_debug_routes())
|
.or(get_v1_debug_routes())
|
||||||
.or(get_v1_kcomebacks_routes())
|
.or(get_v1_kcomebacks_routes())
|
||||||
|
.or(get_v1_project_routes())
|
||||||
.or(get_v1_updates_routes());
|
.or(get_v1_updates_routes());
|
||||||
}
|
}
|
566
src/v1/projects/filter/mod.rs
Normal file
566
src/v1/projects/filter/mod.rs
Normal file
|
@ -0,0 +1,566 @@
|
||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
|
use serde_json::{Value, json};
|
||||||
|
use warp::Filter;
|
||||||
|
|
||||||
|
use crate::{error_responses::BadRequestError, v1::projects::{fetch_data, create_json_response, Project as EntryProject}};
|
||||||
|
|
||||||
|
pub fn get_project_filter_routes() -> impl warp::Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
|
||||||
|
warp::path("filter")
|
||||||
|
.and((warp::path("getall").and(warp::get()).and(warp::query::<HashMap<String, String>>()).and_then(filter_getall_handler))
|
||||||
|
.or(warp::path("lastupdaterange").and(warp::get()).and(warp::query::<HashMap<String, String>>()).and_then(filter_lastupdaterange_handler))
|
||||||
|
.or(warp::path("title").and(warp::get()).and(warp::query::<HashMap<String, String>>()).and_then(filter_title_handler))
|
||||||
|
.or(warp::path("description").and(warp::get()).and(warp::query::<HashMap<String, String>>()).and_then(filter_description_handler))
|
||||||
|
.or(warp::path("search").and(warp::get()).and(warp::query::<HashMap<String, String>>()).and_then(filter_search_handler))
|
||||||
|
.or(warp::path("status").and(warp::get()).and(warp::query::<HashMap<String, String>>()).and_then(filter_status_handler))
|
||||||
|
.or(warp::path("statuscolor").and(warp::get().and(warp::query::<HashMap<String, String>>()).and_then(filter_statuscolor_handler)))
|
||||||
|
.or(warp::path("category").and(warp::get().and(warp::query::<HashMap<String, String>>()).and_then(filter_category_handler)))
|
||||||
|
.or(warp::path("language").and(warp::get().and(warp::query::<HashMap<String, String>>()).and_then(filter_language_handler)))
|
||||||
|
.or(warp::path("getlangs").and(warp::get().and_then(filter_getlangs_handler)))
|
||||||
|
.or(warp::path("getstatuses").and(warp::get().and_then(filter_getstatuses_handler)))
|
||||||
|
.or(warp::path("getcolors").and(warp::get().and_then(filter_getcolors_handler)))
|
||||||
|
.or(warp::path("getcategories").and(warp::get().and_then(filter_getcategories_handler))))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn filter_getall_handler(params: HashMap<String, String>) -> Result<impl warp::Reply, warp::Rejection> {
|
||||||
|
// Access the parameters from the HashMap
|
||||||
|
let limit = params.get("limit").unwrap_or(&"".to_string()).to_string();
|
||||||
|
let offset = params.get("offset").unwrap_or(&"".to_string()).to_string();
|
||||||
|
|
||||||
|
// check if the parameters are valid
|
||||||
|
if params.len() > 2
|
||||||
|
|| !limit.parse::<i32>().is_ok()
|
||||||
|
|| !offset.parse::<i32>().is_ok()
|
||||||
|
|| limit.parse::<i32>().unwrap() < 0
|
||||||
|
|| limit.parse::<i32>().unwrap() > 50
|
||||||
|
|| offset.parse::<i32>().unwrap() < 0
|
||||||
|
|| limit.is_empty()
|
||||||
|
|| offset.is_empty()
|
||||||
|
{
|
||||||
|
return Err(warp::reject::custom(BadRequestError));
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch the data
|
||||||
|
let data = fetch_data().await.unwrap();
|
||||||
|
|
||||||
|
// filter the data
|
||||||
|
let filtered_data: Vec<EntryProject> = match data {
|
||||||
|
Value::Array(items) => {
|
||||||
|
items
|
||||||
|
.iter()
|
||||||
|
.filter_map(|item| serde_json::from_value::<EntryProject>(item.clone()).ok())
|
||||||
|
.filter(|project| project.visible)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
_ => Vec::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// get the total number of results
|
||||||
|
let total_results = filtered_data.len();
|
||||||
|
|
||||||
|
// apply the limit and offset
|
||||||
|
let filtered_data = filtered_data.iter().skip(offset.parse::<usize>().unwrap() + 1).take(limit.parse::<usize>().unwrap()).collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// return the data
|
||||||
|
Ok(warp::reply::json(&create_json_response(filtered_data, total_results)))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async fn filter_lastupdaterange_handler(params: HashMap<String, String>) -> Result<impl warp::Reply, warp::Rejection> {
|
||||||
|
// Access the parameters from the HashMap
|
||||||
|
let start = params.get("start").unwrap_or(&"".to_string()).to_string();
|
||||||
|
let end = params.get("end").unwrap_or(&"".to_string()).to_string();
|
||||||
|
let limit = params.get("limit").unwrap_or(&"".to_string()).to_string();
|
||||||
|
let offset = params.get("offset").unwrap_or(&"".to_string()).to_string();
|
||||||
|
|
||||||
|
// Parse the start and end dates (from unix time) to i64 integers
|
||||||
|
let start = start.parse::<i64>().unwrap_or(-1);
|
||||||
|
let end = end.parse::<i64>().unwrap_or(-1);
|
||||||
|
|
||||||
|
// check if the parameters are valid
|
||||||
|
if params.len() > 4
|
||||||
|
|| !limit.parse::<i32>().is_ok()
|
||||||
|
|| !offset.parse::<i32>().is_ok()
|
||||||
|
|| limit.parse::<i32>().unwrap() < 0
|
||||||
|
|| limit.parse::<i32>().unwrap() > 50
|
||||||
|
|| offset.parse::<i32>().unwrap() < 0
|
||||||
|
|| limit.is_empty()
|
||||||
|
|| offset.is_empty()
|
||||||
|
|| start == -1
|
||||||
|
|| end == -1
|
||||||
|
|| end < start
|
||||||
|
{
|
||||||
|
return Err(warp::reject::custom(BadRequestError));
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch the data
|
||||||
|
let data = fetch_data().await.unwrap();
|
||||||
|
|
||||||
|
// filter the data
|
||||||
|
let filtered_data: Vec<EntryProject> = match data {
|
||||||
|
Value::Array(items) => {
|
||||||
|
items
|
||||||
|
.iter()
|
||||||
|
.filter_map(|item| serde_json::from_value::<EntryProject>(item.clone()).ok())
|
||||||
|
.filter(|project| project.visible)
|
||||||
|
.filter(|project| project.last_update >= start && project.last_update <= end)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
_ => Vec::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("{:?}", filtered_data);
|
||||||
|
|
||||||
|
// get the total number of results
|
||||||
|
let total_results = filtered_data.len();
|
||||||
|
|
||||||
|
// apply the limit and offset
|
||||||
|
let filtered_data = filtered_data.iter().skip(offset.parse::<usize>().unwrap()).take(limit.parse::<usize>().unwrap()).collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// return the data
|
||||||
|
Ok(warp::reply::json(&create_json_response(filtered_data, total_results)))
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn filter_title_handler(params: HashMap<String, String>) -> Result<impl warp::Reply, warp::Rejection> {
|
||||||
|
// Access the parameters from the HashMap
|
||||||
|
let title = params.get("title").unwrap_or(&"".to_string()).to_string();
|
||||||
|
let limit = params.get("limit").unwrap_or(&"".to_string()).to_string();
|
||||||
|
let offset = params.get("offset").unwrap_or(&"".to_string()).to_string();
|
||||||
|
|
||||||
|
// check if the parameters are valid
|
||||||
|
if params.len() > 3
|
||||||
|
|| !limit.parse::<i32>().is_ok()
|
||||||
|
|| !offset.parse::<i32>().is_ok()
|
||||||
|
|| limit.parse::<i32>().unwrap() < 0
|
||||||
|
|| limit.parse::<i32>().unwrap() > 50
|
||||||
|
|| offset.parse::<i32>().unwrap() < 0
|
||||||
|
|| limit.is_empty()
|
||||||
|
|| offset.is_empty()
|
||||||
|
|| title.is_empty()
|
||||||
|
{
|
||||||
|
return Err(warp::reject::custom(BadRequestError));
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch the data
|
||||||
|
let data = fetch_data().await.unwrap();
|
||||||
|
|
||||||
|
// filter the data
|
||||||
|
let filtered_data: Vec<EntryProject> = match data {
|
||||||
|
Value::Array(items) => {
|
||||||
|
items
|
||||||
|
.iter()
|
||||||
|
.filter_map(|item| serde_json::from_value::<EntryProject>(item.clone()).ok())
|
||||||
|
.filter(|project| project.visible)
|
||||||
|
.filter(|project| project.title.to_lowercase().contains(title.to_lowercase().as_str()))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
_ => Vec::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// get the total number of results
|
||||||
|
let total_results = filtered_data.len();
|
||||||
|
|
||||||
|
// apply the limit and offset
|
||||||
|
let filtered_data = filtered_data.iter().skip(offset.parse::<usize>().unwrap()).take(limit.parse::<usize>().unwrap()).collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// return the data
|
||||||
|
Ok(warp::reply::json(&create_json_response(filtered_data, total_results)))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn filter_description_handler(params: HashMap<String, String>) -> Result<impl warp::Reply, warp::Rejection> {
|
||||||
|
// Access the parameters from the HashMap
|
||||||
|
let description = params.get("description").unwrap_or(&"".to_string()).to_string();
|
||||||
|
let limit = params.get("limit").unwrap_or(&"".to_string()).to_string();
|
||||||
|
let offset = params.get("offset").unwrap_or(&"".to_string()).to_string();
|
||||||
|
|
||||||
|
// check if the parameters are valid
|
||||||
|
if params.len() > 3
|
||||||
|
|| !limit.parse::<i32>().is_ok()
|
||||||
|
|| !offset.parse::<i32>().is_ok()
|
||||||
|
|| limit.parse::<i32>().unwrap() < 0
|
||||||
|
|| limit.parse::<i32>().unwrap() > 50
|
||||||
|
|| offset.parse::<i32>().unwrap() < 0
|
||||||
|
|| limit.is_empty()
|
||||||
|
|| offset.is_empty()
|
||||||
|
|| description.is_empty()
|
||||||
|
{
|
||||||
|
return Err(warp::reject::custom(BadRequestError));
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch the data
|
||||||
|
let data = fetch_data().await.unwrap();
|
||||||
|
|
||||||
|
// filter the data
|
||||||
|
let filtered_data: Vec<EntryProject> = match data {
|
||||||
|
Value::Array(items) => {
|
||||||
|
items
|
||||||
|
.iter()
|
||||||
|
.filter_map(|item| serde_json::from_value::<EntryProject>(item.clone()).ok())
|
||||||
|
.filter(|project| project.visible)
|
||||||
|
.filter(|project| project.description.to_lowercase().contains(description.to_lowercase().as_str()))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
_ => Vec::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// get the total number of results
|
||||||
|
let total_results = filtered_data.len();
|
||||||
|
|
||||||
|
// apply the limit and offset
|
||||||
|
let filtered_data = filtered_data.iter().skip(offset.parse::<usize>().unwrap()).take(limit.parse::<usize>().unwrap()).collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// return the data
|
||||||
|
Ok(warp::reply::json(&create_json_response(filtered_data, total_results)))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn filter_search_handler(params: HashMap<String, String>) -> Result<impl warp::Reply, warp::Rejection> {
|
||||||
|
// Access the parameters from the HashMap
|
||||||
|
let search = params.get("search").unwrap_or(&"".to_string()).to_string();
|
||||||
|
let limit = params.get("limit").unwrap_or(&"".to_string()).to_string();
|
||||||
|
let offset = params.get("offset").unwrap_or(&"".to_string()).to_string();
|
||||||
|
|
||||||
|
// check if the parameters are valid
|
||||||
|
if params.len() > 3
|
||||||
|
|| !limit.parse::<i32>().is_ok()
|
||||||
|
|| !offset.parse::<i32>().is_ok()
|
||||||
|
|| limit.parse::<i32>().unwrap() < 0
|
||||||
|
|| limit.parse::<i32>().unwrap() > 50
|
||||||
|
|| offset.parse::<i32>().unwrap() < 0
|
||||||
|
|| limit.is_empty()
|
||||||
|
|| offset.is_empty()
|
||||||
|
|| search.is_empty()
|
||||||
|
{
|
||||||
|
return Err(warp::reject::custom(BadRequestError));
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch the data
|
||||||
|
let data = fetch_data().await.unwrap();
|
||||||
|
|
||||||
|
// filter the data
|
||||||
|
let filtered_data: Vec<EntryProject> = match data {
|
||||||
|
Value::Array(items) => {
|
||||||
|
items
|
||||||
|
.iter()
|
||||||
|
.filter_map(|item| serde_json::from_value::<EntryProject>(item.clone()).ok())
|
||||||
|
.filter(|project| project.visible)
|
||||||
|
.filter(|project| project.title.to_lowercase().contains(search.to_lowercase().as_str()) || project.description.to_lowercase().contains(search.to_lowercase().as_str()))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
_ => Vec::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// get the total number of results
|
||||||
|
let total_results = filtered_data.len();
|
||||||
|
|
||||||
|
// apply the limit and offset
|
||||||
|
let filtered_data = filtered_data.iter().skip(offset.parse::<usize>().unwrap()).take(limit.parse::<usize>().unwrap()).collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// return the data
|
||||||
|
Ok(warp::reply::json(&create_json_response(filtered_data, total_results)))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn filter_status_handler(params: HashMap<String, String>) -> Result<impl warp::Reply, warp::Rejection> {
|
||||||
|
// Access the parameres from the HashMap
|
||||||
|
let status = params.get("status").unwrap_or(&"".to_string()).to_string();
|
||||||
|
let limit = params.get("limit").unwrap_or(&"".to_string()).to_string();
|
||||||
|
let offset = params.get("offset").unwrap_or(&"".to_string()).to_string();
|
||||||
|
|
||||||
|
// check if the parameters are valid
|
||||||
|
if status.is_empty()
|
||||||
|
|| params.len() > 3
|
||||||
|
|| !limit.parse::<i32>().is_ok()
|
||||||
|
|| !offset.parse::<i32>().is_ok()
|
||||||
|
|| limit.parse::<i32>().unwrap() < 0
|
||||||
|
|| limit.parse::<i32>().unwrap() > 50
|
||||||
|
|| offset.parse::<i32>().unwrap() < 0
|
||||||
|
|| limit.is_empty() || offset.is_empty()
|
||||||
|
{
|
||||||
|
return Err(warp::reject::custom(BadRequestError));
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch the data
|
||||||
|
let data = fetch_data().await.unwrap();
|
||||||
|
|
||||||
|
// filter the data
|
||||||
|
let filtered_data: Vec<EntryProject> = match data {
|
||||||
|
Value::Array(items) => {
|
||||||
|
items
|
||||||
|
.iter()
|
||||||
|
.filter_map(|item| serde_json::from_value::<EntryProject>(item.clone()).ok())
|
||||||
|
.filter(|project| project.visible)
|
||||||
|
.filter(|project| project.status == status)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
_ => Vec::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// get the total number of results
|
||||||
|
let total_results = filtered_data.len();
|
||||||
|
|
||||||
|
// apply the limit and offset
|
||||||
|
let filtered_data = filtered_data.iter().skip(offset.parse::<usize>().unwrap()).take(limit.parse::<usize>().unwrap()).collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// return the data
|
||||||
|
Ok(warp::reply::json(&create_json_response(filtered_data, total_results)))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn filter_statuscolor_handler(params: HashMap<String, String>) -> Result<impl warp::Reply, warp::Rejection> {
|
||||||
|
// Access the parameres from the HashMap
|
||||||
|
let statuscolor = params.get("statuscolor").unwrap_or(&"".to_string()).to_string();
|
||||||
|
let limit = params.get("limit").unwrap_or(&"".to_string()).to_string();
|
||||||
|
let offset = params.get("offset").unwrap_or(&"".to_string()).to_string();
|
||||||
|
|
||||||
|
// check if the parameters are valid
|
||||||
|
if statuscolor.is_empty()
|
||||||
|
|| params.len() > 3
|
||||||
|
|| !limit.parse::<i32>().is_ok()
|
||||||
|
|| !offset.parse::<i32>().is_ok()
|
||||||
|
|| limit.parse::<i32>().unwrap() < 0
|
||||||
|
|| limit.parse::<i32>().unwrap() > 50
|
||||||
|
|| offset.parse::<i32>().unwrap() < 0
|
||||||
|
|| limit.is_empty() || offset.is_empty()
|
||||||
|
{
|
||||||
|
return Err(warp::reject::custom(BadRequestError));
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch the data
|
||||||
|
let data = fetch_data().await.unwrap();
|
||||||
|
|
||||||
|
// filter the data
|
||||||
|
let filtered_data: Vec<EntryProject> = match data {
|
||||||
|
Value::Array(items) => {
|
||||||
|
items
|
||||||
|
.iter()
|
||||||
|
.filter_map(|item| serde_json::from_value::<EntryProject>(item.clone()).ok())
|
||||||
|
.filter(|project| project.visible)
|
||||||
|
.filter(|project| project.statuscolor == statuscolor)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
_ => Vec::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// get the total number of results
|
||||||
|
let total_results = filtered_data.len();
|
||||||
|
|
||||||
|
// apply the limit and offset
|
||||||
|
let filtered_data = filtered_data.iter().skip(offset.parse::<usize>().unwrap()).take(limit.parse::<usize>().unwrap()).collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// return the data
|
||||||
|
Ok(warp::reply::json(&create_json_response(filtered_data, total_results)))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn filter_category_handler(params: HashMap<String, String>) -> Result<impl warp::Reply, warp::Rejection> {
|
||||||
|
// Access the parameres from the HashMap
|
||||||
|
let category = params.get("category").unwrap_or(&"".to_string()).to_string();
|
||||||
|
let limit = params.get("limit").unwrap_or(&"".to_string()).to_string();
|
||||||
|
let offset = params.get("offset").unwrap_or(&"".to_string()).to_string();
|
||||||
|
|
||||||
|
// check if the parameters are valid
|
||||||
|
if category.is_empty()
|
||||||
|
|| params.len() > 3
|
||||||
|
|| !limit.parse::<i32>().is_ok()
|
||||||
|
|| !offset.parse::<i32>().is_ok()
|
||||||
|
|| limit.parse::<i32>().unwrap() < 0
|
||||||
|
|| limit.parse::<i32>().unwrap() > 50
|
||||||
|
|| offset.parse::<i32>().unwrap() < 0
|
||||||
|
|| limit.is_empty() || offset.is_empty()
|
||||||
|
{
|
||||||
|
return Err(warp::reject::custom(BadRequestError));
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch the data
|
||||||
|
let data = fetch_data().await.unwrap();
|
||||||
|
|
||||||
|
// filter the data
|
||||||
|
let filtered_data: Vec<EntryProject> = match data {
|
||||||
|
Value::Array(items) => {
|
||||||
|
items
|
||||||
|
.iter()
|
||||||
|
.filter_map(|item| serde_json::from_value::<EntryProject>(item.clone()).ok())
|
||||||
|
.filter(|project| project.visible)
|
||||||
|
.filter(|project| project.categories.iter().any(|cat| cat == category.as_str()))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
_ => Vec::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// get the total number of results
|
||||||
|
let total_results = filtered_data.len();
|
||||||
|
|
||||||
|
// apply the limit and offset
|
||||||
|
let filtered_data = filtered_data.iter().skip(offset.parse::<usize>().unwrap()).take(limit.parse::<usize>().unwrap()).collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// return the data
|
||||||
|
Ok(warp::reply::json(&create_json_response(filtered_data, total_results)))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn filter_language_handler(params: HashMap<String, String>) -> Result<impl warp::Reply, warp::Rejection> {
|
||||||
|
// Access the parameres from the HashMap
|
||||||
|
let language = params.get("language").unwrap_or(&"".to_string()).to_string();
|
||||||
|
let limit = params.get("limit").unwrap_or(&"".to_string()).to_string();
|
||||||
|
let offset = params.get("offset").unwrap_or(&"".to_string()).to_string();
|
||||||
|
|
||||||
|
// check if the parameters are valid
|
||||||
|
if language.is_empty()
|
||||||
|
|| params.len() > 3
|
||||||
|
|| !limit.parse::<i32>().is_ok()
|
||||||
|
|| !offset.parse::<i32>().is_ok()
|
||||||
|
|| limit.parse::<i32>().unwrap() < 0
|
||||||
|
|| limit.parse::<i32>().unwrap() > 50
|
||||||
|
|| offset.parse::<i32>().unwrap() < 0
|
||||||
|
|| limit.is_empty() || offset.is_empty()
|
||||||
|
{
|
||||||
|
return Err(warp::reject::custom(BadRequestError));
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch the data
|
||||||
|
let data = fetch_data().await.unwrap();
|
||||||
|
|
||||||
|
// filter the data
|
||||||
|
let filtered_data: Vec<EntryProject> = match data {
|
||||||
|
Value::Array(items) => {
|
||||||
|
items
|
||||||
|
.iter()
|
||||||
|
.filter_map(|item| serde_json::from_value::<EntryProject>(item.clone()).ok())
|
||||||
|
.filter(|project| project.visible)
|
||||||
|
.filter(|project| project.languages.contains_key(&language))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
_ => Vec::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// get the total number of results
|
||||||
|
let total_results = filtered_data.len();
|
||||||
|
|
||||||
|
// apply the limit and offset
|
||||||
|
let filtered_data = filtered_data.iter().skip(offset.parse::<usize>().unwrap()).take(limit.parse::<usize>().unwrap()).collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// return the data
|
||||||
|
Ok(warp::reply::json(&create_json_response(filtered_data, total_results)))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn filter_getlangs_handler() -> Result<impl warp::Reply, warp::Rejection> {
|
||||||
|
// fetch the data
|
||||||
|
let data = fetch_data().await.unwrap();
|
||||||
|
|
||||||
|
//filter the data
|
||||||
|
let filtered_data: Vec<EntryProject> = match data {
|
||||||
|
Value::Array(items) => {
|
||||||
|
items
|
||||||
|
.iter()
|
||||||
|
.filter_map(|item| serde_json::from_value::<EntryProject>(item.clone()).ok())
|
||||||
|
.filter(|project| project.visible)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
_ => Vec::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// filter the data
|
||||||
|
let mut languages_set: HashSet<String> = HashSet::new();
|
||||||
|
|
||||||
|
for project in filtered_data {
|
||||||
|
for language in project.languages.keys() {
|
||||||
|
languages_set.insert(language.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the total number of results
|
||||||
|
let total_results = languages_set.len();
|
||||||
|
|
||||||
|
// json response
|
||||||
|
let json_response = json!({
|
||||||
|
"results": languages_set,
|
||||||
|
"total_results": total_results,
|
||||||
|
});
|
||||||
|
|
||||||
|
// return the data
|
||||||
|
Ok(warp::reply::json(&json_response))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn filter_getstatuses_handler() -> Result<impl warp::Reply, warp::Rejection> {
|
||||||
|
// fetch the data
|
||||||
|
let data = fetch_data().await.unwrap();
|
||||||
|
|
||||||
|
let mut all_statuses: Vec<String> = Vec::new();
|
||||||
|
for i in 1..data.as_array().unwrap().len() {
|
||||||
|
if !data[i]["status"].as_str().unwrap().to_string().is_empty() {
|
||||||
|
all_statuses.push(data[i]["status"].as_str().unwrap().to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove duplicates
|
||||||
|
all_statuses.sort();
|
||||||
|
all_statuses.dedup();
|
||||||
|
|
||||||
|
// get the total number of results
|
||||||
|
let total_results = all_statuses.len();
|
||||||
|
|
||||||
|
// json response
|
||||||
|
let json_response = json!({
|
||||||
|
"results": all_statuses,
|
||||||
|
"total_results": total_results,
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(warp::reply::json(&json_response))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn filter_getcolors_handler() -> Result<impl warp::Reply, warp::Rejection> {
|
||||||
|
// fetch the data
|
||||||
|
let data = fetch_data().await.unwrap();
|
||||||
|
|
||||||
|
let mut all_colors: Vec<String> = Vec::new();
|
||||||
|
for i in 1..data.as_array().unwrap().len() {
|
||||||
|
if !data[i]["statuscolor"].as_str().unwrap().to_string().is_empty() {
|
||||||
|
all_colors.push(data[i]["statuscolor"].as_str().unwrap().to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove duplicates
|
||||||
|
all_colors.sort();
|
||||||
|
all_colors.dedup();
|
||||||
|
|
||||||
|
// get the total number of results
|
||||||
|
let total_results = all_colors.len();
|
||||||
|
|
||||||
|
// json response
|
||||||
|
let json_response = json!({
|
||||||
|
"results": all_colors,
|
||||||
|
"total_results": total_results,
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(warp::reply::json(&json_response))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn filter_getcategories_handler() -> Result<impl warp::Reply, warp::Rejection> {
|
||||||
|
// fetch the data
|
||||||
|
let data = fetch_data().await.unwrap();
|
||||||
|
|
||||||
|
let mut all_categories: Vec<String> = Vec::new();
|
||||||
|
for i in 1..data.as_array().unwrap().len() {
|
||||||
|
if !data[i]["categories"].as_array().unwrap().is_empty() {
|
||||||
|
for j in 0..data[i]["categories"].as_array().unwrap().len() {
|
||||||
|
if !data[i]["categories"][j].as_str().unwrap().to_string().is_empty() {
|
||||||
|
all_categories.push(data[i]["categories"][j].as_str().unwrap().to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove duplicates
|
||||||
|
all_categories.sort();
|
||||||
|
all_categories.dedup();
|
||||||
|
|
||||||
|
// get the total number of results
|
||||||
|
let total_results = all_categories.len();
|
||||||
|
|
||||||
|
// json response
|
||||||
|
let json_response = json!({
|
||||||
|
"results": all_categories,
|
||||||
|
"total_results": total_results,
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(warp::reply::json(&json_response))
|
||||||
|
}
|
76
src/v1/projects/mod.rs
Normal file
76
src/v1/projects/mod.rs
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_json::{Value, json};
|
||||||
|
use warp::Filter;
|
||||||
|
use reqwest::Error;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
mod filter;
|
||||||
|
|
||||||
|
use filter::get_project_filter_routes;
|
||||||
|
|
||||||
|
use crate::error_responses::InternalServerError;
|
||||||
|
|
||||||
|
pub fn get_project_routes() -> impl warp::Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
|
||||||
|
warp::path("v1").and(warp::path("projects"))
|
||||||
|
|
||||||
|
.and(warp::path("last_update").and(warp::get()).and_then(last_update)
|
||||||
|
.or(warp::path("start_update").map(|| "Not implemented yet"))
|
||||||
|
.or(get_project_filter_routes())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// get json data from https://https://cdn.jonasjones.dev/api/projects/projects.json
|
||||||
|
pub async fn fetch_data() -> Result<serde_json::Value, Error> {
|
||||||
|
let url = "https://cdn.jonasjones.dev/api/projects/projects.json";
|
||||||
|
let response = reqwest::get(url).await?;
|
||||||
|
|
||||||
|
if response.status().is_success() {
|
||||||
|
// Parse the JSON response
|
||||||
|
let json_data: serde_json::Value = response.json().await?;
|
||||||
|
return Ok(json_data);
|
||||||
|
} else {
|
||||||
|
// Handle non-successful status codes
|
||||||
|
Err(response.error_for_status().unwrap_err())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn last_update() -> Result<impl warp::Reply, warp::Rejection> {
|
||||||
|
|
||||||
|
// get the value of last_update of the first element of the json that fetch_data() returns
|
||||||
|
let last_update_value = fetch_data().await.unwrap()[0]["last_update"].clone();
|
||||||
|
|
||||||
|
// get the value from last_update_value and return it as a json if it's Ok, otherwise return an InternalServerError
|
||||||
|
match last_update_value {
|
||||||
|
serde_json::Value::String(last_update) => Ok(warp::reply::json(&last_update)),
|
||||||
|
_ => Err(warp::reject::custom(InternalServerError)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
pub struct Project {
|
||||||
|
pub title: String,
|
||||||
|
pub description: String,
|
||||||
|
pub status: String,
|
||||||
|
pub statuscolor: String,
|
||||||
|
pub categories: Vec<String>,
|
||||||
|
pub languages: HashMap<String, i32>,
|
||||||
|
pub gh_api: String,
|
||||||
|
pub version: String,
|
||||||
|
pub backgroud: String,
|
||||||
|
pub links: HashMap<String, String>,
|
||||||
|
pub visible: bool,
|
||||||
|
pub last_update: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_json_response(items: Vec<&Project>, total_results: usize) -> Value {
|
||||||
|
// Serialize the vector of items to a JSON array
|
||||||
|
let results_array: Vec<Value> = items.into_iter().map(|item| json!(item)).collect();
|
||||||
|
|
||||||
|
// Build the final JSON object with "results" and "total_results" fields
|
||||||
|
let json_response = json!({
|
||||||
|
"results": results_array,
|
||||||
|
"total_results": total_results,
|
||||||
|
});
|
||||||
|
|
||||||
|
json_response
|
||||||
|
}
|
284
src/v1/run/mod.rs
Normal file
284
src/v1/run/mod.rs
Normal file
|
@ -0,0 +1,284 @@
|
||||||
|
use std::fs;
|
||||||
|
// use std::io::BufRead;
|
||||||
|
use std::process::{Stdio, Command};
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_json::json;
|
||||||
|
// use tokio::sync::mpsc;
|
||||||
|
use tokio::task;
|
||||||
|
use warp::Filter;
|
||||||
|
use crate::error_responses::InternalServerError;
|
||||||
|
use crate::Logger;
|
||||||
|
|
||||||
|
// DiscographyQuery
|
||||||
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
pub struct DiscographyQuery {
|
||||||
|
artists: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_run_routes() -> impl warp::Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
|
||||||
|
warp::path("v1").and(warp::path("run"))
|
||||||
|
// update/kcomebacks
|
||||||
|
// update/projects
|
||||||
|
// update/makediscography?artists=artist1,artist2,artist3
|
||||||
|
// update/synclikedsongs
|
||||||
|
.and((warp::path("kcomebacks").and_then(update_kcomebacks))
|
||||||
|
.or(warp::path("projects").and_then(update_projects))
|
||||||
|
.or(warp::path("makediscography").map(||"Not implemented yet"))
|
||||||
|
.or(warp::path("synclikedsongs").and_then(sync_liked_songs))
|
||||||
|
.or(warp::path("sync_all").and_then(sync_all))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn update_kcomebacks() -> Result<impl warp::Reply, warp::Rejection> {
|
||||||
|
// check if the local repository exists, if not, clone it
|
||||||
|
if !fs::metadata("./resources/turbo_octo_potato").is_ok() {
|
||||||
|
setup().unwrap();
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(err) = run_kcomebacks_command() {
|
||||||
|
// Handle the error here
|
||||||
|
eprintln!("Error: {}", err);
|
||||||
|
// Return an appropriate response or error
|
||||||
|
return Err(warp::reject::custom(InternalServerError));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(warp::reply::json(&json!({"status": "updating..."})))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn update_projects() -> Result<impl warp::Reply, warp::Rejection> {
|
||||||
|
// check if the local repository exists, if not, clone it
|
||||||
|
if !fs::metadata("./resources/turbo_octo_potato").is_ok() {
|
||||||
|
setup().unwrap();
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(err) = run_projects_command() {
|
||||||
|
// Handle the error here
|
||||||
|
eprintln!("Error: {}", err);
|
||||||
|
// Return an appropriate response or error
|
||||||
|
return Err(warp::reject::custom(InternalServerError));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(warp::reply::json(&json!({"status": "updating..."})))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn sync_liked_songs() -> Result<impl warp::Reply, warp::Rejection> {
|
||||||
|
// check if the local repository exists, if not, clone it
|
||||||
|
if !fs::metadata("./resources/turbo_octo_potato").is_ok() {
|
||||||
|
setup().unwrap();
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(err) = run_likedsongs_command() {
|
||||||
|
// Handle the error here
|
||||||
|
eprintln!("Error: {}", err);
|
||||||
|
// Return an appropriate response or error
|
||||||
|
return Err(warp::reject::custom(InternalServerError));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(warp::reply::json(&json!({"status": "updating..."})))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn sync_all() -> Result<impl warp::Reply, warp::Rejection> {
|
||||||
|
// check if the local repository exists, if not, clone it
|
||||||
|
if !fs::metadata("./resources/turbo_octo_potato").is_ok() {
|
||||||
|
setup().unwrap();
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(err) = run_sync_all_command() {
|
||||||
|
// Handle the error here
|
||||||
|
eprintln!("Error: {}", err);
|
||||||
|
// Return an appropriate response or error
|
||||||
|
return Err(warp::reject::custom(InternalServerError));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(warp::reply::json(&json!({"status": "syncing..."})))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setup() -> Result<(), git2::Error> {
|
||||||
|
let repository_url = "https://github.com/JonasunderscoreJones/turbo-octo-potato.git";
|
||||||
|
let local_directory = "resources/turbo_octo_potato";
|
||||||
|
|
||||||
|
git2::Repository::clone(repository_url, local_directory)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// fn run_command() -> Result<(), std::io::Error> {
|
||||||
|
// let (tx, mut rx) = mpsc::channel(1);
|
||||||
|
|
||||||
|
// task::spawn_blocking(move || {
|
||||||
|
// let mut child = Command::new("python3")
|
||||||
|
// .arg(&py_file)
|
||||||
|
// .arg(&args)
|
||||||
|
// .current_dir("resources/turbo_octo_potato")
|
||||||
|
// .stdout(Stdio::piped())
|
||||||
|
// .spawn()
|
||||||
|
// .expect("failed to execute child");
|
||||||
|
|
||||||
|
// let stdout = child.stdout.as_mut().unwrap();
|
||||||
|
|
||||||
|
// let mut reader = std::io::BufReader::new(stdout);
|
||||||
|
|
||||||
|
// let mut line = String::new();
|
||||||
|
|
||||||
|
// loop {
|
||||||
|
// let len = reader.read_line(&mut line).unwrap();
|
||||||
|
// if len == 0 {
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// tx.blocking_send(line.clone()).unwrap();
|
||||||
|
// line.clear();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// child.wait().unwrap();
|
||||||
|
// });
|
||||||
|
|
||||||
|
// task::spawn(async move {
|
||||||
|
// while let Some(line) = rx.recv().await {
|
||||||
|
// println!("{}", line);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
// Ok(())
|
||||||
|
// }
|
||||||
|
|
||||||
|
// run_command with python file and args as parameters
|
||||||
|
|
||||||
|
|
||||||
|
pub fn run_kcomebacks_command() -> Result<(), std::io::Error> {
|
||||||
|
// let (tx, mut rx) = mpsc::channel(1);
|
||||||
|
|
||||||
|
task::spawn_blocking(move || {
|
||||||
|
let mut child = Command::new("python3")
|
||||||
|
.arg("rpopfetch.py")
|
||||||
|
.arg("--cdn")
|
||||||
|
.current_dir("resources/turbo_octo_potato")
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.spawn()
|
||||||
|
.expect("failed to execute child");
|
||||||
|
|
||||||
|
// let stdout = child.stdout.as_mut().unwrap();
|
||||||
|
|
||||||
|
// let mut reader = std::io::BufReader::new(stdout);
|
||||||
|
|
||||||
|
// let mut line = String::new();
|
||||||
|
|
||||||
|
// loop {
|
||||||
|
// let len = reader.read_line(&mut line).unwrap();
|
||||||
|
// if len == 0 {
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// tx.blocking_send(line.clone()).unwrap();
|
||||||
|
// line.clear();
|
||||||
|
// }
|
||||||
|
|
||||||
|
child.wait().unwrap();
|
||||||
|
});
|
||||||
|
|
||||||
|
// task::spawn(async move {
|
||||||
|
// while let Some(line) = rx.recv().await {
|
||||||
|
// Logger::info(&format!("[/v1/kcomebacks/update]: {}", line));
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
Logger::info("Updating kcomebacks...");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_projects_command() -> Result<(), std::io::Error> {
|
||||||
|
// let (tx, mut rx) = mpsc::channel(1);
|
||||||
|
|
||||||
|
task::spawn_blocking(move || {
|
||||||
|
let mut child = Command::new("python3")
|
||||||
|
.arg("update_projects.py")
|
||||||
|
.arg("--cdn")
|
||||||
|
.current_dir("resources/turbo_octo_potato")
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.spawn()
|
||||||
|
.expect("failed to execute child");
|
||||||
|
|
||||||
|
// let stdout = child.stdout.as_mut().unwrap();
|
||||||
|
|
||||||
|
// let mut reader = std::io::BufReader::new(stdout);
|
||||||
|
|
||||||
|
// let mut line = String::new();
|
||||||
|
|
||||||
|
// loop {
|
||||||
|
// let len = reader.read_line(&mut line).unwrap();
|
||||||
|
// if len == 0 {
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// tx.blocking_send(line.clone()).unwrap();
|
||||||
|
// line.clear();
|
||||||
|
// }
|
||||||
|
|
||||||
|
child.wait().unwrap();
|
||||||
|
});
|
||||||
|
|
||||||
|
// task::spawn(async move {
|
||||||
|
// while let Some(line) = rx.recv().await {
|
||||||
|
// Logger::info(&format!("[/v1/projects/update]: {}", line));
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
Logger::info("Updating projects...");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_likedsongs_command() -> Result<(), std::io::Error> {
|
||||||
|
// let (tx, mut rx) = mpsc::channel(1);
|
||||||
|
|
||||||
|
task::spawn_blocking(move || {
|
||||||
|
let mut child = Command::new("python3")
|
||||||
|
.arg("likedsongsync2.py")
|
||||||
|
.current_dir("resources/turbo_octo_potato")
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.spawn()
|
||||||
|
.expect("failed to execute child");
|
||||||
|
|
||||||
|
// let stdout = child.stdout.as_mut().unwrap();
|
||||||
|
|
||||||
|
// let mut reader = std::io::BufReader::new(stdout);
|
||||||
|
|
||||||
|
// let mut line = String::new();
|
||||||
|
|
||||||
|
// loop {
|
||||||
|
// let len = reader.read_line(&mut line).unwrap();
|
||||||
|
// if len == 0 {
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// tx.blocking_send(line.clone()).unwrap();
|
||||||
|
// line.clear();
|
||||||
|
// }
|
||||||
|
|
||||||
|
child.wait().unwrap();
|
||||||
|
});
|
||||||
|
|
||||||
|
// task::spawn(async move {
|
||||||
|
// while let Some(line) = rx.recv().await {
|
||||||
|
// Logger::info(&format!("[/v1/synclikedsongs]: {}", line));
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
Logger::info("Syncing liked songs...");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_sync_all_command() -> Result<(), std::io::Error> {
|
||||||
|
task::spawn_blocking(move || {
|
||||||
|
let mut child = Command::new("python3")
|
||||||
|
.arg("script_interval_runner.py")
|
||||||
|
.current_dir("resources/turbo_octo_potato")
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.spawn()
|
||||||
|
.expect("failed to execute child");
|
||||||
|
child.wait().unwrap();
|
||||||
|
});
|
||||||
|
Logger::info("Running all Sync Scripts...");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -1,29 +1,144 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::net::IpAddr;
|
||||||
|
use reqwest::Error;
|
||||||
|
use serde_json::json;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use warp::Filter;
|
use warp::Filter;
|
||||||
|
use regex::Regex;
|
||||||
|
|
||||||
|
use crate::error_responses::BadRequestError;
|
||||||
|
|
||||||
pub fn get_mods_paths() -> impl warp::Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
|
pub fn get_mods_paths() -> impl warp::Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
|
||||||
// any path that starts with /v1/updates/minecraft/mods/{modname}/{loadername}/{version} calls handle_path
|
// any path that starts with /v1/updates/minecraft/mods/{modname}/{loadername}/{version} calls handle_path
|
||||||
warp::path("v1").and(warp::path("updates")).and(warp::path("minecraft")).and(warp::path("mods"))
|
warp::path("v1").and(warp::path("updates")).and(warp::path("minecraft")).and(warp::path("mods"))
|
||||||
|
|
||||||
.and(warp::path::param())
|
.and(warp::get().and(warp::path::param()).and(warp::path::param()).and(warp::path::param()).and(warp::path::param()).and(warp::path::end()).and(warp::filters::header::headers_cloned()).and(warp::query::<HashMap<String, String>>()).and_then(handle_path))
|
||||||
.and(warp::path::param())
|
|
||||||
.and(warp::path::param())
|
|
||||||
.and(warp::path::end())
|
|
||||||
.and(warp::addr::remote())
|
|
||||||
.map(handle_path)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_path(modname: String, loadername: String, version: String, remote_ip: Option<std::net::SocketAddr>) -> String {
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
format!("modname: {}, loadername: {}, version: {}, IP: {}", modname, loadername, version, remote_ip.unwrap_or(std::net::SocketAddr::from(([0, 0, 0, 0], 0))).ip())
|
struct ModData {
|
||||||
|
package: String,
|
||||||
|
name: String,
|
||||||
|
versions: Vec<HashMap<String, HashMap<String, ModVersion>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_with_headers(
|
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||||
headers: warp::http::HeaderMap,
|
struct ModVersion {
|
||||||
) -> String {
|
recommended: String,
|
||||||
// Iterate through the headers and print them
|
latest: String,
|
||||||
for (name, value) in headers.iter() {
|
all: Vec<String>,
|
||||||
println!("Header: {}: {}", name, value.to_str().unwrap_or("Invalid UTF-8"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Respond with a message or perform other actions as needed
|
// get json data from https://https://cdn.jonasjones.dev/api/mcmods/mcmod_metadata.json
|
||||||
"Headers received".to_string()
|
pub async fn fetch_data() -> Result<serde_json::Value, Error> {
|
||||||
|
let url = "https://cdn.jonasjones.dev/api/mcmods/mcmod_metadata.json";
|
||||||
|
let response = reqwest::get(url).await?;
|
||||||
|
|
||||||
|
if response.status().is_success() {
|
||||||
|
// Parse the JSON response
|
||||||
|
let json_data: serde_json::Value = response.json().await?;
|
||||||
|
return Ok(json_data);
|
||||||
|
} else {
|
||||||
|
// Handle non-successful status codes
|
||||||
|
Err(response.error_for_status().unwrap_err())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_valid_ip(ip_str: &str) -> bool {
|
||||||
|
if let Ok(ip) = ip_str.parse::<IpAddr>() {
|
||||||
|
match ip {
|
||||||
|
IpAddr::V4(_) => true,
|
||||||
|
IpAddr::V6(_) => true,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_valid_minecraft_version(version: &str) -> bool {
|
||||||
|
// Define the regex pattern for the Minecraft version
|
||||||
|
let pattern = Regex::new(r"^1\.\d{1,2}(\.\d)?$").unwrap();
|
||||||
|
|
||||||
|
// Check if the provided version matches the pattern
|
||||||
|
pattern.is_match(version)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_header_forward_for_ip(headers: warp::http::HeaderMap) -> String {
|
||||||
|
// check if the header X-Forward-For exists and return the ip, if not, return an empty string
|
||||||
|
if let Some(forwarded_for) = headers.get("X-Forwarded-For") {
|
||||||
|
if let Ok(ip) = forwarded_for.to_str() {
|
||||||
|
// Extract the first IP address from the comma-separated list
|
||||||
|
if let Some(first_ip) = ip.split(',').next() {
|
||||||
|
return first_ip.trim().to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_path(modpackage: String, loadername: String, mcversion: String, modversion: String, headers: warp::http::HeaderMap, params: HashMap<String, String>) -> Result<impl warp::Reply, warp::Rejection> {
|
||||||
|
// Retrieve the IP from the header and check if it's valid
|
||||||
|
let mut client_ip = get_header_forward_for_ip(headers);
|
||||||
|
if !is_valid_ip(&client_ip) {
|
||||||
|
client_ip = params.get("ip").unwrap_or(&"".to_string()).to_string();
|
||||||
|
if !is_valid_ip(&client_ip) {
|
||||||
|
client_ip = "Not valid".to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if the minecraft version is valid
|
||||||
|
if !is_valid_minecraft_version(&mcversion) {
|
||||||
|
return Err(warp::reject::custom(BadRequestError));
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch the data
|
||||||
|
let data = fetch_data().await.unwrap();
|
||||||
|
|
||||||
|
// filter the data
|
||||||
|
// convert the raw list of data into a list of ModData and ModVersion
|
||||||
|
let mods_data: Vec<ModData> = serde_json::from_value(data).unwrap();
|
||||||
|
|
||||||
|
// get the mod data from the requested mod
|
||||||
|
let mod_data: ModData = mods_data.into_iter().find(|mod_data| mod_data.package == modpackage).unwrap();
|
||||||
|
|
||||||
|
|
||||||
|
// get the version data from the requested loader and remove the other loaders
|
||||||
|
let version_data: HashMap<std::string::String, ModVersion> = mod_data.versions.into_iter().find(|version_data| version_data.contains_key(&loadername)).unwrap().remove(&loadername).unwrap();
|
||||||
|
|
||||||
|
// turn version_data into an object of String: ModVersion key value pairs
|
||||||
|
let version_data: HashMap<std::string::String, ModVersion> = version_data.into_iter().map(|(key, value)| (key, value)).collect();
|
||||||
|
|
||||||
|
// get the version data for the current minecraft version
|
||||||
|
let version_data: ModVersion = version_data.get(&mcversion).unwrap().clone();
|
||||||
|
|
||||||
|
// get recommended and latest version
|
||||||
|
let recommended_version = version_data.recommended.clone();
|
||||||
|
let latest_version = version_data.latest.clone();
|
||||||
|
|
||||||
|
// determine whether the client is up to date
|
||||||
|
let mut up_to_date = false;
|
||||||
|
if modversion == recommended_version {
|
||||||
|
up_to_date = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// determine if telemetry is enabled by checking if the client_ip is valid
|
||||||
|
let mut telemetry = false;
|
||||||
|
if is_valid_ip(&client_ip) {
|
||||||
|
telemetry = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the response
|
||||||
|
let response = json!({
|
||||||
|
"promos": {
|
||||||
|
"latest": latest_version,
|
||||||
|
"recommended": recommended_version
|
||||||
|
},
|
||||||
|
"upToDate": up_to_date,
|
||||||
|
"telemetry_enabled": telemetry
|
||||||
|
});
|
||||||
|
|
||||||
|
//TODO: Add way to process telemetry data
|
||||||
|
|
||||||
|
// return the data
|
||||||
|
return Ok(warp::reply::json(&response));
|
||||||
}
|
}
|
46
src/v1/updates/minecraft/mods/telemetry/mod.rs
Normal file
46
src/v1/updates/minecraft/mods/telemetry/mod.rs
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
use std::fs::OpenOptions;
|
||||||
|
use std::io::{self, Write};
|
||||||
|
use std::collections::hash_map::DefaultHasher;
|
||||||
|
use std::hash::{Hash, Hasher};
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||||
|
struct IpInfo {
|
||||||
|
region: String,
|
||||||
|
// Add other fields as needed
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_ip_hash(ip: &str) -> u64 {
|
||||||
|
let mut hasher = DefaultHasher::new();
|
||||||
|
ip.hash(&mut hasher);
|
||||||
|
hasher.finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn log_ip_info(ip_address: &str, file_path: &str, mod_package: &str) -> io::Result<()> {
|
||||||
|
let ip_hash = get_ip_hash(ip_address);
|
||||||
|
|
||||||
|
let ip_info = match get_ip_info(ip_address) {
|
||||||
|
Ok(info) => info,
|
||||||
|
Err(err) => {
|
||||||
|
IpInfo { region: "Unknown".to_string() } // Default to "Unknown" in case of an error
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut file = OpenOptions::new()
|
||||||
|
.create(true)
|
||||||
|
.append(true)
|
||||||
|
.open(file_path)?;
|
||||||
|
|
||||||
|
writeln!(file, "{} {} {}", ip_hash, ip_info.region, mod_package)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let file_path = "ip_log.txt"; // Replace with your desired file path
|
||||||
|
|
||||||
|
// Example usage
|
||||||
|
match log_ip_info("8.8.8.8", file_path) {
|
||||||
|
Err(err) => eprintln!("Error: {}", err),
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue