Added teacher registration mailer
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing

This commit is contained in:
Dominic Grimm 2022-01-28 18:30:20 +01:00
parent fb5eb47f5b
commit 0f6be6c7b6
21 changed files with 203 additions and 70 deletions

View file

@ -1,4 +1,12 @@
POSTGRES_USER="mw"
POSTGRES_PASSWORD="SUPERDUPERSECRET"
POSTGRES_PASSWORD=
API_JWT_SECRET="SUPERDUPERSECRET"
URL=
BACKEND_JWT_SECRET=
BACKEND_SMTP_HELO=
BACKEND_SMTP_ADDRESS=
BACKEND_SMTP_PORT=
BACKEND_SMTP_NAME=
BACKEND_SMTP_USERNAME=
BACKEND_SMTP_PASSWORD=

View file

@ -54,9 +54,16 @@ services:
- db
- redis
environment:
URL: ${URL}
BACKEND_DATABASE_URL: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_USER}
BACKEND_JWT_SECRET: ${API_JWT_SECRET}
BACKEND_JWT_SECRET: ${BACKEND_JWT_SECRET}
BACKEND_WORKER_REDIS_URL: redis://redis:6379
BACKEND_SMTP_HELO: ${BACKEND_SMTP_HELO}
BACKEND_SMTP_ADDRESS: ${BACKEND_SMTP_ADDRESS}
BACKEND_SMTP_PORT: ${BACKEND_SMTP_PORT}
BACKEND_SMTP_NAME: ${BACKEND_SMTP_NAME}
BACKEND_SMTP_USERNAME: ${BACKEND_SMTP_USERNAME}
BACKEND_SMTP_PASSWORD: ${BACKEND_SMTP_PASSWORD}
depends_on:
- db
- redis

View file

@ -11,20 +11,20 @@ COPY --from=deps /app/shard.yml /app/shard.lock ./
COPY --from=deps /app/lib ./lib
COPY ./src ./src
RUN if [ "${BUILD_ENV}" = "development" ]; then \
shards build --static --verbose -s -p -t; \
time shards build --static --verbose -s -p -t; \
else \
shards build --static --release --no-debug --verbose -s -p -t; \
time shards build --static --release --no-debug --verbose -s -p -t; \
fi
FROM ubuntu:latest as user
RUN useradd -u 10001 backend
FROM scratch as runner
FROM alpine as runner
WORKDIR /app
COPY --from=user /etc/passwd /etc/passwd
COPY --from=builder /app/bin /bin
COPY --from=builder /app/bin ./bin
COPY ./db ./db
USER backend
EXPOSE 80
ENTRYPOINT [ "backend" ]
ENTRYPOINT [ "./bin/backend" ]
CMD [ "run" ]

View file

@ -24,6 +24,10 @@ shards:
git: https://github.com/crystal-lang/crystal-db.git
version: 0.10.1
email:
git: https://github.com/arcage/crystal-email.git
version: 0.6.3
fancyline:
git: https://github.com/papierkorb/fancyline.git
version: 0.4.1
@ -48,13 +52,17 @@ 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
micrate:
git: https://github.com/juanedi/micrate.git
version: 0.12.0
mosquito:
git: https://github.com/mosquito-cr/mosquito.git
version: 0.11.1
version: 0.11.2
openssl_ext:
git: https://github.com/spider-gazelle/openssl_ext.git
@ -62,12 +70,16 @@ shards:
pg:
git: https://github.com/will/crystal-pg.git
version: 0.24.0
version: 0.25.0
pool:
git: https://github.com/ysbaddaden/pool.git
version: 0.2.4
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
@ -88,3 +100,7 @@ shards:
git: https://github.com/soveran/toro.git
version: 0.4.3
version_from_shard:
git: https://github.com/hugopl/version_from_shard.git
version: 1.2.5

View file

@ -43,3 +43,11 @@ dependencies:
github: mosquito-cr/mosquito
secrets-env:
github: spider-gazelle/secrets-env
quartz_mailer:
github: amberframework/quartz-mailer
version_from_shard:
github: hugopl/version_from_shard
kilt:
github: jeromegn/kilt
email:
github: arcage/crystal-email

View file

