This commit is contained in:
Dominic Grimm 2023-02-25 14:38:14 +01:00
parent ece7e8e87b
commit a9fcab7add
No known key found for this signature in database
GPG key ID: 6F294212DEAAC530
20 changed files with 53 additions and 619 deletions

View file

@ -30,26 +30,11 @@ AUTH_UNTIS_PASSWORD=
# Backend # Backend
BACKEND_MINIMUM_TEACHER_SELECTION_COUNT=6 BACKEND_MINIMUM_TEACHER_SELECTION_COUNT=6
BACKEND_ASSIGNMENT_POSSIBILITY_COUNT=65536 BACKEND_ASSIGNMENT_POSSIBILITY_COUNT=16777216
BACKEND_URL=URL BACKEND_URL=URL
# Backend - API # Backend - API
BACKEND_API_JWT_SECRET= BACKEND_API_JWT_SECRET=
BACKEND_API_JWT_EXPIRATION=360 BACKEND_API_JWT_EXPIRATION=360
# Backend - Worker # Backend - Worker
# Backend - SMTP
BACKEND_SMTP_HELO=
BACKEND_SMTP_HOST=
BACKEND_SMTP_PORT=587
BACKEND_SMTP_NAME=
BACKEND_SMTP_USERNAME=
BACKEND_SMTP_PASSWORD=
# Backend - Db # Backend - Db
BACKEND_DB_ALLOW_OLD_SCHEMA=false BACKEND_DB_ALLOW_OLD_SCHEMA=false
# Backend - LDAP
BACKEND_LDAP_HOST="ldap.example.com"
BACKEND_LDAP_PORT=389
BACKEND_LDAP_BASE_DN="dc=ldap,dc=example,dc=com"
BACKEND_LDAP_BASE_USER_DN="ou=users,dc=ldap,dc=example,dc=com"
BACKEND_LDAP_BIND_DN="cn=admin,dc=ldap,dc=example,dc=com"
BACKEND_LDAP_BIND_PASSWORD=
BACKEND_LDAP_CACHE_REFRESH_INTERVAL=720

View file

@ -61,18 +61,11 @@ COPY --from=templates-html /usr/src/templates/html ./templates/html
COPY ./src ./src COPY ./src ./src
RUN if [ "${BUILD_ENV}" = "development" ]; then \ RUN if [ "${BUILD_ENV}" = "development" ]; then \
make dev; \ make dev; \
ldd bin/backend | tr -s '[:blank:]' '\n' | grep '^/' | \
xargs -I % sh -c 'mkdir -p $(dirname deps%); cp % deps%;'; \
else \ else \
make; \ make; \
fi fi
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 alpine as config
# WORKDIR /usr/src/config
# RUN mkdir ./tmp
# RUN chmod -R 1777 ./tmp
FROM busybox as runner FROM busybox as runner
LABEL maintainer="Dominic Grimm <dominic@dergrimm.net>" \ LABEL maintainer="Dominic Grimm <dominic@dergrimm.net>" \
@ -80,7 +73,6 @@ LABEL maintainer="Dominic Grimm <dominic@dergrimm.net>" \
org.opencontainers.image.licenses="GPL-3.0" \ org.opencontainers.image.licenses="GPL-3.0" \
org.opencontainers.image.source="https://git.dergrimm.net/mentorenwahl/mentorenwahl" \ org.opencontainers.image.source="https://git.dergrimm.net/mentorenwahl/mentorenwahl" \
org.opencontainers.image.url="https://git.dergrimm.net/mentorenwahl/mentorenwahl" org.opencontainers.image.url="https://git.dergrimm.net/mentorenwahl/mentorenwahl"
# COPY --from=config /usr/src/config/tmp /tmp
WORKDIR /usr/src/mentorenwahl WORKDIR /usr/src/mentorenwahl
COPY --from=micrate-builder /usr/src/micrate/bin/micrate ./bin/micrate COPY --from=micrate-builder /usr/src/micrate/bin/micrate ./bin/micrate
COPY --from=builder /usr/src/mentorenwahl/db ./db COPY --from=builder /usr/src/mentorenwahl/db ./db

View file

@ -60,6 +60,10 @@ shards:
git: https://git.dergrimm.net/dergrimm/compiled_license.git git: https://git.dergrimm.net/dergrimm/compiled_license.git
version: 2.0.0 version: 2.0.0
cute:
git: https://github.com/papierkorb/cute.git
version: 0.4.0
db: db:
git: https://github.com/crystal-lang/crystal-db.git git: https://github.com/crystal-lang/crystal-db.git
version: 0.11.0 version: 0.11.0
@ -68,14 +72,18 @@ shards:
git: https://github.com/repomaa/docker.cr.git git: https://github.com/repomaa/docker.cr.git
version: 0.1.2 version: 0.1.2
email:
git: https://github.com/arcage/crystal-email.git
version: 0.6.4
env_config: env_config:
git: https://github.com/repomaa/env_config.cr.git git: https://github.com/repomaa/env_config.cr.git
version: 0.1.0+git.commit.a3ef5b955f27e2c65de2fe0ff41718e2eea7c06f version: 0.1.0+git.commit.a3ef5b955f27e2c65de2fe0ff41718e2eea7c06f
fancyline:
git: https://github.com/papierkorb/fancyline.git
version: 0.4.1
future:
git: https://github.com/crystal-community/future.cr.git
version: 1.0.0
generate: generate:
git: https://github.com/anykeyh/generate.cr.git git: https://github.com/anykeyh/generate.cr.git
version: 0.1.0+git.commit.f5dafc934a70e0ee2f246dddf3df44686f844da2 version: 0.1.0+git.commit.f5dafc934a70e0ee2f246dddf3df44686f844da2
@ -100,18 +108,6 @@ shards:
git: https://github.com/crystal-community/jwt.git git: https://github.com/crystal-community/jwt.git
version: 1.6.0 version: 1.6.0
kilt:
git: https://github.com/jeromegn/kilt.git
version: 0.6.1
ldap:
git: https://github.com/spider-gazelle/crystal-ldap.git
version: 0.9.1
ldap_escape:
git: https://git.dergrimm.net/dergrimm/ldap_escape.git
version: 0.1.0
mosquito: mosquito:
git: https://github.com/mosquito-cr/mosquito.git git: https://github.com/mosquito-cr/mosquito.git
version: 0.11.2 version: 0.11.2
@ -132,14 +128,6 @@ shards:
git: https://github.com/maiha/pretty.cr.git git: https://github.com/maiha/pretty.cr.git
version: 1.1.2 version: 1.1.2
promise:
git: https://github.com/spider-gazelle/promise.git
version: 3.0.0
quartz_mailer:
git: https://github.com/amberframework/quartz-mailer.git
version: 0.8.0
redis: redis:
git: https://github.com/stefanwille/crystal-redis.git git: https://github.com/stefanwille/crystal-redis.git
version: 2.8.3 version: 2.8.3

View file

@ -41,16 +41,8 @@ dependencies:
github: mrrooijen/commander github: mrrooijen/commander
mosquito: mosquito:
github: mosquito-cr/mosquito github: mosquito-cr/mosquito
quartz_mailer:
github: amberframework/quartz-mailer
kilt:
github: jeromegn/kilt
env_config: env_config:
github: repomaa/env_config.cr github: repomaa/env_config.cr
ldap:
github: spider-gazelle/crystal-ldap
ldap_escape:
git: https://git.dergrimm.net/dergrimm/ldap_escape.git
shard: shard:
github: maiha/shard.cr github: maiha/shard.cr
retriable: retriable:
@ -59,8 +51,6 @@ dependencies:
git: https://git.dergrimm.net/dergrimm/service.git git: https://git.dergrimm.net/dergrimm/service.git
redis: redis:
github: stefanwille/crystal-redis github: stefanwille/crystal-redis
pool:
github: ysbaddaden/pool
athena: athena:
github: athena-framework/framework github: athena-framework/framework
baked_file_system: baked_file_system:
@ -76,3 +66,5 @@ dependencies:
graphql-dataloader: graphql-dataloader:
github: graphql-crystal/dataloader github: graphql-crystal/dataloader
branch: main branch: main
fancyline:
github: Papierkorb/fancyline

View file

@ -36,11 +36,11 @@ module Backend::Api::Errors
end end
end end
class LdapUserDoesNotExist < PublicError # class LdapUserDoesNotExist < PublicError
def api_message : String # def api_message : String
"LDAP user does not exist" # "LDAP user does not exist"
end # end
end # end
class DuplicateTeachers < PublicError class DuplicateTeachers < PublicError
def api_message : String def api_message : String
@ -54,9 +54,15 @@ module Backend::Api::Errors
end end
end end
class TeachersNotRegistered < PublicError # class TeachersNotRegistered < PublicError
# def api_message : String
# "Teachers not registered"
# end
# end
class VotingNotAllowed < PublicError
def api_message : String def api_message : String
"Teachers not registered" "Voting not yet allowed"
end end
end end

View file

@ -14,7 +14,6 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>. # along with this program. If not, see <https://www.gnu.org/licenses/>.
require "ldap"
require "uuid" require "uuid"
require "uuid/json" require "uuid/json"
@ -191,8 +190,11 @@ module Backend
raise Errors::DuplicateTeachers.new if input.teacher_ids.uniq.size != input.teacher_ids.size raise Errors::DuplicateTeachers.new if input.teacher_ids.uniq.size != input.teacher_ids.size
raise Errors::NotEnoughTeachers.new 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 Errors::TeachersNotRegistered.new 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?
pp! config = Db::Config.query.select(:can_vote).where { active }.first!
raise Errors::VotingNotAllowed.new unless Db::Config.query.select(:can_vote).where { active }.first!.can_vote
input.teacher_ids.each do |id| input.teacher_ids.each do |id|
teacher = Db::Teacher.find(id) teacher = Db::Teacher.find(id)
@ -226,7 +228,7 @@ module Backend
config.can_vote = state config.can_vote = state
config.save! config.save!
state true
end end
end end
end end

View file

