Update
This commit is contained in:
parent
ece7e8e87b
commit
a9fcab7add
17
.example.env
17
.example.env
|
@ -30,26 +30,11 @@ AUTH_UNTIS_PASSWORD=
|
|||
|
||||
# Backend
|
||||
BACKEND_MINIMUM_TEACHER_SELECTION_COUNT=6
|
||||
BACKEND_ASSIGNMENT_POSSIBILITY_COUNT=65536
|
||||
BACKEND_ASSIGNMENT_POSSIBILITY_COUNT=16777216
|
||||
BACKEND_URL=URL
|
||||
# Backend - API
|
||||
BACKEND_API_JWT_SECRET=
|
||||
BACKEND_API_JWT_EXPIRATION=360
|
||||
# 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_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
|
||||
|
|
|
@ -61,18 +61,11 @@ COPY --from=templates-html /usr/src/templates/html ./templates/html
|
|||
COPY ./src ./src
|
||||
RUN if [ "${BUILD_ENV}" = "development" ]; then \
|
||||
make dev; \
|
||||
ldd bin/backend | tr -s '[:blank:]' '\n' | grep '^/' | \
|
||||
xargs -I % sh -c 'mkdir -p $(dirname deps%); cp % deps%;'; \
|
||||
else \
|
||||
make; \
|
||||
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
|
||||
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.source="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
|
||||
COPY --from=micrate-builder /usr/src/micrate/bin/micrate ./bin/micrate
|
||||
COPY --from=builder /usr/src/mentorenwahl/db ./db
|
||||
|
|
|
@ -60,6 +60,10 @@ shards:
|
|||
git: https://git.dergrimm.net/dergrimm/compiled_license.git
|
||||
version: 2.0.0
|
||||
|
||||
cute:
|
||||
git: https://github.com/papierkorb/cute.git
|
||||
version: 0.4.0
|
||||
|
||||
db:
|
||||
git: https://github.com/crystal-lang/crystal-db.git
|
||||
version: 0.11.0
|
||||
|
@ -68,14 +72,18 @@ shards:
|
|||
git: https://github.com/repomaa/docker.cr.git
|
||||
version: 0.1.2
|
||||
|
||||
email:
|
||||
git: https://github.com/arcage/crystal-email.git
|
||||
version: 0.6.4
|
||||
|
||||
env_config:
|
||||
git: https://github.com/repomaa/env_config.cr.git
|
||||
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:
|
||||
git: https://github.com/anykeyh/generate.cr.git
|
||||
version: 0.1.0+git.commit.f5dafc934a70e0ee2f246dddf3df44686f844da2
|
||||
|
@ -100,18 +108,6 @@ shards:
|
|||
git: https://github.com/crystal-community/jwt.git
|
||||
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:
|
||||
git: https://github.com/mosquito-cr/mosquito.git
|
||||
version: 0.11.2
|
||||
|
@ -132,14 +128,6 @@ shards:
|
|||
git: https://github.com/maiha/pretty.cr.git
|
||||
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:
|
||||
git: https://github.com/stefanwille/crystal-redis.git
|
||||
version: 2.8.3
|
||||
|
|
|
@ -41,16 +41,8 @@ dependencies:
|
|||
github: mrrooijen/commander
|
||||
mosquito:
|
||||
github: mosquito-cr/mosquito
|
||||
quartz_mailer:
|
||||
github: amberframework/quartz-mailer
|
||||
kilt:
|
||||
github: jeromegn/kilt
|
||||
env_config:
|
||||
github: repomaa/env_config.cr
|
||||
ldap:
|
||||
github: spider-gazelle/crystal-ldap
|
||||
ldap_escape:
|
||||
git: https://git.dergrimm.net/dergrimm/ldap_escape.git
|
||||
shard:
|
||||
github: maiha/shard.cr
|
||||
retriable:
|
||||
|
@ -59,8 +51,6 @@ dependencies:
|
|||
git: https://git.dergrimm.net/dergrimm/service.git
|
||||
redis:
|
||||
github: stefanwille/crystal-redis
|
||||
pool:
|
||||
github: ysbaddaden/pool
|
||||
athena:
|
||||
github: athena-framework/framework
|
||||
baked_file_system:
|
||||
|
@ -76,3 +66,5 @@ dependencies:
|
|||
graphql-dataloader:
|
||||
github: graphql-crystal/dataloader
|
||||
branch: main
|
||||
fancyline:
|
||||
github: Papierkorb/fancyline
|
||||
|
|
|
@ -36,11 +36,11 @@ module Backend::Api::Errors
|
|||
end
|
||||
end
|
||||
|
||||
class LdapUserDoesNotExist < PublicError
|
||||
def api_message : String
|
||||
"LDAP user does not exist"
|
||||
end
|
||||
end
|
||||
# class LdapUserDoesNotExist < PublicError
|
||||
# def api_message : String
|
||||
# "LDAP user does not exist"
|
||||
# end
|
||||
# end
|
||||
|
||||
class DuplicateTeachers < PublicError
|
||||
def api_message : String
|
||||
|
@ -54,9 +54,15 @@ module Backend::Api::Errors
|
|||
end
|
||||
end
|
||||
|
||||
class TeachersNotRegistered < PublicError
|
||||
# class TeachersNotRegistered < PublicError
|
||||
# def api_message : String
|
||||
# "Teachers not registered"
|
||||
# end
|
||||
# end
|
||||
|
||||
class VotingNotAllowed < PublicError
|
||||
def api_message : String
|
||||
"Teachers not registered"
|
||||
"Voting not yet allowed"
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
# 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 "uuid"
|
||||
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::NotEnoughTeachers.new if input.teacher_ids.size < Backend.config.minimum_teacher_selection_count
|
||||
teacher_role_count = Db::User.query.where(role: Db::UserRole::Teacher).count
|
||||
raise Errors::TeachersNotRegistered.new if teacher_role_count != Db::Teacher.query.count || teacher_role_count.zero?
|
||||
# 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?
|
||||
|
||||
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|
|
||||
teacher = Db::Teacher.find(id)
|
||||
|
@ -226,7 +228,7 @@ module Backend
|
|||
config.can_vote = state
|
||||
config.save!
|
||||
|
||||
state
|
||||
true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,124 +1,9 @@
|
|||
require "commander"
|
||||
require "tallboy"
|
||||
require "wannabe_bool"
|
||||
require "csv"
|
||||
require "fancyline"
|
||||
|
||||
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|
|
||||
cmd.use = "backend"
|
||||
cmd.short = "Mentorenwahl backend CLI"
|
||||
|
@ -365,118 +250,5 @@ module Backend
|
|||
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
|
||||
|
|
|
@ -68,10 +68,6 @@ module Backend
|
|||
# Configuration for `Api`
|
||||
getter api : ApiConfig
|
||||
|
||||
@[EnvConfig::Setting(key: "smtp")]
|
||||
# Configuration for `Mailers`
|
||||
getter smtp : SmtpConfig
|
||||
|
||||
@[EnvConfig::Setting(key: "db")]
|
||||
# Configuration for `Db`
|
||||
getter db : DbConfig
|
||||
|
@ -79,10 +75,6 @@ module Backend
|
|||
@[EnvConfig::Setting(key: "redis")]
|
||||
getter redis : RedisConfig
|
||||
|
||||
@[EnvConfig::Setting(key: "ldap")]
|
||||
# Configuration for `Ldap`
|
||||
getter ldap : LdapConfig
|
||||
|
||||
@[EnvConfig::Setting(key: "auth")]
|
||||
# Configuration for authorization provider
|
||||
getter auth : AuthConfig
|
||||
|
@ -103,31 +95,6 @@ module Backend
|
|||
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`
|
||||
class DbConfig
|
||||
include EnvConfig
|
||||
|
@ -155,34 +122,6 @@ module Backend
|
|||
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
|
||||
class AuthConfig
|
||||
include EnvConfig
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -154,6 +154,8 @@
|
|||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
<%- else -%>
|
||||
<td class="border-padded" />
|
||||
<%- end -%>
|
||||
<%- end -%>
|
||||
</tr>
|
||||
|
|
|
@ -23,6 +23,8 @@ http {
|
|||
more_clear_headers Server;
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
|
||||
location / {
|
||||
proxy_pass http://frontend/;
|
||||
}
|
||||
|
@ -30,6 +32,10 @@ http {
|
|||
location /graphql {
|
||||
proxy_pass http://backend/;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 8080;
|
||||
|
||||
location /adminer {
|
||||
proxy_pass http://adminer:8080/;
|
||||
|
|
|
@ -18,12 +18,13 @@ version: "3"
|
|||
|
||||
services:
|
||||
nginx:
|
||||
image: docker.io/byjg/nginx-extras
|
||||
image: docker.io/openresty/openresty:1.21.4.1-0-alpine
|
||||
restart: always
|
||||
volumes:
|
||||
- ./config/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
|
||||
- ./config/nginx/nginx.conf:/usr/local/openresty/nginx/conf/nginx.conf:ro
|
||||
ports:
|
||||
- 80:80
|
||||
- 8080:8080
|
||||
depends_on:
|
||||
- adminer
|
||||
- backend
|
||||
|
@ -82,23 +83,10 @@ services:
|
|||
BACKEND_ASSIGNMENT_POSSIBILITY_COUNT: ${BACKEND_ASSIGNMENT_POSSIBILITY_COUNT}
|
||||
BACKEND_API_JWT_SECRET: ${BACKEND_API_JWT_SECRET}
|
||||
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_ALLOW_OLD_SCHEMA: ${BACKEND_DB_ALLOW_OLD_SCHEMA}
|
||||
BACKEND_REDIS_HOST: redis
|
||||
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"
|
||||
volumes:
|
||||
- /etc/timezone:/etc/timezone:ro
|
||||
|
|
|
@ -47,11 +47,11 @@ WORKDIR /usr/src/public
|
|||
COPY --from=public /usr/src/public .
|
||||
RUN find . -name "*.wasm" -type f | xargs -I % wasm-opt % -o % -O --intrinsic-lowering -Oz
|
||||
|
||||
FROM 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>" \
|
||||
org.opencontainers.image.description="Frontend of Mentorenwahl" \
|
||||
org.opencontainers.image.licenses="GPL-3.0" \
|
||||
org.opencontainers.image.source="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
|
||||
|
|
|
@ -3,7 +3,7 @@ events {
|
|||
}
|
||||
|
||||
http {
|
||||
include /etc/nginx/mime.types;
|
||||
include mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
sendfile on;
|
||||
|
|
|
@ -168,7 +168,8 @@ impl Component for StudentVote {
|
|||
)
|
||||
.await
|
||||
.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))
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue