kosapi_client 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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
+