@ -1,124 +1,9 @@
require "commander" require "commander"
require "tallboy" require "tallboy"
require "wannabe_bool" require "wannabe_bool"
require "csv" require "fancyline"
module Backend module Backend
alias TeacherVote = {student: Int32, priority: Int32}
alias Assignment = {teacher: Int32, priority: Int32}
def do_assignment(roll_count : UInt32) : Time::Span
teachers = Db::Teacher.query
.where do
raw("EXISTS (SELECT 1 FROM teacher_votes WHERE teacher_id = teachers.id)") &
(max_students > 0)
end
.with_teacher_votes
.to_a
teacher_ids = teachers.map(&.id)
teacher_max_students = Hash.zip(teacher_ids, teachers.map(&.max_students))
teacher_votes : Hash(Int32, Array(TeacherVote)) = Hash.zip(
teacher_ids,
teachers.map do |t|
t.teacher_votes.map do |tv|
{
student: tv.vote.student.id,
priority: tv.priority,
}
end
end
)
students = Db::Student.query
.with_vote(&.with_teacher_votes(&.order_by(priority: :desc)))
.to_a
student_ids = students.map(&.id)
votes = Hash.zip(
student_ids,
students.map do |s|
s.vote.not_nil!.teacher_votes
.to_a
.select! { |tv| teacher_votes.has_key?(tv.teacher.id) }
.map do |tv|
{
teacher: tv.teacher.id,
priority: tv.priority,
}
end
end
)
votes_a = votes.to_a
t1 = Time.utc
best : {assignment: Hash(Int32, Assignment), priority_score: Int64, teacher_score: Int64}? = nil
empty_assignment_count = Hash.zip(teachers.map(&.id), [0] * teachers.size)
roll_count.times.each do |i|
p! i
assignment = {} of Int32 => Assignment
assignment_count = empty_assignment_count.clone
votes_a.shuffle(Random::Secure).each do |s, tvs|
tvs.each do |tv|
if assignment_count[tv[:teacher]] < teacher_max_students[tv[:teacher]]
if assignment[s]?.nil?
assignment_count[tv[:teacher]] += 1
assignment[s] = {teacher: tv[:teacher], priority: tv[:priority]}
else
assignment_count[assignment[s][:teacher]] -= 1
assignment_count[tv[:teacher]] += 1
assignment[s] = {teacher: tv[:teacher], priority: tv[:priority]}
end
end
end
end
priority_score = 0_i64
assignment.each do |_, a|
priority_score += a[:priority] ** 2
end
teacher_score = 0_i64
assignment_count.each do |t, c|
teacher_score += (teacher_max_students[t] ** c) * teacher_max_students[t]
end
if best.nil?
best = {
assignment: assignment,
priority_score: priority_score,
teacher_score: teacher_score,
}
elsif priority_score < best.not_nil![:priority_score] && teacher_score < best.not_nil![:teacher_score]
best = {
assignment: assignment,
priority_score: priority_score,
teacher_score: teacher_score,
}
end
end
t2 = Time.utc
pp! best
# Db::Assignment.import(best.not_nil![:assignment].map { |s, a| Db::Assignment.new({student_id: s, teacher_id: a[:teacher]}) })
Db::Assignment.query.where { active }.to_update.set(active: false).execute
assignment_id = Db::Assignment.create!({
active: true,
priority_score: best.not_nil![:priority_score],
teacher_score: best.not_nil![:teacher_score],
}).id
Db::StudentAssignment.import(
best.not_nil![:assignment].map do |s, a|
Db::StudentAssignment.new({
assignment_id: assignment_id,
student_id: s,
teacher_id: a[:teacher],
})
end
)
t2 - t1
end
CLI = Commander::Command.new do |cmd| CLI = Commander::Command.new do |cmd|
cmd.use = "backend" cmd.use = "backend"
cmd.short = "Mentorenwahl backend CLI" cmd.short = "Mentorenwahl backend CLI"
@ -365,118 +250,5 @@ module Backend
end end
end end
end end
# ameba:disable Lint/ShadowingOuterLocalVar
cmd.commands.add do |cmd|
cmd.use = "stats"
cmd.short = "Runs tests and outputs statistics for analysis"
cmd.long = cmd.short
cmd.run do
result = CSV.build do |csv|
csv.row "roll_count", "students", "time", "priority_score", "teacher_score"
(2..100).each do |i|
p! i
p! student_count = 0...i * 10
p! teacher_count = 0...i * 5
Db::User.import(
student_count.map do |x|
Db::User.new({
username: "student#{x}",
password_hash: "$2a$12$eEkFG9OAfaAgwPSHTIVfMedH9VIijpRIz1jddkxuJnbe5zwfVIQ6y",
initial_password: "12345",
password_changed: false,
first_name: "Student#{x}",
last_name: "Student#{x}",
role: Db::UserRole::Student,
admin: false,
})
end.concat(
teacher_count.map do |x|
Db::User.new({
username: "teacher#{x}",
password_hash: "$2a$12$eEkFG9OAfaAgwPSHTIVfMedH9VIijpRIz1jddkxuJnbe5zwfVIQ6y",
initial_password: "12345",
password_changed: false,
first_name: "Teacher#{x}",
last_name: "Teacher#{x}",
role: Db::UserRole::Teacher,
admin: false,
})
end
)
)
class_id = Db::Class.create!({name: "Default class"}).id
student_user_ids = Db::User.query.select(:id).where { role == Db::UserRole::Student }.map(&.id)
Db::Student.import(
student_user_ids.map do |id|
Db::Student.new({
user_id: id,
class_id: class_id,
})
end
)
student_ids = Db::Student.query.select(:id).map(&.id)
teacher_user_ids = Db::User.query.select(:id).where { role == Db::UserRole::Teacher }.map(&.id)
Db::Teacher.import(
teacher_user_ids.map do |id|
Db::Teacher.new({
user_id: id,
max_students: id % 5,
})
end
)
teacher_ids = Db::Teacher.query.select(:id).map(&.id)
Db::Vote.import(
student_ids.map do |id|
Db::Vote.new({student_id: id})
end
)
vote_ids = Db::Vote.query.select(:id).map(&.id)
Db::TeacherVote.import(
vote_ids.flat_map do |id|
size = 6 + id % 4
tvs = [] of Int32
while tvs.size < size
t_id = teacher_ids.sample
tvs << t_id unless t_id.in?(tvs)
end
tvs.map_with_index do |t, j|
Db::TeacherVote.new({
vote_id: id,
teacher_id: t,
priority: j,
})
end
end
)
dt = do_assignment(Backend.config.assignment_possibility_count)
a = Db::Assignment.query.where { active }.first!
csv.row Backend.config.assignment_possibility_count, student_ids.size, dt.seconds, a.priority_score, a.teacher_score
puts csv
Db::StudentAssignment.query.to_delete.execute
Db::Assignment.query.to_delete.execute
Db::TeacherVote.query.to_delete.execute
Db::Vote.query.to_delete.execute
Db::Teacher.query.to_delete.execute
Db::Student.query.to_delete.execute
Db::Class.query.to_delete.execute
Db::User.query.to_delete.execute
end
end
puts result
File.write("/static/stats.csv", result.to_s)
end
end
end end
end end