@ -19,12 +19,7 @@ module Backend
end
private def create_jwt(data, expiration : Int) : String
payload = {
"data" => data.to_h,
"exp" => expiration,
}
JWT.encode(payload.to_h, SAFE_ENV["BACKEND_JWT_SECRET"], JWT::Algorithm::HS256)
JWT.encode({"data" => data.to_h, "exp" => expiration}, SAFE_ENV["BACKEND_JWT_SECRET"], JWT::Algorithm::HS256)
end
def create_user_jwt(user_id : Int, expiration : Int = (Time.utc + Time::Span.new(hours: 6)).to_unix) : String

View file

@ -22,18 +22,16 @@ module Backend
return if @user.nil? || @user.not_nil!.blocked
if @user
@role = Schema::UserRole.parse?(@user.as(Db::User).role).not_nil!
if @role
@external =
case Schema::UserRole.parse?(@user.not_nil!.role)
when Schema::UserRole::Admin
@user.not_nil!.admin
when Schema::UserRole::Teacher
@user.not_nil!.teacher
when Schema::UserRole::Student
@user.not_nil!.student
end
end
@role = Schema::UserRole.parse(@user.not_nil!.role).not_nil!
@external =
case @role.not_nil!
when .admin?
@user.not_nil!.admin
when .teacher?
@user.not_nil!.teacher
when .student?
@user.not_nil!.student
end
end
end
end

View file

@ -42,15 +42,6 @@ module Backend
role: input.role.to_s,
blocked: input.blocked,
)
if input.create_external && input.role
case input.role
when UserRole::Teacher
user.teacher = Db::Teacher.create!(user_id: user.id, max_students: input.teacher.not_nil!.max_students)
when UserRole::Student
user.student = Db::Student.create!(user_id: user.id, skif: input.student.not_nil!.skif)
end
user.save!
end
User.new(user)
end
@ -83,6 +74,15 @@ module Backend
id
end
@[GraphQL::Field]
def send_teachers_registration_email(context : Context) : Bool
context.admin!
Worker::Jobs::SendTeachersRegistrationEmailJob.new.enqueue
true
end
@[GraphQL::Field]
def create_teacher(context : Context, input : TeacherCreateInput) : Teacher
context.admin!
@ -115,7 +115,7 @@ module Backend
context.admin!
user = Db::User.find!(input.user_id)
raise "User not a student" unless UserRole.parse(user.role) == UserRole::Student
raise "User not a student" unless Db::UserRole.parse(user.role).student?
student = Db::Student.create!(user_id: user.id)
Student.new(student)

View file

@ -40,11 +40,11 @@ module Backend
@[GraphQL::Field]
def external_id : Int32?
case Db::UserRole.parse(find!.role)
when Db::UserRole::Admin
when .admin?
find!.admin
when Db::UserRole::Teacher
when .teacher?
find!.teacher
when Db::UserRole::Student
when .student?
find!.student
end.not_nil!.id.not_nil!.to_i
rescue NilAssertionError
@ -88,9 +88,6 @@ module Backend
getter email
getter password
getter role
getter teacher
getter student
getter create_external
getter blocked
@[GraphQL::Field]
@ -100,9 +97,6 @@ module Backend
@email : String,
@password : String,
@role : UserRole,
@teacher : TeacherInput? = nil,
@student : StudentInput? = nil,
@create_external : Bool = false,
@blocked : Bool = false
)
end

View file

@ -32,6 +32,15 @@ module Backend
puts cmd.help
end
cmd.commands.add do |c|
c.use = "version"
c.long = "Print version"
c.run do
puts Backend::VERSION
end
end
cmd.commands.add do |c|
c.use = "run"
c.long = "Run the backend"

View file

@ -17,12 +17,16 @@ module Backend
column role : String
column blocked : Bool = false
def name : String
"#{@firstname} #{@lastname}"
end
validate :email, "needs to be an email address" do |user|
CrystalEmail::Rfc5322::Public.validates?(user.email)
end
validate :role, "needs to be a valid role" do |user|
UserRole.parse?(user.role).in?(UserRole.values)
UserRole.parse(user.role).in?(UserRole.values)
end
validate :role, "user external needs to be a valid role" do |user|

View file

