roseflow-proxycurl 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.standard.yml +3 -0
- data/CHANGELOG.md +10 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +12 -0
- data/LICENSE.txt +21 -0
- data/README.md +49 -0
- data/Rakefile +10 -0
- data/lib/roseflow/linkedin/company/lookup_query.rb +58 -0
- data/lib/roseflow/linkedin/company/object.rb +82 -0
- data/lib/roseflow/linkedin/company/profile_query.rb +50 -0
- data/lib/roseflow/linkedin/company.rb +38 -0
- data/lib/roseflow/linkedin/job/object.rb +77 -0
- data/lib/roseflow/linkedin/job/profile_query.rb +32 -0
- data/lib/roseflow/linkedin/job/search_query.rb +48 -0
- data/lib/roseflow/linkedin/job.rb +38 -0
- data/lib/roseflow/linkedin/job_list_entry.rb +20 -0
- data/lib/roseflow/linkedin/person/experience.rb +73 -0
- data/lib/roseflow/linkedin/person/lookup_query.rb +57 -0
- data/lib/roseflow/linkedin/person/object.rb +69 -0
- data/lib/roseflow/linkedin/person/profile_query.rb +53 -0
- data/lib/roseflow/linkedin/person/role_query.rb +48 -0
- data/lib/roseflow/linkedin/person.rb +50 -0
- data/lib/roseflow/proxycurl/client.rb +27 -0
- data/lib/roseflow/proxycurl/config.rb +17 -0
- data/lib/roseflow/proxycurl/object.rb +11 -0
- data/lib/roseflow/proxycurl/version.rb +18 -0
- data/lib/roseflow/proxycurl.rb +10 -0
- data/roseflow-proxycurl.gemspec +49 -0
- data/sig/roseflow/proxycurl.rbs +6 -0
- metadata +244 -0
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Types
|
4
|
+
DateOrNil = Date | Nil
|
5
|
+
StringOrNil = String | Nil
|
6
|
+
end
|
7
|
+
|
8
|
+
module Functions
|
9
|
+
extend Dry::Transformer::Registry
|
10
|
+
|
11
|
+
register :to_date, ->(v) { Date.parse("#{v.dig(:year)}-#{v.dig(:month)}-#{v.dig(:day)}") unless v.nil? }
|
12
|
+
end
|
13
|
+
|
14
|
+
module Roseflow
|
15
|
+
module LinkedIn
|
16
|
+
class Person
|
17
|
+
class ExperienceMapper < Dry::Transformer::Pipe
|
18
|
+
import Dry::Transformer::HashTransformations
|
19
|
+
|
20
|
+
def custom_t(*args)
|
21
|
+
Functions[*args]
|
22
|
+
end
|
23
|
+
|
24
|
+
define! do
|
25
|
+
deep_symbolize_keys
|
26
|
+
rename_keys company_linkedin_profile_url: :company_profile_url, starts_at: :started_on, ends_at: :ended_on
|
27
|
+
|
28
|
+
map_value :started_on, ->(v) { Date.parse("#{v.dig(:year)}-#{v.dig(:month)}-#{v.dig(:day)}") unless v.nil? }
|
29
|
+
map_value :ended_on, ->(v) { Date.parse("#{v.dig(:year)}-#{v.dig(:month)}-#{v.dig(:day)}") unless v.nil? }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class Experience < Dry::Struct
|
34
|
+
defines :contract_object
|
35
|
+
|
36
|
+
class ExperienceContract < Dry::Validation::Contract
|
37
|
+
params do
|
38
|
+
required(:title).filled(:string)
|
39
|
+
required(:company).filled(:string)
|
40
|
+
optional(:company_profile_url).filled(:string)
|
41
|
+
optional(:location)
|
42
|
+
optional(:description)
|
43
|
+
optional(:started_on).filled(:date)
|
44
|
+
optional(:ended_on)
|
45
|
+
end
|
46
|
+
|
47
|
+
def call(input)
|
48
|
+
transformed = ExperienceMapper.new.call(input)
|
49
|
+
super(transformed)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
attribute :title, Types::String
|
54
|
+
attribute :company, Types::String
|
55
|
+
attribute? :company_profile_url, Types::StringOrNil
|
56
|
+
attribute? :location, Types::StringOrNil
|
57
|
+
attribute? :description, Types::StringOrNil
|
58
|
+
attribute :started_on, Types::Date
|
59
|
+
attribute? :ended_on, Types::DateOrNil
|
60
|
+
|
61
|
+
contract_object ExperienceContract
|
62
|
+
|
63
|
+
schema schema.strict
|
64
|
+
|
65
|
+
def self.new(input)
|
66
|
+
validation = self.contract_object.new.call(input)
|
67
|
+
raise ArgumentError, validation.errors.to_h unless validation.success?
|
68
|
+
super(validation.to_h)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry-validation"
|
4
|
+
require "roseflow/proxycurl/object"
|
5
|
+
require "roseflow/linkedin/person"
|
6
|
+
require "roseflow/types"
|
7
|
+
|
8
|
+
module Roseflow
|
9
|
+
module LinkedIn
|
10
|
+
class Person
|
11
|
+
class LookupQuery < Proxycurl::ProxycurlObject
|
12
|
+
class LookupQueryContract < Dry::Validation::Contract
|
13
|
+
params do
|
14
|
+
required(:domain).filled(:string)
|
15
|
+
required(:first_name).filled(:string)
|
16
|
+
optional(:last_name).filled(:string)
|
17
|
+
optional(:title).filled(:string)
|
18
|
+
optional(:location).filled(:string)
|
19
|
+
optional(:enrich).filled(:bool)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
contract_object LookupQueryContract
|
24
|
+
|
25
|
+
schema schema.strict
|
26
|
+
|
27
|
+
attribute :domain, Types::String
|
28
|
+
attribute :first_name, Types::String
|
29
|
+
attribute? :last_name, Types::String
|
30
|
+
attribute? :title, Types::String
|
31
|
+
attribute? :location, Types::String
|
32
|
+
attribute? :enrich, Types::Bool.default(false)
|
33
|
+
|
34
|
+
def self.new(input)
|
35
|
+
validation = self.contract_object.new.call(input)
|
36
|
+
raise ArgumentError, validation.errors.to_h.inspect unless validation.success?
|
37
|
+
super(input)
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_request_params
|
41
|
+
params = {
|
42
|
+
company_domain: domain,
|
43
|
+
first_name: first_name,
|
44
|
+
last_name: last_name,
|
45
|
+
title: title,
|
46
|
+
location: location,
|
47
|
+
enrich_profile: enrich == true ? "enrich" : "skip",
|
48
|
+
}
|
49
|
+
|
50
|
+
params.each_pair do |key, value|
|
51
|
+
params.delete(key) unless value
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry-transformer"
|
4
|
+
|
5
|
+
require "roseflow/linkedin/person/experience"
|
6
|
+
|
7
|
+
module Roseflow
|
8
|
+
module LinkedIn
|
9
|
+
class Person
|
10
|
+
class Object < Dry::Struct
|
11
|
+
defines :contract_object
|
12
|
+
|
13
|
+
class PersonMapper < Dry::Transformer::Pipe
|
14
|
+
import Dry::Transformer::HashTransformations
|
15
|
+
|
16
|
+
define! do
|
17
|
+
deep_symbolize_keys
|
18
|
+
rename_keys public_identifier: :id, profile_pic_url: :profile_picture_url, follower_count: :followers
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class PersonContract < Dry::Validation::Contract
|
23
|
+
params do
|
24
|
+
required(:id).filled(:string)
|
25
|
+
required(:first_name).filled(:string)
|
26
|
+
required(:last_name).filled(:string)
|
27
|
+
required(:full_name).filled(:string)
|
28
|
+
required(:profile_url).filled(:string)
|
29
|
+
required(:profile_picture_url).filled(:string)
|
30
|
+
required(:headline).filled(:string)
|
31
|
+
optional(:followers)
|
32
|
+
optional(:connections)
|
33
|
+
optional(:occupation)
|
34
|
+
optional(:experiences).filled(:array)
|
35
|
+
end
|
36
|
+
|
37
|
+
def call(input)
|
38
|
+
transformed = PersonMapper.new.call(input)
|
39
|
+
super(transformed)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
attribute :id, Types::String
|
44
|
+
attribute :first_name, Types::String
|
45
|
+
attribute :last_name, Types::String
|
46
|
+
attribute :full_name, Types::String
|
47
|
+
attribute :profile_url, Types::String
|
48
|
+
attribute :profile_picture_url, Types::String
|
49
|
+
attribute :headline, Types::String
|
50
|
+
attribute :followers, Types::Integer.optional
|
51
|
+
attribute :connections, Types::Integer.optional
|
52
|
+
attribute :occupation, Types::String
|
53
|
+
attribute :experiences, Types::Array.of(Experience).optional
|
54
|
+
|
55
|
+
contract_object PersonContract
|
56
|
+
|
57
|
+
schema schema.strict
|
58
|
+
|
59
|
+
def self.new(input)
|
60
|
+
experiences = input.delete("experiences")
|
61
|
+
input[:experiences] = experiences.map { |experience| Experience.new(experience) } if experiences
|
62
|
+
validation = self.contract_object.new.call(input)
|
63
|
+
raise ArgumentError, validation.errors.to_h.inspect unless validation.success?
|
64
|
+
super(validation.to_h)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry-validation"
|
4
|
+
require "roseflow/proxycurl/object"
|
5
|
+
require "roseflow/linkedin/person"
|
6
|
+
require "roseflow/types"
|
7
|
+
|
8
|
+
module Roseflow
|
9
|
+
module LinkedIn
|
10
|
+
class Person
|
11
|
+
end
|
12
|
+
|
13
|
+
class Person::ProfileQuery < Proxycurl::ProxycurlObject
|
14
|
+
class ProfileQueryContract < Dry::Validation::Contract
|
15
|
+
params do
|
16
|
+
required(:url).filled(:string)
|
17
|
+
end
|
18
|
+
|
19
|
+
rule (:url) do
|
20
|
+
unless URI.parse(value).is_a?(URI::HTTP)
|
21
|
+
key.failure("must be a valid URL")
|
22
|
+
end
|
23
|
+
|
24
|
+
unless value.match?(/linkedin\.com\/in\/\w+/)
|
25
|
+
key.failure("must be a valid LinkedIn profile URL")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
contract_object ProfileQueryContract
|
31
|
+
|
32
|
+
schema schema.strict
|
33
|
+
|
34
|
+
attribute :url, Types::String
|
35
|
+
attribute :fallback_to_cache, Types::String.default("on-error")
|
36
|
+
attribute :use_cache, Types::String.default("if-present")
|
37
|
+
attribute :skills, Types::String.default("exclude")
|
38
|
+
attribute :inferred_salary, Types::String.default("exclude")
|
39
|
+
attribute :personal_email, Types::String.default("exclude")
|
40
|
+
attribute :personal_contact_number, Types::String.default("exclude")
|
41
|
+
attribute :twitter_profile_id, Types::String.default("exclude")
|
42
|
+
attribute :facebook_profile_id, Types::String.default("exclude")
|
43
|
+
attribute :github_profile_id, Types::String.default("exclude")
|
44
|
+
attribute :extra, Types::String.default("exclude")
|
45
|
+
|
46
|
+
def self.new(input)
|
47
|
+
validation = self.contract_object.new.call(input)
|
48
|
+
raise ArgumentError, validation.errors.to_h.inspect unless validation.success?
|
49
|
+
super(input)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry-validation"
|
4
|
+
require "roseflow/proxycurl/object"
|
5
|
+
require "roseflow/linkedin/person"
|
6
|
+
require "roseflow/types"
|
7
|
+
|
8
|
+
module Roseflow
|
9
|
+
module LinkedIn
|
10
|
+
class Person
|
11
|
+
class RoleQuery < Proxycurl::ProxycurlObject
|
12
|
+
class RoleQueryContract < Dry::Validation::Contract
|
13
|
+
params do
|
14
|
+
required(:role).filled(:string)
|
15
|
+
required(:company_name).filled(:string)
|
16
|
+
optional(:enrich).filled(:bool)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
contract_object RoleQueryContract
|
21
|
+
|
22
|
+
schema schema.strict
|
23
|
+
|
24
|
+
attribute :role, Types::String
|
25
|
+
attribute :company_name, Types::String
|
26
|
+
attribute? :enrich, Types::Bool.default(false)
|
27
|
+
|
28
|
+
def self.new(input)
|
29
|
+
validation = self.contract_object.new.call(input)
|
30
|
+
raise ArgumentError, validation.errors.to_h.inspect unless validation.success?
|
31
|
+
super(input)
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_request_params
|
35
|
+
params = {
|
36
|
+
company_name: company_name,
|
37
|
+
role: role,
|
38
|
+
enrich_profile: enrich == true ? "enrich" : "skip",
|
39
|
+
}
|
40
|
+
|
41
|
+
params.each_pair do |key, value|
|
42
|
+
params.delete(key) unless value
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "roseflow/linkedin/person/lookup_query"
|
4
|
+
require "roseflow/linkedin/person/profile_query"
|
5
|
+
require "roseflow/linkedin/person/role_query"
|
6
|
+
|
7
|
+
module Roseflow
|
8
|
+
module LinkedIn
|
9
|
+
class Person
|
10
|
+
def initialize(connection)
|
11
|
+
@connection = connection
|
12
|
+
end
|
13
|
+
|
14
|
+
def find(url, **options)
|
15
|
+
query = ProfileQuery.new(url: url, **options)
|
16
|
+
response = @connection.get("v2/linkedin", query.to_h)
|
17
|
+
return Person::Object.new(JSON.parse(response.body).merge(profile_url: url)) if person_found?(response)
|
18
|
+
return nil if person_not_found?(response)
|
19
|
+
end
|
20
|
+
|
21
|
+
def lookup(query)
|
22
|
+
query = LookupQuery.new(query)
|
23
|
+
response = @connection.get("linkedin/profile/resolve", query.to_request_params)
|
24
|
+
return JSON.parse(response.body).dig("url") if person_found?(response)
|
25
|
+
return nil if person_not_found?(response)
|
26
|
+
end
|
27
|
+
|
28
|
+
def role(query)
|
29
|
+
query = RoleQuery.new(query)
|
30
|
+
response = @connection.get("find/company/role/", query.to_request_params)
|
31
|
+
if person_found?(response)
|
32
|
+
url = JSON.parse(response.body).dig("linkedin_profile_url")
|
33
|
+
return find(url)
|
34
|
+
end
|
35
|
+
|
36
|
+
return nil if person_not_found?(response)
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def person_found?(response)
|
42
|
+
response.success? && response.status == 200
|
43
|
+
end
|
44
|
+
|
45
|
+
def person_not_found?(response)
|
46
|
+
response.success? && response.status == 404
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Roseflow
|
4
|
+
module Proxycurl
|
5
|
+
class Client
|
6
|
+
def initialize(config = Config.new)
|
7
|
+
@config = config
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def connection
|
13
|
+
@connection ||= Faraday.new(
|
14
|
+
url: proxycurl_base_url,
|
15
|
+
) do |faraday|
|
16
|
+
faraday.request :authorization, :Bearer, -> { config.api_key }
|
17
|
+
faraday.request :url_encoded
|
18
|
+
faraday.adapter Faraday.default_adapter
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def proxycurl_base_url
|
23
|
+
@config.base_url || Config::DEFAULT_BASE_URL
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "anyway_config"
|
4
|
+
|
5
|
+
module Roseflow
|
6
|
+
module Proxycurl
|
7
|
+
class Config < Anyway::Config
|
8
|
+
DEFAULT_BASE_URL = "https://nubela.co/proxycurl/api/"
|
9
|
+
|
10
|
+
config_name :proxycurl
|
11
|
+
|
12
|
+
attr_config :api_key, :base_url
|
13
|
+
|
14
|
+
required :api_key
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Roseflow
|
4
|
+
module Proxycurl
|
5
|
+
def self.gem_version
|
6
|
+
Gem::Version.new(VERSION::STRING)
|
7
|
+
end
|
8
|
+
|
9
|
+
module VERSION
|
10
|
+
MAJOR = 0
|
11
|
+
MINOR = 5
|
12
|
+
PATCH = 0
|
13
|
+
PRE = nil
|
14
|
+
|
15
|
+
STRING = [MAJOR, MINOR, PATCH, PRE].compact.join(".")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/roseflow/proxycurl/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "roseflow-proxycurl"
|
7
|
+
spec.version = Roseflow::Proxycurl.gem_version
|
8
|
+
spec.authors = ["Lauri Jutila"]
|
9
|
+
spec.email = ["ljuti@roseflow.ai"]
|
10
|
+
|
11
|
+
spec.summary = "Proxycurl integration for Roseflow."
|
12
|
+
spec.description = "Proxycurl integration for Roseflow."
|
13
|
+
spec.homepage = "https://github.com/roseflow-ai/roseflow-proxycurl"
|
14
|
+
spec.license = "MIT"
|
15
|
+
spec.required_ruby_version = ">= 3.2.0"
|
16
|
+
|
17
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
18
|
+
spec.metadata["source_code_uri"] = "https://github.com/roseflow-ai/roseflow-proxycurl"
|
19
|
+
spec.metadata["changelog_uri"] = "https://github.com/roseflow-ai/roseflow-proxycurl/blob/master/CHANGELOG.md"
|
20
|
+
|
21
|
+
# Specify which files should be added to the gem when it is released.
|
22
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
23
|
+
spec.files = Dir.chdir(__dir__) do
|
24
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
25
|
+
(File.expand_path(f) == __FILE__) || f.start_with?(*%w[bin/ test/ spec/ features/ .git .circleci appveyor])
|
26
|
+
end
|
27
|
+
end
|
28
|
+
spec.bindir = "exe"
|
29
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
30
|
+
spec.require_paths = ["lib"]
|
31
|
+
|
32
|
+
# Uncomment to register a new dependency of your gem
|
33
|
+
spec.add_dependency "activesupport", "~> 6.0"
|
34
|
+
spec.add_dependency "anyway_config", "~> 2.0"
|
35
|
+
spec.add_dependency "dry-struct", "~> 1.6"
|
36
|
+
spec.add_dependency "dry-transformer", "~> 1.0"
|
37
|
+
spec.add_dependency "dry-validation", "~> 1.10"
|
38
|
+
spec.add_dependency "faraday", "~> 2.0"
|
39
|
+
spec.add_dependency "faraday-retry", "~> 2.0"
|
40
|
+
|
41
|
+
spec.add_development_dependency "awesome_print"
|
42
|
+
spec.add_development_dependency "pry"
|
43
|
+
spec.add_development_dependency "webmock"
|
44
|
+
spec.add_development_dependency "vcr"
|
45
|
+
spec.add_development_dependency "roseflow"
|
46
|
+
|
47
|
+
# For more information and examples about making a new gem, check out our
|
48
|
+
# guide at: https://bundler.io/guides/creating_gem.html
|
49
|
+
end
|