···3838 rm -f "$INFRA_FILE"
3939 fi
40404141- $CONTAINER_CMD rm -f "${CONTAINER_PREFIX}-postgres" "${CONTAINER_PREFIX}-minio" 2>/dev/null || true
4141+ $CONTAINER_CMD rm -f "${CONTAINER_PREFIX}-postgres" "${CONTAINER_PREFIX}-minio" "${CONTAINER_PREFIX}-valkey" 2>/dev/null || true
42424343 echo "Starting PostgreSQL..."
4444 $CONTAINER_CMD run -d \
···5959 --label bspds_test=true \
6060 minio/minio:latest server /data >/dev/null
61616262+ echo "Starting Valkey..."
6363+ $CONTAINER_CMD run -d \
6464+ --name "${CONTAINER_PREFIX}-valkey" \
6565+ -P \
6666+ --label bspds_test=true \
6767+ valkey/valkey:8-alpine >/dev/null
6868+6269 echo "Waiting for services to be ready..."
6370 sleep 2
64716572 PG_PORT=$($CONTAINER_CMD port "${CONTAINER_PREFIX}-postgres" 5432 | head -1 | cut -d: -f2)
6673 MINIO_PORT=$($CONTAINER_CMD port "${CONTAINER_PREFIX}-minio" 9000 | head -1 | cut -d: -f2)
7474+ VALKEY_PORT=$($CONTAINER_CMD port "${CONTAINER_PREFIX}-valkey" 6379 | head -1 | cut -d: -f2)
67756876 for i in {1..30}; do
6977 if $CONTAINER_CMD exec "${CONTAINER_PREFIX}-postgres" pg_isready -U postgres >/dev/null 2>&1; then
···8189 sleep 1
8290 done
83919292+ for i in {1..30}; do
9393+ if $CONTAINER_CMD exec "${CONTAINER_PREFIX}-valkey" valkey-cli ping 2>/dev/null | grep -q PONG; then
9494+ break
9595+ fi
9696+ echo "Waiting for Valkey... ($i/30)"
9797+ sleep 1
9898+ done
9999+84100 echo "Creating MinIO bucket..."
85101 $CONTAINER_CMD run --rm --network host \
86102 -e MC_HOST_minio="http://minioadmin:minioadmin@127.0.0.1:${MINIO_PORT}" \
···94110export AWS_ACCESS_KEY_ID="minioadmin"
95111export AWS_SECRET_ACCESS_KEY="minioadmin"
96112export AWS_REGION="us-east-1"
113113+export VALKEY_URL="redis://127.0.0.1:${VALKEY_PORT}"
97114export BSPDS_TEST_INFRA_READY="1"
98115export BSPDS_ALLOW_INSECURE_SECRETS="1"
99116export SKIP_IMPORT_VERIFICATION="true"
···108125109126stop_infra() {
110127 echo "Stopping test infrastructure..."
111111- $CONTAINER_CMD rm -f "${CONTAINER_PREFIX}-postgres" "${CONTAINER_PREFIX}-minio" 2>/dev/null || true
128128+ $CONTAINER_CMD rm -f "${CONTAINER_PREFIX}-postgres" "${CONTAINER_PREFIX}-minio" "${CONTAINER_PREFIX}-valkey" 2>/dev/null || true
112129 rm -f "$INFRA_FILE"
113130 echo "Infrastructure stopped."
114131}
···157174 echo "Usage: $0 {start|stop|restart|status|env}"
158175 echo ""
159176 echo "Commands:"
160160- echo " start - Start test infrastructure (Postgres, MinIO)"
177177+ echo " start - Start test infrastructure (Postgres, MinIO, Valkey)"
161178 echo " stop - Stop and remove test containers"
162179 echo " restart - Stop then start infrastructure"
163180 echo " status - Show infrastructure status"
+5-3
src/api/admin/account/delete.rs
···3737 .into_response();
3838 }
39394040- let user = sqlx::query!("SELECT id FROM users WHERE did = $1", did)
4040+ let user = sqlx::query!("SELECT id, handle FROM users WHERE did = $1", did)
4141 .fetch_optional(&state.db)
4242 .await;
43434444- let user_id = match user {
4545- Ok(Some(row)) => row.id,
4444+ let (user_id, handle) = match user {
4545+ Ok(Some(row)) => (row.id, row.handle),
4646 Ok(None) => {
4747 return (
4848 StatusCode::NOT_FOUND,
···185185 )
186186 .into_response();
187187 }
188188+189189+ let _ = state.cache.delete(&format!("handle:{}", handle)).await;
188190189191 (StatusCode::OK, Json(json!({}))).into_response()
190192}
+10
src/api/admin/account/update.rs
···108108 .into_response();
109109 }
110110111111+ let old_handle = sqlx::query_scalar!("SELECT handle FROM users WHERE did = $1", did)
112112+ .fetch_optional(&state.db)
113113+ .await
114114+ .ok()
115115+ .flatten();
116116+111117 let existing = sqlx::query!("SELECT id FROM users WHERE handle = $1 AND did != $2", handle, did)
112118 .fetch_optional(&state.db)
113119 .await;
···133139 )
134140 .into_response();
135141 }
142142+ if let Some(old) = old_handle {
143143+ let _ = state.cache.delete(&format!("handle:{}", old)).await;
144144+ }
145145+ let _ = state.cache.delete(&format!("handle:{}", handle)).await;
136146 (StatusCode::OK, Json(json!({}))).into_response()
137147 }
138148 Err(e) => {
+7
src/api/admin/status.rs
···305305 .into_response();
306306 }
307307308308+ if let Ok(Some(handle)) = sqlx::query_scalar!("SELECT handle FROM users WHERE did = $1", did)
309309+ .fetch_optional(&state.db)
310310+ .await
311311+ {
312312+ let _ = state.cache.delete(&format!("handle:{}", handle)).await;
313313+ }
314314+308315 return (
309316 StatusCode::OK,
310317 Json(json!({
+19-1
src/api/identity/did.rs
···3333 .into_response();
3434 }
35353636+ let cache_key = format!("handle:{}", handle);
3737+ if let Some(did) = state.cache.get(&cache_key).await {
3838+ return (StatusCode::OK, Json(json!({ "did": did }))).into_response();
3939+ }
4040+3641 let user = sqlx::query!("SELECT did FROM users WHERE handle = $1", handle)
3742 .fetch_optional(&state.db)
3843 .await;
39444045 match user {
4146 Ok(Some(row)) => {
4747+ let _ = state.cache.set(&cache_key, &row.did, std::time::Duration::from_secs(300)).await;
4248 (StatusCode::OK, Json(json!({ "did": row.did }))).into_response()
4349 }
4450 Ok(None) => (
···406412 .into_response();
407413 }
408414415415+ let old_handle = sqlx::query_scalar!("SELECT handle FROM users WHERE id = $1", user_id)
416416+ .fetch_optional(&state.db)
417417+ .await
418418+ .ok()
419419+ .flatten();
420420+409421 let existing = sqlx::query!("SELECT id FROM users WHERE handle = $1 AND id != $2", new_handle, user_id)
410422 .fetch_optional(&state.db)
411423 .await;
···423435 .await;
424436425437 match result {
426426- Ok(_) => (StatusCode::OK, Json(json!({}))).into_response(),
438438+ Ok(_) => {
439439+ if let Some(old) = old_handle {
440440+ let _ = state.cache.delete(&format!("handle:{}", old)).await;
441441+ }
442442+ let _ = state.cache.delete(&format!("handle:{}", new_handle)).await;
443443+ (StatusCode::OK, Json(json!({}))).into_response()
444444+ }
427445 Err(e) => {
428446 error!("DB error updating handle: {:?}", e);
429447 (
+11
src/api/repo/record/batch.rs
···11+use super::validation::validate_record;
12use crate::api::repo::record::utils::{commit_and_log, RecordOp};
23use crate::repo::tracking::TrackingBlockStore;
34use crate::state::AppState;
···211212 rkey,
212213 value,
213214 } => {
215215+ if input.validate.unwrap_or(true) {
216216+ if let Err(err_response) = validate_record(value, collection) {
217217+ return err_response;
218218+ }
219219+ }
214220 let rkey = rkey
215221 .clone()
216222 .unwrap_or_else(|| Utc::now().format("%Y%m%d%H%M%S%f").to_string());
···249255 rkey,
250256 value,
251257 } => {
258258+ if input.validate.unwrap_or(true) {
259259+ if let Err(err_response) = validate_record(value, collection) {
260260+ return err_response;
261261+ }
262262+ }
252263 let mut record_bytes = Vec::new();
253264 if serde_ipld_dagcbor::to_writer(&mut record_bytes, value).is_err() {
254265 return (StatusCode::BAD_REQUEST, Json(json!({"error": "InvalidRecord", "message": "Failed to serialize record"}))).into_response();
+1
src/api/repo/record/mod.rs
···22pub mod delete;
33pub mod read;
44pub mod utils;
55+pub mod validation;
56pub mod write;
6778pub use batch::apply_writes;