Server overhaul: sync push delete step in its own function; clap env feature dropped
process<K> (158 lines) hands step 7 against a stored row to delete_stored<K>; same SQL, audit and push. kvitt-server never reads a clap argument from the environment (Config reads it), so clap's env feature goes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
This commit is contained in:
@@ -21,7 +21,7 @@ axum = "0.8.9"
|
||||
base64 = "0.23.1"
|
||||
chrono = { version = "0.4.45", default-features = false, features = ["clock", "std"] }
|
||||
chrono-tz = "0.10.4"
|
||||
clap = { version = "4.6.7", features = ["derive", "env"] }
|
||||
clap = { version = "4.6.7", features = ["derive"] }
|
||||
getrandom = "0.4.3"
|
||||
image = { version = "0.25.10", default-features = false, features = ["jpeg", "png", "webp"] }
|
||||
jsonwebtoken = { version = "11.1.0", default-features = false, features = ["aws_lc_rs", "use_pem"] }
|
||||
|
||||
@@ -462,6 +462,46 @@ async fn audit<K: Kind>(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Step 7 of SPEC §14.4.4 against a live stored row: the delete always wins. `updated_at` becomes the later of the
|
||||
/// stored and the pushed one; audited as `delete` with the pushed `deleted_at` as `client_at`.
|
||||
async fn delete_stored<K: Kind>(
|
||||
conn: &mut SqliteConnection,
|
||||
ctx: &Ctx<'_>,
|
||||
env: &Envelope,
|
||||
stored: &K::Wire,
|
||||
deleted_at: &str,
|
||||
updated_at: DateTime<Utc>,
|
||||
) -> Result<Processed, AppError> {
|
||||
let stored_updated = parse_ts(K::stored_updated_at(stored))
|
||||
.ok_or_else(|| AppError::internal("stored updated_at does not parse"))?;
|
||||
let new_updated_at = format_ts(stored_updated.max(updated_at));
|
||||
let version = db::next_server_version(conn).await?;
|
||||
sqlx::query(K::DELETE_SQL)
|
||||
.bind(deleted_at)
|
||||
.bind(&new_updated_at)
|
||||
.bind(ctx.caller.user_id.to_string())
|
||||
.bind(ctx.caller.device_id.to_string())
|
||||
.bind(version)
|
||||
.bind(env.entity_id.to_string())
|
||||
.execute(&mut *conn)
|
||||
.await?;
|
||||
let after = reload::<K>(conn, env.entity_id).await?;
|
||||
audit::<K>(
|
||||
conn,
|
||||
ctx,
|
||||
env,
|
||||
AuditAction::Delete,
|
||||
deleted_at,
|
||||
Some(stored),
|
||||
&after,
|
||||
)
|
||||
.await?;
|
||||
Ok(Processed {
|
||||
result: accepted(env, K::entity(after)),
|
||||
push: Some(push_of(K::DELETED, env, ctx)),
|
||||
})
|
||||
}
|
||||
|
||||
/// Steps 3 to 11 of SPEC §14.4.4 for one mutation, on the mutation's savepoint.
|
||||
async fn process<K: Kind>(
|
||||
conn: &mut SqliteConnection,
|
||||
@@ -529,34 +569,7 @@ async fn process<K: Kind>(
|
||||
// 7. Pushed entity is a delete: always wins.
|
||||
if let Some(deleted_at) = K::deleted_at(&pushed).map(str::to_owned) {
|
||||
if let Some(stored) = stored {
|
||||
let stored_updated = parse_ts(K::stored_updated_at(&stored))
|
||||
.ok_or_else(|| AppError::internal("stored updated_at does not parse"))?;
|
||||
let new_updated_at = format_ts(stored_updated.max(updated_at));
|
||||
let version = db::next_server_version(conn).await?;
|
||||
sqlx::query(K::DELETE_SQL)
|
||||
.bind(&deleted_at)
|
||||
.bind(&new_updated_at)
|
||||
.bind(ctx.caller.user_id.to_string())
|
||||
.bind(ctx.caller.device_id.to_string())
|
||||
.bind(version)
|
||||
.bind(env.entity_id.to_string())
|
||||
.execute(&mut *conn)
|
||||
.await?;
|
||||
let after = reload::<K>(conn, env.entity_id).await?;
|
||||
audit::<K>(
|
||||
conn,
|
||||
ctx,
|
||||
env,
|
||||
AuditAction::Delete,
|
||||
&deleted_at,
|
||||
Some(&stored),
|
||||
&after,
|
||||
)
|
||||
.await?;
|
||||
return Ok(Processed {
|
||||
result: accepted(env, K::entity(after)),
|
||||
push: Some(push_of(K::DELETED, env, ctx)),
|
||||
});
|
||||
return delete_stored::<K>(conn, ctx, env, &stored, &deleted_at, updated_at).await;
|
||||
}
|
||||
// Created and deleted before it ever synced: store the tombstone, no audit, no push. It is validated like a
|
||||
// create so the row satisfies every rule of the schema (SPEC §14.4.4 step 7 asks for the references).
|
||||
|
||||
Reference in New Issue
Block a user