feat: release build bundles assets
@@ -1046,6 +1046,10 @@ dependencies = [
|
|||||||
"futures",
|
"futures",
|
||||||
"mime",
|
"mime",
|
||||||
"mime_guess",
|
"mime_guess",
|
||||||
|
"minijinja",
|
||||||
|
"minijinja-autoreload",
|
||||||
|
"minijinja-contrib",
|
||||||
|
"minijinja-embed",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@@ -2014,6 +2018,7 @@ dependencies = [
|
|||||||
"common",
|
"common",
|
||||||
"composite-retrieval",
|
"composite-retrieval",
|
||||||
"futures",
|
"futures",
|
||||||
|
"include_dir",
|
||||||
"json-stream-parser",
|
"json-stream-parser",
|
||||||
"minijinja",
|
"minijinja",
|
||||||
"minijinja-autoreload",
|
"minijinja-autoreload",
|
||||||
@@ -2027,6 +2032,7 @@ dependencies = [
|
|||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tower-http",
|
"tower-http",
|
||||||
|
"tower-serve-static",
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -2368,6 +2374,25 @@ dependencies = [
|
|||||||
"icu_properties",
|
"icu_properties",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "include_dir"
|
||||||
|
version = "0.7.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd"
|
||||||
|
dependencies = [
|
||||||
|
"include_dir_macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "include_dir_macros"
|
||||||
|
version = "0.7.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "1.9.3"
|
version = "1.9.3"
|
||||||
@@ -3421,6 +3446,26 @@ version = "0.5.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315"
|
checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pin-project"
|
||||||
|
version = "1.1.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a"
|
||||||
|
dependencies = [
|
||||||
|
"pin-project-internal",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pin-project-internal"
|
||||||
|
version = "1.1.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.87",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-project-lite"
|
name = "pin-project-lite"
|
||||||
version = "0.2.14"
|
version = "0.2.14"
|
||||||
@@ -5284,6 +5329,27 @@ version = "0.3.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
|
checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tower-serve-static"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "148022c3d604d85a3b558ef7154aa90aaec5f9506beae64f6ad4e856306d287f"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"futures-util",
|
||||||
|
"http",
|
||||||
|
"http-body",
|
||||||
|
"http-body-util",
|
||||||
|
"include_dir",
|
||||||
|
"mime",
|
||||||
|
"mime_guess",
|
||||||
|
"percent-encoding",
|
||||||
|
"pin-project",
|
||||||
|
"tokio",
|
||||||
|
"tokio-util",
|
||||||
|
"tower-service",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tower-service"
|
name = "tower-service"
|
||||||
version = "0.3.3"
|
version = "0.3.3"
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use super::types::{analytics::Analytics, system_settings::SystemSettings, Stored
|
|||||||
use axum_session::{SessionConfig, SessionError, SessionStore};
|
use axum_session::{SessionConfig, SessionError, SessionStore};
|
||||||
use axum_session_surreal::SessionSurrealPool;
|
use axum_session_surreal::SessionSurrealPool;
|
||||||
use futures::Stream;
|
use futures::Stream;
|
||||||
use std::ops::Deref;
|
use std::{ops::Deref, sync::Arc};
|
||||||
use surrealdb::{
|
use surrealdb::{
|
||||||
engine::any::{connect, Any},
|
engine::any::{connect, Any},
|
||||||
opt::auth::Root,
|
opt::auth::Root,
|
||||||
@@ -15,6 +15,9 @@ use surrealdb::{
|
|||||||
pub struct SurrealDbClient {
|
pub struct SurrealDbClient {
|
||||||
pub client: Surreal<Any>,
|
pub client: Surreal<Any>,
|
||||||
}
|
}
|
||||||
|
pub trait ProvidesDb {
|
||||||
|
fn db(&self) -> &Arc<SurrealDbClient>;
|
||||||
|
}
|
||||||
|
|
||||||
impl SurrealDbClient {
|
impl SurrealDbClient {
|
||||||
/// # Initialize a new datbase client
|
/// # Initialize a new datbase client
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ pub use minijinja_contrib;
|
|||||||
pub use minijinja_embed;
|
pub use minijinja_embed;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
pub trait ProvidesTemplateEngine {
|
||||||
|
fn template_engine(&self) -> &Arc<TemplateEngine>;
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum TemplateEngine {
|
pub enum TemplateEngine {
|
||||||
// Use AutoReload for debug builds (debug_assertions is true)
|
// Use AutoReload for debug builds (debug_assertions is true)
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ plotly = "0.12.1"
|
|||||||
surrealdb = "2.0.4"
|
surrealdb = "2.0.4"
|
||||||
tower-http = { version = "0.6.2", features = ["fs"] }
|
tower-http = { version = "0.6.2", features = ["fs"] }
|
||||||
chrono-tz = "0.10.1"
|
chrono-tz = "0.10.1"
|
||||||
|
tower-serve-static = "0.1.1"
|
||||||
|
include_dir = "0.7.4"
|
||||||
|
|
||||||
common = { path = "../common" }
|
common = { path = "../common" }
|
||||||
composite-retrieval = { path = "../composite-retrieval" }
|
composite-retrieval = { path = "../composite-retrieval" }
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 252 KiB After Width: | Height: | Size: 252 KiB |
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 790 B After Width: | Height: | Size: 790 B |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
@@ -1,48 +1,43 @@
|
|||||||
use axum_session::SessionStore;
|
|
||||||
use axum_session_surreal::SessionSurrealPool;
|
|
||||||
use common::create_template_engine;
|
|
||||||
use common::storage::db::SurrealDbClient;
|
use common::storage::db::SurrealDbClient;
|
||||||
use common::utils::config::AppConfig;
|
use common::utils::template_engine::{ProvidesTemplateEngine, TemplateEngine};
|
||||||
use common::utils::template_engine::TemplateEngine;
|
use common::{create_template_engine, storage::db::ProvidesDb};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use surrealdb::engine::any::Any;
|
use tracing::debug;
|
||||||
|
|
||||||
|
use crate::{OpenAIClientType, SessionStoreType};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct HtmlState {
|
pub struct HtmlState {
|
||||||
pub db: Arc<SurrealDbClient>,
|
pub db: Arc<SurrealDbClient>,
|
||||||
pub openai_client: Arc<async_openai::Client<async_openai::config::OpenAIConfig>>,
|
pub openai_client: Arc<OpenAIClientType>,
|
||||||
pub templates: Arc<TemplateEngine>,
|
pub templates: Arc<TemplateEngine>,
|
||||||
pub session_store: Arc<SessionStore<SessionSurrealPool<Any>>>,
|
pub session_store: Arc<SessionStoreType>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HtmlState {
|
impl HtmlState {
|
||||||
pub async fn new(config: &AppConfig) -> Result<Self, Box<dyn std::error::Error>> {
|
pub fn new_with_resources(
|
||||||
|
db: Arc<SurrealDbClient>,
|
||||||
|
openai_client: Arc<OpenAIClientType>,
|
||||||
|
session_store: Arc<SessionStoreType>,
|
||||||
|
) -> Result<Self, Box<dyn std::error::Error>> {
|
||||||
let template_engine = create_template_engine!("templates");
|
let template_engine = create_template_engine!("templates");
|
||||||
|
debug!("Template engine created for html_router.");
|
||||||
|
|
||||||
let surreal_db_client = Arc::new(
|
Ok(Self {
|
||||||
SurrealDbClient::new(
|
db,
|
||||||
&config.surrealdb_address,
|
openai_client,
|
||||||
&config.surrealdb_username,
|
|
||||||
&config.surrealdb_password,
|
|
||||||
&config.surrealdb_namespace,
|
|
||||||
&config.surrealdb_database,
|
|
||||||
)
|
|
||||||
.await?,
|
|
||||||
);
|
|
||||||
|
|
||||||
surreal_db_client.ensure_initialized().await?;
|
|
||||||
|
|
||||||
let openai_client = Arc::new(async_openai::Client::new());
|
|
||||||
|
|
||||||
let session_store = Arc::new(surreal_db_client.create_session_store().await?);
|
|
||||||
|
|
||||||
let app_state = HtmlState {
|
|
||||||
db: surreal_db_client.clone(),
|
|
||||||
templates: Arc::new(template_engine),
|
|
||||||
openai_client: openai_client.clone(),
|
|
||||||
session_store,
|
session_store,
|
||||||
};
|
templates: Arc::new(template_engine),
|
||||||
|
})
|
||||||
Ok(app_state)
|
}
|
||||||
|
}
|
||||||
|
impl ProvidesDb for HtmlState {
|
||||||
|
fn db(&self) -> &Arc<SurrealDbClient> {
|
||||||
|
&self.db
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl ProvidesTemplateEngine for HtmlState {
|
||||||
|
fn template_engine(&self) -> &Arc<TemplateEngine> {
|
||||||
|
&self.templates
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ pub mod router_factory;
|
|||||||
pub mod routes;
|
pub mod routes;
|
||||||
|
|
||||||
use axum::{extract::FromRef, Router};
|
use axum::{extract::FromRef, Router};
|
||||||
use axum_session::Session;
|
use axum_session::{Session, SessionStore};
|
||||||
use axum_session_auth::AuthSession;
|
use axum_session_auth::AuthSession;
|
||||||
use axum_session_surreal::SessionSurrealPool;
|
use axum_session_surreal::SessionSurrealPool;
|
||||||
use common::storage::types::user::User;
|
use common::storage::types::user::User;
|
||||||
@@ -14,6 +14,8 @@ use surrealdb::{engine::any::Any, Surreal};
|
|||||||
|
|
||||||
pub type AuthSessionType = AuthSession<User, String, SessionSurrealPool<Any>, Surreal<Any>>;
|
pub type AuthSessionType = AuthSession<User, String, SessionSurrealPool<Any>, Surreal<Any>>;
|
||||||
pub type SessionType = Session<SessionSurrealPool<Any>>;
|
pub type SessionType = Session<SessionSurrealPool<Any>>;
|
||||||
|
pub type SessionStoreType = SessionStore<SessionSurrealPool<Any>>;
|
||||||
|
pub type OpenAIClientType = async_openai::Client<async_openai::config::OpenAIConfig>;
|
||||||
|
|
||||||
/// Html routes
|
/// Html routes
|
||||||
pub fn html_routes<S>(app_state: &HtmlState) -> Router<S>
|
pub fn html_routes<S>(app_state: &HtmlState) -> Router<S>
|
||||||
|
|||||||
@@ -3,31 +3,28 @@ use axum::{
|
|||||||
middleware::Next,
|
middleware::Next,
|
||||||
response::Response,
|
response::Response,
|
||||||
};
|
};
|
||||||
use axum_session_surreal::SessionSurrealPool;
|
|
||||||
use surrealdb::engine::any::Any;
|
|
||||||
|
|
||||||
use common::storage::types::analytics::Analytics;
|
use common::storage::{db::ProvidesDb, types::analytics::Analytics};
|
||||||
|
|
||||||
use crate::html_state::HtmlState;
|
use crate::SessionType;
|
||||||
|
|
||||||
pub async fn analytics_middleware(
|
/// Middleware to count unique visitors and page loads
|
||||||
State(state): State<HtmlState>,
|
pub async fn analytics_middleware<S>(
|
||||||
session: axum_session::Session<SessionSurrealPool<Any>>,
|
State(state): State<S>,
|
||||||
|
session: SessionType,
|
||||||
request: Request,
|
request: Request,
|
||||||
next: Next,
|
next: Next,
|
||||||
) -> Response {
|
) -> Response
|
||||||
// Get the path from the request
|
where
|
||||||
|
S: ProvidesDb + Clone + Send + Sync + 'static,
|
||||||
|
{
|
||||||
let path = request.uri().path();
|
let path = request.uri().path();
|
||||||
|
if !path.starts_with("/assets") && !path.contains('.') {
|
||||||
// Only count if it's a main page request (not assets or other resources)
|
|
||||||
if !path.starts_with("/assets") && !path.starts_with("/_next") && !path.contains('.') {
|
|
||||||
if !session.get::<bool>("counted_visitor").unwrap_or(false) {
|
if !session.get::<bool>("counted_visitor").unwrap_or(false) {
|
||||||
let _ = Analytics::increment_visitors(&state.db).await;
|
let _ = Analytics::increment_visitors(state.db()).await;
|
||||||
session.set("counted_visitor", true);
|
session.set("counted_visitor", true);
|
||||||
}
|
}
|
||||||
|
let _ = Analytics::increment_page_loads(state.db()).await;
|
||||||
let _ = Analytics::increment_page_loads(&state.db).await;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
next.run(request).await
|
next.run(request).await
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
use crate::html_state::HtmlState;
|
|
||||||
use axum::{
|
use axum::{
|
||||||
extract::State,
|
extract::State,
|
||||||
http::StatusCode,
|
http::StatusCode,
|
||||||
response::{Html, IntoResponse, Response},
|
response::{Html, IntoResponse, Response},
|
||||||
Extension,
|
Extension,
|
||||||
};
|
};
|
||||||
use common::error::AppError;
|
use common::{error::AppError, utils::template_engine::ProvidesTemplateEngine};
|
||||||
use minijinja::{context, Value};
|
use minijinja::{context, Value};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
@@ -106,58 +105,45 @@ impl IntoResponse for TemplateResponse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TemplateStateWrapper {
|
pub async fn with_template_response<S>(State(state): State<S>, response: Response) -> Response
|
||||||
state: HtmlState,
|
where
|
||||||
template_response: TemplateResponse,
|
S: ProvidesTemplateEngine + Clone + Send + Sync + 'static,
|
||||||
}
|
{
|
||||||
|
if let Some(template_response) = response.extensions().get::<TemplateResponse>().cloned() {
|
||||||
|
let template_engine = state.template_engine();
|
||||||
|
|
||||||
impl IntoResponse for TemplateStateWrapper {
|
match &template_response.template_kind {
|
||||||
fn into_response(self) -> Response {
|
|
||||||
let template_engine = &self.state.templates;
|
|
||||||
|
|
||||||
match &self.template_response.template_kind {
|
|
||||||
TemplateKind::Full(name) => {
|
TemplateKind::Full(name) => {
|
||||||
match template_engine.render(name, &self.template_response.context) {
|
match template_engine.render(name, &template_response.context) {
|
||||||
Ok(html) => Html(html).into_response(),
|
Ok(html) => Html(html).into_response(),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Failed to render template: {:?}", e);
|
error!("Failed to render template '{}': {:?}", name, e);
|
||||||
(StatusCode::INTERNAL_SERVER_ERROR, fallback_error()).into_response()
|
(StatusCode::INTERNAL_SERVER_ERROR, fallback_error()).into_response()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TemplateKind::Partial(template, block) => {
|
TemplateKind::Partial(template, block) => {
|
||||||
match template_engine.render_block(template, block, &self.template_response.context)
|
match template_engine.render_block(template, block, &template_response.context) {
|
||||||
{
|
|
||||||
Ok(html) => Html(html).into_response(),
|
Ok(html) => Html(html).into_response(),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Failed to render block: {:?}", e);
|
error!("Failed to render block '{}/{}': {:?}", template, block, e);
|
||||||
(StatusCode::INTERNAL_SERVER_ERROR, fallback_error()).into_response()
|
(StatusCode::INTERNAL_SERVER_ERROR, fallback_error()).into_response()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TemplateKind::Error(status) => {
|
TemplateKind::Error(status) => {
|
||||||
match template_engine.render("errors/error.html", &self.template_response.context) {
|
match template_engine.render("errors/error.html", &template_response.context) {
|
||||||
Ok(html) => (*status, Html(html)).into_response(),
|
Ok(html) => (*status, Html(html)).into_response(),
|
||||||
Err(_) => (*status, fallback_error()).into_response(),
|
Err(e) => {
|
||||||
|
error!("Failed to render error template: {:?}", e);
|
||||||
|
(*status, fallback_error()).into_response()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TemplateKind::Redirect(path) => {
|
TemplateKind::Redirect(path) => {
|
||||||
(StatusCode::OK, [(axum_htmx::HX_REDIRECT, path.clone())], "").into_response()
|
(StatusCode::OK, [(axum_htmx::HX_REDIRECT, path.clone())], "").into_response()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn with_template_response(
|
|
||||||
State(state): State<HtmlState>,
|
|
||||||
response: Response,
|
|
||||||
) -> Response {
|
|
||||||
if let Some(template_response) = response.extensions().get::<TemplateResponse>().cloned() {
|
|
||||||
TemplateStateWrapper {
|
|
||||||
state,
|
|
||||||
template_response,
|
|
||||||
}
|
|
||||||
.into_response()
|
|
||||||
} else {
|
} else {
|
||||||
response
|
response
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ use axum_session_auth::{AuthConfig, AuthSessionLayer};
|
|||||||
use axum_session_surreal::SessionSurrealPool;
|
use axum_session_surreal::SessionSurrealPool;
|
||||||
use common::storage::types::user::User;
|
use common::storage::types::user::User;
|
||||||
use surrealdb::{engine::any::Any, Surreal};
|
use surrealdb::{engine::any::Any, Surreal};
|
||||||
use tower_http::services::ServeDir;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
html_state::HtmlState,
|
html_state::HtmlState,
|
||||||
@@ -18,6 +17,27 @@ use crate::{
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! create_asset_service {
|
||||||
|
// Takes the relative path to the asset directory
|
||||||
|
($relative_path:expr) => {{
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
{
|
||||||
|
let crate_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||||
|
let assets_path = crate_dir.join($relative_path);
|
||||||
|
tracing::debug!("Assets: Serving from filesystem: {:?}", assets_path);
|
||||||
|
tower_http::services::ServeDir::new(assets_path)
|
||||||
|
}
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
{
|
||||||
|
tracing::debug!("Assets: Serving embedded directory");
|
||||||
|
static ASSETS_DIR: include_dir::Dir<'static> =
|
||||||
|
include_dir::include_dir!("$CARGO_MANIFEST_DIR/assets");
|
||||||
|
tower_serve_static::ServeDir::new(&ASSETS_DIR)
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
pub struct RouterFactory<S> {
|
pub struct RouterFactory<S> {
|
||||||
app_state: HtmlState,
|
app_state: HtmlState,
|
||||||
public_routers: Vec<Router<S>>,
|
public_routers: Vec<Router<S>>,
|
||||||
@@ -108,27 +128,35 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add public assets to public router
|
// Add public assets to public router
|
||||||
if let Some(assets) = self.public_assets_config {
|
if let Some(assets_config) = self.public_assets_config {
|
||||||
public_router =
|
// Call the macro using the stored relative directory path
|
||||||
public_router.nest_service(&assets.path, ServeDir::new(assets.directory));
|
let asset_service = create_asset_service!(&assets_config.directory);
|
||||||
|
// Nest the resulting service under the stored URL path
|
||||||
|
public_router = public_router.nest_service(&assets_config.path, asset_service);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start with an empty protected router
|
// Start with an empty protected router
|
||||||
let mut protected_router = Router::new();
|
let mut protected_router = Router::new();
|
||||||
|
|
||||||
// Merge all protected routers
|
// Check if there are any protected routers
|
||||||
|
let has_protected_routes =
|
||||||
|
!self.protected_routers.is_empty() || !self.nested_protected_routes.is_empty();
|
||||||
|
|
||||||
|
// Merge root-level protected routers
|
||||||
for router in self.protected_routers {
|
for router in self.protected_routers {
|
||||||
protected_router = protected_router.merge(router);
|
protected_router = protected_router.merge(router);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add nested protected routes
|
// Nest protected routers
|
||||||
for (path, router) in self.nested_protected_routes {
|
for (path, router) in self.nested_protected_routes {
|
||||||
protected_router = protected_router.nest(&path, router);
|
protected_router = protected_router.nest(&path, router);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply auth middleware to all protected routes
|
// Apply auth middleware
|
||||||
let protected_router =
|
if has_protected_routes {
|
||||||
protected_router.route_layer(from_fn_with_state(self.app_state.clone(), require_auth));
|
protected_router = protected_router
|
||||||
|
.route_layer(from_fn_with_state(self.app_state.clone(), require_auth));
|
||||||
|
}
|
||||||
|
|
||||||
// Combine public and protected routes
|
// Combine public and protected routes
|
||||||
let mut router = Router::new().merge(public_router).merge(protected_router);
|
let mut router = Router::new().merge(public_router).merge(protected_router);
|
||||||
@@ -142,11 +170,11 @@ where
|
|||||||
router
|
router
|
||||||
.layer(from_fn_with_state(
|
.layer(from_fn_with_state(
|
||||||
self.app_state.clone(),
|
self.app_state.clone(),
|
||||||
analytics_middleware,
|
analytics_middleware::<HtmlState>,
|
||||||
))
|
))
|
||||||
.layer(map_response_with_state(
|
.layer(map_response_with_state(
|
||||||
self.app_state.clone(),
|
self.app_state.clone(),
|
||||||
with_template_response,
|
with_template_response::<HtmlState>,
|
||||||
))
|
))
|
||||||
.layer(
|
.layer(
|
||||||
AuthSessionLayer::<User, String, SessionSurrealPool<Any>, Surreal<Any>>::new(Some(
|
AuthSessionLayer::<User, String, SessionSurrealPool<Any>, Surreal<Any>>::new(Some(
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
{% extends "body_base.html" %}
|
|
||||||
{% block main %}
|
|
||||||
<main class="flex justify-center grow mt-2 sm:mt-4 pb-10">
|
|
||||||
<div class="container">
|
|
||||||
<div class="grid grid-cols-1 lg:grid-cols-[auto_1fr] gap-4">
|
|
||||||
<!-- Documentation Menu -->
|
|
||||||
<aside class="bg-base-200 rounded-lg p-4">
|
|
||||||
{% include "documentation/menu.html" %}
|
|
||||||
</aside>
|
|
||||||
<!-- Main Content -->
|
|
||||||
|
|
||||||
<article
|
|
||||||
class="prose prose-sm md:prose-base prose-h1:mb-2 prose-h2:my-2 prose-p:my-2 prose-ul:my-2 prose-pre:my-2 flex mx-auto justify-center flex-col">
|
|
||||||
{% block article %}
|
|
||||||
{% endblock %}
|
|
||||||
</article>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
{% endblock %}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
{% extends 'documentation/base.html' %}
|
|
||||||
{% block article %}
|
|
||||||
<h1>Get Started with Minne</h1>
|
|
||||||
<p>Minne offers two installation options to suit your needs:</p>
|
|
||||||
<ol>
|
|
||||||
<li>
|
|
||||||
<strong>Hosted Version:</strong> Enjoy a hassle‐free experience by signing up for the ready‐to‐use service.
|
|
||||||
Simply navigate to <code>/signup</code> to create an account.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Self-Hosted:</strong> Gain full control by running Minne on your own infrastructure. Visit
|
|
||||||
<a href="https://github.com/perstarkse/minne">GitHub</a> to download the latest release. After extracting the
|
|
||||||
release, open the <code>config.yaml</code> file and set the following configurations:
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
<pre class="overflow-x-auto text-sm">
|
|
||||||
<code class="break-words whitespace-pre-wrap">
|
|
||||||
OPENAI_API_KEY: your_api_key
|
|
||||||
DB_ADDRESS: your_db_address
|
|
||||||
DB_USER: your_db_user
|
|
||||||
DB_PASSWORD: your_db_password
|
|
||||||
</code>
|
|
||||||
</pre>
|
|
||||||
<p>The database settings relate to a running instance of SurrealDB. You can opt for their cloud solution or run your
|
|
||||||
own instance.</p>
|
|
||||||
|
|
||||||
<p>Once your configuration is complete, start both the server and the worker. They can be hosted on separate
|
|
||||||
machines, with different resource requirements:</p>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<strong>Server:</strong> Lightweight. A minimum of 1 core and 256MB of RAM is recommended.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Worker:</strong> Handles content parsing and creation of database entities. It's recommended to allocate at
|
|
||||||
least two cores and 1024 MB RAM. It will run on less but might run into constraints depending on the content being
|
|
||||||
parsed.
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>After launching the services, navigate to <code><your_url>:3000/signup</code> to register. The first
|
|
||||||
account created will automatically receive admin permissions, allowing you to later disable further registrations
|
|
||||||
via the <code>/admin</code> page if desired.</p>
|
|
||||||
|
|
||||||
<p>From the homepage (<code>/</code>), you can:</p>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>Submit content, including files, videos, and URLs for ingestion.</li>
|
|
||||||
<li>Monitor job statuses and manage your existing content.</li>
|
|
||||||
<li>Search your content or start a chat conversation for assistance.</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<p>Visit the <code>/knowledge</code> page to view your content organized by different sections. This page also
|
|
||||||
provides a visual demonstration of the graph database structure, enhancing your understanding of content
|
|
||||||
relationships.</p>
|
|
||||||
|
|
||||||
<p>This streamlined setup ensures intuitive onboarding while offering robust customization options. Whether you are
|
|
||||||
a novice or an advanced user, Minne is designed to deliver a smooth experience and reliable performance.</p>
|
|
||||||
{% endblock %}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
{% extends "documentation/base.html" %}
|
|
||||||
{% block article %}
|
|
||||||
<h1 class="text-3xl mb-2">Documentation</h1>
|
|
||||||
<p>
|
|
||||||
Personalised Knowledge Management (PKM) is a system designed to help individuals organise, store, and retrieve
|
|
||||||
information effectively. It empowers users to create a personalised workflow for managing knowledge, enabling
|
|
||||||
better decision-making and productivity.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
This documentation will guide you through the core concepts, tools, and best practices for building and
|
|
||||||
maintaining your own PKM system.
|
|
||||||
</p>
|
|
||||||
<div class="card bg-base-200 rounded-lg shadow-md">
|
|
||||||
<div class="card-body">
|
|
||||||
<h3 class="card-title not-prose">Getting Started</h3>
|
|
||||||
<p>
|
|
||||||
To begin, explore the sections in the navigation menu. Each section provides detailed insights into
|
|
||||||
different
|
|
||||||
aspects of PKM, from foundational principles to advanced techniques.
|
|
||||||
</p>
|
|
||||||
<div class="card-actions">
|
|
||||||
<a href="/documentation/quick-start" class="btn btn-primary text-primary-content" hx-boost="true">Learn More</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
<ul class="menu bg-base-200 rounded-box w-full ">
|
|
||||||
<li><a hx-boost="true" class="{{'menu-active' if current_path=='/index' }}" href="/documentation">Start</a></li>
|
|
||||||
<li><a hx-boost="true" class="{{'menu-active' if current_path=='/get-started' }}"
|
|
||||||
href="/documentation/get-started">Get Started</a></li>
|
|
||||||
<li><a hx-boost="true" class="{{'menu-active' if current_path=='/mobile-friendly' }}"
|
|
||||||
href="/documentation/mobile-friendly">Mobile friendly</a></li>
|
|
||||||
<li><a hx-boost="true" class="{{'menu-active' if current_path=='/privacy-policy' }}"
|
|
||||||
href="/documentation/privacy-policy">Privacy Policy</a></li>
|
|
||||||
<li>
|
|
||||||
<details open>
|
|
||||||
<summary>Core Concepts</summary>
|
|
||||||
<ul>
|
|
||||||
<li><a hx-boost="true" href="/documentation/submenu1">What is PKM?</a></li>
|
|
||||||
<li><a hx-boost="true" href="/documentation/submenu2">Benefits of PKM</a></li>
|
|
||||||
</ul>
|
|
||||||
</details>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<details>
|
|
||||||
<summary>Tools & Techniques</summary>
|
|
||||||
<ul>
|
|
||||||
<li><a hx-boost="true" href="/documentation/tools">Tools for PKM</a></li>
|
|
||||||
<li><a hx-boost="true" href="/documentation/techniques">Effective Techniques</a></li>
|
|
||||||
</ul>
|
|
||||||
</details>
|
|
||||||
</li>
|
|
||||||
<li><a hx-boost="true" href="/documentation/faq">FAQ</a></li>
|
|
||||||
</ul>
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
{% extends 'documentation/base.html' %}
|
|
||||||
{% block article %}
|
|
||||||
<h1>Mobile Friendly Ingression: How to Submit Content from iOS to Minne</h1>
|
|
||||||
<p>Minne is built with simplicity in mind. Whether you wish to save a file, capture a thought, or share a page,
|
|
||||||
submitting content is effortless. Our server provides API access that enables users to perform actions using a
|
|
||||||
personalized API key.</p>
|
|
||||||
|
|
||||||
<p>An iOS shortcut has been developed to streamline the process of sending content. To begin, navigate to
|
|
||||||
<code>/account</code> and generate an API key. Once created, you will see an option to download the iOS shortcut.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>After downloading the shortcut, update the "Get response from URL" authentication headers with your API key. If
|
|
||||||
you are self-hosting, ensure the URL is adjusted accordingly.</p>
|
|
||||||
|
|
||||||
<p>The shortcut integrates seamlessly with iOS. When you "share with Minne," you will be prompted to provide
|
|
||||||
instructions to the AI and to either choose an existing category or create a new one for your submission.</p>
|
|
||||||
|
|
||||||
<p>While an Android solution is in the works, for now you can add the web app to your home screen as a Progressive
|
|
||||||
Web App (PWA) for a similar mobile-friendly experience.</p>
|
|
||||||
{% endblock %}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
{% extends 'documentation/base.html' %}
|
|
||||||
{% block article %}
|
|
||||||
<h1>Privacy Policy</h1>
|
|
||||||
<p>We value your privacy and are committed to protecting your personal information. This policy
|
|
||||||
outlines how we handle your data and ensures transparency in our practices.</p>
|
|
||||||
|
|
||||||
<h2>Data Collection</h2>
|
|
||||||
<p>We only collect data that is necessary for the functionality of our services. Any data you upload to
|
|
||||||
our site remains your property and will not be shared with third parties unless required by law.</p>
|
|
||||||
|
|
||||||
<h2>Cookies</h2>
|
|
||||||
<p>We do not use cookies for tracking or analytics. The cookies we employ are strictly for session
|
|
||||||
management, ensuring a smooth and secure user experience.</p>
|
|
||||||
|
|
||||||
<h2>No Unnecessary Data Extraction</h2>
|
|
||||||
<p>We believe that unnecessary data extraction is unethical and a poor practice. We only collect the
|
|
||||||
minimum amount of data required to provide our services effectively, ensuring your privacy is respected at all
|
|
||||||
times.</p>
|
|
||||||
|
|
||||||
<h2>Your Rights</h2>
|
|
||||||
<p>You have the right to access, modify, or delete your data at any time. If you have any concerns
|
|
||||||
about how your data is handled, please contact us.</p>
|
|
||||||
|
|
||||||
<h2>Changes to This Policy</h2>
|
|
||||||
<p>We may update this privacy policy from time to time. Any changes will be posted on this page, and we
|
|
||||||
encourage you to review it periodically.</p>
|
|
||||||
{% endblock %}
|
|
||||||
@@ -21,8 +21,26 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
// Get config
|
// Get config
|
||||||
let config = get_config()?;
|
let config = get_config()?;
|
||||||
|
|
||||||
// Set up server components
|
// Set up router states
|
||||||
let html_state = HtmlState::new(&config).await?;
|
let db = Arc::new(
|
||||||
|
SurrealDbClient::new(
|
||||||
|
&config.surrealdb_address,
|
||||||
|
&config.surrealdb_username,
|
||||||
|
&config.surrealdb_password,
|
||||||
|
&config.surrealdb_namespace,
|
||||||
|
&config.surrealdb_database,
|
||||||
|
)
|
||||||
|
.await?,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Ensure db is initialized
|
||||||
|
db.ensure_initialized().await?;
|
||||||
|
|
||||||
|
let session_store = Arc::new(db.create_session_store().await?);
|
||||||
|
let openai_client = Arc::new(async_openai::Client::new());
|
||||||
|
|
||||||
|
let html_state = HtmlState::new_with_resources(db, openai_client, session_store)?;
|
||||||
|
|
||||||
let api_state = ApiState {
|
let api_state = ApiState {
|
||||||
db: html_state.db.clone(),
|
db: html_state.db.clone(),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use api_router::{api_routes_v1, api_state::ApiState};
|
use api_router::{api_routes_v1, api_state::ApiState};
|
||||||
use axum::{extract::FromRef, Router};
|
use axum::{extract::FromRef, Router};
|
||||||
use common::utils::config::get_config;
|
use common::{storage::db::SurrealDbClient, utils::config::get_config};
|
||||||
use html_router::{html_routes, html_state::HtmlState};
|
use html_router::{html_routes, html_state::HtmlState};
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
use tracing_subscriber::{fmt, prelude::*, EnvFilter};
|
use tracing_subscriber::{fmt, prelude::*, EnvFilter};
|
||||||
@@ -18,7 +20,25 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
let config = get_config()?;
|
let config = get_config()?;
|
||||||
|
|
||||||
// Set up router states
|
// Set up router states
|
||||||
let html_state = HtmlState::new(&config).await?;
|
let db = Arc::new(
|
||||||
|
SurrealDbClient::new(
|
||||||
|
&config.surrealdb_address,
|
||||||
|
&config.surrealdb_username,
|
||||||
|
&config.surrealdb_password,
|
||||||
|
&config.surrealdb_namespace,
|
||||||
|
&config.surrealdb_database,
|
||||||
|
)
|
||||||
|
.await?,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Ensure db is initialized
|
||||||
|
db.ensure_initialized().await?;
|
||||||
|
|
||||||
|
let session_store = Arc::new(db.create_session_store().await?);
|
||||||
|
let openai_client = Arc::new(async_openai::Client::new());
|
||||||
|
|
||||||
|
let html_state = HtmlState::new_with_resources(db, openai_client, session_store)?;
|
||||||
|
|
||||||
let api_state = ApiState {
|
let api_state = ApiState {
|
||||||
db: html_state.db.clone(),
|
db: html_state.db.clone(),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
\[\] configs primarily get envs
|
\[\] configs primarily get envs
|
||||||
\[\] filtering on categories
|
\[\] filtering on categories
|
||||||
\[\] integrate assets folder in release build
|
\[\] integrate assets folder in release build
|
||||||
\[\] integrate templates in release build
|
\[x] integrate templates in release build
|
||||||
\[\] markdown rendering in client
|
\[\] markdown rendering in client
|
||||||
\[\] openai api key in config
|
\[\] openai api key in config
|
||||||
\[\] three js graph explorer
|
\[\] three js graph explorer
|
||||||
|
|||||||