Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 60 additions & 1 deletion src/db_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use serde::{Deserialize, Serialize};
use std::fmt;
use std::marker::PhantomData;
use std::path::Path;
Expand All @@ -22,6 +23,7 @@ use sqlx::sqlite::{SqliteConnectOptions, SqliteRow};
use sqlx::{query_as, FromRow, QueryBuilder, Row, Sqlite, SqlitePool};
use tracing::{info, instrument, trace};

use crate::db_service::error::InsertConfigurationsError;
use crate::paths::{
DetectorField, DetectorTemplate, DirectoryField, DirectoryTemplate, InvalidPathTemplate,
PathSpec, ScanField, ScanTemplate,
Expand Down Expand Up @@ -220,7 +222,7 @@ impl InstrumentConfigurationUpdate {
}
}

#[derive(Debug)]
#[derive(Debug, Deserialize, Serialize)]
struct DbInstrumentConfig {
#[allow(unused)] // unused but allows use of 'SELECT * ...' queries
id: Option<i64>,
Expand Down Expand Up @@ -323,6 +325,51 @@ impl SqliteScanPathService {
.collect())
}

pub async fn insert_configurations(
&self,
configs: &[InstrumentConfiguration],
force_clear: bool,
) -> Result<(), InsertConfigurationsError> {
let mut tx = self.pool.begin().await?;

if force_clear{ sqlx::query!("DELETE FROM instrument").execute(&mut tx).await?; } //User has chosen to overwrite existing data so delete the table before inserting new rows
else if !configs.is_empty() { // Not forcing clear and configs is not empty, check if table is empty
let count: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM instrument")
.fetch_one(&mut tx)
.await?;
if count.0 > 0 { //Table not empty, do not clear
return Err(InsertConfigurationsError::NotEmpty);
}
}

for config in configs {
sqlx::query!(
"INSERT INTO instrument (name, scan_number, directory, scan, detector, tracker_file_extension) VALUES (?, ?, ?, ?, ?, ?)",
config.name,
config.scan_number,
config.directory,
config.scan,
config.detector,
config.tracker_file_extension
)
.execute(&mut tx)
.await?;
}

tx.commit().await?;
Ok(())
}

pub async fn next_scan_configuration(
&self,
instrument: &str,
current_high: Option<u32>,
) -> Result<InstrumentConfiguration, ConfigurationError> {
let exp = current_high.unwrap_or(0);
query_as!(
DbInstrumentConfig,
}

pub async fn next_scan_configuration(
&self,
instrument: &str,
Expand Down Expand Up @@ -370,6 +417,8 @@ impl fmt::Debug for SqliteScanPathService {
mod error {
use derive_more::{Display, Error, From};

use crate::db_service::InstrumentConfiguration;

#[derive(Debug, Display, Error, From)]
pub enum ConfigurationError {
#[display("No configuration available for instrument {_0:?}")]
Expand All @@ -392,6 +441,16 @@ mod error {
Self::MissingField(value.into())
}
}

#[derive(Debug, Display, Error, From)]
pub enum InsertConfigurationsError {
#[display("Instrument table not empty and force_clear not set to true")]
NotEmpty,
#[from]
#[display("Error inserting configurations: {_0}")]
Db(sqlx::Error),
}

}

#[cfg(test)]
Expand Down
11 changes: 10 additions & 1 deletion src/graphql/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ use async_graphql::{
};
use async_graphql_axum::{GraphQLRequest, GraphQLResponse};
use auth::{AuthError, PolicyCheck};
use axum::extract::State;
use axum::http::StatusCode;
use axum::response::{Html, IntoResponse};
use axum::routing::{get, post};
Expand Down Expand Up @@ -67,14 +68,16 @@ pub async fn serve_graphql(opts: ServeOptions) {
let schema = Schema::build(Query, Mutation, EmptySubscription)
.extension(Tracing)
.limit_directives(32)
.data(db)
.data(db.clone())
.data(directory_numtracker)
.data(opts.policy.map(PolicyCheck::new))
.finish();
let app = Router::new()
// status check endpoint allows external processes to monitor status of server without
// making graphql queries
.route("/status", get(server_status))
.route("/admin/export", get(export_handler))
// .route("/admin/restore", post(restore_handler))
.route("/graphql", post(graphql_handler))
// make it obvious that /graphql isn't expected to work when visiting from a browser
.route(
Expand All @@ -88,6 +91,7 @@ pub async fn serve_graphql(opts: ServeOptions) {
// Interactive graphiql playground
.route("/graphiql", get(graphiql))
// Make it look less like something is broken when going to any other page
.with_state(db)
.fallback((
StatusCode::NOT_FOUND,
Html(include_str!("../../static/404.html")),
Expand All @@ -102,6 +106,11 @@ pub async fn serve_graphql(opts: ServeOptions) {
.expect("Can't serve graphql endpoint");
}

async fn export_handler(State(db): State<SqliteScanPathService>) -> String {
#let configs = db.all_configurations().await;
return format!("{configs:?}");
}

async fn create_signal_handler() {
let mut term = signal(SignalKind::terminate()).expect("Failed to create SIGTERM listener");
let mut int = signal(SignalKind::interrupt()).expect("Failed to create SIGINT listener");
Expand Down
Loading