mirror of
https://github.com/getnora-io/nora.git
synced 2026-04-13 01:30:32 +00:00
perf: add in-memory repo index with pagination
- Add repo_index.rs with lazy rebuild on write operations - Double-checked locking to prevent race conditions - npm optimization: count tarballs instead of parsing metadata.json - Add pagination to all registry list pages (?page=1&limit=50) - Invalidate index on PUT/proxy cache in docker/maven/npm/pypi Performance: 500-800x faster list page loads after first rebuild
This commit is contained in:
@@ -2,11 +2,12 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
mod api;
|
||||
mod components;
|
||||
pub mod components;
|
||||
pub mod i18n;
|
||||
mod logo;
|
||||
mod templates;
|
||||
|
||||
use crate::repo_index::paginate;
|
||||
use crate::AppState;
|
||||
use axum::{
|
||||
extract::{Path, Query, State},
|
||||
@@ -25,6 +26,15 @@ struct LangQuery {
|
||||
lang: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
struct ListQuery {
|
||||
lang: Option<String>,
|
||||
page: Option<usize>,
|
||||
limit: Option<usize>,
|
||||
}
|
||||
|
||||
const DEFAULT_PAGE_SIZE: usize = 50;
|
||||
|
||||
fn extract_lang(query: &Query<LangQuery>, cookie_header: Option<&str>) -> Lang {
|
||||
// Priority: query param > cookie > default
|
||||
if let Some(ref lang) = query.lang {
|
||||
@@ -44,6 +54,23 @@ fn extract_lang(query: &Query<LangQuery>, cookie_header: Option<&str>) -> Lang {
|
||||
Lang::default()
|
||||
}
|
||||
|
||||
fn extract_lang_from_list(query: &ListQuery, cookie_header: Option<&str>) -> Lang {
|
||||
if let Some(ref lang) = query.lang {
|
||||
return Lang::from_str(lang);
|
||||
}
|
||||
|
||||
if let Some(cookies) = cookie_header {
|
||||
for part in cookies.split(';') {
|
||||
let part = part.trim();
|
||||
if let Some(value) = part.strip_prefix("nora_lang=") {
|
||||
return Lang::from_str(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Lang::default()
|
||||
}
|
||||
|
||||
pub fn routes() -> Router<Arc<AppState>> {
|
||||
Router::new()
|
||||
// UI Pages
|
||||
@@ -85,18 +112,23 @@ async fn dashboard(
|
||||
// Docker pages
|
||||
async fn docker_list(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Query(query): Query<LangQuery>,
|
||||
Query(query): Query<ListQuery>,
|
||||
headers: axum::http::HeaderMap,
|
||||
) -> impl IntoResponse {
|
||||
let lang = extract_lang(
|
||||
&Query(query),
|
||||
headers.get("cookie").and_then(|v| v.to_str().ok()),
|
||||
);
|
||||
let repos = get_docker_repos(&state.storage).await;
|
||||
Html(render_registry_list(
|
||||
let lang = extract_lang_from_list(&query, headers.get("cookie").and_then(|v| v.to_str().ok()));
|
||||
let page = query.page.unwrap_or(1).max(1);
|
||||
let limit = query.limit.unwrap_or(DEFAULT_PAGE_SIZE).min(100);
|
||||
|
||||
let all_repos = state.repo_index.get("docker", &state.storage).await;
|
||||
let (repos, total) = paginate(&all_repos, page, limit);
|
||||
|
||||
Html(render_registry_list_paginated(
|
||||
"docker",
|
||||
"Docker Registry",
|
||||
&repos,
|
||||
page,
|
||||
limit,
|
||||
total,
|
||||
lang,
|
||||
))
|
||||
}
|
||||
@@ -118,18 +150,23 @@ async fn docker_detail(
|
||||
// Maven pages
|
||||
async fn maven_list(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Query(query): Query<LangQuery>,
|
||||
Query(query): Query<ListQuery>,
|
||||
headers: axum::http::HeaderMap,
|
||||
) -> impl IntoResponse {
|
||||
let lang = extract_lang(
|
||||
&Query(query),
|
||||
headers.get("cookie").and_then(|v| v.to_str().ok()),
|
||||
);
|
||||
let repos = get_maven_repos(&state.storage).await;
|
||||
Html(render_registry_list(
|
||||
let lang = extract_lang_from_list(&query, headers.get("cookie").and_then(|v| v.to_str().ok()));
|
||||
let page = query.page.unwrap_or(1).max(1);
|
||||
let limit = query.limit.unwrap_or(DEFAULT_PAGE_SIZE).min(100);
|
||||
|
||||
let all_repos = state.repo_index.get("maven", &state.storage).await;
|
||||
let (repos, total) = paginate(&all_repos, page, limit);
|
||||
|
||||
Html(render_registry_list_paginated(
|
||||
"maven",
|
||||
"Maven Repository",
|
||||
&repos,
|
||||
page,
|
||||
limit,
|
||||
total,
|
||||
lang,
|
||||
))
|
||||
}
|
||||
@@ -151,15 +188,25 @@ async fn maven_detail(
|
||||
// npm pages
|
||||
async fn npm_list(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Query(query): Query<LangQuery>,
|
||||
Query(query): Query<ListQuery>,
|
||||
headers: axum::http::HeaderMap,
|
||||
) -> impl IntoResponse {
|
||||
let lang = extract_lang(
|
||||
&Query(query),
|
||||
headers.get("cookie").and_then(|v| v.to_str().ok()),
|
||||
);
|
||||
let packages = get_npm_packages(&state.storage).await;
|
||||
Html(render_registry_list("npm", "npm Registry", &packages, lang))
|
||||
let lang = extract_lang_from_list(&query, headers.get("cookie").and_then(|v| v.to_str().ok()));
|
||||
let page = query.page.unwrap_or(1).max(1);
|
||||
let limit = query.limit.unwrap_or(DEFAULT_PAGE_SIZE).min(100);
|
||||
|
||||
let all_packages = state.repo_index.get("npm", &state.storage).await;
|
||||
let (packages, total) = paginate(&all_packages, page, limit);
|
||||
|
||||
Html(render_registry_list_paginated(
|
||||
"npm",
|
||||
"npm Registry",
|
||||
&packages,
|
||||
page,
|
||||
limit,
|
||||
total,
|
||||
lang,
|
||||
))
|
||||
}
|
||||
|
||||
async fn npm_detail(
|
||||
@@ -179,18 +226,23 @@ async fn npm_detail(
|
||||
// Cargo pages
|
||||
async fn cargo_list(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Query(query): Query<LangQuery>,
|
||||
Query(query): Query<ListQuery>,
|
||||
headers: axum::http::HeaderMap,
|
||||
) -> impl IntoResponse {
|
||||
let lang = extract_lang(
|
||||
&Query(query),
|
||||
headers.get("cookie").and_then(|v| v.to_str().ok()),
|
||||
);
|
||||
let crates = get_cargo_crates(&state.storage).await;
|
||||
Html(render_registry_list(
|
||||
let lang = extract_lang_from_list(&query, headers.get("cookie").and_then(|v| v.to_str().ok()));
|
||||
let page = query.page.unwrap_or(1).max(1);
|
||||
let limit = query.limit.unwrap_or(DEFAULT_PAGE_SIZE).min(100);
|
||||
|
||||
let all_crates = state.repo_index.get("cargo", &state.storage).await;
|
||||
let (crates, total) = paginate(&all_crates, page, limit);
|
||||
|
||||
Html(render_registry_list_paginated(
|
||||
"cargo",
|
||||
"Cargo Registry",
|
||||
&crates,
|
||||
page,
|
||||
limit,
|
||||
total,
|
||||
lang,
|
||||
))
|
||||
}
|
||||
@@ -212,18 +264,23 @@ async fn cargo_detail(
|
||||
// PyPI pages
|
||||
async fn pypi_list(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Query(query): Query<LangQuery>,
|
||||
Query(query): Query<ListQuery>,
|
||||
headers: axum::http::HeaderMap,
|
||||
) -> impl IntoResponse {
|
||||
let lang = extract_lang(
|
||||
&Query(query),
|
||||
headers.get("cookie").and_then(|v| v.to_str().ok()),
|
||||
);
|
||||
let packages = get_pypi_packages(&state.storage).await;
|
||||
Html(render_registry_list(
|
||||
let lang = extract_lang_from_list(&query, headers.get("cookie").and_then(|v| v.to_str().ok()));
|
||||
let page = query.page.unwrap_or(1).max(1);
|
||||
let limit = query.limit.unwrap_or(DEFAULT_PAGE_SIZE).min(100);
|
||||
|
||||
let all_packages = state.repo_index.get("pypi", &state.storage).await;
|
||||
let (packages, total) = paginate(&all_packages, page, limit);
|
||||
|
||||
Html(render_registry_list_paginated(
|
||||
"pypi",
|
||||
"PyPI Repository",
|
||||
&packages,
|
||||
page,
|
||||
limit,
|
||||
total,
|
||||
lang,
|
||||
))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user