Compare commits

..

2 Commits

9 changed files with 57 additions and 11 deletions

View File

@@ -3,7 +3,15 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>Movies Diary</title> <title>{{ ctx.page_title }}</title>
<meta name="description" content="A personal movie diary — track what you watch, rate and review films.">
<meta property="og:type" content="website">
<meta property="og:site_name" content="Movies Diary">
<meta property="og:title" content="{{ ctx.page_title }}">
<meta property="og:url" content="{{ ctx.canonical_url }}">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="{{ ctx.page_title }}">
<link rel="canonical" href="{{ ctx.canonical_url }}">
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700;800&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700;800&display=swap" rel="stylesheet">

View File

@@ -1,6 +1,7 @@
#[derive(Clone)] #[derive(Clone)]
pub struct AppConfig { pub struct AppConfig {
pub allow_registration: bool, pub allow_registration: bool,
pub base_url: String,
} }
impl AppConfig { impl AppConfig {
@@ -8,6 +9,8 @@ impl AppConfig {
let allow_registration = std::env::var("ALLOW_REGISTRATION") let allow_registration = std::env::var("ALLOW_REGISTRATION")
.map(|v| v == "true" || v == "1") .map(|v| v == "true" || v == "1")
.unwrap_or(false); .unwrap_or(false);
Self { allow_registration } let base_url = std::env::var("BASE_URL")
.unwrap_or_else(|_| "http://localhost:3000".to_string());
Self { allow_registration, base_url }
} }
} }

View File

@@ -7,6 +7,8 @@ pub struct HtmlPageContext {
pub user_id: Option<Uuid>, pub user_id: Option<Uuid>,
pub register_enabled: bool, pub register_enabled: bool,
pub rss_url: String, pub rss_url: String,
pub page_title: String,
pub canonical_url: String,
} }
impl HtmlPageContext { impl HtmlPageContext {

View File

@@ -160,7 +160,7 @@ mod tests {
auth_service: Arc::new(PanicAuth), auth_service: Arc::new(PanicAuth),
password_hasher: Arc::new(PanicHasher), password_hasher: Arc::new(PanicHasher),
user_repository: Arc::new(PanicUserRepo), user_repository: Arc::new(PanicUserRepo),
config: AppConfig { allow_registration: false }, config: AppConfig { allow_registration: false, base_url: "http://localhost:3000".to_string() },
} }
} }

View File

@@ -175,7 +175,7 @@ mod tests {
auth_service: Arc::new(PanicAuth), auth_service: Arc::new(PanicAuth),
password_hasher: Arc::new(PanicHasher), password_hasher: Arc::new(PanicHasher),
user_repository: Arc::new(PanicUserRepo), user_repository: Arc::new(PanicUserRepo),
config: application::config::AppConfig { allow_registration: false }, config: application::config::AppConfig { allow_registration: false, base_url: "http://localhost:3000".to_string() },
}, },
html_renderer: Arc::new(PanicRenderer), html_renderer: Arc::new(PanicRenderer),
rss_renderer: Arc::new(PanicRssRenderer), rss_renderer: Arc::new(PanicRssRenderer),
@@ -282,7 +282,7 @@ mod tests {
auth_service: Arc::new(PanicAuth2), auth_service: Arc::new(PanicAuth2),
password_hasher: Arc::new(PanicHasher2), password_hasher: Arc::new(PanicHasher2),
user_repository: Arc::new(PanicUserRepo2), user_repository: Arc::new(PanicUserRepo2),
config: application::config::AppConfig { allow_registration: false }, config: application::config::AppConfig { allow_registration: false, base_url: "http://localhost:3000".to_string() },
}, },
html_renderer: Arc::new(PanicRenderer2), html_renderer: Arc::new(PanicRenderer2),
rss_renderer: Arc::new(PanicRssRenderer2), rss_renderer: Arc::new(PanicRssRenderer2),
@@ -341,7 +341,7 @@ mod tests {
auth_service: Arc::new(RejectingAuth), auth_service: Arc::new(RejectingAuth),
password_hasher: Arc::new(PanicHasher3), password_hasher: Arc::new(PanicHasher3),
user_repository: Arc::new(PanicUserRepo3), user_repository: Arc::new(PanicUserRepo3),
config: application::config::AppConfig { allow_registration: false }, config: application::config::AppConfig { allow_registration: false, base_url: "http://localhost:3000".to_string() },
}, },
html_renderer: Arc::new(PanicRenderer3), html_renderer: Arc::new(PanicRenderer3),
rss_renderer: Arc::new(PanicRssRenderer3), rss_renderer: Arc::new(PanicRssRenderer3),

View File

