feat: release build bundles assets

This commit is contained in:
Per Stark
2025-03-28 22:24:09 +01:00
parent c556d48151
commit 56a2e1d801
43 changed files with 6193 additions and 277 deletions
Generated
+66
View File
@@ -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 -1
View File
@@ -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)
+2
View File
@@ -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

File diff suppressed because it is too large Load Diff
+28 -33
View File
@@ -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
} }
} }
+3 -1
View File
@@ -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
} }
+39 -11
View File
@@ -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 hasslefree experience by signing up for the readytouse 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>&lt;your_url&gt;: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 %}
+20 -2
View File
@@ -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(),
}; };
+22 -2
View File
@@ -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(),
}; };
+1 -1
View File
@@ -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