This commit is contained in:
Dominic Grimm 2022-01-08 13:29:22 +01:00
commit d14ed4be15
61 changed files with 3477 additions and 0 deletions

4
.example.env Normal file
View file

@ -0,0 +1,4 @@
POSTGRES_USER=
POSTGRES_PASSWORD=
BACKEND_JWT_SECRET=

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
.env

1
README.md Normal file
View file

@ -0,0 +1 @@
# mentorenwahl

20
config/nginx/nginx.conf Normal file
View file

@ -0,0 +1,20 @@
events {
}
http {
server {
# location / {
# # proxy_set_header Host $host;
# # proxy_set_header X-Real-IP $remote_addr;
# # proxy_pass http://frontend:3000;
# }
location /graphql {
proxy_pass http://backend:8080;
}
location /adminer {
proxy_pass http://adminer:8080;
}
}
}

43
docker-compose.yml Normal file
View file

@ -0,0 +1,43 @@
services:
nginx:
container_name: nginx
image: nginx:1.20.2-alpine
volumes:
- ./config/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
ports:
- 80:80
depends_on:
- backend
db:
image: postgres:alpine3.15
container_name: db
env_file: .env
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- db:/var/lib/postgresql/data
adminer:
image: adminer:4.8.1-standalone
container_name: adminer
depends_on:
- db
backend:
build:
context: ./docker/backend
args:
BUILD_ENV: production
container_name: backend
environment:
BACKEND_SERVER_PORT: 8080
BACKEND_SERVER_HOST: 0.0.0.0
BACKEND_DATABASE_URL: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_USER}
BACKEND_JWT_SECRET: ${BACKEND_JWT_SECRET}
depends_on:
- db
volumes:
db:

View file

@ -0,0 +1,6 @@
/docs/
/lib/
/bin/
/.shards/
*.dwarf
.ameba.yml

6
docker/backend/.gitignore vendored Normal file
View file

@ -0,0 +1,6 @@
/docs/
/lib/
/bin/
/.shards/
*.dwarf
.ameba.yml

44
docker/backend/Dockerfile Normal file
View file

@ -0,0 +1,44 @@
FROM python:alpine3.15 as pgsanity
WORKDIR /app
RUN apk add libecpg-dev --no-cache
RUN pip3 install pgsanity
COPY ./db ./db
RUN find -name "*.sql" | xargs pgsanity
FROM crystallang/crystal:1.2.2-alpine as micrate-deps
WORKDIR /app
COPY ./micrate/shard.yml ./micrate/shard.lock ./
RUN shards install --production
FROM crystallang/crystal:1.2.2-alpine as micrate-builder
ARG BUILD_ENV
WORKDIR /app
COPY --from=micrate-deps /app/shard.yml /app/shard.lock ./
COPY --from=micrate-deps /app/lib lib/
COPY ./micrate/src ./src
COPY ./scripts ./scripts
RUN . ./scripts/build.sh ${BUILD_ENV}
FROM crystallang/crystal:1.2.2-alpine as deps
WORKDIR /app
RUN apk add curl --no-cache
COPY ./shard.yml ./shard.lock ./
RUN shards install
FROM crystallang/crystal:1.2.2-alpine as builder
ARG BUILD_ENV
WORKDIR /app
COPY --from=deps /app/shard.yml /app/shard.lock ./
COPY --from=deps /app/lib ./lib
COPY --from=deps /app/bin ./bin
COPY ./src ./src
RUN if [ ${BUILD_ENV} = "development" ]; then ./bin/ameba ./src; fi
COPY ./scripts ./scripts
RUN . ./scripts/build.sh ${BUILD_ENV}
FROM scratch as runner
COPY --from=micrate-builder /app/bin/micrate .
COPY --from=builder /app/bin/mw .
COPY --from=pgsanity /app/db ./db
EXPOSE 8080
CMD [ "/mw" ]

View file

