diff --git a/.gitignore b/.gitignore
index 489f321..5afd1d1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,7 +6,7 @@ target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
-Cargo.lock
+# Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
@@ -99,3 +99,4 @@ fabric.properties
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
+!/Cargo.lock
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..13566b8
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/ALHP_API_RS.iml b/.idea/ALHP_API_RS.iml
new file mode 100644
index 0000000..6102194
--- /dev/null
+++ b/.idea/ALHP_API_RS.iml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..24735be
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..8306744
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..a9a9099
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,96 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "alhp_api"
+version = "0.1.0"
+dependencies = [
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
+
+[[package]]
+name = "memchr"
+version = "2.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.94"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
+
+[[package]]
+name = "serde"
+version = "1.0.219"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.219"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.140"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
+dependencies = [
+ "itoa",
+ "memchr",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..f6ad11a
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "alhp_api"
+version = "0.1.0"
+edition = "2024"
+authors = ["mpuchstein"]
+
+[dependencies]
+serde = { version = "1.0.219", features = ["derive"] }
+serde_json = "1.0.140"
+reqwest = { version = "0.12.15" , features = ["blocking"] }
+log = "0.4.27"
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..b3afe67
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,205 @@
+use log::{error, info};
+use reqwest::StatusCode;
+use serde::{Deserialize, Serialize};
+use std::error::Error;
+use std::fmt;
+use std::io::ErrorKind;
+
+const API_BASE_URL: &str = "https://api.alhp.dev";
+const API_PACKAGES_EXT: &str = "/packages?";
+const API_GENERAL_EXT: &str = "/stats?";
+
+pub mod packages {
+ use super::*;
+
+ #[derive(Debug, Clone, Copy, Serialize, Deserialize, Eq, PartialEq)]
+ #[serde(rename_all = "lowercase")]
+ pub enum PackageStatus {
+ Latest,
+ Failed,
+ Built,
+ Skipped,
+ Delayed,
+ Building,
+ Signing,
+ Unknown,
+ Queued,
+ }
+
+ impl fmt::Display for PackageStatus {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(
+ f,
+ "{}",
+ match self {
+ PackageStatus::Latest => "latest",
+ PackageStatus::Failed => "failed",
+ PackageStatus::Built => "build",
+ PackageStatus::Skipped => "skipped",
+ PackageStatus::Delayed => "delayed",
+ PackageStatus::Building => "building",
+ PackageStatus::Signing => "signing",
+ PackageStatus::Unknown => "unknown",
+ PackageStatus::Queued => "queued",
+ }
+ )
+ }
+ }
+
+ #[derive(Debug, Clone, Serialize, Deserialize)]
+ pub struct Package {
+ pub pkgbase: String,
+ pub repo: String,
+ pub split_packages: Vec,
+ pub status: PackageStatus,
+ pub skip_reason: Option,
+ pub lto: String,
+ pub debug_symbols: String,
+ pub arch_version: String,
+ pub repo_version: String,
+ pub build_date: Option,
+ pub peak_mem: Option,
+ }
+
+ #[derive(Debug, Clone, Serialize, Deserialize)]
+ pub struct PackageResponse {
+ pub total: usize,
+ pub offset: usize,
+ pub limit: usize,
+ pub packages: Vec,
+ }
+ #[derive(Debug, Serialize, Default)]
+ pub struct PackageRequest {
+ pub status: Vec,
+ pub pkgbase: Option,
+ pub exact: bool,
+ pub repo: Option,
+ pub offset: usize,
+ pub limit: usize,
+ }
+
+ impl PackageRequest {
+ fn query_string(&self) -> String {
+ let mut params = Vec::new();
+ for status in &self.status {
+ params.push(format!("status={}", status));
+ }
+ if let Some(pkgbase) = &self.pkgbase {
+ params.push(format!("pkgbase={}", pkgbase));
+ }
+ if self.exact {
+ params.push("exact".to_string());
+ }
+ if let Some(repo) = &self.repo {
+ params.push(format!("repo={}", repo));
+ }
+ params.push(format!("offset={}", self.offset));
+ params.push(format!("limit={}", self.limit));
+ params.join("&")
+ }
+ pub fn response(&self) -> Result> {
+ let query_url = format!(
+ "{}{}{}",
+ API_BASE_URL,
+ API_PACKAGES_EXT,
+ self.query_string()
+ );
+ info!("Fetching URL: {}", query_url);
+ let response = reqwest::blocking::get(query_url)?;
+ match response.status() {
+ StatusCode::OK => {
+ let response = response.text()?;
+ match serde_json::from_str(&response) {
+ Ok(pkgResponse) => Ok(pkgResponse),
+ Err(e) => {
+ error!("Failed to deserialize JSON: {}", e);
+ error!("Response body: {}", response);
+ Err(Box::new(e))
+ }
+ }
+ }
+ StatusCode::NOT_FOUND => {
+ info!("No packages found");
+ Ok(PackageResponse {
+ total: 0,
+ offset: 0,
+ limit: 0,
+ packages: vec![],
+ })
+ }
+ StatusCode::INTERNAL_SERVER_ERROR => {
+ error!("Internal server error");
+ Err(Box::new(std::io::Error::new(
+ ErrorKind::ConnectionAborted,
+ "Internal server error",
+ )))
+ }
+ _ => {
+ error!("Unexpected status: {}", response.status());
+ Err(Box::new(std::io::Error::new(
+ ErrorKind::Unsupported,
+ "Unexpected server response"
+ )))
+ }
+ }
+ }
+ }
+}
+
+pub mod general {
+ use super::*;
+
+ #[derive(Debug, Clone, Serialize, Deserialize)]
+ pub struct LtoStats {
+ pub enabled: usize,
+ pub disabled: usize,
+ pub unknown: usize,
+ }
+
+ #[derive(Debug, Clone, Serialize, Deserialize)]
+ pub struct GeneralResponse {
+ pub failed: usize,
+ pub skipped: usize,
+ pub latest: usize,
+ pub queued: usize,
+ pub lto: LtoStats,
+ }
+
+ #[derive(Debug, Clone, Serialize, Deserialize)]
+ pub struct GeneralRequest {
+ //No params yet.
+ }
+ impl GeneralRequest {
+ pub fn response(&self) -> Result> {
+ let query_url = format!("{}{}", API_BASE_URL, API_GENERAL_EXT);
+ let response = reqwest::blocking::get(query_url)?;
+ match response.status() {
+ StatusCode::OK => {
+ let response = response.text()?;
+ match serde_json::from_str(&response) {
+ Ok(genResponse) => Ok(genResponse),
+ Err(e) => {
+ error!("Failed to deserialize JSON: {}", e);
+ error!("Response body: {}", response);
+ Err(Box::new(e))
+ }
+ }
+ }
+ StatusCode::INTERNAL_SERVER_ERROR => {
+ error!("Internal server error");
+ Err(Box::new(std::io::Error::new(
+ ErrorKind::ConnectionAborted,
+ "Internal server error",
+ )))
+ }
+ _ => {
+ error!("Unexpected status: {}", response.status());
+ Err(Box::new(std::io::Error::new(
+ ErrorKind::Unsupported,
+ "Unexpected server response"
+ )))
+ }
+ }
+ }
+ }
+}
\ No newline at end of file