add root template
This commit is contained in:
		
							parent
							
								
									eb1bb7309a
								
							
						
					
					
						commit
						26de7b3cc4
					
				
					 10 changed files with 141 additions and 21 deletions
				
			
		| 
						 | 
					@ -2,6 +2,7 @@ CREATE TABLE users (
 | 
				
			||||||
    id       SERIAL PRIMARY KEY,
 | 
					    id       SERIAL PRIMARY KEY,
 | 
				
			||||||
    username TEXT NOT NULL UNIQUE,
 | 
					    username TEXT NOT NULL UNIQUE,
 | 
				
			||||||
    password TEXT NOT NULL,
 | 
					    password TEXT NOT NULL,
 | 
				
			||||||
 | 
					    role     INTEGER NOT NULL,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    created_at  TIMESTAMPTZ NOT NULL DEFAULT now(),
 | 
					    created_at  TIMESTAMPTZ NOT NULL DEFAULT now(),
 | 
				
			||||||
    last_active TIMESTAMPTZ NOT NULL DEFAULT now()
 | 
					    last_active TIMESTAMPTZ NOT NULL DEFAULT now()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										22
									
								
								src/main.rs
									
										
									
									
									
								
							
							
						
						
									
										22
									
								
								src/main.rs
									
										
									
									
									
								
							| 
						 | 
					@ -1,26 +1,27 @@
 | 
				
			||||||
pub mod config;
 | 
					pub mod config;
 | 
				
			||||||
pub mod model;
 | 
					pub mod model;
 | 
				
			||||||
 | 
					pub mod pages;
 | 
				
			||||||
pub mod state;
 | 
					pub mod state;
 | 
				
			||||||
pub mod templates;
 | 
					pub mod templates;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
 | 
					use std::net::{IpAddr, Ipv4Addr, SocketAddr};
 | 
				
			||||||
use std::sync::Arc;
 | 
					use std::sync::Arc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use axum::extract::State;
 | 
					 | 
				
			||||||
use axum::http::{header, StatusCode};
 | 
					 | 
				
			||||||
use axum::response::IntoResponse;
 | 
					 | 
				
			||||||
use axum::{routing::get, Router};
 | 
					use axum::{routing::get, Router};
 | 
				
			||||||
use clap::Parser;
 | 
					use clap::Parser;
 | 
				
			||||||
use eyre::{Result, WrapErr};
 | 
					use eyre::{Result, WrapErr};
 | 
				
			||||||
use sqlx::migrate;
 | 
					use sqlx::migrate;
 | 
				
			||||||
use sqlx::postgres::PgPoolOptions;
 | 
					use sqlx::postgres::PgPoolOptions;
 | 
				
			||||||
use tower_http::trace::TraceLayer;
 | 
					use tower_http::trace::TraceLayer;
 | 
				
			||||||
use tracing::{debug, error, info};
 | 
					use tracing::{debug, info};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::config::Config;
 | 
					use crate::config::Config;
 | 
				
			||||||
use crate::state::AppState;
 | 
					use crate::state::AppState;
 | 
				
			||||||
use crate::templates::handlebars;
 | 
					use crate::templates::handlebars;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::pages::index::index;
 | 
				
			||||||
 | 
					use crate::pages::user::get_user;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[tokio::main]
 | 
					#[tokio::main]
 | 
				
			||||||
async fn main() -> Result<()> {
 | 
					async fn main() -> Result<()> {
 | 
				
			||||||
    dotenvy::dotenv().ok();
 | 
					    dotenvy::dotenv().ok();
 | 
				
			||||||
| 
						 | 
					@ -51,6 +52,7 @@ async fn main() -> Result<()> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let app = Router::new()
 | 
					    let app = Router::new()
 | 
				
			||||||
        .route("/", get(index))
 | 
					        .route("/", get(index))
 | 
				
			||||||
 | 
					        .route("/users/:id", get(get_user))
 | 
				
			||||||
        .layer(TraceLayer::new_for_http())
 | 
					        .layer(TraceLayer::new_for_http())
 | 
				
			||||||
        .with_state(state);
 | 
					        .with_state(state);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -62,15 +64,3 @@ async fn main() -> Result<()> {
 | 
				
			||||||
        .await
 | 
					        .await
 | 
				
			||||||
        .wrap_err("running server")
 | 
					        .wrap_err("running server")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
async fn index(State(state): State<Arc<AppState>>) -> impl IntoResponse {
 | 
					 | 
				
			||||||
    let out = match state.hbs.render("root.hbs", &16) {
 | 
					 | 
				
			||||||
        Ok(s) => s,
 | 
					 | 
				
			||||||
        Err(err) => {
 | 
					 | 
				
			||||||
            error!("Rendering index page: {}", err);
 | 
					 | 
				
			||||||
            return (StatusCode::INTERNAL_SERVER_ERROR, "Internal server error").into_response();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return ([(header::CONTENT_TYPE, "text/html")], out).into_response();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,12 +1,48 @@
 | 
				
			||||||
use chrono::{DateTime, Utc};
 | 
					use chrono::{DateTime, Utc};
 | 
				
			||||||
use serde::Serialize;
 | 
					use serde::{Serialize, Serializer};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Serialize)]
 | 
					#[derive(Debug, Serialize)]
 | 
				
			||||||
pub struct User {
 | 
					pub struct User {
 | 
				
			||||||
    pub id: i32,
 | 
					    pub id: i32,
 | 
				
			||||||
    pub username: String,
 | 
					    pub username: String,
 | 
				
			||||||
    pub password: String,
 | 
					    pub password: String,
 | 
				
			||||||
 | 
					    pub role: Role,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub created_at: DateTime<Utc>,
 | 
					    pub created_at: DateTime<Utc>,
 | 
				
			||||||
    pub last_active: DateTime<Utc>,
 | 
					    pub last_active: DateTime<Utc>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(sqlx::Type, Debug)]
 | 
				
			||||||
 | 
					#[repr(i32)]
 | 
				
			||||||
 | 
					pub enum Role {
 | 
				
			||||||
 | 
					    Viewer = 1,
 | 
				
			||||||
 | 
					    Editor = 2,
 | 
				
			||||||
 | 
					    Manager = 3,
 | 
				
			||||||
 | 
					    Admin = 4,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Serialize for Role {
 | 
				
			||||||
 | 
					    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
 | 
				
			||||||
 | 
					    where
 | 
				
			||||||
 | 
					        S: Serializer,
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        serializer.serialize_str(match self {
 | 
				
			||||||
 | 
					            Role::Viewer => "viwer",
 | 
				
			||||||
 | 
					            Role::Editor => "editor",
 | 
				
			||||||
 | 
					            Role::Manager => "manager",
 | 
				
			||||||
 | 
					            Role::Admin => "admin",
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl From<i32> for Role {
 | 
				
			||||||
 | 
					    fn from(value: i32) -> Self {
 | 
				
			||||||
 | 
					        match value {
 | 
				
			||||||
 | 
					            1 => Self::Viewer,
 | 
				
			||||||
 | 
					            2 => Self::Editor,
 | 
				
			||||||
 | 
					            3 => Self::Manager,
 | 
				
			||||||
 | 
					            4 => Self::Admin,
 | 
				
			||||||
 | 
					            _ => unreachable!("Role should never be outside 1-4")
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										9
									
								
								src/pages/index.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/pages/index.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,9 @@
 | 
				
			||||||
 | 
					use std::sync::Arc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use axum::{extract::State, response::IntoResponse};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::{state::AppState, templates::{Page, PageData}};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub async fn index(State(state): State<Arc<AppState>>) -> impl IntoResponse {
 | 
				
			||||||
 | 
					    Page::new(state.hbs.render::<PageData>("index.hbs", &Default::default()))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										2
									
								
								src/pages/mod.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								src/pages/mod.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,2 @@
 | 
				
			||||||
 | 
					pub mod index;
 | 
				
			||||||
 | 
					pub mod user;
 | 
				
			||||||
							
								
								
									
										40
									
								
								src/pages/user/mod.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								src/pages/user/mod.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,40 @@
 | 
				
			||||||
 | 
					use std::sync::Arc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use axum::{
 | 
				
			||||||
 | 
					    extract::{Path, State},
 | 
				
			||||||
 | 
					    response::IntoResponse,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					use sqlx::query_as;
 | 
				
			||||||
 | 
					use tracing::error;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::{
 | 
				
			||||||
 | 
					    model::user::User,
 | 
				
			||||||
 | 
					    state::AppState,
 | 
				
			||||||
 | 
					    templates::{Page, PageData},
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub async fn get_user(
 | 
				
			||||||
 | 
					    State(state): State<Arc<AppState>>,
 | 
				
			||||||
 | 
					    Path(user_id): Path<i32>,
 | 
				
			||||||
 | 
					) -> impl IntoResponse {
 | 
				
			||||||
 | 
					    let user = match query_as!(User, r#"SELECT * FROM users WHERE id = $1"#, user_id)
 | 
				
			||||||
 | 
					        .fetch_one(&state.pool)
 | 
				
			||||||
 | 
					        .await
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        Ok(user) => user,
 | 
				
			||||||
 | 
					        Err(why) => {
 | 
				
			||||||
 | 
					            error!("Getting user: {}", why);
 | 
				
			||||||
 | 
					            return Page::new(
 | 
				
			||||||
 | 
					                state
 | 
				
			||||||
 | 
					                    .hbs
 | 
				
			||||||
 | 
					                    .render::<PageData>("error.hbs", &Default::default()),
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Page::new(
 | 
				
			||||||
 | 
					        state
 | 
				
			||||||
 | 
					            .hbs
 | 
				
			||||||
 | 
					            .render("error.hbs", &PageData { user: Some(user) }),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,14 @@
 | 
				
			||||||
 | 
					use axum::{
 | 
				
			||||||
 | 
					    http::{header, StatusCode},
 | 
				
			||||||
 | 
					    response::IntoResponse,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
use eyre::{Context, Result};
 | 
					use eyre::{Context, Result};
 | 
				
			||||||
use handlebars::Handlebars;
 | 
					use handlebars::{Handlebars, RenderError};
 | 
				
			||||||
use rust_embed::RustEmbed;
 | 
					use rust_embed::RustEmbed;
 | 
				
			||||||
use tracing::info;
 | 
					use serde::Serialize;
 | 
				
			||||||
 | 
					use tracing::{error, info};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::model::user::User;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(RustEmbed)]
 | 
					#[derive(RustEmbed)]
 | 
				
			||||||
#[folder = "templates/"]
 | 
					#[folder = "templates/"]
 | 
				
			||||||
| 
						 | 
					@ -23,3 +30,28 @@ pub fn handlebars(dev_mode: bool) -> Result<Handlebars<'static>> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Ok(hbs)
 | 
					    Ok(hbs)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub struct Page(Result<String, RenderError>);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Page {
 | 
				
			||||||
 | 
					    pub fn new(res: Result<String, RenderError>) -> Self {
 | 
				
			||||||
 | 
					        Self(res)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl IntoResponse for Page {
 | 
				
			||||||
 | 
					    fn into_response(self) -> axum::response::Response {
 | 
				
			||||||
 | 
					        match self.0 {
 | 
				
			||||||
 | 
					            Ok(s) => ([(header::CONTENT_TYPE, "text/html; charset=utf-8")], s).into_response(),
 | 
				
			||||||
 | 
					            Err(why) => {
 | 
				
			||||||
 | 
					                error!("Error rendering page: {}", why);
 | 
				
			||||||
 | 
					                (StatusCode::INTERNAL_SERVER_ERROR, "Internal server error").into_response()
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Default, Serialize)]
 | 
				
			||||||
 | 
					pub struct PageData {
 | 
				
			||||||
 | 
					    pub user: Option<User>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										5
									
								
								templates/error.hbs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								templates/error.hbs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,5 @@
 | 
				
			||||||
 | 
					{{#*inline "page"}}
 | 
				
			||||||
 | 
					<h1>Error</h1>
 | 
				
			||||||
 | 
					<p>An internal error occurred. Sorry :(</p>
 | 
				
			||||||
 | 
					{{/inline}}
 | 
				
			||||||
 | 
					{{> root.hbs}}
 | 
				
			||||||
							
								
								
									
										5
									
								
								templates/index.hbs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								templates/index.hbs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,5 @@
 | 
				
			||||||
 | 
					{{#*inline "page"}}
 | 
				
			||||||
 | 
					<h1>board!</h1>
 | 
				
			||||||
 | 
					<p>this will be a site eventually</p>
 | 
				
			||||||
 | 
					{{/inline}}
 | 
				
			||||||
 | 
					{{> root.hbs}}
 | 
				
			||||||
| 
						 | 
					@ -6,6 +6,6 @@
 | 
				
			||||||
    <title>imgboard</title>
 | 
					    <title>imgboard</title>
 | 
				
			||||||
</head>
 | 
					</head>
 | 
				
			||||||
<body>
 | 
					<body>
 | 
				
			||||||
    <h1>Hello world!!!</h1>
 | 
					    {{> page}}
 | 
				
			||||||
</body>
 | 
					</body>
 | 
				
			||||||
</html>
 | 
					</html>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue