cvrvaelger 0.1.0 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b4c8df3ad4e794628d2f3e5a0010b9a9aa4e35123a64370926196639cb119eb6
4
- data.tar.gz: 7395f85cc2c4bb3cf458b80d61da01602af672b893f2317e900e833257a64601
3
+ metadata.gz: 58d9cf95c7e30a7d2c52371a520ffde0fc0627e1da44f0c34117b7ec39d82dba
4
+ data.tar.gz: 9cba1d237b9cd246de41381661b80a3de974b742cf212875efda280c7b013329
5
5
  SHA512:
6
- metadata.gz: 3623ddc0be170cf6cdbed892a4ea9d18dee2958687945e23982b5271a19e45eb5c33e3c095c3e249177ae442d9c31b103e3f6739f4501b15532efd681c379c96
7
- data.tar.gz: c622efb3b0d2001065bc756baf0b1bc9be9b746a67875e553b7aa40b4006c682c02f555204095587c241ac7c2f13628ea47442af83f19c4a6150e713db1c85cd
6
+ metadata.gz: 581bd6cb8f37378492b3934a88bfd8c6b52f9b765cd2eb5409d8a2a8f8944792e3b117e68326563e37f769becc554044dc2785b775da4eaf1f823e1ecd8b6d8d
7
+ data.tar.gz: fbd748cc79bf6e771f1a106d65778103f295f89f41815597f0599b1323268b6e03456f9eb40c146006ee2152bb35572d616628f2a1eff6a7c5c4c30d975d52f3
data/.env.example CHANGED
@@ -5,3 +5,5 @@ CVRVAELGER_API_KEY=
5
5
  # DATAFORDELER_API_KEY=
6
6
  # Optional endpoint override (default https://graphql.datafordeler.dk/CVR/v2):
7
7
  # CVRVAELGER_BASE_URL=
8
+ # Optional Fildownload host (default https://api.datafordeler.dk):
9
+ # CVRVAELGER_FILE_DOWNLOAD_BASE_URL=
data/.rubocop.yml CHANGED
@@ -26,7 +26,7 @@ Metrics/MethodLength:
26
26
  - "test/**/*"
27
27
 
28
28
  Metrics/ClassLength:
29
- Max: 200
29
+ Max: 300
30
30
  Exclude:
31
31
  - "test/**/*"
32
32
 
@@ -52,4 +52,4 @@ Metrics/BlockLength:
52
52
  - "Rakefile"
53
53
 
54
54
  Minitest/MultipleAssertions:
55
- Max: 10
55
+ Max: 12
data/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## Unreleased
4
+
5
+ ## 0.2.0
6
+
7
+ - Fildownload client: `available_file_downloads`, `download_file`, `download_latest`
8
+ for CVR totalextract zips.
9
+ - CVR_Events pull/poll: `events` (cursor via `since_event_id` / `after`),
10
+ `virksomhed_by_row_id` / `navn_by_row_id` / `adressering_by_row_id`, and
11
+ `register_import_status` for incremental sync after a TotalDownload baseline.
12
+
3
13
  ## 0.1.0
4
14
 
5
15
  - Initial public release: Datafordeler CVR GraphQL v2 client (`lookup` / `search`).
data/README.md CHANGED
@@ -1,10 +1,13 @@
1
1
  # Cvrvaelger
2
2
 
3
3
  Ruby client for **Det Centrale Virksomhedsregister (CVR)** via