@@ -43,6 +43,8 @@ pub mod html {
user_id: uuid, user_id: uuid,
register_enabled: state.app_ctx.config.allow_registration, register_enabled: state.app_ctx.config.allow_registration,
rss_url: "/feed.rss".to_string(), rss_url: "/feed.rss".to_string(),
page_title: "Movies Diary".to_string(),
canonical_url: state.app_ctx.config.base_url.clone(),
} }
} }
@@ -74,6 +76,8 @@ pub mod html {
user_id: None, user_id: None,
register_enabled: state.app_ctx.config.allow_registration, register_enabled: state.app_ctx.config.allow_registration,
rss_url: "/feed.rss".to_string(), rss_url: "/feed.rss".to_string(),
page_title: "Login — Movies Diary".to_string(),
canonical_url: format!("{}/login", state.app_ctx.config.base_url),
}; };
let html = state let html = state
.html_renderer .html_renderer
@@ -125,6 +129,8 @@ pub mod html {
user_id: None, user_id: None,
register_enabled: true, register_enabled: true,
rss_url: "/feed.rss".to_string(), rss_url: "/feed.rss".to_string(),
page_title: "Register — Movies Diary".to_string(),
canonical_url: format!("{}/register", state.app_ctx.config.base_url),
}; };
let html = state let html = state
.html_renderer .html_renderer
@@ -175,7 +181,9 @@ pub mod html {
State(state): State<AppState>, State(state): State<AppState>,
Query(params): Query<ErrorQuery>, Query(params): Query<ErrorQuery>,
) -> impl IntoResponse { ) -> impl IntoResponse {
let ctx = build_page_context(&state, Some(user_id)).await; let mut ctx = build_page_context(&state, Some(user_id)).await;
ctx.page_title = "Log a Review — Movies Diary".to_string();
ctx.canonical_url = format!("{}/reviews/new", state.app_ctx.config.base_url);
let html = state let html = state
.html_renderer .html_renderer
.render_new_review_page(NewReviewPageData { .render_new_review_page(NewReviewPageData {
@@ -262,7 +270,9 @@ pub mod html {
OptionalCookieUser(user_id): OptionalCookieUser, OptionalCookieUser(user_id): OptionalCookieUser,
State(state): State<AppState>, State(state): State<AppState>,
) -> impl IntoResponse { ) -> impl IntoResponse {
let ctx = build_page_context(&state, user_id).await; let mut ctx = build_page_context(&state, user_id).await;
ctx.page_title = "Members — Movies Diary".to_string();
ctx.canonical_url = format!("{}/users", state.app_ctx.config.base_url);
match application::use_cases::get_users::execute(&state.app_ctx, application::queries::GetUsersQuery).await { match application::use_cases::get_users::execute(&state.app_ctx, application::queries::GetUsersQuery).await {
Ok(users) => { Ok(users) => {
let data = application::ports::UsersPageData { ctx, users }; let data = application::ports::UsersPageData { ctx, users };
@@ -293,6 +303,11 @@ pub mod html {
Err(e) => return (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(), Err(e) => return (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
}; };
let display_name = profile_user.email().value()
.split('@').next().unwrap_or("User");
ctx.page_title = format!("{}'s Diary — Movies Diary", display_name);
ctx.canonical_url = format!("{}/users/{}", state.app_ctx.config.base_url, profile_user_uuid);
let query = application::queries::GetUserProfileQuery { let query = application::queries::GetUserProfileQuery {
user_id: profile_user_uuid, user_id: profile_user_uuid,
view: view.clone(), view: view.clone(),

View File

@@ -32,8 +32,11 @@ async fn main() -> anyhow::Result<()> {
let app = routes::build_router(state); let app = routes::build_router(state);
let listener = TcpListener::bind("0.0.0.0:3000").await?; let host = std::env::var("HOST").unwrap_or_else(|_| "0.0.0.0".to_string());
tracing::info!("Listening on 0.0.0.0:3000"); let port = std::env::var("PORT").unwrap_or_else(|_| "3000".to_string());
let addr = format!("{}:{}", host, port);
let listener = TcpListener::bind(&addr).await?;
tracing::info!("Listening on {}", addr);
axum::serve(listener, app).await?; axum::serve(listener, app).await?;
Ok(()) Ok(())

View File

@@ -105,7 +105,7 @@ async fn test_app() -> Router {
auth_service: Arc::new(PanicAuth), auth_service: Arc::new(PanicAuth),
password_hasher: Arc::new(PanicHasher), password_hasher: Arc::new(PanicHasher),
user_repository: Arc::new(NobodyUserRepo), user_repository: Arc::new(NobodyUserRepo),
config: AppConfig { allow_registration: false }, config: AppConfig { allow_registration: false, base_url: "http://localhost:3000".to_string() },
}, },
html_renderer: Arc::new(AskamaHtmlRenderer::new()), html_renderer: Arc::new(AskamaHtmlRenderer::new()),
rss_renderer: Arc::new(RssAdapter::new("http://localhost:3000".into())), rss_renderer: Arc::new(RssAdapter::new("http://localhost:3000".into())),

View File

@@ -34,6 +34,21 @@ body {
background: url("/static/background.avif") center / cover no-repeat fixed; background: url("/static/background.avif") center / cover no-repeat fixed;
min-height: 100%; min-height: 100%;
line-height: 1.5; line-height: 1.5;
position: relative;
}
body::before {
content: "";
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.2);
z-index: 0;
pointer-events: none;
}
body > * {
position: relative;
z-index: 1;
} }
a { a {