Rewrite frontend in rust with yew
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
c56d359814
commit
860ae7ed5e
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
require "jwt"
|
require "jwt"
|
||||||
require "json"
|
require "json"
|
||||||
|
require "uuid"
|
||||||
|
|
||||||
module Backend
|
module Backend
|
||||||
module Api
|
module Api
|
||||||
|
@ -31,16 +32,18 @@ module Backend
|
||||||
include JSON::Serializable
|
include JSON::Serializable
|
||||||
|
|
||||||
getter iss : String
|
getter iss : String
|
||||||
|
getter vrs : String
|
||||||
getter iat : Int64
|
getter iat : Int64
|
||||||
getter exp : Int64
|
getter exp : Int64
|
||||||
getter jti : String
|
getter jti : UUID
|
||||||
getter context : Context
|
getter context : Context
|
||||||
|
|
||||||
def initialize(
|
def initialize(
|
||||||
@iss : String,
|
@iss : String,
|
||||||
|
@vrs : String,
|
||||||
@iat : Int64,
|
@iat : Int64,
|
||||||
@exp : Int64,
|
@exp : Int64,
|
||||||
@jti : String,
|
@jti : UUID,
|
||||||
@context : Context
|
@context : Context
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
@ -52,9 +55,10 @@ module Backend
|
||||||
def self.from_hash(token : Hash(String, JSON::Any)) : self
|
def self.from_hash(token : Hash(String, JSON::Any)) : self
|
||||||
self.new(
|
self.new(
|
||||||
iss: token["iss"].as_s,
|
iss: token["iss"].as_s,
|
||||||
|
vrs: token["vrs"].as_s,
|
||||||
iat: token["iat"].as_i64,
|
iat: token["iat"].as_i64,
|
||||||
exp: token["exp"].as_i64,
|
exp: token["exp"].as_i64,
|
||||||
jti: token["jti"].as_s,
|
jti: UUID.new(token["jti"].as_s),
|
||||||
context: Context.from_hash(token["context"].as_h)
|
context: Context.from_hash(token["context"].as_h)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
|
@ -62,6 +62,8 @@ module Backend
|
||||||
rescue
|
rescue
|
||||||
@status = Status::JWTError
|
@status = Status::JWTError
|
||||||
else
|
else
|
||||||
|
pp! payload
|
||||||
|
|
||||||
if @user = Db::User.find(payload.context.user)
|
if @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
|
||||||
|
@ -98,8 +100,10 @@ module Backend
|
||||||
|
|
||||||
# :ditto:
|
# :ditto:
|
||||||
def authenticated! : Bool
|
def authenticated! : Bool
|
||||||
raise "Session expired" if @status.session_expired?
|
# raise "Session expired" if @status.session_expired?
|
||||||
raise "Not authenticated" unless authenticated?
|
raise Errors::SessionExpired.new if @status.session_expired?
|
||||||
|
# raise "Not authenticated" unless authenticated?
|
||||||
|
raise Errors::NotAuthenticated.new unless authenticated?
|
||||||
|
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
@ -112,7 +116,8 @@ module Backend
|
||||||
# :ditto:
|
# :ditto:
|
||||||
def admin! : Bool
|
def admin! : Bool
|
||||||
authenticated!
|
authenticated!
|
||||||
raise "Invalid permissions" unless admin?
|
# raise "Invalid permissions" unless admin?
|
||||||
|
raise Errors::InvalidPermissions.new unless admin?
|
||||||
|
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
@ -142,7 +147,8 @@ module Backend
|
||||||
# :ditto:
|
# :ditto:
|
||||||
def role!(roles : Array(Schema::UserRole), external_check = true) : Bool
|
def role!(roles : Array(Schema::UserRole), external_check = true) : Bool
|
||||||
authenticated!
|
authenticated!
|
||||||
raise "Invalid permissions" unless role?(roles, external_check)
|
# raise "Invalid permissions" unless role?(roles, external_check)
|
||||||
|
raise Errors::InvalidPermissions.new unless role?(roles, external_check)
|
||||||
|
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
@ -169,32 +175,31 @@ module Backend
|
||||||
|
|
||||||
# Custom error handler
|
# Custom error handler
|
||||||
def handle_exception(ex : Exception) : String?
|
def handle_exception(ex : Exception) : String?
|
||||||
pp! ex, ex.message
|
# pp! ex, ex.message, ex.class, typeof(ex), ex.is_a? Errors::PublicError
|
||||||
|
# # ex.message
|
||||||
# ex.message
|
|
||||||
|
|
||||||
case ex
|
case ex
|
||||||
when Errors::Error
|
when Errors::PublicError
|
||||||
|
ex.api_message
|
||||||
|
when Errors::PrivateError
|
||||||
{% if !flag?(:release) %}
|
{% if !flag?(:release) %}
|
||||||
if @development
|
if @development
|
||||||
ex.message
|
ex.api_message
|
||||||
else
|
else
|
||||||
nil
|
Errors::UNKNOWN_PRIVATE_ERROR
|
||||||
end
|
end
|
||||||
{% else %}
|
{% else %}
|
||||||
nil
|
Errors::UNKNOWN_PRIVATE_ERROR
|
||||||
{% end %}
|
{% end %}
|
||||||
when Errors::PublicError
|
|
||||||
ex.message
|
|
||||||
else
|
else
|
||||||
{% if !flag?(:release) %}
|
{% if !flag?(:release) %}
|
||||||
if @development
|
if @development
|
||||||
ex.message
|
ex.message || Errors::UNKNOWN_PRIVATE_ERROR
|
||||||
else
|
else
|
||||||
nil
|
Errors::UNKNOWN_PRIVATE_ERROR
|
||||||
end
|
end
|
||||||
{% else %}
|
{% else %}
|
||||||
nil
|
Errors::UNKNOWN_PRIVATE_ERROR
|
||||||
{% end %}
|
{% end %}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
module Backend::Api::Errors
|
module Backend::Api::Errors
|
||||||
|
UNKNOWN_PRIVATE_ERROR = "UNKNOWN_ERROR"
|
||||||
|
UNKNOWN_PUBLIC_ERROR = "UNKNOWN_PUBLIC_ERROR"
|
||||||
|
|
||||||
abstract class Error < Exception
|
abstract class Error < Exception
|
||||||
|
abstract def api_message : String
|
||||||
end
|
end
|
||||||
|
|
||||||
abstract class PrivateError < Error
|
abstract class PrivateError < Error
|
||||||
|
@ -8,6 +12,57 @@ module Backend::Api::Errors
|
||||||
abstract class PublicError < Error
|
abstract class PublicError < Error
|
||||||
end
|
end
|
||||||
|
|
||||||
class AuthenticationError < PublicError
|
class SessionExpired < PublicError
|
||||||
|
def api_message : String
|
||||||
|
"Session expired"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class NotAuthenticated < PublicError
|
||||||
|
def api_message : String
|
||||||
|
"Not authenticated"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Authentication < PublicError
|
||||||
|
def api_message : String
|
||||||
|
"Invalid username or password"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class InvalidPermissions < PublicError
|
||||||
|
def api_message : String
|
||||||
|
"Invalid permissions"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class LdapUserDoesNotExist < PublicError
|
||||||
|
def api_message : String
|
||||||
|
"LDAP user does not exist"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class DuplicateTeachers < PublicError
|
||||||
|
def api_message : String
|
||||||
|
"Duplicate teachers"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class NotEnoughTeachers < PublicError
|
||||||
|
def api_message : String
|
||||||
|
"Not enough teachers"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class TeachersNotRegistered < PublicError
|
||||||
|
def api_message : String
|
||||||
|
"Teachers not registered"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class TeachersNotFound < PublicError
|
||||||
|
def api_message : String
|
||||||
|
"Teachers not found"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
|
|
||||||
require "ldap"
|
require "ldap"
|
||||||
require "uuid"
|
require "uuid"
|
||||||
|
require "uuid/json"
|
||||||
|
require "random/secure"
|
||||||
|
|
||||||
module Backend
|
module Backend
|
||||||
module Api
|
module Api
|
||||||
|
@ -25,19 +27,20 @@ module Backend
|
||||||
@[GraphQL::Field]
|
@[GraphQL::Field]
|
||||||
# Logs in as *username* with credential *password*
|
# Logs in as *username* with credential *password*
|
||||||
def login(username : String, password : String) : LoginPayload
|
def login(username : String, password : String) : LoginPayload
|
||||||
raise Errors::AuthenticationError.new if username.empty? || password.empty?
|
raise Errors::Authentication.new if username.empty? || password.empty?
|
||||||
|
|
||||||
user = Db::User.query.find { var(:username) == username }
|
user = Db::User.query.find { var(:username) == username }
|
||||||
raise Errors::AuthenticationError.new unless user && Ldap.authenticate?(Ldap::DN.uid(username), password)
|
raise Errors::Authentication.new unless user && Ldap.authenticate?(Ldap::DN.uid(username), password)
|
||||||
|
|
||||||
jti = UUID.random
|
jti = UUID.random(Random::Secure)
|
||||||
LoginPayload.new(
|
LoginPayload.new(
|
||||||
user: User.new(user),
|
user: User.new(user),
|
||||||
token: Auth::Token.new(
|
token: Auth::Token.new(
|
||||||
iss: "mentorenwahl",
|
iss: "Mentorenwahl",
|
||||||
|
vrs: Backend::VERSION,
|
||||||
iat: Time.utc.to_unix,
|
iat: Time.utc.to_unix,
|
||||||
exp: (Time.utc + Backend.config.api.jwt_expiration.minutes).to_unix,
|
exp: (Time.utc + Backend.config.api.jwt_expiration.minutes).to_unix,
|
||||||
jti: jti.hexstring,
|
jti: jti,
|
||||||
context: Auth::Context.new(user.id.not_nil!)
|
context: Auth::Context.new(user.id.not_nil!)
|
||||||
).encode
|
).encode
|
||||||
)
|
)
|
||||||
|
@ -48,11 +51,11 @@ module Backend
|
||||||
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 "LDAP user does not exist" if check_ldap && begin
|
raise Errors::LdapUserDoesNotExist.new if check_ldap && begin
|
||||||
!Ldap::User.from_username(input.username)
|
!Ldap::User.from_username(input.username)
|
||||||
rescue LDAP::Client::AuthError
|
rescue LDAP::Client::AuthError
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
user = Db::User.create!(username: input.username, role: input.role.to_db, admin: input.admin)
|
user = Db::User.create!(username: input.username, role: input.role.to_db, admin: input.admin)
|
||||||
Worker::Jobs::CacheLdapUserJob.new(user.id.not_nil!.to_i).enqueue
|
Worker::Jobs::CacheLdapUserJob.new(user.id.not_nil!.to_i).enqueue
|
||||||
|
|
||||||
|
@ -148,16 +151,16 @@ module Backend
|
||||||
def create_vote(context : Context, input : VoteCreateInput) : Vote
|
def create_vote(context : Context, input : VoteCreateInput) : Vote
|
||||||
context.student!
|
context.student!
|
||||||
|
|
||||||
raise "Duplicate teachers" if input.teacher_ids.uniq.size != input.teacher_ids.size
|
raise Errors::DuplicateTeachers.new if input.teacher_ids.uniq.size != input.teacher_ids.size
|
||||||
raise "Not enough teachers" if input.teacher_ids.size < Backend.config.minimum_teacher_selection_count
|
raise Errors::NotEnoughTeachers.new if input.teacher_ids.size < Backend.config.minimum_teacher_selection_count
|
||||||
teacher_role_count = Db::User.query.where(role: Db::UserRole::Teacher).count
|
teacher_role_count = Db::User.query.where(role: Db::UserRole::Teacher).count
|
||||||
raise "Teachers not registered" if teacher_role_count != Db::Teacher.query.count || teacher_role_count.zero?
|
raise Errors::TeachersNotRegistered.new if teacher_role_count != Db::Teacher.query.count || teacher_role_count.zero?
|
||||||
|
|
||||||
input.teacher_ids.each do |id|
|
input.teacher_ids.each do |id|
|
||||||
teacher = Db::Teacher.find(id)
|
teacher = Db::Teacher.find(id)
|
||||||
|
|
||||||
if teacher.nil?
|
if teacher.nil?
|
||||||
raise "Teachers not found"
|
raise Errors::TeachersNotFound.new
|
||||||
# elsif teacher.user.skif != context.user.not_nil!.skif
|
# elsif teacher.user.skif != context.user.not_nil!.skif
|
||||||
# if teacher.user.skif
|
# if teacher.user.skif
|
||||||
# raise "Teacher is SKIF, student is not"
|
# raise "Teacher is SKIF, student is not"
|
||||||
|
|
|
@ -125,6 +125,14 @@ module Backend
|
||||||
students > 0 && votes >= students
|
students > 0 && votes >= students
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@[GraphQL::Field]
|
||||||
|
# Students can vote
|
||||||
|
def students_can_vote : Bool
|
||||||
|
teacher_role_count = Db::User.query.where(role: Db::UserRole::Teacher).count
|
||||||
|
|
||||||
|
teacher_role_count > 0 && teacher_role_count == Db::Teacher.query.count
|
||||||
|
end
|
||||||
|
|
||||||
@[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
|
||||||
|
|
|
@ -56,7 +56,7 @@ module Backend
|
||||||
if ex
|
if ex
|
||||||
raise ex
|
raise ex
|
||||||
else
|
else
|
||||||
raise Exception.new unless Backend.config.db.allow_old_schema
|
raise "Database schema is not up to date" unless Backend.config.db.allow_old_schema
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -33,7 +33,7 @@ module Backend
|
||||||
|
|
||||||
@[ARTA::Get("")]
|
@[ARTA::Get("")]
|
||||||
def playground : ATH::Response
|
def playground : ATH::Response
|
||||||
ATH::StreamedResponse.new(headers: HTTP::Headers{"Content-Type" => "text/html"}) do |io|
|
ATH::StreamedResponse.new(headers: HTTP::Headers{"Content-Type" => "text/html", "Access-Control-Allow-Origin" => "*"}) do |io|
|
||||||
IO.copy(Public.get("index.html"), io)
|
IO.copy(Public.get("index.html"), io)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -20,7 +20,7 @@ events {
|
||||||
http {
|
http {
|
||||||
server {
|
server {
|
||||||
location / {
|
location / {
|
||||||
proxy_pass http://frontend:3000/;
|
proxy_pass http://frontend/;
|
||||||
}
|
}
|
||||||
|
|
||||||
location /graphql {
|
location /graphql {
|
||||||
|
|
|
@ -104,8 +104,6 @@ services:
|
||||||
- default
|
- default
|
||||||
depends_on:
|
depends_on:
|
||||||
- backend
|
- backend
|
||||||
environment:
|
|
||||||
NODE_ENV: production
|
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
db:
|
db:
|
||||||
|
|
2
frontend/.cargo/config
Normal file
2
frontend/.cargo/config
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
[build]
|
||||||
|
target = "wasm32-unknown-unknown"
|
|
@ -1,14 +1,20 @@
|
||||||
.DS_Store
|
# Generated by Cargo
|
||||||
node_modules
|
# will have compiled files and executables
|
||||||
yarn-error.log
|
debug/
|
||||||
/build
|
target/
|
||||||
/.svelte-kit
|
|
||||||
/package
|
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||||
.env
|
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
||||||
.env.*
|
Cargo.lock
|
||||||
!.env.example
|
|
||||||
|
# These are backup files generated by rustfmt
|
||||||
|
**/*.rs.bk
|
||||||
|
|
||||||
|
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||||
|
*.pdb
|
||||||
|
|
||||||
|
dist/
|
||||||
Dockerfile
|
Dockerfile
|
||||||
.dockerignore
|
|
||||||
.gitignore
|
.gitignore
|
||||||
README.md
|
.dockerignore
|
||||||
.nvmrc
|
vendor/
|
||||||
|
|
26
frontend/.gitignore
vendored
26
frontend/.gitignore
vendored
|
@ -1,9 +1,17 @@
|
||||||
.DS_Store
|
# Generated by Cargo
|
||||||
node_modules
|
# will have compiled files and executables
|
||||||
/build
|
debug/
|
||||||
/.svelte-kit
|
target/
|
||||||
/package
|
|
||||||
.env
|
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||||
.env.*
|
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
||||||
!.env.example
|
Cargo.lock
|
||||||
yarn-error.log
|
|
||||||
|
# These are backup files generated by rustfmt
|
||||||
|
**/*.rs.bk
|
||||||
|
|
||||||
|
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||||
|
*.pdb
|
||||||
|
|
||||||
|
dist/
|
||||||
|
vendor/
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
v16.17.1
|
|
19
frontend/Cargo.toml
Normal file
19
frontend/Cargo.toml
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
[package]
|
||||||
|
name = "frontend"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
yew = "0.19.3"
|
||||||
|
wasm-logger = "0.2.0"
|
||||||
|
log = "0.4.6"
|
||||||
|
yew-router = "0.16.0"
|
||||||
|
wee_alloc = "0.4.5"
|
||||||
|
graphql_client = { version = "0.11.0", features = ["reqwest"] }
|
||||||
|
reqwest = "0.11.12"
|
||||||
|
wasm-bindgen-futures = "0.4.33"
|
||||||
|
serde = "1.0.147"
|
||||||
|
web-sys = { version = "0.3.60", features = ["Window", "Location"] }
|
||||||
|
wasm-cookies = "0.1.0"
|
||||||
|
lazy_static = "1.4.0"
|
||||||
|
const_format = "0.2.30"
|
|
@ -1,8 +1,26 @@
|
||||||
FROM node:16-alpine
|
FROM lukemathwalker/cargo-chef:latest-rust-1.65.0 as chef
|
||||||
WORKDIR /usr/src/frontend
|
WORKDIR /usr/src/frontend
|
||||||
COPY ./package.json ./yarn.lock ./
|
|
||||||
RUN yarn install --frozen-lockfile
|
FROM chef as planner
|
||||||
COPY . .
|
WORKDIR /usr/src/frontend
|
||||||
RUN yarn build
|
RUN mkdir src && touch src/main.rs
|
||||||
EXPOSE 3000
|
COPY ./Cargo.toml .
|
||||||
CMD ["yarn", "preview", "--host"]
|
RUN cargo chef prepare --recipe-path recipe.json
|
||||||
|
|
||||||
|
FROM chef as builder
|
||||||
|
WORKDIR /usr/local/bin
|
||||||
|
ARG TRUNK_VERSION="v0.16.0"
|
||||||
|
RUN wget -qO- https://github.com/thedodd/trunk/releases/download/${TRUNK_VERSION}/trunk-x86_64-unknown-linux-gnu.tar.gz | tar -xzf-
|
||||||
|
WORKDIR /usr/src/frontend
|
||||||
|
RUN rustup target add wasm32-unknown-unknown
|
||||||
|
COPY ./.cargo ./.cargo
|
||||||
|
COPY --from=planner /usr/src/frontend/recipe.json .
|
||||||
|
RUN cargo chef cook --release --recipe-path recipe.json
|
||||||
|
COPY ./index.html .
|
||||||
|
COPY ./graphql ./graphql
|
||||||
|
COPY ./src ./src
|
||||||
|
RUN trunk build --release
|
||||||
|
|
||||||
|
FROM nginx:alpine as runner
|
||||||
|
COPY ./nginx.conf /etc/nginx/nginx.conf
|
||||||
|
COPY --from=builder /usr/src/frontend/dist /var/www/html
|
||||||
|
|
3
frontend/graphql/queries/ok.graphql
Normal file
3
frontend/graphql/queries/ok.graphql
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
query Ok {
|
||||||
|
ok
|
||||||
|
}
|
92
frontend/graphql/schema.graphql
Normal file
92
frontend/graphql/schema.graphql
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
type Config {
|
||||||
|
minimumTeacherSelectionCount: Int!
|
||||||
|
}
|
||||||
|
|
||||||
|
type Query {
|
||||||
|
admins: [User!]!
|
||||||
|
allStudentsVoted: Boolean!
|
||||||
|
config: Config!
|
||||||
|
me: User!
|
||||||
|
ok: Boolean!
|
||||||
|
student(id: Int!): Student!
|
||||||
|
students: [Student!]!
|
||||||
|
studentsCanVote: Boolean!
|
||||||
|
teacher(id: Int!): Teacher!
|
||||||
|
teacherVote(id: Int!): TeacherVote!
|
||||||
|
teacherVotes: [TeacherVote!]!
|
||||||
|
teachers: [Teacher!]!
|
||||||
|
user(id: Int!): User!
|
||||||
|
userByUsername(username: String!): User!
|
||||||
|
users: [User!]!
|
||||||
|
vote(id: Int!): Vote!
|
||||||
|
votes: [Vote!]!
|
||||||
|
}
|
||||||
|
|
||||||
|
type Student {
|
||||||
|
id: Int!
|
||||||
|
user: User!
|
||||||
|
vote: Vote
|
||||||
|
}
|
||||||
|
|
||||||
|
type Teacher {
|
||||||
|
id: Int!
|
||||||
|
maxStudents: Int!
|
||||||
|
teacherVotes: [TeacherVote!]!
|
||||||
|
user: User!
|
||||||
|
}
|
||||||
|
|
||||||
|
type TeacherVote {
|
||||||
|
id: Int!
|
||||||
|
priority: Int!
|
||||||
|
teacher: Teacher!
|
||||||
|
vote: Vote!
|
||||||
|
}
|
||||||
|
|
||||||
|
type User {
|
||||||
|
admin: Boolean!
|
||||||
|
email: String!
|
||||||
|
externalId: Int!
|
||||||
|
firstName: String!
|
||||||
|
id: Int!
|
||||||
|
lastName: String!
|
||||||
|
name(formal: Boolean! = true): String!
|
||||||
|
role: UserRole!
|
||||||
|
student: Student
|
||||||
|
teacher: Teacher
|
||||||
|
username: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
enum UserRole {
|
||||||
|
Student
|
||||||
|
Teacher
|
||||||
|
}
|
||||||
|
|
||||||
|
type Vote {
|
||||||
|
id: Int!
|
||||||
|
student: Student!
|
||||||
|
teacherVotes: [TeacherVote!]!
|
||||||
|
}
|
||||||
|
|
||||||
|
type LoginPayload {
|
||||||
|
bearer: String!
|
||||||
|
token: String!
|
||||||
|
user: User!
|
||||||
|
}
|
||||||
|
|
||||||
|
type Mutation {
|
||||||
|
assignStudents: Boolean!
|
||||||
|
createUser(checkLdap: Boolean! = true, input: UserCreateInput!): User!
|
||||||
|
createVote(input: VoteCreateInput!): Vote!
|
||||||
|
deleteUser(id: Int!): Int!
|
||||||
|
login(password: String!, username: String!): LoginPayload!
|
||||||
|
}
|
||||||
|
|
||||||
|
input UserCreateInput {
|
||||||
|
username: String!
|
||||||
|
role: UserRole!
|
||||||
|
admin: Boolean! = false
|
||||||
|
}
|
||||||
|
|
||||||
|
input VoteCreateInput {
|
||||||
|
teacherIds: [Int!]!
|
||||||
|
}
|
13
frontend/index.html
Normal file
13
frontend/index.html
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
37
frontend/nginx.conf
Normal file
37
frontend/nginx.conf
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
user nginx;
|
||||||
|
worker_processes auto;
|
||||||
|
|
||||||
|
error_log /var/log/nginx/error.log notice;
|
||||||
|
pid /var/run/nginx.pid;
|
||||||
|
|
||||||
|
events {
|
||||||
|
worker_connections 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
include /etc/nginx/mime.types;
|
||||||
|
default_type application/octet-stream;
|
||||||
|
|
||||||
|
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||||
|
'$status $body_bytes_sent "$http_referer" '
|
||||||
|
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||||
|
|
||||||
|
access_log /var/log/nginx/access.log main;
|
||||||
|
|
||||||
|
sendfile on;
|
||||||
|
tcp_nopush on;
|
||||||
|
|
||||||
|
keepalive_timeout 65;
|
||||||
|
|
||||||
|
#gzip on;
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
|
||||||
|
root /var/www/html;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri /index.html;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
4
frontend/src/cookie_names.rs
Normal file
4
frontend/src/cookie_names.rs
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
use const_format::concatcp;
|
||||||
|
|
||||||
|
pub const BASE: &str = "mentorenwahl_";
|
||||||
|
pub const TOKEN: &str = concatcp!(BASE, "token");
|
13
frontend/src/graphql/mod.rs
Normal file
13
frontend/src/graphql/mod.rs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
pub mod queries;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
pub static ref URL: String =
|
||||||
|
Path::new(&web_sys::window().unwrap().location().origin().unwrap())
|
||||||
|
.join("graphql")
|
||||||
|
.to_str()
|
||||||
|
.unwrap()
|
||||||
|
.to_string();
|
||||||
|
}
|
9
frontend/src/graphql/queries.rs
Normal file
9
frontend/src/graphql/queries.rs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
use graphql_client::GraphQLQuery;
|
||||||
|
|
||||||
|
#[derive(GraphQLQuery)]
|
||||||
|
#[graphql(
|
||||||
|
schema_path = "graphql/schema.graphql",
|
||||||
|
query_path = "graphql/queries/ok.graphql",
|
||||||
|
response_derives = "Debug"
|
||||||
|
)]
|
||||||
|
pub struct Ok;
|
56
frontend/src/layouts/main.rs
Normal file
56
frontend/src/layouts/main.rs
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
use graphql_client::reqwest::post_graphql;
|
||||||
|
use wasm_bindgen_futures;
|
||||||
|
use yew::prelude::*;
|
||||||
|
use yew_router::prelude::*;
|
||||||
|
|
||||||
|
use crate::cookie_names;
|
||||||
|
use crate::graphql;
|
||||||
|
use crate::routes;
|
||||||
|
|
||||||
|
#[derive(Properties, PartialEq)]
|
||||||
|
pub struct MainProps {
|
||||||
|
#[prop_or_default]
|
||||||
|
pub children: Children,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[function_component(Main)]
|
||||||
|
pub fn main(props: &MainProps) -> Html {
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
wasm_bindgen_futures::spawn_local(async move {
|
||||||
|
let response = post_graphql::<graphql::queries::Ok, _>(
|
||||||
|
&client,
|
||||||
|
graphql::URL.as_str(),
|
||||||
|
graphql::queries::ok::Variables {},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
log::debug!("{:?}", response);
|
||||||
|
log::debug!("{:?}", wasm_cookies::get(cookie_names::TOKEN));
|
||||||
|
});
|
||||||
|
|
||||||
|
let history = use_history().unwrap();
|
||||||
|
let loginout_onclick = Callback::once(move |_| history.push(routes::Route::Login));
|
||||||
|
|
||||||
|
html! {
|
||||||
|
<>
|
||||||
|
<nav>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<Link<routes::Route> to={routes::Route::Home}>
|
||||||
|
<button>{ "Home" }</button>
|
||||||
|
</Link<routes::Route>>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<button onclick={loginout_onclick}>{ "Login/Logout" }</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<main>
|
||||||
|
{ for props.children.iter() }
|
||||||
|
</main>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
}
|
1
frontend/src/layouts/mod.rs
Normal file
1
frontend/src/layouts/mod.rs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
pub mod main;
|
5
frontend/src/lib.rs
Normal file
5
frontend/src/lib.rs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
pub mod components;
|
||||||
|
pub mod cookie_names;
|
||||||
|
pub mod graphql;
|
||||||
|
pub mod layouts;
|
||||||
|
pub mod routes;
|
22
frontend/src/main.rs
Normal file
22
frontend/src/main.rs
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
use yew::prelude::*;
|
||||||
|
use yew_router::prelude::*;
|
||||||
|
|
||||||
|
#[global_allocator]
|
||||||
|
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
|
||||||
|
|
||||||
|
use frontend::routes;
|
||||||
|
|
||||||
|
#[function_component(App)]
|
||||||
|
fn app() -> Html {
|
||||||
|
html! {
|
||||||
|
<BrowserRouter>
|
||||||
|
<Switch<routes::Route> render={Switch::render(routes::switch)} />
|
||||||
|
</BrowserRouter>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
wasm_logger::init(wasm_logger::Config::default());
|
||||||
|
|
||||||
|
yew::start_app::<App>();
|
||||||
|
}
|
8
frontend/src/routes/home.rs
Normal file
8
frontend/src/routes/home.rs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
use yew::prelude::*;
|
||||||
|
|
||||||
|
#[function_component(Home)]
|
||||||
|
pub fn home() -> Html {
|
||||||
|
html! {
|
||||||
|
<h1>{ "HOME!" }</h1>
|
||||||
|
}
|
||||||
|
}
|
8
frontend/src/routes/login.rs
Normal file
8
frontend/src/routes/login.rs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
use yew::prelude::*;
|
||||||
|
|
||||||
|
#[function_component(Login)]
|
||||||
|
pub fn login() -> Html {
|
||||||
|
html! {
|
||||||
|
<h1>{ "LOGIN!" }</h1>
|
||||||
|
}
|
||||||
|
}
|
29
frontend/src/routes/mod.rs
Normal file
29
frontend/src/routes/mod.rs
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
use yew::prelude::*;
|
||||||
|
use yew_router::prelude::*;
|
||||||
|
|
||||||
|
use crate::layouts;
|
||||||
|
|
||||||
|
pub mod home;
|
||||||
|
pub mod login;
|
||||||
|
pub mod not_found;
|
||||||
|
|
||||||
|
#[derive(Clone, Routable, PartialEq)]
|
||||||
|
pub enum Route {
|
||||||
|
#[at("/")]
|
||||||
|
Home,
|
||||||
|
#[at("/login")]
|
||||||
|
Login,
|
||||||
|
#[not_found]
|
||||||
|
#[at("/404")]
|
||||||
|
NotFound,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn switch(routes: &Route) -> Html {
|
||||||
|
match routes {
|
||||||
|
Route::Home => html! { <layouts::main::Main><home::Home /></layouts::main::Main> },
|
||||||
|
Route::Login => html! { <layouts::main::Main><login::Login /></layouts::main::Main> },
|
||||||
|
Route::NotFound => {
|
||||||
|
html! { <layouts::main::Main><not_found::NotFound /></layouts::main::Main> }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
8
frontend/src/routes/not_found.rs
Normal file
8
frontend/src/routes/not_found.rs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
use yew::prelude::*;
|
||||||
|
|
||||||
|
#[function_component(NotFound)]
|
||||||
|
pub fn not_found() -> Html {
|
||||||
|
html! {
|
||||||
|
<h1>{ "404" }</h1>
|
||||||
|
}
|
||||||
|
}
|
12
frontend_old/.dockerignore
Normal file
12
frontend_old/.dockerignore
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
.DS_Store
|
||||||
|
node_modules
|
||||||
|
/build
|
||||||
|
/.svelte-kit
|
||||||
|
/package
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
yarn-error.log
|
||||||
|
Dockerfile
|
||||||
|
.gitignore
|
||||||
|
.dockerignore
|
9
frontend_old/.gitignore
vendored
Normal file
9
frontend_old/.gitignore
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
.DS_Store
|
||||||
|
node_modules
|
||||||
|
/build
|
||||||
|
/.svelte-kit
|
||||||
|
/package
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
yarn-error.log
|
22
frontend_old/Dockerfile
Normal file
22
frontend_old/Dockerfile
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
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" ]
|
|
@ -12,7 +12,7 @@
|
||||||
"format": "prettier --ignore-path .gitignore --write --plugin-search-dir=. ."
|
"format": "prettier --ignore-path .gitignore --write --plugin-search-dir=. ."
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@sveltejs/adapter-auto": "next",
|
"@sveltejs/adapter-auto": "^1.0.0-next.86",
|
||||||
"@sveltejs/kit": "next",
|
"@sveltejs/kit": "next",
|
||||||
"@types/cookie": "^0.4.1",
|
"@types/cookie": "^0.4.1",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.10.1",
|
"@typescript-eslint/eslint-plugin": "^5.10.1",
|
|
@ -1,4 +1,4 @@
|
||||||
import type { RequestEvent, App, ResolveOpts } from "@sveltejs/kit/types";
|
import type { RequestEvent, ResolveOpts } from "@sveltejs/kit";
|
||||||
import * as cookie from "cookie";
|
import * as cookie from "cookie";
|
||||||
|
|
||||||
import * as cookieNames from "$lib/cookieNames";
|
import * as cookieNames from "$lib/cookieNames";
|
||||||
|
@ -8,7 +8,8 @@ export async function handle(input: {
|
||||||
opts?: ResolveOpts;
|
opts?: ResolveOpts;
|
||||||
resolve(event: RequestEvent, opts?: ResolveOpts): Promise<Response>;
|
resolve(event: RequestEvent, opts?: ResolveOpts): Promise<Response>;
|
||||||
}): Promise<Response> {
|
}): Promise<Response> {
|
||||||
const cookies = cookie.parse(input.event.request.headers.get("cookie") || "");
|
const header = input.event.request.headers.get("cookie");
|
||||||
|
const cookies = header ? cookie.parse(header) : {};
|
||||||
const token: string | undefined = cookies[cookieNames.TOKEN];
|
const token: string | undefined = cookies[cookieNames.TOKEN];
|
||||||
|
|
||||||
input.event.locals = {
|
input.event.locals = {
|
1
frontend_old/src/lib/StudentHome.svelte
Normal file
1
frontend_old/src/lib/StudentHome.svelte
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<h3>STUDENT!</h3>
|
1
frontend_old/src/lib/TeacherHome.svelte
Normal file
1
frontend_old/src/lib/TeacherHome.svelte
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<h3>TEACHER!</h3>
|
|
@ -17,11 +17,12 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { operationStore, query, gql, mutation } from "@urql/svelte";
|
import { operationStore, query, gql, mutation } from "@urql/svelte";
|
||||||
import * as svelteForms from "svelte-forms";
|
// import * as svelteForms from "svelte-forms";
|
||||||
import * as validators from "svelte-forms/validators";
|
// import * as validators from "svelte-forms/validators";
|
||||||
|
|
||||||
import type { User, Teacher, Student } from "$lib/graphql";
|
import type { User } from "$lib/graphql";
|
||||||
import { UserRole } from "$lib/graphql";
|
import { UserRole } from "$lib/graphql";
|
||||||
|
import StudentHome from "$lib/StudentHome.svelte";
|
||||||
|
|
||||||
interface Data {
|
interface Data {
|
||||||
me: User;
|
me: User;
|
||||||
|
@ -175,5 +176,6 @@
|
||||||
<p>Registrierung erfolgreich!</p>
|
<p>Registrierung erfolgreich!</p>
|
||||||
{/if}
|
{/if}
|
||||||
{/if} -->
|
{/if} -->
|
||||||
|
<StudentHome />
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
0
frontend_old/static/.keep
Normal file
0
frontend_old/static/.keep
Normal file
2085
frontend_old/test.json
Normal file
2085
frontend_old/test.json
Normal file
File diff suppressed because it is too large
Load diff
|
@ -27,5 +27,5 @@
|
||||||
"$lib/*": ["src/lib/*"]
|
"$lib/*": ["src/lib/*"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.ts", "src/**/*.svelte"]
|
"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 a new issue