4
- [Datafordeler GraphQL](https://datafordeler.dk/dataoversigt/det-centrale-virksomhedsregister-cvr/cvr-graphql/).
4
+ [Datafordeler GraphQL](https://datafordeler.dk/dataoversigt/det-centrale-virksomhedsregister-cvr/cvr-graphql/)
5
+ and [Fildownload](https://datafordeler.dk/dataoversigt/det-centrale-virksomhedsregister-cvr/cvr-fildownload/).
5
6
 
6
7
  Denmark only — look up an 8-digit CVR or registered company name and return
7
- picker-shaped fields (name, light address, status).
8
+ picker-shaped fields (name, light address, status). Also supports Datafordeler
9
+ CVR Fildownload (list/download totalextract zips) and `CVR_Events` pull/poll for
10
+ incremental sync.
8
11
 
9
12
  ## Install
10
13
 
@@ -44,8 +47,35 @@ client.search("lego") # => []
44
47
 
45
48
  # Optional bitemporal cut-off (GraphQL `virkningstid`, default: now)
46
49
  client.lookup("47458714", at: Time.utc(2020, 1, 1))
50
+
51
+ # Fildownload: list metadata and stream a zip to disk
52
+ files = client.available_file_downloads(entity: "Navn", type_of_data: "Current", format: "json")
53
+ client.download_latest(entity: "Virksomhed", type: "current", format: "json", to: "tmp/virksomhed.zip")
54
+ client.download_file(files.first.file_name, to: "tmp/navn.zip")
55
+
56
+ # Incremental sync after a TotalDownload baseline (persist last event_id yourself).
57
+ # See Datafordeler transitions guide for entitetsbaserede hændelser.
58
+ status = client.register_import_status
59
+ page = client.events(since_event_id: last_id, first: 100)
60
+ page.events.each do |event|
61
+ # from_failed_import marks duplicate catch-up events — handle in the host app.
62
+ next if event.event_action == "d" # delete local row for event.object_datafordeler_row_id
63
+
64
+ case event.entity_name
65
+ when "Virksomhed"
66
+ client.virksomhed_by_row_id(event.object_datafordeler_row_id) # => Hash or nil
67
+ when "Navn"
68
+ client.navn_by_row_id(event.object_datafordeler_row_id)
69
+ when "Adressering"
70
+ client.adressering_by_row_id(event.object_datafordeler_row_id)
71
+ # other entities: query GraphQL yourself
72
+ end
73
+ end
74
+ page = client.events(since_event_id: last_id, first: 100, after: page.end_cursor) if page.has_next_page
47
75
  ```
48
76
 
77
+ SSE / GraphQL subscriptions are not wrapped here; poll with `events` instead.
78
+
49
79
  Environment variables:
50
80
 
51
81
  | Variable | Purpose |
@@ -53,6 +83,7 @@ Environment variables:
53
83
  | `CVRVAELGER_API_KEY` | Datafordeler API key (preferred) |
54
84
  | `DATAFORDELER_API_KEY` | Accepted alias |
55
85
  | `CVRVAELGER_BASE_URL` | Override GraphQL endpoint (default `https://graphql.datafordeler.dk/CVR/v2`) |
86
+ | `CVRVAELGER_FILE_DOWNLOAD_BASE_URL` | Override Fildownload host (default `https://api.datafordeler.dk`) |
56
87
 
57
88
  ## API key
58
89
 
@@ -75,6 +106,8 @@ To refresh cassettes locally:
75
106
  ```sh
76
107
  VCR_RECORD=all bundle exec rake test TEST=test/client_live_test.rb
77
108
  VCR_RECORD=all bundle exec rake test TEST=test/graphql_features_test.rb
109
+ VCR_RECORD=all bundle exec rake test TEST=test/file_download_live_test.rb
110
+ VCR_RECORD=all bundle exec rake test TEST=test/client_events_live_test.rb
78
111
  ```
79
112
 
80
113
  Never hand-write cassette response bodies.
data/cvrvaelger.gemspec CHANGED
@@ -10,9 +10,9 @@ Gem::Specification.new do |spec|
10
10
 
11
11
  spec.summary = "Ruby client for Danish CVR company lookup via Datafordeler GraphQL."
12
12
  spec.description = <<~DESC
13
- HTTP client for Det Centrale Virksomhedsregister (CVR) on Datafordeler GraphQL:
14
- verify an 8-digit CVR number or exact registered name and return the company
15
- name plus light address fields for picker UX. No Rails dependency.
13
+ HTTP client for Det Centrale Virksomhedsregister (CVR) on Datafordeler:
14
+ GraphQL lookup/search for picker UX, Fildownload helpers for totalextract
15
+ zips, and CVR_Events pull/poll for incremental sync. No Rails dependency.
16
16
  DESC
17
17
  spec.homepage = "https://github.com/bergholdt/cvrvaelger"
18
18
  spec.license = "MIT"
@@ -5,15 +5,24 @@ require "json"
5
5
  require "uri"
6
6
 
7
7
  require_relative "company"
8
+ require_relative "file_download"
9
+ require_relative "event"
10
+ require_relative "client_events"
8
11
 
9
12
  module Cvrvaelger
10
- # HTTP client for Det Centrale Virksomhedsregister via Datafordeler GraphQL.
13
+ # HTTP client for Det Centrale Virksomhedsregister via Datafordeler GraphQL
14
+ # and Fildownload.
11
15
  #
12
- # Official endpoint: https://graphql.datafordeler.dk/CVR/v2
16
+ # GraphQL: https://graphql.datafordeler.dk/CVR/v2
17
+ # Files: https://api.datafordeler.dk/FileDownloads/...
13
18
  # Auth: free API key from Datafordeler Administration (query param `apiKey`).
14
19
  # No demo/default secret — configure CVRVAELGER_API_KEY or pass api_key:.
20
+ #
15
21
  class Client
22
+ include ClientEvents
23
+
16
24
  DEFAULT_BASE_URL = "https://graphql.datafordeler.dk/CVR/v2"
25
+ DEFAULT_FILE_DOWNLOAD_BASE_URL = "https://api.datafordeler.dk"
17
26
 
18
27
  # Datafordeler allows one root field per query. Name match is exact (`eq`);
19
28
  # there is no contains/fuzzy operator. `virkningstid` selects the bitemporal row.
@@ -63,12 +72,15 @@ module Cvrvaelger
63
72
  }
64
73
  GRAPHQL
65
74
 
66
- attr_reader :base_url, :api_key
75
+ attr_reader :base_url, :file_download_base_url, :api_key
67
76
 
68
- def initialize(api_key: nil, base_url: nil, http: nil)
77
+ def initialize(api_key: nil, base_url: nil, file_download_base_url: nil, http: nil)
69
78
  @api_key = present_string(api_key) || present_string(ENV.fetch("CVRVAELGER_API_KEY", nil)) ||
70
79
  present_string(ENV.fetch("DATAFORDELER_API_KEY", nil))
71
80
  @base_url = present_string(base_url) || present_string(ENV.fetch("CVRVAELGER_BASE_URL", nil)) || DEFAULT_BASE_URL
81
+ @file_download_base_url = present_string(file_download_base_url) ||
82
+ present_string(ENV.fetch("CVRVAELGER_FILE_DOWNLOAD_BASE_URL", nil)) ||
83
+ DEFAULT_FILE_DOWNLOAD_BASE_URL
72
84
  @http = http
73
85
  end
74
86
 
@@ -106,6 +118,51 @@ module Cvrvaelger
106
118
  end
107
119
  end
108
120
 
121
+ # List available CVR totaldownload zips (metadata only).
122
+ # Optional filters: entity ("Navn"), type_of_data ("Current"), format ("json").
123
+ def available_file_downloads(entity: nil, type_of_data: nil, format: nil)
124
+ ensure_api_key!
125
+ payload = get_json("/FileDownloads/GetAvailableFileDownloads", Register: "CVR")
126
+ rows = Array(payload["availableFileDownloads"])
127
+ rows.filter_map do |row|
128
+ next unless row.is_a?(Hash)
129
+
130
+ file = FileDownload.from_node(row)
131
+ next if entity && !entity.to_s.casecmp?(file.entity_name.to_s)
132
+ next if type_of_data && !type_of_data.to_s.casecmp?(file.type_of_data.to_s)
133
+ next if format && !format.to_s.casecmp?(file.contained_file_format.to_s)
134
+
135
+ file
136
+ end
137
+ end
138
+
139
+ # Download a named zip to `to` (filesystem path). Returns the path.
140
+ def download_file(file_name, to:)
141
+ ensure_api_key!
142
+ raise ArgumentError, "to: path is required" if blank?(to)
143
+ raise ArgumentError, "file_name is required" if blank?(file_name)
144
+
145
+ get_file({ Filename: file_name.to_s }, to: to.to_s)
146
+ end
147
+
148
+ # Download the latest totaldownload for an entity (e.g. Navn / Virksomhed).
149
+ # `type` is Current/Temporal/Bitemporal; `format` is json/csv.
150
+ def download_latest(entity:, to:, type: "current", format: "json")
151
+ ensure_api_key!
152
+ raise ArgumentError, "entity is required" if blank?(entity)
153
+ raise ArgumentError, "to: path is required" if blank?(to)
154
+
155
+ get_file(
156
+ {
157
+ Register: "CVR",
158
+ LatestTotalForEntity: entity.to_s,
159
+ type: type.to_s.downcase,
160
+ format: format.to_s.upcase
161
+ },
162
+ to: to.to_s
163
+ )
164
+ end
165
+
109
166
  private
110
167
 
111
168
  attr_reader :http
@@ -180,10 +237,7 @@ module Cvrvaelger
180
237
  end
181
238
 
182
239
  def post_graphql(query, **variables)
183
- uri = URI(base_url)
184
- query_params = URI.decode_www_form(uri.query.to_s).to_h
185
- query_params["apiKey"] = api_key
186
- uri.query = URI.encode_www_form(query_params)
240
+ uri = with_api_key(URI(base_url))
187
241
 
188
242
  body = JSON.generate(query: query, variables: variables)
189
243
  request = Net::HTTP::Post.new(uri)
@@ -209,6 +263,59 @@ module Cvrvaelger
209
263
  payload
210
264
  end
211
265
 
266
+ def get_json(path, **params)
267
+ uri = with_api_key(URI.join("#{file_download_base_url}/", path.delete_prefix("/")), **params)
268
+ request = Net::HTTP::Get.new(uri)
269
+ request["Accept"] = "application/json"
270
+ request["User-Agent"] = "Cvrvaelger/#{VERSION}"
271
+
272
+ response = perform(uri, request)
273
+ raise ProviderError, "Datafordeler CVR error (#{response.code})" unless response.is_a?(Net::HTTPSuccess)
274
+
275
+ begin
276
+ JSON.parse(response.body)
277
+ rescue JSON::ParserError
278
+ raise ProviderError, "Datafordeler CVR returned invalid JSON"
279
+ end
280
+ end
281
+
282
+ def get_file(params, to:)
283
+ uri = with_api_key(URI.join("#{file_download_base_url}/", "FileDownloads/GetFile"), **params)
284
+ request = Net::HTTP::Get.new(uri)
285
+ request["Accept"] = "application/zip, application/octet-stream, */*"
286
+ request["User-Agent"] = "Cvrvaelger/#{VERSION}"
287
+
288
+ if http
289
+ response = http.call(uri, request)
290
+ raise ProviderError, "Datafordeler CVR error (#{response.code})" unless response.is_a?(Net::HTTPSuccess)
291
+
292
+ File.binwrite(to, response.body)
293
+ return to
294
+ end
295
+
296
+ File.open(to, "wb") do |io|
297
+ Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https", open_timeout: 10,
298
+ read_timeout: 600) do |client|
299
+ client.request(request) do |response|
300
+ raise ProviderError, "Datafordeler CVR error (#{response.code})" unless response.is_a?(Net::HTTPSuccess)
301
+
302
+ response.read_body { |chunk| io.write(chunk) }
303
+ end
304
+ end
305
+ end
306
+ to
307
+ rescue Net::OpenTimeout, Net::ReadTimeout, Errno::ECONNREFUSED, SocketError => e
308
+ raise ProviderError, "Datafordeler CVR unreachable (#{e.class})"
309
+ end
310
+
311
+ def with_api_key(uri, **params)
312
+ query_params = URI.decode_www_form(uri.query.to_s).to_h
313
+ params.each { |key, value| query_params[key.to_s] = value unless value.nil? }
314
+ query_params["apiKey"] = api_key
315
+ uri.query = URI.encode_www_form(query_params)
316
+ uri
317
+ end
318
+
212
319
  def perform(uri, request)
213
320
  return http.call(uri, request) if http
214
321
 
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cvrvaelger
4
+ # Pull/poll helpers for CVR_Events and row-id entity fetch (incremental sync).
5
+ module ClientEvents
6
+ EVENT_NODE_FIELDS = <<~GRAPHQL
7
+ eventid
8
+ entityname
9
+ eventaction
10
+ fromfailedimport
11
+ object_id
12
+ object_datafordelerRowId
13
+ datafordelerOpdateringstid
14
+ datafordelerRegisterImportSequenceNumber
15
+ GRAPHQL
16
+
17
+ EVENTS_QUERY = <<~GRAPHQL.freeze
18
+ query Events($first: Int!, $after: String) {
19
+ CVR_Events(first: $first, after: $after) {
20
+ pageInfo { hasNextPage endCursor }
21
+ nodes {
22
+ #{EVENT_NODE_FIELDS}
23
+ }
24
+ }
25
+ }
26
+ GRAPHQL
27
+
28
+ EVENTS_SINCE_QUERY = <<~GRAPHQL.freeze
29
+ query EventsSince($first: Int!, $after: String, $where: CVR_EventsFilterInput) {
30
+ CVR_Events(first: $first, after: $after, where: $where) {
31
+ pageInfo { hasNextPage endCursor }
32
+ nodes {
33
+ #{EVENT_NODE_FIELDS}
34
+ }
35
+ }
36
+ }
37
+ GRAPHQL
38
+
39
+ VIRKSOMHED_BY_ROW_ID_QUERY = <<~GRAPHQL
40
+ query VirksomhedByRowId($rowId: UUID!, $at: DafDateTime!) {
41
+ CVR_Virksomhed(first: 1, virkningstid: $at, where: { datafordelerRowId: { eq: $rowId } }) {
42
+ nodes { id CVRNummer status datafordelerRowId }
43
+ }
44
+ }
45
+ GRAPHQL
46
+
47
+ NAVN_BY_ROW_ID_QUERY = <<~GRAPHQL
48
+ query NavnByRowId($rowId: UUID!, $at: DafDateTime!) {
49
+ CVR_Navn(first: 1, virkningstid: $at, where: { datafordelerRowId: { eq: $rowId } }) {
50
+ nodes { CVREnhedsId vaerdi datafordelerRowId }
51
+ }
52
+ }
53
+ GRAPHQL
54
+
55
+ ADRESSERING_BY_ROW_ID_QUERY = <<~GRAPHQL
56
+ query AdresseringByRowId($rowId: UUID!, $at: DafDateTime!) {
57
+ CVR_Adressering(first: 1, virkningstid: $at, where: { datafordelerRowId: { eq: $rowId } }) {
58
+ nodes {
59
+ CVREnhedsId
60
+ AdresseringAnvendelse
61
+ CVRAdresse_vejnavn
62
+ CVRAdresse_husnummerFra
63
+ CVRAdresse_postnummer
64
+ CVRAdresse_postdistrikt
65
+ datafordelerRowId
66
+ }
67
+ }
68
+ }
69
+ GRAPHQL
70
+
71
+ REGISTER_IMPORT_STATUS_QUERY = <<~GRAPHQL
72
+ query RegisterImportStatus {
73
+ DAF_RegisterImportStatus { lastEventId lastSequenceNumber lastUpdated }
74
+ }
75
+ GRAPHQL
76
+
77
+ # Page CVR_Events since a durable cursor (`eventid`).
78
+ # `since_event_id:` uses `gt` (exclusive); `inclusive: true` uses `gte`.
79
+ def events(since_event_id: nil, first: 100, after: nil, inclusive: false)
80
+ ensure_api_key!
81
+ variables = { first: first.to_i, after: after }
82
+ if since_event_id
83
+ op = inclusive ? "gte" : "gt"
84
+ variables[:where] = { "eventid" => { op => since_event_id.to_i } }
85
+ payload = post_graphql(EVENTS_SINCE_QUERY, **variables)
86
+ else
87
+ payload = post_graphql(EVENTS_QUERY, **variables)
88
+ end
89
+
90
+ EventPage.from_connection(payload.dig("data", "CVR_Events"))
91
+ end
92
+
93
+ # Fetch a Virksomhed GraphQL node by datafordelerRowId, or nil.
94
+ def virksomhed_by_row_id(row_id, at: Time.now)
95
+ entity_by_row_id(VIRKSOMHED_BY_ROW_ID_QUERY, row_id, at: at)
96
+ end
97
+
98
+ # Fetch a Navn GraphQL node by datafordelerRowId, or nil.
99
+ def navn_by_row_id(row_id, at: Time.now)
100
+ entity_by_row_id(NAVN_BY_ROW_ID_QUERY, row_id, at: at)
101
+ end
102
+
103
+ # Fetch an Adressering GraphQL node by datafordelerRowId, or nil.
104
+ def adressering_by_row_id(row_id, at: Time.now)
105
+ entity_by_row_id(ADRESSERING_BY_ROW_ID_QUERY, row_id, at: at)
106
+ end
107
+
108
+ # Latest import watermark from Datafordeler (bootstrap / catch-up).
109
+ def register_import_status
110
+ ensure_api_key!
111
+ payload = post_graphql(REGISTER_IMPORT_STATUS_QUERY)
112
+ status = payload.dig("data", "DAF_RegisterImportStatus")
113
+ status.is_a?(Hash) ? status : {}
114
+ end
115
+
116
+ private
117
+
118
+ def entity_by_row_id(query, row_id, at:)
119
+ ensure_api_key!
120
+ raise ArgumentError, "row_id is required" if blank?(row_id)
121
+
122
+ payload = post_graphql(query, rowId: row_id.to_s, at: format_time(at))
123
+ node = first_node(payload)
124
+ node.is_a?(Hash) ? node : nil
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cvrvaelger
4
+ # One CVR_Events row from Datafordeler GraphQL (pull/poll sync cursor).
5
+ Event = Data.define(
6
+ :event_id,
7
+ :entity_name,
8
+ :event_action,
9
+ :from_failed_import,
10
+ :object_entity_id,
11
+ :object_datafordeler_row_id,
12
+ :datafordeler_opdateringstid,
13
+ :datafordeler_register_import_sequence_number
14
+ ) do
15
+ def self.from_node(node)
16
+ new(
17
+ event_id: node["eventid"],
18
+ entity_name: present_string(node["entityname"]),
19
+ event_action: present_string(node["eventaction"]),
20
+ from_failed_import: node["fromfailedimport"],
21
+ # GraphQL field object_id — not Ruby Object#object_id.
22
+ object_entity_id: present_string(node["object_id"]),
23
+ object_datafordeler_row_id: present_string(node["object_datafordelerRowId"]),
24
+ datafordeler_opdateringstid: present_string(node["datafordelerOpdateringstid"]),
25
+ datafordeler_register_import_sequence_number: node["datafordelerRegisterImportSequenceNumber"]
26
+ )
27
+ end
28
+
29
+ def self.present_string(value)
30
+ return nil if value.nil?
31
+
32
+ string = value.to_s.strip
33
+ string.empty? ? nil : string
34
+ end
35
+
36
+ private_class_method :present_string
37
+ end
38
+
39
+ # One page of CVR_Events plus GraphQL connection cursors.
40
+ EventPage = Data.define(:events, :has_next_page, :end_cursor) do
41
+ def self.from_connection(connection)
42
+ connection = {} unless connection.is_a?(Hash)
43
+ page_info = connection["pageInfo"]
44
+ page_info = {} unless page_info.is_a?(Hash)
45
+
46
+ new(
47
+ events: Array(connection["nodes"]).grep(Hash).map { |node| Event.from_node(node) },
48
+ has_next_page: page_info["hasNextPage"] == true,
49
+ end_cursor: present_string(page_info["endCursor"])
50
+ )
51
+ end
52
+
53
+ def self.present_string(value)
54
+ return nil if value.nil?
55
+
56
+ string = value.to_s.strip
57
+ string.empty? ? nil : string
58
+ end
59
+
60
+ private_class_method :present_string
61
+ end
62
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cvrvaelger
4
+ # Metadata for a Datafordeler CVR totaldownload zip (no local unzipping).
5
+ FileDownload = Data.define(
6
+ :file_name,
7
+ :register,
8
+ :entity_name,
9
+ :type_of_download,
10
+ :type_of_data,
11
+ :contained_file_format,
12
+ :output_file_format,
13
+ :generation_number,
14
+ :file_size_in_bytes,
15
+ :md5_hash,
16
+ :generation_time,
17
+ :expiration_date
18
+ ) do
19
+ def self.from_node(node)
20
+ new(
21
+ file_name: present_string(node["fileName"]),
22
+ register: present_string(node["register"]),
23
+ entity_name: present_string(node["entityName"]),
24
+ type_of_download: present_string(node["typeOfDownload"]),
25
+ type_of_data: present_string(node["typeOfData"]),
26
+ contained_file_format: present_string(node["containedFileFormat"]),
27
+ output_file_format: present_string(node["outputFileFormat"]),
28
+ generation_number: node["generationNumber"],
29
+ file_size_in_bytes: node["fileSizeInBytes"],
30
+ md5_hash: present_string(node["md5Hash"]),
31
+ generation_time: present_string(node["generationTime"]),
32
+ expiration_date: present_string(node["expirationDate"])
33
+ )
34
+ end
35
+
36
+ def self.present_string(value)
37
+ return nil if value.nil?
38
+
39
+ string = value.to_s.strip
40
+ string.empty? ? nil : string
41
+ end
42
+
43
+ private_class_method :present_string
44
+ end
45
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Cvrvaelger
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
data/lib/cvrvaelger.rb CHANGED
@@ -3,9 +3,12 @@
3
3
  require_relative "cvrvaelger/version"
4
4
  require_relative "cvrvaelger/error"
5
5
  require_relative "cvrvaelger/company"
6
+ require_relative "cvrvaelger/file_download"
7
+ require_relative "cvrvaelger/event"
6
8
  require_relative "cvrvaelger/client"
7
9
 
8
- # Ruby client for Det Centrale Virksomhedsregister (CVR) via Datafordeler GraphQL.
10
+ # Ruby client for Det Centrale Virksomhedsregister (CVR) via Datafordeler
11
+ # GraphQL and Fildownload.
9
12
  #
10
13
  # Denmark only. Rails controllers, Stimulus pickers, and persistence belong in the
11
14
  # host application (same split as adressevaelger).
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cvrvaelger
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rasmus Bergholdt
@@ -10,9 +10,9 @@ cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies: []
12
12
  description: |
13
- HTTP client for Det Centrale Virksomhedsregister (CVR) on Datafordeler GraphQL:
14
- verify an 8-digit CVR number or exact registered name and return the company
15
- name plus light address fields for picker UX. No Rails dependency.
13
+ HTTP client for Det Centrale Virksomhedsregister (CVR) on Datafordeler:
14
+ GraphQL lookup/search for picker UX, Fildownload helpers for totalextract
15
+ zips, and CVR_Events pull/poll for incremental sync. No Rails dependency.
16
16
  email:
17
17
  - rasmus.bergholdt@gmail.com
18
18
  executables: []
@@ -27,16 +27,19 @@ files:
27
27
  - cvrvaelger.gemspec
28
28
  - lib/cvrvaelger.rb
29
29
  - lib/cvrvaelger/client.rb
30
+ - lib/cvrvaelger/client_events.rb
30
31
  - lib/cvrvaelger/company.rb
31
32
  - lib/cvrvaelger/error.rb
33
+ - lib/cvrvaelger/event.rb
34
+ - lib/cvrvaelger/file_download.rb
32
35
  - lib/cvrvaelger/version.rb
33
36
  homepage: https://github.com/bergholdt/cvrvaelger
34
37
  licenses:
35
38
  - MIT
36
39
  metadata:
37
40
  homepage_uri: https://github.com/bergholdt/cvrvaelger
38
- source_code_uri: https://github.com/bergholdt/cvrvaelger/tree/v0.1.0
39
- changelog_uri: https://github.com/bergholdt/cvrvaelger/blob/v0.1.0/CHANGELOG.md
41
+ source_code_uri: https://github.com/bergholdt/cvrvaelger/tree/v0.2.0
42
+ changelog_uri: https://github.com/bergholdt/cvrvaelger/blob/v0.2.0/CHANGELOG.md
40
43
  bug_tracker_uri: https://github.com/bergholdt/cvrvaelger/issues
41
44
  rubygems_mfa_required: 'true'
42
45
  rdoc_options: []