@ -0,0 +1,79 @@
-- +micrate Up
-- SQL in section 'Up' is executed when this migration is applied
CREATE TYPE user_roles AS ENUM ('Admin', 'Teacher', 'Student');
CREATE TABLE users(
id BIGSERIAL PRIMARY KEY,
firstname TEXT NOT NULL,
lastname TEXT NOT NULL,
email TEXT NOT NULL,
PASSWORD TEXT NOT NULL,
role user_roles NOT NULL,
blocked BOOLEAN NOT NULL,
UNIQUE (firstname, lastname, email)
);
CREATE TABLE admins(
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL UNIQUE REFERENCES users(id)
);
CREATE TABLE teachers(
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL UNIQUE REFERENCES users(id),
max_students INT 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 admin_id BIGINT UNIQUE REFERENCES admins(id);
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 users;
DROP TABLE admins;
DROP TABLE teachers;
DROP TABLE students;
DROP TYPE user_roles;

View file

@ -0,0 +1,5 @@
/docs/
/lib/
/bin/
/.shards/
*.dwarf

5
docker/backend/micrate/.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
/docs/
/lib/
/bin/
/.shards/
*.dwarf

View file

@ -0,0 +1,14 @@
version: 2.0
shards:
db:
git: https://github.com/crystal-lang/crystal-db.git
version: 0.10.1
micrate:
git: https://github.com/juanedi/micrate.git
version: 0.12.0
pg:
git: https://github.com/will/crystal-pg.git
version: 0.24.0

View file

@ -0,0 +1,19 @@
name: micrate
version: 0.1.0
authors:
- Dominic Grimm <dominic.grimm@gmail.com>
targets:
micrate:
main: src/micrate.cr
crystal: 1.2.2
license: MIT
dependencies:
micrate:
github: juanedi/micrate
pg:
github: will/crystal-pg

View file

@ -0,0 +1,5 @@
require "micrate"
require "pg"
Micrate::DB.connection_url = ENV["BACKEND_DATABASE_URL"]?
Micrate::Cli.run

View file

@ -0,0 +1,14 @@
#!/usr/bin/env sh
BASE_FLAGS="--production --static --verbose -s -p -t"
DEV_FLAGS="$BASE_FLAGS"
PROD_FLAGS="--release --no-debug $BASE_FLAGS"
echo "Building targets in '$1' mode..."
if [ "$1" = "development" ]; then
# shellcheck disable=SC2086
time shards build $DEV_FLAGS
else
# shellcheck disable=SC2086
time shards build $PROD_FLAGS
fi

66
docker/backend/shard.lock Normal file
View file

@ -0,0 +1,66 @@
version: 2.0
shards:
CrystalEmail:
git: https://git.sceptique.eu/Sceptique/CrystalEmail
version: 0.2.6+git.commit.f217992c51048b3f94f4e064cd6c5123e32a1e27
ameba:
git: https://github.com/crystal-ameba/ameba.git
version: 0.14.3
bindata:
git: https://github.com/spider-gazelle/bindata.git
version: 1.9.1
commander:
git: https://github.com/mrrooijen/commander.git
version: 0.4.0
compiled_license:
git: https://github.com/grimmigerfuchs/compiled_license.git
version: 2.0.0
crystal-argon2:
git: https://github.com/axentro/crystal-argon2.git
version: 0.1.3
db:
git: https://github.com/crystal-lang/crystal-db.git
version: 0.10.1
granite:
git: https://github.com/amberframework/granite.git
version: 0.23.0
graphql:
git: https://github.com/graphql-crystal/graphql.git
version: 0.3.2+git.commit.8c6dc73c0c898ca511d9d12efefca7c837c25946
jwt:
git: https://github.com/crystal-community/jwt.git
version: 1.6.0
openssl_ext:
git: https://github.com/spider-gazelle/openssl_ext.git
version: 2.1.5
pg:
git: https://github.com/will/crystal-pg.git
version: 0.24.0
pretty:
git: https://github.com/maiha/pretty.cr.git
version: 1.1.1
seg:
git: https://github.com/soveran/seg.git
version: 0.1.0+git.commit.b404c986b5a894830878cd6b296df53ef34d9cfa
shard:
git: https://github.com/maiha/shard.cr.git
version: 0.3.1
toro:
git: https://github.com/soveran/toro.git
version: 0.4.2+git.commit.d2103dddcc9cc757e6b82613e040b1d50663a2e6

40
docker/backend/shard.yml Normal file
View file

@ -0,0 +1,40 @@
name: mw
version: 0.1.0
authors:
- Dominic Grimm <dominic.grimm@gmail.com>
targets:
mw:
main: src/app.cr
crystal: 1.2.2
dependencies:
granite:
github: amberframework/granite
pg:
github: will/crystal-pg
crystal-argon2:
github: Axentro/crystal-argon2
graphql:
github: graphql-crystal/graphql
branch: master
jwt:
github: crystal-community/jwt
CrystalEmail:
git: https://git.sceptique.eu/Sceptique/CrystalEmail
branch: master
shard:
github: maiha/shard.cr
toro:
github: soveran/toro
branch: master
commander:
github: mrrooijen/commander
compiled_license:
github: grimmigerFuchs/compiled_license
development_dependencies:
ameba:
github: crystal-ameba/ameba

94
docker/backend/src/app.cr Normal file
View file

@ -0,0 +1,94 @@
require "commander"
require "./mw.cr"
def input(prompt : String) : String
print prompt
(gets || "").chomp.strip
end
cli = Commander::Command.new do |cmd|
cmd.use = "mw"
cmd.long = "Mentorenwahl"
cmd.run do
MW.run
end
cmd.commands.add do |c|
c.use = "version"
c.long = "Prints the current version"
c.run do
puts MW::VERSION
end
end
cmd.commands.add do |c|
c.use = "authors"
c.long = "Prints the authors"
c.run do
puts MW::AUTHORS.join(",\n")
end
end
cmd.commands.add do |c|
c.use = "licenses"
c.long = "Prints the licenses of libraries used"
c.run do
puts MW::LICENSES
end
end
cmd.commands.add do |c|
c.use = "seed"
c.long = "Seeds the database with required data"
c.run do
puts "Seeding database with admin user..."
# firstname = input "Firstname: "
# lastname = input "Lastname: "
# email = input "Email: "
# password = input "Password: "
# password_confirmation = input "Password confirmation: "
data = {
"firstname" => input("Firstname: "),
"lastname" => input("Lastname: "),
"email" => input("Email: "),
"password" => MW::Auth.hash_password(input("Password: ")),
"role" => MW::Db::UserRole::Admin.to_s,
}
password_confirmation = input("Password confirmation: ")
if data.values.any?(&.empty?)
abort "Values can't be empty!"
elsif !MW::Auth.verify_password?(password_confirmation, data["password"])
abort "Passwords do not match!"
end
puts "---"
data.each { |k, v| puts "#{k.capitalize}: #{v}" }
puts "---"
unless input("Are you sure? (y/n) ") == "y"
abort "Aborted!"
end
puts "Seeding database with admin user..."
user = MW::Db::User.create!(data)
admin = MW::Db::Admin.create!(user_id: user.id)
puts "Done!"
puts "---"
puts "User id: #{user.id}"
puts "Admin id: #{admin.id}"
puts "Token: #{MW::Auth.create_user_jwt(user_id: user.id.not_nil!)}"
puts "---"
end
end
end
Commander.run(cli, ARGV)

1
docker/backend/src/mw.cr Normal file
View file

@ -0,0 +1 @@
require "./mw/*"

View file

@ -0,0 +1,43 @@
require "crystal-argon2"
require "jwt"
module MW
module Auth
extend self
BEARER = "Bearer "
def hash_password(password : String) : String
Argon2::Password.create(password)
end
def verify_password?(password : String, hash : String) : Bool
!!Argon2::Password.verify_password(password, hash)
rescue
false
end
private def create_jwt(data, expiration : Int) : String
payload = {
"data" => data.to_h,
"exp" => expiration,
}
JWT.encode(payload.to_h, ENV_REQUESTER["BACKEND_JWT_SECRET"], JWT::Algorithm::HS256)
end
def create_user_jwt(user_id : Int, expiration : Int = (Time.utc + Time::Span.new(days: 1)).to_unix) : String
create_jwt({user_id: user_id}, expiration)
end
def decode_jwt(jwt : String) : JSON::Any
JWT.decode(jwt, ENV_REQUESTER["BACKEND_JWT_SECRET"], JWT::Algorithm::HS256)[0]
end
def decode_jwt?(jwt : String) : JSON::Any?
decode_jwt(jwt)
rescue
nil
end
end
end

View file

@ -0,0 +1,5 @@
require "shard"
module MW
AUTHORS = Shard.authors
end

View file

@ -0,0 +1,88 @@
require "http/request"
require "graphql"
require "granite"
module MW
class Context < GraphQL::Context
getter user : Db::User?
getter role : Schema::UserRole?
getter external : (Db::Admin | Db::Teacher | Db::Student)?
# ameba:disable Metrics/CyclomaticComplexity
def initialize(request : HTTP::Request, *rest)
super(*rest)
token = request.headers["Authorization"]?
if token && token[..Auth::BEARER.size - 1] == Auth::BEARER
payload = Auth.decode_jwt?(token[Auth::BEARER.size..])
return unless payload
data = payload["data"].as_h
@user = Db::User.find(data["user_id"].as_i)
return if @user.nil? || @user.not_nil!.blocked
if @user
tmp_role = Schema::UserRole.parse?(@user.as(Db::User).role).not_nil!
if tmp_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
@role = tmp_role if @external
end
end
end
end
def authenticated? : Bool
!(@role.nil? && @external.nil?)
end
def authenticated! : Bool
raise "Not authenticated" unless authenticated?
true
end
def role?(role : Schema::UserRole) : Bool
@role == role == case @external
when Db::Admin
Schema::UserRole::Admin
when Db::Teacher
Schema::UserRole::Teacher
when Db::Student
Schema::UserRole::Student
end
end
def role!(role : Schema::UserRole) : Bool
raise "Invalid permissions" unless role? role
true
end
def admin? : Bool
role? Schema::UserRole::Admin
end
def admin! : Bool
role! Schema::UserRole::Admin
end
def self.db_eq_role?(external : Granite::Base, role : Schema::UserRole) : Bool
role == case external
when Db::Admin
Schema::UserRole::Admin
when Db::Teacher
Schema::UserRole::Teacher
when Db::Student
Schema::UserRole::Student
end
end
end
end

View file

@ -0,0 +1,10 @@
require "granite"
require "granite/adapter/pg"
require "./db/*"
module MW
module Db
Granite::Connections << Granite::Adapter::Pg.new(name: "pg", url: ENV_REQUESTER["BACKEND_DATABASE_URL"])
end
end

View file

@ -0,0 +1,13 @@
require "granite"
module MW
module Db
class Admin < Granite::Base
table admins
belongs_to :user
column id : Int64, primary: true
end
end
end

View file

@ -0,0 +1,15 @@
require "granite"
module MW
module Db
class Student < Granite::Base
table students
belongs_to :user
has_one :vote
column id : Int64, primary: true
column skif : Bool
end
end
end

View file

@ -0,0 +1,15 @@
require "granite"
module MW
module Db
class Teacher < Granite::Base
table teachers
belongs_to :user
has_many teacher_votes : TeacherVote
column id : Int64, primary: true
column max_students : Int32
end
end
end

View file

@ -0,0 +1,31 @@
require "granite"
module MW
module Db
class TeacherVote < Granite::Base
table teacher_votes
belongs_to :vote
belongs_to :teacher
column id : Int64, primary: true
column priority : Int32
validate :teacher, "must be present" do |teacher_vote|
!teacher_vote.teacher.nil?
end
validate :teacher, "must be student unique" do |teacher_vote|
self.where(vote_id: teacher_vote.vote.id, teacher_id: teacher_vote.teacher.not_nil!.id).count == 0
end
validate :priority, "must be greater than 0" 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
end
end
end

View file

@ -0,0 +1,47 @@
require "CrystalEmail"
require "granite"
module MW
module Db
class User < Granite::Base
table users
has_one :admin
has_one :teacher
has_one :student
column id : Int64, primary: true
column firstname : String
column lastname : String
column email : String
column password : String
column role : String
column blocked : Bool = false
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)
end
validate :role, "user external needs to be a valid role" do |user|
if user.admin.nil? && user.teacher.nil? && user.student.nil?
true
else
!!case UserRole.parse(user.role)
when UserRole::Admin
user.admin && user.teacher.nil? && user.student.nil?
when UserRole::Teacher
user.admin.nil? && user.teacher && user.student.nil?
when UserRole::Student
user.admin.nil? && user.teacher.nil? && user.student
else
false
end
end
end
end
end
end

