Update
This commit is contained in:
parent
9682962ac6
commit
44d6f59453
|
@ -14,12 +14,12 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
FROM crystallang/crystal:1.6-alpine as micrate-deps
|
||||
FROM crystallang/crystal:1.6.2-alpine as micrate-deps
|
||||
WORKDIR /usr/src/micrate
|
||||
COPY ./micrate/shard.yml ./micrate/shard.lock ./
|
||||
RUN shards install --production
|
||||
|
||||
FROM crystallang/crystal:1.6-alpine as micrate-builder
|
||||
FROM crystallang/crystal:1.6.2-alpine as micrate-builder
|
||||
WORKDIR /usr/src/micrate
|
||||
COPY --from=micrate-deps /usr/src/micrate/shard.yml /usr/src/micrate/shard.lock ./
|
||||
COPY --from=micrate-deps /usr/src/micrate/lib ./lib
|
||||
|
@ -31,12 +31,12 @@ WORKDIR /usr/src/public
|
|||
COPY ./public ./src
|
||||
RUN minify -r -o ./dist ./src
|
||||
|
||||
FROM crystallang/crystal:1.6-alpine as deps
|
||||
FROM crystallang/crystal:1.6.2-alpine as deps
|
||||
WORKDIR /usr/src/mentorenwahl
|
||||
COPY ./shard.yml ./shard.lock ./
|
||||
RUN shards install --production
|
||||
|
||||
FROM crystallang/crystal:1.6-alpine as builder
|
||||
FROM crystallang/crystal:1.6.2-alpine as builder
|
||||
WORKDIR /usr/src/mentorenwahl
|
||||
RUN apk add --no-cache pcre2-dev
|
||||
RUN mkdir deps
|
||||
|
|
|
@ -8,7 +8,7 @@ targets:
|
|||
micrate:
|
||||
main: src/micrate.cr
|
||||
|
||||
crystal: 1.5.0
|
||||
crystal: 1.6.2
|
||||
|
||||
dependencies:
|
||||
micrate:
|
||||
|
|
|
@ -26,7 +26,7 @@ targets:
|
|||
backend:
|
||||
main: src/backend.cr
|
||||
|
||||
crystal: 1.6.1
|
||||
crystal: 1.6.2
|
||||
|
||||
dependencies:
|
||||
clear:
|
||||
|
|
|
@ -34,13 +34,13 @@ module Backend
|
|||
getter iat : Int64
|
||||
getter exp : Int64
|
||||
getter jti : UUID
|
||||
getter context : Context
|
||||
getter user_id : Int32
|
||||
|
||||
def initialize(
|
||||
@iat : Int64,
|
||||
@exp : Int64,
|
||||
@jti : UUID,
|
||||
@context : Context
|
||||
@user_id : Int32
|
||||
)
|
||||
end
|
||||
|
||||
|
@ -53,7 +53,7 @@ module Backend
|
|||
iat: token["iat"].as_i64,
|
||||
exp: token["exp"].as_i64,
|
||||
jti: UUID.new(token["jti"].as_s),
|
||||
context: Context.from_hash(token["context"].as_h)
|
||||
user_id: token["user_id"].as_i
|
||||
)
|
||||
end
|
||||
|
||||
|
@ -61,20 +61,6 @@ module Backend
|
|||
self.from_hash(JWT.decode(jwt, Backend.config.api.jwt_secret, JWT::Algorithm::HS256)[0].as_h)
|
||||
end
|
||||
end
|
||||
|
||||
# JWT token context data
|
||||
struct Context
|
||||
include JSON::Serializable
|
||||
|
||||
getter user : Int32
|
||||
|
||||
def initialize(@user : Int32)
|
||||
end
|
||||
|
||||
def self.from_hash(data : Hash(String, JSON::Any))
|
||||
self.new(user: data["user"].as_i)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -67,7 +67,7 @@ module Backend
|
|||
rescue
|
||||
@status = Status::JWTError
|
||||
else
|
||||
if @user = Db::User.find(payload.context.user)
|
||||
if @user = Db::User.find(payload.user_id)
|
||||
@jti = payload.jti
|
||||
if Db::Token.query.where { (id == payload.jti) & active }.first.nil?
|
||||
@status = Status::SessionExpired
|
||||
|
|
|
@ -46,7 +46,7 @@ module Backend
|
|||
iat: token.iat.to_unix,
|
||||
exp: token.exp.to_unix,
|
||||
jti: token.id.not_nil!,
|
||||
context: Auth::Context.new(user.id.not_nil!)
|
||||
user_id: user.id.not_nil!
|
||||
).encode
|
||||
)
|
||||
end
|
||||
|
|
|
@ -56,6 +56,9 @@ module Backend
|
|||
raw("EXISTS (SELECT 1 FROM teacher_votes WHERE teacher_id = teachers.id)") &
|
||||
max_students > 0
|
||||
end
|
||||
pp! teachers
|
||||
students = Db::Student.query.with_vote(&.with_teacher_votes).to_a
|
||||
pp! students
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -24,3 +24,5 @@ lazy_static = "1.4.0"
|
|||
const_format = "0.2.30"
|
||||
yew-agent = "0.1.0"
|
||||
yew-side-effect = "0.2.0"
|
||||
uuid = { version = "1.2.2", features = ["serde"] }
|
||||
chrono = { version = "0.4.23", features = ["serde"] }
|
||||
|
|
|
@ -9,3 +9,25 @@ body {
|
|||
#wrapper {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.fieldset {
|
||||
background-color: #fff;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 0.5em 1em -0.125em rgba(10, 10, 10, 0.1),
|
||||
0 0 0 1px rgba(10, 10, 10, 0.02);
|
||||
color: #4a4a4a;
|
||||
display: block;
|
||||
padding: 1.25rem;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.fieldset > legend {
|
||||
color: #363636;
|
||||
display: block;
|
||||
font-size: 1rem;
|
||||
font-weight: 700;
|
||||
background-color: #fff;
|
||||
padding: 0 5px;
|
||||
width: max-content;
|
||||
border: 0 none;
|
||||
}
|
||||
|
|
|
@ -4,13 +4,12 @@ use yew_agent::{Dispatched, Dispatcher};
|
|||
use yew_router::prelude::*;
|
||||
|
||||
use crate::agents;
|
||||
use crate::components;
|
||||
use crate::graphql;
|
||||
use crate::routes;
|
||||
|
||||
pub enum Msg {
|
||||
LogoutClicked,
|
||||
Logout(graphql::Errors),
|
||||
Logout,
|
||||
BurgerClicked,
|
||||
}
|
||||
|
||||
|
@ -18,14 +17,11 @@ pub enum Msg {
|
|||
pub struct NavbarProps {
|
||||
pub token: Option<String>,
|
||||
pub logged_in: bool,
|
||||
#[prop_or(true)]
|
||||
pub default_theme: bool,
|
||||
}
|
||||
|
||||
pub struct Navbar {
|
||||
logged_in: bool,
|
||||
logged_in_event_bus: Dispatcher<agents::logged_in::EventBus>,
|
||||
errors: Option<Vec<String>>,
|
||||
burger_active: bool,
|
||||
}
|
||||
|
||||
|
@ -37,7 +33,6 @@ impl Component for Navbar {
|
|||
Self {
|
||||
logged_in: ctx.props().logged_in,
|
||||
logged_in_event_bus: agents::logged_in::EventBus::dispatcher(),
|
||||
errors: None,
|
||||
burger_active: false,
|
||||
}
|
||||
}
|
||||
|
@ -47,7 +42,7 @@ impl Component for Navbar {
|
|||
Msg::LogoutClicked => {
|
||||
let client = graphql::client(ctx.props().token.as_ref()).unwrap();
|
||||
ctx.link().send_future(async move {
|
||||
let response = post_graphql::<graphql::mutations::logout::Logout, _>(
|
||||
post_graphql::<graphql::mutations::logout::Logout, _>(
|
||||
&client,
|
||||
graphql::URL.as_str(),
|
||||
graphql::mutations::logout::logout::Variables,
|
||||
|
@ -55,20 +50,15 @@ impl Component for Navbar {
|
|||
.await
|
||||
.unwrap();
|
||||
|
||||
Msg::Logout(graphql::convert(response.errors))
|
||||
Msg::Logout
|
||||
});
|
||||
|
||||
false
|
||||
}
|
||||
Msg::Logout(errors) => {
|
||||
if errors.is_none() {
|
||||
self.logged_in_event_bus
|
||||
.send(agents::logged_in::Request::LoggedIn(false));
|
||||
false
|
||||
} else {
|
||||
self.errors = errors;
|
||||
true
|
||||
}
|
||||
Msg::Logout => {
|
||||
self.logged_in_event_bus
|
||||
.send(agents::logged_in::Request::LoggedIn(false));
|
||||
false
|
||||
}
|
||||
Msg::BurgerClicked => {
|
||||
self.burger_active = !self.burger_active;
|
||||
|
@ -101,10 +91,15 @@ impl Component for Navbar {
|
|||
</a>
|
||||
</div>
|
||||
|
||||
<div class={classes!("navbar-menu", if self.burger_active { Some("is-active") } else { None })}>
|
||||
<div class={classes!(
|
||||
"navbar-menu",
|
||||
"is-white",
|
||||
if self.burger_active { Some("is-active") } else { None }
|
||||
)}
|
||||
>
|
||||
<div class={classes!("navbar-start")}>
|
||||
<Link<routes::Route> to={routes::Route::Home} classes={classes!("navbar-item")}>
|
||||
<strong>{ "Home" }</strong>
|
||||
{ "Home" }
|
||||
</Link<routes::Route>>
|
||||
|
||||
<Link<routes::Route> to={routes::Route::Settings} classes={classes!("navbar-item")}>
|
||||
|
@ -132,8 +127,6 @@ impl Component for Navbar {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<components::graphql_errors::GraphQLErrors errors={self.errors.to_owned()} />
|
||||
</nav>
|
||||
</header>
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ use std::path::Path;
|
|||
|
||||
pub mod mutations;
|
||||
pub mod queries;
|
||||
pub mod scalars;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref URL: String =
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
use graphql_client::GraphQLQuery;
|
||||
|
||||
type UUID = String;
|
||||
use crate::graphql::scalars;
|
||||
|
||||
type UUID = scalars::UUID;
|
||||
|
||||
#[derive(GraphQLQuery)]
|
||||
#[graphql(
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
use graphql_client::GraphQLQuery;
|
||||
|
||||
type UUID = String;
|
||||
use crate::graphql::scalars;
|
||||
|
||||
// type UUID = String;
|
||||
type UUID = scalars::UUID;
|
||||
type Time = String;
|
||||
|
||||
#[derive(GraphQLQuery)]
|
||||
|
|
2
frontend/src/graphql/scalars.rs
Normal file
2
frontend/src/graphql/scalars.rs
Normal file
|
@ -0,0 +1,2 @@
|
|||
pub type UUID = uuid::Uuid;
|
||||
// pub type Time =
|
|
@ -82,7 +82,7 @@ impl Component for Home {
|
|||
|
||||
html! {
|
||||
<>
|
||||
<section class={classes!("hero", "is-primary")}>
|
||||
<section class={classes!("hero", "is-light")}>
|
||||
<div class={classes!("hero-body")}>
|
||||
<p class={classes!("title")}>{ format!("Hey, {}!", me.first_name) }</p>
|
||||
</div>
|
||||
|
|
|
@ -28,21 +28,17 @@ impl Component for Index {
|
|||
<div id="wrapper">
|
||||
<section class={classes!("hero", "is-success", "is-fullheight")}>
|
||||
<div class={classes!("hero-head")}>
|
||||
<components::navbar::Navbar
|
||||
token={ctx.props().token.to_owned()}
|
||||
logged_in={ctx.props().logged_in}
|
||||
default_theme=false
|
||||
/>
|
||||
<components::navbar::Navbar token={ctx.props().token.to_owned()} logged_in={ctx.props().logged_in} />
|
||||
</div>
|
||||
|
||||
<div class={classes!("hero-body")}>
|
||||
<div class={classes!("container", "has-text-centered")}>
|
||||
<p class={classes!("title")}>
|
||||
<h1 class={classes!("title")}>
|
||||
{ "Mentorenwahl" }
|
||||
</p>
|
||||
<p class={classes!("subtitle")}>
|
||||
{ "Programmierprojekt des Otto-Hahn-Gymnasiums Furtwangen vermarktet als GFS" }
|
||||
</p>
|
||||
</h1>
|
||||
<h2 class={classes!("subtitle")}>
|
||||
{ "Programmierprojekt für das Otto-Hahn-Gymnasium Furtwangen vermarktet als GFS" }
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -82,11 +82,9 @@ pub fn switch(routes: &Route) -> Html {
|
|||
}
|
||||
Route::Info => {
|
||||
html! {
|
||||
<layouts::logged_in::LoggedIn {logged_in}>
|
||||
<layouts::main::Main token={token.to_owned()} {logged_in}>
|
||||
<h1>{ "Informationen" }</h1>
|
||||
</layouts::main::Main>
|
||||
</layouts::logged_in::LoggedIn>
|
||||
<layouts::main::Main token={token.to_owned()} {logged_in}>
|
||||
<h1>{ "Informationen" }</h1>
|
||||
</layouts::main::Main>
|
||||
}
|
||||
}
|
||||
Route::Login => html! {
|
||||
|
|
|
@ -10,10 +10,7 @@ pub enum Msg {
|
|||
tokens: Vec<graphql::queries::tokens::tokens::TokensTokens>,
|
||||
},
|
||||
Revoke(usize),
|
||||
RevokeDone {
|
||||
errors: Option<Vec<String>>,
|
||||
id: usize,
|
||||
},
|
||||
RevokeDone(Option<Vec<String>>),
|
||||
}
|
||||
|
||||
#[derive(Properties, PartialEq, Eq)]
|
||||
|
@ -64,6 +61,8 @@ impl Component for Tokens {
|
|||
true
|
||||
}
|
||||
Msg::Revoke(id) => {
|
||||
self.tokens.as_mut().unwrap().remove(id);
|
||||
|
||||
let client = graphql::client(Some(&ctx.props().token)).unwrap();
|
||||
let token = self.tokens.as_ref().unwrap()[id].id.to_owned();
|
||||
ctx.link().send_future(async move {
|
||||
|
@ -76,17 +75,13 @@ impl Component for Tokens {
|
|||
.await
|
||||
.unwrap();
|
||||
|
||||
Msg::RevokeDone {
|
||||
errors: graphql::convert(response.errors),
|
||||
id,
|
||||
}
|
||||
Msg::RevokeDone(graphql::convert(response.errors))
|
||||
});
|
||||
|
||||
false
|
||||
true
|
||||
}
|
||||
Msg::RevokeDone { errors, id } => {
|
||||
Msg::RevokeDone(errors) => {
|
||||
self.errors = errors;
|
||||
self.tokens.as_mut().unwrap().remove(id);
|
||||
true
|
||||
}
|
||||
}
|
||||
|
@ -94,32 +89,36 @@ impl Component for Tokens {
|
|||
|
||||
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||
html! {
|
||||
<fieldset>
|
||||
<fieldset class={classes!("fieldset")}>
|
||||
<legend>{ "Tokens" }</legend>
|
||||
if self.fetching {
|
||||
<p>{ "Fetching..." }</p>
|
||||
} else {
|
||||
<table>
|
||||
<tr>
|
||||
<th>{ "ID" }</th>
|
||||
<th>{ "Issued at" }</th>
|
||||
<th>{ "Expires at" }</th>
|
||||
<th>{ "Revoke" }</th>
|
||||
</tr>
|
||||
{ for self.tokens.as_ref().unwrap().iter().enumerate().map(|(i, t)| {
|
||||
html! {
|
||||
<tr>
|
||||
<td><code>{ &t.id }</code></td>
|
||||
<td><code>{ &t.iat }</code></td>
|
||||
<td><code>{ &t.exp }</code></td>
|
||||
<td>
|
||||
<button onclick={ctx.link().callback(move |_| Msg::Revoke(i))}>
|
||||
{ "Revoke" }
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
}) }
|
||||
<table class={classes!("table")}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th><abbr title="JTI claim of the JWT">{ "ID" }</abbr></th>
|
||||
<th><abbr title="IAT claim of the JWT">{ "Issued at" }</abbr></th>
|
||||
<th><abbr title="EXP claim of the JWT">{ "Expires at" }</abbr></th>
|
||||
<th>{ "Revoke" }</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{ for self.tokens.as_ref().unwrap().iter().enumerate().map(|(i, t)| {
|
||||
html! {
|
||||
<tr>
|
||||
<th><code>{ &t.id }</code></th>
|
||||
<td><code>{ &t.iat }</code></td>
|
||||
<td><code>{ &t.exp }</code></td>
|
||||
<td>
|
||||
<button onclick={ctx.link().callback(move |_| Msg::Revoke(i))}>
|
||||
{ "Revoke" }
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
}) }
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<components::graphql_errors::GraphQLErrors errors={self.errors.to_owned()} />
|
||||
|
|
Loading…
Reference in a new issue