Initial Commit
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
use axum::{
|
||||
extract::{Multipart, Path, State},
|
||||
Json,
|
||||
};
|
||||
|
||||
use crate::phd2::parse_phd2_log;
|
||||
|
||||
use super::{AppError, AppState};
|
||||
|
||||
pub async fn upload_phd2(
|
||||
State(state): State<AppState>,
|
||||
mut multipart: Multipart,
|
||||
) -> Result<Json<serde_json::Value>, AppError> {
|
||||
let mut filename = String::new();
|
||||
let mut content = String::new();
|
||||
|
||||
while let Some(field) = multipart.next_field().await.map_err(|e| AppError::Internal(e.to_string()))? {
|
||||
let name = field.name().unwrap_or("").to_string();
|
||||
match name.as_str() {
|
||||
"file" => {
|
||||
filename = field.file_name().unwrap_or("phd2.log").to_string();
|
||||
let bytes = field.bytes().await.map_err(|e| AppError::Internal(e.to_string()))?;
|
||||
content = String::from_utf8_lossy(&bytes).to_string();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
if content.is_empty() {
|
||||
return Err(AppError::BadRequest("No file content".to_string()));
|
||||
}
|
||||
|
||||
let analysis = parse_phd2_log(&content)
|
||||
.map_err(|e| AppError::BadRequest(format!("PHD2 parse error: {}", e)))?;
|
||||
|
||||
let session_date = &analysis.session_date;
|
||||
|
||||
// Check for duplicates: same session_date, similar duration, and similar RMS stats
|
||||
let existing: Option<(i32, i32)> = sqlx::query_as(
|
||||
r#"SELECT id, duration_min FROM phd2_logs
|
||||
WHERE session_date = ?
|
||||
AND abs(duration_min - ?) < 2
|
||||
AND abs(rms_total - ?) < 0.1
|
||||
LIMIT 1"#
|
||||
)
|
||||
.bind(session_date)
|
||||
.bind(analysis.duration_min as i32)
|
||||
.bind(analysis.rms_total_arcsec)
|
||||
.fetch_optional(&state.pool)
|
||||
.await?;
|
||||
|
||||
if let Some((dup_id, _)) = existing {
|
||||
return Ok(Json(serde_json::json!({
|
||||
"duplicate": true,
|
||||
"duplicate_id": dup_id,
|
||||
"message": format!("Duplicate session detected (ID: {}). Not inserted.", dup_id),
|
||||
"analysis": analysis,
|
||||
"filename": filename,
|
||||
})));
|
||||
}
|
||||
|
||||
let id: i64 = sqlx::query_scalar(
|
||||
r#"INSERT INTO phd2_logs
|
||||
(session_date, filename, rms_total, rms_ra, rms_dec, peak_error,
|
||||
star_lost_count, duration_min, guide_star_snr)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
RETURNING id"#,
|
||||
)
|
||||
.bind(session_date)
|
||||
.bind(&filename)
|
||||
.bind(analysis.rms_total_arcsec)
|
||||
.bind(analysis.rms_ra_arcsec)
|
||||
.bind(analysis.rms_dec_arcsec)
|
||||
.bind(analysis.peak_error_arcsec)
|
||||
.bind(analysis.star_lost_count)
|
||||
.bind(analysis.duration_min)
|
||||
.bind(analysis.mean_snr)
|
||||
.fetch_one(&state.pool)
|
||||
.await?;
|
||||
|
||||
Ok(Json(serde_json::json!({
|
||||
"id": id,
|
||||
"duplicate": false,
|
||||
"analysis": analysis,
|
||||
"filename": filename,
|
||||
})))
|
||||
}
|
||||
|
||||
pub async fn list_phd2(
|
||||
State(state): State<AppState>,
|
||||
) -> Result<Json<serde_json::Value>, AppError> {
|
||||
let rows = sqlx::query(
|
||||
"SELECT * FROM phd2_logs ORDER BY session_date DESC, created_at DESC",
|
||||
)
|
||||
.fetch_all(&state.pool)
|
||||
.await?;
|
||||
|
||||
let items: Vec<serde_json::Value> = rows.iter().map(|r| {
|
||||
use sqlx::Row;
|
||||
serde_json::json!({
|
||||
"id": r.try_get::<i32, _>("id").unwrap_or_default(),
|
||||
"session_date": r.try_get::<String, _>("session_date").unwrap_or_default(),
|
||||
"filename": r.try_get::<String, _>("filename").unwrap_or_default(),
|
||||
"rms_total": r.try_get::<Option<f64>, _>("rms_total").unwrap_or_default(),
|
||||
"rms_ra": r.try_get::<Option<f64>, _>("rms_ra").unwrap_or_default(),
|
||||
"rms_dec": r.try_get::<Option<f64>, _>("rms_dec").unwrap_or_default(),
|
||||
"peak_error": r.try_get::<Option<f64>, _>("peak_error").unwrap_or_default(),
|
||||
"star_lost_count": r.try_get::<Option<i32>, _>("star_lost_count").unwrap_or_default(),
|
||||
"duration_min": r.try_get::<Option<i32>, _>("duration_min").unwrap_or_default(),
|
||||
"guide_star_snr": r.try_get::<Option<f64>, _>("guide_star_snr").unwrap_or_default(),
|
||||
"created_at": r.try_get::<i64, _>("created_at").unwrap_or_default(),
|
||||
})
|
||||
}).collect();
|
||||
|
||||
Ok(Json(serde_json::json!({ "items": items })))
|
||||
}
|
||||
|
||||
pub async fn get_phd2(
|
||||
State(state): State<AppState>,
|
||||
Path(id): Path<i32>,
|
||||
) -> Result<Json<serde_json::Value>, AppError> {
|
||||
let row = sqlx::query("SELECT * FROM phd2_logs WHERE id = ?")
|
||||
.bind(id)
|
||||
.fetch_optional(&state.pool)
|
||||
.await?
|
||||
.ok_or_else(|| AppError::NotFound(format!("PHD2 log {} not found", id)))?;
|
||||
|
||||
use sqlx::Row;
|
||||
Ok(Json(serde_json::json!({
|
||||
"id": row.try_get::<i32, _>("id").unwrap_or_default(),
|
||||
"session_date": row.try_get::<String, _>("session_date").unwrap_or_default(),
|
||||
"filename": row.try_get::<String, _>("filename").unwrap_or_default(),
|
||||
"rms_total": row.try_get::<Option<f64>, _>("rms_total").unwrap_or_default(),
|
||||
"rms_ra": row.try_get::<Option<f64>, _>("rms_ra").unwrap_or_default(),
|
||||
"rms_dec": row.try_get::<Option<f64>, _>("rms_dec").unwrap_or_default(),
|
||||
"peak_error": row.try_get::<Option<f64>, _>("peak_error").unwrap_or_default(),
|
||||
"star_lost_count": row.try_get::<Option<i32>, _>("star_lost_count").unwrap_or_default(),
|
||||
"duration_min": row.try_get::<Option<i32>, _>("duration_min").unwrap_or_default(),
|
||||
"guide_star_snr": row.try_get::<Option<f64>, _>("guide_star_snr").unwrap_or_default(),
|
||||
})))
|
||||
}
|
||||
|
||||
pub async fn delete_phd2(
|
||||
State(state): State<AppState>,
|
||||
Path(id): Path<i32>,
|
||||
) -> Result<Json<serde_json::Value>, AppError> {
|
||||
let result = sqlx::query("DELETE FROM phd2_logs WHERE id = ?")
|
||||
.bind(id)
|
||||
.execute(&state.pool)
|
||||
.await?;
|
||||
|
||||
if result.rows_affected() == 0 {
|
||||
return Err(AppError::NotFound(format!("PHD2 log {} not found", id)));
|
||||
}
|
||||
|
||||
Ok(Json(serde_json::json!({
|
||||
"status": "deleted",
|
||||
"id": id,
|
||||
})))
|
||||
}
|
||||
Reference in New Issue
Block a user