diff --git a/.drone.yml b/.drone.yml
index 57d162d..d017d6c 100644
--- a/.drone.yml
+++ b/.drone.yml
@@ -43,25 +43,25 @@ steps:
- name: ameba
image: veelenga/ameba
commands:
- - cd docker/backend
+ - cd backend
- ameba micrate/src src
- name: deps
- image: crystallang/crystal:1.3-alpine
+ image: crystallang/crystal:1.6-alpine
volumes:
- name: lib
- path: /drone/src/docker/backend/lib
+ path: /drone/src/backend/lib
- name: cache
path: /cache
commands:
- - cd docker/backend
+ - cd backend
- shards install
- name: docs
- image: crystallang/crystal:1.3-alpine
+ image: crystallang/crystal:1.6-alpine
volumes:
- name: lib
- path: /drone/src/docker/backend/lib
+ path: /drone/src/backend/lib
commands:
- - cd docker/backend
+ - cd backend
- make docs
depends_on:
- ameba
@@ -74,7 +74,7 @@ steps:
settings:
rebuild: true
mount:
- - ./docker/backend/docs
+ - ./backend/docs
depends_on:
- docs
- name: build
@@ -83,20 +83,10 @@ steps:
- name: dockersock
path: /var/run/docker.sock
commands:
- - docker-compose build --build-arg BUILD_ENV=development backend
+ - docker-compose build backend
depends_on:
- ameba
-volumes:
- - name: lib
- temp: {}
- - name: cache
- host:
- path: /tmp/cache
- - name: dockersock
- host:
- path: /var/run/docker.sock
-
---
kind: pipeline
type: docker
@@ -109,7 +99,7 @@ steps:
- name: dockersock
path: /var/run/docker.sock
commands:
- - docker-compose build --build-arg BUILD_ENV=development frontend
+ - docker-compose build frontend
volumes:
- name: dockersock
@@ -131,7 +121,7 @@ steps:
restore: true
mount:
- ./docs/book
- - ./docker/backend/docs
+ - ./backend/docs
- name: prepare-pages
image: bitnami/git
volumes:
@@ -144,7 +134,7 @@ steps:
- rm -rf /tmp/pages/*
- cp -r ./docs/book/* /tmp/pages
- mkdir -p /tmp/pages/_api/backend
- - cp -r ./docker/backend/docs/* /tmp/pages/_api/backend
+ - cp -r ./backend/docs/* /tmp/pages/_api/backend
depends_on:
- restore-cache
- name: deploy-pages
diff --git a/.example.env b/.example.env
index 97f61dc..e17b7bf 100644
--- a/.example.env
+++ b/.example.env
@@ -26,7 +26,6 @@ BACKEND_MINIMUM_TEACHER_SELECTION_COUNT=6
BACKEND_ASSIGNMENT_POSSIBILITY_COUNT=500
BACKEND_URL=URL
# Backend - API
-BACKEND_API_GRAPHQL_PLAYGROUND=false
BACKEND_API_JWT_SECRET=
BACKEND_API_JWT_EXPIRATION=360
# Backend - Worker
diff --git a/docker/backend/.dockerignore b/backend/.dockerignore
similarity index 100%
rename from docker/backend/.dockerignore
rename to backend/.dockerignore
diff --git a/docker/backend/.editorconfig b/backend/.editorconfig
similarity index 100%
rename from docker/backend/.editorconfig
rename to backend/.editorconfig
diff --git a/docker/backend/.gitignore b/backend/.gitignore
similarity index 100%
rename from docker/backend/.gitignore
rename to backend/.gitignore
diff --git a/docker/backend/Dockerfile b/backend/Dockerfile
similarity index 50%
rename from docker/backend/Dockerfile
rename to backend/Dockerfile
index 7f8fbdc..a40a006 100644
--- a/docker/backend/Dockerfile
+++ b/backend/Dockerfile
@@ -14,50 +14,66 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-FROM crystallang/crystal:1.5-alpine as micrate-deps
-WORKDIR /src
+FROM veelenga/ameba:latest
+WORKDIR /usr/src/micrate
+COPY ./micrate/src ./src
+RUN ameba src/ || true
+
+FROM crystallang/crystal:1.6-alpine as micrate-deps
+WORKDIR /usr/src/micrate
COPY ./micrate/shard.yml ./micrate/shard.lock ./
RUN shards install --production
-FROM crystallang/crystal:1.5-alpine as micrate-builder
-WORKDIR /src
-# RUN apk add --no-cache sqlite-static
-COPY --from=micrate-deps /src/shard.yml /src/shard.lock ./
-COPY --from=micrate-deps /src/lib ./lib
+FROM crystallang/crystal:1.6-alpine as micrate-builder
+WORKDIR /usr/src/micrate
+COPY --from=micrate-deps /usr/src/micrate/shard.yml /usr/src/micrate/shard.lock ./
+COPY --from=micrate-deps /usr/src/micrate/lib ./lib
COPY ./micrate/src ./src
RUN shards build --release --static --verbose -s -p -t
FROM tdewolff/minify as public
-WORKDIR /src
+WORKDIR /usr/src/public
COPY ./public ./src
RUN minify -r -o ./dist ./src
-FROM crystallang/crystal:1.5-alpine as deps
-WORKDIR /src
+FROM veelenga/ameba:latest
+WORKDIR /usr/src/mentorenwahl
+COPY ./src ./src
+RUN ameba src/ || true
+
+FROM crystallang/crystal:1.6-alpine as deps
+WORKDIR /usr/src/mentorenwahl
COPY ./shard.yml ./shard.lock ./
RUN shards install --production
-FROM crystallang/crystal:1.5-alpine as builder
-ARG BUILD_ENV
-WORKDIR /src/mentorenwahl
+FROM crystallang/crystal:1.6-alpine as builder
+WORKDIR /usr/src/mentorenwahl
RUN apk add --no-cache pcre2-dev
-COPY --from=deps /src/shard.yml /src/shard.lock ./
-COPY --from=deps /src/lib ./lib
+COPY --from=deps /usr/src/mentorenwahl/shard.yml /usr/src/mentorenwahl/shard.lock ./
+COPY --from=deps /usr/src/mentorenwahl/lib ./lib
COPY ./LICENSE .
COPY ./Makefile .
-COPY ./src ./src
COPY ./db ./db
-COPY --from=public /src/dist ./public
+COPY ./src ./src
+ARG BUILD_ENV
+COPY --from=public /usr/src/public/dist ./public
RUN if [ "${BUILD_ENV}" = "development" ]; then \
make dev; \
else \
make; \
fi
+RUN mkdir deps
+RUN if [ "${BUILD_ENV}" = "development" ]; then \
+ ldd bin/backend | tr -s '[:blank:]' '\n' | grep '^/' | \
+ xargs -I % sh -c 'mkdir -p $(dirname deps%); cp % deps%;'; \
+ fi
FROM scratch as runner
-COPY --from=micrate-builder /src/bin/micrate /bin/micrate
-COPY --from=builder /src/mentorenwahl/bin /bin
-COPY --from=builder /src/mentorenwahl/db ./db
+WORKDIR /
+COPY --from=micrate-builder /usr/src/micrate/bin/micrate ./bin/micrate
+COPY --from=builder /usr/src/mentorenwahl/deps /
+COPY --from=builder /usr/src/mentorenwahl/bin/backend ./bin/backend
+COPY --from=builder /usr/src/mentorenwahl/db ./db
EXPOSE 80
-ENTRYPOINT [ "backend" ]
+ENTRYPOINT [ "./bin/backend" ]
CMD [ "run" ]
diff --git a/docker/backend/LICENSE b/backend/LICENSE
similarity index 100%
rename from docker/backend/LICENSE
rename to backend/LICENSE
diff --git a/docker/backend/Makefile b/backend/Makefile
similarity index 83%
rename from docker/backend/Makefile
rename to backend/Makefile
index e5c573d..9c33eb6 100644
--- a/docker/backend/Makefile
+++ b/backend/Makefile
@@ -19,10 +19,10 @@
all: prod
dev:
- shards build -Ddevelopment --static --verbose -s -p -t
+ shards build -Dplayground --verbose -s -p -t
prod:
- shards build --static --release --verbose -s -p -t
+ shards build --production --static --release --verbose -s -p -t
docs:
- crystal docs --project-name "Mentorenwahl Backend"
+ crystal docs --project-name "Mentorenwahl"
diff --git a/docker/backend/db/migrations/20220414171336_create_users.sql b/backend/db/migrations/20220414171336_create_users.sql
similarity index 93%
rename from docker/backend/db/migrations/20220414171336_create_users.sql
rename to backend/db/migrations/20220414171336_create_users.sql
index f29bfb1..e489e42 100644
--- a/docker/backend/db/migrations/20220414171336_create_users.sql
+++ b/backend/db/migrations/20220414171336_create_users.sql
@@ -17,14 +17,14 @@
*/
-- +micrate Up
-- SQL in section ' Up ' is executed when this migration is applied
-CREATE TYPE user_roles AS ENUM ('teacher', 'student');
+CREATE TYPE user_roles AS ENUM ('student', 'teacher');
CREATE TABLE users(
id SERIAL PRIMARY KEY,
username TEXT UNIQUE NOT NULL,
role user_roles NOT NULL,
- skif BOOLEAN NOT NULL,
- admin BOOLEAN NOT NULL
+ admin BOOLEAN NOT NULL,
+ jti uuid UNIQUE
);
CREATE TABLE teachers(
diff --git a/docker/backend/micrate/.editorconfig b/backend/micrate/.editorconfig
similarity index 100%
rename from docker/backend/micrate/.editorconfig
rename to backend/micrate/.editorconfig
diff --git a/docker/backend/micrate/.gitignore b/backend/micrate/.gitignore
similarity index 100%
rename from docker/backend/micrate/.gitignore
rename to backend/micrate/.gitignore
diff --git a/docker/backend/micrate/shard.lock b/backend/micrate/shard.lock
similarity index 100%
rename from docker/backend/micrate/shard.lock
rename to backend/micrate/shard.lock
diff --git a/docker/backend/micrate/shard.yml b/backend/micrate/shard.yml
similarity index 100%
rename from docker/backend/micrate/shard.yml
rename to backend/micrate/shard.yml
diff --git a/docker/backend/micrate/src/micrate.cr b/backend/micrate/src/micrate.cr
similarity index 100%
rename from docker/backend/micrate/src/micrate.cr
rename to backend/micrate/src/micrate.cr
diff --git a/docker/backend/public/index.html b/backend/public/index.html
similarity index 100%
rename from docker/backend/public/index.html
rename to backend/public/index.html
diff --git a/docker/backend/shard.lock b/backend/shard.lock
similarity index 95%
rename from docker/backend/shard.lock
rename to backend/shard.lock
index 27fc376..f2e10f9 100644
--- a/docker/backend/shard.lock
+++ b/backend/shard.lock
@@ -6,7 +6,7 @@ shards:
athena:
git: https://github.com/athena-framework/framework.git
- version: 0.17.0
+ version: 0.17.1
athena-config:
git: https://github.com/athena-framework/config.git
@@ -30,15 +30,15 @@ shards:
athena-routing:
git: https://github.com/athena-framework/routing.git
- version: 0.1.2
+ version: 0.1.3
athena-serializer:
git: https://github.com/athena-framework/serializer.git
- version: 0.3.0
+ version: 0.3.1
athena-validator:
git: https://github.com/athena-framework/validator.git
- version: 0.2.0
+ version: 0.2.1
baked_file_system:
git: https://github.com/schovi/baked_file_system.git
@@ -46,7 +46,7 @@ shards:
bindata:
git: https://github.com/spider-gazelle/bindata.git
- version: 1.10.0
+ version: 1.11.0
clear:
git: https://github.com/vici37/clear.git
@@ -82,7 +82,7 @@ shards:
graphql:
git: https://github.com/graphql-crystal/graphql.git
- version: 0.4.0
+ version: 0.4.0+git.commit.e3281bb0ef0ca301ccea176e6839422ac766465b
habitat:
git: https://github.com/luckyframework/habitat.git
@@ -126,7 +126,7 @@ shards:
pretty:
git: https://github.com/maiha/pretty.cr.git
- version: 1.1.1
+ version: 1.1.2
promise:
git: https://github.com/spider-gazelle/promise.git
@@ -150,7 +150,7 @@ shards:
shard:
git: https://github.com/maiha/shard.cr.git
- version: 0.3.1
+ version: 1.0.0
version_from_shard:
git: https://github.com/hugopl/version_from_shard.git
diff --git a/docker/backend/shard.yml b/backend/shard.yml
similarity index 97%
rename from docker/backend/shard.yml
rename to backend/shard.yml
index a3a9924..3c5da78 100644
--- a/docker/backend/shard.yml
+++ b/backend/shard.yml
@@ -24,9 +24,9 @@ license: GNU GPLv3
targets:
backend:
- main: src/cli/backend.cr
+ main: src/backend.cr
-crystal: 1.5.0
+crystal: 1.6.1
dependencies:
clear:
@@ -34,6 +34,7 @@ dependencies:
branch: master
graphql:
github: graphql-crystal/graphql
+ branch: main
jwt:
github: crystal-community/jwt
commander:
diff --git a/docker/backend/src/backend.cr b/backend/src/backend.cr
similarity index 90%
rename from docker/backend/src/backend.cr
rename to backend/src/backend.cr
index 1a39e68..882c582 100644
--- a/docker/backend/src/backend.cr
+++ b/backend/src/backend.cr
@@ -14,8 +14,15 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+require "commander"
+require "docker"
+
require "./backend/*"
# Base module
module Backend
+ Docker.setup
+ Db.init
+
+ Commander.run(CLI, ARGV)
end
diff --git a/docker/backend/src/backend/api.cr b/backend/src/backend/api.cr
similarity index 100%
rename from docker/backend/src/backend/api.cr
rename to backend/src/backend/api.cr
diff --git a/backend/src/backend/api/auth.cr b/backend/src/backend/api/auth.cr
new file mode 100644
index 0000000..fa482a6
--- /dev/null
+++ b/backend/src/backend/api/auth.cr
@@ -0,0 +1,82 @@
+# 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 .
+
+require "jwt"
+require "json"
+
+module Backend
+ module Api
+ # Authorization and authentication utilities
+ module Auth
+ extend self
+
+ # Bearer token header
+ BEARER = "Bearer "
+
+ # JWT token
+ struct Token
+ include JSON::Serializable
+
+ getter iss : String
+ getter iat : Int64
+ getter exp : Int64
+ getter jti : String
+ getter context : Context
+
+ def initialize(
+ @iss : String,
+ @iat : Int64,
+ @exp : Int64,
+ @jti : String,
+ @context : Context
+ )
+ end
+
+ def encode : String
+ JWT.encode(self, Backend.config.api.jwt_secret, JWT::Algorithm::HS256)
+ end
+
+ def self.from_hash(token : Hash(String, JSON::Any)) : self
+ self.new(
+ iss: token["iss"].as_s,
+ iat: token["iat"].as_i64,
+ exp: token["exp"].as_i64,
+ jti: token["jti"].as_s,
+ context: Context.from_hash(token["context"].as_h)
+ )
+ end
+
+ def self.decode(jwt : String) : self
+ self.from_hash(JWT.decode(jwt, Backend.config.api.jwt_secret, JWT::Algorithm::HS256)[0].as_h)
+ end
+ end
+
+ # JWT token context data
+ struct Context
+ include JSON::Serializable
+
+ getter user : Int32
+
+ def initialize(@user : Int32)
+ end
+
+ def self.from_hash(data : Hash(String, JSON::Any))
+ self.new(user: data["user"].as_i)
+ end
+ end
+ end
+ end
+end
diff --git a/docker/backend/src/backend/api/context.cr b/backend/src/backend/api/context.cr
similarity index 54%
rename from docker/backend/src/backend/api/context.cr
rename to backend/src/backend/api/context.cr
index 5219d5c..3359f01 100644
--- a/docker/backend/src/backend/api/context.cr
+++ b/backend/src/backend/api/context.cr
@@ -16,6 +16,8 @@
require "graphql"
require "http/headers"
+require "jwt/errors"
+require "json"
module Backend
module Api
@@ -24,6 +26,9 @@ module Backend
# Development mode
getter development
+ # Request status
+ getter status
+
# Authenticated user
getter user
@@ -38,6 +43,7 @@ module Backend
def initialize(
@development : Bool,
+ @status : Status,
@user : Db::User?,
@admin : Bool?,
@role : Schema::UserRole?,
@@ -45,37 +51,54 @@ module Backend
)
end
- def initialize(headers : HTTP::Headers, @development : Bool, *rest)
+ def initialize(headers : HTTP::Headers, @development : Bool, @status = Status::OK, *rest)
super(*rest)
- if (token = headers["authorization"]?) && token[..Auth::BEARER.size - 1] == Auth::BEARER
- payload = Auth.decode_jwt?(token[Auth::BEARER.size..])
- return unless payload
-
- data = payload["data"].as_h
- return unless @user = Db::User.find(data["user_id"].as_i)
-
- if @user
- @admin = user.not_nil!.admin
- @role = user.not_nil!.role.to_api
- @external =
- case @role.not_nil!
- when .teacher?
- @user.not_nil!.teacher
- when .student?
- @user.not_nil!.student
- end
+ if (token = headers["authorization"]?) && token.starts_with?(Auth::BEARER)
+ begin
+ payload = Auth::Token.decode(token[Auth::BEARER.size..].strip)
+ rescue ex : JWT::ExpiredSignatureError
+ @status = Status::SessionExpired
+ rescue
+ @status = Status::JWTError
+ else
+ if @user = Db::User.find(payload.context.user)
+ @admin = user.not_nil!.admin
+ @role = user.not_nil!.role.to_api
+ @external =
+ case @role.not_nil!
+ when .teacher?
+ @user.not_nil!.teacher
+ when .student?
+ @user.not_nil!.student
+ end
+ end
end
end
end
+ def on_development : Nil
+ {% if !flag?(:release) %}
+ if @development
+ yield
+ end
+ {% end %}
+ end
+
+ enum Status
+ OK
+ SessionExpired
+ JWTError
+ end
+
# User is authenticated
def authenticated? : Bool
- !!@user
+ !!@user && @status.ok?
end
# :ditto:
def authenticated! : Bool
+ raise "Session expired" if @status.session_expired?
raise "Not authenticated" unless authenticated?
true
@@ -88,20 +111,21 @@ module Backend
# :ditto:
def admin! : Bool
+ authenticated!
raise "Invalid permissions" unless admin?
true
end
# User's is one of *roles*
- def role?(external_check = true, *roles : Schema::UserRole) : Bool
+ def role?(roles : Array(Schema::UserRole), external_check = true) : Bool
return false unless authenticated?
roles.each do |role|
return true if @role == role &&
if external_check
role ==
- case @external.not_nil! # TODO: Simplify with Germanium in future but for now with macro iteration over `#resolve#constants`
+ case @external.not_nil!
when Db::Teacher
Schema::UserRole::Teacher
when Db::Student
@@ -116,33 +140,64 @@ module Backend
end
# :ditto:
- def role!(external_check = true, *roles : Schema::UserRole) : Bool
- raise "Invalid permissions" unless role? external, *roles
+ def role!(roles : Array(Schema::UserRole), external_check = true) : Bool
+ authenticated!
+ raise "Invalid permissions" unless role?(roles, external_check)
true
end
# User is teacher
def teacher?(external_check = true) : Bool
- role? external_check, Schema::UserRole::Teacher
+ role?([Schema::UserRole::Teacher], external_check)
end
# :ditto:
def teacher!(external_check = true) : Bool
- role! external_check, Schema::UserRole::Teacher
+ role!([Schema::UserRole::Teacher], external_check)
end
# User is student
def student?(external_check = true) : Bool
- role? external_check, Schema::UserRole::Student
+ role?([Schema::UserRole::Student], external_check)
end
# :ditto:
def student!(external_check = true) : Bool
- role! external_check, Schema::UserRole::Student
+ role!([Schema::UserRole::Student], external_check)
end
- # TODO: Custom error handler
+ # Custom error handler
+ def handle_exception(ex : Exception) : String?
+ pp! ex, ex.message
+
+ # ex.message
+
+ case ex
+ when Errors::Error
+ {% if !flag?(:release) %}
+ if @development
+ ex.message
+ else
+ nil
+ end
+ {% else %}
+ nil
+ {% end %}
+ when Errors::PublicError
+ ex.message
+ else
+ {% if !flag?(:release) %}
+ if @development
+ ex.message
+ else
+ nil
+ end
+ {% else %}
+ nil
+ {% end %}
+ end
+ end
end
end
end
diff --git a/backend/src/backend/api/errors.cr b/backend/src/backend/api/errors.cr
new file mode 100644
index 0000000..54b6008
--- /dev/null
+++ b/backend/src/backend/api/errors.cr
@@ -0,0 +1,13 @@
+module Backend::Api::Errors
+ abstract class Error < Exception
+ end
+
+ abstract class PrivateError < Error
+ end
+
+ abstract class PublicError < Error
+ end
+
+ class AuthenticationError < PublicError
+ end
+end
diff --git a/docker/backend/src/backend/api/schema.cr b/backend/src/backend/api/schema.cr
similarity index 100%
rename from docker/backend/src/backend/api/schema.cr
rename to backend/src/backend/api/schema.cr
diff --git a/docker/backend/src/backend/api/schema/config.cr b/backend/src/backend/api/schema/config.cr
similarity index 100%
rename from docker/backend/src/backend/api/schema/config.cr
rename to backend/src/backend/api/schema/config.cr
diff --git a/docker/backend/src/backend/api/schema/helpers.cr b/backend/src/backend/api/schema/helpers.cr
similarity index 100%
rename from docker/backend/src/backend/api/schema/helpers.cr
rename to backend/src/backend/api/schema/helpers.cr
diff --git a/docker/backend/src/backend/api/schema/mutation.cr b/backend/src/backend/api/schema/mutation.cr
similarity index 56%
rename from docker/backend/src/backend/api/schema/mutation.cr
rename to backend/src/backend/api/schema/mutation.cr
index 5078f91..b676478 100644
--- a/docker/backend/src/backend/api/schema/mutation.cr
+++ b/backend/src/backend/api/schema/mutation.cr
@@ -15,6 +15,7 @@
# along with this program. If not, see .
require "ldap"
+require "uuid"
module Backend
module Api
@@ -24,17 +25,21 @@ module Backend
@[GraphQL::Field]
# Logs in as *username* with credential *password*
def login(username : String, password : String) : LoginPayload
- raise "Auth failed" if username.empty? || password.empty?
+ raise Errors::AuthenticationError.new if username.empty? || password.empty?
user = Db::User.query.find { var(:username) == username }
- raise "Auth failed" unless user && Ldap.authenticate?(Ldap::DN.uid(username), password)
+ raise Errors::AuthenticationError.new unless user && Ldap.authenticate?(Ldap::DN.uid(username), password)
+ jti = UUID.random
LoginPayload.new(
user: User.new(user),
- token: Auth.create_user_jwt(
- user.id.not_nil!.to_i,
- (Time.utc + Backend.config.api.jwt_expiration.minutes).to_unix
- ),
+ token: Auth::Token.new(
+ iss: "mentorenwahl",
+ iat: Time.utc.to_unix,
+ exp: (Time.utc + Backend.config.api.jwt_expiration.minutes).to_unix,
+ jti: jti.hexstring,
+ context: Auth::Context.new(user.id.not_nil!)
+ ).encode
)
end
@@ -48,7 +53,7 @@ module Backend
rescue LDAP::Client::AuthError
true
end
- user = Db::User.create!(username: input.username, role: input.role.to_db, skif: input.skif, 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
User.new(user)
@@ -65,16 +70,6 @@ module Backend
id
end
- @[GraphQL::Field]
- # Sends all unregistered teachers a registration email
- def send_teachers_registration_email(context : Context) : Bool
- context.admin!
-
- Worker::Jobs::SendTeachersRegistrationEmailJob.new.enqueue
-
- true
- end
-
@[GraphQL::Field]
# Starts assignment job of mentors to students
def assign_students(context : Context) : Bool
@@ -85,68 +80,68 @@ module Backend
true
end
- @[GraphQL::Field]
- # Creates teacher
- def create_teacher(context : Context, input : TeacherCreateInput) : Teacher
- context.admin!
+ # @[GraphQL::Field]
+ # # Creates teacher
+ # def create_teacher(context : Context, input : TeacherCreateInput) : Teacher
+ # context.admin!
- teacher = Db::Teacher.create!(user_id: input.user_id, max_students: input.max_students)
- Teacher.new(teacher)
- end
+ # teacher = Db::Teacher.create!(user_id: input.user_id, max_students: input.max_students)
+ # Teacher.new(teacher)
+ # end
- @[GraphQL::Field]
- # Deletes teacher by ID
- def delete_teacher(context : Context, id : Int32) : Int32
- context.admin!
+ # @[GraphQL::Field]
+ # # Deletes teacher by ID
+ # def delete_teacher(context : Context, id : Int32) : Int32
+ # context.admin!
- teacher = Db::Teacher.find!(id)
- teacher.delete
+ # teacher = Db::Teacher.find!(id)
+ # teacher.delete
- id
- end
+ # id
+ # end
- @[GraphQL::Field]
- # Self register as teacher
- def register_teacher(context : Context, input : TeacherInput) : Teacher
- context.teacher! external_check: false
+ # @[GraphQL::Field]
+ # # Self register as teacher
+ # def register_teacher(context : Context, input : TeacherInput) : Teacher
+ # context.teacher! external_check: false
- Teacher.new(
- Db::Teacher.create!(user_id: context.user.not_nil!.id, max_students: input.max_students)
- )
- end
+ # Teacher.new(
+ # Db::Teacher.create!(user_id: context.user.not_nil!.id, max_students: input.max_students)
+ # )
+ # end
- @[GraphQL::Field]
- # Creates student
- def create_student(context : Context, input : StudentCreateInput) : Student
- context.admin!
+ # @[GraphQL::Field]
+ # # Creates student
+ # def create_student(context : Context, input : StudentCreateInput) : Student
+ # context.admin!
- user = Db::User.find!(input.user_id)
- raise "User not a student" unless user.role.to_api.student?
+ # user = Db::User.find!(input.user_id)
+ # raise "User not a student" unless user.role.to_api.student?
- student = Db::Student.create!(user_id: user.id)
- Student.new(student)
- end
+ # student = Db::Student.create!(user_id: user.id)
+ # Student.new(student)
+ # end
- @[GraphQL::Field]
- # Deletes student by ID
- def delete_student(context : Context, id : Int32) : Int32
- context.admin!
+ # @[GraphQL::Field]
+ # # Deletes student by ID
+ # def delete_student(context : Context, id : Int32) : Int32
+ # context.admin!
- student = Db::Student.find!(id)
- student.delete
+ # student = Db::Student.find!(id)
+ # student.delete
- id
- end
+ # id
+ # end
- @[GraphQL::Field]
- # Self register as student
- def register_student(context : Context) : Student
- context.student! external_check: false
+ # @[GraphQL::Field]
+ # # Self register as student
+ # def register_student(context : Context) : Student
+ # context.student! external_check: false
- Student.new(
- Db::Student.create!(user_id: context.user.not_nil!.id)
- )
- end
+ # Student.new(
+ # Db::Student.create!(user_id: context.user.not_nil!.id)
+ # )
+ # end
@[GraphQL::Field]
# Creates vote for authenticated user's student
@@ -163,12 +158,12 @@ module Backend
if teacher.nil?
raise "Teachers not found"
- elsif teacher.user.skif != context.user.not_nil!.skif
- if teacher.user.skif
- raise "Teacher is SKIF, student is not"
- else
- raise "Teacher is not SKIF, student is"
- end
+ # elsif teacher.user.skif != context.user.not_nil!.skif
+ # if teacher.user.skif
+ # raise "Teacher is SKIF, student is not"
+ # else
+ # raise "Teacher is not SKIF, student is"
+ # end
end
end
diff --git a/docker/backend/src/backend/api/schema/query.cr b/backend/src/backend/api/schema/query.cr
similarity index 100%
rename from docker/backend/src/backend/api/schema/query.cr
rename to backend/src/backend/api/schema/query.cr
diff --git a/docker/backend/src/backend/api/schema/student.cr b/backend/src/backend/api/schema/student.cr
similarity index 100%
rename from docker/backend/src/backend/api/schema/student.cr
rename to backend/src/backend/api/schema/student.cr
diff --git a/docker/backend/src/backend/api/schema/teacher.cr b/backend/src/backend/api/schema/teacher.cr
similarity index 100%
rename from docker/backend/src/backend/api/schema/teacher.cr
rename to backend/src/backend/api/schema/teacher.cr
diff --git a/docker/backend/src/backend/api/schema/teacher_vote.cr b/backend/src/backend/api/schema/teacher_vote.cr
similarity index 100%
rename from docker/backend/src/backend/api/schema/teacher_vote.cr
rename to backend/src/backend/api/schema/teacher_vote.cr
diff --git a/docker/backend/src/backend/api/schema/user.cr b/backend/src/backend/api/schema/user.cr
similarity index 85%
rename from docker/backend/src/backend/api/schema/user.cr
rename to backend/src/backend/api/schema/user.cr
index 94c9119..2a208df 100644
--- a/docker/backend/src/backend/api/schema/user.cr
+++ b/backend/src/backend/api/schema/user.cr
@@ -25,7 +25,12 @@ module Backend
# DB representation of the enum
def to_db : Db::UserRole
- Db::UserRole.from_string(self.to_s.underscore)
+ case self
+ in Teacher
+ Db::UserRole::Teacher
+ in Student
+ Db::UserRole::Student
+ end
end
# GraphQL representation of the DB enum
@@ -45,7 +50,7 @@ module Backend
# LDAP user data
def ldap : Ldap::User
- unless raw_cache = Redis::CLIENT.get("ldap:user:#{id}")
+ if @ldap.nil? && (raw_cache = Redis::CLIENT.get("ldap:user:#{id}")).nil?
Worker::Jobs::CacheLdapUserJob.new(id).perform
raw_cache = Redis::CLIENT.get("ldap:user:#{id}").not_nil!
end
@@ -67,8 +72,8 @@ module Backend
@[GraphQL::Field]
# User's full name
- def name : String
- ldap.name
+ def name(formal : Bool = true) : String
+ ldap.name(formal)
end
@[GraphQL::Field]
@@ -95,40 +100,27 @@ module Backend
@model.role.to_api
end
- @[GraphQL::Field]
- # User is at SKIF
- def skif : Bool
- @model.skif
- end
-
@[GraphQL::Field]
# User's external ID
- def external_id : Int32?
+ def external_id : Int32
case @model.role.to_api
- when .teacher?
+ when Db::UserRole::Teacher
@model.teacher
- when .student?
+ when Db::UserRole::Student
@model.student
- end
- .try(&.id.try(&.to_i))
+ end.not_nil!.id
end
@[GraphQL::Field]
# User's external teacher object
def teacher : Teacher?
- teacher = @model.teacher
- if teacher
- Teacher.new(teacher)
- end
+ @model.teacher.try { |t| Teacher.new(t) }
end
@[GraphQL::Field]
# User's external student object
def student : Student?
- student = @model.student
- if student
- Student.new(student)
- end
+ @model.student.try { |s| Student.new(s) }
end
end
@@ -137,14 +129,12 @@ module Backend
class UserCreateInput < GraphQL::BaseInputObject
getter username
getter role
- getter skif
getter admin
@[GraphQL::Field]
def initialize(
@username : String,
@role : UserRole,
- @skif : Bool,
@admin : Bool = false
)
end
diff --git a/docker/backend/src/backend/api/schema/vote.cr b/backend/src/backend/api/schema/vote.cr
similarity index 100%
rename from docker/backend/src/backend/api/schema/vote.cr
rename to backend/src/backend/api/schema/vote.cr
diff --git a/docker/backend/src/backend/authors.cr b/backend/src/backend/authors.cr
similarity index 100%
rename from docker/backend/src/backend/authors.cr
rename to backend/src/backend/authors.cr
diff --git a/docker/backend/src/backend/cli.cr b/backend/src/backend/cli.cr
similarity index 87%
rename from docker/backend/src/backend/cli.cr
rename to backend/src/backend/cli.cr
index 7296448..4454f18 100644
--- a/docker/backend/src/backend/cli.cr
+++ b/backend/src/backend/cli.cr
@@ -1,5 +1,4 @@
require "commander"
-require "compiled_license"
module Backend
CLI = Commander::Command.new do |cmd|
@@ -36,7 +35,7 @@ module Backend
c.long = c.short
c.run do
- puts CompiledLicense::LICENSES
+ puts LICENSES
end
end
@@ -55,13 +54,6 @@ module Backend
c.short = "Seeds the database with required data"
c.long = c.short
- c.flags.add do |f|
- f.name = "skif"
- f.long = "--skif"
- f.default = false
- f.description = "User at SKIF"
- end
-
c.flags.add do |f|
f.name = "admin"
f.long = "--admin"
@@ -85,7 +77,14 @@ module Backend
abort unless gets(chomp: true).not_nil!.strip.downcase == "y"
end
- user = Db::User.create!(username: username, role: role.to_s, skif: opts.bool["skif"], admin: opts.bool["admin"])
+ user = Db::User.create!(username: username, role: role.to_s, admin: opts.bool["admin"])
+ case role.to_api
+ in Api::Schema::UserRole::Student
+ Db::Student.create!(user_id: user.id)
+ in Api::Schema::UserRole::Teacher
+ Db::Teacher.create!(user_id: user.id)
+ end
+
Worker::Jobs::CacheLdapUserJob.new(user.id).enqueue
puts "Done!"
diff --git a/docker/backend/src/backend/config.cr b/backend/src/backend/config.cr
similarity index 88%
rename from docker/backend/src/backend/config.cr
rename to backend/src/backend/config.cr
index d3e9407..96a9b2b 100644
--- a/docker/backend/src/backend/config.cr
+++ b/backend/src/backend/config.cr
@@ -32,25 +32,25 @@ module Backend
# Types of environments program can compiled for / with
enum BuildEnv
- Production
+ Release
Development
end
# Type of environment program is running in
def build_env : BuildEnv
- {{ flag?(:development) }} ? BuildEnv::Development : BuildEnv::Production
+ {{ flag?(:release) }} ? BuildEnv::Release : BuildEnv::Development
end
- # Production mode
+ # Release mode
#
- # `true` if the build environment is `BuildEnv::Development`
- def production? : Bool
+ # `true` if the build environment is `BuildEnv::Release`
+ def release? : Bool
build_env.production?
end
# Development mode
#
- # `true` if the build environment is `BuildEnv::Production`
+ # `true` if the build environment is `BuildEnv::Development`
def development? : Bool
build_env.development?
end
@@ -87,20 +87,15 @@ module Backend
class ApiConfig
include EnvConfig
- # GraphQL playground enable
- getter graphql_playground : Bool
-
# JWT signing key
getter jwt_secret : String
# JWT expiration time in minutes
getter jwt_expiration : Int32
- # Helper method for enabling GraphQL playground
- #
- # Returns `true` if `Config#development?` or `#graphql_playground` are
- def graphql_playground_fully_enabled? : Bool
- Backend.config.development? || graphql_playground
+ # Returns true of `playground` flag was set on compile time
+ def graphql_playground? : Bool
+ flag?(:playground)
end
end
diff --git a/docker/backend/src/backend/db.cr b/backend/src/backend/db.cr
similarity index 91%
rename from docker/backend/src/backend/db.cr
rename to backend/src/backend/db.cr
index c73f936..3e5eb5a 100644
--- a/docker/backend/src/backend/db.cr
+++ b/backend/src/backend/db.cr
@@ -29,7 +29,7 @@ module Backend
# Migration UIDs
MIGRATIONS = {{ run("./macros/migrations.cr", "db/migrations/*.sql").stringify.split("\n") }}
- def init(severity = {% if flag?(:development) %} ::Log::Severity::Debug {% else %} ::Log::Severity::Info {% end %}) : Nil
+ def init(severity = {% if !flag?(:release) %} ::Log::Severity::Debug {% else %} ::Log::Severity::Info {% end %}) : Nil
::Log.builder.bind "clear.*", severity, ::Log::IOBackend.new
Retriable.retry(on: DB::ConnectionRefused, backoff: false) do
Clear::SQL.init(Backend.config.db.url)
diff --git a/docker/backend/src/backend/db/assignment.cr b/backend/src/backend/db/assignment.cr
similarity index 100%
rename from docker/backend/src/backend/db/assignment.cr
rename to backend/src/backend/db/assignment.cr
diff --git a/docker/backend/src/backend/db/micrate_db_version.cr b/backend/src/backend/db/micrate_db_version.cr
similarity index 100%
rename from docker/backend/src/backend/db/micrate_db_version.cr
rename to backend/src/backend/db/micrate_db_version.cr
diff --git a/docker/backend/src/backend/db/student.cr b/backend/src/backend/db/student.cr
similarity index 100%
rename from docker/backend/src/backend/db/student.cr
rename to backend/src/backend/db/student.cr
diff --git a/docker/backend/src/backend/db/teacher.cr b/backend/src/backend/db/teacher.cr
similarity index 100%
rename from docker/backend/src/backend/db/teacher.cr
rename to backend/src/backend/db/teacher.cr
diff --git a/docker/backend/src/backend/db/teacher_vote.cr b/backend/src/backend/db/teacher_vote.cr
similarity index 100%
rename from docker/backend/src/backend/db/teacher_vote.cr
rename to backend/src/backend/db/teacher_vote.cr
diff --git a/docker/backend/src/backend/db/user.cr b/backend/src/backend/db/user.cr
similarity index 72%
rename from docker/backend/src/backend/db/user.cr
rename to backend/src/backend/db/user.cr
index 8b9e45e..e959c9f 100644
--- a/docker/backend/src/backend/db/user.cr
+++ b/backend/src/backend/db/user.cr
@@ -5,7 +5,14 @@ module Backend
struct UserRole
# API representation of the enum
def to_api : Api::Schema::UserRole
- Api::Schema::UserRole.parse(self.to_s)
+ case self
+ when Student
+ Api::Schema::UserRole::Student
+ when Teacher
+ Api::Schema::UserRole::Teacher
+ else
+ raise "Invalid enum value for UserRole"
+ end
end
# DB representation of the enum
@@ -23,7 +30,7 @@ module Backend
column username : String
column role : UserRole
column admin : Bool = false
- column skif : Bool
+ column jti : UUID?
has_one student : Student?, foreign_key: :user_id
has_one teacher : Teacher?, foreign_key: :user_id
diff --git a/docker/backend/src/backend/db/vote.cr b/backend/src/backend/db/vote.cr
similarity index 100%
rename from docker/backend/src/backend/db/vote.cr
rename to backend/src/backend/db/vote.cr
diff --git a/docker/backend/src/backend/ldap.cr b/backend/src/backend/ldap.cr
similarity index 100%
rename from docker/backend/src/backend/ldap.cr
rename to backend/src/backend/ldap.cr
diff --git a/docker/backend/src/backend/ldap/dn.cr b/backend/src/backend/ldap/dn.cr
similarity index 100%
rename from docker/backend/src/backend/ldap/dn.cr
rename to backend/src/backend/ldap/dn.cr
diff --git a/docker/backend/src/backend/ldap/user.cr b/backend/src/backend/ldap/user.cr
similarity index 89%
rename from docker/backend/src/backend/ldap/user.cr
rename to backend/src/backend/ldap/user.cr
index 8bd7cd5..94d1221 100644
--- a/docker/backend/src/backend/ldap/user.cr
+++ b/backend/src/backend/ldap/user.cr
@@ -26,22 +26,26 @@ module Backend
@[JSON::Field(key: "givenName")]
# First name
- property first_name : String
+ getter first_name : String
@[JSON::Field(key: "sn")]
# Last name
- property last_name : String
+ getter last_name : String
@[JSON::Field(key: "mail")]
# Email address
- property email : String
+ getter email : String
def initialize(@first_name : String, @last_name : String, @email : String)
end
# Name
- def name : String
- "#{first_name} #{last_name}"
+ def name(formal = true) : String
+ if formal
+ "#{@last_name}, #{@first_name}"
+ else
+ "#{@first_name} #{@last_name}"
+ end
end
# Creates user data from LDAP entry
diff --git a/docker/backend/src/backend/license.cr b/backend/src/backend/license.cr
similarity index 100%
rename from docker/backend/src/backend/license.cr
rename to backend/src/backend/license.cr
diff --git a/backend/src/backend/licenses.cr b/backend/src/backend/licenses.cr
new file mode 100644
index 0000000..e0a1ea8
--- /dev/null
+++ b/backend/src/backend/licenses.cr
@@ -0,0 +1,5 @@
+require "compiled_license"
+
+module Backend
+ LICENSES = CompiledLicense::LICENSES
+end
diff --git a/docker/backend/src/backend/log.cr b/backend/src/backend/log.cr
similarity index 100%
rename from docker/backend/src/backend/log.cr
rename to backend/src/backend/log.cr
diff --git a/docker/backend/src/backend/macros/migrations.cr b/backend/src/backend/macros/migrations.cr
similarity index 100%
rename from docker/backend/src/backend/macros/migrations.cr
rename to backend/src/backend/macros/migrations.cr
diff --git a/docker/backend/src/backend/mailers.cr b/backend/src/backend/mailers.cr
similarity index 100%
rename from docker/backend/src/backend/mailers.cr
rename to backend/src/backend/mailers.cr
diff --git a/docker/backend/src/backend/redis.cr b/backend/src/backend/redis.cr
similarity index 100%
rename from docker/backend/src/backend/redis.cr
rename to backend/src/backend/redis.cr
diff --git a/docker/backend/src/backend/runner.cr b/backend/src/backend/runner.cr
similarity index 98%
rename from docker/backend/src/backend/runner.cr
rename to backend/src/backend/runner.cr
index 7da4af5..40ef586 100644
--- a/docker/backend/src/backend/runner.cr
+++ b/backend/src/backend/runner.cr
@@ -36,7 +36,7 @@ module Backend
# Run the backend
def run : self
- {% if flag?(:development) %}
+ {% if !flag?(:release) %}
Log.warn { "Backend is running in development mode! Do not use this in production!" }
{% end %}
diff --git a/docker/backend/src/backend/version.cr b/backend/src/backend/version.cr
similarity index 100%
rename from docker/backend/src/backend/version.cr
rename to backend/src/backend/version.cr
diff --git a/docker/backend/src/backend/web.cr b/backend/src/backend/web.cr
similarity index 100%
rename from docker/backend/src/backend/web.cr
rename to backend/src/backend/web.cr
diff --git a/docker/backend/src/backend/web/controllers.cr b/backend/src/backend/web/controllers.cr
similarity index 100%
rename from docker/backend/src/backend/web/controllers.cr
rename to backend/src/backend/web/controllers.cr
diff --git a/docker/backend/src/backend/web/controllers/api_controller.cr b/backend/src/backend/web/controllers/api_controller.cr
similarity index 61%
rename from docker/backend/src/backend/web/controllers/api_controller.cr
rename to backend/src/backend/web/controllers/api_controller.cr
index 8de2203..74b2a73 100644
--- a/docker/backend/src/backend/web/controllers/api_controller.cr
+++ b/backend/src/backend/web/controllers/api_controller.cr
@@ -15,7 +15,7 @@
# along with this program. If not, see .
require "http/headers"
-require "mime"
+require "baked_file_system"
module Backend
module Web
@@ -23,25 +23,29 @@ module Backend
@[ARTA::Route(path: "/")]
# GraphQL API controller
class ApiController < ATH::Controller
- @[ARTA::Get("")]
- def playground : ATH::Response | ATH::Exceptions::HTTPException
- {% if flag?(:development) %}
- ATH::StreamedResponse.new(headers: HTTP::Headers{"content-type" => MIME.from_extension(".html")}) do |io|
+ {% if flag?(:playground) %}
+ # Public folder virtual filesystem
+ private module Public
+ extend BakedFileSystem
+
+ bake_folder "../../../../public"
+ end
+
+ @[ARTA::Get("")]
+ def playground : ATH::Response
+ ATH::StreamedResponse.new(headers: HTTP::Headers{"Content-Type" => "text/html"}) do |io|
IO.copy(Public.get("index.html"), io)
end
- {% else %}
- if Backend.config.api.graphql_playground_fully_enabled?
- ATH::StreamedResponse.new(headers: HTTP::Headers{"content-type" => MIME.from_extension(".html")}) do |io|
- IO.copy(Public.get("index.html"), io)
- end
- else
- ATH::Exceptions::ServiceUnavailable.new("GraphQL Playground is not enabled. Please enable it in the backend configuration.")
- end
- {% end %}
- end
+ end
+ {% else %}
+ @[ARTA::Get("")]
+ def playground : ATH::Exceptions::ServiceUnavailable
+ ATH::Exceptions::ServiceUnavailable.new("GraphQL playground is disabled")
+ end
+ {% end %}
- # GraphQL query request data
- struct GraphQLQueryData
+ # GraphQL query data
+ struct GraphQLQuery
include JSON::Serializable
# Raw query
@@ -55,15 +59,18 @@ module Backend
end
@[ARTA::Post("")]
- @[ATHA::QueryParam("development")]
- def endpoint(request : ATH::Request, development : Bool = false) : ATH::Response
- {% if flag?(:development) %}
+ {% if !flag?(:release) %}
+ @[ATHA::QueryParam("development", description: "Enables development mode")]
+ {% end %}
+ def endpoint(request : ATH::Request, development : Bool = false) : ATH::Exceptions::BadRequest | ATH::Response
+ {% if !flag?(:release) %}
Log.notice { "Development request icoming" } if development
{% end %}
- query = GraphQLQueryData.from_json(request.body.not_nil!)
+ return ATH::Exceptions::BadRequest.new("No request body given") unless request.body
+ query = GraphQLQuery.from_json(request.body.not_nil!)
- ATH::StreamedResponse.new(headers: HTTP::Headers{"content-type" => MIME.from_extension(".json")}) do |io|
+ ATH::StreamedResponse.new(headers: HTTP::Headers{"content-type" => "application/json"}) do |io|
Api::Schema::SCHEMA.execute(
io,
query.query,
diff --git a/docker/backend/src/backend/web/service.cr b/backend/src/backend/web/service.cr
similarity index 100%
rename from docker/backend/src/backend/web/service.cr
rename to backend/src/backend/web/service.cr
diff --git a/docker/backend/src/backend/worker.cr b/backend/src/backend/worker.cr
similarity index 61%
rename from docker/backend/src/backend/worker.cr
rename to backend/src/backend/worker.cr
index 571c516..c3a6739 100644
--- a/docker/backend/src/backend/worker.cr
+++ b/backend/src/backend/worker.cr
@@ -21,23 +21,23 @@ require "./worker/*"
module Backend
# Worker module
module Worker
- # :inherit:
- module Mosquito::Serializers::Granite
- # :inherit:
- macro serialize_granite_model(klass)
- {% method_suffix = klass.resolve.stringify.underscore.gsub(/::/, "__").id %}
+ # # :inherit:
+ # module Mosquito::Serializers::Granite
+ # # :inherit:
+ # macro serialize_granite_model(klass)
+ # {% method_suffix = klass.resolve.stringify.underscore.gsub(/::/, "__").id %}
- # Serializes {{ klaas.id }} to redis manageable data
- def serialize_{{ method_suffix }}(model : {{ klass.id }}) : String
- model.id.to_s
- end
+ # # Serializes {{ klaas.id }} to redis manageable data
+ # def serialize_{{ method_suffix }}(model : {{ klass.id }}) : String
+ # model.id.to_s
+ # end
- # Deserializes {{ klaas.id }} from redis manageable data
- def deserialize_{{ method_suffix }}(raw : String) : {{ klass.id }}
- {{ klass.id }}.find!(raw.to_i)
- end
- end
- end
+ # # Deserializes {{ klaas.id }} from redis manageable data
+ # def deserialize_{{ method_suffix }}(raw : String) : {{ klass.id }}
+ # {{ klass.id }}.find!(raw.to_i)
+ # end
+ # end
+ # end
Mosquito.configure do |settings|
settings.redis_url = Backend.config.redis.url
diff --git a/docker/backend/src/backend/worker/jobs.cr b/backend/src/backend/worker/jobs.cr
similarity index 100%
rename from docker/backend/src/backend/worker/jobs.cr
rename to backend/src/backend/worker/jobs.cr
diff --git a/docker/backend/src/backend/worker/jobs/assignment_job.cr b/backend/src/backend/worker/jobs/assignment_job.cr
similarity index 91%
rename from docker/backend/src/backend/worker/jobs/assignment_job.cr
rename to backend/src/backend/worker/jobs/assignment_job.cr
index 2af2e69..42567ae 100644
--- a/docker/backend/src/backend/worker/jobs/assignment_job.cr
+++ b/backend/src/backend/worker/jobs/assignment_job.cr
@@ -72,10 +72,10 @@ module Backend
# pp! possibilities
- teacher_ids = Db::Teacher.query
- .select("id")
- .where { raw("(SELECT COUNT(*) FROM assignments WHERE teacher_id = teachers.id)") < max_students }
- .map(&.id)
+ # teacher_ids = Db::Teacher.query
+ # .select("id")
+ # .where { raw("(SELECT COUNT(*) FROM assignments WHERE teacher_id = teachers.id)") < max_students }
+ # .map(&.id)
students = Db::Student.query
.where do
raw("NOT EXISTS (SELECT 1 FROM assignments WHERE student_id = students.id)") &
@@ -100,13 +100,12 @@ module Backend
end
assignments = [] of {assignment: Hash(Int32, Array(Int32)), weighting: Int32}
- empty_assignment = Hash.zip(teacher_ids, [[] of {assignment: Hash(Int32, Array(Int32)), weighting: Int32}] * teacher_ids.size)
+ # empty_assignment = Hash.zip(teacher_ids, [[] of {assignment: Hash(Int32, Array(Int32)), weighting: Int32}] * teacher_ids.size)
Backend.config.assignment_possibility_count.times do
random_votes.shuffle!(Random::Secure)
- votes = random_votes.dup
- a = empty_assignment.clone
-
+ # votes = random_votes.dup
+ # a = empty_assignment.clone
end
pp! assignments
end
diff --git a/docker/backend/src/backend/worker/jobs/cache_ldap_user_job.cr b/backend/src/backend/worker/jobs/cache_ldap_user_job.cr
similarity index 100%
rename from docker/backend/src/backend/worker/jobs/cache_ldap_user_job.cr
rename to backend/src/backend/worker/jobs/cache_ldap_user_job.cr
diff --git a/docker/backend/src/backend/worker/log.cr b/backend/src/backend/worker/log.cr
similarity index 100%
rename from docker/backend/src/backend/worker/log.cr
rename to backend/src/backend/worker/log.cr
diff --git a/docker/backend/src/backend/worker/service.cr b/backend/src/backend/worker/service.cr
similarity index 100%
rename from docker/backend/src/backend/worker/service.cr
rename to backend/src/backend/worker/service.cr
diff --git a/docker-compose.yml b/docker-compose.yml
index 1952e10..bde2849 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -19,7 +19,6 @@ version: "3"
services:
nginx:
image: nginx:alpine
- container_name: nginx
restart: always
volumes:
- ./config/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
@@ -32,7 +31,6 @@ services:
db:
image: postgres:alpine
- container_name: db
restart: always
networks:
- db
@@ -44,7 +42,6 @@ services:
adminer:
image: adminer:standalone
- container_name: adminer
restart: always
networks:
- default
@@ -54,7 +51,6 @@ services:
redis:
image: redis:alpine
- container_name: redis
restart: always
networks:
- redis
@@ -62,11 +58,11 @@ services:
- redis:/data
backend:
+ image: mentorenwahl/backend
build:
- context: ./docker/backend
+ context: ./backend
args:
BUILD_ENV: production
- container_name: backend
restart: always
networks:
- default
@@ -79,7 +75,6 @@ services:
BACKEND_URL: ${URL}
BACKEND_MINIMUM_TEACHER_SELECTION_COUNT: ${BACKEND_MINIMUM_TEACHER_SELECTION_COUNT}
BACKEND_ASSIGNMENT_POSSIBILITY_COUNT: ${BACKEND_ASSIGNMENT_POSSIBILITY_COUNT}
- BACKEND_API_GRAPHQL_PLAYGROUND: ${BACKEND_API_GRAPHQL_PLAYGROUND}
BACKEND_API_JWT_SECRET: ${BACKEND_API_JWT_SECRET}
BACKEND_API_JWT_EXPIRATION: ${BACKEND_API_JWT_EXPIRATION}
BACKEND_SMTP_HELO: ${BACKEND_SMTP_HELO}
@@ -101,9 +96,9 @@ services:
BACKEND_LDAP_CACHE_REFRESH_INTERVAL: ${BACKEND_LDAP_CACHE_REFRESH_INTERVAL}
frontend:
+ image: mentorenwahl/frontend
build:
- context: ./docker/frontend
- container_name: frontend
+ context: ./frontend
restart: always
networks:
- default
@@ -116,6 +111,7 @@ networks:
db:
redis:
+
volumes:
db:
redis:
diff --git a/docker/backend/src/backend/api/auth.cr b/docker/backend/src/backend/api/auth.cr
deleted file mode 100644
index 36e0ba8..0000000
--- a/docker/backend/src/backend/api/auth.cr
+++ /dev/null
@@ -1,53 +0,0 @@
-# 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 .
-
-require "jwt"
-
-module Backend
- module Api
- # Authorization and authentication utilities
- module Auth
- extend self
-
- # Bearer token header
- BEARER = "Bearer "
-
- # Creates raw JWT token
- #
- # WARNING: Always use a wrapper for this method
- private def create_jwt(data, expiration : Int) : String
- JWT.encode({"data" => data.to_h, "exp" => expiration}, Backend.config.api.jwt_secret, JWT::Algorithm::HS256)
- end
-
- # Decodes JWT token
- def decode_jwt(jwt : String) : JSON::Any
- JWT.decode(jwt, Backend.config.api.jwt_secret, JWT::Algorithm::HS256)[0]
- end
-
- # :ditto:
- def decode_jwt?(jwt : String) : JSON::Any?
- decode_jwt(jwt)
- rescue
- nil
- end
-
- # Creates JWT token for user
- def create_user_jwt(user_id : Int, expiration : Int) : String
- create_jwt({user_id: user_id}, expiration)
- end
- end
- end
-end
diff --git a/docker/backend/src/backend/mailers/teacher_registration_mailer.cr b/docker/backend/src/backend/mailers/teacher_registration_mailer.cr
deleted file mode 100644
index 7a9ee37..0000000
--- a/docker/backend/src/backend/mailers/teacher_registration_mailer.cr
+++ /dev/null
@@ -1,32 +0,0 @@
-# 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 .
-
-module Backend
- module Mailers
- # Sends teacher a polite registration mail to ask if they may input their data
- class TeacherRegistrationMailer < Quartz::Composer
- def sender : Quartz::Message::Address
- address email: Backend.config.smtp.username, name: Backend.config.smtp.name
- end
-
- def initialize(user : Ldap::User)
- to name: user.name, email: user.email
- subject "Mentorenwahl Lehrer Registrierung"
- text Kilt.render("#{__DIR__}/templates/teacher_registration_mailer.txt.ecr")
- end
- end
- end
-end
diff --git a/docker/backend/src/backend/mailers/templates/teacher_registration_mailer.txt.ecr b/docker/backend/src/backend/mailers/templates/teacher_registration_mailer.txt.ecr
deleted file mode 100644
index 38f0307..0000000
--- a/docker/backend/src/backend/mailers/templates/teacher_registration_mailer.txt.ecr
+++ /dev/null
@@ -1,5 +0,0 @@
-Hey, <%= user.first_name %>!
-
-Du wurdest erfolgreich als Lehrer registriert.
-Initialisiere deinen Account, indem du auf den folgenden Link klickst und deine Daten eingibst:
-<%= Path[Backend.config.url, "login"] %>
diff --git a/docker/backend/src/backend/public.cr b/docker/backend/src/backend/public.cr
deleted file mode 100644
index e50ce1e..0000000
--- a/docker/backend/src/backend/public.cr
+++ /dev/null
@@ -1,26 +0,0 @@
-# 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 .
-
-require "baked_file_system"
-
-module Backend
- # Public folder virtual filesystem
- module Public
- extend BakedFileSystem
-
- bake_folder "../../public"
- end
-end
diff --git a/docker/backend/src/backend/worker/jobs/send_teachers_registration_email_job.cr b/docker/backend/src/backend/worker/jobs/send_teachers_registration_email_job.cr
deleted file mode 100644
index 52af323..0000000
--- a/docker/backend/src/backend/worker/jobs/send_teachers_registration_email_job.cr
+++ /dev/null
@@ -1,49 +0,0 @@
-# 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 .
-
-module Backend
- module Worker
- module Jobs
- # Sends all unregistered teachers a polite registration mail to ask if they may input their data
- class SendTeachersRegistrationEmailJob < Mosquito::QueuedJob
- # :ditto:
- def perform : Nil
- users = Db::User.query.where { (x.role == Db::UserRole::Teacher) & (x.teacher_id == nil) }
- count = users.count.to_i
-
- channel = Channel(Nil).new(count)
-
- users.each do |user|
- spawn do
- next unless user.role.to_api.teacher?
-
- ldap_user = Ldap::User.from_username(user.username)
- log "Sending teacher registration email to #{ldap_user.email} ##{user.id}"
- Mailers::TeacherRegistrationMailer.new(ldap_user).deliver
-
- channel.send(nil)
- end
- end
-
- count.times do
- channel.receive
- end
- Fiber.yield
- end
- end
- end
- end
-end
diff --git a/docker/backend/src/cli/backend.cr b/docker/backend/src/cli/backend.cr
deleted file mode 100644
index 3e92d4c..0000000
--- a/docker/backend/src/cli/backend.cr
+++ /dev/null
@@ -1,117 +0,0 @@
-# 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 .
-
-require "commander"
-require "compiled_license"
-require "docker"
-
-require "../backend"
-
-Docker.setup
-Backend::Db.init
-
-cli = Commander::Command.new do |cmd|
- cmd.use = "backend"
- cmd.short = "Mentorenwahl backend CLI"
-
- cmd.run do
- puts cmd.help
- end
-
- cmd.commands.add do |c|
- c.use = "version"
- c.short = "Prints version"
- c.long = c.short
-
- c.run do
- puts Backend::VERSION
- end
- end
-
- cmd.commands.add do |c|
- c.use = "authors"
- c.short = "Prints authors"
- c.long = c.short
-
- c.run do
- puts Backend::AUTHORS.join("\n")
- end
- end
-
- cmd.commands.add do |c|
- c.use = "licenses"
- c.short = "Prints licenses of projects used by this programs"
- c.long = c.short
-
- c.run do
- puts CompiledLicense::LICENSES
- end
- end
-
- cmd.commands.add do |c|
- c.use = "run"
- c.short = "Run the backend"
- c.long = c.short
-
- c.run do
- Backend::Runner.new.run
- end
- end
-
- cmd.commands.add do |c|
- c.use = "register "
- c.short = "Seeds the database with required data"
- c.long = c.short
-
- c.flags.add do |f|
- f.name = "skif"
- f.long = "--skif"
- f.default = false
- f.description = "User at SKIF"
- end
-
- c.flags.add do |f|
- f.name = "admin"
- f.long = "--admin"
- f.default = false
- f.description = "Register as admin"
- end
-
- c.flags.add do |f|
- f.name = "yes"
- f.short = "-y"
- f.long = "--yes"
- f.default = false
- f.description = "Answer yes to all questions"
- end
-
- c.run do |opts, args|
- username = args[0]
- role = Backend::Db::UserRole.from_string(args[1].underscore)
- unless opts.bool["yes"]
- print "Register '#{username}' as '#{role.to_api}'#{opts.bool["admin"] ? " with admin privileges" : nil}? [y/N] "
- abort unless gets(chomp: true).not_nil!.strip.downcase == "y"
- end
-
- user = Backend::Db::User.create!(username: username, role: role.to_s, skif: opts.bool["skif"], admin: opts.bool["admin"])
- Backend::Worker::Jobs::CacheLdapUserJob.new(user.id).enqueue
-
- puts "Done!"
- end
- end
-end
-
-Commander.run(cli, ARGV)
diff --git a/docker/frontend/.nvmrc b/docker/frontend/.nvmrc
deleted file mode 100644
index d9f8800..0000000
--- a/docker/frontend/.nvmrc
+++ /dev/null
@@ -1 +0,0 @@
-16.14.2
diff --git a/docker/frontend/.dockerignore b/frontend/.dockerignore
similarity index 100%
rename from docker/frontend/.dockerignore
rename to frontend/.dockerignore
diff --git a/docker/frontend/.eslintrc.cjs b/frontend/.eslintrc.cjs
similarity index 100%
rename from docker/frontend/.eslintrc.cjs
rename to frontend/.eslintrc.cjs
diff --git a/docker/frontend/.gitignore b/frontend/.gitignore
similarity index 100%
rename from docker/frontend/.gitignore
rename to frontend/.gitignore
diff --git a/docker/frontend/.npmrc b/frontend/.npmrc
similarity index 100%
rename from docker/frontend/.npmrc
rename to frontend/.npmrc
diff --git a/frontend/.nvmrc b/frontend/.nvmrc
new file mode 100644
index 0000000..e0325e5
--- /dev/null
+++ b/frontend/.nvmrc
@@ -0,0 +1 @@
+v16.17.1
diff --git a/docker/frontend/.prettierrc b/frontend/.prettierrc
similarity index 100%
rename from docker/frontend/.prettierrc
rename to frontend/.prettierrc
diff --git a/docker/frontend/Dockerfile b/frontend/Dockerfile
similarity index 86%
rename from docker/frontend/Dockerfile
rename to frontend/Dockerfile
index 932ad37..b7d83a6 100644
--- a/docker/frontend/Dockerfile
+++ b/frontend/Dockerfile
@@ -1,5 +1,5 @@
FROM node:16-alpine
-WORKDIR /app
+WORKDIR /usr/src/frontend
COPY ./package.json ./yarn.lock ./
RUN yarn install --frozen-lockfile
COPY . .
diff --git a/docker/frontend/README.md b/frontend/README.md
similarity index 100%
rename from docker/frontend/README.md
rename to frontend/README.md
diff --git a/docker/frontend/package.json b/frontend/package.json
similarity index 100%
rename from docker/frontend/package.json
rename to frontend/package.json
diff --git a/docker/frontend/src/app.d.ts b/frontend/src/app.d.ts
similarity index 100%
rename from docker/frontend/src/app.d.ts
rename to frontend/src/app.d.ts
diff --git a/docker/frontend/src/app.html b/frontend/src/app.html
similarity index 100%
rename from docker/frontend/src/app.html
rename to frontend/src/app.html
diff --git a/docker/frontend/src/hooks.ts b/frontend/src/hooks.ts
similarity index 100%
rename from docker/frontend/src/hooks.ts
rename to frontend/src/hooks.ts
diff --git a/docker/frontend/src/lib/cookieNames.ts b/frontend/src/lib/cookieNames.ts
similarity index 100%
rename from docker/frontend/src/lib/cookieNames.ts
rename to frontend/src/lib/cookieNames.ts
diff --git a/docker/frontend/src/lib/graphql.ts b/frontend/src/lib/graphql.ts
similarity index 80%
rename from docker/frontend/src/lib/graphql.ts
rename to frontend/src/lib/graphql.ts
index 44c5108..cb6f96b 100644
--- a/docker/frontend/src/lib/graphql.ts
+++ b/frontend/src/lib/graphql.ts
@@ -4,10 +4,6 @@ export interface Node {
id: ID;
}
-export interface Skif {
- skif: boolean;
-}
-
export enum UserRole {
STUDENT = "Student",
TEACHER = "Teacher",
@@ -30,11 +26,11 @@ export interface UserExternal {
user: User;
}
-export interface Student extends Node, UserExternal, Skif {
+export interface Student extends Node, UserExternal {
vote?: Vote;
}
-export interface Teacher extends Node, UserExternal, Skif {
+export interface Teacher extends Node, UserExternal {
maxStudents: number;
}
diff --git a/docker/frontend/src/routes/__layout.svelte b/frontend/src/routes/__layout.svelte
similarity index 100%
rename from docker/frontend/src/routes/__layout.svelte
rename to frontend/src/routes/__layout.svelte
diff --git a/docker/frontend/src/routes/index.svelte b/frontend/src/routes/index.svelte
similarity index 59%
rename from docker/frontend/src/routes/index.svelte
rename to frontend/src/routes/index.svelte
index 7bda3f4..5481e63 100644
--- a/docker/frontend/src/routes/index.svelte
+++ b/frontend/src/routes/index.svelte
@@ -47,81 +47,63 @@
`);
query(meStore);
- const teacherRegisterFormMaxStudents = svelteForms.field("maxStudents", 0, [
- validators.required(),
- validators.min(0),
- ]);
- const teacherRegisterFormSkif = svelteForms.field("skif", false);
- const teacheRegisterForm = svelteForms.form(
- teacherRegisterFormMaxStudents,
- teacherRegisterFormSkif
- );
+ // const teacherRegisterFormMaxStudents = svelteForms.field("maxStudents", 0, [
+ // validators.required(),
+ // validators.min(0),
+ // ]);
+ // const teacheRegisterForm = svelteForms.form(teacherRegisterFormMaxStudents);
- interface RegisterTeacherData {
- registerTeacher: Teacher;
- }
+ // interface RegisterTeacherData {
+ // registerTeacher: Teacher;
+ // }
- interface RegisterTeacherVars {
- maxStudents: number;
- skif: boolean;
- }
+ // interface RegisterTeacherVars {
+ // maxStudents: number;
+ // }
- const registerTeacherStore = operationStore<
- RegisterTeacherData,
- RegisterTeacherVars
- >(gql`
- mutation RegisterTeacher($maxStudents: Int!, $skif: Boolean!) {
- registerTeacher(input: { maxStudents: $maxStudents, skif: $skif }) {
- id
- }
- }
- `);
+ // const registerTeacherStore = operationStore<
+ // RegisterTeacherData,
+ // RegisterTeacherVars
+ // >(gql`
+ // mutation RegisterTeacher($maxStudents: Int!) {
+ // registerTeacher(input: { maxStudents: $maxStudents }) {
+ // id
+ // }
+ // }
+ // `);
- const registerTeacherMutation = mutation(registerTeacherStore);
+ // const registerTeacherMutation = mutation(registerTeacherStore);
- async function registerTeacher(): Promise {
- await registerTeacherMutation({
- maxStudents: $teacherRegisterFormMaxStudents.value,
- skif: $teacherRegisterFormSkif.value,
- });
+ // async function registerTeacher(): Promise {
+ // await registerTeacherMutation({
+ // maxStudents: $teacherRegisterFormMaxStudents.value,
+ // });
- if (!$registerTeacherStore.error && $registerTeacherStore.data) {
- location.reload();
- }
- }
+ // if (!$registerTeacherStore.error && $registerTeacherStore.data) {
+ // location.reload();
+ // }
+ // }
- const registerStudentSkif = svelteForms.field("skif", false);
- const registerStudentForm = svelteForms.form(registerStudentSkif);
+ // interface RegisterStudentData {
+ // registerStudent: Student;
+ // }
- interface RegisterStudentData {
- registerStudent: Student;
- }
+ // const registerStudentStore = operationStore(gql`
+ // mutation RegisterStudent() {
+ // registerStudent() {
+ // id
+ // }
+ // }
+ // `);
+ // const registerStudentMutation = mutation(registerStudentStore);
- interface RegisterStudentVars {
- skif: boolean;
- }
+ // async function registerStudent(): Promise {
+ // await registerStudentMutation();
- const registerStudentStore = operationStore<
- RegisterStudentData,
- RegisterStudentVars
- >(gql`
- mutation RegisterStudent($skif: Boolean!) {
- registerStudent(input: { skif: $skif }) {
- id
- }
- }
- `);
- const registerStudentMutation = mutation(registerStudentStore);
-
- async function registerStudent(): Promise {
- await registerStudentMutation({
- skif: $registerStudentSkif.value,
- });
-
- if (!$registerStudentStore.error && $registerStudentStore.data) {
- location.reload();
- }
- }
+ // if (!$registerStudentStore.error && $registerStudentStore.data) {
+ // location.reload();
+ // }
+ // }
{#if $meStore.error}
@@ -133,7 +115,7 @@
{#if $meStore.data.me.role === UserRole.TEACHER}
- {#if !$meStore.data.me.teacher}
+
{:else if $meStore.data.me.role === UserRole.STUDENT}
- {#if !$meStore.data.me.student}
+
{/if}
{/if}
diff --git a/docker/frontend/src/routes/login.svelte b/frontend/src/routes/login.svelte
similarity index 100%
rename from docker/frontend/src/routes/login.svelte
rename to frontend/src/routes/login.svelte
diff --git a/docker/frontend/static/.keep b/frontend/static/.keep
similarity index 100%
rename from docker/frontend/static/.keep
rename to frontend/static/.keep
diff --git a/docker/frontend/svelte.config.js b/frontend/svelte.config.js
similarity index 100%
rename from docker/frontend/svelte.config.js
rename to frontend/svelte.config.js
diff --git a/docker/frontend/tsconfig.json b/frontend/tsconfig.json
similarity index 100%
rename from docker/frontend/tsconfig.json
rename to frontend/tsconfig.json
diff --git a/docker/frontend/yarn.lock b/frontend/yarn.lock
similarity index 100%
rename from docker/frontend/yarn.lock
rename to frontend/yarn.lock