more frontend

This commit is contained in:
Michael Zhang 2019-02-08 08:10:18 -06:00
parent 29fd690ad7
commit 95c71b68b2
No known key found for this signature in database
GPG Key ID: 5BAEFE5D04F0CE6C
7 changed files with 129 additions and 23 deletions

View File

@ -281,6 +281,20 @@ impl UsefulFunctions<sqlite::Sqlite> for sqlite::SqlitePooledConnection {
} }
impl DbConn { impl DbConn {
/// Tries to fetch the user with the given id.
pub fn fetch_user_id(&self, id: i32) -> Result<User, Error> {
use crate::schema::users::dsl;
let query = dsl::users.filter(dsl::id.eq(id));
match self {
#[cfg(feature = "mysql")]
DbConn::Mysql(conn) => query.first(conn),
#[cfg(feature = "postgres")]
DbConn::Postgres(conn) => query.first(conn),
#[cfg(feature = "sqlite")]
DbConn::Sqlite(conn) => query.first(conn),
}
.map_err(Error::from)
}
/// Tries to fetch the user with the given email. (TODO: lookup by username) /// Tries to fetch the user with the given email. (TODO: lookup by username)
pub fn fetch_user(&self, email: impl AsRef<str>) -> Result<User, Error> { pub fn fetch_user(&self, email: impl AsRef<str>) -> Result<User, Error> {
use crate::schema::users::dsl; use crate::schema::users::dsl;

View File

@ -1,8 +1,11 @@
use tera::Context;
use warp::Filter; use warp::Filter;
use crate::extractors::{get_context, navbar, Context};
use crate::render::render_template; use crate::render::render_template;
pub fn get_index() -> Resp!() { pub fn get_index() -> Resp!() {
warp::path::end().and_then(|| render_template("base/index.html", Context::new())) warp::path::end()
.and(navbar())
.and(get_context())
.and_then(|ctx: Context| render_template("base/index.html", ctx.into()))
} }

View File

@ -1,20 +1,60 @@
use core::State; use core::{Error, State};
use std::ops::{Deref, DerefMut};
use tera::Context as TeraContext;
use warp::{Filter, Rejection}; use warp::{Filter, Rejection};
use crate::session::Session; use crate::session::Session;
#[derive(Default)] #[derive(Clone, Default)]
pub struct Context(::tera::Context); pub struct Context(pub TeraContext);
fn get_context() -> impl Clone + Filter<Extract = (Context,), Error = Rejection> { impl Deref for Context {
warp::ext::get::<State>() type Target = TeraContext;
.and(warp::ext::get::<Session>()) fn deref(&self) -> &Self::Target {
.map(|state, session| Context::default()) &self.0
}
}
impl DerefMut for Context {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl Into<TeraContext> for Context {
fn into(self) -> TeraContext {
self.0
}
}
pub fn get_context() -> impl Clone + Filter<Extract = (Context,), Error = Rejection> {
warp::ext::get::<Context>()
.or(warp::any().map(|| Context::default())) .or(warp::any().map(|| Context::default()))
.unify() .unify()
} }
fn render_navbar(state: &State, session: &Session, ctx: &mut Context) -> Result<(), Error> {
let conn = state.get_connection()?;
// retrieve the user data
if let Some(user_id) = session.user_id {
conn.fetch_user_id(user_id)
.map(|user| ctx.insert("user", &user))?;
}
Ok(())
}
/// Generates all of the information needed to populate the navbar. /// Generates all of the information needed to populate the navbar.
pub fn navbar() -> impl Clone + Filter<Extract = (), Error = Rejection> { pub fn navbar() -> impl Clone + Filter<Extract = (), Error = Rejection> {
get_context().map(|ctx| {}).untuple_one() warp::ext::get::<State>()
.and(warp::ext::get::<Session>())
.and(get_context())
.map(|state: State, session: Session, mut ctx: Context| {
render_navbar(&state, &session, &mut ctx).unwrap_or_else(|err| {
println!("err: {:?}", err);
});
warp::ext::set::<Context>(ctx);
})
.untuple_one()
} }

View File

@ -6,6 +6,7 @@ use warp::{Filter, Rejection};
#[derive(Debug, Copy, Clone, Default, Serialize, Deserialize)] #[derive(Debug, Copy, Clone, Default, Serialize, Deserialize)]
pub struct Session { pub struct Session {
pub user_id: Option<i32>, pub user_id: Option<i32>,
pub team_id: Option<i32>,
} }
pub fn extract() -> impl Clone + Filter<Extract = (), Error = Rejection> { pub fn extract() -> impl Clone + Filter<Extract = (), Error = Rejection> {
@ -48,8 +49,9 @@ pub fn apply() -> impl Clone + Filter<Extract = (Option<String>,), Error = Rejec
.map(|data| { .map(|data| {
jar.private(&key).add( jar.private(&key).add(
Cookie::build("session", data) Cookie::build("session", data)
.secure(true) // .secure(true) // TODO: enable this based on a config
.http_only(true) .http_only(true)
.path("/")
.same_site(SameSite::Strict) .same_site(SameSite::Strict)
.finish(), .finish(),
); );

View File

@ -4,16 +4,18 @@ use core::{
Error, State, Error, State,
}; };
use http::uri::Uri; use http::uri::Uri;
use tera::Context;
use warp::Filter; use warp::Filter;
use wtforms::Form; use wtforms::Form;
use crate::extractors::navbar; use crate::extractors::{get_context, navbar, Context};
use crate::render::render_template; use crate::render::render_template;
use crate::session::Session; use crate::session::Session;
pub fn get_login() -> Resp!() { pub fn get_login() -> Resp!() {
navbar().and_then(|| render_template("users/login.html", Context::new())) warp::path::end()
.and(navbar())
.and(get_context())
.and_then(|ctx: Context| render_template("users/login.html", ctx.into()))
} }
pub fn post_login() -> Resp!() { pub fn post_login() -> Resp!() {
@ -39,11 +41,17 @@ pub fn post_login() -> Resp!() {
} }
pub fn get_profile() -> Resp!() { pub fn get_profile() -> Resp!() {
navbar().and_then(|| render_template("users/profile.html", Context::new())) warp::path::end()
.and(navbar())
.and(get_context())
.and_then(|ctx: Context| render_template("users/profile.html", ctx.into()))
} }
pub fn get_register() -> Resp!() { pub fn get_register() -> Resp!() {
navbar().and_then(|| render_template("users/register.html", Context::new())) warp::path::end()
.and(navbar())
.and(get_context())
.and_then(|ctx: Context| render_template("users/register.html", ctx.into()))
} }
pub fn post_register() -> Resp!() { pub fn post_register() -> Resp!() {

View File

@ -9,7 +9,7 @@
<title>{% block title %}{% endblock %} - LibreCTF</title> <title>{% block title %}{% endblock %} - LibreCTF</title>
</head> </head>
<body> <body>
<nav class="container navbar" role="navigation" aria-label="main navigation"> <nav class="container navbar" style="margin-bottom: 20px;" role="navigation" aria-label="main navigation">
<div class="navbar-brand"> <div class="navbar-brand">
<a class="navbar-item" href="/"><h3>LibreCTF</h3></a> <a class="navbar-item" href="/"><h3>LibreCTF</h3></a>
@ -22,7 +22,7 @@
<div id="navbarBasicExample" class="navbar-menu"> <div id="navbarBasicExample" class="navbar-menu">
<div class="navbar-start"> <div class="navbar-start">
<a class="navbar-item">Home</a> <a class="navbar-item" href="/">Home</a>
<a class="navbar-item">Documentation</a> <a class="navbar-item">Documentation</a>
<div class="navbar-item has-dropdown is-hoverable"> <div class="navbar-item has-dropdown is-hoverable">
@ -39,10 +39,22 @@
<div class="navbar-end"> <div class="navbar-end">
<div class="navbar-item"> <div class="navbar-item">
<div class="buttons"> {% if user %}
<a href="/users/register" class="button is-primary"><strong>Sign up</strong></a> <a class="navbar-item">My Team</a>
<a href="/users/login" class="button is-light">Log in</a> <div class="navbar-item has-dropdown is-hoverable">
</div> <a class="navbar-link">{{ user.name }}</a>
<div class="navbar-dropdown">
<a class="navbar-item" href="/users/profile">Profile</a>
<hr class="navbar-divider">
<a class="navbar-item" href="/users/logout">Logout</a>
</div>
</div>
{% else %}
<div class="buttons">
<a href="/users/register" class="button is-primary"><strong>Sign up</strong></a>
<a href="/users/login" class="button is-light">Log in</a>
</div>
{% endif %}
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,5 +1,32 @@
{% extends "layout.html" %} {% extends "layout.html" %}
{% block title %}Register{% endblock %} {% block title %}Profile{% endblock %}
{% block content %} {% block content %}
<section class="container">
<div class="columns">
<div class="column is-one-quarter">
<nav class="panel">
<div class="panel-block">
<p class="control has-icons-left">
<input class="input" type="text" placeholder="search">
<span class="icon is-left">
<i class="fas fa-search" aria-hidden="true"></i>
</span>
</p>
</div>
</nav>
</div>
<div class="column">
<a href="/users/settings" class="button is-link" style="float: right; margin-right: 30px;">Settings</a>
<div class="tabs is-boxed">
<ul>
<li class="is-active"><a>Profile</a></li>
<li><a>Music</a></li>
<li><a>Videos</a></li>
<li><a>Documents</a></li>
</ul>
</div>
</div>
</div>
</section>
{% endblock %} {% endblock %}