@ -0,0 +1,23 @@
require "quartz_mailer"
require "email"
require "kilt"
require "./mailers/*"
module Backend
module Mailers
NAME = SAFE_ENV["BACKEND_SMTP_NAME"]
EMAIL = SAFE_ENV["BACKEND_SMTP_USERNAME"]
Quartz.config do |config|
config.smtp_enabled = true
config.smtp_address = SAFE_ENV["BACKEND_SMTP_ADDRESS"]
config.smtp_port = SAFE_ENV["BACKEND_SMTP_PORT"]
config.helo_domain = SAFE_ENV["BACKEND_SMTP_HELO"]
config.use_tls = EMail::Client::TLSMode::STARTTLS
config.username = EMAIL
config.password = SAFE_ENV["BACKEND_SMTP_PASSWORD"]
config.use_authentication = true
end
end
end

View file

@ -0,0 +1,15 @@
module Backend
module Mailers
class TeacherRegistrationMailer < Quartz::Composer
def sender : Quartz::Message::Address
address email: EMAIL, name: NAME
end
def initialize(user : Db::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

View file

@ -0,0 +1,5 @@
Hey, <%= user.name %>!
Du wurdest erfolgreich als Lehrer registriert.
Initialisiere deinen Account, indem du auf den folgenden Link klickst und deine Daten eingibst:
<%= Path[SAFE_ENV["URL"], "register/teacher?jwt=TEST"] %>

View file

@ -1,11 +1,18 @@
require "senf"
module Backend
SAFE_ENV = Senf::SafeEnv.new([
"BACKEND_DATABASE_URL",
"BACKEND_ADMIN_EMAIL",
"BACKEND_ADMIN_PASSWORD",
"BACKEND_JWT_SECRET",
"BACKEND_WORKER_REDIS_URL",
])
SAFE_ENV = Senf::SafeEnv.new(%w(
URL
BACKEND_DATABASE_URL
BACKEND_ADMIN_EMAIL
BACKEND_ADMIN_PASSWORD
BACKEND_JWT_SECRET
BACKEND_WORKER_REDIS_URL
BACKEND_SMTP_HELO
BACKEND_SMTP_ADDRESS
BACKEND_SMTP_PORT
BACKEND_SMTP_NAME
BACKEND_SMTP_USERNAME
BACKEND_SMTP_PASSWORD
))
end

View file

@ -0,0 +1,5 @@
require "version_from_shard"
module Backend
VersionFromShard.declare
end

View file

@ -1,5 +1,23 @@
require "mosquito"
module Mosquito::Serializers::Array
end
module Mosquito::Serializers::Granite
macro serialize_granite_model(klass)
{% method_suffix = klass.resolve.stringify.underscore.gsub(/::/, "__").id %}
def serialize_{{ method_suffix }}(model : {{ klass.id }}) : String
model.id.to_s
end
def deserialize_{{ method_suffix }}(raw : String) : {{ klass.id }}
id = raw.to_i
{{ klass.id }}.find(id).not_nil!
end
end
end
require "./worker/*"
module Backend

View file

@ -1,13 +0,0 @@
module Backend
module Worker
module Jobs
class HelloWorldJob < Mosquito::PeriodicJob
run_every 30.seconds
def perform : Nil
log "Hello World!"
end
end
end
end
end

View file

@ -0,0 +1,34 @@
require "../../db/user"
module Backend
module Worker
module Jobs
class SendTeachersRegistrationEmailJob < Mosquito::QueuedJob
def perform : Nil
users = Db::User.where(role: Db::UserRole::Teacher.to_s, teacher_id: nil)
count = users.count.run.as(Int64).to_i
channel = Channel(Nil).new(count)
users.each do |user|
spawn do
unless Db::UserRole.parse(user.role).teacher?
fail
end
log "Sending teacher registration email to #{user.email} (#{user.id})"
Mailers::TeacherRegistrationMailer.new(user).deliver
channel.send(nil)
end
end
count.times do
channel.receive
end
Fiber.yield
end
end
end
end
end

View file

@ -1,3 +1,3 @@
#!/usr/bin/env bash
docker-compose exec backend backend "$@"
docker-compose exec backend ./bin/backend "$@"

View file

@ -1,3 +1,3 @@
#!/usr/bin/env bash
docker-compose exec backend micrate "$@"
docker-compose exec backend ./bin/micrate "$@"