View file

@ -68,10 +68,6 @@ module Backend
# Configuration for `Api` # Configuration for `Api`
getter api : ApiConfig getter api : ApiConfig
@[EnvConfig::Setting(key: "smtp")]
# Configuration for `Mailers`
getter smtp : SmtpConfig
@[EnvConfig::Setting(key: "db")] @[EnvConfig::Setting(key: "db")]
# Configuration for `Db` # Configuration for `Db`
getter db : DbConfig getter db : DbConfig
@ -79,10 +75,6 @@ module Backend
@[EnvConfig::Setting(key: "redis")] @[EnvConfig::Setting(key: "redis")]
getter redis : RedisConfig getter redis : RedisConfig
@[EnvConfig::Setting(key: "ldap")]
# Configuration for `Ldap`
getter ldap : LdapConfig
@[EnvConfig::Setting(key: "auth")] @[EnvConfig::Setting(key: "auth")]
# Configuration for authorization provider # Configuration for authorization provider
getter auth : AuthConfig getter auth : AuthConfig
@ -103,31 +95,6 @@ module Backend
end end
end end
# Configuration for `Mailers`
class SmtpConfig
include EnvConfig
# SMTP host HELO
#
# NOTE: HELOs are [FQDNs](https://en.wikipedia.org/wiki/Fully_qualified_domain_name), so this should be a domain name
getter helo : String
# SMTP hostname
getter host : String
# SMTP port
getter port : Int32
# Name to send from
getter name : String
# SMTP username
getter username : String
# SMTP password
getter password : String
end
# Configuration for `Db` # Configuration for `Db`
class DbConfig class DbConfig
include EnvConfig include EnvConfig
@ -155,34 +122,6 @@ module Backend
end end
end end
# Configuration for `Ldap`
class LdapConfig
include EnvConfig
# LDAP hostname
getter host : String
# LDAP port
getter port : Int32
# LDAP base DN
getter base_dn : String
# LDAP user base DN
getter base_user_dn : String
# LDAP bind DN
#
# NOTE: This is the DN to search with
getter bind_dn : String
# LDAP bind password
getter bind_password : String
# Periodical cache refresh interval
getter cache_refresh_interval : Int32
end
# Configuration for authoriuation API # Configuration for authoriuation API
class AuthConfig class AuthConfig
include EnvConfig include EnvConfig

View file

@ -1,43 +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 <https://www.gnu.org/licenses/>.
require "ldap"
require "socket"
require "ldap_escape"
require "pool/connection"
require "./ldap/*"
module Backend
# Provides LDAP utility functions
module Ldap
extend self
CLIENT = ConnectionPool.new(capacity: 25) { create_client }
# Creates a new LDAP connection
private def create_client : LDAP::Client
LDAP::Client.new(TCPSocket.new(Backend.config.ldap.host, Backend.config.ldap.port))
end
# Checks if credentials are valid
def authenticate?(dn : String, password : String) : Bool
!!create_client.authenticate(dn, password)
rescue LDAP::Client::AuthError
false
end
end
end

View file

@ -1,34 +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 <https://www.gnu.org/licenses/>.
module Backend
module Ldap
# DN construction utilities
module DN
extend self
# Constructs a CN DN from a username
def cn(username : String) : String
"cn=#{LdapEscape.dn(username)},#{Backend.config.ldap.user_dn}"
end
# Constructs a UID DN from a username
def uid(uid : String) : String
"uid=#{LdapEscape.dn(uid)},#{Backend.config.ldap.base_user_dn}"
end
end
end
end

View file

