This commit is contained in:
che
2026-07-28 08:53:19 +05:00
parent 2dd7d33c8b
commit e79bbbfcc4
25 changed files with 1303 additions and 148 deletions
+2
View File
@@ -1196,7 +1196,9 @@ version = "0.1.0"
dependencies = [
"che-orm",
"che-tauri",
"jni",
"reqwest",
"rustls-platform-verifier",
"serde",
"serde_json",
"tauri",
+2
View File
@@ -27,3 +27,5 @@ serde = { version = "1", features = ["derive"] }
serde_json = "1"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
reqwest = { version = "0.13", default-features = false, features = ["json", "query", "rustls"] }
jni = "0.21"
rustls-platform-verifier = "0.6"
+34 -1
View File
@@ -1,4 +1,5 @@
import java.util.Properties
import groovy.json.JsonSlurper
plugins {
id("com.android.application")
@@ -53,11 +54,19 @@ android {
}
}
repositories {
maven {
url = uri(rustlsPlatformVerifierMavenDir())
metadataSources.artifact()
}
}
rust {
rootDirRel = "../../../"
}
dependencies {
implementation("rustls:rustls-platform-verifier:latest.release")
implementation("androidx.webkit:webkit:1.14.0")
implementation("androidx.appcompat:appcompat:1.7.1")
implementation("androidx.activity:activity-ktx:1.10.1")
@@ -68,4 +77,28 @@ dependencies {
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.0")
}
apply(from = "tauri.build.gradle.kts")
apply(from = "tauri.build.gradle.kts")
fun rustlsPlatformVerifierMavenDir(): String {
val manifestPath = providers.exec {
commandLine(
"cargo",
"metadata",
"--format-version",
"1",
"--filter-platform",
"aarch64-linux-android",
"--manifest-path",
rootProject.file("../../Cargo.toml").absolutePath,
)
}.standardOutput.asText.get()
val metadata = JsonSlurper().parseText(manifestPath) as Map<*, *>
val packages = metadata["packages"] as List<*>
val rustlsAndroidPackage = packages
.map { it as Map<*, *> }
.first { it["name"] == "rustls-platform-verifier-android" }
val rustlsManifest = file(rustlsAndroidPackage["manifest_path"] as String)
return File(rustlsManifest.parentFile, "maven").path
}
@@ -1,10 +1,20 @@
package com.che.ewa_mobile
import android.content.Context
import android.os.Bundle
import androidx.activity.enableEdgeToEdge
class MainActivity : TauriActivity() {
companion object {
init {
System.loadLibrary("ewa_mobile_lib")
}
@JvmStatic external fun initRustlsPlatformVerifier(context: Context)
}
override fun onCreate(savedInstanceState: Bundle?) {
initRustlsPlatformVerifier(applicationContext)
enableEdgeToEdge()
super.onCreate(savedInstanceState)
}
@@ -0,0 +1,31 @@
use che_tauri::{Filter, FilterSet};
use super::models::{Memo, MemoCategory};
static MEMO_FILTERS: &[Filter] = &[
Filter::exact("id"),
Filter::contains("text"),
Filter::exact("date"),
Filter::exact("priority"),
Filter::exact("archive"),
Filter::exact("cancel"),
Filter::exact_source("recipient", "recipient_id").remote("recipient"),
Filter::exact_source("sender", "sender_id").remote("sender"),
Filter::exact_source("category", "category_id").remote("category"),
Filter::exact_source("project", "project_id").remote("project"),
];
static MEMO_CATEGORY_FILTERS: &[Filter] = &[
Filter::exact("id"),
Filter::exact("name"),
Filter::contains("name"),
Filter::exact("code"),
];
pub fn memo_filterset() -> FilterSet<Memo> {
FilterSet::new(MEMO_FILTERS).remote_ordering("order_by")
}
pub fn memo_category_filterset() -> FilterSet<MemoCategory> {
FilterSet::new(MEMO_CATEGORY_FILTERS).remote_ordering("order_by")
}
+32
View File
@@ -0,0 +1,32 @@
pub mod filters;
pub mod models;
pub mod serializers;
use che_tauri::{AppModule, ModuleContext};
pub fn module() -> InternalmemoappModule {
InternalmemoappModule
}
pub struct InternalmemoappModule;
impl AppModule for InternalmemoappModule {
fn name(&self) -> &'static str {
"internalmemoapp"
}
fn init(&self, ctx: &mut ModuleContext) {
ctx.mapped_remote_resource::<models::Memo>(
"memo",
"/api/internalmemo/memo/",
serializers::memo_serializer(),
filters::memo_filterset(),
);
ctx.mapped_remote_resource::<models::MemoCategory>(
"memo_category",
"/api/internalmemo/category/",
serializers::memo_category_serializer(),
filters::memo_category_filterset(),
);
}
}
@@ -0,0 +1,53 @@
use che_orm::Model;
use crate::apps::{personemanagment::models::Employee, projectapp::models::Project};
#[derive(Debug, Clone, Model)]
#[model(table = "internal_memo")]
pub struct Memo {
#[field(primary_key)]
pub id: i64,
#[field(foreign_key = Employee)]
pub recipient_id: Option<i64>,
#[field(foreign_key = Employee)]
pub sender_id: Option<i64>,
#[field(foreign_key = MemoCategory)]
pub category_id: Option<i64>,
#[field(foreign_key = Project)]
pub project_id: Option<i64>,
pub task_set: Option<String>,
pub priority_display: String,
pub absolute_url: String,
pub date: String,
pub my_approve: String,
pub text: String,
pub resalution: String,
pub priority: String,
pub archive: bool,
pub cancel: bool,
pub date_start: Option<String>,
pub date_end: Option<String>,
pub value: f64,
pub archive_s: bool,
pub current_step: i64,
pub subject: Option<String>,
pub employee_acl: Option<String>,
}
#[derive(Debug, Clone, Model)]
#[model(table = "internal_memo_category")]
pub struct MemoCategory {
#[field(primary_key)]
pub id: i64,
pub name: String,
pub template: String,
pub template_text: String,
pub code: String,
pub in_month_limit: bool,
}
@@ -0,0 +1,80 @@
use che_tauri::{Field, ModelSerializer, RelatedModel};
use crate::apps::{
personemanagment::{models::Employee, serializers::employee_serializer},
projectapp::{models::Project, serializers::project_serializer},
};
use super::models::{Memo, MemoCategory};
static MEMO_FIELDS: &[Field] = &[
Field::new("id").read_only(),
Field::new("recipient_id")
.source("recipient")
.required(false)
.nullable(),
Field::new("sender_id")
.source("sender")
.required(false)
.nullable(),
Field::new("category_id")
.source("category")
.required(false)
.nullable(),
Field::new("project_id")
.source("project")
.required(false)
.nullable(),
Field::json("task_set")
.ts_type("unknown[]")
.required(false)
.nullable(),
Field::new("priority_display").source("get_priority_display"),
Field::new("absolute_url").source("get_absolute_url"),
Field::new("date"),
Field::new("my_approve"),
Field::new("text"),
Field::new("resalution"),
Field::new("priority"),
Field::new("archive"),
Field::new("cancel"),
Field::new("date_start").required(false).nullable(),
Field::new("date_end").required(false).nullable(),
Field::new("value"),
Field::new("archive_s"),
Field::new("current_step"),
Field::json("subject")
.ts_type("number[]")
.required(false)
.nullable(),
Field::json("employee_acl")
.ts_type("number[]")
.required(false)
.nullable(),
Field::related("recipient", "recipient_id", &EMPLOYEE_RELATION),
Field::related("sender", "sender_id", &EMPLOYEE_RELATION),
Field::related("category", "category_id", &MEMO_CATEGORY_RELATION),
Field::related("project", "project_id", &PROJECT_RELATION),
];
static MEMO_CATEGORY_FIELDS: &[Field] = &[
Field::new("id").read_only(),
Field::new("name"),
Field::new("template"),
Field::new("template_text"),
Field::new("code"),
Field::new("in_month_limit"),
];
static EMPLOYEE_RELATION: RelatedModel<Employee> = RelatedModel::new(employee_serializer);
static PROJECT_RELATION: RelatedModel<Project> = RelatedModel::new(project_serializer);
static MEMO_CATEGORY_RELATION: RelatedModel<MemoCategory> =
RelatedModel::new(memo_category_serializer);
pub fn memo_serializer() -> ModelSerializer<Memo> {
ModelSerializer::new(MEMO_FIELDS)
}
pub fn memo_category_serializer() -> ModelSerializer<MemoCategory> {
ModelSerializer::new(MEMO_CATEGORY_FIELDS)
}
+2
View File
@@ -1,4 +1,5 @@
pub mod frcapp;
pub mod internalmemoapp;
pub mod personemanagment;
pub mod projectapp;
pub mod supplyapp;
@@ -14,5 +15,6 @@ pub fn installed_apps() -> InstalledApps {
.add(personemanagment::module())
.add(contractapp::module())
.add(supplyapp::module())
.add(internalmemoapp::module())
}
pub mod contractapp;
@@ -15,6 +15,7 @@ static TASK_FILTERS: &[Filter] = &[
Filter::contains("text"),
Filter::remote_only("contract"),
Filter::remote_only("bill"),
Filter::remote_only("memo"),
Filter::remote_only("doer"),
Filter::remote_only("author"),
Filter::exact("archive"),
+26 -3
View File
@@ -1,7 +1,10 @@
pub mod apps;
pub mod sync;
use std::path::{Path, PathBuf};
use std::{
path::{Path, PathBuf},
time::Duration,
};
use che_orm::SqliteBackend;
use che_tauri::{
@@ -13,6 +16,18 @@ use tauri_plugin_opener::OpenerExt;
use crate::sync::SyncContractsResult;
#[cfg(target_os = "android")]
#[no_mangle]
pub unsafe extern "system" fn Java_com_che_ewa_1mobile_MainActivity_initRustlsPlatformVerifier(
mut env: jni::JNIEnv,
_class: jni::objects::JClass,
context: jni::objects::JObject,
) {
if let Err(error) = rustls_platform_verifier::android::init_with_env(&mut env, context) {
eprintln!("failed to initialize rustls platform verifier: {error}");
}
}
#[cfg(target_os = "android")]
const REMOTE_BASE_URL: &str = "http://10.0.2.2:8000";
@@ -89,6 +104,14 @@ fn validate_settings(settings: &AppSettings) -> Result<(), ApiError> {
Ok(())
}
pub(crate) fn remote_http_client() -> reqwest::Client {
reqwest::Client::builder()
.timeout(Duration::from_secs(15))
.connect_timeout(Duration::from_secs(5))
.build()
.expect("failed to build reqwest client")
}
fn settings_path(app_data_dir: &Path) -> PathBuf {
app_data_dir.join("settings.json")
}
@@ -219,7 +242,7 @@ async fn current_employee(api: tauri::State<'_, TauriApi>) -> Result<CurrentEmpl
.auth_token()
.ok_or_else(|| ApiError::new("not_authenticated", "authentication token is missing"))?;
let response = reqwest::Client::new()
let response = remote_http_client()
.get(format!(
"{}/api/persone/employee/who_im/",
remote.base_url.trim_end_matches('/')
@@ -322,7 +345,7 @@ async fn cache_remote_file(
.auth_token()
.ok_or_else(|| ApiError::new("not_authenticated", "authentication token is missing"))?;
let response = reqwest::Client::new()
let response = remote_http_client()
.get(file_url)
.header(reqwest::header::AUTHORIZATION, format!("Token {token}"))
.send()
+3 -1
View File
@@ -4,6 +4,8 @@ use che_orm::__private::sqlx;
use che_tauri::{ApiError, TauriApi};
use serde::Deserialize;
use crate::remote_http_client;
#[derive(Debug, serde::Serialize)]
pub struct SyncContractsResult {
pub synced: usize,
@@ -167,7 +169,7 @@ pub async fn sync_contracts(
let remote = state
.remote_config()
.ok_or_else(|| ApiError::bad_request("sync requires [remote].base_url config"))?;
let client = reqwest::Client::new();
let client = remote_http_client();
sync_contract_categories(api, &client, &token, &remote.base_url).await?;
let mut next_url = Some(format!(