合并冲突

This commit is contained in:
Yukino_fox
2026-04-11 12:21:15 +08:00
parent 4db3238a63
commit ee03046bbd
4 changed files with 191 additions and 141 deletions
+9 -6
View File
@@ -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 -2
View File
@@ -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")]
+179 -132
View File
@@ -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()))
+1 -1
View File
@@ -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()