arbetsformedlingen 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/.gitignore +17 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +138 -0
- data/Rakefile +6 -0
- data/arbetsformedlingen.gemspec +33 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/config/locales/errors.yml +16 -0
- data/data/country-codes.csv +237 -0
- data/data/municipality-codes.csv +292 -0
- data/lib/arbetsformedlingen.rb +56 -0
- data/lib/arbetsformedlingen/client.rb +25 -0
- data/lib/arbetsformedlingen/codes/country_code.rb +24 -0
- data/lib/arbetsformedlingen/codes/drivers_license_code.rb +33 -0
- data/lib/arbetsformedlingen/codes/experience_required_code.rb +21 -0
- data/lib/arbetsformedlingen/codes/municipality_code.rb +31 -0
- data/lib/arbetsformedlingen/codes/salary_type_code.rb +21 -0
- data/lib/arbetsformedlingen/models/application_method.rb +20 -0
- data/lib/arbetsformedlingen/models/company.rb +50 -0
- data/lib/arbetsformedlingen/models/document.rb +28 -0
- data/lib/arbetsformedlingen/models/dry/predicates.rb +66 -0
- data/lib/arbetsformedlingen/models/dry/types.rb +34 -0
- data/lib/arbetsformedlingen/models/model.rb +25 -0
- data/lib/arbetsformedlingen/models/packet.rb +39 -0
- data/lib/arbetsformedlingen/models/position.rb +56 -0
- data/lib/arbetsformedlingen/models/publication.rb +30 -0
- data/lib/arbetsformedlingen/models/qualification.rb +23 -0
- data/lib/arbetsformedlingen/models/salary.rb +20 -0
- data/lib/arbetsformedlingen/models/schedule.rb +56 -0
- data/lib/arbetsformedlingen/output_builder.rb +226 -0
- data/lib/arbetsformedlingen/response.rb +34 -0
- data/lib/arbetsformedlingen/version.rb +3 -0
- metadata +178 -0
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'csv'
|
2
|
+
|
3
|
+
module Arbetsformedlingen
|
4
|
+
class MunicipalityCode
|
5
|
+
CODE_MAP = CSV.read(
|
6
|
+
File.expand_path('../../../../data/municipality-codes.csv', __FILE__)
|
7
|
+
).to_h.freeze
|
8
|
+
CODES_MAP_INVERTED = CODE_MAP.invert.freeze
|
9
|
+
|
10
|
+
def self.to_code(name)
|
11
|
+
normalized = normalize(name)
|
12
|
+
CODE_MAP.fetch(normalized) do
|
13
|
+
normalized if CODES_MAP_INVERTED[normalized]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.valid?(name)
|
18
|
+
!to_code(name).nil?
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.normalize(name)
|
22
|
+
name.to_s.strip
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.to_form_array(name_only: false)
|
26
|
+
return CODE_MAP.to_a unless name_only
|
27
|
+
|
28
|
+
CODE_MAP.map { |name, _code| [name, name] }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module Arbetsformedlingen
|
4
|
+
class SalaryTypeCode
|
5
|
+
CODE_MAP = {
|
6
|
+
'fixed' => '1',
|
7
|
+
'fixed_and_commission' => '2',
|
8
|
+
'commission' => '3'
|
9
|
+
}.freeze
|
10
|
+
|
11
|
+
CODES = Set.new(CODE_MAP.values).freeze
|
12
|
+
|
13
|
+
def self.to_code(value)
|
14
|
+
CODE_MAP.fetch(value.to_s, nil)
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.valid?(value)
|
18
|
+
CODES.include?(value.to_s)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Arbetsformedlingen
|
2
|
+
ApplicationMethodSchema = Dry::Validation.Form do
|
3
|
+
configure do
|
4
|
+
config.type_specs = true
|
5
|
+
config.messages_file = File.expand_path('../../../../config/locales/errors.yml', __FILE__)
|
6
|
+
|
7
|
+
predicates(Predicates)
|
8
|
+
end
|
9
|
+
|
10
|
+
required(:external, Types::Bool).filled
|
11
|
+
required(:summary, Types::StrippedString).filled
|
12
|
+
required(:url, Types::StrippedString).filled(:url?)
|
13
|
+
end
|
14
|
+
|
15
|
+
class ApplicationMethod < Model
|
16
|
+
def initialize(hash)
|
17
|
+
super(ApplicationMethodSchema.call(hash))
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Arbetsformedlingen
|
4
|
+
CompanySchema = Dry::Validation.Form do
|
5
|
+
configure do
|
6
|
+
config.type_specs = true
|
7
|
+
config.messages_file = File.expand_path('../../../../config/locales/errors.yml', __FILE__)
|
8
|
+
|
9
|
+
predicates(Predicates)
|
10
|
+
end
|
11
|
+
|
12
|
+
required(:name, Types::StrippedString).filled
|
13
|
+
required(:cin, Types::CIN).filled(:str?, :cin?)
|
14
|
+
required(:description, Types::StrippedString).filled
|
15
|
+
|
16
|
+
required(:address).schema do
|
17
|
+
required(:municipality, Types::Municipality).filled(:municipality?)
|
18
|
+
required(:country_code, Types::Country).filled(:str?, :country_code?)
|
19
|
+
required(:street, Types::StrippedString).filled
|
20
|
+
required(:city, Types::StrippedString).filled
|
21
|
+
required(:zip, Types::Zip).filled(:str?, :zip?)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class Company < Model
|
26
|
+
def initialize(hash)
|
27
|
+
super(CompanySchema.call(hash))
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_h
|
31
|
+
hash = super
|
32
|
+
address = hash.fetch(:address)
|
33
|
+
hash[:address][:full_address] = [
|
34
|
+
address.fetch(:street),
|
35
|
+
address.fetch(:zip),
|
36
|
+
address.fetch(:city)
|
37
|
+
].join(', ')
|
38
|
+
hash[:cin_arbetsformedlingen] = cin_arbetsformedlingen(hash.fetch(:cin))
|
39
|
+
hash
|
40
|
+
end
|
41
|
+
|
42
|
+
# Formats a Company Identification Number the way Arbetsformedlingen likes it
|
43
|
+
def cin_arbetsformedlingen(cin)
|
44
|
+
String.new(cin.dup).
|
45
|
+
delete('-').
|
46
|
+
insert(6, '-').
|
47
|
+
insert(0, '46-')
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
|
3
|
+
module Arbetsformedlingen
|
4
|
+
DocumentSchema = Dry::Validation.Form do
|
5
|
+
configure do
|
6
|
+
config.type_specs = true
|
7
|
+
config.messages_file = File.expand_path('../../../../config/locales/errors.yml', __FILE__)
|
8
|
+
|
9
|
+
predicates(Predicates)
|
10
|
+
end
|
11
|
+
|
12
|
+
required(:id, Types::UUIDString).filled
|
13
|
+
required(:timestamp, :string).filled(:iso8601_date?)
|
14
|
+
required(:customer_id, Types::StrippedString).filled
|
15
|
+
required(:email, Types::StrippedString).filled(:str?, :email?)
|
16
|
+
end
|
17
|
+
|
18
|
+
class Document < Model
|
19
|
+
def initialize(hash)
|
20
|
+
data = {
|
21
|
+
id: SecureRandom.uuid,
|
22
|
+
timestamp: Time.now.utc.iso8601
|
23
|
+
}.merge!(hash)
|
24
|
+
|
25
|
+
super(DocumentSchema.call(data))
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module Arbetsformedlingen
|
2
|
+
module Predicates
|
3
|
+
include Dry::Logic::Predicates
|
4
|
+
|
5
|
+
predicate(:iso8601_date?) do |value|
|
6
|
+
begin
|
7
|
+
Time.iso8601(value)
|
8
|
+
rescue ArgumentError => _e
|
9
|
+
false
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
predicate(:yyyy_mm_dd?) do |value|
|
14
|
+
begin
|
15
|
+
Date.strptime(value, '%Y-%m-%d')
|
16
|
+
rescue ArgumentError => _e
|
17
|
+
false
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
predicate(:email?) do |value|
|
22
|
+
value.include?('@') && value.include?('.')
|
23
|
+
end
|
24
|
+
|
25
|
+
predicate(:currency?) do |value|
|
26
|
+
value.length == 3
|
27
|
+
end
|
28
|
+
|
29
|
+
predicate(:zip?) do |value|
|
30
|
+
value.length == 5
|
31
|
+
end
|
32
|
+
|
33
|
+
predicate(:cin?) do |value|
|
34
|
+
value.length == 10
|
35
|
+
end
|
36
|
+
|
37
|
+
predicate(:municipality?) do |value|
|
38
|
+
MunicipalityCode.valid?(value)
|
39
|
+
end
|
40
|
+
|
41
|
+
predicate(:salary_type?) do |value|
|
42
|
+
SalaryTypeCode.valid?(value)
|
43
|
+
end
|
44
|
+
|
45
|
+
predicate(:experience_required?) do |value|
|
46
|
+
ExperienceRequiredCode.valid?(value)
|
47
|
+
end
|
48
|
+
|
49
|
+
predicate(:country_code?) do |value|
|
50
|
+
CountryCode.valid?(value)
|
51
|
+
end
|
52
|
+
|
53
|
+
predicate(:drivers_license?) do |value|
|
54
|
+
DriversLicenseCode.valid?(value)
|
55
|
+
end
|
56
|
+
|
57
|
+
predicate(:ssyk_id?) do |value|
|
58
|
+
# TODO: Add proper validation
|
59
|
+
value
|
60
|
+
end
|
61
|
+
|
62
|
+
predicate(:url?) do |value|
|
63
|
+
value.include?('https://') || value.include?('http://')
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Arbetsformedlingen
|
2
|
+
module Types
|
3
|
+
include Dry::Types.module
|
4
|
+
|
5
|
+
StrippedString = Types::Strict::String.constructor { |value| value.to_s.strip }
|
6
|
+
UUIDString = Types::Strict::String.constrained(size: 36)
|
7
|
+
Currency = Types::Strict::String.constructor do |string|
|
8
|
+
string.strip.upcase if string
|
9
|
+
end
|
10
|
+
# Company Identification Number
|
11
|
+
CIN = Types::Strict::String.constructor do |string|
|
12
|
+
string.delete(' ').delete('-') if string
|
13
|
+
end
|
14
|
+
Zip = Types::Strict::String.constructor do |string|
|
15
|
+
string.delete(' ') if string
|
16
|
+
end
|
17
|
+
Municipality = Types::Strict::String.constructor do |string|
|
18
|
+
MunicipalityCode.to_code(string)
|
19
|
+
end
|
20
|
+
Country = Types::Strict::String.constructor do |string|
|
21
|
+
CountryCode.to_code(string)
|
22
|
+
end
|
23
|
+
PositionDuration = Types::Strict::Int
|
24
|
+
DriversLicense = Types::Strict::String.constructor do |value|
|
25
|
+
DriversLicenseCode.to_code(value)
|
26
|
+
end
|
27
|
+
SalaryType = Types::Strict::String.constructor do |value|
|
28
|
+
SalaryTypeCode.to_code(value)
|
29
|
+
end
|
30
|
+
ExperienceRequired = Types::Strict::String.constructor do |value|
|
31
|
+
ExperienceRequiredCode.to_code(value)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Arbetsformedlingen
|
2
|
+
class Model
|
3
|
+
def self.from_schema(schema)
|
4
|
+
new(schema)
|
5
|
+
end
|
6
|
+
|
7
|
+
attr_reader :schema
|
8
|
+
|
9
|
+
def initialize(schema)
|
10
|
+
@schema = schema
|
11
|
+
end
|
12
|
+
|
13
|
+
def valid?
|
14
|
+
@schema.success?
|
15
|
+
end
|
16
|
+
|
17
|
+
def errors
|
18
|
+
@schema.errors
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_h
|
22
|
+
@schema.to_h
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'builder'
|
2
|
+
|
3
|
+
module Arbetsformedlingen
|
4
|
+
PacketSchema = Dry::Validation.Form do
|
5
|
+
configure do
|
6
|
+
config.type_specs = true
|
7
|
+
config.messages_file = File.expand_path('../../../../config/locales/errors.yml', __FILE__)
|
8
|
+
|
9
|
+
predicates(Predicates)
|
10
|
+
end
|
11
|
+
|
12
|
+
required(:active, Types::Bool).filled
|
13
|
+
required(:job_id, Types::StrippedString).filled
|
14
|
+
required(:id, Types::StrippedString).filled
|
15
|
+
required(:number_to_fill, Types::Int).filled(gt?: 0)
|
16
|
+
required(:ssyk_id, Types::Int).filled(:ssyk_id?)
|
17
|
+
end
|
18
|
+
|
19
|
+
class Packet < Model
|
20
|
+
DEFAULT_PACKET_ID = 1
|
21
|
+
|
22
|
+
def initialize(attributes:, publication:, document:, position:)
|
23
|
+
hash = attributes
|
24
|
+
@publication = publication
|
25
|
+
@document = document
|
26
|
+
@position = position
|
27
|
+
id = hash.fetch(:id, DEFAULT_PACKET_ID)
|
28
|
+
super(PacketSchema.call(hash.merge(id: id)))
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_h
|
32
|
+
hash = super
|
33
|
+
hash[:publication] = @publication.to_h
|
34
|
+
hash[:document] = @document.to_h
|
35
|
+
hash[:position] = @position.to_h
|
36
|
+
hash
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Arbetsformedlingen
|
2
|
+
PositionSchema = Dry::Validation.Form do
|
3
|
+
configure do
|
4
|
+
config.type_specs = true
|
5
|
+
config.messages_file = File.expand_path('../../../../config/locales/errors.yml', __FILE__)
|
6
|
+
|
7
|
+
predicates(Predicates)
|
8
|
+
end
|
9
|
+
|
10
|
+
required(:title, Types::StrippedString).filled
|
11
|
+
required(:purpose, Types::StrippedString).filled
|
12
|
+
required(:address).schema do
|
13
|
+
required(:municipality, Types::Municipality).filled(:municipality?)
|
14
|
+
required(:country_code, Types::Country).filled(:str?, :country_code?)
|
15
|
+
|
16
|
+
optional(:street, Types::StrippedString).filled
|
17
|
+
optional(:city, Types::StrippedString).filled
|
18
|
+
optional(:zip, Types::Zip).filled(:str?, :zip?)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class Position < Model
|
23
|
+
def initialize(attributes:, company:, schedule:, salary:, qualifications:, application_method:)
|
24
|
+
hash = attributes
|
25
|
+
@company = company
|
26
|
+
@schedule = schedule
|
27
|
+
@salary = salary
|
28
|
+
@qualifications = qualifications
|
29
|
+
@application_method = application_method
|
30
|
+
|
31
|
+
super(PositionSchema.call(hash))
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_h
|
35
|
+
hash = super
|
36
|
+
hash[:company] = @company.to_h
|
37
|
+
hash[:schedule] = @schedule.to_h
|
38
|
+
hash[:salary] = @salary.to_h
|
39
|
+
hash[:qualifications] = @qualifications.map(&:to_h)
|
40
|
+
hash[:application_method] = @application_method.to_h
|
41
|
+
full_address = build_full_address(hash.fetch(:address))
|
42
|
+
hash[:address][:full_address] = full_address if full_address
|
43
|
+
hash
|
44
|
+
end
|
45
|
+
|
46
|
+
def build_full_address(address)
|
47
|
+
return unless address.key?(:street) || address.key?(:zip) || address.key?(:city)
|
48
|
+
|
49
|
+
[
|
50
|
+
address.fetch(:street),
|
51
|
+
address.fetch(:zip),
|
52
|
+
address[:city]
|
53
|
+
].compact.join(', ')
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Arbetsformedlingen
|
2
|
+
PublicationSchema = Dry::Validation.Form do
|
3
|
+
configure do
|
4
|
+
config.type_specs = true
|
5
|
+
config.messages_file = File.expand_path('../../../../config/locales/errors.yml', __FILE__)
|
6
|
+
|
7
|
+
predicates(Predicates)
|
8
|
+
end
|
9
|
+
|
10
|
+
required(:unpublish_at, :string).filled(:yyyy_mm_dd?)
|
11
|
+
required(:name, Types::StrippedString).filled
|
12
|
+
required(:email, Types::StrippedString).filled(:str?, :email?)
|
13
|
+
|
14
|
+
optional(:publish_at, :string).filled(:yyyy_mm_dd?)
|
15
|
+
end
|
16
|
+
|
17
|
+
class Publication < Model
|
18
|
+
def initialize(hash)
|
19
|
+
data = hash.dup
|
20
|
+
publish_date = data[:publish_at] || Time.now.utc
|
21
|
+
|
22
|
+
data[:publish_at] = publish_date.strftime('%Y-%m-%d')
|
23
|
+
data[:unpublish_at] = data[:unpublish_at]&.strftime('%Y-%m-%d')
|
24
|
+
|
25
|
+
# TODO: Validate that unpublish_at - publish_at is not greater that 180 days
|
26
|
+
|
27
|
+
super(PublicationSchema.call(data))
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Arbetsformedlingen
|
2
|
+
QualificationSchema = Dry::Validation.Form do
|
3
|
+
configure do
|
4
|
+
config.type_specs = true
|
5
|
+
config.messages_file = File.expand_path('../../../../config/locales/errors.yml', __FILE__)
|
6
|
+
|
7
|
+
predicates(Predicates)
|
8
|
+
end
|
9
|
+
|
10
|
+
required(:required, Types::Bool).filled
|
11
|
+
|
12
|
+
optional(:drivers_license, Types::DriversLicense).filled(:drivers_license?)
|
13
|
+
optional(:car, Types::Bool).filled(:bool?)
|
14
|
+
optional(:summary, Types::StrippedString).filled
|
15
|
+
optional(:experience, Types::ExperienceRequired).filled(:experience_required?)
|
16
|
+
end
|
17
|
+
|
18
|
+
class Qualification < Model
|
19
|
+
def initialize(hash)
|
20
|
+
super(QualificationSchema.call(hash))
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Arbetsformedlingen
|
2
|
+
SalarySchema = Dry::Validation.Form do
|
3
|
+
configure do
|
4
|
+
config.type_specs = true
|
5
|
+
config.messages_file = File.expand_path('../../../../config/locales/errors.yml', __FILE__)
|
6
|
+
|
7
|
+
predicates(Predicates)
|
8
|
+
end
|
9
|
+
|
10
|
+
required(:summary, Types::StrippedString).filled
|
11
|
+
required(:currency, Types::Currency).filled(:str?, :currency?)
|
12
|
+
required(:type, Types::SalaryType).filled(:str?, :salary_type?)
|
13
|
+
end
|
14
|
+
|
15
|
+
class Salary < Model
|
16
|
+
def initialize(hash)
|
17
|
+
super(SalarySchema.call(hash))
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|