use chrono::{SecondsFormat, Utc};
use laterite_core::query::{bind_values, build, insert_returning_id};
use laterite_core::{Actor, AnyRowExt, Db};
use sea_query::{Expr, Query};
use crate::import::run::{Counts, Note, Run, RunSpec, Status};
use crate::import::schema::ImportRuns;
fn now() -> String {
Utc::now().to_rfc3339_opts(SecondsFormat::Micros, true)
}
fn json<T: serde::Serialize>(value: &T) -> String {
serde_json::to_string(value).expect("a run's spec and notes serialize")
}
pub(crate) async fn insert(db: &Db, spec: &RunSpec, actor: &Actor) -> Result<i64, sqlx::Error> {
let stmt = Query::insert()
.into_table(ImportRuns::Table)
.columns([
ImportRuns::Resource,
ImportRuns::MediaId,
ImportRuns::ActorUserId,
ImportRuns::ActorLabel,
ImportRuns::Status,
ImportRuns::Spec,
ImportRuns::Notes,
ImportRuns::StartedAt,
])
.values_panic([
spec.resource.clone().into(),
spec.media_id.into(),
actor.user_id().into(),
actor.label().to_string().into(),
Status::Running.as_str().into(),
json(spec).into(),
"[]".into(),
now().into(),
])
.to_owned();
insert_returning_id(db, stmt, ImportRuns::Id).await
}
pub(crate) async fn progress(db: &Db, id: i64, counts: &Counts) -> Result<(), sqlx::Error> {
let (sql, values) = build(
db.backend,
Query::update()
.table(ImportRuns::Table)
.values(count_values(counts))
.and_where(Expr::col(ImportRuns::Id).eq(id))
.to_owned(),
);
bind_values(sqlx::query(&sql), values)
.execute(&db.pool)
.await?;
Ok(())
}
pub(crate) async fn finish(
db: &Db,
id: i64,
counts: &Counts,
notes: &[Note],
) -> Result<(), sqlx::Error> {
let (sql, values) = {
let mut set = count_values(counts);
set.push((ImportRuns::Status, Status::Done.as_str().into()));
set.push((ImportRuns::Notes, json(¬es).into()));
set.push((ImportRuns::FinishedAt, now().into()));
build(
db.backend,
Query::update()
.table(ImportRuns::Table)
.values(set)
.and_where(Expr::col(ImportRuns::Id).eq(id))
.to_owned(),
)
};
bind_values(sqlx::query(&sql), values)
.execute(&db.pool)
.await?;
Ok(())
}
pub(crate) async fn fail(db: &Db, id: i64, failure: &str) -> Result<(), sqlx::Error> {
let (sql, values) = build(
db.backend,
Query::update()
.table(ImportRuns::Table)
.values([
(ImportRuns::Status, Status::Failed.as_str().into()),
(ImportRuns::Failure, failure.to_string().into()),
(ImportRuns::FinishedAt, now().into()),
])
.and_where(Expr::col(ImportRuns::Id).eq(id))
.to_owned(),
);
bind_values(sqlx::query(&sql), values)
.execute(&db.pool)
.await?;
Ok(())
}
fn count_values(counts: &Counts) -> Vec<(ImportRuns, sea_query::SimpleExpr)> {
vec![
(ImportRuns::Processed, counts.processed.into()),
(ImportRuns::Created, counts.created.into()),
(ImportRuns::Updated, counts.updated.into()),
(ImportRuns::Skipped, counts.skipped.into()),
(ImportRuns::Errors, counts.errors.into()),
]
}
fn select_runs() -> sea_query::SelectStatement {
Query::select()
.columns([
ImportRuns::Id,
ImportRuns::Resource,
ImportRuns::MediaId,
ImportRuns::ActorLabel,
ImportRuns::Status,
ImportRuns::Spec,
ImportRuns::Processed,
ImportRuns::Created,
ImportRuns::Updated,
ImportRuns::Skipped,
ImportRuns::Errors,
ImportRuns::Notes,
ImportRuns::Failure,
ImportRuns::StartedAt,
ImportRuns::FinishedAt,
])
.from(ImportRuns::Table)
.to_owned()
}
fn read_run(row: &sqlx::any::AnyRow) -> Result<Run, sqlx::Error> {
let status = row.get_text("status")?;
let notes: Vec<Note> = serde_json::from_str(&row.get_text("notes")?).unwrap_or_default();
let resource = row.get_text("resource")?;
let spec: RunSpec = serde_json::from_str(&row.get_text("spec")?)
.unwrap_or_else(|_| RunSpec::new(resource.clone(), Vec::new()));
Ok(Run {
id: row.get_int("id")?,
resource,
media_id: row.get_int_opt("media_id")?,
spec,
actor_label: row.get_text("actor_label")?,
status: Status::parse(&status).unwrap_or(Status::Failed),
processed: row.get_int("processed")?,
created: row.get_int("created")?,
updated: row.get_int("updated")?,
skipped: row.get_int("skipped")?,
errors: row.get_int("errors")?,
notes,
failure: row.get_text_opt("failure")?,
started_at: row.get_text("started_at")?,
finished_at: row.get_text_opt("finished_at")?,
})
}
pub(crate) async fn find(db: &Db, id: i64) -> Result<Option<Run>, sqlx::Error> {
let (sql, values) = build(
db.backend,
select_runs()
.and_where(Expr::col(ImportRuns::Id).eq(id))
.to_owned(),
);
bind_values(sqlx::query(&sql), values)
.fetch_optional(&db.pool)
.await?
.as_ref()
.map(read_run)
.transpose()
}
pub(crate) async fn last_for(db: &Db, resource: &str) -> Result<Option<Run>, sqlx::Error> {
Ok(recent_for(db, resource, 1).await?.into_iter().next())
}
pub(crate) async fn recent_for(
db: &Db,
resource: &str,
limit: i64,
) -> Result<Vec<Run>, sqlx::Error> {
let (sql, values) = build(
db.backend,
select_runs()
.and_where(Expr::col(ImportRuns::Resource).eq(resource))
.order_by(ImportRuns::Id, sea_query::Order::Desc)
.limit(limit as u64)
.to_owned(),
);
bind_values(sqlx::query(&sql), values)
.fetch_all(&db.pool)
.await?
.iter()
.map(read_run)
.collect()
}