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,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 46750e332cf19232187a8a9e5c66b9fa128e246a
4
+ data.tar.gz: 8d9a60a7399406d633ddb03bbadd51bc151b8372
5
+ SHA512:
6
+ metadata.gz: 424aeb28f513261f67bdb98783d22fcf6dbc536f80f652386c9d666b5a9d437e49c92951d4bce03c71dd9ddeb8c518a2f1c23e760eba260a6fce530f1a3c03d7
7
+ data.tar.gz: 8b55b7666492503cf2d382475b4dead2d3f4dc0a9e4aff95df143339e9c3e5613a582a6d77555c1c3d261406baa4880142e2d29ec68a410477902474335e23fe
@@ -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
@@ -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,17 @@
1
+ module KOSapiClient
2
+ module Entity
3
+ class Author
4
+
5
+ attr_reader :name
6
+
7
+ def initialize(name)
8
+ @name = name
9
+ end
10
+
11
+ def self.parse(contents)
12
+ new(contents[:atom_name])
13
+ end
14
+ end
15
+ end
16
+ end
17
+
@@ -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,14 @@
1
+ module KOSapiClient
2
+ module Entity
3
+ class Boolean
4
+
5
+ def self.parse(str)
6
+ return true if str == 'true'
7
+ return false if str == 'false'
8
+ raise "Boolean parsing failed, invalid string: #{str}"
9
+ end
10
+
11
+ end
12
+ end
13
+ end
14
+
@@ -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,11 @@
1
+ module KOSapiClient
2
+ module Entity
3
+ class Enum
4
+
5
+ def self.parse(contents)
6
+ contents.downcase.to_sym
7
+ end
8
+
9
+ end
10
+ end
11
+ 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