View file

@ -0,0 +1,9 @@
module MW
module Db
enum UserRole
Admin
Teacher
Student
end
end
end

View file

@ -0,0 +1,14 @@
require "granite"
module MW
module Db
class Vote < Granite::Base
table votes
belongs_to :student
has_many teacher_votes : TeacherVote
column id : Int64, primary: true
end
end
end

View file

@ -0,0 +1,43 @@
module MW
class EnvRequester
private property keys
def initialize(@keys = {} of String => String?)
end
def initialize(keys : Array(String))
@keys = {} of String => String?
keys.each { |k| self.<< k }
end
def <<(key : String) : self
@keys[key] = ENV[key]?
self
end
def []?(key : String) : String?
if @keys.has_key?(key)
@keys[key]?
end
end
def [](key : String) : String
if @keys.has_key?(key)
val = @keys[key]?
raise "ENV[#{key}] is nil" unless val
val
else
raise "No such key: #{key}"
end
end
end
ENV_REQUESTER = EnvRequester.new([
"BACKEND_DATABASE_URL",
"BACKEND_ADMIN_EMAIL",
"BACKEND_ADMIN_PASSWORD",
"BACKEND_JWT_SECRET",
])
end

View file

@ -0,0 +1,5 @@
require "compiled_license"
module MW
LICENSES = CompiledLicense::LICENSES
end

