Store rev properly like a good boy

This commit is contained in:
lewis
2025-12-28 02:24:09 +02:00
parent f6d7d65ef8
commit 8cb82abc82
19 changed files with 242 additions and 73 deletions
+107 -3
View File
@@ -1,4 +1,5 @@
use crate::api::ApiError;
use crate::api::repo::record::create_signed_commit;
use crate::state::AppState;
use crate::sync::import::{ImportError, apply_import, parse_car};
use crate::sync::verify::CarVerifier;
@@ -9,6 +10,9 @@ use axum::{
http::StatusCode,
response::{IntoResponse, Response},
};
use jacquard::types::{integer::LimitedU32, string::Tid};
use jacquard_repo::storage::BlockStore;
use k256::ecdsa::SigningKey;
use serde_json::json;
use tracing::{debug, error, info, warn};
@@ -312,14 +316,114 @@ pub async fn import_repo(
.and_then(|s| s.parse().ok())
.unwrap_or(DEFAULT_MAX_BLOCKS);
match apply_import(&state.db, user_id, root, blocks, max_blocks).await {
Ok(records) => {
Ok(import_result) => {
info!(
"Successfully imported {} records for user {}",
records.len(),
import_result.records.len(),
did
);
let key_row = match sqlx::query!(
r#"SELECT uk.key_bytes, uk.encryption_version
FROM user_keys uk
JOIN users u ON uk.user_id = u.id
WHERE u.did = $1"#,
did
)
.fetch_optional(&state.db)
.await
{
Ok(Some(row)) => row,
Ok(None) => {
error!("No signing key found for user {}", did);
return (
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({"error": "InternalError", "message": "Signing key not found"})),
)
.into_response();
}
Err(e) => {
error!("DB error fetching signing key: {:?}", e);
return (
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({"error": "InternalError"})),
)
.into_response();
}
};
let key_bytes = match crate::config::decrypt_key(&key_row.key_bytes, key_row.encryption_version) {
Ok(k) => k,
Err(e) => {
error!("Failed to decrypt signing key: {}", e);
return (
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({"error": "InternalError"})),
)
.into_response();
}
};
let signing_key = match SigningKey::from_slice(&key_bytes) {
Ok(k) => k,
Err(e) => {
error!("Invalid signing key: {:?}", e);
return (
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({"error": "InternalError"})),
)
.into_response();
}
};
let new_rev = Tid::now(LimitedU32::MIN);
let new_rev_str = new_rev.to_string();
let (commit_bytes, _sig) = match create_signed_commit(
did,
import_result.data_cid,
&new_rev_str,
None,
&signing_key,
) {
Ok(result) => result,
Err(e) => {
error!("Failed to create new commit: {}", e);
return (
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({"error": "InternalError"})),
)
.into_response();
}
};
let new_root_cid: cid::Cid = match state.block_store.put(&commit_bytes).await {
Ok(cid) => cid,
Err(e) => {
error!("Failed to store new commit block: {:?}", e);
return (
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({"error": "InternalError"})),
)
.into_response();
}
};
let new_root_str = new_root_cid.to_string();
if let Err(e) = sqlx::query!(
"UPDATE repos SET repo_root_cid = $1, updated_at = NOW() WHERE user_id = $2",
new_root_str,
user_id
)
.execute(&state.db)
.await
{
error!("Failed to update repo root: {:?}", e);
return (
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({"error": "InternalError"})),
)
.into_response();
}
info!(
"Created new commit for imported repo: cid={}, rev={}",
new_root_str, new_rev_str
);
if !is_migration {
if let Err(e) = sequence_import_event(&state, did, &root.to_string()).await {
if let Err(e) = sequence_import_event(&state, did, &new_root_str).await {
warn!("Failed to sequence import event: {:?}", e);
}
}
+5 -3
View File
@@ -321,7 +321,7 @@ pub async fn commit_and_log(
.await
.map_err(|e| format!("Failed to commit transaction: {}", e))?;
if is_account_active {
let _ = sequence_sync_event(state, did, &new_root_cid.to_string()).await;
let _ = sequence_sync_event(state, did, &new_root_cid.to_string(), Some(&rev_str)).await;
}
Ok(CommitResult {
commit_cid: new_root_cid,
@@ -470,15 +470,17 @@ pub async fn sequence_sync_event(
state: &AppState,
did: &str,
commit_cid: &str,
rev: Option<&str>,
) -> Result<i64, String> {
let seq_row = sqlx::query!(
r#"
INSERT INTO repo_seq (did, event_type, commit_cid)
VALUES ($1, 'sync', $2)
INSERT INTO repo_seq (did, event_type, commit_cid, rev)
VALUES ($1, 'sync', $2, $3)
RETURNING seq
"#,
did,
commit_cid,
rev,
)
.fetch_one(&state.db)
.await
+32 -3
View File
@@ -8,10 +8,14 @@ use axum::{
response::{IntoResponse, Response},
};
use bcrypt::verify;
use cid::Cid;
use chrono::{Duration, Utc};
use jacquard_repo::commit::Commit;
use jacquard_repo::storage::BlockStore;
use k256::ecdsa::SigningKey;
use serde::{Deserialize, Serialize};
use serde_json::json;
use std::str::FromStr;
use tracing::{error, info, warn};
use uuid::Uuid;
@@ -245,8 +249,24 @@ async fn assert_valid_did_document_for_service(
}
} else if did.starts_with("did:web:") {
let client = reqwest::Client::new();
let did_path = &did[8..];
let url = format!("https://{}/.well-known/did.json", did_path.replace(':', "/"));
let host_and_path = &did[8..];
let decoded = host_and_path.replace("%3A", ":");
let parts: Vec<&str> = decoded.split(':').collect();
let (host, path_parts) = if parts.len() > 1 && parts[1].chars().all(|c| c.is_ascii_digit()) {
(format!("{}:{}", parts[0], parts[1]), parts[2..].to_vec())
} else {
(parts[0].to_string(), parts[1..].to_vec())
};
let scheme = if host.starts_with("localhost") || host.starts_with("127.") || host.contains(':') {
"http"
} else {
"https"
};
let url = if path_parts.is_empty() {
format!("{}://{}/.well-known/did.json", scheme, host)
} else {
format!("{}://{}/{}/did.json", scheme, host, path_parts.join("/"))
};
let resp = client.get(&url).send().await.map_err(|e| {
warn!("Failed to fetch did:web document for {}: {:?}", did, e);
(
@@ -381,8 +401,17 @@ pub async fn activate_account(
.ok()
.flatten();
if let Some(root_cid) = repo_root {
let rev = if let Ok(cid) = Cid::from_str(&root_cid) {
if let Ok(Some(block)) = state.block_store.get(&cid).await {
Commit::from_cbor(&block).ok().map(|c| c.rev().to_string())
} else {
None
}
} else {
None
};
if let Err(e) =
crate::api::repo::record::sequence_sync_event(&state, &did, &root_cid).await
crate::api::repo::record::sequence_sync_event(&state, &did, &root_cid, rev.as_deref()).await
{
warn!("Failed to sequence sync event for activation: {}", e);
}