kosapi_client 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
@@ -0,0 +1,54 @@
|
|
1
|
+
module KOSapiClient
|
2
|
+
module Entity
|
3
|
+
class Link
|
4
|
+
|
5
|
+
attr_reader :link_title, :link_href, :link_rel
|
6
|
+
|
7
|
+
def initialize(title, href, rel, client = nil)
|
8
|
+
@link_title = title
|
9
|
+
@link_href = escape_url(href)
|
10
|
+
@link_rel = rel
|
11
|
+
@client = client
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.parse(contents)
|
15
|
+
href = contents[:xlink_href] || contents[:href]
|
16
|
+
new(contents[:__content__], href, contents[:rel])
|
17
|
+
end
|
18
|
+
|
19
|
+
def link_id
|
20
|
+
@link_href.split('/').last
|
21
|
+
end
|
22
|
+
|
23
|
+
def follow
|
24
|
+
raise "HTTP client not set, cannot send request to #{link_href}" unless @client
|
25
|
+
@client.send_request(:get, link_href)
|
26
|
+
end
|
27
|
+
|
28
|
+
def inject_client(client)
|
29
|
+
@client = client
|
30
|
+
end
|
31
|
+
|
32
|
+
def target
|
33
|
+
@target ||= follow
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_hash
|
37
|
+
{ href: link_href, rel: link_rel, title: link_title }
|
38
|
+
end
|
39
|
+
|
40
|
+
def method_missing(method, *args, &block)
|
41
|
+
target.send(method, *args, &block)
|
42
|
+
end
|
43
|
+
|
44
|
+
def respond_to_missing?(method_name, include_private = false)
|
45
|
+
target.respond_to?(method_name, include_private)
|
46
|
+
end
|
47
|
+
|
48
|
+
def escape_url(url)
|
49
|
+
url.gsub(';','%3B')
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module KOSapiClient
|
2
|
+
module Entity
|
3
|
+
class MLString
|
4
|
+
|
5
|
+
DEFAULT_LANGUAGE = :en
|
6
|
+
attr_reader :translations
|
7
|
+
|
8
|
+
def initialize(translations, default_language = DEFAULT_LANGUAGE)
|
9
|
+
@translations = translations
|
10
|
+
@default_language = default_language
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_s(lang = :implicit)
|
14
|
+
if lang == :implicit
|
15
|
+
lang = select_lang
|
16
|
+
end
|
17
|
+
@translations[lang]
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.parse(item)
|
21
|
+
unless item.is_a?(Array)
|
22
|
+
item = [item]
|
23
|
+
end
|
24
|
+
translations = {}
|
25
|
+
item.each do |it|
|
26
|
+
lang = it[:xml_lang].to_sym
|
27
|
+
value = it[:__content__]
|
28
|
+
translations[lang] = value
|
29
|
+
end
|
30
|
+
|
31
|
+
MLString.new(translations)
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
def select_lang
|
36
|
+
return @default_language if @translations.key?(@default_language)
|
37
|
+
@translations.keys.first
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module KOSapiClient
|
2
|
+
module Entity
|
3
|
+
class Parallel < BaseEntity
|
4
|
+
|
5
|
+
map_data :capacity, Integer
|
6
|
+
map_data :capacity_overfill, Enum #, Permission
|
7
|
+
map_data :code, Integer
|
8
|
+
map_data :course, Link
|
9
|
+
map_data :parallel_type, Enum #, ParallelType
|
10
|
+
map_data :enrollment, Enum #, Permission
|
11
|
+
map_data :note, MLString
|
12
|
+
map_data :occupied, Integer
|
13
|
+
map_data :semester, Link
|
14
|
+
map_data :teachers, [Link], element: :teacher
|
15
|
+
map_data :timetable_slots, [TimetableSlot], element: :timetable_slot
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module KOSapiClient
|
2
|
+
module Entity
|
3
|
+
|
4
|
+
# ResultPage instance is returned from requests to all paginated resources.
|
5
|
+
# It wraps returned objects, stores additional feed metadata and it also
|
6
|
+
# helps to do things like auto pagination and next / previous page callbacks.
|
7
|
+
class ResultPage
|
8
|
+
|
9
|
+
include Enumerable
|
10
|
+
|
11
|
+
attr_reader :items
|
12
|
+
attr_accessor :auto_paginate
|
13
|
+
|
14
|
+
def initialize(items, links, auto_paginate = true)
|
15
|
+
@items = items
|
16
|
+
@links = links
|
17
|
+
@auto_paginate = auto_paginate
|
18
|
+
end
|
19
|
+
|
20
|
+
def count
|
21
|
+
@items.count
|
22
|
+
end
|
23
|
+
|
24
|
+
def next
|
25
|
+
@links.next
|
26
|
+
end
|
27
|
+
|
28
|
+
def prev
|
29
|
+
@links.prev
|
30
|
+
end
|
31
|
+
|
32
|
+
def each(&block)
|
33
|
+
return to_enum(__method__) unless block_given?
|
34
|
+
items.each(&block)
|
35
|
+
return unless @auto_paginate
|
36
|
+
next_link = self.next
|
37
|
+
while next_link
|
38
|
+
next_page = next_link.follow
|
39
|
+
next_link = next_page.next
|
40
|
+
next_page.items.each(&block)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module KOSapiClient
|
2
|
+
module Entity
|
3
|
+
class Student < BasePerson
|
4
|
+
|
5
|
+
map_data :branch, Link
|
6
|
+
map_data :department, Link
|
7
|
+
map_data :email
|
8
|
+
map_data :start_date, Time
|
9
|
+
map_data :faculty, Link
|
10
|
+
map_data :grade, Integer
|
11
|
+
map_data :interrupted_until, Time
|
12
|
+
map_data :programme, Link
|
13
|
+
map_data :end_date, Time
|
14
|
+
map_data :study_form, Enum
|
15
|
+
map_data :study_group, Integer
|
16
|
+
map_data :study_plan, Link
|
17
|
+
map_data :study_state, Enum
|
18
|
+
map_data :supervisor, Link
|
19
|
+
map_data :supervisor_specialist, Link
|
20
|
+
map_data :study_termination_reason, Enum
|
21
|
+
map_data :code
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module KOSapiClient
|
2
|
+
module Entity
|
3
|
+
class Teacher < BasePerson
|
4
|
+
|
5
|
+
map_data :division, Link
|
6
|
+
map_data :extern, Boolean
|
7
|
+
map_data :email
|
8
|
+
map_data :phone
|
9
|
+
map_data :stageName
|
10
|
+
map_data :supervision_phd_students, Enum, element: :supervision_ph_d_students
|
11
|
+
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module KOSapiClient
|
2
|
+
module Entity
|
3
|
+
class TeacherTimetableSlot
|
4
|
+
|
5
|
+
include DataMappings
|
6
|
+
|
7
|
+
map_data :id, Integer
|
8
|
+
map_data :day, Integer
|
9
|
+
map_data :duration, Integer
|
10
|
+
map_data :first_hour, Integer
|
11
|
+
map_data :parity, Enum
|
12
|
+
map_data :title, String
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module KOSapiClient
|
2
|
+
module Entity
|
3
|
+
class TimetableSlot
|
4
|
+
|
5
|
+
include DataMappings
|
6
|
+
|
7
|
+
map_data :id, Integer
|
8
|
+
map_data :day, Integer
|
9
|
+
map_data :duration, Integer
|
10
|
+
map_data :first_hour, Integer
|
11
|
+
map_data :parity, Enum
|
12
|
+
map_data :room, Link
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module KOSapiClient
|
2
|
+
module HashUtils
|
3
|
+
|
4
|
+
def self.deep_transform_hash_keys(item, &block)
|
5
|
+
return item unless item.is_a?(Hash)
|
6
|
+
copy = {}
|
7
|
+
item.each do |key, value|
|
8
|
+
new_value = deep_transform_hash_keys(value, &block) if value.is_a? Hash
|
9
|
+
new_value = value.map { |it| deep_transform_hash_keys(it, &block) } if value.is_a? Array
|
10
|
+
new_value ||= value
|
11
|
+
copy[block.call(key)] = new_value
|
12
|
+
end
|
13
|
+
copy
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module KOSapiClient
|
2
|
+
class HTTPClient
|
3
|
+
|
4
|
+
def initialize(http_adapter, preprocessor = ResponsePreprocessor.new, converter = ResponseConverter.new(self))
|
5
|
+
@http_adapter = http_adapter
|
6
|
+
@preprocessor = preprocessor
|
7
|
+
@converter = converter
|
8
|
+
end
|
9
|
+
|
10
|
+
def send_request(verb, url, options = {})
|
11
|
+
absolute_url = get_absolute_url(url)
|
12
|
+
result = @http_adapter.send_request(verb, absolute_url, options)
|
13
|
+
process_response(result)
|
14
|
+
end
|
15
|
+
|
16
|
+
def process_response(result)
|
17
|
+
preprocessed = @preprocessor.preprocess(result)
|
18
|
+
response = KOSapiClient::KOSapiResponse.new(preprocessed)
|
19
|
+
@converter.convert(response)
|
20
|
+
end
|
21
|
+
|
22
|
+
def get_absolute_url(url)
|
23
|
+
if is_absolute(url)
|
24
|
+
url
|
25
|
+
else
|
26
|
+
"#{@http_adapter.base_url}/#{url}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
def is_absolute(url)
|
32
|
+
url.start_with?('http')
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module KOSapiClient
|
2
|
+
|
3
|
+
singleton_class.class_eval do
|
4
|
+
|
5
|
+
def new(options = {})
|
6
|
+
ApiClient.new(Configuration.new(options))
|
7
|
+
end
|
8
|
+
|
9
|
+
def configure
|
10
|
+
reset
|
11
|
+
yield config
|
12
|
+
self
|
13
|
+
end
|
14
|
+
|
15
|
+
def client
|
16
|
+
@client ||= ApiClient.new(config)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Calling this method clears stored ApiClient instance
|
20
|
+
# if configured previously.
|
21
|
+
def reset
|
22
|
+
@config = nil
|
23
|
+
@client = nil
|
24
|
+
end
|
25
|
+
|
26
|
+
def method_missing(method, *args, &block)
|
27
|
+
if client.respond_to?(method)
|
28
|
+
client.send(method, *args, &block)
|
29
|
+
else
|
30
|
+
super
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def respond_to_missing?(method_name, include_private = false)
|
35
|
+
client.respond_to?(method_name, include_private)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
def config
|
40
|
+
@config ||= Configuration.new
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module KOSapiClient
|
2
|
+
class KOSapiResponse
|
3
|
+
|
4
|
+
attr_reader :contents
|
5
|
+
|
6
|
+
def initialize(contents)
|
7
|
+
@contents = contents
|
8
|
+
end
|
9
|
+
|
10
|
+
def is_paginated?
|
11
|
+
contents[:atom_feed]
|
12
|
+
end
|
13
|
+
|
14
|
+
def items
|
15
|
+
if is_paginated?
|
16
|
+
contents[:atom_feed][:atom_entry]
|
17
|
+
else
|
18
|
+
[contents[:atom_entry]]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def item
|
23
|
+
items.first
|
24
|
+
end
|
25
|
+
|
26
|
+
def links_hash
|
27
|
+
return nil unless contents[:atom_feed]
|
28
|
+
contents[:atom_feed][:atom_link]
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'oauth2'
|
2
|
+
|
3
|
+
module KOSapiClient
|
4
|
+
class OAuth2HttpAdapter
|
5
|
+
|
6
|
+
DEFAULT_AUTH_URL = 'https://auth.fit.cvut.cz/oauth/oauth/authorize'
|
7
|
+
DEFAULT_TOKEN_URL = 'https://auth.fit.cvut.cz/oauth/oauth/token'
|
8
|
+
|
9
|
+
attr_reader :base_url
|
10
|
+
|
11
|
+
def initialize(credentials, base_url, opts = {})
|
12
|
+
@base_url = base_url
|
13
|
+
@credentials = credentials
|
14
|
+
auth_url = opts[:auth_url] || DEFAULT_AUTH_URL
|
15
|
+
token_url = opts[:token_url] || DEFAULT_TOKEN_URL
|
16
|
+
MultiXml.parser = :ox # make sure to use Ox because of different namespace handling in other MultiXML parsers
|
17
|
+
@client = OAuth2::Client.new(credentials[:client_id], credentials[:client_secret], site: base_url, authorize_url: auth_url, token_url: token_url)
|
18
|
+
end
|
19
|
+
|
20
|
+
def send_request(verb, url, options = {})
|
21
|
+
raise 'No credentials set' if @credentials.empty?
|
22
|
+
token.request(verb, url, options)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
def authenticate
|
27
|
+
@token = @client.client_credentials.get_token
|
28
|
+
end
|
29
|
+
|
30
|
+
def token
|
31
|
+
authenticate if !@token || @token.expired?
|
32
|
+
@token
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module KOSapiClient
|
2
|
+
class RequestBuilder
|
3
|
+
|
4
|
+
attr_reader :response
|
5
|
+
|
6
|
+
def find(id)
|
7
|
+
@id = id
|
8
|
+
@url_builder.set_path(id)
|
9
|
+
self
|
10
|
+
end
|
11
|
+
|
12
|
+
def offset(num)
|
13
|
+
@url_builder.set_query_param(:offset, num)
|
14
|
+
self
|
15
|
+
end
|
16
|
+
|
17
|
+
def limit(num)
|
18
|
+
@url_builder.set_query_param(:limit, num)
|
19
|
+
self
|
20
|
+
end
|
21
|
+
|
22
|
+
def query(params = {})
|
23
|
+
raise 'Empty parameters to query are not allowed' if params.empty?
|
24
|
+
if params.instance_of?(String)
|
25
|
+
rsql = params
|
26
|
+
else
|
27
|
+
rsql = params.map { |k, v| "#{k}==#{v}" }.join(';')
|
28
|
+
end
|
29
|
+
@url_builder.set_query_param(:query, rsql)
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
alias where query
|
34
|
+
|
35
|
+
def initialize(resource_name, http_client, url_builder = URLBuilder.new(resource_name.to_s))
|
36
|
+
@base_url = resource_name
|
37
|
+
@http_client = http_client
|
38
|
+
@operation = :get
|
39
|
+
@body = nil
|
40
|
+
@headers = {}
|
41
|
+
@url_builder = url_builder
|
42
|
+
@id = nil
|
43
|
+
end
|
44
|
+
|
45
|
+
def finalize
|
46
|
+
options = {headers: @headers, body: @body }
|
47
|
+
@response = @http_client.send_request(@operation, @url_builder.url, options)
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
attr_reader :url_builder, :id
|
52
|
+
|
53
|
+
def id_set?
|
54
|
+
@id
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|