Update frontend
continuous-integration/drone/push Build is failing
Details
continuous-integration/drone/push Build is failing
Details
This commit is contained in:
parent
ff2b884d42
commit
2ed278683b
|
@ -63,8 +63,9 @@ module Backend
|
||||||
@status = Status::JWTError
|
@status = Status::JWTError
|
||||||
else
|
else
|
||||||
pp! payload
|
pp! payload
|
||||||
|
if payload.iss != "Mentorenwahl" || payload.vrs != Backend::VERSION
|
||||||
if @user = Db::User.find(payload.context.user)
|
@status = Status::JWTError
|
||||||
|
elsif @user = Db::User.find(payload.context.user)
|
||||||
@admin = user.not_nil!.admin
|
@admin = user.not_nil!.admin
|
||||||
@role = user.not_nil!.role.to_api
|
@role = user.not_nil!.role.to_api
|
||||||
@external =
|
@external =
|
||||||
|
|
|
@ -48,7 +48,7 @@ module Backend
|
||||||
|
|
||||||
@[GraphQL::Field]
|
@[GraphQL::Field]
|
||||||
# Creates user
|
# Creates user
|
||||||
def create_user(context : Context, input : UserCreateInput, check_ldap : Bool = true) : User
|
def create_user(context : Context, input : UserCreateInput, check_ldap : Bool = true) : User?
|
||||||
context.admin!
|
context.admin!
|
||||||
|
|
||||||
raise Errors::LdapUserDoesNotExist.new if check_ldap && begin
|
raise Errors::LdapUserDoesNotExist.new if check_ldap && begin
|
||||||
|
@ -64,7 +64,7 @@ module Backend
|
||||||
|
|
||||||
@[GraphQL::Field]
|
@[GraphQL::Field]
|
||||||
# Deletes user by ID
|
# Deletes user by ID
|
||||||
def delete_user(context : Context, id : Int32) : Int32
|
def delete_user(context : Context, id : Int32) : Int32?
|
||||||
context.admin!
|
context.admin!
|
||||||
|
|
||||||
user = Db::User.find!(id)
|
user = Db::User.find!(id)
|
||||||
|
@ -75,7 +75,7 @@ module Backend
|
||||||
|
|
||||||
@[GraphQL::Field]
|
@[GraphQL::Field]
|
||||||
# Starts assignment job of mentors to students
|
# Starts assignment job of mentors to students
|
||||||
def assign_students(context : Context) : Bool
|
def assign_students(context : Context) : Bool?
|
||||||
context.admin!
|
context.admin!
|
||||||
|
|
||||||
Worker::Jobs::AssignStudentsJob.new.enqueue
|
Worker::Jobs::AssignStudentsJob.new.enqueue
|
||||||
|
@ -92,6 +92,15 @@ module Backend
|
||||||
# Teacher.new(teacher)
|
# Teacher.new(teacher)
|
||||||
# end
|
# end
|
||||||
|
|
||||||
|
@[GraphQL::Field]
|
||||||
|
def register_teacher(context : Context, input : TeacherInput) : Teacher?
|
||||||
|
context.teacher!(false)
|
||||||
|
raise Errors::InvalidPermissions.new if context.user.not_nil!.teacher
|
||||||
|
|
||||||
|
teacher = Db::Teacher.create!(user_id: context.user.not_nil!.id, max_students: input.max_students)
|
||||||
|
Teacher.new(teacher)
|
||||||
|
end
|
||||||
|
|
||||||
# @[GraphQL::Field]
|
# @[GraphQL::Field]
|
||||||
# # Deletes teacher by ID
|
# # Deletes teacher by ID
|
||||||
# def delete_teacher(context : Context, id : Int32) : Int32
|
# def delete_teacher(context : Context, id : Int32) : Int32
|
||||||
|
@ -148,7 +157,7 @@ module Backend
|
||||||
|
|
||||||
@[GraphQL::Field]
|
@[GraphQL::Field]
|
||||||
# Creates vote for authenticated user's student
|
# Creates vote for authenticated user's student
|
||||||
def create_vote(context : Context, input : VoteCreateInput) : Vote
|
def create_vote(context : Context, input : VoteCreateInput) : Vote?
|
||||||
context.student!
|
context.student!
|
||||||
|
|
||||||
raise Errors::DuplicateTeachers.new if input.teacher_ids.uniq.size != input.teacher_ids.size
|
raise Errors::DuplicateTeachers.new if input.teacher_ids.uniq.size != input.teacher_ids.size
|
||||||
|
|
|
@ -33,7 +33,7 @@ module Backend
|
||||||
|
|
||||||
@[GraphQL::Field]
|
@[GraphQL::Field]
|
||||||
# Current authenticated user
|
# Current authenticated user
|
||||||
def me(context : Context) : User
|
def me(context : Context) : User?
|
||||||
context.authenticated!
|
context.authenticated!
|
||||||
|
|
||||||
User.new(context.user.not_nil!)
|
User.new(context.user.not_nil!)
|
||||||
|
@ -41,7 +41,7 @@ module Backend
|
||||||
|
|
||||||
@[GraphQL::Field]
|
@[GraphQL::Field]
|
||||||
# User by ID
|
# User by ID
|
||||||
def user(context : Context, id : Int32) : User
|
def user(context : Context, id : Int32) : User?
|
||||||
context.admin!
|
context.admin!
|
||||||
|
|
||||||
User.from_id(id)
|
User.from_id(id)
|
||||||
|
@ -49,14 +49,14 @@ module Backend
|
||||||
|
|
||||||
@[GraphQL::Field]
|
@[GraphQL::Field]
|
||||||
# All users
|
# All users
|
||||||
def users(context : Context) : Array(User)
|
def users(context : Context) : Array(User)?
|
||||||
context.admin!
|
context.admin!
|
||||||
|
|
||||||
Db::User.query.map { |user| User.new(user) }
|
Db::User.query.map { |user| User.new(user) }
|
||||||
end
|
end
|
||||||
|
|
||||||
@[GraphQL::Field]
|
@[GraphQL::Field]
|
||||||
def user_by_username(context : Context, username : String) : User
|
def user_by_username(context : Context, username : String) : User?
|
||||||
context.admin!
|
context.admin!
|
||||||
|
|
||||||
User.new(Db::User.query.find { var(:username) == username }.not_nil!)
|
User.new(Db::User.query.find { var(:username) == username }.not_nil!)
|
||||||
|
@ -64,7 +64,7 @@ module Backend
|
||||||
|
|
||||||
@[GraphQL::Field]
|
@[GraphQL::Field]
|
||||||
# All admins
|
# All admins
|
||||||
def admins(context : Context) : Array(User)
|
def admins(context : Context) : Array(User)?
|
||||||
context.admin!
|
context.admin!
|
||||||
|
|
||||||
Db::User.query.where(admin: true).map { |user| User.new(user) }
|
Db::User.query.where(admin: true).map { |user| User.new(user) }
|
||||||
|
@ -84,7 +84,7 @@ module Backend
|
||||||
|
|
||||||
@[GraphQL::Field]
|
@[GraphQL::Field]
|
||||||
# Student by ID
|
# Student by ID
|
||||||
def student(context : Context, id : Int32) : Student
|
def student(context : Context, id : Int32) : Student?
|
||||||
context.admin!
|
context.admin!
|
||||||
|
|
||||||
Student.new(Db::Student.find!(id))
|
Student.new(Db::Student.find!(id))
|
||||||
|
@ -92,7 +92,7 @@ module Backend
|
||||||
|
|
||||||
@[GraphQL::Field]
|
@[GraphQL::Field]
|
||||||
# All students
|
# All students
|
||||||
def students(context : Context) : Array(Student)
|
def students(context : Context) : Array(Student)?
|
||||||
context.admin!
|
context.admin!
|
||||||
|
|
||||||
Db::Student.query.map { |student| Student.new(student) }
|
Db::Student.query.map { |student| Student.new(student) }
|
||||||
|
@ -100,7 +100,7 @@ module Backend
|
||||||
|
|
||||||
@[GraphQL::Field]
|
@[GraphQL::Field]
|
||||||
# Vote by ID
|
# Vote by ID
|
||||||
def vote(context : Context, id : Int32) : Vote
|
def vote(context : Context, id : Int32) : Vote?
|
||||||
context.admin!
|
context.admin!
|
||||||
|
|
||||||
Vote.new(Db::Vote.find!(id))
|
Vote.new(Db::Vote.find!(id))
|
||||||
|
@ -108,7 +108,7 @@ module Backend
|
||||||
|
|
||||||
@[GraphQL::Field]
|
@[GraphQL::Field]
|
||||||
# All votes
|
# All votes
|
||||||
def votes(context : Context) : Array(Vote)
|
def votes(context : Context) : Array(Vote)?
|
||||||
context.admin!
|
context.admin!
|
||||||
|
|
||||||
Db::Vote.query.map { |vote| Vote.new(vote) }
|
Db::Vote.query.map { |vote| Vote.new(vote) }
|
||||||
|
@ -116,7 +116,7 @@ module Backend
|
||||||
|
|
||||||
@[GraphQL::Field]
|
@[GraphQL::Field]
|
||||||
# All students voted
|
# All students voted
|
||||||
def all_students_voted(context : Context) : Bool
|
def all_students_voted(context : Context) : Bool?
|
||||||
context.admin!
|
context.admin!
|
||||||
|
|
||||||
votes = Db::Vote.query.count
|
votes = Db::Vote.query.count
|
||||||
|
@ -135,7 +135,7 @@ module Backend
|
||||||
|
|
||||||
@[GraphQL::Field]
|
@[GraphQL::Field]
|
||||||
# Teacher vote by ID
|
# Teacher vote by ID
|
||||||
def teacher_vote(context : Context, id : Int32) : TeacherVote
|
def teacher_vote(context : Context, id : Int32) : TeacherVote?
|
||||||
context.admin!
|
context.admin!
|
||||||
|
|
||||||
TeacherVote.new(Db::TeacherVote.find!(id))
|
TeacherVote.new(Db::TeacherVote.find!(id))
|
||||||
|
@ -143,7 +143,7 @@ module Backend
|
||||||
|
|
||||||
@[GraphQL::Field]
|
@[GraphQL::Field]
|
||||||
# All teacher votes
|
# All teacher votes
|
||||||
def teacher_votes(context : Context) : Array(TeacherVote)
|
def teacher_votes(context : Context) : Array(TeacherVote)?
|
||||||
context.admin!
|
context.admin!
|
||||||
|
|
||||||
Db::TeacherVote.query.map { |vote| TeacherVote.new(vote) }
|
Db::TeacherVote.query.map { |vote| TeacherVote.new(vote) }
|
||||||
|
|
|
@ -92,7 +92,7 @@ module Backend
|
||||||
in Api::Schema::UserRole::Student
|
in Api::Schema::UserRole::Student
|
||||||
Db::Student.create!(user_id: user.id)
|
Db::Student.create!(user_id: user.id)
|
||||||
in Api::Schema::UserRole::Teacher
|
in Api::Schema::UserRole::Teacher
|
||||||
Db::Teacher.create!(user_id: user.id)
|
# Db::Teacher.create!(user_id: user.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
Worker::Jobs::CacheLdapUserJob.new(user.id).enqueue
|
Worker::Jobs::CacheLdapUserJob.new(user.id).enqueue
|
||||||
|
|
|
@ -18,3 +18,4 @@ Dockerfile
|
||||||
.gitignore
|
.gitignore
|
||||||
.dockerignore
|
.dockerignore
|
||||||
vendor/
|
vendor/
|
||||||
|
.editorconfig
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
# Mentorenwahl: A fullstack application for assigning mentors to students based on their whishes.
|
||||||
|
# Copyright (C) 2022 Dominic Grimm
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*.rs]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
|
trim_trailing_whitespace = true
|
|
@ -3,6 +3,12 @@ name = "frontend"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
lto = true
|
||||||
|
codegen-units = 1
|
||||||
|
opt-level = "z"
|
||||||
|
panic = "abort"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
yew = "0.19.3"
|
yew = "0.19.3"
|
||||||
wasm-logger = "0.2.0"
|
wasm-logger = "0.2.0"
|
||||||
|
@ -17,3 +23,4 @@ wasm-cookies = "0.1.0"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
const_format = "0.2.30"
|
const_format = "0.2.30"
|
||||||
yew-agent = "0.1.0"
|
yew-agent = "0.1.0"
|
||||||
|
yew-side-effect = "0.2.0"
|
||||||
|
|
|
@ -26,6 +26,12 @@ WORKDIR /usr/src/public
|
||||||
COPY --from=builder /usr/src/frontend/dist .
|
COPY --from=builder /usr/src/frontend/dist .
|
||||||
RUN minify . -r -o .
|
RUN minify . -r -o .
|
||||||
|
|
||||||
|
FROM alpine as binaryen
|
||||||
|
RUN apk add --no-cache binaryen
|
||||||
|
WORKDIR /usr/src/public
|
||||||
|
COPY --from=public /usr/src/public .
|
||||||
|
RUN find . -name "*.wasm" -type f | xargs -I % wasm-opt % -o % -O --intrinsic-lowering -Oz
|
||||||
|
|
||||||
FROM nginx:alpine as runner
|
FROM nginx:alpine as runner
|
||||||
COPY ./nginx.conf /etc/nginx/nginx.conf
|
COPY ./nginx.conf /etc/nginx/nginx.conf
|
||||||
COPY --from=public /usr/src/public /var/www/html
|
COPY --from=binaryen /usr/src/public /var/www/html
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
mutation RegisterTeacher($maxStudents: Int!) {
|
||||||
|
registerTeacher(input: { maxStudents: $maxStudents }) {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
query Config {
|
||||||
|
config {
|
||||||
|
minimumTeacherSelectionCount
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,8 +3,12 @@ query Me {
|
||||||
firstName
|
firstName
|
||||||
role
|
role
|
||||||
student {
|
student {
|
||||||
vote
|
vote {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
teacher {
|
||||||
|
id
|
||||||
}
|
}
|
||||||
teacher
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
query StudentsCanVote {
|
||||||
|
studentsCanVote
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
query Teachers {
|
||||||
|
teachers {
|
||||||
|
id
|
||||||
|
user {
|
||||||
|
name(formal: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,23 +3,23 @@ type Config {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Query {
|
type Query {
|
||||||
admins: [User!]!
|
admins: [User!]
|
||||||
allStudentsVoted: Boolean!
|
allStudentsVoted: Boolean
|
||||||
config: Config!
|
config: Config!
|
||||||
me: User!
|
me: User
|
||||||
ok: Boolean!
|
ok: Boolean!
|
||||||
student(id: Int!): Student!
|
student(id: Int!): Student
|
||||||
students: [Student!]!
|
students: [Student!]
|
||||||
studentsCanVote: Boolean!
|
studentsCanVote: Boolean!
|
||||||
teacher(id: Int!): Teacher!
|
teacher(id: Int!): Teacher!
|
||||||
teacherVote(id: Int!): TeacherVote!
|
teacherVote(id: Int!): TeacherVote
|
||||||
teacherVotes: [TeacherVote!]!
|
teacherVotes: [TeacherVote!]
|
||||||
teachers: [Teacher!]!
|
teachers: [Teacher!]!
|
||||||
user(id: Int!): User!
|
user(id: Int!): User
|
||||||
userByUsername(username: String!): User!
|
userByUsername(username: String!): User
|
||||||
users: [User!]!
|
users: [User!]
|
||||||
vote(id: Int!): Vote!
|
vote(id: Int!): Vote
|
||||||
votes: [Vote!]!
|
votes: [Vote!]
|
||||||
}
|
}
|
||||||
|
|
||||||
type Student {
|
type Student {
|
||||||
|
@ -74,11 +74,16 @@ type LoginPayload {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Mutation {
|
type Mutation {
|
||||||
assignStudents: Boolean!
|
assignStudents: Boolean
|
||||||
createUser(checkLdap: Boolean! = true, input: UserCreateInput!): User!
|
createUser(checkLdap: Boolean! = true, input: UserCreateInput!): User
|
||||||
createVote(input: VoteCreateInput!): Vote!
|
createVote(input: VoteCreateInput!): Vote
|
||||||
deleteUser(id: Int!): Int!
|
deleteUser(id: Int!): Int
|
||||||
login(password: String!, username: String!): LoginPayload
|
login(password: String!, username: String!): LoginPayload
|
||||||
|
registerTeacher(input: TeacherInput!): Teacher
|
||||||
|
}
|
||||||
|
|
||||||
|
input TeacherInput {
|
||||||
|
maxStudents: Int!
|
||||||
}
|
}
|
||||||
|
|
||||||
input UserCreateInput {
|
input UserCreateInput {
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
pub mod logged_in;
|
pub mod logged_in;
|
||||||
|
pub mod teacher_registered;
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use yew_agent::{Agent, AgentLink, Context, HandlerId};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub enum Request {
|
||||||
|
Registered,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct EventBus {
|
||||||
|
link: AgentLink<Self>,
|
||||||
|
subscribers: HashSet<HandlerId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Agent for EventBus {
|
||||||
|
type Reach = Context<Self>;
|
||||||
|
type Message = ();
|
||||||
|
type Input = Request;
|
||||||
|
type Output = ();
|
||||||
|
|
||||||
|
fn create(link: AgentLink<Self>) -> Self {
|
||||||
|
Self {
|
||||||
|
link,
|
||||||
|
subscribers: HashSet::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, _msg: Self::Message) {}
|
||||||
|
|
||||||
|
fn handle_input(&mut self, msg: Self::Input, _id: HandlerId) {
|
||||||
|
match msg {
|
||||||
|
Request::Registered => {
|
||||||
|
for sub in self.subscribers.iter() {
|
||||||
|
self.link.respond(*sub, ());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn connected(&mut self, id: HandlerId) {
|
||||||
|
self.subscribers.insert(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn disconnected(&mut self, id: HandlerId) {
|
||||||
|
self.subscribers.remove(&id);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
use yew::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Properties, PartialEq)]
|
||||||
|
pub struct GraphQLErrorsProps {
|
||||||
|
pub errors: Option<Vec<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct GraphQLErrors;
|
||||||
|
|
||||||
|
impl Component for GraphQLErrors {
|
||||||
|
type Message = ();
|
||||||
|
type Properties = GraphQLErrorsProps;
|
||||||
|
|
||||||
|
fn create(_ctx: &Context<Self>) -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||||
|
html! {
|
||||||
|
if let Some(errors) = &ctx.props().errors {
|
||||||
|
<div>
|
||||||
|
<p style="color: red;">{ "Errors:" }</p>
|
||||||
|
<div>
|
||||||
|
{ for errors.iter().map(|e| html! { <p style="color: red;">{ e }</p> }) }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn convert(errors: Option<Vec<graphql_client::Error>>) -> Option<Vec<String>> {
|
||||||
|
errors.map(|x| x.iter().map(|e| e.message.to_owned()).collect())
|
||||||
|
}
|
|
@ -1 +1,2 @@
|
||||||
|
pub mod graphql_errors;
|
||||||
pub mod logged_in_handler;
|
pub mod logged_in_handler;
|
||||||
|
|
|
@ -12,3 +12,17 @@ lazy_static! {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.to_string();
|
.to_string();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn client(token: Option<&String>) -> Result<reqwest::Client, reqwest::Error> {
|
||||||
|
if let Some(x) = token {
|
||||||
|
let mut headers = reqwest::header::HeaderMap::new();
|
||||||
|
headers.insert(
|
||||||
|
"Authorization",
|
||||||
|
reqwest::header::HeaderValue::from_str(&format!("Bearer {}", x)).unwrap(),
|
||||||
|
);
|
||||||
|
|
||||||
|
reqwest::Client::builder().default_headers(headers).build()
|
||||||
|
} else {
|
||||||
|
Ok(reqwest::Client::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -4,6 +4,6 @@ use graphql_client::GraphQLQuery;
|
||||||
#[graphql(
|
#[graphql(
|
||||||
schema_path = "graphql/schema.graphql",
|
schema_path = "graphql/schema.graphql",
|
||||||
query_path = "graphql/mutations/login.graphql",
|
query_path = "graphql/mutations/login.graphql",
|
||||||
response_derives = "Debug,Serialize,Deserialize"
|
response_derives = "Debug"
|
||||||
)]
|
)]
|
||||||
pub struct Login;
|
pub struct Login;
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
pub mod login;
|
pub mod login;
|
||||||
|
pub mod register_teacher;
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
use graphql_client::GraphQLQuery;
|
||||||
|
|
||||||
|
#[derive(GraphQLQuery)]
|
||||||
|
#[graphql(
|
||||||
|
schema_path = "graphql/schema.graphql",
|
||||||
|
query_path = "graphql/mutations/register_teacher.graphql",
|
||||||
|
response_derives = "Debug"
|
||||||
|
)]
|
||||||
|
pub struct RegisterTeacher;
|
|
@ -0,0 +1,9 @@
|
||||||
|
use graphql_client::GraphQLQuery;
|
||||||
|
|
||||||
|
#[derive(GraphQLQuery)]
|
||||||
|
#[graphql(
|
||||||
|
schema_path = "graphql/schema.graphql",
|
||||||
|
query_path = "graphql/queries/config.graphql",
|
||||||
|
response_derives = "Debug"
|
||||||
|
)]
|
||||||
|
pub struct Config;
|
|
@ -1,2 +1,5 @@
|
||||||
|
pub mod config;
|
||||||
pub mod me;
|
pub mod me;
|
||||||
pub mod ok;
|
pub mod ok;
|
||||||
|
pub mod students_can_vote;
|
||||||
|
pub mod teachers;
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
use graphql_client::GraphQLQuery;
|
||||||
|
|
||||||
|
#[derive(GraphQLQuery)]
|
||||||
|
#[graphql(
|
||||||
|
schema_path = "graphql/schema.graphql",
|
||||||
|
query_path = "graphql/queries/students_can_vote.graphql",
|
||||||
|
response_derives = "Debug"
|
||||||
|
)]
|
||||||
|
pub struct StudentsCanVote;
|
|
@ -0,0 +1,9 @@
|
||||||
|
use graphql_client::GraphQLQuery;
|
||||||
|
|
||||||
|
#[derive(GraphQLQuery)]
|
||||||
|
#[graphql(
|
||||||
|
schema_path = "graphql/schema.graphql",
|
||||||
|
query_path = "graphql/queries/teachers.graphql",
|
||||||
|
response_derives = "Debug"
|
||||||
|
)]
|
||||||
|
pub struct Teachers;
|
|
@ -1,7 +1,6 @@
|
||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
use yew_agent::{Bridge, Bridged};
|
use yew_agent::{Bridge, Bridged};
|
||||||
use yew_router::prelude::*;
|
use yew_router::prelude::*;
|
||||||
use yew_router::scope_ext::RouterScopeExt;
|
|
||||||
|
|
||||||
use crate::agents;
|
use crate::agents;
|
||||||
use crate::routes;
|
use crate::routes;
|
||||||
|
@ -52,7 +51,9 @@ impl Component for LoggedIn {
|
||||||
}
|
}
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
{ for ctx.props().children.iter() }
|
if self.logged_in {
|
||||||
|
{ for ctx.props().children.iter() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
|
use std::rc::Rc;
|
||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
use yew_router::prelude::*;
|
use yew_router::prelude::*;
|
||||||
|
use yew_side_effect::title::TitleProvider;
|
||||||
|
|
||||||
use frontend::routes;
|
use frontend::routes;
|
||||||
|
|
||||||
|
@ -17,9 +19,14 @@ impl Component for App {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view(&self, _ctx: &Context<Self>) -> Html {
|
fn view(&self, _ctx: &Context<Self>) -> Html {
|
||||||
|
let format_title =
|
||||||
|
Rc::new(|m: &str| format!("{} | dergrimm.net", m)) as Rc<dyn Fn(&str) -> String>;
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<Switch<routes::Route> render={Switch::render(routes::switch)} />
|
<TitleProvider default_title="Mentorenwahl | dergrimm.net" {format_title}>
|
||||||
|
<Switch<routes::Route> render={Switch::render(routes::switch)} />
|
||||||
|
</TitleProvider>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,100 +0,0 @@
|
||||||
use graphql_client::reqwest::post_graphql;
|
|
||||||
use yew::prelude::*;
|
|
||||||
|
|
||||||
use crate::graphql;
|
|
||||||
|
|
||||||
pub enum Msg {
|
|
||||||
DoneFetching {
|
|
||||||
errors: Option<Vec<String>>,
|
|
||||||
data: graphql::queries::me::me::ResponseData,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Properties, PartialEq)]
|
|
||||||
pub struct HomeProps {
|
|
||||||
pub token: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
enum State {
|
|
||||||
Fetching,
|
|
||||||
Done,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Home {
|
|
||||||
state: State,
|
|
||||||
errors: Option<Vec<String>>,
|
|
||||||
data: Option<graphql::queries::me::me::ResponseData>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Component for Home {
|
|
||||||
type Message = Msg;
|
|
||||||
type Properties = HomeProps;
|
|
||||||
|
|
||||||
fn create(_ctx: &Context<Self>) -> Self {
|
|
||||||
Self {
|
|
||||||
state: State::Fetching,
|
|
||||||
errors: None,
|
|
||||||
data: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
|
|
||||||
match msg {
|
|
||||||
Msg::DoneFetching { errors, data } => {
|
|
||||||
self.state = State::Done;
|
|
||||||
self.errors = errors;
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn view(&self, ctx: &Context<Self>) -> Html {
|
|
||||||
match self.state {
|
|
||||||
State::Fetching => {
|
|
||||||
let mut headers = reqwest::header::HeaderMap::new();
|
|
||||||
headers.insert(
|
|
||||||
"Authorization",
|
|
||||||
reqwest::header::HeaderValue::from_str(&format!(
|
|
||||||
"Bearer {}",
|
|
||||||
ctx.props().token
|
|
||||||
))
|
|
||||||
.unwrap(),
|
|
||||||
);
|
|
||||||
ctx.link().send_future(async move {
|
|
||||||
let response = post_graphql::<graphql::queries::me::Me, _>(
|
|
||||||
&reqwest::Client::builder()
|
|
||||||
.default_headers(headers)
|
|
||||||
.build()
|
|
||||||
.unwrap(),
|
|
||||||
graphql::URL.as_str(),
|
|
||||||
graphql::queries::me::me::Variables,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
log::debug!("{:?}", response.data);
|
|
||||||
|
|
||||||
Msg::DoneFetching {
|
|
||||||
errors: response
|
|
||||||
.errors
|
|
||||||
.map(|x| x.iter().map(|e| e.message.to_owned()).collect()),
|
|
||||||
data: response.data.unwrap(),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
html! {
|
|
||||||
<p>{ "Fetching..." }</p>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
State::Done => html! {
|
|
||||||
if let Some(errors) = &self.errors {
|
|
||||||
<p>{ "Errors:" }</p>
|
|
||||||
<div>
|
|
||||||
{ for errors.iter().map(|e| html! { <p style="color: red;">{ e }</p> }) }
|
|
||||||
</div>
|
|
||||||
} else {
|
|
||||||
<h1>{ "Hey, !" }</h1>
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,124 @@
|
||||||
|
use graphql_client::reqwest::post_graphql;
|
||||||
|
use yew::prelude::*;
|
||||||
|
use yew_side_effect::title::Title;
|
||||||
|
|
||||||
|
use crate::components;
|
||||||
|
use crate::graphql;
|
||||||
|
|
||||||
|
pub mod student_home;
|
||||||
|
pub mod student_vote;
|
||||||
|
pub mod teacher_home;
|
||||||
|
pub mod teacher_registration;
|
||||||
|
|
||||||
|
pub enum Msg {
|
||||||
|
DoneFetching {
|
||||||
|
errors: Option<Vec<String>>,
|
||||||
|
me: graphql::queries::me::me::MeMe,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Properties, PartialEq)]
|
||||||
|
pub struct HomeProps {
|
||||||
|
pub token: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum State {
|
||||||
|
Fetching,
|
||||||
|
Done,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Home {
|
||||||
|
state: State,
|
||||||
|
errors: Option<Vec<String>>,
|
||||||
|
me: Option<graphql::queries::me::me::MeMe>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for Home {
|
||||||
|
type Message = Msg;
|
||||||
|
type Properties = HomeProps;
|
||||||
|
|
||||||
|
fn create(_ctx: &Context<Self>) -> Self {
|
||||||
|
Self {
|
||||||
|
state: State::Fetching,
|
||||||
|
errors: None,
|
||||||
|
me: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
|
||||||
|
match msg {
|
||||||
|
Msg::DoneFetching { errors, me } => {
|
||||||
|
self.state = State::Done;
|
||||||
|
self.errors = errors;
|
||||||
|
self.me = Some(me);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||||
|
html! {
|
||||||
|
<>
|
||||||
|
<Title value="Home" />
|
||||||
|
{
|
||||||
|
match self.state {
|
||||||
|
State::Fetching => {
|
||||||
|
let client = graphql::client(ctx.props().token.as_ref()).unwrap();
|
||||||
|
ctx.link().send_future(async move {
|
||||||
|
let response = post_graphql::<graphql::queries::me::Me, _>(
|
||||||
|
&client,
|
||||||
|
graphql::URL.as_str(),
|
||||||
|
graphql::queries::me::me::Variables,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
Msg::DoneFetching {
|
||||||
|
errors: components::graphql_errors::convert(response.errors),
|
||||||
|
me: response.data.unwrap().me.unwrap(),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
html! {
|
||||||
|
<p>{ "Fetching..." }</p>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
State::Done => {
|
||||||
|
if let Some(errors) = &self.errors {
|
||||||
|
html! {
|
||||||
|
<components::graphql_errors::GraphQLErrors errors={errors.clone()} />
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let me = self.me.as_ref().unwrap();
|
||||||
|
|
||||||
|
html! {
|
||||||
|
<>
|
||||||
|
<h1>{ format!("Hey, {}!", me.first_name) }</h1>
|
||||||
|
<hr />
|
||||||
|
{
|
||||||
|
match me.role {
|
||||||
|
graphql::queries::me::me::UserRole::Student => html! {
|
||||||
|
<student_home::StudentHome
|
||||||
|
token={ctx.props().token.as_ref().unwrap().to_owned()}
|
||||||
|
voted={me.student.as_ref().unwrap().vote.is_some()}
|
||||||
|
/>
|
||||||
|
},
|
||||||
|
graphql::queries::me::me::UserRole::Teacher => html! {
|
||||||
|
<teacher_home::TeacherHome
|
||||||
|
token={ctx.props().token.as_ref().unwrap().to_owned()}
|
||||||
|
registered={me.teacher.is_some()}
|
||||||
|
/>
|
||||||
|
},
|
||||||
|
graphql::queries::me::me::UserRole::Other(_) => html! {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
use yew::prelude::*;
|
||||||
|
|
||||||
|
use crate::routes::home::student_vote;
|
||||||
|
|
||||||
|
#[derive(Properties, PartialEq)]
|
||||||
|
pub struct StudentHomeProps {
|
||||||
|
pub token: String,
|
||||||
|
pub voted: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct StudentHome;
|
||||||
|
|
||||||
|
impl Component for StudentHome {
|
||||||
|
type Message = ();
|
||||||
|
type Properties = StudentHomeProps;
|
||||||
|
|
||||||
|
fn create(_ctx: &Context<Self>) -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||||
|
html! {
|
||||||
|
if ctx.props().voted {
|
||||||
|
<p>{ "Alles in Ordnung." }</p>
|
||||||
|
} else {
|
||||||
|
<student_vote::StudentVote token={ctx.props().token.to_owned()} />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,220 @@
|
||||||
|
use graphql_client::reqwest::post_graphql;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use yew::prelude::*;
|
||||||
|
|
||||||
|
use crate::components;
|
||||||
|
use crate::graphql;
|
||||||
|
|
||||||
|
pub enum Msg {
|
||||||
|
DoneFetchingCanVote {
|
||||||
|
errors: Option<Vec<String>>,
|
||||||
|
can_vote: bool,
|
||||||
|
},
|
||||||
|
DoneFetchingConfig {
|
||||||
|
errors: Option<Vec<String>>,
|
||||||
|
min: usize,
|
||||||
|
},
|
||||||
|
DoneFetchingTeachers {
|
||||||
|
errors: Option<Vec<String>>,
|
||||||
|
teachers: Vec<graphql::queries::teachers::teachers::TeachersTeachers>,
|
||||||
|
},
|
||||||
|
RadioSelect {
|
||||||
|
priority: usize,
|
||||||
|
teacher: i64,
|
||||||
|
},
|
||||||
|
Submit,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Properties, PartialEq)]
|
||||||
|
pub struct StudentVoteProps {
|
||||||
|
pub token: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct StudentVote {
|
||||||
|
fetching: bool,
|
||||||
|
can_vote: bool,
|
||||||
|
errors: Option<Vec<String>>,
|
||||||
|
min: usize,
|
||||||
|
teachers: Option<Vec<graphql::queries::teachers::teachers::TeachersTeachers>>,
|
||||||
|
votes: HashMap<usize, Option<i64>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for StudentVote {
|
||||||
|
type Message = Msg;
|
||||||
|
type Properties = StudentVoteProps;
|
||||||
|
|
||||||
|
fn create(ctx: &Context<Self>) -> Self {
|
||||||
|
let client = graphql::client(Some(&ctx.props().token)).unwrap();
|
||||||
|
ctx.link().send_future(async move {
|
||||||
|
let can_vote_response =
|
||||||
|
post_graphql::<graphql::queries::students_can_vote::StudentsCanVote, _>(
|
||||||
|
&client,
|
||||||
|
graphql::URL.as_str(),
|
||||||
|
graphql::queries::students_can_vote::students_can_vote::Variables,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
Msg::DoneFetchingCanVote {
|
||||||
|
errors: components::graphql_errors::convert(can_vote_response.errors),
|
||||||
|
can_vote: can_vote_response.data.unwrap().students_can_vote,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Self {
|
||||||
|
fetching: true,
|
||||||
|
can_vote: false,
|
||||||
|
errors: None,
|
||||||
|
min: 0,
|
||||||
|
teachers: None,
|
||||||
|
votes: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
|
||||||
|
match msg {
|
||||||
|
Msg::DoneFetchingCanVote { errors, can_vote } => {
|
||||||
|
self.errors = errors;
|
||||||
|
self.can_vote = can_vote;
|
||||||
|
|
||||||
|
let client = graphql::client(Some(&ctx.props().token)).unwrap();
|
||||||
|
ctx.link().send_future(async move {
|
||||||
|
let response = post_graphql::<graphql::queries::config::Config, _>(
|
||||||
|
&client,
|
||||||
|
graphql::URL.as_str(),
|
||||||
|
graphql::queries::config::config::Variables,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
Msg::DoneFetchingConfig {
|
||||||
|
errors: components::graphql_errors::convert(response.errors),
|
||||||
|
min: response
|
||||||
|
.data
|
||||||
|
.unwrap()
|
||||||
|
.config
|
||||||
|
.minimum_teacher_selection_count as usize,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
Msg::DoneFetchingConfig { errors, min } => {
|
||||||
|
self.errors = errors;
|
||||||
|
self.min = min;
|
||||||
|
|
||||||
|
let client = graphql::client(Some(&ctx.props().token)).unwrap();
|
||||||
|
ctx.link().send_future(async move {
|
||||||
|
let response = post_graphql::<graphql::queries::teachers::Teachers, _>(
|
||||||
|
&client,
|
||||||
|
graphql::URL.as_str(),
|
||||||
|
graphql::queries::teachers::teachers::Variables,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
Msg::DoneFetchingTeachers {
|
||||||
|
errors: components::graphql_errors::convert(response.errors),
|
||||||
|
teachers: response.data.unwrap().teachers,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
Msg::DoneFetchingTeachers { errors, teachers } => {
|
||||||
|
self.fetching = false;
|
||||||
|
self.errors = errors;
|
||||||
|
self.teachers = Some(teachers);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
Msg::RadioSelect { priority, teacher } => {
|
||||||
|
self.votes.insert(priority, Some(teacher));
|
||||||
|
for (p, t) in self.votes.iter_mut() {
|
||||||
|
if *p > priority && *t == Some(teacher) {
|
||||||
|
*t = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
Msg::Submit => {
|
||||||
|
log::debug!("{:?}", self.votes);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||||
|
if self.fetching {
|
||||||
|
html! {
|
||||||
|
<p>{ "Fetching..." }</p>
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let onsubmit = ctx.link().callback(|e: FocusEvent| {
|
||||||
|
e.prevent_default();
|
||||||
|
|
||||||
|
Msg::Submit
|
||||||
|
});
|
||||||
|
let mut teachers: Vec<i64> = vec![];
|
||||||
|
|
||||||
|
html! {
|
||||||
|
<>
|
||||||
|
<h3>{ "Wähle deine Wunschmentoren aus:" }</h3>
|
||||||
|
<form {onsubmit}>
|
||||||
|
{ for (0..self.min).map(|i| {
|
||||||
|
let curr_t = self.votes.get(&i);
|
||||||
|
if let Some(te) = curr_t {
|
||||||
|
if let Some(t) = te {
|
||||||
|
teachers.push(*t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let name = format!("mentors_{}", i);
|
||||||
|
|
||||||
|
html! {
|
||||||
|
<>
|
||||||
|
<fieldset>
|
||||||
|
<legend>{ format!("{}. Wahl", i + 1) }</legend>
|
||||||
|
|
||||||
|
{ for self.teachers.as_ref().unwrap().iter().enumerate().filter_map(|(j, t)| {
|
||||||
|
let checked = curr_t == Some(&Some(t.id));
|
||||||
|
|
||||||
|
if teachers.contains(&t.id) && !checked {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
let id = format!("{}_{}", name, j);
|
||||||
|
let teacher_id = t.id;
|
||||||
|
let onchange = ctx.link().callback(move |_| Msg::RadioSelect { priority: i, teacher: teacher_id });
|
||||||
|
|
||||||
|
Some(html! {
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
id={id.to_owned()}
|
||||||
|
name={name.to_owned()}
|
||||||
|
value={t.user.name.to_owned()}
|
||||||
|
required=true
|
||||||
|
{onchange}
|
||||||
|
{checked}
|
||||||
|
/>
|
||||||
|
<label for={id}>{ &t.user.name }</label>
|
||||||
|
</div>
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}) }
|
||||||
|
</fieldset>
|
||||||
|
if i < self.min - 1 {
|
||||||
|
<br />
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
}) }
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<button type="submit">{ "Submit" }</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
use yew::prelude::*;
|
||||||
|
use yew_agent::{Bridge, Bridged};
|
||||||
|
|
||||||
|
use crate::agents;
|
||||||
|
use crate::routes::home::teacher_registration;
|
||||||
|
|
||||||
|
pub enum Msg {
|
||||||
|
Registered,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Properties, PartialEq)]
|
||||||
|
pub struct TeacherHomeProps {
|
||||||
|
pub token: String,
|
||||||
|
pub registered: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TeacherHome {
|
||||||
|
registered: bool,
|
||||||
|
_teacher_registered_producer: Box<dyn Bridge<agents::teacher_registered::EventBus>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for TeacherHome {
|
||||||
|
type Message = Msg;
|
||||||
|
type Properties = TeacherHomeProps;
|
||||||
|
|
||||||
|
fn create(ctx: &Context<Self>) -> Self {
|
||||||
|
Self {
|
||||||
|
registered: ctx.props().registered,
|
||||||
|
_teacher_registered_producer: agents::teacher_registered::EventBus::bridge(
|
||||||
|
ctx.link().callback(|_| Msg::Registered),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
|
||||||
|
match msg {
|
||||||
|
Msg::Registered => {
|
||||||
|
self.registered = true;
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||||
|
html! {
|
||||||
|
if self.registered {
|
||||||
|
<p>{ "Alles in Ordnung." }</p>
|
||||||
|
} else {
|
||||||
|
<teacher_registration::TeacherRegistration token={ctx.props().token.to_owned()} />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,106 @@
|
||||||
|
use graphql_client::reqwest::post_graphql;
|
||||||
|
use web_sys::HtmlInputElement;
|
||||||
|
use yew::prelude::*;
|
||||||
|
use yew_agent::{Dispatched, Dispatcher};
|
||||||
|
|
||||||
|
use crate::agents;
|
||||||
|
use crate::components;
|
||||||
|
use crate::graphql;
|
||||||
|
|
||||||
|
pub enum Msg {
|
||||||
|
Submit,
|
||||||
|
Registration(Option<Vec<String>>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Properties, PartialEq)]
|
||||||
|
pub struct TeacherRegistrationProps {
|
||||||
|
pub token: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TeacherRegistration {
|
||||||
|
max_students: NodeRef,
|
||||||
|
errors: Option<Vec<String>>,
|
||||||
|
teacher_registered_event_bus: Dispatcher<agents::teacher_registered::EventBus>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for TeacherRegistration {
|
||||||
|
type Message = Msg;
|
||||||
|
type Properties = TeacherRegistrationProps;
|
||||||
|
|
||||||
|
fn create(_ctx: &Context<Self>) -> Self {
|
||||||
|
Self {
|
||||||
|
max_students: NodeRef::default(),
|
||||||
|
errors: None,
|
||||||
|
teacher_registered_event_bus: agents::teacher_registered::EventBus::dispatcher(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
|
||||||
|
match msg {
|
||||||
|
Msg::Submit => {
|
||||||
|
if let Some(max_students) = self
|
||||||
|
.max_students
|
||||||
|
.cast::<HtmlInputElement>()
|
||||||
|
.map(|x| x.value_as_number() as usize)
|
||||||
|
{
|
||||||
|
let client = graphql::client(Some(&ctx.props().token)).unwrap();
|
||||||
|
ctx.link().send_future(async move {
|
||||||
|
let response = post_graphql::<
|
||||||
|
graphql::mutations::register_teacher::RegisterTeacher,
|
||||||
|
_,
|
||||||
|
>(
|
||||||
|
&client,
|
||||||
|
graphql::URL.as_str(),
|
||||||
|
graphql::mutations::register_teacher::register_teacher::Variables {
|
||||||
|
max_students: max_students as i64,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
log::debug!("{:?}", response.data.unwrap().register_teacher);
|
||||||
|
|
||||||
|
Msg::Registration(components::graphql_errors::convert(response.errors))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
Msg::Registration(errors) => {
|
||||||
|
self.errors = errors;
|
||||||
|
if self.errors.is_none() {
|
||||||
|
self.teacher_registered_event_bus
|
||||||
|
.send(agents::teacher_registered::Request::Registered);
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||||
|
let onsubmit = ctx.link().callback(|e: FocusEvent| {
|
||||||
|
e.prevent_default();
|
||||||
|
|
||||||
|
Msg::Submit
|
||||||
|
});
|
||||||
|
|
||||||
|
html! {
|
||||||
|
<>
|
||||||
|
<form {onsubmit}>
|
||||||
|
<label for="max_students">
|
||||||
|
{ "Maximale Anzahl von Schülern:" }
|
||||||
|
<br />
|
||||||
|
<input ref={self.max_students.clone()} type="number" id="max_students" name="max_students" min=1 />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<br /><br />
|
||||||
|
|
||||||
|
<button type="submit">{ "Submit" }</button>
|
||||||
|
|
||||||
|
<components::graphql_errors::GraphQLErrors errors={self.errors.clone()} />
|
||||||
|
</form>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,9 @@ use graphql_client::reqwest::post_graphql;
|
||||||
use web_sys::HtmlInputElement;
|
use web_sys::HtmlInputElement;
|
||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
use yew_router::prelude::*;
|
use yew_router::prelude::*;
|
||||||
|
use yew_side_effect::title::Title;
|
||||||
|
|
||||||
|
use crate::components;
|
||||||
use crate::cookie_names;
|
use crate::cookie_names;
|
||||||
use crate::graphql;
|
use crate::graphql;
|
||||||
use crate::routes;
|
use crate::routes;
|
||||||
|
@ -40,7 +42,7 @@ impl Component for Login {
|
||||||
if !username.is_empty() && !password.is_empty() {
|
if !username.is_empty() && !password.is_empty() {
|
||||||
ctx.link().send_future(async {
|
ctx.link().send_future(async {
|
||||||
let response = post_graphql::<graphql::mutations::login::Login, _>(
|
let response = post_graphql::<graphql::mutations::login::Login, _>(
|
||||||
&reqwest::Client::new(),
|
&graphql::client(None).unwrap(),
|
||||||
graphql::URL.as_str(),
|
graphql::URL.as_str(),
|
||||||
graphql::mutations::login::login::Variables { username, password },
|
graphql::mutations::login::login::Variables { username, password },
|
||||||
)
|
)
|
||||||
|
@ -57,11 +59,7 @@ impl Component for Login {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Msg::Login(
|
Msg::Login(components::graphql_errors::convert(response.errors))
|
||||||
response
|
|
||||||
.errors
|
|
||||||
.map(|x| x.iter().map(|e| e.message.to_owned()).collect()),
|
|
||||||
)
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -81,9 +79,7 @@ impl Component for Login {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view(&self, ctx: &Context<Self>) -> Html {
|
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||||
let link = ctx.link();
|
let onsubmit = ctx.link().callback(|e: FocusEvent| {
|
||||||
|
|
||||||
let onsubmit = link.callback(|e: FocusEvent| {
|
|
||||||
e.prevent_default();
|
e.prevent_default();
|
||||||
|
|
||||||
Msg::Submit
|
Msg::Submit
|
||||||
|
@ -91,11 +87,12 @@ impl Component for Login {
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
<>
|
<>
|
||||||
|
<Title value="Login" />
|
||||||
<form {onsubmit}>
|
<form {onsubmit}>
|
||||||
<label for="username">
|
<label for="username">
|
||||||
{ "Benutzername:" }
|
{ "Benutzername:" }
|
||||||
<br />
|
<br />
|
||||||
<input ref={self.username.clone()} type="text" id="username" />
|
<input ref={self.username.clone()} type="text" id="username" name="username" />
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
|
@ -103,19 +100,15 @@ impl Component for Login {
|
||||||
<label for="password">
|
<label for="password">
|
||||||
{ "Kennwort:" }
|
{ "Kennwort:" }
|
||||||
<br />
|
<br />
|
||||||
<input ref={self.password.clone()} type="password" id="password" />
|
<input ref={self.password.clone()} type="password" id="password" name="password" />
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<br /><br />
|
<br /><br />
|
||||||
|
|
||||||
<button type="submit">{ "Login" }</button>
|
<button type="submit">{ "Login" }</button>
|
||||||
</form>
|
|
||||||
|
|
||||||
if let Some(errors) = &self.errors {
|
<components::graphql_errors::GraphQLErrors errors={self.errors.clone()} />
|
||||||
<div>
|
</form>
|
||||||
{ for errors.iter().map(|e| html! { <p style="color: red;">{ e }</p> }) }
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</>
|
</>
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,7 +40,7 @@ pub fn switch(routes: &Route) -> Html {
|
||||||
html! {
|
html! {
|
||||||
<layouts::logged_in::LoggedIn {logged_in}>
|
<layouts::logged_in::LoggedIn {logged_in}>
|
||||||
<layouts::main::Main {logged_in}>
|
<layouts::main::Main {logged_in}>
|
||||||
<home::Home token={token.unwrap()} />
|
<home::Home {token} />
|
||||||
</layouts::main::Main>
|
</layouts::main::Main>
|
||||||
</layouts::logged_in::LoggedIn>
|
</layouts::logged_in::LoggedIn>
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,22 @@
|
||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
|
use yew_side_effect::title::Title;
|
||||||
|
|
||||||
#[function_component(NotFound)]
|
pub struct NotFound;
|
||||||
pub fn not_found() -> Html {
|
|
||||||
html! {
|
impl Component for NotFound {
|
||||||
<h1>{ "404" }</h1>
|
type Message = ();
|
||||||
|
type Properties = ();
|
||||||
|
|
||||||
|
fn create(_ctx: &Context<Self>) -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self, _ctx: &Context<Self>) -> Html {
|
||||||
|
html! {
|
||||||
|
<>
|
||||||
|
<Title value="404 Not found" />
|
||||||
|
<h1>{ "404" }</h1>
|
||||||
|
</>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
.DS_Store
|
|
||||||
node_modules
|
|
||||||
/build
|
|
||||||
/.svelte-kit
|
|
||||||
/package
|
|
||||||
.env
|
|
||||||
.env.*
|
|
||||||
!.env.example
|
|
||||||
yarn-error.log
|
|
||||||
Dockerfile
|
|
||||||
.gitignore
|
|
||||||
.dockerignore
|
|
|
@ -1,24 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
root: true,
|
|
||||||
parser: "@typescript-eslint/parser",
|
|
||||||
extends: [
|
|
||||||
"eslint:recommended",
|
|
||||||
"plugin:@typescript-eslint/recommended",
|
|
||||||
"prettier",
|
|
||||||
],
|
|
||||||
plugins: ["svelte3", "@typescript-eslint"],
|
|
||||||
ignorePatterns: ["*.cjs"],
|
|
||||||
overrides: [{ files: ["*.svelte"], processor: "svelte3/svelte3" }],
|
|
||||||
settings: {
|
|
||||||
"svelte3/typescript": () => require("typescript"),
|
|
||||||
},
|
|
||||||
parserOptions: {
|
|
||||||
sourceType: "module",
|
|
||||||
ecmaVersion: 2020,
|
|
||||||
},
|
|
||||||
env: {
|
|
||||||
browser: true,
|
|
||||||
es2017: true,
|
|
||||||
node: true,
|
|
||||||
},
|
|
||||||
};
|
|
|
@ -1,9 +0,0 @@
|
||||||
.DS_Store
|
|
||||||
node_modules
|
|
||||||
/build
|
|
||||||
/.svelte-kit
|
|
||||||
/package
|
|
||||||
.env
|
|
||||||
.env.*
|
|
||||||
!.env.example
|
|
||||||
yarn-error.log
|
|
|
@ -1 +0,0 @@
|
||||||
engine-strict=true
|
|
|
@ -1 +0,0 @@
|
||||||
{}
|
|
|
@ -1,22 +0,0 @@
|
||||||
FROM node:16-alpine as deps
|
|
||||||
WORKDIR /usr/src/frontend
|
|
||||||
COPY ./package.json ./yarn.lock ./
|
|
||||||
RUN yarn install --frozen-lockfile
|
|
||||||
|
|
||||||
FROM node:16-alpine as builder
|
|
||||||
WORKDIR /usr/src/frontend
|
|
||||||
COPY --from=deps /usr/src/frontend/package.json .
|
|
||||||
COPY --from=deps /usr/src/frontend/node_modules ./node_modules
|
|
||||||
COPY svelte.config.js tsconfig.json ./
|
|
||||||
COPY ./static ./static
|
|
||||||
COPY ./src ./src
|
|
||||||
RUN yarn build
|
|
||||||
|
|
||||||
FROM node:16-alpine as runner
|
|
||||||
WORKDIR /usr/src/frontend
|
|
||||||
COPY --from=deps /usr/src/frontend/package.json .
|
|
||||||
COPY --from=deps /usr/src/frontend/node_modules ./node_modules
|
|
||||||
COPY svelte.config.js .
|
|
||||||
COPY --from=builder /usr/src/frontend/.svelte-kit ./.svelte-kit
|
|
||||||
EXPOSE 3000
|
|
||||||
CMD [ "yarn", "preview", "--host" ]
|
|
|
@ -1,40 +0,0 @@
|
||||||
# create-svelte
|
|
||||||
|
|
||||||
Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte).
|
|
||||||
|
|
||||||
## Creating a project
|
|
||||||
|
|
||||||
If you're seeing this, you've probably already done this step. Congrats!
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# create a new project in the current directory
|
|
||||||
npm init svelte@next
|
|
||||||
|
|
||||||
# create a new project in my-app
|
|
||||||
npm init svelte@next my-app
|
|
||||||
```
|
|
||||||
|
|
||||||
> Note: the `@next` is temporary
|
|
||||||
|
|
||||||
## Developing
|
|
||||||
|
|
||||||
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run dev
|
|
||||||
|
|
||||||
# or start the server and open the app in a new browser tab
|
|
||||||
npm run dev -- --open
|
|
||||||
```
|
|
||||||
|
|
||||||
## Building
|
|
||||||
|
|
||||||
To create a production version of your app:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
You can preview the production build with `npm run preview`.
|
|
||||||
|
|
||||||
> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs#adapters) for your target environment.
|
|
|
@ -1,37 +0,0 @@
|
||||||
{
|
|
||||||
"name": "frontend",
|
|
||||||
"version": "0.0.1",
|
|
||||||
"scripts": {
|
|
||||||
"dev": "svelte-kit dev",
|
|
||||||
"build": "svelte-kit build",
|
|
||||||
"package": "svelte-kit package",
|
|
||||||
"preview": "svelte-kit preview",
|
|
||||||
"check": "svelte-check --tsconfig ./tsconfig.json",
|
|
||||||
"check:watch": "svelte-check --tsconfig ./tsconfig.json --watch",
|
|
||||||
"lint": "prettier --ignore-path .gitignore --check --plugin-search-dir=. . && eslint --ignore-path .gitignore .",
|
|
||||||
"format": "prettier --ignore-path .gitignore --write --plugin-search-dir=. ."
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@sveltejs/adapter-auto": "^1.0.0-next.86",
|
|
||||||
"@sveltejs/kit": "next",
|
|
||||||
"@types/cookie": "^0.4.1",
|
|
||||||
"@typescript-eslint/eslint-plugin": "^5.10.1",
|
|
||||||
"@typescript-eslint/parser": "^5.10.1",
|
|
||||||
"@urql/svelte": "^1.3.3",
|
|
||||||
"cookie": "^0.4.2",
|
|
||||||
"eslint": "^7.32.0",
|
|
||||||
"eslint-config-prettier": "^8.3.0",
|
|
||||||
"eslint-plugin-svelte3": "^3.2.1",
|
|
||||||
"graphql": "^16.3.0",
|
|
||||||
"prettier": "^2.4.1",
|
|
||||||
"prettier-plugin-svelte": "^2.4.0",
|
|
||||||
"svelte": "^3.44.0",
|
|
||||||
"svelte-check": "^2.2.6",
|
|
||||||
"svelte-forms": "^2.2.1",
|
|
||||||
"svelte-preprocess": "^4.9.4",
|
|
||||||
"tslib": "^2.3.1",
|
|
||||||
"typescript": "^4.4.3",
|
|
||||||
"typescript-cookie": "^1.0.3"
|
|
||||||
},
|
|
||||||
"type": "module"
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
/// <reference types="@sveltejs/kit" />
|
|
||||||
|
|
||||||
// See https://kit.svelte.dev/docs#typescript
|
|
||||||
// for information about these interfaces
|
|
||||||
declare namespace App {
|
|
||||||
interface Locals {
|
|
||||||
user: {
|
|
||||||
token?: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
|
||||||
interface Platform {}
|
|
||||||
|
|
||||||
interface Session {
|
|
||||||
user: Locals["user"];
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
|
||||||
interface Stuff {}
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta name="description" content="" />
|
|
||||||
<link rel="icon" href="%svelte.assets%/favicon.png" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
%svelte.head%
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div>%svelte.body%</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,31 +0,0 @@
|
||||||
import type { RequestEvent, ResolveOpts } from "@sveltejs/kit";
|
|
||||||
import * as cookie from "cookie";
|
|
||||||
|
|
||||||
import * as cookieNames from "$lib/cookieNames";
|
|
||||||
|
|
||||||
export async function handle(input: {
|
|
||||||
event: RequestEvent;
|
|
||||||
opts?: ResolveOpts;
|
|
||||||
resolve(event: RequestEvent, opts?: ResolveOpts): Promise<Response>;
|
|
||||||
}): Promise<Response> {
|
|
||||||
const header = input.event.request.headers.get("cookie");
|
|
||||||
const cookies = header ? cookie.parse(header) : {};
|
|
||||||
const token: string | undefined = cookies[cookieNames.TOKEN];
|
|
||||||
|
|
||||||
input.event.locals = {
|
|
||||||
...input.event.locals,
|
|
||||||
user: {
|
|
||||||
token,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
return input.resolve(input.event, input.opts);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getSession(event: RequestEvent): Promise<App.Session> {
|
|
||||||
return {
|
|
||||||
user: {
|
|
||||||
token: event.locals.user.token,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
<h3>STUDENT!</h3>
|
|
|
@ -1 +0,0 @@
|
||||||
<h3>TEACHER!</h3>
|
|
|
@ -1,2 +0,0 @@
|
||||||
const BASE = "mentorenwahl_";
|
|
||||||
export const TOKEN = BASE + "token";
|
|
|
@ -1,50 +0,0 @@
|
||||||
export type ID = number;
|
|
||||||
|
|
||||||
export interface Node {
|
|
||||||
id: ID;
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum UserRole {
|
|
||||||
STUDENT = "Student",
|
|
||||||
TEACHER = "Teacher",
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface User extends Node {
|
|
||||||
firstName: string;
|
|
||||||
lastName: string;
|
|
||||||
name: string;
|
|
||||||
username: string;
|
|
||||||
email: string;
|
|
||||||
role: UserRole;
|
|
||||||
admin: boolean;
|
|
||||||
externalId: ID;
|
|
||||||
student?: Student;
|
|
||||||
teacher?: Teacher;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UserExternal {
|
|
||||||
user: User;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Student extends Node, UserExternal {
|
|
||||||
vote?: Vote;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Teacher extends Node, UserExternal {
|
|
||||||
maxStudents: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Vote extends Node {
|
|
||||||
student: Student;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TeacherVote extends Node {
|
|
||||||
teacher: Teacher;
|
|
||||||
priority: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LoginPayload {
|
|
||||||
user: User;
|
|
||||||
token: string;
|
|
||||||
bearer: string;
|
|
||||||
}
|
|
|
@ -1,47 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import { initClient } from "@urql/svelte";
|
|
||||||
import { removeCookie } from "typescript-cookie";
|
|
||||||
|
|
||||||
import { session } from "$app/stores";
|
|
||||||
import { goto } from "$app/navigation";
|
|
||||||
import * as cookieNames from "$lib/cookieNames";
|
|
||||||
|
|
||||||
initClient({
|
|
||||||
url: "/graphql",
|
|
||||||
fetchOptions() {
|
|
||||||
if ($session.user.token) {
|
|
||||||
return {
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${$session.user.token}`,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
function logout(): void {
|
|
||||||
$session.user.token = undefined;
|
|
||||||
removeCookie(cookieNames.TOKEN);
|
|
||||||
goto("/login");
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<nav>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
{#if $session.user.token}
|
|
||||||
<button on:click={logout}>Logout</button>
|
|
||||||
{:else}
|
|
||||||
<button><a href="/login">Login</a></button>
|
|
||||||
{/if}
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<hr />
|
|
||||||
|
|
||||||
<main>
|
|
||||||
<slot />
|
|
||||||
</main>
|
|
|
@ -1,181 +0,0 @@
|
||||||
<script lang="ts" context="module">
|
|
||||||
export function load({ session }: { session: App.Session }) {
|
|
||||||
if (!session.user.token) {
|
|
||||||
return {
|
|
||||||
status: 302,
|
|
||||||
redirect: "/login",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
props: {
|
|
||||||
session,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { operationStore, query, gql, mutation } from "@urql/svelte";
|
|
||||||
// import * as svelteForms from "svelte-forms";
|
|
||||||
// import * as validators from "svelte-forms/validators";
|
|
||||||
|
|
||||||
import type { User } from "$lib/graphql";
|
|
||||||
import { UserRole } from "$lib/graphql";
|
|
||||||
import StudentHome from "$lib/StudentHome.svelte";
|
|
||||||
|
|
||||||
interface Data {
|
|
||||||
me: User;
|
|
||||||
}
|
|
||||||
|
|
||||||
const meStore = operationStore<Data>(gql`
|
|
||||||
query Me {
|
|
||||||
me {
|
|
||||||
firstName
|
|
||||||
role
|
|
||||||
admin
|
|
||||||
student {
|
|
||||||
id
|
|
||||||
vote {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
teacher {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
query(meStore);
|
|
||||||
|
|
||||||
// const teacherRegisterFormMaxStudents = svelteForms.field("maxStudents", 0, [
|
|
||||||
// validators.required(),
|
|
||||||
// validators.min(0),
|
|
||||||
// ]);
|
|
||||||
// const teacheRegisterForm = svelteForms.form(teacherRegisterFormMaxStudents);
|
|
||||||
|
|
||||||
// interface RegisterTeacherData {
|
|
||||||
// registerTeacher: Teacher;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// interface RegisterTeacherVars {
|
|
||||||
// maxStudents: number;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const registerTeacherStore = operationStore<
|
|
||||||
// RegisterTeacherData,
|
|
||||||
// RegisterTeacherVars
|
|
||||||
// >(gql`
|
|
||||||
// mutation RegisterTeacher($maxStudents: Int!) {
|
|
||||||
// registerTeacher(input: { maxStudents: $maxStudents }) {
|
|
||||||
// id
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// `);
|
|
||||||
|
|
||||||
// const registerTeacherMutation = mutation(registerTeacherStore);
|
|
||||||
|
|
||||||
// async function registerTeacher(): Promise<void> {
|
|
||||||
// await registerTeacherMutation({
|
|
||||||
// maxStudents: $teacherRegisterFormMaxStudents.value,
|
|
||||||
// });
|
|
||||||
|
|
||||||
// if (!$registerTeacherStore.error && $registerTeacherStore.data) {
|
|
||||||
// location.reload();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// interface RegisterStudentData {
|
|
||||||
// registerStudent: Student;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const registerStudentStore = operationStore<RegisterStudentData>(gql`
|
|
||||||
// mutation RegisterStudent() {
|
|
||||||
// registerStudent() {
|
|
||||||
// id
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// `);
|
|
||||||
// const registerStudentMutation = mutation(registerStudentStore);
|
|
||||||
|
|
||||||
// async function registerStudent(): Promise<void> {
|
|
||||||
// await registerStudentMutation();
|
|
||||||
|
|
||||||
// if (!$registerStudentStore.error && $registerStudentStore.data) {
|
|
||||||
// location.reload();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#if $meStore.error}
|
|
||||||
<p style="color: red;">{$meStore.error.message}</p>
|
|
||||||
{:else if $meStore.fetching}
|
|
||||||
<p>Laden...</p>
|
|
||||||
{:else}
|
|
||||||
<h1>Hey {$meStore.data.me.firstName}!</h1>
|
|
||||||
<hr />
|
|
||||||
|
|
||||||
{#if $meStore.data.me.role === UserRole.TEACHER}
|
|
||||||
<!-- {#if !$meStore.data.me.teacher}
|
|
||||||
<p>Registriere dich jetzt als Lehrer:</p>
|
|
||||||
<form on:submit|preventDefault={registerTeacher}>
|
|
||||||
<label for="maxStudents">Maximale Schüler:</label>
|
|
||||||
<br />
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
name="maxStudents"
|
|
||||||
min="0"
|
|
||||||
bind:value={$teacherRegisterFormMaxStudents.value}
|
|
||||||
/>
|
|
||||||
<br />
|
|
||||||
<label for="skif">SKIF:</label>
|
|
||||||
<br />
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
name="skif"
|
|
||||||
bind:checked={$teacherRegisterFormSkif.value}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<br /><br />
|
|
||||||
|
|
||||||
<button type="submit" disabled={!$teacheRegisterForm.valid}
|
|
||||||
>Registrieren</button
|
|
||||||
>
|
|
||||||
</form>
|
|
||||||
{#if $registerTeacherStore.error}
|
|
||||||
<p style="color: red;">{$registerTeacherStore.error.message}</p>
|
|
||||||
{:else if $registerTeacherStore.fetching}
|
|
||||||
<p>Laden...</p>
|
|
||||||
{:else if $registerTeacherStore.data}
|
|
||||||
<p>Registrierung erfolgreich!</p>
|
|
||||||
{/if}
|
|
||||||
{/if} -->
|
|
||||||
{:else if $meStore.data.me.role === UserRole.STUDENT}
|
|
||||||
<!-- {#if !$meStore.data.me.student}
|
|
||||||
<p>Registriere dich jetzt als Schüler:</p>
|
|
||||||
<form on:submit|preventDefault={registerStudent}>
|
|
||||||
<label for="skif">SKIF:</label>
|
|
||||||
<br />
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
name="skif"
|
|
||||||
bind:checked={$registerStudentSkif.value}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<br /><br />
|
|
||||||
|
|
||||||
<button type="submit" disabled={!$registerStudentForm.valid}
|
|
||||||
>Registrieren</button
|
|
||||||
>
|
|
||||||
</form>
|
|
||||||
{#if $registerStudentStore.error}
|
|
||||||
<p style="color: red;">{$registerStudentStore.error.message}</p>
|
|
||||||
{:else if $registerStudentStore.fetching}
|
|
||||||
<p>Laden...</p>
|
|
||||||
{:else if $registerStudentStore.data}
|
|
||||||
<p>Registrierung erfolgreich!</p>
|
|
||||||
{/if}
|
|
||||||
{/if} -->
|
|
||||||
<StudentHome />
|
|
||||||
{/if}
|
|
||||||
{/if}
|
|
|
@ -1,74 +0,0 @@
|
||||||
<script lang="ts">
|
|
||||||
import * as svelteForms from "svelte-forms";
|
|
||||||
import * as validators from "svelte-forms/validators";
|
|
||||||
import { setCookie, removeCookie } from "typescript-cookie";
|
|
||||||
import { operationStore, mutation, gql } from "@urql/svelte";
|
|
||||||
|
|
||||||
import { session } from "$app/stores";
|
|
||||||
import { goto } from "$app/navigation";
|
|
||||||
import * as cookieNames from "$lib/cookieNames";
|
|
||||||
import type { LoginPayload } from "$lib/graphql";
|
|
||||||
|
|
||||||
const username = svelteForms.field("username", undefined, [
|
|
||||||
validators.required(),
|
|
||||||
]);
|
|
||||||
const password = svelteForms.field("password", undefined, [
|
|
||||||
validators.required(),
|
|
||||||
]);
|
|
||||||
const loginForm = svelteForms.form(username, password);
|
|
||||||
|
|
||||||
interface Data {
|
|
||||||
login: LoginPayload;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Vars {
|
|
||||||
username: string;
|
|
||||||
password: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const loginStore = operationStore<Data, Vars>(gql`
|
|
||||||
mutation Login($username: String!, $password: String!) {
|
|
||||||
login(username: $username, password: $password) {
|
|
||||||
token
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
const login = mutation(loginStore);
|
|
||||||
|
|
||||||
async function submit(): Promise<void> {
|
|
||||||
await login({
|
|
||||||
username: $username.value as string,
|
|
||||||
password: $password.value as string,
|
|
||||||
});
|
|
||||||
|
|
||||||
if ($loginStore.error) {
|
|
||||||
removeCookie(cookieNames.TOKEN);
|
|
||||||
} else {
|
|
||||||
$session.user.token = $loginStore.data.login.token;
|
|
||||||
setCookie(cookieNames.TOKEN, $session.user.token, {
|
|
||||||
expires: 1,
|
|
||||||
});
|
|
||||||
goto("/");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<form on:submit|preventDefault={submit}>
|
|
||||||
<label for="username">Benutzernane (Moodle):</label>
|
|
||||||
<br />
|
|
||||||
<input type="text" id="username" bind:value={$username.value} />
|
|
||||||
<br />
|
|
||||||
<label for="password">Kennwort:</label>
|
|
||||||
<br />
|
|
||||||
<input type="password" id="password" bind:value={$password.value} />
|
|
||||||
|
|
||||||
<br /><br />
|
|
||||||
|
|
||||||
<button type="submit" disabled={!$loginForm.valid}>Login</button>
|
|
||||||
|
|
||||||
<br />
|
|
||||||
|
|
||||||
{#if $loginStore.error}
|
|
||||||
<p style="color: red;">{$loginStore.error.message}</p>
|
|
||||||
{/if}
|
|
||||||
</form>
|
|
|
@ -1,17 +0,0 @@
|
||||||
import adapter from "@sveltejs/adapter-auto";
|
|
||||||
import preprocess from "svelte-preprocess";
|
|
||||||
|
|
||||||
/** @type {import('@sveltejs/kit').Config} */
|
|
||||||
const config = {
|
|
||||||
// Consult https://github.com/sveltejs/svelte-preprocess
|
|
||||||
// for more information about preprocessors
|
|
||||||
preprocess: preprocess(),
|
|
||||||
kit: {
|
|
||||||
adapter: adapter(),
|
|
||||||
},
|
|
||||||
optimizeDeps: {
|
|
||||||
exclude: ["@urql/svelte"],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default config;
|
|
|
@ -1,31 +0,0 @@
|
||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"moduleResolution": "node",
|
|
||||||
"module": "es2020",
|
|
||||||
"lib": ["es2020", "DOM"],
|
|
||||||
"target": "es2020",
|
|
||||||
/**
|
|
||||||
svelte-preprocess cannot figure out whether you have a value or a type, so tell TypeScript
|
|
||||||
to enforce using \`import type\` instead of \`import\` for Types.
|
|
||||||
*/
|
|
||||||
"importsNotUsedAsValues": "error",
|
|
||||||
"isolatedModules": true,
|
|
||||||
"resolveJsonModule": true,
|
|
||||||
/**
|
|
||||||
To have warnings/errors of the Svelte compiler at the correct position,
|
|
||||||
enable source maps by default.
|
|
||||||
*/
|
|
||||||
"sourceMap": true,
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"forceConsistentCasingInFileNames": true,
|
|
||||||
"baseUrl": ".",
|
|
||||||
"allowJs": true,
|
|
||||||
"checkJs": true,
|
|
||||||
"paths": {
|
|
||||||
"$lib": ["src/lib"],
|
|
||||||
"$lib/*": ["src/lib/*"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.ts", "src/**/*.svelte", "src/lib/cookieNames.ts"]
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue