mirror of
https://tangled.org/tranquil.farm/tranquil-pds
synced 2026-08-01 15:06:05 +00:00
Store rev properly like a good boy
This commit is contained in:
+107
-3
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user