mirror of
https://github.com/SECTL/SecScore.git
synced 2026-07-19 19:04:23 +08:00
合并冲突
This commit is contained in:
@@ -362,8 +362,11 @@ pub async fn oauth_exchange_code(
|
|||||||
callback_url: String,
|
callback_url: String,
|
||||||
state: State<'_, Arc<RwLock<AppState>>>,
|
state: State<'_, Arc<RwLock<AppState>>>,
|
||||||
) -> Result<IpcResponse<OAuthTokenResponse>, String> {
|
) -> Result<IpcResponse<OAuthTokenResponse>, String> {
|
||||||
println!("[OAuth] 换取令牌 - code: {}, platform_id: {}, callback_url: {}", code, platform_id, callback_url);
|
println!(
|
||||||
|
"[OAuth] 换取令牌 - code: {}, platform_id: {}, callback_url: {}",
|
||||||
|
code, platform_id, callback_url
|
||||||
|
);
|
||||||
|
|
||||||
let state_guard = state.read();
|
let state_guard = state.read();
|
||||||
let client = &state_guard.http_client;
|
let client = &state_guard.http_client;
|
||||||
let params = [
|
let params = [
|
||||||
@@ -411,7 +414,7 @@ pub async fn oauth_revoke_token(
|
|||||||
) -> Result<IpcResponse<()>, String> {
|
) -> Result<IpcResponse<()>, String> {
|
||||||
let state_guard = state.read();
|
let state_guard = state.read();
|
||||||
let client = &state_guard.http_client;
|
let client = &state_guard.http_client;
|
||||||
|
|
||||||
let mut payload = serde_json::json!({
|
let mut payload = serde_json::json!({
|
||||||
"token": token,
|
"token": token,
|
||||||
"client_id": platform_id,
|
"client_id": platform_id,
|
||||||
@@ -449,7 +452,7 @@ pub async fn oauth_introspect_token(
|
|||||||
) -> Result<IpcResponse<OAuthIntrospectResponse>, String> {
|
) -> Result<IpcResponse<OAuthIntrospectResponse>, String> {
|
||||||
let state_guard = state.read();
|
let state_guard = state.read();
|
||||||
let client = &state_guard.http_client;
|
let client = &state_guard.http_client;
|
||||||
|
|
||||||
let response = client
|
let response = client
|
||||||
.post("https://sectl.top/api/oauth/introspect")
|
.post("https://sectl.top/api/oauth/introspect")
|
||||||
.json(&serde_json::json!({
|
.json(&serde_json::json!({
|
||||||
@@ -484,7 +487,7 @@ pub async fn oauth_get_user_info(
|
|||||||
) -> Result<IpcResponse<OAuthUserInfo>, String> {
|
) -> Result<IpcResponse<OAuthUserInfo>, String> {
|
||||||
let state_guard = state.read();
|
let state_guard = state.read();
|
||||||
let client = &state_guard.http_client;
|
let client = &state_guard.http_client;
|
||||||
|
|
||||||
let response = client
|
let response = client
|
||||||
.get("https://sectl.top/api/oauth/userinfo")
|
.get("https://sectl.top/api/oauth/userinfo")
|
||||||
.header("Authorization", format!("Bearer {}", access_token))
|
.header("Authorization", format!("Bearer {}", access_token))
|
||||||
@@ -517,7 +520,7 @@ pub async fn oauth_refresh_token(
|
|||||||
) -> Result<IpcResponse<OAuthTokenResponse>, String> {
|
) -> Result<IpcResponse<OAuthTokenResponse>, String> {
|
||||||
let state_guard = state.read();
|
let state_guard = state.read();
|
||||||
let client = &state_guard.http_client;
|
let client = &state_guard.http_client;
|
||||||
|
|
||||||
let response = client
|
let response = client
|
||||||
.post("https://sectl.top/api/oauth/token")
|
.post("https://sectl.top/api/oauth/token")
|
||||||
.json(&serde_json::json!({
|
.json(&serde_json::json!({
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ use parking_lot::RwLock;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::Value as JsonValue;
|
use serde_json::Value as JsonValue;
|
||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
use std::sync::Arc;
|
|
||||||
use tauri::{AppHandle, Emitter, State};
|
|
||||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use tauri::{AppHandle, Emitter, State};
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
use winreg::enums::HKEY_LOCAL_MACHINE;
|
use winreg::enums::HKEY_LOCAL_MACHINE;
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use chrono::{DateTime, Months, Utc};
|
use chrono::{DateTime, Months, Utc};
|
||||||
use sea_orm::{
|
use sea_orm::{
|
||||||
ActiveModelTrait, ColumnTrait, ConnectionTrait, DatabaseConnection, EntityTrait,
|
ActiveModelTrait, ColumnTrait, ConnectionTrait, DatabaseConnection, EntityTrait, QueryFilter,
|
||||||
QueryFilter, Set, Statement, TransactionTrait,
|
Set, Statement, TransactionTrait,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::{json, Value as JsonValue};
|
use serde_json::{json, Value as JsonValue};
|
||||||
@@ -497,7 +497,9 @@ fn normalize_execution_config(
|
|||||||
Ok(config)
|
Ok(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn normalize_filter_config(mut config: AutoScoreFilterConfig) -> Result<AutoScoreFilterConfig, String> {
|
fn normalize_filter_config(
|
||||||
|
mut config: AutoScoreFilterConfig,
|
||||||
|
) -> Result<AutoScoreFilterConfig, String> {
|
||||||
config.groups = dedupe_trimmed_strings(config.groups);
|
config.groups = dedupe_trimmed_strings(config.groups);
|
||||||
config.grades = dedupe_trimmed_strings(config.grades);
|
config.grades = dedupe_trimmed_strings(config.grades);
|
||||||
if let (Some(min_score), Some(max_score)) = (config.min_score, config.max_score) {
|
if let (Some(min_score), Some(max_score)) = (config.min_score, config.max_score) {
|
||||||
@@ -513,7 +515,10 @@ fn normalize_filter_config(mut config: AutoScoreFilterConfig) -> Result<AutoScor
|
|||||||
fn build_trigger_tree_from_triggers(triggers: &[AutoScoreTrigger]) -> JsonValue {
|
fn build_trigger_tree_from_triggers(triggers: &[AutoScoreTrigger]) -> JsonValue {
|
||||||
JsonValue::Object(
|
JsonValue::Object(
|
||||||
[
|
[
|
||||||
("id".to_string(), JsonValue::String(Uuid::new_v4().to_string())),
|
(
|
||||||
|
"id".to_string(),
|
||||||
|
JsonValue::String(Uuid::new_v4().to_string()),
|
||||||
|
),
|
||||||
("type".to_string(), JsonValue::String("group".to_string())),
|
("type".to_string(), JsonValue::String("group".to_string())),
|
||||||
(
|
(
|
||||||
"properties".to_string(),
|
"properties".to_string(),
|
||||||
@@ -588,7 +593,10 @@ fn build_trigger_rule_node(trigger: &AutoScoreTrigger) -> JsonValue {
|
|||||||
|
|
||||||
JsonValue::Object(
|
JsonValue::Object(
|
||||||
[
|
[
|
||||||
("id".to_string(), JsonValue::String(Uuid::new_v4().to_string())),
|
(
|
||||||
|
"id".to_string(),
|
||||||
|
JsonValue::String(Uuid::new_v4().to_string()),
|
||||||
|
),
|
||||||
("type".to_string(), JsonValue::String("rule".to_string())),
|
("type".to_string(), JsonValue::String("rule".to_string())),
|
||||||
(
|
(
|
||||||
"properties".to_string(),
|
"properties".to_string(),
|
||||||
@@ -681,7 +689,10 @@ fn normalize_trigger_tree_group(node: JsonValue) -> Result<JsonValue, String> {
|
|||||||
"not": not,
|
"not": not,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
("children1".to_string(), JsonValue::Array(normalized_children)),
|
(
|
||||||
|
"children1".to_string(),
|
||||||
|
JsonValue::Array(normalized_children),
|
||||||
|
),
|
||||||
]
|
]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect(),
|
.collect(),
|
||||||
@@ -817,7 +828,11 @@ fn trigger_from_rule_node(node: &JsonValue) -> Result<AutoScoreTrigger, String>
|
|||||||
};
|
};
|
||||||
let trigger = AutoScoreTrigger {
|
let trigger = AutoScoreTrigger {
|
||||||
event: event.to_string(),
|
event: event.to_string(),
|
||||||
value: if score.trim().is_empty() { None } else { Some(score) },
|
value: if score.trim().is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(score)
|
||||||
|
},
|
||||||
};
|
};
|
||||||
normalize_trigger(trigger)
|
normalize_trigger(trigger)
|
||||||
}
|
}
|
||||||
@@ -1003,7 +1018,11 @@ fn parse_positive_i64(value: &JsonValue) -> Option<i64> {
|
|||||||
fn parse_non_negative_i64(value: &JsonValue) -> Option<i64> {
|
fn parse_non_negative_i64(value: &JsonValue) -> Option<i64> {
|
||||||
match value {
|
match value {
|
||||||
JsonValue::Number(number) => number.as_i64().filter(|parsed| *parsed >= 0),
|
JsonValue::Number(number) => number.as_i64().filter(|parsed| *parsed >= 0),
|
||||||
JsonValue::String(text) => text.trim().parse::<i64>().ok().filter(|parsed| *parsed >= 0),
|
JsonValue::String(text) => text
|
||||||
|
.trim()
|
||||||
|
.parse::<i64>()
|
||||||
|
.ok()
|
||||||
|
.filter(|parsed| *parsed >= 0),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1060,12 +1079,10 @@ fn stringify_interval_trigger_value(value: &IntervalTriggerValue) -> Option<Stri
|
|||||||
|
|
||||||
match unit {
|
match unit {
|
||||||
IntervalUnit::Minute => Some(amount.to_string()),
|
IntervalUnit::Minute => Some(amount.to_string()),
|
||||||
IntervalUnit::Day | IntervalUnit::Month => serde_json::to_string(
|
IntervalUnit::Day | IntervalUnit::Month => serde_json::to_string(&json!({
|
||||||
&json!({
|
"amount": amount,
|
||||||
"amount": amount,
|
"unit": unit,
|
||||||
"unit": unit,
|
}))
|
||||||
}),
|
|
||||||
)
|
|
||||||
.ok(),
|
.ok(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1097,9 +1114,7 @@ fn add_interval_to_time(
|
|||||||
IntervalUnit::Minute => {
|
IntervalUnit::Minute => {
|
||||||
base_time.checked_add_signed(chrono::Duration::minutes(*amount))
|
base_time.checked_add_signed(chrono::Duration::minutes(*amount))
|
||||||
}
|
}
|
||||||
IntervalUnit::Day => {
|
IntervalUnit::Day => base_time.checked_add_signed(chrono::Duration::days(*amount)),
|
||||||
base_time.checked_add_signed(chrono::Duration::days(*amount))
|
|
||||||
}
|
|
||||||
IntervalUnit::Month => {
|
IntervalUnit::Month => {
|
||||||
let months = u32::try_from(*amount).ok()?;
|
let months = u32::try_from(*amount).ok()?;
|
||||||
base_time.checked_add_months(Months::new(months))
|
base_time.checked_add_months(Months::new(months))
|
||||||
@@ -1305,7 +1320,9 @@ fn deserialize_batches(value: &JsonValue) -> Vec<AutoScoreExecutionBatch> {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn load_batches_from_settings(state: &SafeAppState) -> Result<Vec<AutoScoreExecutionBatch>, String> {
|
async fn load_batches_from_settings(
|
||||||
|
state: &SafeAppState,
|
||||||
|
) -> Result<Vec<AutoScoreExecutionBatch>, String> {
|
||||||
let state_guard = state.read();
|
let state_guard = state.read();
|
||||||
let db_conn = state_guard.db.read().clone();
|
let db_conn = state_guard.db.read().clone();
|
||||||
let mut settings = state_guard.settings.write();
|
let mut settings = state_guard.settings.write();
|
||||||
@@ -1334,7 +1351,9 @@ async fn save_batches_to_settings(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn query_execution_batches(state: &SafeAppState) -> Result<Vec<AutoScoreExecutionBatch>, String> {
|
pub async fn query_execution_batches(
|
||||||
|
state: &SafeAppState,
|
||||||
|
) -> Result<Vec<AutoScoreExecutionBatch>, String> {
|
||||||
let mut batches = load_batches_from_settings(state).await?;
|
let mut batches = load_batches_from_settings(state).await?;
|
||||||
batches.sort_by(|a, b| b.run_at.cmp(&a.run_at));
|
batches.sort_by(|a, b| b.run_at.cmp(&a.run_at));
|
||||||
Ok(batches)
|
Ok(batches)
|
||||||
@@ -1444,7 +1463,9 @@ async fn execute_rule(
|
|||||||
if let Some(max_runs) = rule.execution.max_runs_per_day {
|
if let Some(max_runs) = rule.execution.max_runs_per_day {
|
||||||
let today_runs = execution_batches
|
let today_runs = execution_batches
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|batch| batch.rule_id == rule.id && !batch.rolled_back && is_same_utc_day(&batch.run_at))
|
.filter(|batch| {
|
||||||
|
batch.rule_id == rule.id && !batch.rolled_back && is_same_utc_day(&batch.run_at)
|
||||||
|
})
|
||||||
.count() as i64;
|
.count() as i64;
|
||||||
if today_runs >= max_runs {
|
if today_runs >= max_runs {
|
||||||
return Ok(RuleExecutionStats::default());
|
return Ok(RuleExecutionStats::default());
|
||||||
@@ -1461,7 +1482,9 @@ async fn execute_rule(
|
|||||||
|
|
||||||
let mut daily_score_delta_used: i64 = execution_batches
|
let mut daily_score_delta_used: i64 = execution_batches
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|batch| batch.rule_id == rule.id && !batch.rolled_back && is_same_utc_day(&batch.run_at))
|
.filter(|batch| {
|
||||||
|
batch.rule_id == rule.id && !batch.rolled_back && is_same_utc_day(&batch.run_at)
|
||||||
|
})
|
||||||
.map(|batch| batch.score_delta_total.abs())
|
.map(|batch| batch.score_delta_total.abs())
|
||||||
.sum();
|
.sum();
|
||||||
|
|
||||||
@@ -1571,9 +1594,9 @@ async fn execute_rule(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn plan_actions(actions: &[AutoScoreAction]) -> Result<Vec<PlannedAction>, String> {
|
fn plan_actions(actions: &[AutoScoreAction]) -> Result<Vec<PlannedAction>, String> {
|
||||||
let mut planned = Vec::new();
|
let mut planned = Vec::new();
|
||||||
for action in actions {
|
for action in actions {
|
||||||
match action.event.as_str() {
|
match action.event.as_str() {
|
||||||
"add_score" => {
|
"add_score" => {
|
||||||
let delta = action
|
let delta = action
|
||||||
.value
|
.value
|
||||||
@@ -1594,57 +1617,62 @@ fn plan_actions(actions: &[AutoScoreAction]) -> Result<Vec<PlannedAction>, Strin
|
|||||||
planned.push(PlannedAction::SettleScore);
|
planned.push(PlannedAction::SettleScore);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
Ok(planned)
|
||||||
Ok(planned)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn execute_settlement(conn: &DatabaseConnection) -> Result<(), String> {
|
async fn execute_settlement(conn: &DatabaseConnection) -> Result<(), String> {
|
||||||
let backend = conn.get_database_backend();
|
let backend = conn.get_database_backend();
|
||||||
let count_stmt = Statement::from_string(
|
let count_stmt = Statement::from_string(
|
||||||
backend,
|
backend,
|
||||||
"SELECT COUNT(*) AS cnt FROM score_events WHERE settlement_id IS NULL",
|
"SELECT COUNT(*) AS cnt FROM score_events WHERE settlement_id IS NULL",
|
||||||
);
|
);
|
||||||
let count_row = conn.query_one(count_stmt).await.map_err(|e| e.to_string())?;
|
let count_row = conn
|
||||||
let Some(count_row) = count_row else {
|
.query_one(count_stmt)
|
||||||
return Ok(());
|
.await
|
||||||
};
|
.map_err(|e| e.to_string())?;
|
||||||
let unsettled_count = try_get_i64(&count_row, "cnt").unwrap_or(0);
|
let Some(count_row) = count_row else {
|
||||||
if unsettled_count <= 0 {
|
return Ok(());
|
||||||
return Ok(());
|
};
|
||||||
}
|
let unsettled_count = try_get_i64(&count_row, "cnt").unwrap_or(0);
|
||||||
|
if unsettled_count <= 0 {
|
||||||
let last_end_sql = match backend {
|
return Ok(());
|
||||||
sea_orm::DatabaseBackend::Sqlite => {
|
|
||||||
"SELECT end_time AS end_time FROM settlements ORDER BY julianday(end_time) DESC LIMIT 1"
|
|
||||||
}
|
}
|
||||||
sea_orm::DatabaseBackend::Postgres => {
|
|
||||||
"SELECT end_time AS end_time FROM settlements ORDER BY end_time DESC LIMIT 1"
|
|
||||||
}
|
|
||||||
_ => "SELECT end_time AS end_time FROM settlements ORDER BY end_time DESC LIMIT 1",
|
|
||||||
};
|
|
||||||
let min_event_sql =
|
|
||||||
"SELECT MIN(event_time) AS min_event_time FROM score_events WHERE settlement_id IS NULL";
|
|
||||||
|
|
||||||
let last_end = conn
|
let last_end_sql = match backend {
|
||||||
.query_one(Statement::from_string(backend, last_end_sql))
|
sea_orm::DatabaseBackend::Sqlite => {
|
||||||
.await
|
"SELECT end_time AS end_time FROM settlements ORDER BY julianday(end_time) DESC LIMIT 1"
|
||||||
.map_err(|e| e.to_string())?
|
}
|
||||||
.and_then(|row| try_get_string(&row, "end_time"));
|
sea_orm::DatabaseBackend::Postgres => {
|
||||||
|
"SELECT end_time AS end_time FROM settlements ORDER BY end_time DESC LIMIT 1"
|
||||||
|
}
|
||||||
|
_ => "SELECT end_time AS end_time FROM settlements ORDER BY end_time DESC LIMIT 1",
|
||||||
|
};
|
||||||
|
let min_event_sql =
|
||||||
|
"SELECT MIN(event_time) AS min_event_time FROM score_events WHERE settlement_id IS NULL";
|
||||||
|
|
||||||
let min_event_time = conn
|
let last_end = conn
|
||||||
.query_one(Statement::from_string(backend, min_event_sql))
|
.query_one(Statement::from_string(backend, last_end_sql))
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())?
|
.map_err(|e| e.to_string())?
|
||||||
.and_then(|row| try_get_string(&row, "min_event_time"));
|
.and_then(|row| try_get_string(&row, "end_time"));
|
||||||
|
|
||||||
let end_time = now_iso();
|
let min_event_time = conn
|
||||||
let start_time = last_end.or(min_event_time).unwrap_or_else(|| end_time.clone());
|
.query_one(Statement::from_string(backend, min_event_sql))
|
||||||
let created_at = end_time.clone();
|
.await
|
||||||
|
.map_err(|e| e.to_string())?
|
||||||
|
.and_then(|row| try_get_string(&row, "min_event_time"));
|
||||||
|
|
||||||
match backend {
|
let end_time = now_iso();
|
||||||
sea_orm::DatabaseBackend::Postgres => {
|
let start_time = last_end
|
||||||
let insert_stmt = Statement::from_string(
|
.or(min_event_time)
|
||||||
|
.unwrap_or_else(|| end_time.clone());
|
||||||
|
let created_at = end_time.clone();
|
||||||
|
|
||||||
|
match backend {
|
||||||
|
sea_orm::DatabaseBackend::Postgres => {
|
||||||
|
let insert_stmt = Statement::from_string(
|
||||||
backend,
|
backend,
|
||||||
format!(
|
format!(
|
||||||
"INSERT INTO settlements (start_time, end_time, created_at) VALUES ('{}', '{}', '{}') RETURNING id",
|
"INSERT INTO settlements (start_time, end_time, created_at) VALUES ('{}', '{}', '{}') RETURNING id",
|
||||||
@@ -1653,74 +1681,82 @@ async fn execute_settlement(conn: &DatabaseConnection) -> Result<(), String> {
|
|||||||
created_at.replace('\'', "''")
|
created_at.replace('\'', "''")
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
let row = conn
|
let row = conn
|
||||||
.query_one(insert_stmt)
|
.query_one(insert_stmt)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())?
|
.map_err(|e| e.to_string())?
|
||||||
.ok_or_else(|| "Failed to create settlement".to_string())?;
|
.ok_or_else(|| "Failed to create settlement".to_string())?;
|
||||||
let settlement_id = try_get_i32(&row, "id")
|
let settlement_id = try_get_i32(&row, "id")
|
||||||
.ok_or_else(|| "Failed to read settlement id".to_string())?;
|
.ok_or_else(|| "Failed to read settlement id".to_string())?;
|
||||||
|
|
||||||
let update_events_stmt = Statement::from_string(
|
let update_events_stmt = Statement::from_string(
|
||||||
backend,
|
backend,
|
||||||
format!(
|
format!(
|
||||||
"UPDATE score_events SET settlement_id = {} WHERE settlement_id IS NULL",
|
"UPDATE score_events SET settlement_id = {} WHERE settlement_id IS NULL",
|
||||||
settlement_id
|
settlement_id
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
conn.execute(update_events_stmt).await.map_err(|e| e.to_string())?;
|
conn.execute(update_events_stmt)
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
let update_students_stmt = Statement::from_string(
|
let update_students_stmt = Statement::from_string(
|
||||||
backend,
|
backend,
|
||||||
format!(
|
format!(
|
||||||
"UPDATE students SET score = 0, updated_at = '{}' ",
|
"UPDATE students SET score = 0, updated_at = '{}' ",
|
||||||
end_time.replace('\'', "''")
|
end_time.replace('\'', "''")
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
conn.execute(update_students_stmt).await.map_err(|e| e.to_string())?;
|
conn.execute(update_students_stmt)
|
||||||
}
|
.await
|
||||||
_ => {
|
.map_err(|e| e.to_string())?;
|
||||||
let insert_stmt = Statement::from_string(
|
}
|
||||||
backend,
|
_ => {
|
||||||
format!(
|
let insert_stmt = Statement::from_string(
|
||||||
|
backend,
|
||||||
|
format!(
|
||||||
"INSERT INTO settlements (start_time, end_time, created_at) VALUES ('{}', '{}', '{}')",
|
"INSERT INTO settlements (start_time, end_time, created_at) VALUES ('{}', '{}', '{}')",
|
||||||
start_time.replace('\'', "''"),
|
start_time.replace('\'', "''"),
|
||||||
end_time.replace('\'', "''"),
|
end_time.replace('\'', "''"),
|
||||||
created_at.replace('\'', "''")
|
created_at.replace('\'', "''")
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
conn.execute(insert_stmt).await.map_err(|e| e.to_string())?;
|
conn.execute(insert_stmt).await.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
let id_stmt = Statement::from_string(backend, "SELECT last_insert_rowid() AS id");
|
let id_stmt = Statement::from_string(backend, "SELECT last_insert_rowid() AS id");
|
||||||
let row = conn
|
let row = conn
|
||||||
.query_one(id_stmt)
|
.query_one(id_stmt)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())?
|
.map_err(|e| e.to_string())?
|
||||||
.ok_or_else(|| "Failed to read settlement id".to_string())?;
|
.ok_or_else(|| "Failed to read settlement id".to_string())?;
|
||||||
let settlement_id = try_get_i32(&row, "id")
|
let settlement_id = try_get_i32(&row, "id")
|
||||||
.ok_or_else(|| "Failed to read settlement id".to_string())?;
|
.ok_or_else(|| "Failed to read settlement id".to_string())?;
|
||||||
|
|
||||||
let update_events_stmt = Statement::from_string(
|
let update_events_stmt = Statement::from_string(
|
||||||
backend,
|
backend,
|
||||||
format!(
|
format!(
|
||||||
"UPDATE score_events SET settlement_id = {} WHERE settlement_id IS NULL",
|
"UPDATE score_events SET settlement_id = {} WHERE settlement_id IS NULL",
|
||||||
settlement_id
|
settlement_id
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
conn.execute(update_events_stmt).await.map_err(|e| e.to_string())?;
|
conn.execute(update_events_stmt)
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
let update_students_stmt = Statement::from_string(
|
let update_students_stmt = Statement::from_string(
|
||||||
backend,
|
backend,
|
||||||
format!(
|
format!(
|
||||||
"UPDATE students SET score = 0, updated_at = '{}'",
|
"UPDATE students SET score = 0, updated_at = '{}'",
|
||||||
end_time.replace('\'', "''")
|
end_time.replace('\'', "''")
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
conn.execute(update_students_stmt).await.map_err(|e| e.to_string())?;
|
conn.execute(update_students_stmt)
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.to_string())?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
@@ -1729,7 +1765,10 @@ struct TriggerEvalContext {
|
|||||||
sql_refs_by_query: HashMap<String, StudentRefs>,
|
sql_refs_by_query: HashMap<String, StudentRefs>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn collect_sql_queries_from_tree(node: &JsonValue, queries: &mut Vec<String>) -> Result<(), String> {
|
fn collect_sql_queries_from_tree(
|
||||||
|
node: &JsonValue,
|
||||||
|
queries: &mut Vec<String>,
|
||||||
|
) -> Result<(), String> {
|
||||||
let Some(node_type) = node.get("type").and_then(JsonValue::as_str) else {
|
let Some(node_type) = node.get("type").and_then(JsonValue::as_str) else {
|
||||||
return Err("Trigger tree node type is required".to_string());
|
return Err("Trigger tree node type is required".to_string());
|
||||||
};
|
};
|
||||||
@@ -1751,7 +1790,12 @@ fn collect_sql_queries_from_tree(node: &JsonValue, queries: &mut Vec<String>) ->
|
|||||||
trigger.event.as_str(),
|
trigger.event.as_str(),
|
||||||
"query_sql" | "student_query_sql" | "student_sql"
|
"query_sql" | "student_query_sql" | "student_sql"
|
||||||
) {
|
) {
|
||||||
if let Some(sql) = trigger.value.as_deref().map(str::trim).filter(|value| !value.is_empty()) {
|
if let Some(sql) = trigger
|
||||||
|
.value
|
||||||
|
.as_deref()
|
||||||
|
.map(str::trim)
|
||||||
|
.filter(|value| !value.is_empty())
|
||||||
|
{
|
||||||
queries.push(sql.to_string());
|
queries.push(sql.to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1866,7 +1910,9 @@ fn evaluate_trigger_tree_for_student(
|
|||||||
.to_string();
|
.to_string();
|
||||||
ctx.sql_refs_by_query
|
ctx.sql_refs_by_query
|
||||||
.get(&sql)
|
.get(&sql)
|
||||||
.map(|refs| refs.ids.contains(&student.id) || refs.names.contains(&student.name))
|
.map(|refs| {
|
||||||
|
refs.ids.contains(&student.id) || refs.names.contains(&student.name)
|
||||||
|
})
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
"student_score_gt" => trigger
|
"student_score_gt" => trigger
|
||||||
@@ -1890,8 +1936,8 @@ fn evaluate_trigger_tree_for_student(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn resolve_target_students(
|
async fn resolve_target_students(
|
||||||
conn: &DatabaseConnection,
|
conn: &DatabaseConnection,
|
||||||
rule: &AutoScoreRule,
|
rule: &AutoScoreRule,
|
||||||
) -> Result<Vec<students::Model>, String> {
|
) -> Result<Vec<students::Model>, String> {
|
||||||
let all_students = students::Entity::find()
|
let all_students = students::Entity::find()
|
||||||
.all(conn)
|
.all(conn)
|
||||||
@@ -1929,7 +1975,6 @@ async fn resolve_target_students(
|
|||||||
Ok(matched)
|
Ok(matched)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async fn is_student_pass_cooldown(
|
async fn is_student_pass_cooldown(
|
||||||
conn: &DatabaseConnection,
|
conn: &DatabaseConnection,
|
||||||
execution_batches: &[AutoScoreExecutionBatch],
|
execution_batches: &[AutoScoreExecutionBatch],
|
||||||
@@ -1953,16 +1998,18 @@ async fn is_student_pass_cooldown(
|
|||||||
let run_at = DateTime::parse_from_rfc3339(batch.run_at.as_str())
|
let run_at = DateTime::parse_from_rfc3339(batch.run_at.as_str())
|
||||||
.map(|value| value.with_timezone(&Utc))
|
.map(|value| value.with_timezone(&Utc))
|
||||||
.unwrap_or_else(|_| Utc::now() - chrono::Duration::days(3650));
|
.unwrap_or_else(|_| Utc::now() - chrono::Duration::days(3650));
|
||||||
if run_at >= cutoff && batch.affected_student_names.iter().any(|name| name == student_name)
|
if run_at >= cutoff
|
||||||
|
&& batch
|
||||||
|
.affected_student_names
|
||||||
|
.iter()
|
||||||
|
.any(|name| name == student_name)
|
||||||
{
|
{
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let prefix = format!("{}#{}:", AUTO_SCORE_REASON_PREFIX, rule.id);
|
let prefix = format!("{}#{}:", AUTO_SCORE_REASON_PREFIX, rule.id);
|
||||||
let since = cutoff
|
let since = cutoff.format("%Y-%m-%dT%H:%M:%S%.3fZ").to_string();
|
||||||
.format("%Y-%m-%dT%H:%M:%S%.3fZ")
|
|
||||||
.to_string();
|
|
||||||
|
|
||||||
let exists = score_events::Entity::find()
|
let exists = score_events::Entity::find()
|
||||||
.filter(score_events::Column::StudentName.eq(student_name.to_string()))
|
.filter(score_events::Column::StudentName.eq(student_name.to_string()))
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ impl AppState {
|
|||||||
let logger = Arc::new(RwLock::new(LoggerService::new()));
|
let logger = Arc::new(RwLock::new(LoggerService::new()));
|
||||||
let data = Arc::new(RwLock::new(DataService::new()));
|
let data = Arc::new(RwLock::new(DataService::new()));
|
||||||
let db = Arc::new(RwLock::new(None));
|
let db = Arc::new(RwLock::new(None));
|
||||||
|
|
||||||
let http_client = Client::builder()
|
let http_client = Client::builder()
|
||||||
.timeout(std::time::Duration::from_secs(30))
|
.timeout(std::time::Duration::from_secs(30))
|
||||||
.build()
|
.build()
|
||||||
|
|||||||
Reference in New Issue
Block a user