feat: Add related notes functionality with new API endpoint and frontend components, and update note search route.
This commit is contained in:
@@ -172,3 +172,23 @@ impl From<notes_domain::NoteVersion> for NoteVersionResponse {
|
||||
pub struct ConfigResponse {
|
||||
pub allow_registration: bool,
|
||||
}
|
||||
|
||||
/// Note Link response DTO
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct NoteLinkResponse {
|
||||
pub source_note_id: Uuid,
|
||||
pub target_note_id: Uuid,
|
||||
pub score: f32,
|
||||
pub created_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
impl From<notes_domain::entities::NoteLink> for NoteLinkResponse {
|
||||
fn from(link: notes_domain::entities::NoteLink) -> Self {
|
||||
Self {
|
||||
source_note_id: link.source_note_id,
|
||||
target_note_id: link.target_note_id,
|
||||
score: link.score,
|
||||
created_at: link.created_at,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,8 +44,8 @@ async fn main() -> anyhow::Result<()> {
|
||||
let db_config = DatabaseConfig::new(&config.database_url);
|
||||
|
||||
use notes_infra::factory::{
|
||||
build_database_pool, build_note_repository, build_session_store, build_tag_repository,
|
||||
build_user_repository,
|
||||
build_database_pool, build_link_repository, build_note_repository, build_session_store,
|
||||
build_tag_repository, build_user_repository,
|
||||
};
|
||||
let pool = build_database_pool(&db_config)
|
||||
.await
|
||||
@@ -73,6 +73,9 @@ async fn main() -> anyhow::Result<()> {
|
||||
let user_repo = build_user_repository(&pool)
|
||||
.await
|
||||
.map_err(|e| anyhow::anyhow!(e))?;
|
||||
let link_repo = build_link_repository(&pool)
|
||||
.await
|
||||
.map_err(|e| anyhow::anyhow!(e))?;
|
||||
|
||||
// Create services
|
||||
use notes_domain::{NoteService, TagService, UserService};
|
||||
@@ -91,6 +94,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
note_repo,
|
||||
tag_repo,
|
||||
user_repo.clone(),
|
||||
link_repo,
|
||||
note_service,
|
||||
tag_service,
|
||||
user_service,
|
||||
@@ -119,35 +123,36 @@ async fn main() -> anyhow::Result<()> {
|
||||
let auth_layer = AuthManagerLayerBuilder::new(backend, session_layer).build();
|
||||
|
||||
// Parse CORS origins
|
||||
let mut cors = CorsLayer::new()
|
||||
.allow_methods([
|
||||
axum::http::Method::GET,
|
||||
axum::http::Method::POST,
|
||||
axum::http::Method::PATCH,
|
||||
axum::http::Method::DELETE,
|
||||
axum::http::Method::OPTIONS,
|
||||
])
|
||||
.allow_headers([
|
||||
axum::http::header::AUTHORIZATION,
|
||||
axum::http::header::ACCEPT,
|
||||
axum::http::header::CONTENT_TYPE,
|
||||
])
|
||||
.allow_credentials(true);
|
||||
// let mut cors = CorsLayer::new()
|
||||
// .allow_methods([
|
||||
// axum::http::Method::GET,
|
||||
// axum::http::Method::POST,
|
||||
// axum::http::Method::PATCH,
|
||||
// axum::http::Method::DELETE,
|
||||
// axum::http::Method::OPTIONS,
|
||||
// ])
|
||||
// .allow_headers([
|
||||
// axum::http::header::AUTHORIZATION,
|
||||
// axum::http::header::ACCEPT,
|
||||
// axum::http::header::CONTENT_TYPE,
|
||||
// ])
|
||||
// .allow_credentials(true);
|
||||
let mut cors = CorsLayer::very_permissive();
|
||||
|
||||
// Add allowed origins
|
||||
let mut allowed_origins = Vec::new();
|
||||
for origin in &config.cors_allowed_origins {
|
||||
tracing::debug!("Allowing CORS origin: {}", origin);
|
||||
if let Ok(value) = origin.parse::<axum::http::HeaderValue>() {
|
||||
allowed_origins.push(value);
|
||||
} else {
|
||||
tracing::warn!("Invalid CORS origin: {}", origin);
|
||||
}
|
||||
}
|
||||
// let mut allowed_origins = Vec::new();
|
||||
// for origin in &config.cors_allowed_origins {
|
||||
// tracing::debug!("Allowing CORS origin: {}", origin);
|
||||
// if let Ok(value) = origin.parse::<axum::http::HeaderValue>() {
|
||||
// allowed_origins.push(value);
|
||||
// } else {
|
||||
// tracing::warn!("Invalid CORS origin: {}", origin);
|
||||
// }
|
||||
// }
|
||||
|
||||
if !allowed_origins.is_empty() {
|
||||
cors = cors.allow_origin(allowed_origins);
|
||||
}
|
||||
// if !allowed_origins.is_empty() {
|
||||
// cors = cors.allow_origin(allowed_origins);
|
||||
// }
|
||||
|
||||
// Build the application
|
||||
let app = Router::new()
|
||||
|
||||
@@ -30,6 +30,7 @@ pub fn api_v1_router() -> Router<AppState> {
|
||||
.delete(notes::delete_note),
|
||||
)
|
||||
.route("/notes/{id}/versions", get(notes::list_note_versions))
|
||||
.route("/notes/{id}/related", get(notes::get_related_notes))
|
||||
// Search route
|
||||
.route("/search", get(notes::search_notes))
|
||||
// Import/Export routes
|
||||
|
||||
@@ -184,7 +184,7 @@ pub async fn delete_note(
|
||||
}
|
||||
|
||||
/// Search notes
|
||||
/// GET /api/v1/search
|
||||
/// GET /api/v1/notes/search
|
||||
pub async fn search_notes(
|
||||
State(state): State<AppState>,
|
||||
auth: AuthSession<AuthBackend>,
|
||||
@@ -225,3 +225,30 @@ pub async fn list_note_versions(
|
||||
|
||||
Ok(Json(response))
|
||||
}
|
||||
|
||||
/// Get related notes
|
||||
/// GET /api/v1/notes/:id/related
|
||||
pub async fn get_related_notes(
|
||||
State(state): State<AppState>,
|
||||
auth: AuthSession<AuthBackend>,
|
||||
Path(id): Path<Uuid>,
|
||||
) -> ApiResult<Json<Vec<crate::dto::NoteLinkResponse>>> {
|
||||
let user = auth
|
||||
.user
|
||||
.ok_or(ApiError::Domain(notes_domain::DomainError::Unauthorized(
|
||||
"Login required".to_string(),
|
||||
)))?;
|
||||
let user_id = user.id();
|
||||
|
||||
// Verify access to the source note
|
||||
state.note_service.get_note(id, user_id).await?;
|
||||
|
||||
// Get links
|
||||
let links = state.link_repo.get_links_for_note(id).await?;
|
||||
let response: Vec<crate::dto::NoteLinkResponse> = links
|
||||
.into_iter()
|
||||
.map(crate::dto::NoteLinkResponse::from)
|
||||
.collect();
|
||||
|
||||
Ok(Json(response))
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ pub struct AppState {
|
||||
pub note_repo: Arc<dyn NoteRepository>,
|
||||
pub tag_repo: Arc<dyn TagRepository>,
|
||||
pub user_repo: Arc<dyn UserRepository>,
|
||||
pub link_repo: Arc<dyn notes_domain::ports::LinkRepository>,
|
||||
pub note_service: Arc<NoteService>,
|
||||
pub tag_service: Arc<TagService>,
|
||||
pub user_service: Arc<UserService>,
|
||||
@@ -23,6 +24,7 @@ impl AppState {
|
||||
note_repo: Arc<dyn NoteRepository>,
|
||||
tag_repo: Arc<dyn TagRepository>,
|
||||
user_repo: Arc<dyn UserRepository>,
|
||||
link_repo: Arc<dyn notes_domain::ports::LinkRepository>,
|
||||
note_service: Arc<NoteService>,
|
||||
tag_service: Arc<TagService>,
|
||||
user_service: Arc<UserService>,
|
||||
@@ -33,6 +35,7 @@ impl AppState {
|
||||
note_repo,
|
||||
tag_repo,
|
||||
user_repo,
|
||||
link_repo,
|
||||
note_service,
|
||||
tag_service,
|
||||
user_service,
|
||||
|
||||
Reference in New Issue
Block a user