View file

@ -0,0 +1,13 @@
require "http/server"
module MW
extend self
def run : Nil
Server.run(8080, [HTTP::LogHandler.new, HTTP::ErrorHandler.new]) do |server|
address = server.bind_tcp("0.0.0.0", 8080, true)
puts "Listening on http://#{address}"
server.listen
end
end
end

View file

@ -0,0 +1,10 @@
require "graphql"
require "./schema/helpers"
require "./schema/*"
module MW
module Schema
SCHEMA = GraphQL::Schema.new(Query.new, Mutation.new)
end
end

View file

@ -0,0 +1,26 @@
require "graphql"
module MW
module Schema
@[GraphQL::Object]
class Admin < GraphQL::BaseObject
include Helpers::DbObject
db_object Db::Admin
@[GraphQL::Field]
def user : User
User.new(find!.user)
end
end
@[GraphQL::InputObject]
class AdminCreateInput < GraphQL::BaseInputObject
getter user_id
@[GraphQL::Field]
def initialize(@user_id : Int32)
end
end
end
end

View file

@ -0,0 +1,65 @@
require "graphql"
module MW
module Schema
module Helpers
module ObjectMacros
macro field(type)
property {{ type.var }} {% if type.value %} = {{ type.value }}{% end %}
@[GraphQL::Field]
def {{ type.var }} : {{ type.type }}
@{{ type.var }}
end
end
end
module ObjectDbInit
macro db_init(type)
def initialize(obj : {{ type }})
initialize(obj.id.not_nil!)
end
end
end
module ObjectFinders
macro finders(type)
def find : {{ type }}?
{{ type }}.find(@id)
end
def find! : {{ type }}
obj = find
raise "#{{{ type }}} not found" unless obj
obj
end
end
end
module DbObject
macro db_object(type)
include ::MW::Schema::Helpers::ObjectDbInit
include ::MW::Schema::Helpers::ObjectFinders
db_init {{ type }}
finders {{ type }}
property id
def initialize(@id : Int32)
end
def initialize(obj : {{ type }})
@id = obj.id.not_nil!.to_i
end
@[GraphQL::Field]
def id : Int32
@id
end
end
end
end
end
end

