Feature/open chat with persona #110
@@ -366,12 +366,28 @@ impl PersonaDao for SqlitePersonaDao {
|
|||||||
pid: &str,
|
pid: &str,
|
||||||
) -> Result<bool, DbError> {
|
) -> Result<bool, DbError> {
|
||||||
trace_db_call(cx, "delete", "delete_persona", |_span| {
|
trace_db_call(cx, "delete", "delete_persona", |_span| {
|
||||||
use schema::personas::dsl::*;
|
|
||||||
let mut conn = self.connection.lock().expect("PersonaDao lock");
|
let mut conn = self.connection.lock().expect("PersonaDao lock");
|
||||||
let n = diesel::delete(personas.filter(user_id.eq(uid)).filter(persona_id.eq(pid)))
|
// One transaction so a persona and its conversations can't get
|
||||||
.execute(conn.deref_mut())
|
// out of step. There is no FK between the two tables, and the
|
||||||
.map_err(|e| anyhow::anyhow!("Delete error: {}", e))?;
|
// chat list skips conversations whose persona is gone — so
|
||||||
Ok(n > 0)
|
// without this the transcripts would linger invisibly forever.
|
||||||
|
conn.deref_mut().transaction::<_, anyhow::Error, _>(|tx| {
|
||||||
|
{
|
||||||
|
use schema::persona_chat_conversations::dsl::*;
|
||||||
|
diesel::delete(
|
||||||
|
persona_chat_conversations
|
||||||
|
.filter(user_id.eq(uid))
|
||||||
|
.filter(persona_id.eq(pid)),
|
||||||
|
)
|
||||||
|
.execute(tx)
|
||||||
|
.map_err(|e| anyhow::anyhow!("Delete error: {}", e))?;
|
||||||
|
}
|
||||||
|
use schema::personas::dsl::*;
|
||||||
|
let n = diesel::delete(personas.filter(user_id.eq(uid)).filter(persona_id.eq(pid)))
|
||||||
|
.execute(tx)
|
||||||
|
.map_err(|e| anyhow::anyhow!("Delete error: {}", e))?;
|
||||||
|
Ok(n > 0)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.map_err(|e| DbError::log(DbErrorKind::QueryError, e))
|
.map_err(|e| DbError::log(DbErrorKind::QueryError, e))
|
||||||
}
|
}
|
||||||
@@ -861,6 +877,34 @@ mod tests {
|
|||||||
assert!(dao.list_persona_chats(&cx, uid).unwrap().is_empty());
|
assert!(dao.list_persona_chats(&cx, uid).unwrap().is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deleting_a_persona_takes_its_conversations_with_it() {
|
||||||
|
// No FK between the tables, and the chat list hides conversations
|
||||||
|
// whose persona is gone — so an orphan row would be an invisible,
|
||||||
|
// unreachable transcript sitting in the database forever.
|
||||||
|
let cx = opentelemetry::Context::new();
|
||||||
|
let (mut dao, uid) = dao_with_user("p-cascade");
|
||||||
|
dao.create_persona(&cx, uid, "custom-1", "Custom", "prompt", false, false)
|
||||||
|
.unwrap();
|
||||||
|
dao.create_persona_chat(&cx, uid, "custom-1", 100).unwrap();
|
||||||
|
dao.create_persona_chat(&cx, uid, "custom-1", 200).unwrap();
|
||||||
|
let survivor = dao.create_persona_chat(&cx, uid, "other", 300).unwrap();
|
||||||
|
|
||||||
|
assert!(dao.delete_persona(&cx, uid, "custom-1").unwrap());
|
||||||
|
|
||||||
|
let remaining: Vec<String> = dao
|
||||||
|
.list_persona_chats(&cx, uid)
|
||||||
|
.unwrap()
|
||||||
|
.into_iter()
|
||||||
|
.map(|r| r.conversation_id)
|
||||||
|
.collect();
|
||||||
|
assert_eq!(
|
||||||
|
remaining,
|
||||||
|
vec![survivor],
|
||||||
|
"only the untouched persona's conversation survives"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn persona_chat_reads_and_writes_are_scoped_to_the_owner() {
|
fn persona_chat_reads_and_writes_are_scoped_to_the_owner() {
|
||||||
// A conversation_id is a bearer token for someone's transcript, so
|
// A conversation_id is a bearer token for someone's transcript, so
|
||||||
|
|||||||
Reference in New Issue
Block a user