Compare commits

...

3 Commits

Author SHA1 Message Date
5d21a8ad9e chore: Bump 2026-02-09 17:12:29 +01:00
7738043a80 feat: Reporting 2026-02-09 17:11:49 +01:00
7090595505 feat: Reporting 2026-02-09 17:11:48 +01:00
8 changed files with 115 additions and 5 deletions

2
Cargo.lock generated
View File

@@ -357,7 +357,7 @@ checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
[[package]] [[package]]
name = "jecna_supl_client" name = "jecna_supl_client"
version = "0.2.1" version = "0.2.2"
dependencies = [ dependencies = [
"minreq", "minreq",
"serde", "serde",

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "jecna_supl_client" name = "jecna_supl_client"
version = "0.2.1" version = "0.2.2"
edition = "2024" edition = "2024"
[dependencies] [dependencies]

View File

@@ -6,7 +6,7 @@ plugins {
} }
group = "cz.jzitnik" group = "cz.jzitnik"
version = "0.2.1" version = "0.2.2"
repositories { repositories {
mavenCentral() mavenCentral()

View File

@@ -6,7 +6,7 @@
<groupId>cz.jzitnik</groupId> <groupId>cz.jzitnik</groupId>
<artifactId>jecna-supl-client</artifactId> <artifactId>jecna-supl-client</artifactId>
<version>0.2.1</version> <version>0.2.2</version>
<name>Jecna Supl Client</name> <name>Jecna Supl Client</name>
<description>Kotlin bindings for the Jecna Supl Rust library</description> <description>Kotlin bindings for the Jecna Supl Rust library</description>
<url>https://gitea.jzitnik.dev/jzitnik/jecna-supl-client</url> <url>https://gitea.jzitnik.dev/jzitnik/jecna-supl-client</url>

View File

@@ -1,3 +1,5 @@
use serde::Deserialize;
use crate::models::{ApiResponse, SuplError}; use crate::models::{ApiResponse, SuplError};
pub fn fetch_api(provider_url: &str) -> Result<ApiResponse, SuplError> { pub fn fetch_api(provider_url: &str) -> Result<ApiResponse, SuplError> {
@@ -14,3 +16,34 @@ pub fn fetch_api(provider_url: &str) -> Result<ApiResponse, SuplError> {
reason: e.to_string(), reason: e.to_string(),
}) })
} }
#[derive(Deserialize)]
struct StatusResponse {
working: bool,
message: Option<String>,
}
pub fn test_api(provider_url: &str) -> Result<(), SuplError> {
let trimmed = provider_url.trim_end_matches('/');
let url = format!("{}/status", trimmed);
let status: StatusResponse = minreq::get(&url)
.send()
.map_err(|e| SuplError::NetworkError {
reason: e.to_string(),
})?
.json()
.map_err(|e| SuplError::ParseError {
reason: e.to_string(),
})?;
if status.working {
Ok(())
} else {
Err(SuplError::ParseError {
reason: status
.message
.unwrap_or_else(|| "Provider reported not working".to_string()),
})
}
}

View File

@@ -3,11 +3,14 @@ mod models;
mod schedule; mod schedule;
mod teacher_absence; mod teacher_absence;
mod all; mod all;
mod report;
use std::sync::RwLock; use std::sync::RwLock;
pub use models::*; pub use models::*;
use crate::report::{report_impl, ReportLocation};
uniffi::setup_scaffolding!(); uniffi::setup_scaffolding!();
#[derive(uniffi::Object)] #[derive(uniffi::Object)]
@@ -31,16 +34,29 @@ impl JecnaSuplClient {
pub fn get_schedule(&self, class_name: String) -> Result<SuplResult, SuplError> { pub fn get_schedule(&self, class_name: String) -> Result<SuplResult, SuplError> {
let provider = self.provider_url.read().unwrap(); let provider = self.provider_url.read().unwrap();
api::test_api(&provider)?;
schedule::get_schedule_impl(&provider, class_name) schedule::get_schedule_impl(&provider, class_name)
} }
pub fn get_teacher_absence(&self) -> Result<TeacherAbsenceResult, SuplError> { pub fn get_teacher_absence(&self) -> Result<TeacherAbsenceResult, SuplError> {
let provider = self.provider_url.read().unwrap(); let provider = self.provider_url.read().unwrap();
api::test_api(&provider)?;
teacher_absence::get_teacher_absence_impl(&provider) teacher_absence::get_teacher_absence_impl(&provider)
} }
pub fn get_all(&self) -> Result<ApiResponse, SuplError> { pub fn get_all(&self) -> Result<ApiResponse, SuplError> {
let provider = self.provider_url.read().unwrap(); let provider = self.provider_url.read().unwrap();
api::test_api(&provider)?;
all::get_all_impl(&provider) all::get_all_impl(&provider)
} }
pub fn report(&self, content: String, class: String, report_location: ReportLocation) -> Result<(), SuplError> {
let provider = self.provider_url.read().unwrap();
api::test_api(&provider)?;
report_impl(&provider, content, class, report_location)
}
} }

View File

@@ -91,7 +91,7 @@ pub struct DailyData {
#[serde(rename = "takesPlace")] #[serde(rename = "takesPlace")]
pub takes_place: String, pub takes_place: String,
#[serde(rename = "reservedRooms")] #[serde(rename = "reservedRooms")]
pub reserved_rooms: Vec<String>, pub reserved_rooms: Vec<Option<String>>,
} }
#[derive(Deserialize, Debug, uniffi::Record, Clone)] #[derive(Deserialize, Debug, uniffi::Record, Clone)]

61
src/report.rs Normal file
View File

@@ -0,0 +1,61 @@
use serde::Serialize;
use crate::SuplError;
#[derive(uniffi::Enum)]
pub enum ReportLocation {
Timetable,
TeacherAbsence,
TakesPlace
}
impl ReportLocation {
fn to_str(&self) -> &'static str {
match self {
ReportLocation::Timetable => "TIMETABLE",
ReportLocation::TeacherAbsence => "ABSENCE",
ReportLocation::TakesPlace => "TAKES_PLACE"
}
}
}
#[derive(Serialize)]
struct ReportRequest {
content: String,
class: String,
location: String,
}
pub fn report_impl(
provider: &str,
content: String,
class: String,
report_location: ReportLocation,
) -> Result<(), SuplError> {
let trimmed = provider.trim_end_matches('/');
let url = format!("{}/versioned/v3", trimmed);
let body = ReportRequest {
content,
class,
location: report_location.to_str().to_string(),
};
let json = serde_json::to_string(&body)
.map_err(|e| SuplError::ParseError { reason: e.to_string() })?;
let response = minreq::post(url)
.with_header("Content-Type", "application/json")
.with_body(json)
.send()
.map_err(|e| SuplError::NetworkError { reason: e.to_string() })?;
if response.status_code >= 400 {
return Err(SuplError::RuntimeError {
reason: format!("Server returned HTTP {}", response.status_code),
});
}
Ok(())
}