View file

@ -0,0 +1,136 @@
require "graphql"
module MW
module Schema
@[GraphQL::Object]
class Mutation < GraphQL::BaseMutation
@[GraphQL::Field]
def login(input : LoginInput) : LoginPayload
user = Db::User.find_by(email: input.email)
raise "Auth failed" unless user && Auth.verify_password?(input.password, user.password)
LoginPayload.new(
user: User.new(user),
token: Auth.create_user_jwt(user.id.not_nil!.to_i),
)
end
@[GraphQL::Field]
def create_user(context : Context, input : UserCreateInput) : User
context.admin!
user = Db::User.create!(
firstname: input.firstname,
lastname: input.lastname,
email: input.email,
password: Auth.hash_password(input.password),
role: input.role.to_s,
blocked: input.blocked,
)
if 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
@[GraphQL::Field]
def delete_user(context : Context, id : Int32) : Int32
context.admin!
user = Db::User.find!(id)
user.destroy!
id
end
@[GraphQL::Field]
def create_admin(context : Context, input : AdminCreateInput) : Admin
context.admin!
admin = Db::Admin.create!(user_id: input.user_id)
Admin.new(admin)
end
@[GraphQL::Field]
def delete_admin(context : Context, id : Int32) : Int32
context.admin!
admin = Db::Admin.find!(id)
admin.destroy!
id
end
@[GraphQL::Field]
def create_teacher(context : Context, input : TeacherCreateInput) : Teacher
context.admin!
teacher = Db::Teacher.create!(user_id: input.user_id, max_students: input.max_students)
Teacher.new(teacher)
end
@[GraphQL::Field]
def delete_teacher(context : Context, id : Int32) : Int32
context.admin!
teacher = Db::Teacher.find!(id)
teacher.destroy!
id
end
@[GraphQL::Field]
def create_student(context : Context, input : StudentCreateInput) : Student
context.admin!
user = Db::User.find!(input.user_id)
raise "User not a student" unless UserRole.parse(user.role) == UserRole::Student
student = Db::Student.create!(user_id: user.id)
Student.new(student)
end
@[GraphQL::Field]
def delete_student(context : Context, id : Int32) : Int32
context.admin!
student = Db::Student.find!(id)
student.destroy!
id
end
@[GraphQL::Field]
def create_vote(context : Context, input : VoteCreateInput) : Vote
# context.admin!
# student = Db::Student.find!(input.student_id)
# # student.vote = Db::Vote.new(student_id: student.id)
# # student.save!
# pp! Db::Vote.create!(student_id: student.id.not_nil!)
# pp! student
# pp! student.vote
# input.teacher_ids.not_nil!.each_with_index do |t_id, i|
# Db::TeacherVote.create!(vote_id: student.vote.not_nil!.id.not_nil!, teacher_id: t_id, priority: i)
# end if input.teacher_ids
# pp! student.vote.not_nil!.teacher_votes.to_a
# Vote.new(student.vote.not_nil!)
context.role! UserRole::Student
student = context.external.not_nil!.as(Db::Student)
vote = Db::Vote.create!(student_id: student.id)
Vote.new(vote)
end
end
end
end

View file

@ -0,0 +1,87 @@
require "graphql"
module MW
module Schema
@[GraphQL::Object]
class Query < GraphQL::BaseQuery
@[GraphQL::Field]
def ok : Bool
true
end
@[GraphQL::Field]
def me(context : Context) : User
context.authenticated!
User.new(context.user.not_nil!)
end
@[GraphQL::Field]
def user(context : Context, id : Int32) : User
context.admin!
User.new(id)
end
@[GraphQL::Field({UserRole::Admin})]
def users(context : Context) : Array(User)
puts "AUTHORIZED ALLOWED ROLES: #{{{ @def.annotation(GraphQL::Field)[0] }}}"
context.admin!
Db::User.all.map { |user| User.new(user) }
end
@[GraphQL::Field]
def admin(context : Context, id : Int32) : Admin
context.admin!
Admin.new(Db::Admin.find!(id))
end
@[GraphQL::Field]
def admins(context : Context) : Array(Admin)
context.admin!
Db::Admin.all.map { |admin| Admin.new(admin) }
end
@[GraphQL::Field]
def teacher(id : Int32) : Teacher
Teacher.new(Db::Teacher.find!(id))
end
@[GraphQL::Field]
def teachers : Array(Teacher)
Db::Teacher.all.map { |teacher| Teacher.new(teacher) }
end
@[GraphQL::Field]
def student(context : Context, id : Int32) : Student
context.admin!
Student.new(Db::Student.find!(id))
end
@[GraphQL::Field]
def students(context : Context) : Array(Student)
context.admin!
Db::Student.all.map { |student| Student.new(student) }
end
@[GraphQL::Field]
def vote(context : Context, id : Int32) : Vote
context.admin!
Vote.new(Db::Vote.find!(id))
end
@[GraphQL::Field]
def votes(context : Context) : Array(Vote)
context.admin!
Db::Vote.all.map { |vote| Vote.new(vote) }
end
end
end
end

