From 5f02fb265ab9257805131b9a235b81aedb0e6af7 Mon Sep 17 00:00:00 2001
From: Joe Ardent
Date: Wed, 14 Jun 2023 22:05:50 -0700
Subject: [PATCH 1/4] move macro import to lib.rs
---
src/lib.rs | 37 +++++++++++++++++++++++++++++++++++++
src/templates.rs | 36 +-----------------------------------
src/watches/templates.rs | 3 +--
3 files changed, 39 insertions(+), 37 deletions(-)
diff --git a/src/lib.rs b/src/lib.rs
index 28f9dc9..a8a2157 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -18,6 +18,7 @@ mod util;
mod watches;
// things we want in the crate namespace
+use optional_optional_user::OptionalOptionalUser;
use templates::*;
use users::User;
use watches::{templates::*, ShowKind, Watch};
@@ -62,3 +63,39 @@ pub async fn app(db_pool: sqlx::SqlitePool, session_secret: &[u8]) -> axum::Rout
.layer(session_layer)
.with_state(db_pool)
}
+
+//-************************************************************************
+// tests for the proc macro for optional user
+//-************************************************************************
+#[cfg(test)]
+mod test {
+ use super::{CreateUserSuccess, MainPage, OptionalOptionalUser, User};
+
+ #[test]
+ fn main_page_has_optional_user() {
+ assert!(MainPage::default().has_optional_user());
+ }
+
+ #[test]
+ fn signup_success_has_no_optional_user() {
+ assert!(!CreateUserSuccess::default().has_optional_user());
+ }
+
+ #[test]
+ fn user_is_not_optional() {
+ #[derive(Default, OptionalOptionalUser)]
+ struct TestThing {
+ user: User,
+ }
+ assert!(!TestThing::default().has_optional_user());
+ }
+
+ #[test]
+ fn user_is_not_user() {
+ #[derive(Default, OptionalOptionalUser)]
+ struct TestThing {
+ user: Option,
+ }
+ assert!(!TestThing::default().has_optional_user());
+ }
+}
diff --git a/src/templates.rs b/src/templates.rs
index 6fc1b88..664533f 100644
--- a/src/templates.rs
+++ b/src/templates.rs
@@ -1,8 +1,7 @@
use askama::Template;
-use optional_optional_user::OptionalOptionalUser;
use serde::{Deserialize, Serialize};
-use crate::User;
+use crate::{OptionalOptionalUser, User};
#[derive(Debug, Default, Template, Deserialize, Serialize, PartialEq, Eq, OptionalOptionalUser)]
#[template(path = "signup.html")]
@@ -47,36 +46,3 @@ pub struct LogoutPost;
pub struct MainPage {
pub user: Option,
}
-
-#[cfg(test)]
-mod test {
- use super::*;
-
- #[test]
- fn main_page_has_optional_user() {
- assert!(MainPage::default().has_optional_user());
- }
-
- #[test]
- fn signup_success_has_no_optional_user() {
- assert!(!CreateUserSuccess::default().has_optional_user());
- }
-
- #[test]
- fn user_is_not_optional() {
- #[derive(Default, OptionalOptionalUser)]
- struct TestThing {
- user: User,
- }
- assert!(!TestThing::default().has_optional_user());
- }
-
- #[test]
- fn user_is_not_user() {
- #[derive(Default, OptionalOptionalUser)]
- struct TestThing {
- user: Option,
- }
- assert!(!TestThing::default().has_optional_user());
- }
-}
diff --git a/src/watches/templates.rs b/src/watches/templates.rs
index 3e913f2..4a8d6c6 100644
--- a/src/watches/templates.rs
+++ b/src/watches/templates.rs
@@ -1,8 +1,7 @@
use askama::Template;
-use optional_optional_user::OptionalOptionalUser;
use serde::{Deserialize, Serialize};
-use crate::{User, Watch};
+use crate::{OptionalOptionalUser, User, Watch};
#[derive(Debug, Default, Template, Deserialize, Serialize, PartialEq, Eq, OptionalOptionalUser)]
#[template(path = "get_watches.html")]
From 1540153b679f918d4db6d7069c79df17dd4ede33 Mon Sep 17 00:00:00 2001
From: Joe Ardent
Date: Wed, 14 Jun 2023 22:11:43 -0700
Subject: [PATCH 2/4] add search results page
---
src/watches/handlers.rs | 17 +++++++++++------
src/watches/templates.rs | 6 +++++-
templates/get_search_watches.html | 27 +++++++++++++++++++++++++++
3 files changed, 43 insertions(+), 7 deletions(-)
diff --git a/src/watches/handlers.rs b/src/watches/handlers.rs
index 67e55f5..245b761 100644
--- a/src/watches/handlers.rs
+++ b/src/watches/handlers.rs
@@ -1,5 +1,3 @@
-use std::path::Path;
-
use axum::{
extract::{Form, Query, State},
http::StatusCode,
@@ -9,6 +7,7 @@ use serde::Deserialize;
use sqlx::{query_as, SqlitePool};
use uuid::Uuid;
+use super::templates::GetSearchWatches;
use crate::{AuthContext, GetWatches, ShowKind, User, Watch};
//-************************************************************************
@@ -79,15 +78,21 @@ pub async fn get_watches(auth: AuthContext, State(pool): State) -> i
}
pub async fn get_search_watch(
- _auth: AuthContext,
- State(pool): State,
+ auth: AuthContext,
+ State(_pool): State,
search: Option>,
-) {
+) -> impl IntoResponse {
let search = match search {
Some(Query(SimpleSearchQuery { search })) => search,
None => "".to_owned(),
};
- let search = search.trim();
+ let search = search.trim().to_string();
+ let user = auth.current_user;
+ GetSearchWatches {
+ watches: vec![],
+ user,
+ search,
+ }
}
pub async fn post_search_watch() {}
diff --git a/src/watches/templates.rs b/src/watches/templates.rs
index 4a8d6c6..b60e0d3 100644
--- a/src/watches/templates.rs
+++ b/src/watches/templates.rs
@@ -12,4 +12,8 @@ pub struct GetWatches {
#[derive(Debug, Default, Template, Deserialize, Serialize, PartialEq, Eq, OptionalOptionalUser)]
#[template(path = "get_search_watches.html")]
-pub struct GetSearchWatches {}
+pub struct GetSearchWatches {
+ pub watches: Vec,
+ pub user: Option,
+ pub search: String,
+}
diff --git a/templates/get_search_watches.html b/templates/get_search_watches.html
index e69de29..6835865 100644
--- a/templates/get_search_watches.html
+++ b/templates/get_search_watches.html
@@ -0,0 +1,27 @@
+{% extends "base.html" %}
+{% import "macros.html" as m %}
+
+{% block title %}Welcome to Witch Watch, Bish{% endblock %}
+
+{% block content %}
+
+Whatcha Watchin?
+
+{{self.search}}
+
+
+
+ {% for watch in watches %}
+ - {{watch.title}} -- {% call m::get_or_default(watch.release_date, "when??") %}:
+ {% endfor %}
+
+
+
+
+
+
+{% endblock %}
From 48a123353409e9204fc48e0be80a387208aa6d4c Mon Sep 17 00:00:00 2001
From: Joe Ardent
Date: Thu, 15 Jun 2023 13:13:12 -0700
Subject: [PATCH 3/4] Use graphemes for input length checks instead of number
of bytes.
---
src/signup.rs | 34 +++++++++++++++++++++++++++++++---
src/util.rs | 5 ++++-
2 files changed, 35 insertions(+), 4 deletions(-)
diff --git a/src/signup.rs b/src/signup.rs
index 171a643..a2e73f0 100644
--- a/src/signup.rs
+++ b/src/signup.rs
@@ -81,10 +81,9 @@ pub async fn post_create_user(
if password != verify {
return Err(CreateUserErrorKind::PasswordMismatch.into());
}
-
- let password = password.trim();
+ let pwlen = password.graphemes(true).size_hint().1.unwrap_or(0);
let password = password.as_bytes();
- if !(4..=50).contains(&password.len()) {
+ if !(4..=50).contains(&pwlen) {
return Err(CreateUserErrorKind::BadPassword.into());
}
@@ -352,6 +351,35 @@ mod test {
assert_eq!(&expected, body);
}
+ #[tokio::test]
+ async fn multibyte_password_too_short() {
+ let pw = "🤡";
+ // min length is 4
+ assert_eq!(pw.len(), 4);
+
+ let pool = get_db_pool().await;
+ let server = server_with_pool(&pool).await;
+ let form =
+ format!("username=test_user&displayname=Test+User&password={pw}&pw_verify={pw}");
+ let body = massage(&form);
+
+ let resp = server
+ .post("/signup")
+ // failure to sign up is not failure to submit the request
+ .expect_success()
+ .bytes(body)
+ .content_type(FORM_CONTENT_TYPE)
+ .await;
+
+ // no user in db
+ let user = User::try_get("test_user", &pool).await;
+ assert!(user.is_err());
+
+ let body = std::str::from_utf8(resp.bytes()).unwrap();
+ let expected = CreateUserError(CreateUserErrorKind::BadPassword).to_string();
+ assert_eq!(&expected, body);
+ }
+
#[tokio::test]
async fn username_short() {
let pool = get_db_pool().await;
diff --git a/src/util.rs b/src/util.rs
index 466660c..022c0f8 100644
--- a/src/util.rs
+++ b/src/util.rs
@@ -1,5 +1,7 @@
use std::{error::Error, ops::Range};
+use unicode_segmentation::UnicodeSegmentation;
+
pub fn validate_optional_length(
opt: &Option,
len_range: Range,
@@ -7,7 +9,8 @@ pub fn validate_optional_length(
) -> Result