Rewrite frontend in rust with yew
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
This commit is contained in:
parent
c56d359814
commit
860ae7ed5e
|
@ -16,6 +16,7 @@
|
|||
|
||||
require "jwt"
|
||||
require "json"
|
||||
require "uuid"
|
||||
|
||||
module Backend
|
||||
module Api
|
||||
|
@ -31,16 +32,18 @@ module Backend
|
|||
include JSON::Serializable
|
||||
|
||||
getter iss : String
|
||||
getter vrs : String
|
||||
getter iat : Int64
|
||||
getter exp : Int64
|
||||
getter jti : String
|
||||
getter jti : UUID
|
||||
getter context : Context
|
||||
|
||||
def initialize(
|
||||
@iss : String,
|
||||
@vrs : String,
|
||||
@iat : Int64,
|
||||
@exp : Int64,
|
||||
@jti : String,
|
||||
@jti : UUID,
|
||||
@context : Context
|
||||
)
|
||||
end
|
||||
|
@ -52,9 +55,10 @@ module Backend
|
|||
def self.from_hash(token : Hash(String, JSON::Any)) : self
|
||||
self.new(
|
||||
iss: token["iss"].as_s,
|
||||
vrs: token["vrs"].as_s,
|
||||
iat: token["iat"].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)
|
||||
)
|
||||
end
|
||||
|
|
|
@ -62,6 +62,8 @@ module Backend
|
|||
rescue
|
||||
@status = Status::JWTError
|
||||
else
|
||||
pp! payload
|
||||
|
||||
if @user = Db::User.find(payload.context.user)
|
||||
@admin = user.not_nil!.admin
|
||||
@role = user.not_nil!.role.to_api
|
||||
|
@ -98,8 +100,10 @@ module Backend
|
|||
|
||||
# :ditto:
|
||||
def authenticated! : Bool
|
||||
raise "Session expired" if @status.session_expired?
|
||||
raise "Not authenticated" unless authenticated?
|
||||
# raise "Session expired" if @status.session_expired?
|
||||
raise Errors::SessionExpired.new if @status.session_expired?
|
||||
# raise "Not authenticated" unless authenticated?
|
||||
raise Errors::NotAuthenticated.new unless authenticated?
|
||||
|
||||
true
|
||||
end
|
||||
|
@ -112,7 +116,8 @@ module Backend
|
|||
# :ditto:
|
||||
def admin! : Bool
|
||||
authenticated!
|
||||
raise "Invalid permissions" unless admin?
|
||||
# raise "Invalid permissions" unless admin?
|
||||
raise Errors::InvalidPermissions.new unless admin?
|
||||
|
||||
true
|
||||
end
|
||||
|
@ -142,7 +147,8 @@ module Backend
|
|||
# :ditto:
|
||||
def role!(roles : Array(Schema::UserRole), external_check = true) : Bool
|
||||
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
|
||||
end
|
||||
|
@ -169,32 +175,31 @@ module Backend
|
|||
|
||||
# Custom error handler
|
||||
def handle_exception(ex : Exception) : String?
|
||||
pp! ex, ex.message
|
||||
|
||||
# ex.message
|
||||
# pp! ex, ex.message, ex.class, typeof(ex), ex.is_a? Errors::PublicError
|
||||
# # ex.message
|
||||
|
||||
case ex
|
||||
when Errors::Error
|
||||
when Errors::PublicError
|
||||
ex.api_message
|
||||
when Errors::PrivateError
|
||||
{% if !flag?(:release) %}
|
||||
if @development
|
||||
ex.message
|
||||
ex.api_message
|
||||
else
|
||||
nil
|
||||
Errors::UNKNOWN_PRIVATE_ERROR
|
||||
end
|
||||
{% else %}
|
||||
nil
|
||||
Errors::UNKNOWN_PRIVATE_ERROR
|
||||
{% end %}
|
||||
when Errors::PublicError
|
||||
ex.message
|
||||
else
|
||||
{% if !flag?(:release) %}
|
||||
if @development
|
||||
ex.message
|
||||
ex.message || Errors::UNKNOWN_PRIVATE_ERROR
|
||||
else
|
||||
nil
|
||||
Errors::UNKNOWN_PRIVATE_ERROR
|
||||
end
|
||||
{% else %}
|
||||
nil
|
||||
Errors::UNKNOWN_PRIVATE_ERROR
|
||||
{% end %}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
module Backend::Api::Errors
|
||||
UNKNOWN_PRIVATE_ERROR = "UNKNOWN_ERROR"
|
||||
UNKNOWN_PUBLIC_ERROR = "UNKNOWN_PUBLIC_ERROR"
|
||||
|
||||
abstract class Error < Exception
|
||||
abstract def api_message : String
|
||||
end
|
||||
|
||||
abstract class PrivateError < Error
|
||||
|
@ -8,6 +12,57 @@ module Backend::Api::Errors
|
|||
abstract class PublicError < Error
|
||||
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
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
|
||||
require "ldap"
|
||||
require "uuid"
|
||||
require "uuid/json"
|
||||
require "random/secure"
|
||||
|
||||
module Backend
|
||||
module Api
|
||||
|
@ -25,19 +27,20 @@ module Backend
|
|||
@[GraphQL::Field]
|
||||
# Logs in as *username* with credential *password*
|
||||
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 }
|
||||
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(
|
||||
user: User.new(user),
|
||||
token: Auth::Token.new(
|
||||
iss: "mentorenwahl",
|
||||
iss: "Mentorenwahl",
|
||||
vrs: Backend::VERSION,
|
||||
iat: Time.utc.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!)
|
||||
).encode
|
||||
)
|
||||
|
@ -48,11 +51,11 @@ module Backend
|
|||
def create_user(context : Context, input : UserCreateInput, check_ldap : Bool = true) : User
|
||||
context.admin!
|
||||
|
||||
raise "LDAP user does not exist" if check_ldap && begin
|
||||
!Ldap::User.from_username(input.username)
|
||||
rescue LDAP::Client::AuthError
|
||||
true
|
||||
end
|
||||
raise Errors::LdapUserDoesNotExist.new if check_ldap && begin
|
||||
!Ldap::User.from_username(input.username)
|
||||
rescue LDAP::Client::AuthError
|
||||
true
|
||||
end
|
||||
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
|
||||
|
||||
|
@ -148,16 +151,16 @@ module Backend
|
|||
def create_vote(context : Context, input : VoteCreateInput) : Vote
|
||||
context.student!
|
||||
|
||||
raise "Duplicate teachers" 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::DuplicateTeachers.new if input.teacher_ids.uniq.size != input.teacher_ids.size
|
||||
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
|
||||
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|
|
||||
teacher = Db::Teacher.find(id)
|
||||
|
||||
if teacher.nil?
|
||||
raise "Teachers not found"
|
||||
raise Errors::TeachersNotFound.new
|
||||
# elsif teacher.user.skif != context.user.not_nil!.skif
|
||||
# if teacher.user.skif
|
||||
# raise "Teacher is SKIF, student is not"
|
||||
|
|
|
@ -125,6 +125,14 @@ module Backend
|
|||
students > 0 && votes >= students
|
||||
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]
|
||||
# Teacher vote by ID
|
||||
def teacher_vote(context : Context, id : Int32) : TeacherVote
|
||||
|
|
|
@ -56,7 +56,7 @@ module Backend
|
|||
if ex
|
||||
raise ex
|
||||
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
|
||||
|
|
|
@ -33,7 +33,7 @@ module Backend
|
|||
|
||||
@[ARTA::Get("")]
|
||||
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)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -20,7 +20,7 @@ events {
|
|||
http {
|
||||
server {
|
||||
location / {
|
||||
proxy_pass http://frontend:3000/;
|
||||
proxy_pass http://frontend/;
|
||||
}
|
||||
|
||||
location /graphql {
|
||||
|
|
|
@ -104,8 +104,6 @@ services:
|
|||
- default
|
||||
depends_on:
|
||||
- backend
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
|
||||
networks:
|
||||
db:
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
[build]
|
||||
target = "wasm32-unknown-unknown"
|
|
@ -1,14 +1,20 @@
|
|||
.DS_Store
|
||||
node_modules
|
||||
yarn-error.log
|
||||
/build
|
||||
/.svelte-kit
|
||||
/package
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
debug/
|
||||
target/
|
||||
|
||||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
||||
Cargo.lock
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||
*.pdb
|
||||
|
||||
dist/
|
||||
Dockerfile
|
||||
.dockerignore
|
||||
.gitignore
|
||||
README.md
|
||||
.nvmrc
|
||||
.dockerignore
|
||||
vendor/
|
||||
|
|
|
@ -1,9 +1,17 @@
|
|||
.DS_Store
|
||||
node_modules
|
||||
/build
|
||||
/.svelte-kit
|
||||
/package
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
yarn-error.log
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
debug/
|
||||
target/
|
||||
|
||||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
||||
Cargo.lock
|
||||
|
||||
# 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
|
|
@ -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
|
||||
COPY ./package.json ./yarn.lock ./
|
||||
RUN yarn install --frozen-lockfile
|
||||
COPY . .
|
||||
RUN yarn build
|
||||
EXPOSE 3000
|
||||
CMD ["yarn", "preview", "--host"]
|
||||
|
||||
FROM chef as planner
|
||||
WORKDIR /usr/src/frontend
|
||||
RUN mkdir src && touch src/main.rs
|
||||
COPY ./Cargo.toml .
|
||||
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
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
query Ok {
|
||||
ok
|
||||
}
|
|
@ -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!]!
|
||||
}
|
|
@ -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>
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
use const_format::concatcp;
|
||||
|
||||
pub const BASE: &str = "mentorenwahl_";
|
||||
pub const TOKEN: &str = concatcp!(BASE, "token");
|
|
@ -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();
|
||||
}
|
|
@ -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;
|
|
@ -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>
|
||||
</>
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
pub mod main;
|
|
@ -0,0 +1,5 @@
|
|||
pub mod components;
|
||||
pub mod cookie_names;
|
||||
pub mod graphql;
|
||||
pub mod layouts;
|
||||
pub mod routes;
|
|
@ -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>();
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
use yew::prelude::*;
|
||||
|
||||
#[function_component(Home)]
|
||||
pub fn home() -> Html {
|
||||
html! {
|
||||
<h1>{ "HOME!" }</h1>
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
use yew::prelude::*;
|
||||
|
||||
#[function_component(Login)]
|
||||
pub fn login() -> Html {
|
||||
html! {
|
||||
<h1>{ "LOGIN!" }</h1>
|
||||
}
|
||||
}
|
|
@ -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> }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
use yew::prelude::*;
|
||||
|
||||
#[function_component(NotFound)]
|
||||
pub fn not_found() -> Html {
|
||||
html! {
|
||||
<h1>{ "404" }</h1>
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
.DS_Store
|
||||
node_modules
|
||||
/build
|
||||
/.svelte-kit
|
||||
/package
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
yarn-error.log
|
||||
Dockerfile
|
||||
.gitignore
|
||||
.dockerignore
|
|
@ -0,0 +1,9 @@
|
|||
.DS_Store
|
||||
node_modules
|
||||
/build
|
||||
/.svelte-kit
|
||||
/package
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
yarn-error.log
|
|
@ -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=. ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-auto": "next",
|
||||
"@sveltejs/adapter-auto": "^1.0.0-next.86",
|
||||
"@sveltejs/kit": "next",
|
||||
"@types/cookie": "^0.4.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 cookieNames from "$lib/cookieNames";
|
||||
|
@ -8,7 +8,8 @@ export async function handle(input: {
|
|||
opts?: ResolveOpts;
|
||||
resolve(event: RequestEvent, opts?: ResolveOpts): 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];
|
||||
|
||||
input.event.locals = {
|
|
@ -0,0 +1 @@
|
|||
<h3>STUDENT!</h3>
|
|
@ -0,0 +1 @@
|
|||
<h3>TEACHER!</h3>
|
|
@ -17,11 +17,12 @@
|
|||
|
||||
<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 * as svelteForms from "svelte-forms";
|
||||
// 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 StudentHome from "$lib/StudentHome.svelte";
|
||||
|
||||
interface Data {
|
||||
me: User;
|
||||
|
@ -175,5 +176,6 @@
|
|||
<p>Registrierung erfolgreich!</p>
|
||||
{/if}
|
||||
{/if} -->
|
||||
<StudentHome />
|
||||
{/if}
|
||||
{/if}
|
File diff suppressed because it is too large
Load Diff
|
@ -27,5 +27,5 @@
|
|||
"$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 New Issue