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
@@ -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
|
+
|