hawatel_search_jobs 0.1.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/.codeclimate.yml +19 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.rubocop.yml +1169 -0
- data/.travis.yml +4 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/CONTRIBUTING.md +70 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +146 -0
- data/Rakefile +6 -0
- data/hawatel_search_jobs.gemspec +30 -0
- data/lib/hawatel_search_jobs.rb +64 -0
- data/lib/hawatel_search_jobs/api.rb +9 -0
- data/lib/hawatel_search_jobs/api/careerbuilder.rb +170 -0
- data/lib/hawatel_search_jobs/api/careerjet.rb +126 -0
- data/lib/hawatel_search_jobs/api/indeed.rb +158 -0
- data/lib/hawatel_search_jobs/api/reed.rb +186 -0
- data/lib/hawatel_search_jobs/api/xing.rb +121 -0
- data/lib/hawatel_search_jobs/client.rb +165 -0
- data/lib/hawatel_search_jobs/helpers.rb +1 -0
- data/lib/hawatel_search_jobs/helpers/base.rb +39 -0
- data/lib/hawatel_search_jobs/version.rb +3 -0
- data/spec/careerbuilder_spec.rb +87 -0
- data/spec/careerjet_spec.rb +53 -0
- data/spec/client_spec.rb +62 -0
- data/spec/indeed_spec.rb +62 -0
- data/spec/reed_spec.rb +81 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/xing_spec.rb +48 -0
- metadata +158 -0
@@ -0,0 +1,121 @@
|
|
1
|
+
require 'xing_api'
|
2
|
+
|
3
|
+
module HawatelSearchJobs::Api
|
4
|
+
module Xing
|
5
|
+
class << self
|
6
|
+
include HawatelSearchJobs::Helpers::Base
|
7
|
+
|
8
|
+
DEFAULT = {
|
9
|
+
:keywords => '',
|
10
|
+
:location => '',
|
11
|
+
:company => ''
|
12
|
+
}
|
13
|
+
|
14
|
+
# @see https://github.com/xing/xing_api
|
15
|
+
# Search jobs based on specified keywords
|
16
|
+
#
|
17
|
+
# @param args [Hash]
|
18
|
+
# @option args :query [Hash] search criteria
|
19
|
+
# - *:keywords* (String )
|
20
|
+
# @option args :setting [Hash] @see https://dev.xing.com/docs/authentication
|
21
|
+
# - *:consumer_key* (String) - consumer key, required for authentication
|
22
|
+
# - *:consumer_secret* (String) - consumer secret, required for authentication
|
23
|
+
# - *:oauth_token* (String) - outh toker, required for authentication
|
24
|
+
# - *:oauth_token_secret* (String) - outh token secret, required for authentication
|
25
|
+
#
|
26
|
+
# @example
|
27
|
+
# search(:settings => HawatelSearchJobs.xing,:query => {:keywords => 'ruby'})
|
28
|
+
#
|
29
|
+
# @return [Hash<OpenStruct>]
|
30
|
+
def search(args)
|
31
|
+
args[:query] = DEFAULT.merge(args[:query]) if args[:query]
|
32
|
+
keywords = args[:query][:keywords]
|
33
|
+
result = send_request({:keywords => keywords, :offset => 0, :settings => args[:settings]})
|
34
|
+
if !result[:code]
|
35
|
+
set_attributes({:result => result, :page => 0, :keywords => keywords})
|
36
|
+
else
|
37
|
+
OpenStruct.new({:code => 501, :msg => 'incorrect settings'})
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Show next page of results
|
42
|
+
#
|
43
|
+
# @param args [Hash]
|
44
|
+
# @option page [Integer] page numer (default 0)
|
45
|
+
# @option query_key [String] keywords from last query
|
46
|
+
#
|
47
|
+
# @example
|
48
|
+
# page({:query_key => result.key, :page => 2}
|
49
|
+
#
|
50
|
+
# @return [Hash<OpenStruct>]
|
51
|
+
def page(args)
|
52
|
+
args[:page] = 0 if args[:page].nil?
|
53
|
+
result = XingApi::Job.search(args[:query_key], {:limit => 25, :offset => args[:page]*25})
|
54
|
+
set_attributes({:result => result, :page => args[:page], :keywords => args[:query_key]})
|
55
|
+
rescue XingApi::Error => e
|
56
|
+
{:code => e.status_code, :msg => e.text}
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
# Call Xing client request
|
61
|
+
#
|
62
|
+
# @param args [Hash]
|
63
|
+
# @option settings [Hash] authentication attributes
|
64
|
+
# @option keywords [String] keywords for query
|
65
|
+
#
|
66
|
+
# @return [Hash<OpenStruct>]
|
67
|
+
def send_request(args)
|
68
|
+
set_settings(args[:settings])
|
69
|
+
XingApi::Job.search(args[:keywords], {:limit => 25, :offset => 0})
|
70
|
+
rescue XingApi::Error => e
|
71
|
+
{:code => e.status_code, :msg => e.text}
|
72
|
+
end
|
73
|
+
|
74
|
+
# Build final result - set required attributes and return openstruct object
|
75
|
+
#
|
76
|
+
def set_attributes(args)
|
77
|
+
attributes = Hash.new
|
78
|
+
attributes[:totalResults] = args[:result][:jobs][:total]
|
79
|
+
attributes[:code] = '200'
|
80
|
+
attributes[:msg] = "OK"
|
81
|
+
attributes[:page] = args[:page]
|
82
|
+
attributes[:last] = args[:result][:jobs][:total] / 25
|
83
|
+
attributes[:key] = args[:keywords]
|
84
|
+
attributes[:jobs] = parse_raw_data(args[:result])
|
85
|
+
OpenStruct.new(attributes)
|
86
|
+
end
|
87
|
+
|
88
|
+
# Build jobs array with specified attributes
|
89
|
+
#
|
90
|
+
# @return [Array<OpenStruct>]
|
91
|
+
def parse_raw_data(result)
|
92
|
+
jobs = Array.new
|
93
|
+
return jobs if result['jobs'].to_s.empty?
|
94
|
+
result[:jobs][:items].each do |offer|
|
95
|
+
job = Hash.new
|
96
|
+
job[:jobtitle] = offer[:title]
|
97
|
+
job[:location] = "#{offer[:location][:country]}, #{offer[:location][:city]}"
|
98
|
+
job[:company] = offer[:company][:name]
|
99
|
+
job[:date] = convert_date_to_format(offer[:published_at], '%d/%m/%y')
|
100
|
+
job[:url] = offer[:links][:xing]
|
101
|
+
job = convert_empty_to_nil(job)
|
102
|
+
jobs << OpenStruct.new(job)
|
103
|
+
end
|
104
|
+
return jobs
|
105
|
+
end
|
106
|
+
|
107
|
+
# Set settings for XingApi client
|
108
|
+
#
|
109
|
+
# @param args [Hash]
|
110
|
+
def set_settings(args)
|
111
|
+
XingApi::Client.configure do |config|
|
112
|
+
config.consumer_key = args[:consumer_key]
|
113
|
+
config.consumer_secret = args[:consumer_secret]
|
114
|
+
config.oauth_token = args[:oauth_token]
|
115
|
+
config.oauth_token_secret = args[:oauth_token_secret]
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,165 @@
|
|
1
|
+
module HawatelSearchJobs
|
2
|
+
##
|
3
|
+
# = Client for a jobs search engine
|
4
|
+
#
|
5
|
+
# @!attribute [rw] indeed
|
6
|
+
# @return [Hash] settings of API
|
7
|
+
# @!attribute [rw] xing
|
8
|
+
# @return [Hash] settings of API
|
9
|
+
# @!attribute [rw] reed
|
10
|
+
# @return [Hash] settings of API
|
11
|
+
# @!attribute [rw] careerbuilder
|
12
|
+
# @return [Hash] settings of API
|
13
|
+
# @!attribute [rw] careerjet
|
14
|
+
# @return [Hash] settings of API
|
15
|
+
class Client
|
16
|
+
include HawatelSearchJobs::Api
|
17
|
+
|
18
|
+
# Values have to be the same name like module name of usesd APIs (HawatelSearchJobs::Api::[ApiName])
|
19
|
+
APIS = ['Indeed', 'Xing', 'Reed', 'Careerbuilder', 'CareerJet']
|
20
|
+
|
21
|
+
attr_reader :jobs_table
|
22
|
+
|
23
|
+
DEFAULT = {
|
24
|
+
:keywords => '',
|
25
|
+
:location => '',
|
26
|
+
:company => '',
|
27
|
+
}
|
28
|
+
|
29
|
+
def initialize
|
30
|
+
APIS.each do |api|
|
31
|
+
metaclasses.send(:attr_reader, api.downcase.to_sym)
|
32
|
+
instance_variable_set("@#{api.downcase}", HawatelSearchJobs.instance_variable_get("@"+api.downcase))
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def metaclasses
|
37
|
+
class << self; self; end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Search Jobs by specific criteria
|
41
|
+
# @param query [Hash]
|
42
|
+
# @option query [String] :keywords
|
43
|
+
# @option query [String] :location
|
44
|
+
# @option query [String] :company not working in Reed API
|
45
|
+
# @example
|
46
|
+
# HawatelSearchJobs.configure do |config|
|
47
|
+
# config.indeed[:activated] = true
|
48
|
+
# config.indeed[:api] = 'api.indeed.com'
|
49
|
+
# config.indeed[:version] = '2'
|
50
|
+
# config.indeed[:publisher] = 'secret-key'
|
51
|
+
#
|
52
|
+
# config.xing[:activated] = true
|
53
|
+
# config.xing[:consumer_key] = 'secret-key'
|
54
|
+
# config.xing[:consumer_secret] = 'secret-key'
|
55
|
+
# config.xing[:oauth_token] = 'secret-key'
|
56
|
+
# config.xing[:oauth_token_secret] = 'secret-key'
|
57
|
+
#
|
58
|
+
# config.reed[:activated] = true
|
59
|
+
# config.reed[:api] = 'reed.co.uk/api'
|
60
|
+
# config.reed[:clientid] = 'secret-key'
|
61
|
+
# config.reed[:version] = '1.0'
|
62
|
+
#
|
63
|
+
# config.careerbuilder[:activated] = true
|
64
|
+
# config.careerbuilder[:api] = 'api.careerbuilder.com'
|
65
|
+
# config.careerbuilder[:clientid] = 'secret-key'
|
66
|
+
# config.careerbuilder[:version] = 'v2'
|
67
|
+
#
|
68
|
+
# config.careerjet[:activated] = true
|
69
|
+
# config.careerjet[:api] = 'public.api.careerjet.net'
|
70
|
+
# end
|
71
|
+
#
|
72
|
+
# client = HawatelSearchJobs::Client.new
|
73
|
+
# client.search_jobs({:keywords => 'ruby'})
|
74
|
+
#
|
75
|
+
# p client.jobs_table[:indeed]
|
76
|
+
# p client.jobs_table[:xing]
|
77
|
+
# p client.jobs_table[:reed]
|
78
|
+
# p client.jobs_table[:careerbuilder]
|
79
|
+
# p client.jobs_table[:careerjet]
|
80
|
+
#
|
81
|
+
# client.next
|
82
|
+
# @return [Hash] First page of result for all providers (default maximum 25 records for each page)
|
83
|
+
def search_jobs(query = {})
|
84
|
+
query = DEFAULT.merge(query)
|
85
|
+
|
86
|
+
@jobs_table = Hash.new
|
87
|
+
|
88
|
+
APIS.each do |api|
|
89
|
+
api_module_name = Object.const_get('HawatelSearchJobs').const_get('Api').const_get(api)
|
90
|
+
api_inst_var = instance_variable_get("@"+api.downcase)
|
91
|
+
@jobs_table[api.downcase.to_sym] = api_module_name.search({:settings => api_inst_var, :query => query}) if api_inst_var[:activated]
|
92
|
+
end
|
93
|
+
|
94
|
+
@jobs_table
|
95
|
+
end
|
96
|
+
|
97
|
+
# Get next page of result
|
98
|
+
# @example
|
99
|
+
# p client.next
|
100
|
+
# p client.jobs_table
|
101
|
+
# @return [Hash] Next page of result for all providers (default maximum 25 records for each)
|
102
|
+
def next
|
103
|
+
next_result = Hash.new
|
104
|
+
APIS.each do |api|
|
105
|
+
api_module_name = Object.const_get('HawatelSearchJobs').const_get('Api').const_get(api)
|
106
|
+
api_inst_var = instance_variable_get("@"+api.downcase)
|
107
|
+
api_sym_name = api.downcase.to_sym
|
108
|
+
|
109
|
+
if api_inst_var[:activated] && next_result?(api_sym_name)
|
110
|
+
next_result[api_sym_name] = api_module_name.page({:settings => api_inst_var,
|
111
|
+
:page => @jobs_table[api_sym_name].page + 1,
|
112
|
+
:query_key => @jobs_table[api_sym_name].key})
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
return nil if next_result.empty?
|
117
|
+
|
118
|
+
@jobs_table = next_result
|
119
|
+
end
|
120
|
+
|
121
|
+
|
122
|
+
# Sum jobs offers from specified api or count all
|
123
|
+
#
|
124
|
+
# @param args [Hash]
|
125
|
+
# @option args [String] :api name
|
126
|
+
# @example
|
127
|
+
# p client.count
|
128
|
+
# p client.count('indeed')
|
129
|
+
#
|
130
|
+
# @return [Integer]
|
131
|
+
def count(api = nil)
|
132
|
+
sum = 0
|
133
|
+
|
134
|
+
if api
|
135
|
+
api = api.downcase
|
136
|
+
api_inst_var = instance_variable_get("@"+api)
|
137
|
+
if api_inst_var[:activated]
|
138
|
+
sum = @jobs_table[:"#{api}"].totalResults if @jobs_table[:"#{api}"].totalResults
|
139
|
+
end
|
140
|
+
else
|
141
|
+
APIS.each do |provider|
|
142
|
+
api_inst_var = instance_variable_get("@"+provider.downcase)
|
143
|
+
if api_inst_var[:activated]
|
144
|
+
provider = provider.downcase
|
145
|
+
sum += @jobs_table[:"#{provider}"].totalResults if @jobs_table[:"#{provider}"].totalResults
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
return sum
|
150
|
+
end
|
151
|
+
|
152
|
+
private
|
153
|
+
def next_result?(provider)
|
154
|
+
if @jobs_table[provider] && @jobs_table[provider].page && @jobs_table[provider].last &&
|
155
|
+
@jobs_table[provider].page < @jobs_table[provider].last
|
156
|
+
true
|
157
|
+
else
|
158
|
+
false
|
159
|
+
end
|
160
|
+
|
161
|
+
end
|
162
|
+
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'hawatel_search_jobs/helpers/base'
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'json'
|
3
|
+
require 'date'
|
4
|
+
|
5
|
+
module HawatelSearchJobs
|
6
|
+
module Helpers
|
7
|
+
module Base
|
8
|
+
|
9
|
+
private
|
10
|
+
def send_request(url, opt = {})
|
11
|
+
uri = URI.parse(url)
|
12
|
+
req = Net::HTTP::Get.new(uri)
|
13
|
+
if opt[:basic_auth] && opt[:basic_auth][:username] && opt[:basic_auth][:password]
|
14
|
+
req.basic_auth(opt[:basic_auth][:username], opt[:basic_auth][:password])
|
15
|
+
end
|
16
|
+
sock = Net::HTTP.new(uri.host, uri.port)
|
17
|
+
sock.use_ssl = true if uri.scheme == 'https'
|
18
|
+
|
19
|
+
sock.start { |http| http.request(req) }
|
20
|
+
end
|
21
|
+
|
22
|
+
def convert_empty_to_nil(hash)
|
23
|
+
new = {}
|
24
|
+
hash.each do |k,v|
|
25
|
+
if v.to_s.empty?
|
26
|
+
new[k] = nil
|
27
|
+
else
|
28
|
+
new[k] = v
|
29
|
+
end
|
30
|
+
end
|
31
|
+
new
|
32
|
+
end
|
33
|
+
|
34
|
+
def convert_date_to_format(date, format)
|
35
|
+
DateTime.parse(date).to_date.strftime(format) if !date.to_s.empty?
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe HawatelSearchJobs::Api::Careerbuilder do
|
4
|
+
context 'APIs returned jobs' do
|
5
|
+
before(:each) do
|
6
|
+
HawatelSearchJobs.configure do |config|
|
7
|
+
config.careerbuilder[:api] = 'api.careerbuilder.com'
|
8
|
+
config.careerbuilder[:version] = 'v2'
|
9
|
+
config.careerbuilder[:clientid] = ''
|
10
|
+
end
|
11
|
+
|
12
|
+
@query_api = {:keywords => 'ruby', :location => ''}
|
13
|
+
@result = HawatelSearchJobs::Api::Careerbuilder.search(
|
14
|
+
:settings => HawatelSearchJobs.careerbuilder,
|
15
|
+
:query => {
|
16
|
+
:keywords => @query_api[:keywords],
|
17
|
+
:location => @query_api[:location]
|
18
|
+
})
|
19
|
+
end
|
20
|
+
|
21
|
+
xit '#search' do
|
22
|
+
validate_result(@result, @query_api)
|
23
|
+
expect(@result.page).to be >= 0
|
24
|
+
expect(@result.last).to be >= 0
|
25
|
+
end
|
26
|
+
|
27
|
+
xit '#page' do
|
28
|
+
validate_result(@result, @query_api)
|
29
|
+
page_result = HawatelSearchJobs::Api::Careerbuilder.page({
|
30
|
+
:settings => HawatelSearchJobs.careerbuilder,
|
31
|
+
:query_key => @result.key,
|
32
|
+
:page => 1})
|
33
|
+
expect(page_result.key).to match(/&PageNumber=2/)
|
34
|
+
expect(page_result.page).to be == 1
|
35
|
+
expect(page_result.last).to be >= 0
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context 'APIs returned empty table' do
|
40
|
+
before(:each) do
|
41
|
+
HawatelSearchJobs.configure do |config|
|
42
|
+
config.careerbuilder[:api] = 'api.careerbuilder.com'
|
43
|
+
config.careerbuilder[:version] = 'v2'
|
44
|
+
config.careerbuilder[:clientid] = ''
|
45
|
+
end
|
46
|
+
|
47
|
+
@query_api = {:keywords => 'job-not-found-zero-records', :location => 'London'}
|
48
|
+
@result = HawatelSearchJobs::Api::Careerbuilder.search(
|
49
|
+
:settings => HawatelSearchJobs.careerbuilder,
|
50
|
+
:query => {
|
51
|
+
:keywords => @query_api[:keywords],
|
52
|
+
:location => @query_api[:location]
|
53
|
+
})
|
54
|
+
end
|
55
|
+
|
56
|
+
xit '#search' do
|
57
|
+
validate_result(@result, @query_api)
|
58
|
+
expect(@result.totalResults).to eq(0)
|
59
|
+
expect(@result.page).to be_nil
|
60
|
+
expect(@result.last).to be_nil
|
61
|
+
expect(@result.jobs).to be_nil
|
62
|
+
end
|
63
|
+
|
64
|
+
xit '#page' do
|
65
|
+
validate_result(@result, @query_api)
|
66
|
+
page_result = HawatelSearchJobs::Api::Careerbuilder.page({
|
67
|
+
:settings => HawatelSearchJobs.careerbuilder,
|
68
|
+
:query_key => @result.key,
|
69
|
+
:page => 1})
|
70
|
+
expect(page_result.key).to match(/&PageNumber=2/)
|
71
|
+
expect(@result.totalResults).to eq(0)
|
72
|
+
expect(@result.page).to be_nil
|
73
|
+
expect(@result.last).to be_nil
|
74
|
+
expect(@result.jobs).to be_nil
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def validate_result(result, query_api)
|
81
|
+
expect(result.code).to eq(200)
|
82
|
+
expect(result.msg).to eq('OK')
|
83
|
+
expect(result.totalResults).to be >= 0
|
84
|
+
expect(result.key).to match("Location=#{query_api[:location]}")
|
85
|
+
expect(result.key).to match("Keywords=#{query_api[:keywords]}")
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe HawatelSearchJobs::Api::CareerJet do
|
4
|
+
|
5
|
+
let(:client) { return HawatelSearchJobs::Client.new }
|
6
|
+
let(:result) {
|
7
|
+
return HawatelSearchJobs::Api::CareerJet.search(
|
8
|
+
:settings => HawatelSearchJobs.careerjet,
|
9
|
+
:query => { :keywords => 'ruby', :company => '' }
|
10
|
+
)
|
11
|
+
}
|
12
|
+
|
13
|
+
before(:each) do
|
14
|
+
HawatelSearchJobs.configure do |config|
|
15
|
+
config.careerjet[:api] = 'public.api.careerjet.net'
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
it "metadata from search() result" do
|
20
|
+
expect(result.totalResults).to be_a_kind_of(Integer)
|
21
|
+
expect(result.page).to be_a_kind_of(Integer)
|
22
|
+
expect(result.last).to be_a_kind_of(Integer)
|
23
|
+
expect(result.key).to be_a_kind_of(String)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "job attributes from search() result" do
|
27
|
+
expect(result.jobs.size).to be > 0
|
28
|
+
result.jobs.each do |job|
|
29
|
+
expect(job.jobtitle).to be_a_kind_of(String)
|
30
|
+
expect(job.url).to include('http')
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
it "call page() without page param (default 0)" do
|
35
|
+
jobs = HawatelSearchJobs::Api::CareerJet.page({:query_key => result.key})
|
36
|
+
expect(jobs.page).to eq(0)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "call page() with specified page" do
|
40
|
+
jobs = HawatelSearchJobs::Api::CareerJet.page({:query_key => result.key, :page => 2})
|
41
|
+
expect(jobs.page).to eq(2)
|
42
|
+
end
|
43
|
+
|
44
|
+
it "call search() with location param" do
|
45
|
+
result = HawatelSearchJobs::Api::CareerJet.search(:settings => HawatelSearchJobs.careerjet,
|
46
|
+
:query => { :keywords => 'ruby', :location => 'London' })
|
47
|
+
result.jobs.each do |job|
|
48
|
+
expect(job.location).to include("London")
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
end
|