add: schema, add user but no middleware

This commit is contained in:
2025-05-11 20:16:42 -07:00
parent a68ffc4bb7
commit 66d8144a03
8 changed files with 198 additions and 10 deletions

View File

@@ -4,9 +4,12 @@ use leptos_router::{
components::{Route, Router, Routes},
StaticSegment,
};
use user::User;
use movies::Movies;
mod movies;
mod user;
pub fn shell(options: LeptosOptions) -> impl IntoView {
view! {
@@ -89,6 +92,7 @@ fn HomePage() -> impl IntoView {
view! {
<h1>"Welcome to Wiseau movie picker"</h1>
<User></User>
<Movies></Movies>
<button on:click=onclick>"Click Me: "
<Transition fallback=move || view! { <p>"Loading..."</p> }>

87
src/app/user.rs Normal file
View File

@@ -0,0 +1,87 @@
use leptos::prelude::*;
#[server]
pub async fn set_user(display_name: String, secret: String) -> Result<(), ServerFnError> {
use crate::common::Context;
use axum::http::{HeaderName, HeaderValue};
use leptos_axum::ResponseOptions;
use log::info;
use rand::{distr::Alphanumeric, Rng};
let mut secret = secret;
if display_name.len() == 0 && secret.len() == 0 {
return Err(ServerFnError::MissingArg(
"need either secret or display_name".into(),
));
}
info!("set_user called");
let data = use_context::<Context>().unwrap();
let mut client = data.client.lock().await;
let txn = client.transaction().await?;
// If the secret exists, update the database
if secret.len() > 0 {
// Validate the secret exists
txn.query_one("SELECT user_id FROM Users WHERE priv = $1", &[&secret])
.await?;
// Update display name if needed
if display_name.len() > 0 {
info!("Updating user with name {}", &display_name);
txn.execute(
"UPDATE Users SET display_name = $1 WHERE secret = $2;",
&[&display_name, &secret],
)
.await?;
}
} else if secret.len() == 0 {
// Create a new secret
info!("Creating user with name {}", &display_name);
secret = rand::rng()
.sample_iter(&Alphanumeric)
.take(4096)
.map(char::from)
.collect();
let public: String = rand::rng()
.sample_iter(&Alphanumeric)
.take(16)
.map(char::from)
.collect();
txn.execute(
"INSERT INTO Users (display_name, priv, pub) VALUES ($1, $2, $3);",
&[&display_name, &secret, &public],
)
.await?;
}
txn.commit().await?;
info!("Setting headers");
// Set user auth token
let response = expect_context::<ResponseOptions>();
info!("Appending header");
response.insert_header(
HeaderName::from_static("authorization"),
HeaderValue::from_str(&format! {"Basic {}", secret})?,
);
info!("Returning");
Ok(())
}
/// Renders the home page of your application.
#[component]
pub fn User() -> impl IntoView {
let set_user = ServerMultiAction::<SetUser>::new();
let create_user_view = view! {
<MultiActionForm action=set_user>
<div>
<p>"Display name to use; this will change your display name if you set a secret"</p>
<label>"Display name" <input type="text" name="display_name"/></label>
</div>
<div>
<p>"Leave blank to create a new user; enter the secret key to login to an existing user"</p>
<label>"Secret" <input type="text" name="secret"/></label>
</div>
<div>
<input type="submit" value="Login"/>
</div>
</MultiActionForm>
};
create_user_view
}

View File

@@ -1,5 +1,8 @@
use anyhow::{anyhow, Result};
use axum::extract::Request;
use std::sync::Arc;
use tokio::sync::Mutex;
use tokio_postgres::Client;
use crate::model::Movie;
@@ -7,17 +10,25 @@ use crate::model::Movie;
pub struct Context {
pub counter: Arc<Mutex<usize>>,
pub movies: Arc<Mutex<Vec<Movie>>>,
pub client: Arc<Mutex<Client>>,
}
impl Context {
pub fn new() -> Self {
let movies = vec![
Movie::new("Hackers"),
Movie::new("Princess Bridge"),
];
pub fn new(client: Client) -> Self {
let movies = vec![Movie::new("Hackers"), Movie::new("Princess Bridge")];
Self {
counter: Arc::new(Mutex::new(0)),
movies: Arc::new(Mutex::new(movies)),
client: Arc::new(Mutex::new(client)),
}
}
}
}
pub async fn user_id(ctx: &Context, request: Request) -> Result<usize> {
let client = ctx.client.lock().await;
let secret = request.headers().get("authorization").ok_or(anyhow!("auth header not found"))?;
let res = client
.query_one("SELECT user_id FROM Users WHERE secret = $1", &[&secret.to_str()?])
.await?;
Ok(res.get::<_, i64>(0) as usize)
}

View File

@@ -2,6 +2,8 @@ pub mod app;
pub mod model;
#[cfg(feature = "ssr")]
pub mod common;
#[cfg(feature = "ssr")]
pub mod pool;
#[cfg(feature = "hydrate")]
#[wasm_bindgen::prelude::wasm_bindgen]

View File

@@ -1,21 +1,54 @@
use wiseau::common;
//#[cfg(feature = "ssr")]
/// Simple program to greet a person
#[cfg(feature = "ssr")]
#[derive(clap::Parser, Debug)]
#[command(version, about, long_about = None)]
struct Args {
/// Postgres connection string
#[arg(short, long)]
postgres: String,
}
#[cfg(feature = "ssr")]
#[tokio::main]
async fn main() {
async fn main() -> anyhow::Result<()> {
use axum::Router;
use clap::Parser;
use leptos::logging::log;
use leptos::prelude::*;
use leptos_axum::{generate_route_list, LeptosRoutes};
use log::info;
use tokio_postgres::NoTls;
use wiseau::app::*;
env_logger::init();
let args = Args::parse();
// Connect to the database
let (client, connection) = tokio_postgres::connect(&args.postgres, NoTls).await?;
// Spin off the database worker
tokio::spawn(async move {
if let Err(e) = connection.await {
eprintln!("connection error: {}", e);
}
});
// Sanity check the database connection
let rows = client.query("SELECT $1::TEXT", &[&"hello world"]).await?;
let value: &str = rows[0].get(0);
assert_eq!(value, "hello world");
// Setup leptos
let conf = get_configuration(None).unwrap();
let addr = conf.leptos_options.site_addr;
let leptos_options = conf.leptos_options;
// Generate the list of routes in your Leptos App
let routes = generate_route_list(App);
let context = common::Context::new();
let context = common::Context::new(client);
let app = Router::new()
.leptos_routes_with_context(
@@ -32,9 +65,11 @@ async fn main() {
// run our app with hyper
// `axum::Server` is a re-export of `hyper::Server`
log!("listening on http://{}", &addr);
info!("listening on http://{}", &addr);
let listener = tokio::net::TcpListener::bind(&addr).await.unwrap();
axum::serve(listener, app.into_make_service())
.await
.unwrap();
Ok(())
}

1
src/pool.rs Normal file
View File

@@ -0,0 +1 @@
// Write a psql connection pool