View file

@ -0,0 +1,48 @@
require "graphql"
module MW
module Schema
@[GraphQL::Object]
class Student < GraphQL::BaseObject
include Helpers::DbObject
db_object Db::Student
@[GraphQL::Field]
def user : User
User.new(find!.user)
end
@[GraphQL::Field]
def skif : Bool
find!.skif
end
@[GraphQL::Field]
def vote : Vote?
vote = find!.vote
Vote.new(vote) if vote
end
end
@[GraphQL::InputObject]
class StudentInput < GraphQL::BaseInputObject
getter skif
@[GraphQL::Field]
def initialize(@skif : Bool)
end
end
@[GraphQL::InputObject]
class StudentCreateInput < StudentInput
getter user_id
@[GraphQL::Field]
def initialize(@user_id : Int32, skif : Bool)
super(skif)
end
end
end
end

View file

@ -0,0 +1,43 @@
require "graphql"
module MW
module Schema
@[GraphQL::Object]
class Teacher < GraphQL::BaseObject
include Helpers::DbObject
db_object Db::Teacher
@[GraphQL::Field]
def user : User
User.new(find!.user)
end
@[GraphQL::Field]
def max_students(context : Context) : Int32
context.admin!
find!.max_students
end
end
@[GraphQL::InputObject]
class TeacherInput < GraphQL::BaseInputObject
getter max_students
@[GraphQL::Field]
def initialize(@max_students : Int32)
end
end
@[GraphQL::InputObject]
class TeacherCreateInput < TeacherInput
getter user_id
@[GraphQL::Field]
def initialize(@user_id : Int32, max_students : Int32)
super(max_students)
end
end
end
end

View file

@ -0,0 +1,33 @@
require "graphql"
module MW
module Schema
@[GraphQL::Object]
class TeacherVote < GraphQL::BaseObject
include Helpers::DbObject
db_object Db::TeacherVote
@[GraphQL::Field]
def teacher : Teacher
Teacher.new(find!.teacher.not_nil!)
end
@[GraphQL::Field]
def priority : Int32
find!.priority
end
end
@[GraphQL::InputObject]
class TeacherVoteCreateInput < GraphQL::BaseInputObject
getter vote_id
getter teacher_id
getter priority
@[GraphQL::Field]
def initialize(@vote_id : Int32, @teacher_id : Int32, @priority : Int32)
end
end
end
end

View file

@ -0,0 +1,157 @@
require "graphql"
module MW
module Schema
@[GraphQL::Enum]
enum UserRole
Admin
Teacher
Student
end
@[GraphQL::Object]
class User < GraphQL::BaseObject
include Helpers::DbObject
db_object Db::User
@[GraphQL::Field]
def firstname : String
find!.firstname
end
@[GraphQL::Field]
def lastname : String
find!.lastname
end
@[GraphQL::Field]
def email : String
find!.email
end
@[GraphQL::Field]
def role : UserRole
role = Db::UserRole.parse(find!.role)
case role
when Db::UserRole::Admin
UserRole::Admin
when Db::UserRole::Teacher
UserRole::Teacher
when Db::UserRole::Student
UserRole::Student
else
raise "Unknown role: #{role}"
end
end
@[GraphQL::Field]
def external_id : Int32?
case Db::UserRole.parse(find!.role)
when Db::UserRole::Admin
find!.admin
when Db::UserRole::Teacher
find!.teacher
when Db::UserRole::Student
find!.student
end.not_nil!.id.not_nil!.to_i
rescue NilAssertionError
nil
end
@[GraphQL::Field]
def admin : Admin?
admin = find!.admin
if admin
Admin.new(admin)
end
end
@[GraphQL::Field]
def teacher : Teacher?
teacher = find!.teacher
if teacher
Teacher.new(teacher)
end
end
@[GraphQL::Field]
def student : Student?
student = find!.student
if student
Student.new(student)
end
end
@[GraphQL::Field]
def blocked : Bool
find!.blocked
end
end
@[GraphQL::InputObject]
class UserCreateInput < GraphQL::BaseInputObject
getter firstname
getter lastname
getter email
getter password
getter role
getter teacher
getter student
getter blocked
@[GraphQL::Field]
def initialize(
@firstname : String,
@lastname : String,
@email : String,
@password : String,
@role : UserRole,
@teacher : TeacherInput? = nil,
@student : StudentInput? = nil,
@blocked : Bool = false
)
end
end
@[GraphQL::InputObject]
class LoginInput < GraphQL::BaseInputObject
getter email
getter password
@[GraphQL::Field]
def initialize(
@email : String,
@password : String
)
end
end
@[GraphQL::Object]
class LoginPayload < GraphQL::BaseObject
property user
property token
def initialize(
@user : User,
@token : String
)
end
@[GraphQL::Field]
def user : User
@user
end
@[GraphQL::Field]
def token : String
@token
end
@[GraphQL::Field]
def bearer : String
Auth::BEARER + @token
end
end
end
end

View file

@ -0,0 +1,31 @@
require "graphql"
module MW
module Schema
@[GraphQL::Object]
class Vote < GraphQL::BaseObject
include Helpers::DbObject
db_object Db::Vote
@[GraphQL::Field]
def student : Student
Student.new(find!.student)
end
@[GraphQL::Field]
def teacher_votes : Array(TeacherVote)
find!.teacher_votes.map { |tv| TeacherVote.new(tv) }
end
end
@[GraphQL::InputObject]
class VoteCreateInput < GraphQL::BaseInputObject
getter teacher_ids
@[GraphQL::Field]
def initialize(@teacher_ids : Array(String))
end
end
end
end

View file

@ -0,0 +1,31 @@
require "toro"
require "json"
module MW
class Server < Toro::Router
private struct GraphQLData
include JSON::Serializable
property query : String
property variables : Hash(String, JSON::Any)?
property operation_name : String?
end
def routes
on "graphql" do
post do
content_type "application/json"
data = GraphQLData.from_json(context.request.body.not_nil!.gets.not_nil!)
write Schema::SCHEMA.execute(
data.query,
data.variables,
data.operation_name,
Context.new(context.request)
)
end
end
end
end
end

View file

@ -0,0 +1,5 @@
require "shard"
module MW
VERSION = Shard.version
end

View file

@ -0,0 +1,129 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
.prettierrc

127
docker/backend_old/.gitignore vendored Normal file
View file

@ -0,0 +1,127 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*

View file

@ -0,0 +1 @@
{}

View file

@ -0,0 +1,23 @@
FROM node:17-alpine3.12 as deps
WORKDIR /app
COPY ./package.json ./yarn.lock ./
RUN yarn --frozen-lockfile
FROM node:17-alpine3.12 as builder
WORKDIR /app
COPY --from=deps /app/package.json /app/yarn.lock ./
COPY --from=deps /app/node_modules ./node_modules
COPY ./prisma ./prisma
RUN npx prisma generate
COPY . .
RUN yarn build
RUN npm prune --production
FROM node:17-alpine3.12 as runner
WORKDIR /app
COPY --from=builder /app/package.json .
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/prisma ./prisma
EXPOSE 8080
CMD ["yarn", "start"]

View file

@ -0,0 +1,28 @@
{
"name": "backend",
"version": "1.0.0",
"private": true,
"main": "dist/index.js",
"license": "MIT",
"scripts": {
"start": "node --trace-warnings --enable-source-maps dist/index.js",
"build": "rm -rf dist/ && tsc"
},
"devDependencies": {
"@types/node": "^17.0.5",
"typescript": "^4.5.4"
},
"dependencies": {
"@envelop/apollo-server-errors": "^1.2.1",
"@envelop/graphql-jit": "^1.3.1",
"@envelop/resource-limitations": "^0.4.1",
"@prisma/client": "^3.7.0",
"argon2": "^0.28.3",
"graphql": "15",
"graphql-scalars": "^1.14.1",
"graphql-yoga": "^2.0.0-alpha.6",
"nexus": "^1.1.0",
"nexus-prisma": "^0.35.0",
"prisma": "^3.7.0"
}
}

View file

@ -0,0 +1,47 @@
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
generator client {
provider = "prisma-client-js"
}
generator nexusPrisma {
provider = "nexus-prisma"
}
datasource db {
provider = "postgresql"
url = env("BACKEND_DATABASE_URL")
}
enum Role {
SUPER_ADMIN
ADMIN
TEACHER
STUDENT
}
model User {
id String @id @default(uuid())
firstname String
lastname String
email String @unique
password String
blocked Boolean @default(false)
role Role
teacher Teacher?
student Student?
}
model Teacher {
id String @id @default(uuid())
user User @relation(fields: [userId], references: [id])
userId String @unique
maxStudents Int
}
model Student {
id String @id @default(uuid())
user User @relation(fields: [userId], references: [id])
userId String @unique
}

View file

@ -0,0 +1,7 @@
import { PrismaClient } from "@prisma/client";
const client = new PrismaClient();
export class Context {
constructor(public readonly req: unknown, public readonly prisma = client) {}
}

