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.
Files changed (83) hide show
  1. checksums.yaml +7 -0
  2. data/bin/console +15 -0
  3. data/bin/setup +23 -0
  4. data/lib/kosapi_client.rb +22 -0
  5. data/lib/kosapi_client/api_client.rb +43 -0
  6. data/lib/kosapi_client/configuration.rb +23 -0
  7. data/lib/kosapi_client/entity.rb +19 -0
  8. data/lib/kosapi_client/entity/author.rb +17 -0
  9. data/lib/kosapi_client/entity/base_entity.rb +16 -0
  10. data/lib/kosapi_client/entity/base_person.rb +20 -0
  11. data/lib/kosapi_client/entity/boolean.rb +14 -0
  12. data/lib/kosapi_client/entity/course.rb +34 -0
  13. data/lib/kosapi_client/entity/course_event.rb +20 -0
  14. data/lib/kosapi_client/entity/data_mappings.rb +122 -0
  15. data/lib/kosapi_client/entity/enum.rb +11 -0
  16. data/lib/kosapi_client/entity/exam.rb +25 -0
  17. data/lib/kosapi_client/entity/id.rb +13 -0
  18. data/lib/kosapi_client/entity/link.rb +54 -0
  19. data/lib/kosapi_client/entity/ml_string.rb +42 -0
  20. data/lib/kosapi_client/entity/parallel.rb +20 -0
  21. data/lib/kosapi_client/entity/person.rb +10 -0
  22. data/lib/kosapi_client/entity/result_page.rb +46 -0
  23. data/lib/kosapi_client/entity/student.rb +25 -0
  24. data/lib/kosapi_client/entity/teacher.rb +14 -0
  25. data/lib/kosapi_client/entity/teacher_timetable_slot.rb +16 -0
  26. data/lib/kosapi_client/entity/timetable_slot.rb +16 -0
  27. data/lib/kosapi_client/hash_utils.rb +17 -0
  28. data/lib/kosapi_client/http_client.rb +36 -0
  29. data/lib/kosapi_client/kosapi_client.rb +45 -0
  30. data/lib/kosapi_client/kosapi_response.rb +32 -0
  31. data/lib/kosapi_client/oauth2_http_adapter.rb +36 -0
  32. data/lib/kosapi_client/request_builder.rb +59 -0
  33. data/lib/kosapi_client/request_builder_delegator.rb +52 -0
  34. data/lib/kosapi_client/resource.rb +12 -0
  35. data/lib/kosapi_client/resource/course_events_builder.rb +13 -0
  36. data/lib/kosapi_client/resource/courses_builder.rb +12 -0
  37. data/lib/kosapi_client/resource/exams_builder.rb +13 -0
  38. data/lib/kosapi_client/resource/parallels_builder.rb +20 -0
  39. data/lib/kosapi_client/resource/teachers_builder.rb +16 -0
  40. data/lib/kosapi_client/resource_mapper.rb +20 -0
  41. data/lib/kosapi_client/response_converter.rb +65 -0
  42. data/lib/kosapi_client/response_links.rb +40 -0
  43. data/lib/kosapi_client/response_preprocessor.rb +48 -0
  44. data/lib/kosapi_client/url_builder.rb +24 -0
  45. data/lib/kosapi_client/version.rb +3 -0
  46. data/spec/integration/course_events_spec.rb +13 -0
  47. data/spec/integration/courses_spec.rb +28 -0
  48. data/spec/integration/exams_spec.rb +20 -0
  49. data/spec/integration/parallels_spec.rb +68 -0
  50. data/spec/kosapi_client/api_client_spec.rb +22 -0
  51. data/spec/kosapi_client/configuration_spec.rb +30 -0
  52. data/spec/kosapi_client/entity/base_entity_spec.rb +20 -0
  53. data/spec/kosapi_client/entity/base_person_spec.rb +19 -0
  54. data/spec/kosapi_client/entity/boolean_spec.rb +23 -0
  55. data/spec/kosapi_client/entity/course_event_spec.rb +30 -0
  56. data/spec/kosapi_client/entity/data_mappings_spec.rb +154 -0
  57. data/spec/kosapi_client/entity/enum_spec.rb +20 -0
  58. data/spec/kosapi_client/entity/id_spec.rb +13 -0
  59. data/spec/kosapi_client/entity/link_spec.rb +89 -0
  60. data/spec/kosapi_client/entity/ml_string_spec.rb +52 -0
  61. data/spec/kosapi_client/entity/parallel_spec.rb +18 -0
  62. data/spec/kosapi_client/entity/result_page_spec.rb +42 -0
  63. data/spec/kosapi_client/entity/teacher_timetable_slot_spec.rb +19 -0
  64. data/spec/kosapi_client/entity/timetable_slot_spec.rb +22 -0
  65. data/spec/kosapi_client/hash_utils_spec.rb +20 -0
  66. data/spec/kosapi_client/http_client_spec.rb +30 -0
  67. data/spec/kosapi_client/kosapi_client_spec.rb +57 -0
  68. data/spec/kosapi_client/kosapi_response_spec.rb +96 -0
  69. data/spec/kosapi_client/oauth2_http_adapter_spec.rb +25 -0
  70. data/spec/kosapi_client/request_builder_delegator_spec.rb +72 -0
  71. data/spec/kosapi_client/request_builder_spec.rb +80 -0
  72. data/spec/kosapi_client/resource/courses_builder_spec.rb +20 -0
  73. data/spec/kosapi_client/resource/parallels_builder_spec.rb +49 -0
  74. data/spec/kosapi_client/resource_mapper_spec.rb +33 -0
  75. data/spec/kosapi_client/response_converter_spec.rb +58 -0
  76. data/spec/kosapi_client/response_links_spec.rb +52 -0
  77. data/spec/kosapi_client/response_preprocessor_spec.rb +51 -0
  78. data/spec/kosapi_client/url_builder_spec.rb +44 -0
  79. data/spec/spec_helper.rb +40 -0
  80. data/spec/support/client_helpers.rb +11 -0
  81. data/spec/support/helpers.rb +7 -0
  82. data/spec/support/shared_examples_for_fluent_api.rb +7 -0
  83. metadata +401 -0
@@ -0,0 +1,13 @@
1
+ module KOSapiClient
2
+ module Entity
3
+ class Id < String
4
+
5
+ def self.parse(str)
6
+ id = str.split(':').last
7
+ new(id)
8
+ end
9
+
10
+ end
11
+ end
12
+ end
13
+
@@ -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,10 @@
1
+ module KOSapiClient
2
+ module Entity
3
+ class Person < BasePerson
4
+
5
+ #todo roles/student
6
+ #todo roles/teacher
7
+
8
+ end
9
+ end
10
+ end
@@ -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
+