Documented Api
This commit is contained in:
parent
eed1042529
commit
046c33f45f
|
@ -1 +1,5 @@
|
|||
require "./api/*"
|
||||
|
||||
# Api module
|
||||
module Backend::Api
|
||||
end
|
||||
|
|
|
@ -2,28 +2,36 @@ require "jwt"
|
|||
|
||||
module Backend
|
||||
module Api
|
||||
# Authorization and authentication utilities
|
||||
module Auth
|
||||
extend self
|
||||
|
||||
# Bearer token header
|
||||
BEARER = "Bearer "
|
||||
|
||||
# Creates raw JWT token
|
||||
#
|
||||
# WARNING: Always use a wrapper for this method
|
||||
private def create_jwt(data, expiration : Int) : String
|
||||
JWT.encode({"data" => data.to_h, "exp" => expiration}, Backend.config.api.jwt_secret, JWT::Algorithm::HS256)
|
||||
end
|
||||
|
||||
def create_user_jwt(user_id : Int, expiration : Int) : String
|
||||
create_jwt({user_id: user_id}, expiration)
|
||||
end
|
||||
|
||||
# Decodes JWT token
|
||||
def decode_jwt(jwt : String) : JSON::Any
|
||||
JWT.decode(jwt, Backend.config.api.jwt_secret, JWT::Algorithm::HS256)[0]
|
||||
end
|
||||
|
||||
# :ditto:
|
||||
def decode_jwt?(jwt : String) : JSON::Any?
|
||||
decode_jwt(jwt)
|
||||
rescue
|
||||
nil
|
||||
end
|
||||
|
||||
# Creates JWT token for user
|
||||
def create_user_jwt(user_id : Int, expiration : Int) : String
|
||||
create_jwt({user_id: user_id}, expiration)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,10 +4,18 @@ require "granite"
|
|||
|
||||
module Backend
|
||||
module Api
|
||||
# GraphQL request context class
|
||||
class Context < GraphQL::Context
|
||||
# Authenticated user
|
||||
getter user : Db::User?
|
||||
|
||||
# User is admin
|
||||
getter admin : Bool?
|
||||
|
||||
# User's role
|
||||
getter role : Schema::UserRole?
|
||||
|
||||
# User's external object
|
||||
getter external : (Db::Teacher | Db::Student)?
|
||||
|
||||
def initialize(request : HTTP::Request, max_complexity : Int32? = nil)
|
||||
|
@ -36,26 +44,31 @@ module Backend
|
|||
end
|
||||
end
|
||||
|
||||
# User is authenticated
|
||||
def authenticated? : Bool
|
||||
!!@user
|
||||
end
|
||||
|
||||
# :ditto:
|
||||
def authenticated! : Bool
|
||||
raise "Not authenticated" unless authenticated?
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
# User is admin
|
||||
def admin? : Bool
|
||||
authenticated? && !!@admin
|
||||
end
|
||||
|
||||
# :ditto:
|
||||
def admin! : Bool
|
||||
raise "Invalid permissions" unless admin?
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
# User's is one of *roles*
|
||||
def role?(external = true, *roles : Schema::UserRole) : Bool
|
||||
return false unless authenticated?
|
||||
|
||||
|
@ -75,51 +88,32 @@ module Backend
|
|||
false
|
||||
end
|
||||
|
||||
# :ditto:
|
||||
def role!(external = true, *roles : Schema::UserRole) : Bool
|
||||
raise "Invalid permissions" unless role? external, *roles
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
# User is teacher
|
||||
def teacher?(external = false) : Bool
|
||||
role? external, Schema::UserRole::Teacher
|
||||
end
|
||||
|
||||
# :ditto:
|
||||
def teacher! : Bool
|
||||
role! external, Schema::UserRole::Teacher
|
||||
end
|
||||
|
||||
# User is student
|
||||
def student?(external = false) : Bool
|
||||
role? external, Schema::UserRole::Student
|
||||
end
|
||||
|
||||
# :ditto:
|
||||
def student! : Bool
|
||||
role! external, Schema::UserRole::Student
|
||||
end
|
||||
|
||||
# private macro role_check(*roles)
|
||||
# {% for role in roles %}
|
||||
# {% name = role.names.last.underscore %}
|
||||
# def {{ name }}?(external = true) : Bool
|
||||
# role? external, {{ role }}
|
||||
# end
|
||||
|
||||
# def {{ name }}!(external = true) : Bool
|
||||
# role! external, {{ role }}
|
||||
# end
|
||||
# {% end %}
|
||||
# end
|
||||
|
||||
# role_check Schema::UserRole::Teacher, Schema::UserRole::Student
|
||||
|
||||
# def self.db_eq_role?(external : Granite::Base, role : Schema::UserRole) : Bool
|
||||
# role == case external
|
||||
# when Db::Teacher
|
||||
# Schema::UserRole::Teacher
|
||||
# when Db::Student
|
||||
# Schema::UserRole::Student
|
||||
# end
|
||||
# end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,6 +4,7 @@ module Backend
|
|||
module Api
|
||||
extend self
|
||||
|
||||
# Runs API
|
||||
def run : Nil
|
||||
WebServer.new.run
|
||||
end
|
||||
|
|
|
@ -5,7 +5,9 @@ require "./schema/*"
|
|||
|
||||
module Backend
|
||||
module Api
|
||||
# GraphQL schema definitions
|
||||
module Schema
|
||||
# Compiled GraphQL schema
|
||||
SCHEMA = GraphQL::Schema.new(Query.new, Mutation.new)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
module Backend
|
||||
module Api
|
||||
module Schema
|
||||
# Schema helper macros
|
||||
module Helpers
|
||||
# Object helpers
|
||||
module ObjectMacros
|
||||
# Defines field property and GraphQL specific getter
|
||||
macro field(type)
|
||||
property {{ type.var }} {% if type.value %} = {{ type.value }}{% end %}
|
||||
|
||||
|
@ -13,7 +16,9 @@ module Backend
|
|||
end
|
||||
end
|
||||
|
||||
# DB model leverage helpers
|
||||
module ObjectDbInit
|
||||
# Defines a DB model specific initializer
|
||||
macro db_init(type)
|
||||
def initialize(obj : {{ type }})
|
||||
initialize(obj.id.not_nil!)
|
||||
|
@ -21,12 +26,16 @@ module Backend
|
|||
end
|
||||
end
|
||||
|
||||
# DB model finder helpers
|
||||
module ObjectFinders
|
||||
# Defines finder
|
||||
macro finders(type)
|
||||
# Finds object by ID
|
||||
def find : {{ type }}?
|
||||
{{ type }}.find(@id)
|
||||
end
|
||||
|
||||
# :ditto:
|
||||
def find! : {{ type }}
|
||||
obj = find
|
||||
raise "#{{{ type }}} not found" unless obj
|
||||
|
@ -36,8 +45,12 @@ module Backend
|
|||
end
|
||||
end
|
||||
|
||||
# DB model field helpers
|
||||
module DbObject
|
||||
# Defines DB model field helper functions
|
||||
macro db_object(type)
|
||||
{% space_name = type.names.last.underscore.gsub(/_/, " ").capitalize %}
|
||||
|
||||
include ::Backend::Api::Schema::Helpers::ObjectDbInit
|
||||
include ::Backend::Api::Schema::Helpers::ObjectFinders
|
||||
|
||||
|
@ -54,6 +67,7 @@ module Backend
|
|||
end
|
||||
|
||||
@[GraphQL::Field]
|
||||
# {{ space_name }}'s ID
|
||||
def id : Int32
|
||||
@id
|
||||
end
|
||||
|
|
|
@ -6,6 +6,7 @@ module Backend
|
|||
@[GraphQL::Object]
|
||||
class Mutation < GraphQL::BaseMutation
|
||||
@[GraphQL::Field]
|
||||
# Logs in as *username* with credential *password*
|
||||
def login(username : String, password : String) : LoginPayload
|
||||
raise "Auth failed" if username.empty? || password.empty?
|
||||
|
||||
|
@ -22,6 +23,7 @@ module Backend
|
|||
end
|
||||
|
||||
@[GraphQL::Field]
|
||||
# Creates user
|
||||
def create_user(context : Context, input : UserCreateInput, check_ldap : Bool = true) : User
|
||||
context.admin!
|
||||
|
||||
|
@ -36,6 +38,7 @@ module Backend
|
|||
end
|
||||
|
||||
@[GraphQL::Field]
|
||||
# Deletes user by ID
|
||||
def delete_user(context : Context, id : Int32) : Int32
|
||||
context.admin!
|
||||
|
||||
|
@ -46,6 +49,7 @@ module Backend
|
|||
end
|
||||
|
||||
@[GraphQL::Field]
|
||||
# Sends all unregistered teachers a registration email
|
||||
def send_teachers_registration_email(context : Context) : Bool
|
||||
context.admin!
|
||||
|
||||
|
@ -55,6 +59,7 @@ module Backend
|
|||
end
|
||||
|
||||
@[GraphQL::Field]
|
||||
# Creates teacher
|
||||
def create_teacher(context : Context, input : TeacherCreateInput) : Teacher
|
||||
context.admin!
|
||||
|
||||
|
@ -63,6 +68,7 @@ module Backend
|
|||
end
|
||||
|
||||
@[GraphQL::Field]
|
||||
# Deletes teacher by ID
|
||||
def delete_teacher(context : Context, id : Int32) : Int32
|
||||
context.admin!
|
||||
|
||||
|
@ -73,6 +79,7 @@ module Backend
|
|||
end
|
||||
|
||||
@[GraphQL::Field]
|
||||
# Self register as teacher
|
||||
def register_teacher(context : Context, input : TeacherInput) : Teacher
|
||||
context.teacher? external: false
|
||||
|
||||
|
@ -82,6 +89,7 @@ module Backend
|
|||
end
|
||||
|
||||
@[GraphQL::Field]
|
||||
# Creates student
|
||||
def create_student(context : Context, input : StudentCreateInput) : Student
|
||||
context.admin!
|
||||
|
||||
|
@ -93,6 +101,7 @@ module Backend
|
|||
end
|
||||
|
||||
@[GraphQL::Field]
|
||||
# Deletes student by ID
|
||||
def delete_student(context : Context, id : Int32) : Int32
|
||||
context.admin!
|
||||
|
||||
|
@ -103,6 +112,7 @@ module Backend
|
|||
end
|
||||
|
||||
@[GraphQL::Field]
|
||||
# Creates vote for authenticated user's student
|
||||
def create_vote(context : Context, input : VoteCreateInput) : Vote
|
||||
context.student!
|
||||
|
||||
|
|
|
@ -4,11 +4,13 @@ module Backend
|
|||
@[GraphQL::Object]
|
||||
class Query < GraphQL::BaseQuery
|
||||
@[GraphQL::Field]
|
||||
# Retuns true
|
||||
def ok : Bool
|
||||
true
|
||||
end
|
||||
|
||||
@[GraphQL::Field]
|
||||
# Current authenticated user
|
||||
def me(context : Context) : User
|
||||
context.authenticated!
|
||||
|
||||
|
@ -16,6 +18,7 @@ module Backend
|
|||
end
|
||||
|
||||
@[GraphQL::Field]
|
||||
# User by ID
|
||||
def user(context : Context, id : Int32) : User
|
||||
context.admin!
|
||||
|
||||
|
@ -23,6 +26,7 @@ module Backend
|
|||
end
|
||||
|
||||
@[GraphQL::Field]
|
||||
# All users
|
||||
def users(context : Context) : Array(User)
|
||||
context.admin!
|
||||
|
||||
|
@ -30,6 +34,7 @@ module Backend
|
|||
end
|
||||
|
||||
@[GraphQL::Field]
|
||||
# All admins
|
||||
def admins(context : Context) : Array(User)
|
||||
context.admin!
|
||||
|
||||
|
@ -37,16 +42,19 @@ module Backend
|
|||
end
|
||||
|
||||
@[GraphQL::Field]
|
||||
# Teacher by ID
|
||||
def teacher(id : Int32) : Teacher
|
||||
Teacher.new(Db::Teacher.find!(id))
|
||||
end
|
||||
|
||||
@[GraphQL::Field]
|
||||
# All teachers
|
||||
def teachers : Array(Teacher)
|
||||
Db::Teacher.all.map { |teacher| Teacher.new(teacher) }
|
||||
end
|
||||
|
||||
@[GraphQL::Field]
|
||||
# Student by ID
|
||||
def student(context : Context, id : Int32) : Student
|
||||
context.admin!
|
||||
|
||||
|
@ -54,6 +62,7 @@ module Backend
|
|||
end
|
||||
|
||||
@[GraphQL::Field]
|
||||
# All students
|
||||
def students(context : Context) : Array(Student)
|
||||
context.admin!
|
||||
|
||||
|
@ -61,6 +70,7 @@ module Backend
|
|||
end
|
||||
|
||||
@[GraphQL::Field]
|
||||
# Vote by ID
|
||||
def vote(context : Context, id : Int32) : Vote
|
||||
context.admin!
|
||||
|
||||
|
@ -68,6 +78,7 @@ module Backend
|
|||
end
|
||||
|
||||
@[GraphQL::Field]
|
||||
# All votes
|
||||
def votes(context : Context) : Array(Vote)
|
||||
context.admin!
|
||||
|
||||
|
|
|
@ -2,31 +2,35 @@ module Backend
|
|||
module Api
|
||||
module Schema
|
||||
@[GraphQL::Object]
|
||||
# Student model
|
||||
class Student < GraphQL::BaseObject
|
||||
include Helpers::DbObject
|
||||
|
||||
db_object Db::Student
|
||||
|
||||
@[GraphQL::Field]
|
||||
# Student's user
|
||||
def user : User
|
||||
User.new(find!.user)
|
||||
end
|
||||
|
||||
@[GraphQL::Field]
|
||||
# Student at SKIF
|
||||
def skif : Bool
|
||||
find!.skif
|
||||
end
|
||||
|
||||
@[GraphQL::Field]
|
||||
# Student's vote
|
||||
def vote : Vote?
|
||||
vote = find!.vote
|
||||
|
||||
Vote.new(vote) if vote
|
||||
find!.vote.try { |v| Vote.new(v) }
|
||||
end
|
||||
end
|
||||
|
||||
@[GraphQL::InputObject]
|
||||
# Student base input
|
||||
class StudentInput < GraphQL::BaseInputObject
|
||||
# Student at SKIF
|
||||
getter skif
|
||||
|
||||
@[GraphQL::Field]
|
||||
|
@ -35,7 +39,9 @@ module Backend
|
|||
end
|
||||
|
||||
@[GraphQL::InputObject]
|
||||
# Student creation input
|
||||
class StudentCreateInput < StudentInput
|
||||
# Student's user ID
|
||||
getter user_id
|
||||
|
||||
@[GraphQL::Field]
|
||||
|
|
|
@ -2,30 +2,38 @@ module Backend
|
|||
module Api
|
||||
module Schema
|
||||
@[GraphQL::Object]
|
||||
# Teacher model
|
||||
class Teacher < GraphQL::BaseObject
|
||||
include Helpers::DbObject
|
||||
|
||||
db_object Db::Teacher
|
||||
|
||||
@[GraphQL::Field]
|
||||
# Teacher's user
|
||||
def user : User
|
||||
User.new(find!.user)
|
||||
end
|
||||
|
||||
@[GraphQL::Field]
|
||||
# Teacher's max students
|
||||
def max_students : Int32
|
||||
find!.max_students
|
||||
end
|
||||
|
||||
@[GraphQL::Field]
|
||||
# Teacher is at SKIF
|
||||
def skif : Bool
|
||||
find!.skif
|
||||
end
|
||||
end
|
||||
|
||||
@[GraphQL::InputObject]
|
||||
# Base teacher input
|
||||
class TeacherInput < GraphQL::BaseInputObject
|
||||
# Teacher's max students
|
||||
getter max_students
|
||||
|
||||
# Teacher at SKIF
|
||||
getter skif
|
||||
|
||||
@[GraphQL::Field]
|
||||
|
@ -34,7 +42,9 @@ module Backend
|
|||
end
|
||||
|
||||
@[GraphQL::InputObject]
|
||||
# Teacher creation input
|
||||
class TeacherCreateInput < TeacherInput
|
||||
# Teacher's user ID
|
||||
getter user_id
|
||||
|
||||
@[GraphQL::Field]
|
||||
|
|
|
@ -2,26 +2,35 @@ module Backend
|
|||
module Api
|
||||
module Schema
|
||||
@[GraphQL::Object]
|
||||
# Teacher vote model
|
||||
class TeacherVote < GraphQL::BaseObject
|
||||
include Helpers::DbObject
|
||||
|
||||
db_object Db::TeacherVote
|
||||
|
||||
@[GraphQL::Field]
|
||||
# Voted teacher
|
||||
def teacher : Teacher
|
||||
Teacher.new(find!.teacher.not_nil!)
|
||||
end
|
||||
|
||||
@[GraphQL::Field]
|
||||
# Teacher vote's priority
|
||||
def priority : Int32
|
||||
find!.priority
|
||||
end
|
||||
end
|
||||
|
||||
@[GraphQL::InputObject]
|
||||
# Teacher vote creation input
|
||||
class TeacherVoteCreateInput < GraphQL::BaseInputObject
|
||||
# Teacher vote's vote ID
|
||||
getter vote_id
|
||||
|
||||
# Teacher vote's teacher ID
|
||||
getter teacher_id
|
||||
|
||||
# Teacher vote's priority
|
||||
getter priority
|
||||
|
||||
@[GraphQL::Field]
|
||||
|
|
|
@ -2,42 +2,50 @@ module Backend
|
|||
module Api
|
||||
module Schema
|
||||
@[GraphQL::Object]
|
||||
# User model
|
||||
class User < GraphQL::BaseObject
|
||||
include Helpers::DbObject
|
||||
|
||||
db_object Db::User
|
||||
|
||||
@[GraphQL::Field]
|
||||
# User's first name
|
||||
def firstname : String
|
||||
find!.firstname
|
||||
end
|
||||
|
||||
@[GraphQL::Field]
|
||||
# User's last name
|
||||
def lastname : String
|
||||
find!.lastname
|
||||
end
|
||||
|
||||
@[GraphQL::Field]
|
||||
# User's full name
|
||||
def name : String
|
||||
find!.name
|
||||
end
|
||||
|
||||
@[GraphQL::Field]
|
||||
# User's LDAP username
|
||||
def username : String
|
||||
find!.username
|
||||
end
|
||||
|
||||
@[GraphQL::Field]
|
||||
# User's email
|
||||
def email : String
|
||||
find!.email
|
||||
end
|
||||
|
||||
@[GraphQL::Field]
|
||||
# User is admin
|
||||
def admin : Bool
|
||||
find!.admin
|
||||
end
|
||||
|
||||
@[GraphQL::Field]
|
||||
# User's role
|
||||
def role : UserRole
|
||||
role = Db::UserRole.parse(find!.role)
|
||||
case role
|
||||
|
@ -51,6 +59,7 @@ module Backend
|
|||
end
|
||||
|
||||
@[GraphQL::Field]
|
||||
# User's external ID
|
||||
def external_id : Int32?
|
||||
case Db::UserRole.parse(find!.role)
|
||||
when .teacher?
|
||||
|
@ -63,6 +72,7 @@ module Backend
|
|||
end
|
||||
|
||||
@[GraphQL::Field]
|
||||
# User's external teacher object
|
||||
def teacher : Teacher?
|
||||
teacher = find!.teacher
|
||||
if teacher
|
||||
|
@ -71,6 +81,7 @@ module Backend
|
|||
end
|
||||
|
||||
@[GraphQL::Field]
|
||||
# User's external student object
|
||||
def student : Student?
|
||||
student = find!.student
|
||||
if student
|
||||
|
@ -80,6 +91,7 @@ module Backend
|
|||
end
|
||||
|
||||
@[GraphQL::InputObject]
|
||||
# User creation input
|
||||
class UserCreateInput < GraphQL::BaseInputObject
|
||||
getter username
|
||||
getter role
|
||||
|
@ -93,8 +105,12 @@ module Backend
|
|||
end
|
||||
|
||||
@[GraphQL::Object]
|
||||
# Login payload returned after successful login
|
||||
class LoginPayload < GraphQL::BaseObject
|
||||
# Logged in user
|
||||
property user
|
||||
|
||||
# JWT token
|
||||
property token
|
||||
|
||||
def initialize(
|
||||
|
@ -104,16 +120,19 @@ module Backend
|
|||
end
|
||||
|
||||
@[GraphQL::Field]
|
||||
# Logged in user
|
||||
def user : User
|
||||
@user
|
||||
end
|
||||
|
||||
@[GraphQL::Field]
|
||||
# Raw bearer token
|
||||
def token : String
|
||||
@token
|
||||
end
|
||||
|
||||
@[GraphQL::Field]
|
||||
# Ready to use bearer token
|
||||
def bearer : String
|
||||
Auth::BEARER + @token
|
||||
end
|
||||
|
|
|
@ -2,6 +2,7 @@ module Backend
|
|||
module Api
|
||||
module Schema
|
||||
@[GraphQL::Enum]
|
||||
# Possible roles of a user
|
||||
enum UserRole
|
||||
Teacher
|
||||
Student
|
||||
|
|
|
@ -2,24 +2,29 @@ module Backend
|
|||
module Api
|
||||
module Schema
|
||||
@[GraphQL::Object]
|
||||
# Vote model
|
||||
class Vote < GraphQL::BaseObject
|
||||
include Helpers::DbObject
|
||||
|
||||
db_object Db::Vote
|
||||
|
||||
@[GraphQL::Field]
|
||||
# Student who voted
|
||||
def student : Student
|
||||
Student.new(find!.student)
|
||||
end
|
||||
|
||||
@[GraphQL::Field]
|
||||
# Teacher votes for student
|
||||
def teacher_votes : Array(TeacherVote)
|
||||
find!.teacher_votes.map { |tv| TeacherVote.new(tv) }
|
||||
end
|
||||
end
|
||||
|
||||
@[GraphQL::InputObject]
|
||||
# Vote creation input
|
||||
class VoteCreateInput < GraphQL::BaseInputObject
|
||||
# Teacher IDs student votes for
|
||||
getter teacher_ids
|
||||
|
||||
@[GraphQL::Field]
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
module Backend
|
||||
module Api
|
||||
# Api service
|
||||
SERVICE = ->do
|
||||
Log.info { "Starting Api service..." }
|
||||
run
|
||||
|
|
|
@ -4,11 +4,16 @@ require "json"
|
|||
|
||||
module Backend
|
||||
module Api
|
||||
# Api webserver
|
||||
class WebServer
|
||||
include Router
|
||||
|
||||
# GraphQL playground HTML code
|
||||
#
|
||||
# NOTE: Is minified in production
|
||||
GRAPHQL_PLAYGROUND = {{ flag?(:development) ? read_file("#{__DIR__}/playground.html") : run("./macros/minify_html.cr", read_file("#{__DIR__}/playground.html")).stringify }}
|
||||
|
||||
# GraphQL request data serializer
|
||||
struct GraphQLQueryData
|
||||
include JSON::Serializable
|
||||
|
||||
|
@ -17,6 +22,7 @@ module Backend
|
|||
property operation_name : String?
|
||||
end
|
||||
|
||||
# "Draws" (creates) routes
|
||||
def draw_routes : Nil
|
||||
# enable graphql playground when in development mode or explicitly enabled
|
||||
if Backend.config.api.graphql_playground_fully_enabled?
|
||||
|
@ -47,6 +53,7 @@ module Backend
|
|||
end
|
||||
end
|
||||
|
||||
# Runs the webserver with according middleware
|
||||
def run : Nil
|
||||
draw_routes
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
require "./jobs/*"
|
||||
|
||||
# Job definitions
|
||||
module Jobs
|
||||
module Backend::Worker::Jobs
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue