Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

projects directory with 4 projects #2500

Merged
merged 1 commit into from
Apr 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions projects/meilisearch-searchbar/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/target
meilisearch
data.ms
dumps
Cargo.lock
61 changes: 61 additions & 0 deletions projects/meilisearch-searchbar/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
[package]
name = "meilisearch_searchbar"
version = "0.1.0"
edition = "2021"


[lib]
crate-type = ["cdylib", "rlib"]

[dependencies]
leptos = {version = "0.6.5",features = ["nightly"]}
leptos_axum = { version = "0.6.5", optional = true}
meilisearch-sdk = { version = "0.24.3", optional = true}
axum = {version = "0.7.4", optional = true}
leptos_meta = {version = "0.6.5",features = ["nightly"]}
leptos_router = {version = "0.6.5",features = ["nightly"]}
console_log = "1.0.0"
console_error_panic_hook = "0.1.7"
log = "0.4.20"
tower = {verison= "0.4.13", optional=true}
tower-http = {version = "0.5.1", optional = true, features = ["fs"]}
simple_logger = {version = "4.3.3", optional = true}
tokio = { version = "1", features = ["full"], optional = true }
lazy_static = { version = "1.4.0", optional = true }
serde = "1.0.196"
serde_json = "1.0.113"
csv = {version = "1.3.0", optional=true}

[features]
default = ["ssr"]
hydrate = ["leptos/hydrate","leptos_meta/hydrate","leptos_router/hydrate"]
ssr = [
"tokio",
"lazy_static",
"simple_logger",
"dep:meilisearch-sdk",
"dep:axum",
"leptos/ssr",
"leptos_meta/ssr",
"leptos_router/ssr",
"tower",
"tower-http",
"leptos_axum",
"csv",
]
lazy_static = ["dep:lazy_static"]

[package.metadata.leptos]
output-name = "meilisearch_searchbar"
site-root = "target/site"
site-pkg-dir = "pkg"
assets-dir = "public"
site-addr = "127.0.0.1:3000"
reload-port = 3001
browserquery = "defaults"
watch = false
env = "DEV"
bin-features = ["ssr"]
bin-default-features = false
lib-features = ["hydrate"]
lib-default-features = false
4 changes: 4 additions & 0 deletions projects/meilisearch-searchbar/Makefile.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[tasks.ci]
description = "Continuous Integration task"
command = "cargo"
args = ["test"]
28 changes: 28 additions & 0 deletions projects/meilisearch-searchbar/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Meilisearch Searchbar

This show how to integrate meilisearch with a leptos app, including a search bar and showing the results to the user.
<br><br>
We'll run meilisearch locally, as opposed to using their cloud service.
<br><br>
To get started install meilisearch into this example's root.

```sh
curl -L https://install.meilisearch.com | sh
```

Run it.

```sh
./meilisearch
```

Then set the environment variable and serve the app. I've included the address of my own local meilisearch server.
I didn't provide a password to meilisearch during my setup, and I didn't provide one in my environment variables either.
```sh
MEILISEARCH_URL=http://localhost:7700 && cargo leptos serve
```

Navigate to 127.0.0.1:3000 and start typing in popular American company names. (Boeing, Pepsi, etc)

## Thoughts, Feedback, Criticism, Comments?
Send me any of the above, I'm @sjud on leptos discord. I'm always looking to improve and make these projects more helpful for the community. So please let me know how I can do that. Thanks!
504 changes: 504 additions & 0 deletions projects/meilisearch-searchbar/data_set.csv

Large diffs are not rendered by default.

45 changes: 45 additions & 0 deletions projects/meilisearch-searchbar/src/fallback.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
use axum::{
body::Body,
extract::State,
http::{Request, Response, StatusCode, Uri},
response::{IntoResponse, Response as AxumResponse},
};
use leptos::{view, LeptosOptions};
use tower::ServiceExt;
use tower_http::services::ServeDir;

