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
checksums.yaml
ADDED
@@ -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
|
data/bin/console
ADDED
@@ -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
|
data/bin/setup
ADDED
@@ -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,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,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,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
|