diff --git a/.example.env b/.example.env
index 5591344..2aed8e7 100644
--- a/.example.env
+++ b/.example.env
@@ -37,6 +37,7 @@ 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
@@ -44,4 +45,4 @@ 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=60
+BACKEND_LDAP_CACHE_REFRESH_INTERVAL=720
diff --git a/docker-compose.yml b/docker-compose.yml
index 3632aa6..e524460 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -88,6 +88,7 @@ services:
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}
diff --git a/docker/backend/Dockerfile b/docker/backend/Dockerfile
index e59a176..8363686 100644
--- a/docker/backend/Dockerfile
+++ b/docker/backend/Dockerfile
@@ -43,7 +43,6 @@ RUN if [ "${BUILD_ENV}" = "development" ]; then \
FROM scratch as runner
COPY --from=builder /src/bin /bin
-COPY ./db /db
EXPOSE 80
ENTRYPOINT [ "backend" ]
CMD [ "run" ]
diff --git a/docker/backend/Makefile b/docker/backend/Makefile
index 1b0388a..24f9f2c 100644
--- a/docker/backend/Makefile
+++ b/docker/backend/Makefile
@@ -22,7 +22,7 @@ dev:
shards build -Ddevelopment --static --verbose -s -p -t
prod:
- shards build --static --release --no-debug --verbose -s -p -t
+ shards build --static --release --verbose -s -p -t
docs:
crystal docs --project-name "Mentorenwahl Backend" -D granite_docs
diff --git a/docker/backend/db/migrations/20220205143534_init.sql b/docker/backend/db/migrations/20220205143534_init.sql
deleted file mode 100644
index f0d53e5..0000000
--- a/docker/backend/db/migrations/20220205143534_init.sql
+++ /dev/null
@@ -1,83 +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 .
- */
--- +micrate Up
--- SQL in section ' Up ' is executed when this migration is applied
-CREATE TYPE user_role AS ENUM ('Teacher', 'Student');
-
-CREATE TABLE users(
- id BIGSERIAL PRIMARY KEY,
- username TEXT UNIQUE NOT NULL,
- role user_role NOT NULL,
- admin BOOLEAN NOT NULL
-);
-
-CREATE TABLE teachers(
- id BIGSERIAL PRIMARY KEY,
- user_id BIGINT NOT NULL UNIQUE REFERENCES users(id),
- max_students INT NOT NULL,
- skif BOOLEAN NOT NULL
-);
-
-CREATE TABLE students(
- id BIGSERIAL PRIMARY KEY,
- user_id BIGINT NOT NULL UNIQUE REFERENCES users(id),
- skif BOOLEAN NOT NULL
-);
-
-ALTER TABLE
- users
-ADD
- COLUMN teacher_id BIGINT UNIQUE REFERENCES teachers(id);
-
-ALTER TABLE
- users
-ADD
- COLUMN student_id BIGINT UNIQUE REFERENCES students(id);
-
-CREATE TABLE votes(
- id BIGSERIAL PRIMARY KEY,
- student_id BIGINT NOT NULL UNIQUE REFERENCES students(id)
-);
-
-ALTER TABLE
- students
-ADD
- COLUMN vote_id BIGINT UNIQUE REFERENCES votes(id);
-
-CREATE TABLE teacher_votes(
- id BIGSERIAL PRIMARY KEY,
- vote_id BIGINT NOT NULL REFERENCES votes(id),
- teacher_id BIGINT NOT NULL REFERENCES teachers(id),
- priority INT NOT NULL
-);
-
--- +micrate Down
--- SQL section ' Down ' is executed when this migration is rolled back
-DROP TABLE teacher_votes;
-
-DROP TABLE votes;
-
-DROP TABLE admins;
-
-DROP TABLE teachers;
-
-DROP TABLE students;
-
-DROP TABLE users;
-
-DROP TYPE user_roles;
\ No newline at end of file
diff --git a/docker/backend/shard.lock b/docker/backend/shard.lock
index f5fa21b..5cf043e 100644
--- a/docker/backend/shard.lock
+++ b/docker/backend/shard.lock
@@ -1,5 +1,9 @@
version: 2.0
shards:
+ admiral:
+ git: https://github.com/jwaldrip/admiral.cr.git
+ version: 1.12.1
+
athena:
git: https://github.com/athena-framework/framework.git
version: 0.16.0
@@ -40,13 +44,17 @@ shards:
git: https://github.com/spider-gazelle/bindata.git
version: 1.10.0
+ clear:
+ git: https://github.com/vici37/clear.git
+ version: 0.9+git.commit.2139d151d966b1119fd75c97d3b4d40a269592b9
+
commander:
git: https://github.com/mrrooijen/commander.git
version: 0.4.0
db:
git: https://github.com/crystal-lang/crystal-db.git
- version: 0.10.1
+ version: 0.11.0
email:
git: https://github.com/arcage/crystal-email.git
@@ -56,18 +64,22 @@ shards:
git: https://github.com/repomaa/env_config.cr.git
version: 0.1.0+git.commit.a3ef5b955f27e2c65de2fe0ff41718e2eea7c06f
- granite:
- git: https://github.com/amberframework/granite.git
- version: 0.23.0
+ generate:
+ git: https://github.com/anykeyh/generate.cr.git
+ version: 0.1.0+git.commit.f5dafc934a70e0ee2f246dddf3df44686f844da2
graphql:
git: https://github.com/graphql-crystal/graphql.git
- version: 0.3.2+git.commit.f49615eb286e90cfa9041107706a50d2c95e988d
+ version: 0.4.0
habitat:
git: https://github.com/luckyframework/habitat.git
version: 0.4.7
+ inflector:
+ git: https://github.com/anykeyh/inflector.cr.git
+ version: 0.1.8+git.commit.dc5c898b0a834617d8b3ff73ac5a2239bd9fc019
+
jwt:
git: https://github.com/crystal-community/jwt.git
version: 1.6.0
@@ -84,10 +96,6 @@ shards:
git: https://git.dergrimm.net/dergrimm/ldap_escape.git
version: 0.1.0
- micrate:
- git: https://github.com/juanedi/micrate.git
- version: 0.12.0
-
mosquito:
git: https://github.com/mosquito-cr/mosquito.git
version: 1.0.0.rc1+git.commit.afd53dd241447b60ece9232b6c71669b192baaa4
@@ -98,7 +106,7 @@ shards:
pg:
git: https://github.com/will/crystal-pg.git
- version: 0.25.0
+ version: 0.26.0
pool:
git: https://github.com/ysbaddaden/pool.git
diff --git a/docker/backend/shard.yml b/docker/backend/shard.yml
index 89402b8..b910d60 100644
--- a/docker/backend/shard.yml
+++ b/docker/backend/shard.yml
@@ -25,25 +25,21 @@ license: GNU GPLv3
targets:
backend:
main: src/cli/backend.cr
- micrate:
- main: src/cli/micrate.cr
+ clear:
+ main: src/cli/clear.cr
crystal: 1.3.2
dependencies:
- granite:
- github: amberframework/granite
- pg:
- github: will/crystal-pg
+ clear:
+ github: vici37/clear
+ branch: master
graphql:
github: graphql-crystal/graphql
- branch: main
jwt:
github: crystal-community/jwt
commander:
github: mrrooijen/commander
- micrate:
- github: juanedi/micrate
mosquito:
github: mosquito-cr/mosquito
branch: master
diff --git a/docker/backend/src/backend/api/context.cr b/docker/backend/src/backend/api/context.cr
index 5ccd524..1c714c5 100644
--- a/docker/backend/src/backend/api/context.cr
+++ b/docker/backend/src/backend/api/context.cr
@@ -89,13 +89,15 @@ module Backend
return false unless authenticated?
roles.each do |role|
- return true if @role == role && if external_check
- role == case @external.not_nil!
- when Db::Teacher
- Schema::UserRole::Teacher
- when Db::Student
- Schema::UserRole::Student
- end
+ return true if @role == role &&
+ if external_check
+ role ==
+ case @external.not_nil!
+ when Db::Teacher
+ Schema::UserRole::Teacher
+ when Db::Student
+ Schema::UserRole::Student
+ end
else
true
end
@@ -130,6 +132,8 @@ module Backend
def student!(external_check = true) : Bool
role! external_check, Schema::UserRole::Student
end
+
+ # TODO: Custom error handler
end
end
end
diff --git a/docker/backend/src/backend/api/schema/helpers.cr b/docker/backend/src/backend/api/schema/helpers.cr
index 7d29ba9..46d8ea7 100644
--- a/docker/backend/src/backend/api/schema/helpers.cr
+++ b/docker/backend/src/backend/api/schema/helpers.cr
@@ -19,25 +19,13 @@ module Backend
module Schema
# Schema helper macros
module Helpers
- # Defines field property and GraphQL specific getter
- macro field(type)
- property {{ type.var }} {% if type.value %} = {{ type.value }}{% end %}
-
- @[GraphQL::Field]
- def {{ type.var }} : {{ type.type }}
- @{{ type.var }}
- end
- end
-
# Defines DB model field helper functions
macro db_object(type)
- private property model
-
def initialize(@model : {{ type }})
end
def self.from_id(id : Int32) : self
- new({{ type }}.find!(id))
+ new({{ type }}.query.find! { var(:id) == id })
end
{% space_name = type.names.last.underscore.gsub(/_/, " ").capitalize %}
@@ -45,7 +33,7 @@ module Backend
@[GraphQL::Field]
# {{ space_name }}'s ID
def id : Int32
- @model.id.not_nil!.to_i
+ @model.id
end
end
end
diff --git a/docker/backend/src/backend/api/schema/mutation.cr b/docker/backend/src/backend/api/schema/mutation.cr
index 7fe2992..d9e4f89 100644
--- a/docker/backend/src/backend/api/schema/mutation.cr
+++ b/docker/backend/src/backend/api/schema/mutation.cr
@@ -26,7 +26,7 @@ module Backend
def login(username : String, password : String) : LoginPayload
raise "Auth failed" if username.empty? || password.empty?
- user = Db::User.find_by(username: username)
+ user = Db::User.query.find { var(:username) == username }
raise "Auth failed" unless user && Ldap.authenticate?(Ldap::Constructor.uid(username), password)
LoginPayload.new(
@@ -48,7 +48,7 @@ module Backend
rescue LDAP::Client::AuthError
true
end
- user = Db::User.create!(username: input.username, role: input.role.to_s, admin: input.admin)
+ user = Db::User.create!(username: input.username, role: input.role.to_db, skif: input.skif, admin: input.admin)
Worker::Jobs::CacheLdapUserJob.new(user.id.not_nil!.to_i).enqueue
User.new(user)
@@ -60,7 +60,7 @@ module Backend
context.admin!
user = Db::User.find!(id)
- user.destroy!
+ user.delete
id
end
@@ -90,7 +90,7 @@ module Backend
context.admin!
teacher = Db::Teacher.find!(id)
- teacher.destroy!
+ teacher.delete
id
end
@@ -101,7 +101,7 @@ module Backend
context.teacher! external_check: false
Teacher.new(
- Db::Teacher.create!(user_id: context.user.not_nil!.id, max_students: input.max_students, skif: input.skif)
+ Db::Teacher.create!(user_id: context.user.not_nil!.id, max_students: input.max_students)
)
end
@@ -111,7 +111,7 @@ module Backend
context.admin!
user = Db::User.find!(input.user_id)
- raise "User not a student" unless user.role.student?
+ raise "User not a student" unless user.role.to_api.student?
student = Db::Student.create!(user_id: user.id)
Student.new(student)
@@ -123,18 +123,18 @@ module Backend
context.admin!
student = Db::Student.find!(id)
- student.destroy!
+ student.delete
id
end
@[GraphQL::Field]
# Self register as student
- def register_student(context : Context, input : StudentInput) : Student
+ def register_student(context : Context) : Student
context.student! external_check: false
Student.new(
- Db::Student.create!(user_id: context.user.not_nil!.id, skif: input.skif)
+ Db::Student.create!(user_id: context.user.not_nil!.id)
)
end
@@ -144,18 +144,16 @@ module Backend
context.student!
raise "Not enough teachers" if input.teacher_ids.size < Backend.config.minimum_teacher_selection_count
- teacher_role_count = Db::User.where(role: Db::UserRole::Teacher.to_s).count.run.as(Int64)
- raise "Teachers not registered" if teacher_role_count != Db::Teacher.count ||
- teacher_role_count.zero?
+ teacher_role_count = Db::User.query.where(role: Db::UserRole::Teacher).count
+ raise "Teachers not registered" if teacher_role_count != Db::Teacher.query.count || teacher_role_count.zero?
- skif = context.external.as(Db::Student).skif
input.teacher_ids.each do |id|
teacher = Db::Teacher.find(id)
if teacher.nil?
raise "Teachers not found"
- elsif teacher.skif != skif
- if teacher.skif
+ 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"
@@ -165,7 +163,7 @@ module Backend
student = context.external.not_nil!.as(Db::Student)
vote = Db::Vote.create!(student_id: student.id)
- Db::TeacherVote.import(input.teacher_ids.map_with_index { |id, i| Db::TeacherVote.new(vote_id: vote.id, teacher_id: id.to_i64, priority: i) })
+ Db::TeacherVote.import(input.teacher_ids.map_with_index { |id, i| Db::TeacherVote.new({vote_id: vote.id, teacher_id: id, priority: i}) })
Vote.new(vote)
end
diff --git a/docker/backend/src/backend/api/schema/query.cr b/docker/backend/src/backend/api/schema/query.cr
index fec8e58..01a7b6c 100644
--- a/docker/backend/src/backend/api/schema/query.cr
+++ b/docker/backend/src/backend/api/schema/query.cr
@@ -52,7 +52,7 @@ module Backend
def users(context : Context) : Array(User)
context.admin!
- Db::User.all.map { |user| User.new(user) }
+ Db::User.query.map { |user| User.new(user) }
end
@[GraphQL::Field]
@@ -60,7 +60,7 @@ module Backend
def admins(context : Context) : Array(User)
context.admin!
- Db::User.where(admin: true).map { |user| User.new(user) }
+ Db::User.query.where(admin: true).map { |user| User.new(user) }
end
@[GraphQL::Field]
@@ -72,7 +72,7 @@ module Backend
@[GraphQL::Field]
# All teachers
def teachers : Array(Teacher)
- Db::Teacher.all.map { |teacher| Teacher.new(teacher) }
+ Db::Teacher.query.map { |teacher| Teacher.new(teacher) }
end
@[GraphQL::Field]
@@ -88,7 +88,7 @@ module Backend
def students(context : Context) : Array(Student)
context.admin!
- Db::Student.all.map { |student| Student.new(student) }
+ Db::Student.query.map { |student| Student.new(student) }
end
@[GraphQL::Field]
@@ -104,7 +104,7 @@ module Backend
def votes(context : Context) : Array(Vote)
context.admin!
- Db::Vote.all.map { |vote| Vote.new(vote) }
+ Db::Vote.query.map { |vote| Vote.new(vote) }
end
@[GraphQL::Field]
@@ -120,7 +120,7 @@ module Backend
def teacher_votes(context : Context) : Array(TeacherVote)
context.admin!
- Db::TeacherVote.all.map { |vote| TeacherVote.new(vote) }
+ Db::TeacherVote.query.map { |vote| TeacherVote.new(vote) }
end
end
end
diff --git a/docker/backend/src/backend/api/schema/student.cr b/docker/backend/src/backend/api/schema/student.cr
index 5dad9a7..60daa47 100644
--- a/docker/backend/src/backend/api/schema/student.cr
+++ b/docker/backend/src/backend/api/schema/student.cr
@@ -30,12 +30,6 @@ module Backend
User.new(@model.user)
end
- @[GraphQL::Field]
- # Student at SKIF
- def skif : Bool
- @model.skif
- end
-
@[GraphQL::Field]
# Student's vote
def vote : Vote?
@@ -43,26 +37,22 @@ module Backend
end
end
- @[GraphQL::InputObject]
- # Student base input
- class StudentInput < GraphQL::BaseInputObject
- # Student at SKIF
- getter skif
-
- @[GraphQL::Field]
- def initialize(@skif : Bool)
- end
- end
+ # @[GraphQL::InputObject]
+ # # Student base input
+ # class StudentInput < GraphQL::BaseInputObject
+ # @[GraphQL::Field]
+ # def initialize
+ # end
+ # end
@[GraphQL::InputObject]
# Student creation input
- class StudentCreateInput < StudentInput
+ class StudentCreateInput < GraphQL::BaseInputObject
# Student's user ID
getter user_id
@[GraphQL::Field]
- def initialize(@user_id : Int32, skif : Bool)
- super(skif)
+ def initialize(@user_id : Int32)
end
end
end
diff --git a/docker/backend/src/backend/api/schema/teacher.cr b/docker/backend/src/backend/api/schema/teacher.cr
index 7b15bab..3c01e85 100644
--- a/docker/backend/src/backend/api/schema/teacher.cr
+++ b/docker/backend/src/backend/api/schema/teacher.cr
@@ -35,12 +35,6 @@ module Backend
def max_students : Int32
@model.max_students
end
-
- @[GraphQL::Field]
- # Teacher is at SKIF
- def skif : Bool
- @model.skif
- end
end
@[GraphQL::InputObject]
@@ -49,11 +43,8 @@ module Backend
# Teacher's max students
getter max_students
- # Teacher at SKIF
- getter skif
-
@[GraphQL::Field]
- def initialize(@max_students : Int32, @skif : Bool)
+ def initialize(@max_students : Int32)
end
end
@@ -64,8 +55,8 @@ module Backend
getter user_id
@[GraphQL::Field]
- def initialize(@user_id : Int32, max_students : Int32, skif : Bool)
- super(max_students, skif)
+ def initialize(@user_id : Int32, max_students : Int32)
+ super(max_students)
end
end
end
diff --git a/docker/backend/src/backend/api/schema/user.cr b/docker/backend/src/backend/api/schema/user.cr
index 7f09494..b68c113 100644
--- a/docker/backend/src/backend/api/schema/user.cr
+++ b/docker/backend/src/backend/api/schema/user.cr
@@ -25,12 +25,12 @@ module Backend
# DB representation of the enum
def to_db : Db::UserRole
- Db::UserRole.parse(self.to_s)
+ Db::UserRole.from_string(self.to_s.underscore)
end
# GraphQL representation of the DB enum
def self.from_db(role : Db::UserRole) : self
- Db::UserRole.to_api
+ role.to_api
end
end
@@ -42,29 +42,31 @@ module Backend
db_object Db::User
# LDAP user data
- getter ldap : Ldap::User?
+ def ldap : Ldap::User
+ unless raw_cache = Redis::CLIENT.get("ldap:user:#{id}")
+ Worker::Jobs::CacheLdapUserJob.new(id).perform
+ raw_cache = Redis::CLIENT.get("ldap:user:#{id}").not_nil!
+ end
- # Refreshes LDAP user data
- def refresh_ldap : Ldap::User
- (@ldap ||= Ldap::User.from_json(Redis::CLIENT.get("ldap:user:#{id}").as(String))).not_nil!
+ (@ldap ||= Ldap::User.from_json(raw_cache.not_nil!)).not_nil!
end
@[GraphQL::Field]
# User's first name
def first_name : String
- refresh_ldap.first_name
+ ldap.first_name
end
@[GraphQL::Field]
# User's last name
def last_name : String
- refresh_ldap.last_name
+ ldap.last_name
end
@[GraphQL::Field]
# User's full name
def name : String
- refresh_ldap.name
+ ldap.name
end
@[GraphQL::Field]
@@ -76,7 +78,7 @@ module Backend
@[GraphQL::Field]
# User's email
def email : String
- refresh_ldap.email
+ ldap.email
end
@[GraphQL::Field]
@@ -91,10 +93,16 @@ 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?
- case @model.role
+ case @model.role.to_api
when .teacher?
@model.teacher
when .student?
@@ -127,12 +135,14 @@ 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/config.cr b/docker/backend/src/backend/config.cr
index 77d9da9..d6803ae 100644
--- a/docker/backend/src/backend/config.cr
+++ b/docker/backend/src/backend/config.cr
@@ -132,6 +132,9 @@ module Backend
# Database URL
getter url : String
+
+ # Allow old database migrations to be used
+ getter allow_old_schema : Bool
end
# Configuration for `REDIS`
diff --git a/docker/backend/src/backend/db.cr b/docker/backend/src/backend/db.cr
index f4c2701..c81b8d4 100644
--- a/docker/backend/src/backend/db.cr
+++ b/docker/backend/src/backend/db.cr
@@ -14,8 +14,10 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-require "granite"
-require "granite/adapter/pg"
+require "clear"
+require "log"
+require "db"
+require "retriable"
require "./db/*"
@@ -24,17 +26,23 @@ module Backend
module Db
extend self
- Granite::Connections << Granite::Adapter::Pg.new(name: "pg", url: Backend.config.db.url)
+ # Migration UIDs
+ MIGRATIONS = {{ run("./macros/migrations.cr", "#{__DIR__}/db/migrations/*.cr").stringify.split("\n") }}
- # Checks if database schema is up to date
- def schema_up_to_date_compare : Int32?
- migrations = Dir["db/migrations/*.sql"].map { |f| Path[f].basename }.sort!
- return unless latest_migration = migrations.try(&.last.match(/\d+/).try(&.to_a.first.not_nil!.to_u64))
+ def init(severity = {% if flag?(:development) %} ::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)
+ end
+ end
- begin
- MicrateDbVersion.order(tstamp: :desc).limit(1).assembler.select.run.first.version_id <=> latest_migration
- rescue PQ::PQError
- nil
+ def schema_up_to_date? : Bool
+ last_migration = ClearMetadata.query.last!.value
+
+ if last_migration == "-1"
+ false
+ else
+ last_migration == MIGRATIONS.last
end
end
end
diff --git a/docker/backend/src/backend/db/clear_metadata.cr b/docker/backend/src/backend/db/clear_metadata.cr
new file mode 100644
index 0000000..973fdd5
--- /dev/null
+++ b/docker/backend/src/backend/db/clear_metadata.cr
@@ -0,0 +1,11 @@
+module Backend
+ module Db
+ class ClearMetadata
+ include Clear::Model
+ self.table = :__clear_metadatas
+
+ column metatype : String, primary: true
+ column value : String, primary: true
+ end
+ end
+end
diff --git a/docker/backend/src/backend/db/micrate_db_version.cr b/docker/backend/src/backend/db/micrate_db_version.cr
index f319075..91d5c1e 100644
--- a/docker/backend/src/backend/db/micrate_db_version.cr
+++ b/docker/backend/src/backend/db/micrate_db_version.cr
@@ -1,26 +1,11 @@
-# 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 Db
- # Micrate DB migrator model / configuration
- class MicrateDbVersion < Granite::Base
- table micrate_db_version
+ class MicrateDbVersion
+ include Clear::Model
+ self.table = :micrate_db_version
+
+ primary_key type: :serial
- column id : Int32, primary: true
column version_id : Int64
column is_applied : Bool
column tstamp : Time?
diff --git a/docker/backend/src/backend/db/migrations.cr b/docker/backend/src/backend/db/migrations.cr
new file mode 100644
index 0000000..1d57346
--- /dev/null
+++ b/docker/backend/src/backend/db/migrations.cr
@@ -0,0 +1,9 @@
+require "./migrations/*"
+
+module Backend
+ module Db
+ # DB SQL migrations
+ module Migration
+ end
+ end
+end
diff --git a/docker/backend/src/backend/db/migrations/1_create_users.cr b/docker/backend/src/backend/db/migrations/1_create_users.cr
new file mode 100644
index 0000000..4b64460
--- /dev/null
+++ b/docker/backend/src/backend/db/migrations/1_create_users.cr
@@ -0,0 +1,44 @@
+module Backend
+ module Db
+ module Migrations
+ class CreateUsers1
+ include Clear::Migration
+
+ def change(dir) : Nil
+ create_enum :user_role, %w(teacher student)
+
+ create_table :users, id: false do |t| # We create the table users
+ t.column :id, :serial, primary: true, null: false
+ t.column :username, :string, unique: true, index: true, null: false
+ t.column :role, :user_role, null: false
+ t.column :skif, :bool, null: false
+ t.column :admin, :bool, default: false, null: false
+ end
+
+ create_table :teachers, id: false do |t|
+ t.column :id, :serial, primary: true, null: false
+ t.references to: "users", on_delete: :cascade, null: false
+ t.column :max_students, :int, null: false
+ end
+
+ create_table :students, id: false do |t|
+ t.column :id, :serial, primary: true, null: false
+ t.references to: "users", on_delete: :cascade, null: false
+ end
+
+ create_table :votes, id: false do |t|
+ t.column :id, :serial, primary: true, null: false
+ t.references to: "students", on_delete: :cascade, null: false
+ end
+
+ create_table :teacher_votes, id: false do |t|
+ t.column :id, :serial, primary: true, null: false
+ t.references to: "votes", on_delete: :cascade, null: false
+ t.references to: "teachers", on_delete: :cascade, null: false
+ t.column :priority, :int, null: false
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/docker/backend/src/backend/db/student.cr b/docker/backend/src/backend/db/student.cr
index 64ad955..78b98cd 100644
--- a/docker/backend/src/backend/db/student.cr
+++ b/docker/backend/src/backend/db/student.cr
@@ -1,33 +1,14 @@
-# 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 Db
- # Student model
- class Student < Granite::Base
- table students
+ class Student
+ include Clear::Model
+ self.table = :students
- belongs_to :user
- has_one :vote
+ primary_key type: :serial
- # Student's ID
- column id : Int64, primary: true
+ belongs_to user : User
- # Student is at SKIF
- column skif : Bool
+ has_one vote : Vote?, foreign_key: :student_id
end
end
end
diff --git a/docker/backend/src/backend/db/teacher.cr b/docker/backend/src/backend/db/teacher.cr
index 4eed0d5..d1ecf61 100644
--- a/docker/backend/src/backend/db/teacher.cr
+++ b/docker/backend/src/backend/db/teacher.cr
@@ -1,36 +1,16 @@
-# 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 Db
- # Teacher model
- class Teacher < Granite::Base
- table teachers
+ class Teacher
+ include Clear::Model
+ self.table = :teachers
- belongs_to :user
- has_many teacher_votes : TeacherVote
+ primary_key type: :serial
- # Teacher's ID
- column id : Int64, primary: true
+ belongs_to user : User
- # Teacher's max students count
column max_students : Int32
- # Teacher is at SKIF
- column skif : Bool
+ has_many teacher_votes : TeacherVote
end
end
end
diff --git a/docker/backend/src/backend/db/teacher_vote.cr b/docker/backend/src/backend/db/teacher_vote.cr
index d0d9c3c..8464099 100644
--- a/docker/backend/src/backend/db/teacher_vote.cr
+++ b/docker/backend/src/backend/db/teacher_vote.cr
@@ -1,49 +1,15 @@
-# 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 Db
- # Teacher vote model
- class TeacherVote < Granite::Base
- table teacher_votes
+ class TeacherVote
+ include Clear::Model
+ self.table = :teacher_votes
- belongs_to :vote
- belongs_to :teacher
+ primary_key type: :serial
- # Teacher votes's ID
- column id : Int64, primary: true
+ belongs_to vote : Vote
+ belongs_to teacher : Teacher
- # Teacher vote's priority
column priority : Int32
-
- validate :teacher, "must be vote unique" do |teacher_vote|
- self.where(vote_id: teacher_vote.vote.id, teacher_id: teacher_vote.teacher.not_nil!.id).count.run.as(Int64).zero?
- end
-
- validate :priority, "must be positive" do |teacher_vote|
- teacher_vote.priority >= 0
- end
-
- validate :priority, "must be less than the number of teachers" do |teacher_vote|
- teacher_vote.priority < Teacher.count
- end
-
- validate :priority, "must be vote unique" do |teacher_vote|
- self.where(vote_id: teacher_vote.vote.id, priority: teacher_vote.priority).count.run.as(Int64).zero?
- end
end
end
end
diff --git a/docker/backend/src/backend/db/user.cr b/docker/backend/src/backend/db/user.cr
index 949b1ce..8b9e45e 100644
--- a/docker/backend/src/backend/db/user.cr
+++ b/docker/backend/src/backend/db/user.cr
@@ -1,26 +1,8 @@
-# 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 Db
- # Possible roles a user can have
- enum UserRole
- Teacher
- Student
+ Clear.enum UserRole, :teacher, :student
+ struct UserRole
# API representation of the enum
def to_api : Api::Schema::UserRole
Api::Schema::UserRole.parse(self.to_s)
@@ -32,24 +14,19 @@ module Backend
end
end
- # User model
- class User < Granite::Base
- table users
+ class User
+ include Clear::Model
+ self.table = :users
- has_one :teacher
- has_one :student
+ primary_key type: :serial
- # User's ID
- column id : Int64, primary: true
-
- # User's LDAP username
column username : String
-
- # User's role
- column role : UserRole, converter: Granite::Converters::Enum(Backend::Db::UserRole, String)
-
- # User is admin
+ column role : UserRole
column admin : Bool = false
+ column skif : Bool
+
+ has_one student : Student?, foreign_key: :user_id
+ has_one teacher : Teacher?, foreign_key: :user_id
end
end
end
diff --git a/docker/backend/src/backend/db/vote.cr b/docker/backend/src/backend/db/vote.cr
index 6808ad8..9a30b99 100644
--- a/docker/backend/src/backend/db/vote.cr
+++ b/docker/backend/src/backend/db/vote.cr
@@ -1,28 +1,14 @@
-# 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 Db
- class Vote < Granite::Base
- table votes
+ class Vote
+ include Clear::Model
+ self.table = :votes
- belongs_to :student
- has_many teacher_votes : TeacherVote
+ primary_key type: :serial
- column id : Int64, primary: true
+ belongs_to student : Student
+
+ has_many teacher_votes : TeacherVote, foreign_key: :vote_id
end
end
end
diff --git a/docker/backend/src/backend/ldap/user.cr b/docker/backend/src/backend/ldap/user.cr
index a5bb5e3..f4967ab 100644
--- a/docker/backend/src/backend/ldap/user.cr
+++ b/docker/backend/src/backend/ldap/user.cr
@@ -77,7 +77,7 @@ module Backend
# Creates user data from DB entry index
def self.from_id(id : Int32) : self
- from_db(Db::User.find!(id))
+ from_username(Db::User.query.first(id).select(:id))
end
end
end
diff --git a/docker/backend/src/backend/macros/migrations.cr b/docker/backend/src/backend/macros/migrations.cr
new file mode 100644
index 0000000..7625bfc
--- /dev/null
+++ b/docker/backend/src/backend/macros/migrations.cr
@@ -0,0 +1 @@
+print Dir[ARGV[0]].map { |f| File.basename(f).split("_", 2).first }.uniq!.sort!.join("\n")
diff --git a/docker/backend/src/backend/runner.cr b/docker/backend/src/backend/runner.cr
index 87cf9c6..c43e11f 100644
--- a/docker/backend/src/backend/runner.cr
+++ b/docker/backend/src/backend/runner.cr
@@ -17,6 +17,7 @@
require "service"
require "log"
require "retriable"
+require "clear"
module Backend
# Backend runner
@@ -44,18 +45,22 @@ module Backend
Log.info { "Checking if DB schema is up to date..." }
Retriable.retry(backoff: false, base_interval: 10.seconds, multiplier: 1.0) do
- case Retriable.retry(on: DB::ConnectionRefused, backoff: false) do
- Db.schema_up_to_date_compare
- end
- when nil
- Log.fatal { "No database schema is applied. Please run `bash scripts/micrate.sh up` urgently!" }
- raise Exception.new
- when -1
- Log.warn { "Database schema is not up to date. Please run `bash scripts/micrate.sh up`." }
- when 0
+ ex : Clear::SQL::Error? = nil
+ if begin
+ Db.schema_up_to_date?
+ rescue exc : Clear::SQL::Error
+ ex = exc
+
+ false
+ end && ex.nil?
Log.info { "Database schema is up to date." }
else
- Log.warn { "Database schema is maybe up to date but not consistent. Please run `bash scripts/micrate.sh up` to be safe." }
+ Log.warn { "Database schema is not up to date. Please run `bash scripts/clear.sh migrate`." }
+ if ex
+ raise ex
+ else
+ raise Exception.new("Database schema is not up to date.") unless Backend.config.db.allow_old_schema
+ end
end
end
diff --git a/docker/backend/src/backend/web/controllers/api_controller.cr b/docker/backend/src/backend/web/controllers/api_controller.cr
index c1ecf5b..a8455a3 100644
--- a/docker/backend/src/backend/web/controllers/api_controller.cr
+++ b/docker/backend/src/backend/web/controllers/api_controller.cr
@@ -39,14 +39,29 @@ module Backend
{% end %}
end
+ # GraphQL query request data
+ struct GraphQLQueryData
+ include JSON::Serializable
+
+ # Raw query
+ getter query : String
+
+ # Variables
+ getter variables = {} of String => JSON::Any
+
+ # Operation name
+ getter operation_name : String?
+ end
+
@[ARTA::Post("")]
@[ATHA::QueryParam("development")]
- @[ATHA::ParamConverter("query", converter: ATH::RequestBodyConverter)]
- def endpoint(query : Backend::Web::GraphQLQueryData, request : ATH::Request, development : Bool = false) : ATH::Response
+ def endpoint(request : ATH::Request, development : Bool = false) : ATH::Response
{% if flag?(:development) %}
Log.notice { "Development request icoming" } if development
{% end %}
+ query = GraphQLQueryData.from_json(request.body.not_nil!)
+
ATH::StreamedResponse.new(headers: HTTP::Headers{"content-type" => "application/json"}) do |io|
Api::Schema::SCHEMA.execute(
io,
diff --git a/docker/backend/src/backend/web/graphql_query_data.cr b/docker/backend/src/backend/web/graphql_query_data.cr
deleted file mode 100644
index d749ae6..0000000
--- a/docker/backend/src/backend/web/graphql_query_data.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 Web
- # GraphQL query request data
- struct GraphQLQueryData
- include AVD::Validatable
- include ASR::Serializable
-
- @[Assert::NotBlank]
- getter query : String
-
- getter variables : Hash(String, JSON::Any)?
-
- getter operation_name : String?
- end
- end
-end
diff --git a/docker/backend/src/backend/worker/jobs/cache_ldap_user_job.cr b/docker/backend/src/backend/worker/jobs/cache_ldap_user_job.cr
index 2a2b8d9..73afcce 100644
--- a/docker/backend/src/backend/worker/jobs/cache_ldap_user_job.cr
+++ b/docker/backend/src/backend/worker/jobs/cache_ldap_user_job.cr
@@ -24,11 +24,11 @@ module Backend
# :ditto:
def perform : Nil
key = "ldap:user:#{id}"
- user = Db::User.find(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)
+ 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)
diff --git a/docker/backend/src/backend/worker/jobs/cache_ldap_users_job.cr b/docker/backend/src/backend/worker/jobs/cache_ldap_users_job.cr
deleted file mode 100644
index 2562e77..0000000
--- a/docker/backend/src/backend/worker/jobs/cache_ldap_users_job.cr
+++ /dev/null
@@ -1,41 +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
- # Peridically caches user data in redis cache
- class CacheLdapUsersJob < Mosquito::PeriodicJob
- run_every Backend.config.ldap.cache_refresh_interval.minutes
-
- # :ditto:
- def perform : Nil
- Redis::CLIENT.keys("ldap:user:*")
- .map(&.as(String).split(":")[2].to_i)
- .concat(Db::User.all.map(&.id.not_nil!.to_i))
- .uniq!
- .each do |id|
- spawn do
- CacheLdapUserJob.new(id).enqueue
- end
- end
-
- Fiber.yield
- end
- end
- end
- 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
index 6545f36..21508c4 100644
--- 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
@@ -21,14 +21,14 @@ module Backend
class SendTeachersRegistrationEmailJob < Mosquito::QueuedJob
# :ditto:
def perform : Nil
- users = Db::User.where(role: Db::UserRole::Teacher.to_s, teacher_id: nil)
- count = users.count.run.as(Int64).to_i
+ 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
- fail unless user.role.teacher?
+ fail 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}"
diff --git a/docker/backend/src/cli/backend.cr b/docker/backend/src/cli/backend.cr
index be59c09..b041659 100644
--- a/docker/backend/src/cli/backend.cr
+++ b/docker/backend/src/cli/backend.cr
@@ -17,6 +17,8 @@
require "commander"
require "../backend"
+Backend::Db.init
+
cli = Commander::Command.new do |cmd|
cmd.use = "backend"
cmd.short = "Mentorenwahl backend CLI"
@@ -70,6 +72,13 @@ cli = Commander::Command.new do |cmd|
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"
@@ -77,22 +86,26 @@ cli = Commander::Command.new do |cmd|
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.parse(args[1].downcase)
- print "Register '#{username}' as '#{role}'#{opts.bool["admin"] ? " with admin privileges" : nil}? [y/N] "
- abort unless (gets(chomp: true) || "").strip.downcase == "y"
+ 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, admin: opts.bool["admin"])
- Backend::Worker::Jobs::CacheLdapUserJob.new(user.id.not_nil!.to_i).enqueue
+ 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!"
-
- puts "---"
- puts "User: #{user.id}"
- puts "Role: #{user.role}"
- puts "Admin: #{user.admin}"
- puts "---"
end
end
end
diff --git a/docker/backend/src/cli/clear.cr b/docker/backend/src/cli/clear.cr
new file mode 100644
index 0000000..4c88f6e
--- /dev/null
+++ b/docker/backend/src/cli/clear.cr
@@ -0,0 +1,7 @@
+require "clear"
+require "../backend"
+require "log"
+
+Backend::Db.init(Log::Severity::Debug)
+
+Clear::CLI.run(false)
diff --git a/docker/backend/src/cli/micrate.cr b/docker/backend/src/cli/micrate.cr
deleted file mode 100644
index 041d287..0000000
--- a/docker/backend/src/cli/micrate.cr
+++ /dev/null
@@ -1,21 +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 "micrate"
-require "pg"
-
-Micrate::DB.connection_url = ENV["BACKEND_DB_URL"]?
-Micrate::Cli.run
diff --git a/docker/frontend/.dockerignore b/docker/frontend/.dockerignore
index 229835d..da9c4db 100644
--- a/docker/frontend/.dockerignore
+++ b/docker/frontend/.dockerignore
@@ -11,3 +11,4 @@ Dockerfile
.dockerignore
.gitignore
README.md
+.nvmrc
diff --git a/docker/frontend/.nvmrc b/docker/frontend/.nvmrc
new file mode 100644
index 0000000..d9f8800
--- /dev/null
+++ b/docker/frontend/.nvmrc
@@ -0,0 +1 @@
+16.14.2
diff --git a/scripts/backend.sh b/scripts/backend.sh
index b89a048..391f6a6 100644
--- a/scripts/backend.sh
+++ b/scripts/backend.sh
@@ -16,4 +16,4 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-docker-compose exec backend ./bin/backend "$@"
+docker-compose exec backend backend "$@"
diff --git a/scripts/micrate.sh b/scripts/clear.sh
similarity index 94%
rename from scripts/micrate.sh
rename to scripts/clear.sh
index f958f6b..08b3d57 100644
--- a/scripts/micrate.sh
+++ b/scripts/clear.sh
@@ -16,4 +16,4 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-docker-compose exec backend ./bin/micrate "$@"
+docker-compose exec backend clear "$@"