feat: Add related notes functionality with new API endpoint and frontend components, and update note search route.

This commit is contained in:
2025-12-26 00:35:32 +01:00
parent 64f8118228
commit c8276ac306
12 changed files with 245 additions and 37 deletions

View File

@@ -65,4 +65,47 @@ impl LinkRepository for SqliteLinkRepository {
Ok(())
}
async fn get_links_for_note(&self, source_note_id: Uuid) -> DomainResult<Vec<NoteLink>> {
let source_str = source_note_id.to_string();
// We select links where the note is the source
// TODO: Should we also include links where the note is the target?
// For now, let's stick to outgoing links as defined by the service logic.
// Actually, semantic similarity is symmetric, but we only save (A -> B) if we process A.
// Ideally we should look for both directions or enforce symmetry.
// Given current implementation saves A->B when A is processed, if B is processed it saves B->A.
// So just querying source_note_id is fine if we assume all notes are processed.
let links = sqlx::query_as::<_, SqliteNoteLink>(
"SELECT * FROM note_links WHERE source_note_id = ? ORDER BY score DESC",
)
.bind(source_str)
.fetch_all(&self.pool)
.await
.map_err(|e| DomainError::RepositoryError(e.to_string()))?;
Ok(links.into_iter().map(NoteLink::from).collect())
}
}
#[derive(sqlx::FromRow)]
struct SqliteNoteLink {
source_note_id: String,
target_note_id: String,
score: f32,
created_at: String, // Stored as ISO string
}
impl From<SqliteNoteLink> for NoteLink {
fn from(row: SqliteNoteLink) -> Self {
Self {
source_note_id: Uuid::parse_str(&row.source_note_id).unwrap_or_default(),
target_note_id: Uuid::parse_str(&row.target_note_id).unwrap_or_default(),
score: row.score,
created_at: chrono::DateTime::parse_from_rfc3339(&row.created_at)
.unwrap_or_default()
.with_timezone(&chrono::Utc),
}
}
}