pub async fn file_and_error_handler(
uri: Uri,
State(options): State<LeptosOptions>,
req: Request<Body>,
) -> AxumResponse {
let root = options.site_root.clone();
log::debug!("uri = {uri:?} root = {root} ");
let res = get_static_file(uri.clone(), &root).await.unwrap();

if res.status() == StatusCode::OK {
res.into_response()
} else {
let handler = leptos_axum::render_app_to_stream(
options.to_owned(),
|| view! {"Error! Error! Error!"},
);
handler(req).await.into_response()
}
}

async fn get_static_file(uri: Uri, root: &str) -> Result<Response<Body>, (StatusCode, String)> {
let req = Request::builder()
.uri(uri.clone())
.body(Body::empty())
.unwrap();
// `ServeDir` implements `tower::Service` so we can call it with `tower::ServiceExt::oneshot`
// This path is relative to the cargo root
match ServeDir::new(root).oneshot(req).await {
Ok(res) => Ok(res.into_response()),
Err(err) => Err((
StatusCode::INTERNAL_SERVER_ERROR,
format!("Something went wrong: {}", err),
)),
}
}
121 changes: 121 additions & 0 deletions projects/meilisearch-searchbar/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
use leptos::*;
use leptos_meta::*;
use leptos_router::*;
#[cfg(feature = "ssr")]
pub mod fallback;

#[cfg(feature = "hydrate")]
#[wasm_bindgen::prelude::wasm_bindgen]
pub fn hydrate() {
// initializes logging using the `log` crate
_ = console_log::init_with_level(log::Level::Debug);
console_error_panic_hook::set_once();

leptos::mount_to_body(App);
}

#[component]
pub fn App() -> impl IntoView {
provide_meta_context();
// Provide this two our search components, they'll share a read and write handle to a Vec<StockRow>.
let search_results = create_rw_signal(Vec::<StockRow>::new());
provide_context(search_results);
view! {
<Link rel="shortcut icon" type_="image/ico" href="/favicon.ico"/>
<Meta name="description" content="Leptos implementation of a Meilisearch backed Searchbar."/>
<Router>
<main>
<Routes>
<Route path="/" view=||view!{
<SearchBar/>
<SearchResults/>
}/>
</Routes>
</main>
</Router>
}
}

#[derive(Clone, serde::Deserialize, serde::Serialize, Debug)]
pub struct StockRow {
id: u32,
name: String,
last: String,
high: String,
low: String,
absolute_change: f32,
percentage_change: f32,
volume: u64,
}

#[leptos::server]
pub async fn search_query(query: String) -> Result<Vec<StockRow>, ServerFnError> {
use leptos_axum::extract;
// Wow, so ergonomic!
let axum::Extension::<meilisearch_sdk::Client>(client) = extract().await?;
// Meilisearch has great defaults, lots of things are thought of for out of the box utility.
// They limit the result length automatically (to 20), and have user friendly typo corrections and return similar words.
let hits = client
.get_index("stock_prices")
.await
.unwrap()
.search()
.with_query(query.as_str())
.execute::<StockRow>()
.await
.map_err(|err| ServerFnError::new(err.to_string()))?
.hits;

Ok(hits
.into_iter()
.map(|search_result| search_result.result)
.collect())
}

#[component]
pub fn SearchBar() -> impl IntoView {
let write_search_results = expect_context::<RwSignal<Vec<StockRow>>>().write_only();
let search_query = create_server_action::<SearchQuery>();
create_effect(move |_| {
if let Some(value) = search_query.value()() {
match value {
Ok(search_results) => {
write_search_results.set(search_results);
}
Err(err) => {
leptos::logging::log!("{err}")
}
}
}
});

view! {
<div>
<label for="search">Search</label>
<input id="search" on:input=move|e|{
let query = event_target_value(&e);
search_query.dispatch(SearchQuery{query});
}/>
</div>
}
}

