kosapi_client 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/console +15 -0
- data/bin/setup +23 -0
- data/lib/kosapi_client.rb +22 -0
- data/lib/kosapi_client/api_client.rb +43 -0
- data/lib/kosapi_client/configuration.rb +23 -0
- data/lib/kosapi_client/entity.rb +19 -0
- data/lib/kosapi_client/entity/author.rb +17 -0
- data/lib/kosapi_client/entity/base_entity.rb +16 -0
- data/lib/kosapi_client/entity/base_person.rb +20 -0
- data/lib/kosapi_client/entity/boolean.rb +14 -0
- data/lib/kosapi_client/entity/course.rb +34 -0
- data/lib/kosapi_client/entity/course_event.rb +20 -0
- data/lib/kosapi_client/entity/data_mappings.rb +122 -0
- data/lib/kosapi_client/entity/enum.rb +11 -0
- data/lib/kosapi_client/entity/exam.rb +25 -0
- data/lib/kosapi_client/entity/id.rb +13 -0
- data/lib/kosapi_client/entity/link.rb +54 -0
- data/lib/kosapi_client/entity/ml_string.rb +42 -0
- data/lib/kosapi_client/entity/parallel.rb +20 -0
- data/lib/kosapi_client/entity/person.rb +10 -0
- data/lib/kosapi_client/entity/result_page.rb +46 -0
- data/lib/kosapi_client/entity/student.rb +25 -0
- data/lib/kosapi_client/entity/teacher.rb +14 -0
- data/lib/kosapi_client/entity/teacher_timetable_slot.rb +16 -0
- data/lib/kosapi_client/entity/timetable_slot.rb +16 -0
- data/lib/kosapi_client/hash_utils.rb +17 -0
- data/lib/kosapi_client/http_client.rb +36 -0
- data/lib/kosapi_client/kosapi_client.rb +45 -0
- data/lib/kosapi_client/kosapi_response.rb +32 -0
- data/lib/kosapi_client/oauth2_http_adapter.rb +36 -0
- data/lib/kosapi_client/request_builder.rb +59 -0
- data/lib/kosapi_client/request_builder_delegator.rb +52 -0
- data/lib/kosapi_client/resource.rb +12 -0
- data/lib/kosapi_client/resource/course_events_builder.rb +13 -0
- data/lib/kosapi_client/resource/courses_builder.rb +12 -0
- data/lib/kosapi_client/resource/exams_builder.rb +13 -0
- data/lib/kosapi_client/resource/parallels_builder.rb +20 -0
- data/lib/kosapi_client/resource/teachers_builder.rb +16 -0
- data/lib/kosapi_client/resource_mapper.rb +20 -0
- data/lib/kosapi_client/response_converter.rb +65 -0
- data/lib/kosapi_client/response_links.rb +40 -0
- data/lib/kosapi_client/response_preprocessor.rb +48 -0
- data/lib/kosapi_client/url_builder.rb +24 -0
- data/lib/kosapi_client/version.rb +3 -0
- data/spec/integration/course_events_spec.rb +13 -0
- data/spec/integration/courses_spec.rb +28 -0
- data/spec/integration/exams_spec.rb +20 -0
- data/spec/integration/parallels_spec.rb +68 -0
- data/spec/kosapi_client/api_client_spec.rb +22 -0
- data/spec/kosapi_client/configuration_spec.rb +30 -0
- data/spec/kosapi_client/entity/base_entity_spec.rb +20 -0
- data/spec/kosapi_client/entity/base_person_spec.rb +19 -0
- data/spec/kosapi_client/entity/boolean_spec.rb +23 -0
- data/spec/kosapi_client/entity/course_event_spec.rb +30 -0
- data/spec/kosapi_client/entity/data_mappings_spec.rb +154 -0
- data/spec/kosapi_client/entity/enum_spec.rb +20 -0
- data/spec/kosapi_client/entity/id_spec.rb +13 -0
- data/spec/kosapi_client/entity/link_spec.rb +89 -0
- data/spec/kosapi_client/entity/ml_string_spec.rb +52 -0
- data/spec/kosapi_client/entity/parallel_spec.rb +18 -0
- data/spec/kosapi_client/entity/result_page_spec.rb +42 -0
- data/spec/kosapi_client/entity/teacher_timetable_slot_spec.rb +19 -0
- data/spec/kosapi_client/entity/timetable_slot_spec.rb +22 -0
- data/spec/kosapi_client/hash_utils_spec.rb +20 -0
- data/spec/kosapi_client/http_client_spec.rb +30 -0
- data/spec/kosapi_client/kosapi_client_spec.rb +57 -0
- data/spec/kosapi_client/kosapi_response_spec.rb +96 -0
- data/spec/kosapi_client/oauth2_http_adapter_spec.rb +25 -0
- data/spec/kosapi_client/request_builder_delegator_spec.rb +72 -0
- data/spec/kosapi_client/request_builder_spec.rb +80 -0
- data/spec/kosapi_client/resource/courses_builder_spec.rb +20 -0
- data/spec/kosapi_client/resource/parallels_builder_spec.rb +49 -0
- data/spec/kosapi_client/resource_mapper_spec.rb +33 -0
- data/spec/kosapi_client/response_converter_spec.rb +58 -0
- data/spec/kosapi_client/response_links_spec.rb +52 -0
- data/spec/kosapi_client/response_preprocessor_spec.rb +51 -0
- data/spec/kosapi_client/url_builder_spec.rb +44 -0
- data/spec/spec_helper.rb +40 -0
- data/spec/support/client_helpers.rb +11 -0
- data/spec/support/helpers.rb +7 -0
- data/spec/support/shared_examples_for_fluent_api.rb +7 -0
- metadata +401 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 46750e332cf19232187a8a9e5c66b9fa128e246a
|
4
|
+
data.tar.gz: 8d9a60a7399406d633ddb03bbadd51bc151b8372
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 424aeb28f513261f67bdb98783d22fcf6dbc536f80f652386c9d666b5a9d437e49c92951d4bce03c71dd9ddeb8c518a2f1c23e760eba260a6fce530f1a3c03d7
|
7
|
+
data.tar.gz: 8b55b7666492503cf2d382475b4dead2d3f4dc0a9e4aff95df143339e9c3e5613a582a6d77555c1c3d261406baa4880142e2d29ec68a410477902474335e23fe
|
data/bin/console
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "dotenv"
|
5
|
+
require "kosapi_client"
|
6
|
+
|
7
|
+
Dotenv.load
|
8
|
+
|
9
|
+
KOSapiClient.configure do |c|
|
10
|
+
c.client_id = ENV['KOSAPI_OAUTH_CLIENT_ID']
|
11
|
+
c.client_secret = ENV['KOSAPI_OAUTH_CLIENT_SECRET']
|
12
|
+
end
|
13
|
+
|
14
|
+
require "irb"
|
15
|
+
IRB.start
|
data/bin/setup
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
set -euo pipefail
|
3
|
+
IFS=$'\n\t'
|
4
|
+
|
5
|
+
echo "You will need OAuth credentials to access KOSapi and run integration tests."
|
6
|
+
echo "Register new project at https://auth.fit.cvut.cz/manager/ with readonly access to KOSapi
|
7
|
+
and enter client ID and secret for your default service account."
|
8
|
+
|
9
|
+
echo -n "Client ID (KOSAPI_OAUTH_CLIENT_ID): "
|
10
|
+
read client_id
|
11
|
+
echo -n "Client secret (KOSAPI_OAUTH_CLIENT_SECRET): "
|
12
|
+
read client_secret
|
13
|
+
|
14
|
+
script_path=$(cd `dirname "${BASH_SOURCE[0]}"` && pwd)
|
15
|
+
env_file=$(realpath "$script_path/../.env")
|
16
|
+
echo "Writing credentials to $env_file"
|
17
|
+
cat << EOF > "$env_file"
|
18
|
+
KOSAPI_OAUTH_CLIENT_ID=$client_id
|
19
|
+
KOSAPI_OAUTH_CLIENT_SECRET=$client_secret
|
20
|
+
EOF
|
21
|
+
|
22
|
+
echo "Running bundle install"
|
23
|
+
bundle install
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'corefines'
|
2
|
+
require 'uri_template'
|
3
|
+
require 'escape_utils'
|
4
|
+
|
5
|
+
require 'kosapi_client/entity'
|
6
|
+
require 'kosapi_client/resource'
|
7
|
+
|
8
|
+
require 'kosapi_client/response_links'
|
9
|
+
require 'kosapi_client/configuration'
|
10
|
+
require 'kosapi_client/url_builder'
|
11
|
+
require 'kosapi_client/request_builder'
|
12
|
+
require 'kosapi_client/request_builder_delegator'
|
13
|
+
require 'kosapi_client/resource_mapper'
|
14
|
+
require 'kosapi_client/kosapi_client'
|
15
|
+
require 'kosapi_client/api_client'
|
16
|
+
require 'kosapi_client/hash_utils'
|
17
|
+
require 'kosapi_client/http_client'
|
18
|
+
require 'kosapi_client/kosapi_response'
|
19
|
+
require 'kosapi_client/response_preprocessor'
|
20
|
+
require 'kosapi_client/oauth2_http_adapter'
|
21
|
+
require 'kosapi_client/response_converter'
|
22
|
+
require 'kosapi_client/version'
|
@@ -0,0 +1,43 @@
|
|
1
|
+
using Corefines::String::camelcase
|
2
|
+
|
3
|
+
module KOSapiClient
|
4
|
+
|
5
|
+
class ApiClient
|
6
|
+
include ResourceMapper
|
7
|
+
|
8
|
+
# accessible resources definition
|
9
|
+
resource :courses
|
10
|
+
resource :course_events
|
11
|
+
resource :parallels
|
12
|
+
resource :exams
|
13
|
+
resource :teachers
|
14
|
+
|
15
|
+
attr_reader :http_client
|
16
|
+
|
17
|
+
##
|
18
|
+
# Creates a new KOSapi client.
|
19
|
+
#
|
20
|
+
def initialize(config = Configuration.new)
|
21
|
+
http_adapter = OAuth2HttpAdapter.new(config.credentials, config.base_url)
|
22
|
+
@http_client = HTTPClient.new(http_adapter)
|
23
|
+
end
|
24
|
+
|
25
|
+
def create_builder(resource_name)
|
26
|
+
builder_name = "#{resource_name}_builder".camelcase(:upper).to_sym
|
27
|
+
builder_class = find_builder_class(builder_name)
|
28
|
+
builder_class.new(resource_name.to_s.camelcase(:lower), @http_client)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
def find_builder_class(builder_name)
|
33
|
+
KOSapiClient::Resource.constants.each do |m|
|
34
|
+
constant = KOSapiClient::Resource.const_get(m)
|
35
|
+
if constant.is_a?(Class) && m == builder_name
|
36
|
+
return constant
|
37
|
+
end
|
38
|
+
end
|
39
|
+
RequestBuilder
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module KOSapiClient
|
2
|
+
class Configuration < Struct.new(:client_id, :client_secret, :base_url)
|
3
|
+
|
4
|
+
DEFAULT_OPTIONS = {
|
5
|
+
base_url: 'https://kosapi.fit.cvut.cz/api/3'
|
6
|
+
}
|
7
|
+
|
8
|
+
def initialize(options = {})
|
9
|
+
DEFAULT_OPTIONS.merge(options).each do |option, value|
|
10
|
+
self[option] = value
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def credentials
|
15
|
+
if client_id && client_secret
|
16
|
+
{client_id: client_id, client_secret: client_secret}
|
17
|
+
else
|
18
|
+
{}
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'kosapi_client/entity/boolean'
|
2
|
+
require 'kosapi_client/entity/link'
|
3
|
+
require 'kosapi_client/entity/id'
|
4
|
+
require 'kosapi_client/entity/author'
|
5
|
+
require 'kosapi_client/entity/enum'
|
6
|
+
require 'kosapi_client/entity/ml_string'
|
7
|
+
require 'kosapi_client/entity/data_mappings'
|
8
|
+
require 'kosapi_client/entity/base_entity'
|
9
|
+
require 'kosapi_client/entity/result_page'
|
10
|
+
require 'kosapi_client/entity/course_event'
|
11
|
+
require 'kosapi_client/entity/course'
|
12
|
+
require 'kosapi_client/entity/teacher_timetable_slot'
|
13
|
+
require 'kosapi_client/entity/timetable_slot'
|
14
|
+
require 'kosapi_client/entity/parallel'
|
15
|
+
require 'kosapi_client/entity/base_person'
|
16
|
+
require 'kosapi_client/entity/person'
|
17
|
+
require 'kosapi_client/entity/teacher'
|
18
|
+
require 'kosapi_client/entity/student'
|
19
|
+
require 'kosapi_client/entity/exam'
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module KOSapiClient
|
2
|
+
module Entity
|
3
|
+
class BaseEntity
|
4
|
+
|
5
|
+
include DataMappings
|
6
|
+
|
7
|
+
map_data :id, String, namespace: :atom
|
8
|
+
map_data :title, String, namespace: :atom
|
9
|
+
map_data :updated, Time, namespace: :atom
|
10
|
+
map_data :link, Link, namespace: :atom
|
11
|
+
map_data :author, Author, namespace: :atom
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module KOSapiClient
|
2
|
+
module Entity
|
3
|
+
class BasePerson < BaseEntity
|
4
|
+
|
5
|
+
map_data :first_name
|
6
|
+
map_data :last_name
|
7
|
+
map_data :personal_number, Integer
|
8
|
+
map_data :titles_post
|
9
|
+
map_data :titles_pre
|
10
|
+
map_data :username
|
11
|
+
|
12
|
+
def full_name
|
13
|
+
prefix = "#{titles_pre} " if titles_pre
|
14
|
+
suffix = " #{titles_post}" if titles_post
|
15
|
+
"#{prefix}#{first_name} #{last_name}#{suffix}"
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module KOSapiClient
|
2
|
+
module Entity
|
3
|
+
class Course < BaseEntity
|
4
|
+
|
5
|
+
map_data :allowed_enrollment_count, Integer
|
6
|
+
map_data :approval_date, Time
|
7
|
+
map_data :classes_lang, Enum
|
8
|
+
map_data :classes_type, [Enum]
|
9
|
+
map_data :code
|
10
|
+
map_data :completion, Enum
|
11
|
+
map_data :credits, Integer
|
12
|
+
map_data :department, Link
|
13
|
+
map_data :description, MLString
|
14
|
+
map_data :homepage
|
15
|
+
map_data :keywords, MLString
|
16
|
+
map_data :lectures_contents, MLString
|
17
|
+
map_data :literature, MLString
|
18
|
+
map_data :name, MLString
|
19
|
+
map_data :note, MLString
|
20
|
+
map_data :objectives, MLString
|
21
|
+
map_data :programme_type, Enum
|
22
|
+
map_data :range
|
23
|
+
map_data :requirements, MLString
|
24
|
+
map_data :season, Enum
|
25
|
+
map_data :state, Enum
|
26
|
+
map_data :study_form, Enum
|
27
|
+
map_data :superior_course, Link
|
28
|
+
map_data :subcourses, Link
|
29
|
+
map_data :tutorials_contents, MLString
|
30
|
+
map_data :instance #todo
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module KOSapiClient
|
2
|
+
module Entity
|
3
|
+
class CourseEvent < BaseEntity
|
4
|
+
|
5
|
+
map_data :target_branch, [Link]
|
6
|
+
map_data :capacity, Integer
|
7
|
+
map_data :course, Link
|
8
|
+
map_data :creator, Link
|
9
|
+
map_data :end_date, Time
|
10
|
+
map_data :name, MLString
|
11
|
+
map_data :note, MLString
|
12
|
+
map_data :occupied, Integer
|
13
|
+
map_data :room, Link
|
14
|
+
map_data :semester, Link
|
15
|
+
map_data :signin_deadline, Time
|
16
|
+
map_data :start_date, Time
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
module KOSapiClient
|
2
|
+
module Entity
|
3
|
+
module DataMappings
|
4
|
+
|
5
|
+
def self.included(base)
|
6
|
+
base.extend(ClassMethods)
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_hash
|
10
|
+
result = {}
|
11
|
+
self.class.attr_mappings.each_key { |k| result[k] = convert_value(send(k)) }
|
12
|
+
result
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
def convert_value(val)
|
17
|
+
if val.respond_to? :to_hash
|
18
|
+
val.to_hash
|
19
|
+
elsif val.is_a?(Array)
|
20
|
+
val.map { |it| convert_value(it) }
|
21
|
+
else
|
22
|
+
val
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
module ClassMethods
|
27
|
+
|
28
|
+
def map_data(name, type=String, opts = {})
|
29
|
+
attr_accessor name
|
30
|
+
opts[:type] = type
|
31
|
+
@data_mappings ||= {}
|
32
|
+
@data_mappings[name] = opts
|
33
|
+
end
|
34
|
+
|
35
|
+
def attr_mappings
|
36
|
+
if self.superclass.respond_to? :attr_mappings
|
37
|
+
parent_mappings = self.superclass.attr_mappings
|
38
|
+
end
|
39
|
+
(parent_mappings || {}).merge(@data_mappings)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Parses composed domain type from hash response structure.
|
43
|
+
#
|
44
|
+
# @param [Hash] content hash structure from API response corresponding to single domain object
|
45
|
+
# @return [BaseEntity] parsed domain object
|
46
|
+
def parse(content, context = {})
|
47
|
+
instance = new()
|
48
|
+
set_mapped_attributes(instance, content)
|
49
|
+
instance
|
50
|
+
end
|
51
|
+
|
52
|
+
# Creates new domain object instance and sets values
|
53
|
+
# of mapped domain object attributes from source hash.
|
54
|
+
# Attributes are mapped by .map_data method.
|
55
|
+
def set_mapped_attributes(instance, source_hash)
|
56
|
+
if self.superclass.respond_to? :set_mapped_attributes
|
57
|
+
self.superclass.set_mapped_attributes(instance, source_hash)
|
58
|
+
end
|
59
|
+
raise "Missing data mappings for entity #{self}" unless @data_mappings
|
60
|
+
@data_mappings.each do |name, options|
|
61
|
+
set_mapped_attribute(instance, name, source_hash, options)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
def set_mapped_attribute(instance, name, source_hash, mapping_options)
|
67
|
+
namespace = mapping_options[:namespace]
|
68
|
+
src_element = mapping_options[:element] || name
|
69
|
+
if namespace
|
70
|
+
key = "#{namespace}_#{src_element}".to_sym
|
71
|
+
else
|
72
|
+
key = src_element
|
73
|
+
end
|
74
|
+
value = retrieve_value(source_hash, key, mapping_options)
|
75
|
+
if value.nil?
|
76
|
+
raise "Missing value for attribute #{name}" if mapping_options[:required]
|
77
|
+
if mapping_options[:type].is_a?(Array)
|
78
|
+
value = []
|
79
|
+
else
|
80
|
+
return
|
81
|
+
end
|
82
|
+
else
|
83
|
+
value = convert_type(value, mapping_options[:type])
|
84
|
+
end
|
85
|
+
instance.send("#{name}=".to_sym, value)
|
86
|
+
end
|
87
|
+
|
88
|
+
def convert_type(value, type)
|
89
|
+
return value.to_i if type == Integer
|
90
|
+
return value if type == String
|
91
|
+
return convert_array(value, type.first) if type.is_a?(Array)
|
92
|
+
|
93
|
+
return type.parse(value) if type.respond_to? :parse
|
94
|
+
raise "Unknown type #{type} to convert value #{value} to."
|
95
|
+
end
|
96
|
+
|
97
|
+
# Converts values of array type to proper domain objects.
|
98
|
+
# It checks whether the value is really an array, because
|
99
|
+
# when API returns a single value it does not get parsed
|
100
|
+
# into an array.
|
101
|
+
def convert_array(values, type)
|
102
|
+
if values.is_a?(Array)
|
103
|
+
values.map { |it| convert_type(it, type) }
|
104
|
+
else
|
105
|
+
[ convert_type(values, type) ]
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def retrieve_value(source_hash, key, mapping_options)
|
110
|
+
path = mapping_options[:path]
|
111
|
+
if path
|
112
|
+
parent_element = source_hash[path]
|
113
|
+
else
|
114
|
+
parent_element = source_hash
|
115
|
+
end
|
116
|
+
parent_element[key] if parent_element
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module KOSapiClient
|
2
|
+
module Entity
|
3
|
+
class Exam < BaseEntity
|
4
|
+
|
5
|
+
map_data :cancel_deadline, Time
|
6
|
+
map_data :capacity, Integer
|
7
|
+
map_data :course, Link
|
8
|
+
map_data :department, Link
|
9
|
+
map_data :end_date, Time
|
10
|
+
map_data :examiner, Link
|
11
|
+
map_data :examiners, [Link], element: :teacher, path: :examiners
|
12
|
+
map_data :note
|
13
|
+
map_data :occupied, Integer
|
14
|
+
map_data :resit, Boolean
|
15
|
+
map_data :room, Link
|
16
|
+
map_data :semester, Link
|
17
|
+
map_data :signin_deadline, Date
|
18
|
+
map_data :start_date, Time
|
19
|
+
map_data :substitutes, Enum
|
20
|
+
map_data :superior, Link
|
21
|
+
map_data :term_type, Enum
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|