@ -1,86 +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 <https://www.gnu.org/licenses/>.
require "json"
module Backend
module Ldap
# LDAP user properties
struct User
include JSON::Serializable
alias Raw = Hash(String, Array(String))
# First name
getter first_name : String
# Last name
getter last_name : String
@[JSON::Field(key: "mail")]
# Email address
getter email : String
def initialize(@first_name : String, @last_name : String, @email : String)
end
# 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
def self.from_raw(raw : Raw) : self
self.new(
first_name: raw["givenName"].first,
last_name: raw["sn"].first,
email: raw["mail"].first
)
end
# Creates user data from LDAP DN entry
def self.from_dn(dn : String) : self
from_raw(
CLIENT.connection do |client|
client
.authenticate(Backend.config.ldap.bind_dn, Backend.config.ldap.bind_password)
.search(base: dn, attributes: %w(givenName sn mail))
.first
end
)
end
# Creates user data from LDAP username
def self.from_username(username : String) : self
from_dn(Ldap::DN.uid(username))
end
# Creates user data from DB entry
def self.from_db(user : Db::User) : self
from_username(user.username)
end
# Creates user data from DB entry index
def self.from_id(id : Int32) : self
from_username(Db::User.query.first(id).select(:id))
end
end
end
end

View file

@ -1,36 +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 <https://www.gnu.org/licenses/>.
require "quartz_mailer"
require "kilt"
require "./mailers/*"
module Backend
# Mailer definitions
module Mailers
Quartz.config do |config|
config.smtp_enabled = true
config.smtp_address = Backend.config.smtp.host
config.smtp_port = Backend.config.smtp.port
config.helo_domain = Backend.config.smtp.helo
config.use_tls = EMail::Client::TLSMode::STARTTLS
config.username = Backend.config.smtp.username
config.password = Backend.config.smtp.password
config.use_authentication = true
end
end
end

View file

@ -1,40 +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 <https://www.gnu.org/licenses/>.
module Backend
module Worker
module Jobs
# Caches user data in redis cache
class CacheLdapUserJob < Mosquito::QueuedJob
params id : Int32
# :ditto:
def perform : Nil
key = "ldap:user:#{id}"
user = Db::User.find!(id)
if user
log "Caching user ##{id}..."
ldap_user = Ldap::User.from_username(user.username)
Redis::CLIENT.set(key, ldap_user.to_json, ex: (Time.utc + Backend.config.ldap.cache_refresh_interval.minutes).to_unix)
else
log "User ##{id} not found. Deleting cache..."
Redis::CLIENT.del(key)
end
end
end
end
end
end

View file

@ -154,6 +154,8 @@
</tr> </tr>
</table> </table>
</td> </td>
<%- else -%>
<td class="border-padded" />
<%- end -%> <%- end -%>
<%- end -%> <%- end -%>
</tr> </tr>

View file

@ -23,6 +23,8 @@ http {
more_clear_headers Server; more_clear_headers Server;
server { server {
listen 80;
location / { location / {
proxy_pass http://frontend/; proxy_pass http://frontend/;
} }
@ -30,6 +32,10 @@ http {
location /graphql { location /graphql {
proxy_pass http://backend/; proxy_pass http://backend/;
} }
}
server {
listen 8080;
location /adminer { location /adminer {
proxy_pass http://adminer:8080/; proxy_pass http://adminer:8080/;

View file

@ -18,12 +18,13 @@ version: "3"
services: services:
nginx: nginx:
image: docker.io/byjg/nginx-extras image: docker.io/openresty/openresty:1.21.4.1-0-alpine
restart: always restart: always
volumes: volumes:
- ./config/nginx/nginx.conf:/etc/nginx/nginx.conf:ro - ./config/nginx/nginx.conf:/usr/local/openresty/nginx/conf/nginx.conf:ro
ports: ports:
- 80:80 - 80:80
- 8080:8080
depends_on: depends_on:
- adminer - adminer
- backend - backend
@ -82,23 +83,10 @@ services:
BACKEND_ASSIGNMENT_POSSIBILITY_COUNT: ${BACKEND_ASSIGNMENT_POSSIBILITY_COUNT} BACKEND_ASSIGNMENT_POSSIBILITY_COUNT: ${BACKEND_ASSIGNMENT_POSSIBILITY_COUNT}
BACKEND_API_JWT_SECRET: ${BACKEND_API_JWT_SECRET} BACKEND_API_JWT_SECRET: ${BACKEND_API_JWT_SECRET}
BACKEND_API_JWT_EXPIRATION: ${BACKEND_API_JWT_EXPIRATION} BACKEND_API_JWT_EXPIRATION: ${BACKEND_API_JWT_EXPIRATION}
BACKEND_SMTP_HELO: ${BACKEND_SMTP_HELO}
BACKEND_SMTP_HOST: ${BACKEND_SMTP_HOST}
BACKEND_SMTP_PORT: ${BACKEND_SMTP_PORT}
BACKEND_SMTP_NAME: ${BACKEND_SMTP_NAME}
BACKEND_SMTP_USERNAME: ${BACKEND_SMTP_USERNAME}
BACKEND_SMTP_PASSWORD: ${BACKEND_SMTP_PASSWORD}
BACKEND_DB_URL: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_USER} BACKEND_DB_URL: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_USER}
BACKEND_DB_ALLOW_OLD_SCHEMA: ${BACKEND_DB_ALLOW_OLD_SCHEMA} BACKEND_DB_ALLOW_OLD_SCHEMA: ${BACKEND_DB_ALLOW_OLD_SCHEMA}
BACKEND_REDIS_HOST: redis BACKEND_REDIS_HOST: redis
BACKEND_REDIS_PORT: 6379 BACKEND_REDIS_PORT: 6379
BACKEND_LDAP_HOST: ${BACKEND_LDAP_HOST}
BACKEND_LDAP_PORT: ${BACKEND_LDAP_PORT}
BACKEND_LDAP_BASE_DN: ${BACKEND_LDAP_BASE_DN}
BACKEND_LDAP_BASE_USER_DN: ${BACKEND_LDAP_BASE_USER_DN}
BACKEND_LDAP_BIND_DN: ${BACKEND_LDAP_BIND_DN}
BACKEND_LDAP_BIND_PASSWORD: ${BACKEND_LDAP_BIND_PASSWORD}
BACKEND_LDAP_CACHE_REFRESH_INTERVAL: ${BACKEND_LDAP_CACHE_REFRESH_INTERVAL}
BACKEND_AUTH_URL: "http://auth/v1" BACKEND_AUTH_URL: "http://auth/v1"
volumes: volumes:
- /etc/timezone:/etc/timezone:ro - /etc/timezone:/etc/timezone:ro

View file

@ -47,11 +47,11 @@ WORKDIR /usr/src/public
COPY --from=public /usr/src/public . COPY --from=public /usr/src/public .
RUN find . -name "*.wasm" -type f | xargs -I % wasm-opt % -o % -O --intrinsic-lowering -Oz RUN find . -name "*.wasm" -type f | xargs -I % wasm-opt % -o % -O --intrinsic-lowering -Oz
FROM byjg/nginx-extras as runner FROM docker.io/openresty/openresty:1.21.4.1-0-alpine as runner
LABEL maintainer="Dominic Grimm <dominic@dergrimm.net>" \ LABEL maintainer="Dominic Grimm <dominic@dergrimm.net>" \
org.opencontainers.image.description="Frontend of Mentorenwahl" \ org.opencontainers.image.description="Frontend of Mentorenwahl" \
org.opencontainers.image.licenses="GPL-3.0" \ org.opencontainers.image.licenses="GPL-3.0" \
org.opencontainers.image.source="https://git.dergrimm.net/mentorenwahl/mentorenwahl" \ org.opencontainers.image.source="https://git.dergrimm.net/mentorenwahl/mentorenwahl" \
org.opencontainers.image.url="https://git.dergrimm.net/mentorenwahl/mentorenwahl" org.opencontainers.image.url="https://git.dergrimm.net/mentorenwahl/mentorenwahl"
COPY ./nginx.conf /etc/nginx/nginx.conf COPY ./nginx.conf /usr/local/openresty/nginx/conf/nginx.conf
COPY --from=binaryen /usr/src/public /var/www/html COPY --from=binaryen /usr/src/public /var/www/html

View file

@ -3,7 +3,7 @@ events {
} }
http { http {
include /etc/nginx/mime.types; include mime.types;
default_type application/octet-stream; default_type application/octet-stream;
sendfile on; sendfile on;

View file

@ -168,7 +168,8 @@ impl Component for StudentVote {
) )
.await .await
.unwrap(); .unwrap();
log::debug!("{:?}", response.data.unwrap().create_vote.unwrap()); // log::debug!("{:?}", response.data.unwrap().create_vote.unwrap());
log::debug!("{:?}", response);
Msg::Vote(graphql::convert(response.errors)) Msg::Vote(graphql::convert(response.errors))
}); });