#[component]
pub fn SearchResults() -> impl IntoView {
let read_search_results = expect_context::<RwSignal<Vec<StockRow>>>().read_only();
view! {
<ul>
<For
each=read_search_results
key=|row| row.name.clone()
children=move |StockRow{name,last,high,low,absolute_change,percentage_change,volume,..}: StockRow| {
view! {
<li>
{format!("{name}; last: {last}; high: {high}; low: {low}; chg.: {absolute_change}; chg...:{percentage_change}; volume:{volume}")}
</li>
}
}
/>
</ul>
}
}
83 changes: 83 additions & 0 deletions projects/meilisearch-searchbar/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#[cfg(feature = "ssr")]
#[tokio::main]
async fn main() {
use axum::{routing::get, Extension, Router};
use leptos::get_configuration;
use leptos_axum::{generate_route_list, LeptosRoutes};
use meilisearch_searchbar::StockRow;
use meilisearch_searchbar::{fallback::file_and_error_handler, *};

// simple_logger is a lightweight alternative to tracing, when you absolutely have to trace, use tracing.
simple_logger::SimpleLogger::new()
.with_level(log::LevelFilter::Debug)
.init()
.unwrap();

let mut rdr = csv::Reader::from_path("data_set.csv").unwrap();

// Our data set doesn't have a good id for the purposes of meilisearch, Name is unique but it's not formatted correctly because it may have spaces.
let documents: Vec<StockRow> = rdr
.records()
.enumerate()
.map(|(i, rec)| {
// There's probably a better way to do this.
let mut record = csv::StringRecord::new();
record.push_field(&i.to_string());
for field in rec.unwrap().iter() {
record.push_field(field);
}
record
.deserialize::<StockRow>(None)
.expect(&format!("{:?}", record))
})
.collect();

// My own check. I know how long I expect it to be, if it's not this length something is wrong.
assert_eq!(documents.len(), 503);

let client = meilisearch_sdk::Client::new(
std::env::var("MEILISEARCH_URL").unwrap(),
std::env::var("MEILISEARCH_API_KEY").ok(),
);
// An index is where the documents are stored.
let task = client
.create_index("stock_prices", Some("id"))
.await
.unwrap();

// Meilisearch may take some time to execute the request so we are going to wait till it's completed
client.wait_for_task(task, None, None).await.unwrap();

let task_2 = client
.get_index("stock_prices")
.await
.unwrap()
.add_documents(&documents, Some("id"))
.await
.unwrap();

client.wait_for_task(task_2, None, None).await.unwrap();

drop(documents);

let conf = get_configuration(Some("Cargo.toml")).await.unwrap();
let leptos_options = conf.leptos_options;
let addr = leptos_options.site_addr;
let routes = generate_route_list(App);

// build our application with a route
let app = Router::new()
.route("/favicon.ico", get(file_and_error_handler))
.leptos_routes(&leptos_options, routes, App)
.fallback(file_and_error_handler)
.layer(Extension(client))
.with_state(leptos_options);

// run our app with hyper
// `axum::Server` is a re-export of `hyper::Server`
println!("listening on {}", addr);
let listener = tokio::net::TcpListener::bind(&addr).await.unwrap();
axum::serve(listener, app.into_make_service())
.await
.unwrap();
}
3 changes: 3 additions & 0 deletions projects/nginx-mpmc/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/target
*/target
.vscode
34 changes: 34 additions & 0 deletions projects/nginx-mpmc/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Nginx Multiple Server Multiple Client Example
This example shows how multiple clients can communicate with multiple servers while being shared over a single domain i.e localhost:80 using nginx as a reverse proxy.

### How to run this example
```sh
./run.sh
```
Or

```sh
./run_linux.sh
```

<br>
This will boot up nginx via it's docker image mapped to port 80, and the four servers. App-1, App-2, Shared-Server-1, Shared-Server-2.
<br>
App-1, And App-2 are SSR rendering leptos servers.
<br>
If you go to localhost (you'll get App-1), and localhost/app2 (you'll get app2).
<br>
The two shared servers can be communicated with via actions and local resources, or resources (if using CSR).
<br>
`create_resource` Won't work as expected, when trying to communicate to different servers. It will instead try to run the server function on the server you are serving your server side rendered content from. This will cause errors if your server function relies on state that is not present.
<br>
When you are done with this example, run

```sh
./kill.sh
```

Casting ctrl-c multiple times won't close all the open programs.

## Thoughts, Feedback, Criticism, Comments?
Send me any of the above, I'm @sjud on leptos discord. I'm always looking to improve and make these projects more helpful for the community. So please let me know how I can do that. Thanks!
Loading