View file

@ -0,0 +1,34 @@
import { createServer, useMaskedErrors } from "graphql-yoga";
import { useGraphQlJit } from "@envelop/graphql-jit";
import { useResourceLimitations } from "@envelop/resource-limitations";
import { useApolloServerErrors } from "@envelop/apollo-server-errors";
import { schema } from "./schema";
import { Context } from "./context";
const isProduction = process.env.NODE_ENV !== "development";
console.log(`NODE_ENV: ${process.env.NODE_ENV}`);
console.log("Starting server...");
const server = createServer({
endpoint: "/",
port: 8080,
graphiql: false,
schema: schema as any,
async context(req) {
return new Context(req);
},
plugins: [
useGraphQlJit(),
useResourceLimitations({
extensions: true,
}),
useMaskedErrors(),
useApolloServerErrors({
debug: !isProduction,
}),
],
});
server.start(() => console.log("Server is running on http://localhost:8080"));

View file

@ -0,0 +1,10 @@
import { makeSchema } from "nexus";
import { Query } from "./query";
import { Mutation } from "./mutation";
import { User, UserCreateInput } from "./user";
import { Role } from "./role";
export const schema = makeSchema({
types: [Query, Mutation, User, UserCreateInput, Role],
});

View file

@ -0,0 +1,27 @@
import { mutationType, arg, nonNull } from "nexus";
import * as argon2 from "argon2";
import { Context } from "../context";
import { User, UserCreateInput } from "./user";
export const Mutation = mutationType({
definition(t) {
t.nonNull.field("createUser", {
type: User,
args: {
input: nonNull(arg({ type: UserCreateInput })),
},
async resolve(_root, args, ctx: Context) {
return ctx.prisma.user.create({
data: {
firstname: args.input.firstname,
lastname: args.input.lastname,
email: args.input.email,
password: await argon2.hash(args.input.password),
role: args.input.role,
},
});
},
});
},
});

View file

@ -0,0 +1,33 @@
import { queryType, idArg, nonNull } from "nexus";
import { Context } from "../context";
import { User } from "./user";
export const Query = queryType({
definition(t) {
t.nonNull.boolean("ok", {
resolve() {
return true;
},
});
t.field("user", {
type: User,
args: {
id: nonNull(idArg()),
},
resolve(_root, args, ctx: Context) {
return ctx.prisma.user.findUnique({
where: {
id: args.id,
},
});
},
});
t.nonNull.list.nonNull.field("users", {
type: User,
resolve(_root, _args, ctx: Context) {
return ctx.prisma.user.findMany();
},
});
},
});

View file

@ -0,0 +1,7 @@
import { enumType } from "nexus";
import * as nPrisma from "nexus-prisma";
export const Role = enumType({
name: nPrisma.Role.name,
members: nPrisma.Role.members,
});

View file

@ -0,0 +1,28 @@
import { objectType, inputObjectType } from "nexus";
import * as nPrisma from "nexus-prisma";
import { Role } from "./role";
export const User = objectType({
name: nPrisma.User.$name,
definition(t) {
t.field(nPrisma.User.id);
t.field(nPrisma.User.firstname);
t.field(nPrisma.User.lastname);
t.field(nPrisma.User.email);
t.field(nPrisma.User.role);
t.field(nPrisma.User.blocked);
},
});
export const UserCreateInput = inputObjectType({
name: "UserCreateInput",
definition(t) {
t.nonNull.string("firstname");
t.nonNull.string("lastname");
t.nonNull.string("email");
t.nonNull.string("password");
t.nonNull.field("role", { type: Role });
t.boolean("blocked", { default: false });
},
});

View file

@ -0,0 +1,103 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Projects */
// "incremental": true, /* Enable incremental compilation */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "es6" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
"lib": [
"ESNext"
] /* Specify a set of bundled library declaration files that describe the target runtime environment. */,
// "jsx": "preserve", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */
// "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
/* Modules */
"module": "commonjs" /* Specify what module code is generated. */,
"rootDir": "./src" /* Specify the root folder within your source files. */,
// "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "resolveJsonModule": true, /* Enable importing .json files */
// "noResolve": true, /* Disallow `import`s, `require`s or `<reference>`s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
/* Emit */
"declaration": true /* Generate .d.ts files from TypeScript and JavaScript files in your project. */,
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
"sourceMap": true /* Create source map files for emitted JavaScript files. */,
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
"outDir": "./dist" /* Specify an output folder for all emitted files. */,
// "removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */,
// "newLine": "crlf", /* Set the newline character for emitting files. */
// "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
// "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */,
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
/* Type Checking */
"strict": true /* Enable all strict type-checking options. */,
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */
// "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
// "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */
// "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
// "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
}
}

1380
docker/backend_old/yarn.lock Normal file

File diff suppressed because it is too large Load diff

3
scripts/micrate.sh Normal file
View file

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